Sticker creator updates: new 200 sticker max, WebP supported
This commit is contained in:
parent
94f52edbd0
commit
fe65fd3eaa
15 changed files with 148 additions and 31 deletions
|
@ -2212,7 +2212,7 @@
|
||||||
},
|
},
|
||||||
"StickerCreator--DropStage--help": {
|
"StickerCreator--DropStage--help": {
|
||||||
"message":
|
"message":
|
||||||
"Stickers must be in PNG format with a transparent background and 512x512 pixels. Recommended margin is 16px.",
|
"Stickers must be in PNG or WebP format with a transparent background and 512x512 pixels. Recommended margin is 16px.",
|
||||||
"description": "Help text for the drop stage of the sticker creator"
|
"description": "Help text for the drop stage of the sticker creator"
|
||||||
},
|
},
|
||||||
"StickerCreator--DropStage--showMargins": {
|
"StickerCreator--DropStage--showMargins": {
|
||||||
|
@ -2220,6 +2220,16 @@
|
||||||
"description":
|
"description":
|
||||||
"Text for the show margins toggle on the drop stage of the sticker creator"
|
"Text for the show margins toggle on the drop stage of the sticker creator"
|
||||||
},
|
},
|
||||||
|
"StickerCreator--DropStage--addMore": {
|
||||||
|
"message": "Add $count$ or more",
|
||||||
|
"description": "Text to show user how many more stickers they must add",
|
||||||
|
"placeholders": {
|
||||||
|
"hashtag": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"StickerCreator--EmojiStage--title": {
|
"StickerCreator--EmojiStage--title": {
|
||||||
"message": "Add an emoji to each sticker",
|
"message": "Add an emoji to each sticker",
|
||||||
"description": "Title for the drop stage of the sticker creator"
|
"description": "Title for the drop stage of the sticker creator"
|
||||||
|
@ -2340,6 +2350,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"StickerCreator--Toasts--animated": {
|
||||||
|
"message": "Animated stickers are not currently supported",
|
||||||
|
"description":
|
||||||
|
"Text for the toast when an image that is animated was dropped"
|
||||||
|
},
|
||||||
"StickerCreator--Toasts--tooLarge": {
|
"StickerCreator--Toasts--tooLarge": {
|
||||||
"message": "Dropped image is too large",
|
"message": "Dropped image is too large",
|
||||||
"description":
|
"description":
|
||||||
|
|
|
@ -6,7 +6,7 @@ const { Agent } = require('https');
|
||||||
const is = require('@sindresorhus/is');
|
const is = require('@sindresorhus/is');
|
||||||
const { redactPackId } = require('./stickers');
|
const { redactPackId } = require('./stickers');
|
||||||
|
|
||||||
/* global Signal, Buffer, setTimeout, log, _, getGuid */
|
/* global Signal, Buffer, setTimeout, log, _, getGuid, PQueue */
|
||||||
|
|
||||||
/* eslint-disable more/no-then, no-bitwise, no-nested-ternary */
|
/* eslint-disable more/no-then, no-bitwise, no-nested-ternary */
|
||||||
|
|
||||||
|
@ -936,7 +936,6 @@ function initialize({
|
||||||
// This is going to the CDN, not the service, so we use _outerAjax
|
// This is going to the CDN, not the service, so we use _outerAjax
|
||||||
await _outerAjax(`${cdnUrl}/`, {
|
await _outerAjax(`${cdnUrl}/`, {
|
||||||
...manifestParams,
|
...manifestParams,
|
||||||
key: 'stickers/asdfasdf/manifest.proto',
|
|
||||||
certificateAuthority,
|
certificateAuthority,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
|
@ -945,17 +944,20 @@ function initialize({
|
||||||
});
|
});
|
||||||
|
|
||||||
// Upload stickers
|
// Upload stickers
|
||||||
|
const queue = new PQueue({ concurrency: 3 });
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
stickers.map(async (s, id) => {
|
stickers.map(async (s, id) => {
|
||||||
const stickerParams = makePutParams(s, encryptedStickers[id]);
|
const stickerParams = makePutParams(s, encryptedStickers[id]);
|
||||||
await _outerAjax(`${cdnUrl}/`, {
|
await queue.add(async () =>
|
||||||
...stickerParams,
|
_outerAjax(`${cdnUrl}/`, {
|
||||||
certificateAuthority,
|
...stickerParams,
|
||||||
proxyUrl,
|
certificateAuthority,
|
||||||
timeout: 0,
|
proxyUrl,
|
||||||
type: 'POST',
|
timeout: 0,
|
||||||
processData: false,
|
type: 'POST',
|
||||||
});
|
processData: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress();
|
onProgress();
|
||||||
}
|
}
|
||||||
|
|
15
package.json
15
package.json
|
@ -131,7 +131,7 @@
|
||||||
"testcheck": "1.0.0-rc.2",
|
"testcheck": "1.0.0-rc.2",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
"to-arraybuffer": "1.0.1",
|
"to-arraybuffer": "1.0.1",
|
||||||
"typeface-inter": "^3.10.0",
|
"typeface-inter": "3.10.0",
|
||||||
"underscore": "1.9.0",
|
"underscore": "1.9.0",
|
||||||
"uuid": "3.3.2",
|
"uuid": "3.3.2",
|
||||||
"websocket": "1.0.28"
|
"websocket": "1.0.28"
|
||||||
|
@ -141,7 +141,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.5.5",
|
"@babel/core": "7.5.5",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
"@babel/plugin-proposal-class-properties": "7.7.4",
|
||||||
"@babel/preset-react": "7.0.0",
|
"@babel/preset-react": "7.0.0",
|
||||||
"@babel/preset-typescript": "7.3.3",
|
"@babel/preset-typescript": "7.3.3",
|
||||||
"@storybook/addon-actions": "5.1.11",
|
"@storybook/addon-actions": "5.1.11",
|
||||||
|
@ -177,6 +177,7 @@
|
||||||
"@types/redux-logger": "3.0.7",
|
"@types/redux-logger": "3.0.7",
|
||||||
"@types/rimraf": "2.0.2",
|
"@types/rimraf": "2.0.2",
|
||||||
"@types/semver": "5.5.0",
|
"@types/semver": "5.5.0",
|
||||||
|
"@types/sharp": "0.23.1",
|
||||||
"@types/sinon": "4.3.1",
|
"@types/sinon": "4.3.1",
|
||||||
"@types/storybook__addon-actions": "3.4.3",
|
"@types/storybook__addon-actions": "3.4.3",
|
||||||
"@types/storybook__addon-knobs": "5.0.3",
|
"@types/storybook__addon-knobs": "5.0.3",
|
||||||
|
@ -265,7 +266,10 @@
|
||||||
"bundleVersion": "1"
|
"bundleVersion": "1"
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"asarUnpack": ["node_modules/spellchecker/vendor/hunspell_dictionaries", "node_modules/sharp"],
|
"asarUnpack": [
|
||||||
|
"node_modules/spellchecker/vendor/hunspell_dictionaries",
|
||||||
|
"node_modules/sharp"
|
||||||
|
],
|
||||||
"artifactName": "${name}-win-${version}.${ext}",
|
"artifactName": "${name}-win-${version}.${ext}",
|
||||||
"certificateSubjectName": "Signal",
|
"certificateSubjectName": "Signal",
|
||||||
"publisherName": "Signal (Quiet Riddle Ventures, LLC)",
|
"publisherName": "Signal (Quiet Riddle Ventures, LLC)",
|
||||||
|
@ -298,7 +302,10 @@
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"StartupWMClass": "Signal"
|
"StartupWMClass": "Signal"
|
||||||
},
|
},
|
||||||
"asarUnpack": ["node_modules/spellchecker/vendor/hunspell_dictionaries", "node_modules/sharp"],
|
"asarUnpack": [
|
||||||
|
"node_modules/spellchecker/vendor/hunspell_dictionaries",
|
||||||
|
"node_modules/sharp"
|
||||||
|
],
|
||||||
"target": [
|
"target": [
|
||||||
"deb"
|
"deb"
|
||||||
],
|
],
|
||||||
|
|
|
@ -29,6 +29,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import * as styles from './AppStage.scss';
|
||||||
import { history } from '../../util/history';
|
import { history } from '../../util/history';
|
||||||
import { Button } from '../../elements/Button';
|
import { Button } from '../../elements/Button';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
import { Text } from '../../elements/Typography';
|
||||||
|
import { stickersDuck } from '../../store';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
readonly children: React.ReactNode;
|
readonly children: React.ReactNode;
|
||||||
|
@ -56,6 +58,8 @@ export const AppStage = (props: Props) => {
|
||||||
[prev]
|
[prev]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const addMoreCount = stickersDuck.useAddMoreCount();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className={getClassName(props)}>{children}</main>
|
<main className={getClassName(props)}>{children}</main>
|
||||||
|
@ -65,6 +69,11 @@ export const AppStage = (props: Props) => {
|
||||||
{prevText || i18n('StickerCreator--AppStage--prev')}
|
{prevText || i18n('StickerCreator--AppStage--prev')}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
{addMoreCount > 0 ? (
|
||||||
|
<Text secondary={true}>
|
||||||
|
{i18n('StickerCreator--DropStage--addMore', [addMoreCount])}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
{next || onNext ? (
|
{next || onNext ? (
|
||||||
<Button
|
<Button
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
|
|
|
@ -10,16 +10,28 @@ import { stickersDuck } from '../../store';
|
||||||
import { useI18n } from '../../util/i18n';
|
import { useI18n } from '../../util/i18n';
|
||||||
|
|
||||||
const renderToaster = ({
|
const renderToaster = ({
|
||||||
|
hasAnimated,
|
||||||
hasTooLarge,
|
hasTooLarge,
|
||||||
numberAdded,
|
numberAdded,
|
||||||
resetStatus,
|
resetStatus,
|
||||||
i18n,
|
i18n,
|
||||||
}: {
|
}: {
|
||||||
|
hasAnimated: boolean;
|
||||||
hasTooLarge: boolean;
|
hasTooLarge: boolean;
|
||||||
numberAdded: number;
|
numberAdded: number;
|
||||||
resetStatus: () => unknown;
|
resetStatus: () => unknown;
|
||||||
i18n: ReturnType<typeof useI18n>;
|
i18n: ReturnType<typeof useI18n>;
|
||||||
}) => {
|
}) => {
|
||||||
|
if (hasAnimated) {
|
||||||
|
return (
|
||||||
|
<div className={appStyles.toaster}>
|
||||||
|
<Toast onClick={resetStatus}>
|
||||||
|
{i18n('StickerCreator--Toasts--animated')}
|
||||||
|
</Toast>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (hasTooLarge) {
|
if (hasTooLarge) {
|
||||||
return (
|
return (
|
||||||
<div className={appStyles.toaster}>
|
<div className={appStyles.toaster}>
|
||||||
|
@ -48,6 +60,7 @@ export const DropStage = () => {
|
||||||
const stickerPaths = stickersDuck.useStickerOrder();
|
const stickerPaths = stickersDuck.useStickerOrder();
|
||||||
const stickersReady = stickersDuck.useStickersReady();
|
const stickersReady = stickersDuck.useStickersReady();
|
||||||
const haveStickers = stickerPaths.length > 0;
|
const haveStickers = stickerPaths.length > 0;
|
||||||
|
const hasAnimated = stickersDuck.useHasAnimated();
|
||||||
const hasTooLarge = stickersDuck.useHasTooLarge();
|
const hasTooLarge = stickersDuck.useHasTooLarge();
|
||||||
const numberAdded = stickersDuck.useImageAddedCount();
|
const numberAdded = stickersDuck.useImageAddedCount();
|
||||||
const [showGuide, setShowGuide] = React.useState<boolean>(true);
|
const [showGuide, setShowGuide] = React.useState<boolean>(true);
|
||||||
|
@ -73,7 +86,13 @@ export const DropStage = () => {
|
||||||
<div className={styles.main}>
|
<div className={styles.main}>
|
||||||
<StickerGrid mode="add" showGuide={showGuide} />
|
<StickerGrid mode="add" showGuide={showGuide} />
|
||||||
</div>
|
</div>
|
||||||
{renderToaster({ hasTooLarge, numberAdded, resetStatus, i18n })}
|
{renderToaster({
|
||||||
|
hasAnimated,
|
||||||
|
hasTooLarge,
|
||||||
|
numberAdded,
|
||||||
|
resetStatus,
|
||||||
|
i18n,
|
||||||
|
})}
|
||||||
</AppStage>
|
</AppStage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,15 +22,19 @@ export const MetaStage = () => {
|
||||||
|
|
||||||
const onDrop = React.useCallback(
|
const onDrop = React.useCallback(
|
||||||
async ([{ path }]: Array<FileWithPath>) => {
|
async ([{ path }]: Array<FileWithPath>) => {
|
||||||
const webp = await convertToWebp(path);
|
try {
|
||||||
actions.setCover(webp);
|
const webp = await convertToWebp(path);
|
||||||
|
actions.setCover(webp);
|
||||||
|
} catch (e) {
|
||||||
|
actions.removeSticker(path);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[actions]
|
[actions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
onDrop,
|
onDrop,
|
||||||
accept: ['image/png'],
|
accept: ['image/png', 'image/webp'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const onNext = React.useCallback(
|
const onNext = React.useCallback(
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { stickersDuck } from '../store';
|
||||||
import { DropZone, Props as DropZoneProps } from '../elements/DropZone';
|
import { DropZone, Props as DropZoneProps } from '../elements/DropZone';
|
||||||
import { convertToWebp } from '../util/preload';
|
import { convertToWebp } from '../util/preload';
|
||||||
|
|
||||||
const queue = new PQueue({ concurrency: 5 });
|
const queue = new PQueue({ concurrency: 3 });
|
||||||
|
|
||||||
const SmartStickerFrame = SortableElement(
|
const SmartStickerFrame = SortableElement(
|
||||||
({ id, showGuide, mode }: StickerFrameProps) => {
|
({ id, showGuide, mode }: StickerFrameProps) => {
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const DropZone = (props: Props) => {
|
||||||
|
|
||||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
onDrop: handleDrop,
|
onDrop: handleDrop,
|
||||||
accept: ['image/png'],
|
accept: ['image/png', 'image/webp'],
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
|
|
|
@ -63,3 +63,13 @@
|
||||||
composes: text;
|
composes: text;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
@include light-theme() {
|
||||||
|
color: $color-gray-60;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include dark-theme() {
|
||||||
|
color: $color-gray-25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ export type HeadingProps = React.HTMLProps<HTMLHeadingElement>;
|
||||||
export type ParagraphProps = React.HTMLProps<HTMLParagraphElement> & {
|
export type ParagraphProps = React.HTMLProps<HTMLParagraphElement> & {
|
||||||
center?: boolean;
|
center?: boolean;
|
||||||
wide?: boolean;
|
wide?: boolean;
|
||||||
|
secondary?: boolean;
|
||||||
};
|
};
|
||||||
export type SpanProps = React.HTMLProps<HTMLSpanElement>;
|
export type SpanProps = React.HTMLProps<HTMLSpanElement>;
|
||||||
|
|
||||||
|
@ -30,10 +31,18 @@ export const H2 = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Text = React.memo(
|
export const Text = React.memo(
|
||||||
({ children, className, center, wide, ...rest }: Props & ParagraphProps) => (
|
({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
center,
|
||||||
|
wide,
|
||||||
|
secondary,
|
||||||
|
...rest
|
||||||
|
}: Props & ParagraphProps) => (
|
||||||
<p
|
<p
|
||||||
className={classnames(
|
className={classnames(
|
||||||
center ? styles.textCenter : styles.text,
|
center ? styles.textCenter : styles.text,
|
||||||
|
secondary ? styles.secondary : null,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|
|
@ -5,6 +5,7 @@ const pify = require('pify');
|
||||||
const { readFile } = require('fs');
|
const { readFile } = require('fs');
|
||||||
const config = require('url').parse(window.location.toString(), true).query;
|
const config = require('url').parse(window.location.toString(), true).query;
|
||||||
const { noop, uniqBy } = require('lodash');
|
const { noop, uniqBy } = require('lodash');
|
||||||
|
const pMap = require('p-map');
|
||||||
const { deriveStickerPackKey } = require('../js/modules/crypto');
|
const { deriveStickerPackKey } = require('../js/modules/crypto');
|
||||||
const { makeGetter } = require('../preload_utils');
|
const { makeGetter } = require('../preload_utils');
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ window.PROTO_ROOT = '../../protos';
|
||||||
window.getEnvironment = () => config.environment;
|
window.getEnvironment = () => config.environment;
|
||||||
window.getVersion = () => config.version;
|
window.getVersion = () => config.version;
|
||||||
window.getGuid = require('uuid/v4');
|
window.getGuid = require('uuid/v4');
|
||||||
|
window.PQueue = require('p-queue');
|
||||||
|
|
||||||
window.localeMessages = ipc.sendSync('locale-data');
|
window.localeMessages = ipc.sendSync('locale-data');
|
||||||
|
|
||||||
|
@ -38,8 +40,11 @@ const WebAPI = initializeWebAPI({
|
||||||
});
|
});
|
||||||
|
|
||||||
window.convertToWebp = async (path, width = 512, height = 512) => {
|
window.convertToWebp = async (path, width = 512, height = 512) => {
|
||||||
const pngBuffer = await pify(readFile)(path);
|
const imgBuffer = await pify(readFile)(path);
|
||||||
const buffer = await sharp(pngBuffer)
|
const sharpImg = sharp(imgBuffer);
|
||||||
|
const meta = await sharpImg.metadata();
|
||||||
|
|
||||||
|
const buffer = await sharpImg
|
||||||
.resize({
|
.resize({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
@ -53,6 +58,7 @@ window.convertToWebp = async (path, width = 512, height = 512) => {
|
||||||
path,
|
path,
|
||||||
buffer,
|
buffer,
|
||||||
src: `data:image/webp;base64,${buffer.toString('base64')}`,
|
src: `data:image/webp;base64,${buffer.toString('base64')}`,
|
||||||
|
meta,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,8 +116,10 @@ window.encryptAndUpload = async (
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
iv
|
iv
|
||||||
);
|
);
|
||||||
const encryptedStickers = await Promise.all(
|
const encryptedStickers = await pMap(
|
||||||
uniqueStickers.map(({ webp }) => encrypt(webp.buffer, encryptionKey, iv))
|
uniqueStickers,
|
||||||
|
({ webp }) => encrypt(webp.buffer, encryptionKey, iv),
|
||||||
|
{ concurrency: 3 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const packId = await server.putStickers(
|
const packId = await server.putStickers(
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from 'redux-ts-utils';
|
} from 'redux-ts-utils';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { clamp, pull, take, uniq } from 'lodash';
|
import { clamp, isNumber, pull, take, uniq } from 'lodash';
|
||||||
import { SortEnd } from 'react-sortable-hoc';
|
import { SortEnd } from 'react-sortable-hoc';
|
||||||
import arrayMove from 'array-move';
|
import arrayMove from 'array-move';
|
||||||
import { AppState } from '../reducer';
|
import { AppState } from '../reducer';
|
||||||
|
@ -24,6 +24,7 @@ export const addWebp = createAction<WebpData>('stickers/addSticker');
|
||||||
export const removeSticker = createAction<string>('stickers/removeSticker');
|
export const removeSticker = createAction<string>('stickers/removeSticker');
|
||||||
export const moveSticker = createAction<SortEnd>('stickers/moveSticker');
|
export const moveSticker = createAction<SortEnd>('stickers/moveSticker');
|
||||||
export const setCover = createAction<WebpData>('stickers/setCover');
|
export const setCover = createAction<WebpData>('stickers/setCover');
|
||||||
|
export const resetCover = createAction<WebpData>('stickers/resetCover');
|
||||||
export const setEmoji = createAction<{ id: string; emoji: EmojiPickDataType }>(
|
export const setEmoji = createAction<{ id: string; emoji: EmojiPickDataType }>(
|
||||||
'stickers/setEmoji'
|
'stickers/setEmoji'
|
||||||
);
|
);
|
||||||
|
@ -34,7 +35,7 @@ export const resetStatus = createAction<void>('stickers/resetStatus');
|
||||||
export const reset = createAction<void>('stickers/reset');
|
export const reset = createAction<void>('stickers/reset');
|
||||||
|
|
||||||
export const minStickers = 4;
|
export const minStickers = 4;
|
||||||
export const maxStickers = 40;
|
export const maxStickers = 200;
|
||||||
export const maxByteSize = 100 * 1024;
|
export const maxByteSize = 100 * 1024;
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
|
@ -45,6 +46,7 @@ export type State = {
|
||||||
readonly packId: string;
|
readonly packId: string;
|
||||||
readonly packKey: string;
|
readonly packKey: string;
|
||||||
readonly tooLarge: number;
|
readonly tooLarge: number;
|
||||||
|
readonly animated: number;
|
||||||
readonly imagesAdded: number;
|
readonly imagesAdded: number;
|
||||||
readonly data: {
|
readonly data: {
|
||||||
readonly [src: string]: {
|
readonly [src: string]: {
|
||||||
|
@ -62,6 +64,7 @@ const defaultState: State = {
|
||||||
packId: '',
|
packId: '',
|
||||||
packKey: '',
|
packKey: '',
|
||||||
tooLarge: 0,
|
tooLarge: 0,
|
||||||
|
animated: 0,
|
||||||
imagesAdded: 0,
|
imagesAdded: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,7 +94,11 @@ export const reducer = reduceReducers<State>(
|
||||||
}),
|
}),
|
||||||
|
|
||||||
handleAction(addWebp, (state, { payload }) => {
|
handleAction(addWebp, (state, { payload }) => {
|
||||||
if (payload.buffer.byteLength > maxByteSize) {
|
if (isNumber(payload.meta.pages)) {
|
||||||
|
state.animated = clamp(state.animated + 1, 0, state.order.length);
|
||||||
|
pull(state.order, payload.path);
|
||||||
|
delete state.data[payload.path];
|
||||||
|
} else if (payload.buffer.byteLength > maxByteSize) {
|
||||||
state.tooLarge = clamp(state.tooLarge + 1, 0, state.order.length);
|
state.tooLarge = clamp(state.tooLarge + 1, 0, state.order.length);
|
||||||
pull(state.order, payload.path);
|
pull(state.order, payload.path);
|
||||||
delete state.data[payload.path];
|
delete state.data[payload.path];
|
||||||
|
@ -126,6 +133,10 @@ export const reducer = reduceReducers<State>(
|
||||||
state.cover = payload;
|
state.cover = payload;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
handleAction(resetCover, state => {
|
||||||
|
adjustCover(state);
|
||||||
|
}),
|
||||||
|
|
||||||
handleAction(setEmoji, (state, { payload }) => {
|
handleAction(setEmoji, (state, { payload }) => {
|
||||||
const data = state.data[payload.id];
|
const data = state.data[payload.id];
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -148,6 +159,7 @@ export const reducer = reduceReducers<State>(
|
||||||
|
|
||||||
handleAction(resetStatus, state => {
|
handleAction(resetStatus, state => {
|
||||||
state.tooLarge = 0;
|
state.tooLarge = 0;
|
||||||
|
state.animated = 0;
|
||||||
state.imagesAdded = 0;
|
state.imagesAdded = 0;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -202,8 +214,14 @@ const selectUrl = createSelector(
|
||||||
export const usePackUrl = () => useSelector(selectUrl);
|
export const usePackUrl = () => useSelector(selectUrl);
|
||||||
export const useHasTooLarge = () =>
|
export const useHasTooLarge = () =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.tooLarge > 0);
|
useSelector(({ stickers }: AppState) => stickers.tooLarge > 0);
|
||||||
|
export const useHasAnimated = () =>
|
||||||
|
useSelector(({ stickers }: AppState) => stickers.animated > 0);
|
||||||
export const useImageAddedCount = () =>
|
export const useImageAddedCount = () =>
|
||||||
useSelector(({ stickers }: AppState) => stickers.imagesAdded);
|
useSelector(({ stickers }: AppState) => stickers.imagesAdded);
|
||||||
|
export const useAddMoreCount = () =>
|
||||||
|
useSelector(({ stickers }: AppState) =>
|
||||||
|
clamp(minStickers - stickers.order.length, 0, minStickers)
|
||||||
|
);
|
||||||
|
|
||||||
const selectOrderedData = createSelector(
|
const selectOrderedData = createSelector(
|
||||||
({ stickers }: AppState) => stickers.order,
|
({ stickers }: AppState) => stickers.order,
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { Metadata } from 'sharp';
|
||||||
|
|
||||||
export type WebpData = {
|
export type WebpData = {
|
||||||
buffer: Buffer;
|
buffer: Buffer;
|
||||||
src: string;
|
src: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
meta: Metadata & { pages?: number }; // Pages is not currently in the sharp metadata type
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConvertToWebpFn = (
|
export type ConvertToWebpFn = (
|
||||||
|
|
11
yarn.lock
11
yarn.lock
|
@ -334,7 +334,7 @@
|
||||||
"@babel/helper-create-class-features-plugin" "^7.5.5"
|
"@babel/helper-create-class-features-plugin" "^7.5.5"
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
|
|
||||||
"@babel/plugin-proposal-class-properties@^7.7.4":
|
"@babel/plugin-proposal-class-properties@7.7.4":
|
||||||
version "7.7.4"
|
version "7.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.7.4.tgz#2f964f0cb18b948450362742e33e15211e77c2ba"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.7.4.tgz#2f964f0cb18b948450362742e33e15211e77c2ba"
|
||||||
integrity sha512-EcuXeV4Hv1X3+Q1TsuOmyyxeTRiSqurGJ26+I/FW1WbymmRRapVORm6x1Zl3iDIHyRxEs+VXWp6qnlcfcJSbbw==
|
integrity sha512-EcuXeV4Hv1X3+Q1TsuOmyyxeTRiSqurGJ26+I/FW1WbymmRRapVORm6x1Zl3iDIHyRxEs+VXWp6qnlcfcJSbbw==
|
||||||
|
@ -2003,6 +2003,13 @@
|
||||||
"@types/express-serve-static-core" "*"
|
"@types/express-serve-static-core" "*"
|
||||||
"@types/mime" "*"
|
"@types/mime" "*"
|
||||||
|
|
||||||
|
"@types/sharp@0.23.1":
|
||||||
|
version "0.23.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.23.1.tgz#1e02560371d6603adc121389512f0745028aa507"
|
||||||
|
integrity sha512-iBRM9RjRF9pkIkukk6imlxfaKMRuiRND8L0yYKl5PJu5uLvxuNzp5f0x8aoTG5VX85M8O//BwbttzFVZL1j/FQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/sinon@4.3.1":
|
"@types/sinon@4.3.1":
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.1.tgz#32458f9b166cd44c23844eee4937814276f35199"
|
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.1.tgz#32458f9b166cd44c23844eee4937814276f35199"
|
||||||
|
@ -15947,7 +15954,7 @@ typedarray@^0.0.6, typedarray@~0.0.5:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
|
|
||||||
typeface-inter@^3.10.0:
|
typeface-inter@3.10.0:
|
||||||
version "3.10.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/typeface-inter/-/typeface-inter-3.10.0.tgz#04a55d62e2dc3f60db3afab5d8a547e067692bc6"
|
resolved "https://registry.yarnpkg.com/typeface-inter/-/typeface-inter-3.10.0.tgz#04a55d62e2dc3f60db3afab5d8a547e067692bc6"
|
||||||
integrity sha512-WuXE+TaJLB8pdMuvIVY3LfT5UQqndR8+Js0xfhNpdXlsEx0Abwd1bzg4w4YWl2eoOmmLYrRpx6UJJ7a7/q6wZQ==
|
integrity sha512-WuXE+TaJLB8pdMuvIVY3LfT5UQqndR8+Js0xfhNpdXlsEx0Abwd1bzg4w4YWl2eoOmmLYrRpx6UJJ7a7/q6wZQ==
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue