From 68f27c1c7c900363e1ec4e4b37f936436e177dda Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Mon, 13 Jun 2022 18:48:07 -0700 Subject: [PATCH] Fix sticker creator in our new typescriptified world --- .eslintignore | 1 - .gitignore | 1 + .prettierignore | 1 + scripts/esbuild.js | 2 +- sticker-creator/components/ConfirmModal.scss | 2 +- .../components/StickerFrame.stories.tsx | 32 +- sticker-creator/components/StickerFrame.tsx | 3 +- sticker-creator/preload.ts | 287 +----------------- sticker-creator/window/phase1-dependencies.ts | 23 ++ sticker-creator/window/phase2-signal.ts | 20 ++ .../window/phase3-sticker-functions.ts | 233 ++++++++++++++ sticker-creator/window/phase4-theme.ts | 25 ++ ts/components/emoji/EmojiPicker.tsx | 44 +-- ts/util/lint/exceptions.json | 17 +- ts/windows/context.ts | 1 + 15 files changed, 381 insertions(+), 311 deletions(-) create mode 100644 sticker-creator/window/phase1-dependencies.ts create mode 100644 sticker-creator/window/phase2-signal.ts create mode 100644 sticker-creator/window/phase3-sticker-functions.ts create mode 100644 sticker-creator/window/phase4-theme.ts diff --git a/.eslintignore b/.eslintignore index 20ed14d2b..117780711 100644 --- a/.eslintignore +++ b/.eslintignore @@ -24,7 +24,6 @@ js/WebAudioRecorderMp3.js app/**/*.js ts/**/*.js sticker-creator/**/*.js -!sticker-creator/preload.js **/*.d.ts .eslintrc.js diff --git a/.gitignore b/.gitignore index 7a5b2a5ca..279ad12b7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ sticker-creator/**/*.js # Sticker Creator sticker-creator/dist/* +sticker-creator/**/*.js # Editors /.idea diff --git a/.prettierignore b/.prettierignore index a5e570d31..c36bf096d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,6 +3,7 @@ # Generated files app/**/*.js +sticker-creator/**/*.js config/local-*.json config/local.json dist/** diff --git a/scripts/esbuild.js b/scripts/esbuild.js index 4c0442552..e4b1aa0ca 100644 --- a/scripts/esbuild.js +++ b/scripts/esbuild.js @@ -68,7 +68,7 @@ esbuild.build({ format: 'cjs', mainFields: ['browser', 'main'], entryPoints: glob - .sync('{app,ts}/**/*.{ts,tsx}', { + .sync('{app,ts,sticker-creator}/**/*.{ts,tsx}', { nodir: true, root: ROOT_DIR, }) diff --git a/sticker-creator/components/ConfirmModal.scss b/sticker-creator/components/ConfirmModal.scss index 0c220f61d..7e30f69df 100644 --- a/sticker-creator/components/ConfirmModal.scss +++ b/sticker-creator/components/ConfirmModal.scss @@ -4,7 +4,7 @@ .facade { background: rgba(0, 0, 0, 0.33); width: 100vw; - height: var(--window-height); + height: 100vh; display: flex; justify-content: center; align-items: center; diff --git a/sticker-creator/components/StickerFrame.stories.tsx b/sticker-creator/components/StickerFrame.stories.tsx index ce22c0b14..7c0df44a9 100644 --- a/sticker-creator/components/StickerFrame.stories.tsx +++ b/sticker-creator/components/StickerFrame.stories.tsx @@ -44,5 +44,35 @@ export const _StickerFrame = (): JSX.Element => { }; _StickerFrame.story = { - name: 'StickerFrame', + name: 'StickerFrame, add sticker', +}; + +export const EmojiSelectMode = (): JSX.Element => { + const image = text('image url', '/fixtures/512x515-thumbs-up-lincoln.webp'); + const setSkinTone = action('setSkinTone'); + const onRemove = action('onRemove'); + const onDrop = action('onDrop'); + const [emoji, setEmoji] = React.useState( + undefined + ); + + return ( + + setEmoji(e.emoji)} + onDrop={onDrop} + /> + + ); +}; + +EmojiSelectMode.story = { + name: 'StickerFrame, emoji select mode', }; diff --git a/sticker-creator/components/StickerFrame.tsx b/sticker-creator/components/StickerFrame.tsx index c4782ba85..e764c2e1b 100644 --- a/sticker-creator/components/StickerFrame.tsx +++ b/sticker-creator/components/StickerFrame.tsx @@ -155,6 +155,7 @@ export const StickerFrame = React.memo( return () => { removeRoot(root); + setEmojiPopperRoot(null); document.removeEventListener('click', handleOutsideClick); }; } @@ -254,7 +255,7 @@ export const StickerFrame = React.memo( )} - {emojiPickerOpen && onSetSkinTone && emojiPopperRoot + {emojiPickerOpen && emojiPopperRoot ? createPortal( {({ ref, style }) => ( diff --git a/sticker-creator/preload.ts b/sticker-creator/preload.ts index 87059d557..61f756a43 100644 --- a/sticker-creator/preload.ts +++ b/sticker-creator/preload.ts @@ -1,290 +1,11 @@ // Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import PQueue from 'p-queue'; -import Backbone from 'backbone'; +import './window/phase1-dependencies'; +import './window/phase2-signal'; +import './window/phase3-sticker-functions'; +import './window/phase4-theme'; -import { ipcRenderer as ipc } from 'electron'; -import sharp from 'sharp'; -import pify from 'pify'; -import { readFile } from 'fs'; -import { noop, uniqBy } from 'lodash'; - -// It is important to call this as early as possible import { SignalContext } from '../ts/windows/context'; -import { - deriveStickerPackKey, - encryptAttachment, - getRandomBytes, -} from '../ts/Crypto'; -import * as Bytes from '../ts/Bytes'; -import { SignalService as Proto } from '../ts/protobuf'; -import { getEnvironment } from '../ts/environment'; -import { createSetting } from '../ts/util/preload'; -import * as Attachments from '../ts/windows/attachments'; - -import * as Signal from '../ts/signal'; -import { textsecure } from '../ts/textsecure'; - -import { initialize as initializeWebAPI } from '../ts/textsecure/WebAPI'; -import { getAnimatedPngDataIfExists } from '../ts/util/getAnimatedPngDataIfExists'; - -const { config } = SignalContext; -window.i18n = SignalContext.i18n; - -const STICKER_SIZE = 512; -const MIN_STICKER_DIMENSION = 10; -const MAX_STICKER_DIMENSION = STICKER_SIZE; -const MAX_STICKER_BYTE_LENGTH = 300 * 1024; - -window.ROOT_PATH = window.location.href.startsWith('file') ? '../../' : '/'; -window.getEnvironment = getEnvironment; -window.getVersion = () => window.SignalContext.config.version; - -window.PQueue = PQueue; -window.Backbone = Backbone; - -window.localeMessages = ipc.sendSync('locale-data'); - -require('../ts/SignalProtocolStore'); - -SignalContext.log.info('sticker-creator starting up...'); - -window.Signal = Signal.setup({ - Attachments, - getRegionCode: () => { - throw new Error('Sticker Creator preload: Not implemented!'); - }, - logger: SignalContext.log, - userDataPath: SignalContext.config.userDataPath, -}); -window.textsecure = textsecure; - -const WebAPI = initializeWebAPI({ - url: config.serverUrl, - storageUrl: config.storageUrl, - updatesUrl: config.updatesUrl, - directoryVersion: config.directoryVersion, - directoryUrl: config.directoryUrl, - directoryEnclaveId: config.directoryEnclaveId, - directoryTrustAnchor: config.directoryTrustAnchor, - directoryV2Url: config.directoryV2Url, - directoryV2PublicKey: config.directoryV2PublicKey, - directoryV2CodeHashes: config.directoryV2CodeHashes, - cdnUrlObject: { - 0: config.cdnUrl0, - 2: config.cdnUrl2, - }, - certificateAuthority: config.certificateAuthority, - contentProxyUrl: config.contentProxyUrl, - proxyUrl: config.proxyUrl, - version: config.version, -}); - -function processStickerError(message: string, i18nKey: string): Error { - const result = new Error(message); - result.errorMessageI18nKey = i18nKey; - return result; -} - -window.processStickerImage = async (path: string | undefined) => { - if (!path) { - throw new Error(`Path ${path} is not valid!`); - } - - const imgBuffer = await pify(readFile)(path); - const sharpImg = sharp(imgBuffer); - const meta = await sharpImg.metadata(); - - const { width, height } = meta; - if (!width || !height) { - throw processStickerError( - 'Sticker height or width were falsy', - 'StickerCreator--Toasts--errorProcessing' - ); - } - - let contentType; - let processedBuffer; - - // [Sharp doesn't support APNG][0], so we do something simpler: validate the file size - // and dimensions without resizing, cropping, or converting. In a perfect world, we'd - // resize and convert any animated image (GIF, animated WebP) to APNG. - // [0]: https://github.com/lovell/sharp/issues/2375 - const animatedPngDataIfExists = getAnimatedPngDataIfExists(imgBuffer); - if (animatedPngDataIfExists) { - if (imgBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) { - throw processStickerError( - 'Sticker file was too large', - 'StickerCreator--Toasts--tooLarge' - ); - } - if (width !== height) { - throw processStickerError( - 'Sticker must be square', - 'StickerCreator--Toasts--APNG--notSquare' - ); - } - if (width > MAX_STICKER_DIMENSION) { - throw processStickerError( - 'Sticker dimensions are too large', - 'StickerCreator--Toasts--APNG--dimensionsTooLarge' - ); - } - if (width < MIN_STICKER_DIMENSION) { - throw processStickerError( - 'Sticker dimensions are too small', - 'StickerCreator--Toasts--APNG--dimensionsTooSmall' - ); - } - if (animatedPngDataIfExists.numPlays !== Infinity) { - throw processStickerError( - 'Animated stickers must loop forever', - 'StickerCreator--Toasts--mustLoopForever' - ); - } - contentType = 'image/png'; - processedBuffer = imgBuffer; - } else { - contentType = 'image/webp'; - processedBuffer = await sharpImg - .resize({ - width: STICKER_SIZE, - height: STICKER_SIZE, - fit: 'contain', - background: { r: 0, g: 0, b: 0, alpha: 0 }, - }) - .webp() - .toBuffer(); - if (processedBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) { - throw processStickerError( - 'Sticker file was too large', - 'StickerCreator--Toasts--tooLarge' - ); - } - } - - return { - path, - buffer: processedBuffer, - src: `data:${contentType};base64,${processedBuffer.toString('base64')}`, - meta, - }; -}; - -window.encryptAndUpload = async ( - manifest, - stickers, - cover, - onProgress = noop -) => { - const usernameItem = await window.Signal.Data.getItemById('uuid_id'); - const oldUsernameItem = await window.Signal.Data.getItemById('number_id'); - const passwordItem = await window.Signal.Data.getItemById('password'); - - const username = usernameItem?.value || oldUsernameItem?.value; - if (!username || !passwordItem?.value) { - const { message } = - window.localeMessages['StickerCreator--Authentication--error']; - - ipc.send('show-message-box', { - type: 'warning', - message, - }); - - throw new Error(message); - } - - const { value: password } = passwordItem; - - const packKey = getRandomBytes(32); - const encryptionKey = deriveStickerPackKey(packKey); - const iv = getRandomBytes(16); - - const server = WebAPI.connect({ - username, - password, - useWebSocket: false, - }); - - const uniqueStickers = uniqBy( - [...stickers, { imageData: cover }], - 'imageData' - ); - - const manifestProto = new Proto.StickerPack(); - manifestProto.title = manifest.title; - manifestProto.author = manifest.author; - manifestProto.stickers = stickers.map(({ emoji }, id) => { - const s = new Proto.StickerPack.Sticker(); - s.id = id; - if (emoji) { - s.emoji = emoji; - } - - return s; - }); - const coverSticker = new Proto.StickerPack.Sticker(); - coverSticker.id = - uniqueStickers.length === stickers.length ? 0 : uniqueStickers.length - 1; - coverSticker.emoji = ''; - manifestProto.cover = coverSticker; - - const encryptedManifest = await encrypt( - Proto.StickerPack.encode(manifestProto).finish(), - encryptionKey, - iv - ); - const encryptedStickers = uniqueStickers.map(({ imageData }) => { - if (!imageData?.buffer) { - throw new Error('encryptStickers: Missing image data on sticker'); - } - - return encrypt(imageData.buffer, encryptionKey, iv); - }); - - const packId = await server.putStickers( - encryptedManifest, - encryptedStickers, - onProgress - ); - - const hexKey = Bytes.toHex(packKey); - - ipc.send('install-sticker-pack', packId, hexKey); - - return { packId, key: hexKey }; -}; - -function encrypt( - data: Uint8Array, - key: Uint8Array, - iv: Uint8Array -): Uint8Array { - const { ciphertext } = encryptAttachment(data, key, iv); - - return ciphertext; -} - -const getThemeSetting = createSetting('themeSetting'); - -async function resolveTheme() { - const theme = (await getThemeSetting.getValue()) || 'system'; - if (theme === 'system') { - return SignalContext.nativeThemeListener.getSystemTheme(); - } - return theme; -} - -async function applyTheme() { - window.document.body.classList.remove('dark-theme'); - window.document.body.classList.remove('light-theme'); - window.document.body.classList.add(`${await resolveTheme()}-theme`); -} - -window.addEventListener('DOMContentLoaded', applyTheme); - -SignalContext.nativeThemeListener.subscribe(() => applyTheme()); - SignalContext.log.info('sticker-creator preload complete...'); diff --git a/sticker-creator/window/phase1-dependencies.ts b/sticker-creator/window/phase1-dependencies.ts new file mode 100644 index 000000000..90278326c --- /dev/null +++ b/sticker-creator/window/phase1-dependencies.ts @@ -0,0 +1,23 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import PQueue from 'p-queue'; +import Backbone from 'backbone'; + +import { ipcRenderer as ipc } from 'electron'; + +// It is important to call this as early as possible +import { SignalContext } from '../../ts/windows/context'; + +import { getEnvironment } from '../../ts/environment'; + +SignalContext.log.info('sticker-creator starting up...'); + +window.ROOT_PATH = window.location.href.startsWith('file') ? '../../' : '/'; +window.getEnvironment = getEnvironment; +window.getVersion = () => window.SignalContext.config.version; + +window.PQueue = PQueue; +window.Backbone = Backbone; + +window.localeMessages = ipc.sendSync('locale-data'); diff --git a/sticker-creator/window/phase2-signal.ts b/sticker-creator/window/phase2-signal.ts new file mode 100644 index 000000000..91e9d1762 --- /dev/null +++ b/sticker-creator/window/phase2-signal.ts @@ -0,0 +1,20 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import * as Signal from '../../ts/signal'; +import { textsecure } from '../../ts/textsecure'; + +import * as Attachments from '../../ts/windows/attachments'; +import '../../ts/SignalProtocolStore'; + +import { SignalContext } from '../../ts/windows/context'; + +window.Signal = Signal.setup({ + Attachments, + getRegionCode: () => { + throw new Error('Sticker Creator preload: Not implemented!'); + }, + logger: SignalContext.log, + userDataPath: SignalContext.config.userDataPath, +}); +window.textsecure = textsecure; diff --git a/sticker-creator/window/phase3-sticker-functions.ts b/sticker-creator/window/phase3-sticker-functions.ts new file mode 100644 index 000000000..39230de3c --- /dev/null +++ b/sticker-creator/window/phase3-sticker-functions.ts @@ -0,0 +1,233 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import sharp from 'sharp'; +import pify from 'pify'; +import { readFile } from 'fs'; +import { noop, uniqBy } from 'lodash'; +import { ipcRenderer as ipc } from 'electron'; + +import { + deriveStickerPackKey, + encryptAttachment, + getRandomBytes, +} from '../../ts/Crypto'; +import * as Bytes from '../../ts/Bytes'; +import { SignalService as Proto } from '../../ts/protobuf'; +import { initialize as initializeWebAPI } from '../../ts/textsecure/WebAPI'; + +import { SignalContext } from '../../ts/windows/context'; +import { getAnimatedPngDataIfExists } from '../../ts/util/getAnimatedPngDataIfExists'; + +const STICKER_SIZE = 512; +const MIN_STICKER_DIMENSION = 10; +const MAX_STICKER_DIMENSION = STICKER_SIZE; +const MAX_STICKER_BYTE_LENGTH = 300 * 1024; + +const { config } = SignalContext; + +const WebAPI = initializeWebAPI({ + url: config.serverUrl, + storageUrl: config.storageUrl, + updatesUrl: config.updatesUrl, + directoryVersion: config.directoryVersion, + directoryUrl: config.directoryUrl, + directoryEnclaveId: config.directoryEnclaveId, + directoryTrustAnchor: config.directoryTrustAnchor, + directoryV2Url: config.directoryV2Url, + directoryV2PublicKey: config.directoryV2PublicKey, + directoryV2CodeHashes: config.directoryV2CodeHashes, + cdnUrlObject: { + 0: config.cdnUrl0, + 2: config.cdnUrl2, + }, + certificateAuthority: config.certificateAuthority, + contentProxyUrl: config.contentProxyUrl, + proxyUrl: config.proxyUrl, + version: config.version, +}); + +function processStickerError(message: string, i18nKey: string): Error { + const result = new Error(message); + result.errorMessageI18nKey = i18nKey; + return result; +} + +window.processStickerImage = async (path: string | undefined) => { + if (!path) { + throw new Error(`Path ${path} is not valid!`); + } + + const imgBuffer = await pify(readFile)(path); + const sharpImg = sharp(imgBuffer); + const meta = await sharpImg.metadata(); + + const { width, height } = meta; + if (!width || !height) { + throw processStickerError( + 'Sticker height or width were falsy', + 'StickerCreator--Toasts--errorProcessing' + ); + } + + let contentType; + let processedBuffer; + + // [Sharp doesn't support APNG][0], so we do something simpler: validate the file size + // and dimensions without resizing, cropping, or converting. In a perfect world, we'd + // resize and convert any animated image (GIF, animated WebP) to APNG. + // [0]: https://github.com/lovell/sharp/issues/2375 + const animatedPngDataIfExists = getAnimatedPngDataIfExists(imgBuffer); + if (animatedPngDataIfExists) { + if (imgBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) { + throw processStickerError( + 'Sticker file was too large', + 'StickerCreator--Toasts--tooLarge' + ); + } + if (width !== height) { + throw processStickerError( + 'Sticker must be square', + 'StickerCreator--Toasts--APNG--notSquare' + ); + } + if (width > MAX_STICKER_DIMENSION) { + throw processStickerError( + 'Sticker dimensions are too large', + 'StickerCreator--Toasts--APNG--dimensionsTooLarge' + ); + } + if (width < MIN_STICKER_DIMENSION) { + throw processStickerError( + 'Sticker dimensions are too small', + 'StickerCreator--Toasts--APNG--dimensionsTooSmall' + ); + } + if (animatedPngDataIfExists.numPlays !== Infinity) { + throw processStickerError( + 'Animated stickers must loop forever', + 'StickerCreator--Toasts--mustLoopForever' + ); + } + contentType = 'image/png'; + processedBuffer = imgBuffer; + } else { + contentType = 'image/webp'; + processedBuffer = await sharpImg + .resize({ + width: STICKER_SIZE, + height: STICKER_SIZE, + fit: 'contain', + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) + .webp() + .toBuffer(); + if (processedBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) { + throw processStickerError( + 'Sticker file was too large', + 'StickerCreator--Toasts--tooLarge' + ); + } + } + + return { + path, + buffer: processedBuffer, + src: `data:${contentType};base64,${processedBuffer.toString('base64')}`, + meta, + }; +}; + +window.encryptAndUpload = async ( + manifest, + stickers, + cover, + onProgress = noop +) => { + const usernameItem = await window.Signal.Data.getItemById('uuid_id'); + const oldUsernameItem = await window.Signal.Data.getItemById('number_id'); + const passwordItem = await window.Signal.Data.getItemById('password'); + + const username = usernameItem?.value || oldUsernameItem?.value; + if (!username || !passwordItem?.value) { + const { message } = + window.localeMessages['StickerCreator--Authentication--error']; + + ipc.send('show-message-box', { + type: 'warning', + message, + }); + + throw new Error(message); + } + + const { value: password } = passwordItem; + + const packKey = getRandomBytes(32); + const encryptionKey = deriveStickerPackKey(packKey); + const iv = getRandomBytes(16); + + const server = WebAPI.connect({ + username, + password, + useWebSocket: false, + }); + + const uniqueStickers = uniqBy( + [...stickers, { imageData: cover }], + 'imageData' + ); + + const manifestProto = new Proto.StickerPack(); + manifestProto.title = manifest.title; + manifestProto.author = manifest.author; + manifestProto.stickers = stickers.map(({ emoji }, id) => { + const s = new Proto.StickerPack.Sticker(); + s.id = id; + if (emoji) { + s.emoji = emoji; + } + + return s; + }); + const coverSticker = new Proto.StickerPack.Sticker(); + coverSticker.id = + uniqueStickers.length === stickers.length ? 0 : uniqueStickers.length - 1; + coverSticker.emoji = ''; + manifestProto.cover = coverSticker; + + const encryptedManifest = await encrypt( + Proto.StickerPack.encode(manifestProto).finish(), + encryptionKey, + iv + ); + const encryptedStickers = uniqueStickers.map(({ imageData }) => { + if (!imageData?.buffer) { + throw new Error('encryptStickers: Missing image data on sticker'); + } + + return encrypt(imageData.buffer, encryptionKey, iv); + }); + + const packId = await server.putStickers( + encryptedManifest, + encryptedStickers, + onProgress + ); + + const hexKey = Bytes.toHex(packKey); + + ipc.send('install-sticker-pack', packId, hexKey); + + return { packId, key: hexKey }; +}; + +function encrypt( + data: Uint8Array, + key: Uint8Array, + iv: Uint8Array +): Uint8Array { + const { ciphertext } = encryptAttachment(data, key, iv); + + return ciphertext; +} diff --git a/sticker-creator/window/phase4-theme.ts b/sticker-creator/window/phase4-theme.ts new file mode 100644 index 000000000..7480452bb --- /dev/null +++ b/sticker-creator/window/phase4-theme.ts @@ -0,0 +1,25 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { createSetting } from '../../ts/util/preload'; +import { SignalContext } from '../../ts/windows/context'; + +const getThemeSetting = createSetting('themeSetting'); + +async function resolveTheme() { + const theme = (await getThemeSetting.getValue()) || 'system'; + if (theme === 'system') { + return SignalContext.nativeThemeListener.getSystemTheme(); + } + return theme; +} + +async function applyTheme() { + window.document.body.classList.remove('dark-theme'); + window.document.body.classList.remove('light-theme'); + window.document.body.classList.add(`${await resolveTheme()}-theme`); +} + +window.addEventListener('DOMContentLoaded', applyTheme); + +SignalContext.nativeThemeListener.subscribe(() => applyTheme()); diff --git a/ts/components/emoji/EmojiPicker.tsx b/ts/components/emoji/EmojiPicker.tsx index 0a4fd5201..afc503539 100644 --- a/ts/components/emoji/EmojiPicker.tsx +++ b/ts/components/emoji/EmojiPicker.tsx @@ -34,7 +34,7 @@ export type OwnProps = { readonly onPickEmoji: (o: EmojiPickDataType) => unknown; readonly doSend?: () => unknown; readonly skinTone?: number; - readonly onSetSkinTone: (tone: number) => unknown; + readonly onSetSkinTone?: (tone: number) => unknown; readonly recentEmojis?: Array; readonly onClickSettings?: () => unknown; readonly onClose?: () => unknown; @@ -406,26 +406,28 @@ export const EmojiPicker = React.memo( type="button" /> )} -
- {[0, 1, 2, 3, 4, 5].map(tone => ( - - ))} -
+ {onSetSkinTone ? ( +
+ {[0, 1, 2, 3, 4, 5].map(tone => ( + + ))} +
+ ) : null} {Boolean(onClickSettings) && (
)} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 96268815a..88d5e9df8 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -913,8 +913,7 @@ "path": "node_modules/agent-base/node_modules/debug/src/common.js", "line": "\tcreateDebug.enable(createDebug.load());", "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", - "updated": "2022-02-11T21:58:24.827Z", - "reasonDetail": "" + "updated": "2022-02-11T21:58:24.827Z" }, { "rule": "jQuery-load(", @@ -8273,6 +8272,13 @@ "reasonCategory": "falseMatch", "updated": "2018-09-19T21:59:32.770Z" }, + { + "rule": "React-useRef", + "path": "sticker-creator/components/StickerFrame.js", + "line": " const timerRef = React.useRef();", + "reasonCategory": "falseMatch", + "updated": "2022-06-14T01:19:45.446Z" + }, { "rule": "React-useRef", "path": "sticker-creator/components/StickerFrame.tsx", @@ -8280,6 +8286,13 @@ "reasonCategory": "usageTrusted", "updated": "2021-07-30T16:57:33.618Z" }, + { + "rule": "jQuery-$(", + "path": "sticker-creator/util/i18n.js", + "line": " const FIND_REPLACEMENTS = /\\$([^$]+)\\$/g;", + "reasonCategory": "falseMatch", + "updated": "2022-06-14T01:19:45.446Z" + }, { "rule": "jQuery-$(", "path": "sticker-creator/util/i18n.tsx", diff --git a/ts/windows/context.ts b/ts/windows/context.ts index 12fbd78fb..b2f79bc8d 100644 --- a/ts/windows/context.ts +++ b/ts/windows/context.ts @@ -129,3 +129,4 @@ export const SignalContext: SignalContextType = { }; window.SignalContext = SignalContext; +window.i18n = SignalContext.i18n;