Support additional sticker states
Co-authored-by: scott@signal.org Co-authored-by: ken@signal.org
This commit is contained in:
parent
41880cfe66
commit
be5d0837f8
|
@ -1835,6 +1835,12 @@
|
||||||
"message": "Sticker Pack",
|
"message": "Sticker Pack",
|
||||||
"description": "The title that appears in the sticker pack preview modal."
|
"description": "The title that appears in the sticker pack preview modal."
|
||||||
},
|
},
|
||||||
|
"stickers--StickerPreview--Error": {
|
||||||
|
"message":
|
||||||
|
"Error opening sticker pack. Check your internet connection and try again.",
|
||||||
|
"description":
|
||||||
|
"The message that appears in the sticker preview modal when there is an error."
|
||||||
|
},
|
||||||
"EmojiPicker--empty": {
|
"EmojiPicker--empty": {
|
||||||
"message": "No emoji found",
|
"message": "No emoji found",
|
||||||
"description": "Shown in the emoji picker when a search yields 0 results."
|
"description": "Shown in the emoji picker when a search yields 0 results."
|
||||||
|
|
|
@ -12,6 +12,7 @@ let initialized = false;
|
||||||
|
|
||||||
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
|
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
|
||||||
const ERASE_STICKERS_KEY = 'erase-stickers';
|
const ERASE_STICKERS_KEY = 'erase-stickers';
|
||||||
|
const ERASE_TEMP_KEY = 'erase-temp';
|
||||||
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
||||||
|
|
||||||
async function initialize({ configDir, cleanupOrphanedAttachments }) {
|
async function initialize({ configDir, cleanupOrphanedAttachments }) {
|
||||||
|
@ -22,6 +23,18 @@ async function initialize({ configDir, cleanupOrphanedAttachments }) {
|
||||||
|
|
||||||
const attachmentsDir = Attachments.getPath(configDir);
|
const attachmentsDir = Attachments.getPath(configDir);
|
||||||
const stickersDir = Attachments.getStickersPath(configDir);
|
const stickersDir = Attachments.getStickersPath(configDir);
|
||||||
|
const tempDir = Attachments.getTempPath(configDir);
|
||||||
|
|
||||||
|
ipcMain.on(ERASE_TEMP_KEY, event => {
|
||||||
|
try {
|
||||||
|
rimraf.sync(tempDir);
|
||||||
|
event.sender.send(`${ERASE_TEMP_KEY}-done`);
|
||||||
|
} catch (error) {
|
||||||
|
const errorForDisplay = error && error.stack ? error.stack : error;
|
||||||
|
console.log(`erase temp error: ${errorForDisplay}`);
|
||||||
|
event.sender.send(`${ERASE_TEMP_KEY}-done`, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.on(ERASE_ATTACHMENTS_KEY, event => {
|
ipcMain.on(ERASE_ATTACHMENTS_KEY, event => {
|
||||||
try {
|
try {
|
||||||
|
|
1
app/attachments.d.ts
vendored
Normal file
1
app/attachments.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export function getTempPath(userDataPath: string): string;
|
|
@ -9,6 +9,7 @@ const { map, isArrayBuffer, isString } = require('lodash');
|
||||||
|
|
||||||
const PATH = 'attachments.noindex';
|
const PATH = 'attachments.noindex';
|
||||||
const STICKER_PATH = 'stickers.noindex';
|
const STICKER_PATH = 'stickers.noindex';
|
||||||
|
const TEMP_PATH = 'temp';
|
||||||
|
|
||||||
exports.getAllAttachments = async userDataPath => {
|
exports.getAllAttachments = async userDataPath => {
|
||||||
const dir = exports.getPath(userDataPath);
|
const dir = exports.getPath(userDataPath);
|
||||||
|
@ -42,6 +43,20 @@ exports.getStickersPath = userDataPath => {
|
||||||
return path.join(userDataPath, STICKER_PATH);
|
return path.join(userDataPath, STICKER_PATH);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// getTempPath :: AbsolutePath -> AbsolutePath
|
||||||
|
exports.getTempPath = userDataPath => {
|
||||||
|
if (!isString(userDataPath)) {
|
||||||
|
throw new TypeError("'userDataPath' must be a string");
|
||||||
|
}
|
||||||
|
return path.join(userDataPath, TEMP_PATH);
|
||||||
|
};
|
||||||
|
|
||||||
|
// clearTempPath :: AbsolutePath -> AbsolutePath
|
||||||
|
exports.clearTempPath = userDataPath => {
|
||||||
|
const tempPath = exports.getTempPath(userDataPath);
|
||||||
|
return fse.emptyDir(tempPath);
|
||||||
|
};
|
||||||
|
|
||||||
// createReader :: AttachmentsPath ->
|
// createReader :: AttachmentsPath ->
|
||||||
// RelativePath ->
|
// RelativePath ->
|
||||||
// IO (Promise ArrayBuffer)
|
// IO (Promise ArrayBuffer)
|
||||||
|
|
69
app/sql.js
69
app/sql.js
|
@ -1193,14 +1193,14 @@ async function updateConversation(data) {
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
`UPDATE conversations SET
|
`UPDATE conversations SET
|
||||||
json = $json,
|
json = $json,
|
||||||
|
|
||||||
active_at = $active_at,
|
active_at = $active_at,
|
||||||
type = $type,
|
type = $type,
|
||||||
members = $members,
|
members = $members,
|
||||||
name = $name,
|
name = $name,
|
||||||
profileName = $profileName
|
profileName = $profileName
|
||||||
WHERE id = $id;`,
|
WHERE id = $id;`,
|
||||||
{
|
{
|
||||||
$id: id,
|
$id: id,
|
||||||
$json: objectToJSON(data),
|
$json: objectToJSON(data),
|
||||||
|
@ -1879,8 +1879,46 @@ async function createOrUpdateStickerPack(pack) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rows = await db.all('SELECT id FROM sticker_packs WHERE id = $id;', {
|
||||||
|
$id: id,
|
||||||
|
});
|
||||||
|
const payload = {
|
||||||
|
$attemptedStatus: attemptedStatus,
|
||||||
|
$author: author,
|
||||||
|
$coverStickerId: coverStickerId,
|
||||||
|
$createdAt: createdAt || Date.now(),
|
||||||
|
$downloadAttempts: downloadAttempts || 1,
|
||||||
|
$id: id,
|
||||||
|
$installedAt: installedAt,
|
||||||
|
$key: key,
|
||||||
|
$lastUsed: lastUsed || null,
|
||||||
|
$status: status,
|
||||||
|
$stickerCount: stickerCount,
|
||||||
|
$title: title,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rows && rows.length) {
|
||||||
|
await db.run(
|
||||||
|
`UPDATE sticker_packs SET
|
||||||
|
attemptedStatus = $attemptedStatus,
|
||||||
|
author = $author,
|
||||||
|
coverStickerId = $coverStickerId,
|
||||||
|
createdAt = $createdAt,
|
||||||
|
downloadAttempts = $downloadAttempts,
|
||||||
|
installedAt = $installedAt,
|
||||||
|
key = $key,
|
||||||
|
lastUsed = $lastUsed,
|
||||||
|
status = $status,
|
||||||
|
stickerCount = $stickerCount,
|
||||||
|
title = $title
|
||||||
|
WHERE id = $id;`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT OR REPLACE INTO sticker_packs (
|
`INSERT INTO sticker_packs (
|
||||||
attemptedStatus,
|
attemptedStatus,
|
||||||
author,
|
author,
|
||||||
coverStickerId,
|
coverStickerId,
|
||||||
|
@ -1907,20 +1945,7 @@ async function createOrUpdateStickerPack(pack) {
|
||||||
$stickerCount,
|
$stickerCount,
|
||||||
$title
|
$title
|
||||||
)`,
|
)`,
|
||||||
{
|
payload
|
||||||
$attemptedStatus: attemptedStatus,
|
|
||||||
$author: author,
|
|
||||||
$coverStickerId: coverStickerId,
|
|
||||||
$createdAt: createdAt || Date.now(),
|
|
||||||
$downloadAttempts: downloadAttempts || 1,
|
|
||||||
$id: id,
|
|
||||||
$installedAt: installedAt,
|
|
||||||
$key: key,
|
|
||||||
$lastUsed: lastUsed || null,
|
|
||||||
$status: status,
|
|
||||||
$stickerCount: stickerCount,
|
|
||||||
$title: title,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
async function updateStickerPackStatus(id, status, options) {
|
async function updateStickerPackStatus(id, status, options) {
|
||||||
|
|
|
@ -302,22 +302,25 @@
|
||||||
await window.Signal.Data.shutdown();
|
await window.Signal.Data.shutdown();
|
||||||
},
|
},
|
||||||
|
|
||||||
installStickerPack: async (id, key) => {
|
showStickerPack: async (packId, key) => {
|
||||||
const status = window.Signal.Stickers.getStickerPackStatus(id);
|
// Kick off the download
|
||||||
|
window.Signal.Stickers.downloadEphemeralPack(packId, key);
|
||||||
|
|
||||||
if (status === 'installed') {
|
const props = {
|
||||||
return;
|
packId,
|
||||||
}
|
onClose: async () => {
|
||||||
|
stickerPreviewModalView.remove();
|
||||||
|
await window.Signal.Stickers.removeEphemeralPack(packId);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'advertised') {
|
const stickerPreviewModalView = new Whisper.ReactWrapperView({
|
||||||
await window.reduxActions.stickers.installStickerPack(id, key, {
|
className: 'sticker-preview-modal-wrapper',
|
||||||
fromSync: true,
|
JSX: Signal.State.Roots.createStickerPreviewModal(
|
||||||
});
|
window.reduxStore,
|
||||||
} else {
|
props
|
||||||
await window.Signal.Stickers.downloadStickerPack(id, key, {
|
),
|
||||||
finalStatus: 'installed',
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -464,6 +467,7 @@
|
||||||
user: {
|
user: {
|
||||||
attachmentsPath: window.baseAttachmentsPath,
|
attachmentsPath: window.baseAttachmentsPath,
|
||||||
stickersPath: window.baseStickersPath,
|
stickersPath: window.baseStickersPath,
|
||||||
|
tempPath: window.baseTempPath,
|
||||||
regionCode: window.storage.get('regionCode'),
|
regionCode: window.storage.get('regionCode'),
|
||||||
ourNumber: textsecure.storage.user.getNumber(),
|
ourNumber: textsecure.storage.user.getNumber(),
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
|
@ -1056,7 +1060,7 @@
|
||||||
fromSync: true,
|
fromSync: true,
|
||||||
});
|
});
|
||||||
} else if (isInstall) {
|
} else if (isInstall) {
|
||||||
if (status === 'advertised') {
|
if (status === 'downloaded') {
|
||||||
window.reduxActions.stickers.installStickerPack(id, key, {
|
window.reduxActions.stickers.installStickerPack(id, key, {
|
||||||
fromSync: true,
|
fromSync: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
const {
|
const {
|
||||||
copyStickerToAttachments,
|
copyStickerToAttachments,
|
||||||
deletePackReference,
|
deletePackReference,
|
||||||
downloadStickerPack,
|
savePackMetadata,
|
||||||
getStickerPackStatus,
|
getStickerPackStatus,
|
||||||
} = window.Signal.Stickers;
|
} = window.Signal.Stickers;
|
||||||
const { addStickerPackReference } = window.Signal.Data;
|
const { addStickerPackReference } = window.Signal.Data;
|
||||||
|
@ -1467,7 +1467,7 @@
|
||||||
const status = getStickerPackStatus(packId);
|
const status = getStickerPackStatus(packId);
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
if (status && status !== 'pending' && status !== 'error') {
|
if (status && (status === 'downloaded' || status === 'installed')) {
|
||||||
try {
|
try {
|
||||||
const copiedSticker = await copyStickerToAttachments(
|
const copiedSticker = await copyStickerToAttachments(
|
||||||
packId,
|
packId,
|
||||||
|
@ -1492,8 +1492,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!status) {
|
if (!status) {
|
||||||
// kick off the download without waiting
|
// Save the packId/packKey for future download/install
|
||||||
downloadStickerPack(packId, packKey, { messageId });
|
savePackMetadata(packId, packKey, { messageId });
|
||||||
} else {
|
} else {
|
||||||
await addStickerPackReference(messageId, packId);
|
await addStickerPackReference(messageId, packId);
|
||||||
}
|
}
|
||||||
|
|
2
js/modules/data.d.ts
vendored
2
js/modules/data.d.ts
vendored
|
@ -8,7 +8,7 @@ export function updateStickerLastUsed(
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
export function updateStickerPackStatus(
|
export function updateStickerPackStatus(
|
||||||
packId: string,
|
packId: string,
|
||||||
status: 'advertised' | 'installed' | 'error' | 'pending',
|
status: 'known' | 'downloaded' | 'installed' | 'error' | 'pending',
|
||||||
options?: { timestamp: number }
|
options?: { timestamp: number }
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ const SQL_CHANNEL_KEY = 'sql-channel';
|
||||||
const ERASE_SQL_KEY = 'erase-sql-key';
|
const ERASE_SQL_KEY = 'erase-sql-key';
|
||||||
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
|
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
|
||||||
const ERASE_STICKERS_KEY = 'erase-stickers';
|
const ERASE_STICKERS_KEY = 'erase-stickers';
|
||||||
|
const ERASE_TEMP_KEY = 'erase-temp';
|
||||||
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
||||||
|
|
||||||
const _jobs = Object.create(null);
|
const _jobs = Object.create(null);
|
||||||
|
@ -965,6 +966,7 @@ async function removeOtherData() {
|
||||||
callChannel(ERASE_SQL_KEY),
|
callChannel(ERASE_SQL_KEY),
|
||||||
callChannel(ERASE_ATTACHMENTS_KEY),
|
callChannel(ERASE_ATTACHMENTS_KEY),
|
||||||
callChannel(ERASE_STICKERS_KEY),
|
callChannel(ERASE_STICKERS_KEY),
|
||||||
|
callChannel(ERASE_TEMP_KEY),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@ function initializeMigrations({
|
||||||
const {
|
const {
|
||||||
getPath,
|
getPath,
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
|
getTempPath,
|
||||||
createReader,
|
createReader,
|
||||||
createAbsolutePathGetter,
|
createAbsolutePathGetter,
|
||||||
createWriterForNew,
|
createWriterForNew,
|
||||||
|
@ -161,6 +162,12 @@ function initializeMigrations({
|
||||||
const deleteSticker = Attachments.createDeleter(stickersPath);
|
const deleteSticker = Attachments.createDeleter(stickersPath);
|
||||||
const readStickerData = createReader(stickersPath);
|
const readStickerData = createReader(stickersPath);
|
||||||
|
|
||||||
|
const tempPath = getTempPath(userDataPath);
|
||||||
|
const getAbsoluteTempPath = createAbsolutePathGetter(tempPath);
|
||||||
|
const writeNewTempData = createWriterForNew(tempPath);
|
||||||
|
const deleteTempFile = Attachments.createDeleter(tempPath);
|
||||||
|
const readTempData = createReader(tempPath);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attachmentsPath,
|
attachmentsPath,
|
||||||
copyIntoAttachmentsDirectory,
|
copyIntoAttachmentsDirectory,
|
||||||
|
@ -170,6 +177,7 @@ function initializeMigrations({
|
||||||
deleteOnDisk,
|
deleteOnDisk,
|
||||||
}),
|
}),
|
||||||
deleteSticker,
|
deleteSticker,
|
||||||
|
deleteTempFile,
|
||||||
getAbsoluteAttachmentPath,
|
getAbsoluteAttachmentPath,
|
||||||
getAbsoluteStickerPath,
|
getAbsoluteStickerPath,
|
||||||
getPlaceholderMigrations,
|
getPlaceholderMigrations,
|
||||||
|
@ -181,6 +189,7 @@ function initializeMigrations({
|
||||||
loadStickerData,
|
loadStickerData,
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
readStickerData,
|
readStickerData,
|
||||||
|
readTempData,
|
||||||
run,
|
run,
|
||||||
processNewAttachment: attachment =>
|
processNewAttachment: attachment =>
|
||||||
MessageType.processNewAttachment(attachment, {
|
MessageType.processNewAttachment(attachment, {
|
||||||
|
@ -200,6 +209,13 @@ function initializeMigrations({
|
||||||
getImageDimensions,
|
getImageDimensions,
|
||||||
logger,
|
logger,
|
||||||
}),
|
}),
|
||||||
|
processNewEphemeralSticker: stickerData =>
|
||||||
|
MessageType.processNewSticker(stickerData, {
|
||||||
|
writeNewStickerData: writeNewTempData,
|
||||||
|
getAbsoluteStickerPath: getAbsoluteTempPath,
|
||||||
|
getImageDimensions,
|
||||||
|
logger,
|
||||||
|
}),
|
||||||
upgradeMessageSchema: (message, options = {}) => {
|
upgradeMessageSchema: (message, options = {}) => {
|
||||||
const { maxVersion } = options;
|
const { maxVersion } = options;
|
||||||
|
|
||||||
|
|
10
js/modules/stickers.d.ts
vendored
10
js/modules/stickers.d.ts
vendored
|
@ -1 +1,11 @@
|
||||||
export function maybeDeletePack(packId: string): Promise<void>;
|
export function maybeDeletePack(packId: string): Promise<void>;
|
||||||
|
|
||||||
|
export function downloadStickerPack(
|
||||||
|
packId: string,
|
||||||
|
packKey: string,
|
||||||
|
options?: {
|
||||||
|
finalStatus?: 'installed' | 'downloaded';
|
||||||
|
messageId?: string;
|
||||||
|
fromSync?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<void>;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
textsecure,
|
textsecure,
|
||||||
Signal,
|
Signal,
|
||||||
log,
|
log,
|
||||||
|
navigator,
|
||||||
reduxStore,
|
reduxStore,
|
||||||
reduxActions,
|
reduxActions,
|
||||||
URL
|
URL
|
||||||
|
@ -9,7 +10,7 @@
|
||||||
|
|
||||||
const BLESSED_PACKS = {};
|
const BLESSED_PACKS = {};
|
||||||
|
|
||||||
const { isNumber, pick, reject, groupBy } = require('lodash');
|
const { isNumber, pick, reject, groupBy, values } = require('lodash');
|
||||||
const pMap = require('p-map');
|
const pMap = require('p-map');
|
||||||
const Queue = require('p-queue');
|
const Queue = require('p-queue');
|
||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
|
@ -34,6 +35,7 @@ module.exports = {
|
||||||
deletePack,
|
deletePack,
|
||||||
deletePackReference,
|
deletePackReference,
|
||||||
downloadStickerPack,
|
downloadStickerPack,
|
||||||
|
downloadEphemeralPack,
|
||||||
getDataFromLink,
|
getDataFromLink,
|
||||||
getInitialState,
|
getInitialState,
|
||||||
getInstalledStickerPacks,
|
getInstalledStickerPacks,
|
||||||
|
@ -44,6 +46,8 @@ module.exports = {
|
||||||
maybeDeletePack,
|
maybeDeletePack,
|
||||||
downloadQueuedPacks,
|
downloadQueuedPacks,
|
||||||
redactPackId,
|
redactPackId,
|
||||||
|
removeEphemeralPack,
|
||||||
|
savePackMetadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
let initialState = null;
|
let initialState = null;
|
||||||
|
@ -88,8 +92,8 @@ function getInstalledStickerPacks() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = Object.values(packs);
|
const items = Object.values(packs);
|
||||||
return values.filter(pack => pack.status === 'installed');
|
return items.filter(pack => pack.status === 'installed');
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadQueuedPacks() {
|
function downloadQueuedPacks() {
|
||||||
|
@ -113,7 +117,7 @@ function capturePacksToDownload(existingPackLookup) {
|
||||||
const existing = existingPackLookup[id];
|
const existing = existingPackLookup[id];
|
||||||
if (
|
if (
|
||||||
!existing ||
|
!existing ||
|
||||||
(existing.status !== 'advertised' && existing.status !== 'installed')
|
(existing.status !== 'downloaded' && existing.status !== 'installed')
|
||||||
) {
|
) {
|
||||||
toDownload[id] = {
|
toDownload[id] = {
|
||||||
id,
|
id,
|
||||||
|
@ -130,6 +134,18 @@ function capturePacksToDownload(existingPackLookup) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = existingPackLookup[id];
|
const existing = existingPackLookup[id];
|
||||||
|
|
||||||
|
// These packs should never end up in the database, but if they do we'll delete them
|
||||||
|
if (existing.status === 'ephemeral') {
|
||||||
|
deletePack(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't automatically download these; not until a user action kicks it off
|
||||||
|
if (existing.status === 'known') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (doesPackNeedDownload(existing)) {
|
if (doesPackNeedDownload(existing)) {
|
||||||
toDownload[id] = {
|
toDownload[id] = {
|
||||||
id,
|
id,
|
||||||
|
@ -147,14 +163,23 @@ function doesPackNeedDownload(pack) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stickerCount = Object.keys(pack.stickers || {}).length;
|
const { status, stickerCount } = pack;
|
||||||
return (
|
const stickersDownloaded = Object.keys(pack.stickers || {}).length;
|
||||||
!pack.status ||
|
|
||||||
pack.status === 'error' ||
|
if (
|
||||||
pack.status === 'pending' ||
|
(status === 'installed' || status === 'downloaded') &&
|
||||||
!pack.stickerCount ||
|
stickerCount > 0 &&
|
||||||
stickerCount < pack.stickerCount
|
stickersDownloaded >= stickerCount
|
||||||
);
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't understand a pack's status, we'll download it
|
||||||
|
// If a pack has any other status, we'll download it
|
||||||
|
// If a pack has zero stickers in it, we'll download it
|
||||||
|
// If a pack doesn't have enough downloaded stickers, we'll download it
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPacksForRedux() {
|
async function getPacksForRedux() {
|
||||||
|
@ -209,10 +234,15 @@ async function decryptSticker(packKey, ciphertext) {
|
||||||
return plaintext;
|
return plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadSticker(packId, packKey, proto) {
|
async function downloadSticker(packId, packKey, proto, options) {
|
||||||
|
const { ephemeral } = options || {};
|
||||||
|
|
||||||
const ciphertext = await textsecure.messaging.getSticker(packId, proto.id);
|
const ciphertext = await textsecure.messaging.getSticker(packId, proto.id);
|
||||||
const plaintext = await decryptSticker(packKey, ciphertext);
|
const plaintext = await decryptSticker(packKey, ciphertext);
|
||||||
const sticker = await Signal.Migrations.processNewSticker(plaintext);
|
|
||||||
|
const sticker = ephemeral
|
||||||
|
? await Signal.Migrations.processNewEphemeralSticker(plaintext, options)
|
||||||
|
: await Signal.Migrations.processNewSticker(plaintext, options);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...pick(proto, ['id', 'emoji']),
|
...pick(proto, ['id', 'emoji']),
|
||||||
|
@ -221,6 +251,156 @@ async function downloadSticker(packId, packKey, proto) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function savePackMetadata(packId, packKey, options = {}) {
|
||||||
|
const { messageId } = options;
|
||||||
|
|
||||||
|
const existing = getStickerPack(packId);
|
||||||
|
if (existing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { stickerPackAdded } = getReduxStickerActions();
|
||||||
|
const pack = {
|
||||||
|
id: packId,
|
||||||
|
key: packKey,
|
||||||
|
status: 'known',
|
||||||
|
};
|
||||||
|
stickerPackAdded(pack);
|
||||||
|
|
||||||
|
await createOrUpdateStickerPack(pack);
|
||||||
|
if (messageId) {
|
||||||
|
await addStickerPackReference(messageId, packId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeEphemeralPack(packId) {
|
||||||
|
const existing = getStickerPack(packId);
|
||||||
|
if (
|
||||||
|
existing.status !== 'ephemeral' &&
|
||||||
|
!(existing.status === 'error' && existing.attemptedStatus === 'ephemeral')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { removeStickerPack } = getReduxStickerActions();
|
||||||
|
removeStickerPack(packId);
|
||||||
|
|
||||||
|
const stickers = values(existing.stickers);
|
||||||
|
const paths = stickers.map(sticker => sticker.path);
|
||||||
|
await pMap(paths, Signal.Migrations.deleteTempFile, {
|
||||||
|
concurrency: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove it from database in case it made it there
|
||||||
|
await deleteStickerPack(packId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadEphemeralPack(packId, packKey) {
|
||||||
|
const {
|
||||||
|
stickerAdded,
|
||||||
|
stickerPackAdded,
|
||||||
|
stickerPackUpdated,
|
||||||
|
} = getReduxStickerActions();
|
||||||
|
|
||||||
|
const existingPack = getStickerPack(packId);
|
||||||
|
if (existingPack) {
|
||||||
|
log.warn(
|
||||||
|
`Ephemeral download for pack ${redactPackId(
|
||||||
|
packId
|
||||||
|
)} requested, we already know about it. Skipping.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Synchronous placeholder to help with race conditions
|
||||||
|
const placeholder = {
|
||||||
|
id: packId,
|
||||||
|
key: packKey,
|
||||||
|
status: 'ephemeral',
|
||||||
|
};
|
||||||
|
stickerPackAdded(placeholder);
|
||||||
|
|
||||||
|
const ciphertext = await textsecure.messaging.getStickerPackManifest(
|
||||||
|
packId
|
||||||
|
);
|
||||||
|
const plaintext = await decryptSticker(packKey, ciphertext);
|
||||||
|
const proto = textsecure.protobuf.StickerPack.decode(plaintext);
|
||||||
|
const firstStickerProto = proto.stickers ? proto.stickers[0] : null;
|
||||||
|
const stickerCount = proto.stickers.length;
|
||||||
|
|
||||||
|
const coverProto = proto.cover || firstStickerProto;
|
||||||
|
const coverStickerId = coverProto ? coverProto.id : null;
|
||||||
|
|
||||||
|
if (!coverProto || !isNumber(coverStickerId)) {
|
||||||
|
throw new Error(
|
||||||
|
`Sticker pack ${redactPackId(
|
||||||
|
packId
|
||||||
|
)} is malformed - it has no cover, and no stickers`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonCoverStickers = reject(
|
||||||
|
proto.stickers,
|
||||||
|
sticker => !isNumber(sticker.id) || sticker.id === coverStickerId
|
||||||
|
);
|
||||||
|
|
||||||
|
const coverIncludedInList = nonCoverStickers.length < stickerCount;
|
||||||
|
|
||||||
|
const pack = {
|
||||||
|
id: packId,
|
||||||
|
key: packKey,
|
||||||
|
coverStickerId,
|
||||||
|
stickerCount,
|
||||||
|
status: 'ephemeral',
|
||||||
|
...pick(proto, ['title', 'author']),
|
||||||
|
};
|
||||||
|
stickerPackAdded(pack);
|
||||||
|
|
||||||
|
const downloadStickerJob = async stickerProto => {
|
||||||
|
const stickerInfo = await downloadSticker(packId, packKey, stickerProto, {
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
const sticker = {
|
||||||
|
...stickerInfo,
|
||||||
|
isCoverOnly: !coverIncludedInList && stickerInfo.id === coverStickerId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusCheck = getStickerPackStatus(packId);
|
||||||
|
if (statusCheck !== 'ephemeral') {
|
||||||
|
throw new Error(
|
||||||
|
`Ephemeral download for pack ${redactPackId(
|
||||||
|
packId
|
||||||
|
)} interrupted by status change. Status is now ${statusCheck}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
stickerAdded(sticker);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Download the cover first
|
||||||
|
await downloadStickerJob(coverProto);
|
||||||
|
|
||||||
|
// Then the rest
|
||||||
|
await pMap(nonCoverStickers, downloadStickerJob, { concurrency: 3 });
|
||||||
|
} catch (error) {
|
||||||
|
// Because the user could install this pack while we are still downloading this
|
||||||
|
// ephemeral pack, we don't want to go change its status unless we're still in
|
||||||
|
// ephemeral mode.
|
||||||
|
const statusCheck = getStickerPackStatus(packId);
|
||||||
|
if (statusCheck === 'ephemeral') {
|
||||||
|
stickerPackUpdated(packId, {
|
||||||
|
attemptedStatus: 'ephemeral',
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
log.error(
|
||||||
|
`Ephemeral download error for sticker pack ${redactPackId(packId)}:`,
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function downloadStickerPack(packId, packKey, options = {}) {
|
async function downloadStickerPack(packId, packKey, options = {}) {
|
||||||
// This will ensure that only one download process is in progress at any given time
|
// This will ensure that only one download process is in progress at any given time
|
||||||
return downloadQueue.add(async () => {
|
return downloadQueue.add(async () => {
|
||||||
|
@ -244,7 +424,12 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
installStickerPack,
|
installStickerPack,
|
||||||
} = getReduxStickerActions();
|
} = getReduxStickerActions();
|
||||||
|
|
||||||
const finalStatus = options.finalStatus || 'advertised';
|
const finalStatus = options.finalStatus || 'downloaded';
|
||||||
|
if (finalStatus !== 'downloaded' && finalStatus !== 'installed') {
|
||||||
|
throw new Error(
|
||||||
|
`doDownloadStickerPack: invalid finalStatus of ${finalStatus} requested.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const existing = getStickerPack(packId);
|
const existing = getStickerPack(packId);
|
||||||
if (!doesPackNeedDownload(existing)) {
|
if (!doesPackNeedDownload(existing)) {
|
||||||
|
@ -256,7 +441,10 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadAttempts = (existing ? existing.downloadAttempts || 0 : 0) + 1;
|
// We don't count this as an attempt if we're offline
|
||||||
|
const attemptIncrement = navigator.onLine ? 1 : 0;
|
||||||
|
const downloadAttempts =
|
||||||
|
(existing ? existing.downloadAttempts || 0 : 0) + attemptIncrement;
|
||||||
if (downloadAttempts > 3) {
|
if (downloadAttempts > 3) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`Refusing to attempt another download for pack ${redactPackId(
|
`Refusing to attempt another download for pack ${redactPackId(
|
||||||
|
@ -280,6 +468,16 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
let nonCoverStickers;
|
let nonCoverStickers;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Synchronous placeholder to help with race conditions
|
||||||
|
const placeholder = {
|
||||||
|
id: packId,
|
||||||
|
key: packKey,
|
||||||
|
attemptedStatus: finalStatus,
|
||||||
|
downloadAttempts,
|
||||||
|
status: 'pending',
|
||||||
|
};
|
||||||
|
stickerPackAdded(placeholder);
|
||||||
|
|
||||||
const ciphertext = await textsecure.messaging.getStickerPackManifest(
|
const ciphertext = await textsecure.messaging.getStickerPackManifest(
|
||||||
packId
|
packId
|
||||||
);
|
);
|
||||||
|
@ -307,8 +505,10 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
coverIncludedInList = nonCoverStickers.length < stickerCount;
|
coverIncludedInList = nonCoverStickers.length < stickerCount;
|
||||||
|
|
||||||
// status can be:
|
// status can be:
|
||||||
|
// - 'known'
|
||||||
|
// - 'ephemeral' (should not hit database)
|
||||||
// - 'pending'
|
// - 'pending'
|
||||||
// - 'advertised'
|
// - 'downloaded'
|
||||||
// - 'error'
|
// - 'error'
|
||||||
// - 'installed'
|
// - 'installed'
|
||||||
const pack = {
|
const pack = {
|
||||||
|
@ -365,6 +565,13 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
// Then the rest
|
// Then the rest
|
||||||
await pMap(nonCoverStickers, downloadStickerJob, { concurrency: 3 });
|
await pMap(nonCoverStickers, downloadStickerJob, { concurrency: 3 });
|
||||||
|
|
||||||
|
// Allow for the user marking this pack as installed in the middle of our download;
|
||||||
|
// don't overwrite that status.
|
||||||
|
const existingStatus = getStickerPackStatus(packId);
|
||||||
|
if (existingStatus === 'installed') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (finalStatus === 'installed') {
|
if (finalStatus === 'installed') {
|
||||||
await installStickerPack(packId, packKey, { fromSync });
|
await installStickerPack(packId, packKey, { fromSync });
|
||||||
} else {
|
} else {
|
||||||
|
@ -380,11 +587,12 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
|
|
||||||
const errorState = 'error';
|
const errorStatus = 'error';
|
||||||
await updateStickerPackStatus(packId, errorState);
|
await updateStickerPackStatus(packId, errorStatus);
|
||||||
if (stickerPackUpdated) {
|
if (stickerPackUpdated) {
|
||||||
stickerPackUpdated(packId, {
|
stickerPackUpdated(packId, {
|
||||||
state: errorState,
|
attemptedStatus: finalStatus,
|
||||||
|
status: errorStatus,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1331,15 +1331,19 @@
|
||||||
dialog.focusCancel();
|
dialog.focusCancel();
|
||||||
},
|
},
|
||||||
|
|
||||||
showStickerPackPreview(packId) {
|
showStickerPackPreview(packId, packKey) {
|
||||||
if (!window.ENABLE_STICKER_SEND) {
|
if (!window.ENABLE_STICKER_SEND) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.Signal.Stickers.downloadEphemeralPack(packId, packKey);
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
packId,
|
packId,
|
||||||
onClose: () => {
|
onClose: async () => {
|
||||||
this.stickerPreviewModalView.remove();
|
this.stickerPreviewModalView.remove();
|
||||||
|
this.stickerPreviewModalView = null;
|
||||||
|
await window.Signal.Stickers.removeEphemeralPack(packId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1349,9 +1353,6 @@
|
||||||
window.reduxStore,
|
window.reduxStore,
|
||||||
props
|
props
|
||||||
),
|
),
|
||||||
onClose: () => {
|
|
||||||
this.stickerPreviewModalView = null;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1364,8 +1365,8 @@
|
||||||
}
|
}
|
||||||
const sticker = message.get('sticker');
|
const sticker = message.get('sticker');
|
||||||
if (sticker) {
|
if (sticker) {
|
||||||
const { packId } = sticker;
|
const { packId, packKey } = sticker;
|
||||||
this.showStickerPackPreview(packId);
|
this.showStickerPackPreview(packId, packKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1992,17 +1993,25 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
async getStickerPackPreview(url) {
|
async getStickerPackPreview(url) {
|
||||||
|
const isPackDownloaded = pack =>
|
||||||
|
pack && (pack.status === 'downloaded' || pack.status === 'installed');
|
||||||
const isPackValid = pack =>
|
const isPackValid = pack =>
|
||||||
pack && (pack.status === 'advertised' || pack.status === 'installed');
|
pack &&
|
||||||
|
(pack.status === 'ephemeral' ||
|
||||||
|
pack.status === 'downloaded' ||
|
||||||
|
pack.status === 'installed');
|
||||||
|
|
||||||
|
let id;
|
||||||
|
let key;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { id, key } = window.Signal.Stickers.getDataFromLink(url);
|
({ id, key } = window.Signal.Stickers.getDataFromLink(url));
|
||||||
const keyBytes = window.Signal.Crypto.bytesFromHexString(key);
|
const keyBytes = window.Signal.Crypto.bytesFromHexString(key);
|
||||||
const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes);
|
const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes);
|
||||||
|
|
||||||
const existing = window.Signal.Stickers.getStickerPack(id);
|
const existing = window.Signal.Stickers.getStickerPack(id);
|
||||||
if (!isPackValid(existing)) {
|
if (!isPackDownloaded(existing)) {
|
||||||
await window.Signal.Stickers.downloadStickerPack(id, keyBase64);
|
await window.Signal.Stickers.downloadEphemeralPack(id, keyBase64);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pack = window.Signal.Stickers.getStickerPack(id);
|
const pack = window.Signal.Stickers.getStickerPack(id);
|
||||||
|
@ -2015,9 +2024,10 @@
|
||||||
|
|
||||||
const { title, coverStickerId } = pack;
|
const { title, coverStickerId } = pack;
|
||||||
const sticker = pack.stickers[coverStickerId];
|
const sticker = pack.stickers[coverStickerId];
|
||||||
const data = await window.Signal.Migrations.readStickerData(
|
const data =
|
||||||
sticker.path
|
pack.status === 'ephemeral'
|
||||||
);
|
? await window.Signal.Migrations.readTempData(sticker.path)
|
||||||
|
: await window.Signal.Migrations.readStickerData(sticker.path);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
@ -2035,6 +2045,10 @@
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (id) {
|
||||||
|
await window.Signal.Stickers.removeEphemeralPack(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
10
main.js
10
main.js
|
@ -726,6 +726,14 @@ app.on('ready', async () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await attachments.clearTempPath(userDataPath);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
'main/ready: Error deleting temp dir:',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
await attachmentChannel.initialize({
|
await attachmentChannel.initialize({
|
||||||
configDir: userDataPath,
|
configDir: userDataPath,
|
||||||
cleanupOrphanedAttachments,
|
cleanupOrphanedAttachments,
|
||||||
|
@ -1034,7 +1042,7 @@ function handleSgnlLink(incomingUrl) {
|
||||||
if (command === 'addstickers' && mainWindow && mainWindow.webContents) {
|
if (command === 'addstickers' && mainWindow && mainWindow.webContents) {
|
||||||
const { pack_id: packId, pack_key: packKeyHex } = args;
|
const { pack_id: packId, pack_key: packKeyHex } = args;
|
||||||
const packKey = Buffer.from(packKeyHex, 'hex').toString('base64');
|
const packKey = Buffer.from(packKeyHex, 'hex').toString('base64');
|
||||||
mainWindow.webContents.send('add-sticker-pack', { packId, packKey });
|
mainWindow.webContents.send('show-sticker-pack', { packId, packKey });
|
||||||
} else {
|
} else {
|
||||||
console.error('Unhandled sgnl link');
|
console.error('Unhandled sgnl link');
|
||||||
}
|
}
|
||||||
|
|
11
preload.js
11
preload.js
|
@ -9,7 +9,7 @@ const { app } = electron.remote;
|
||||||
const { systemPreferences } = electron.remote.require('electron');
|
const { systemPreferences } = electron.remote.require('electron');
|
||||||
|
|
||||||
// Waiting for clients to implement changes on receive side
|
// Waiting for clients to implement changes on receive side
|
||||||
window.ENABLE_STICKER_SEND = false;
|
window.ENABLE_STICKER_SEND = true;
|
||||||
window.TIMESTAMP_VALIDATION = false;
|
window.TIMESTAMP_VALIDATION = false;
|
||||||
window.PAD_ALL_ATTACHMENTS = false;
|
window.PAD_ALL_ATTACHMENTS = false;
|
||||||
window.SEND_RECIPIENT_UPDATES = false;
|
window.SEND_RECIPIENT_UPDATES = false;
|
||||||
|
@ -175,11 +175,11 @@ ipc.on('delete-all-data', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('add-sticker-pack', (_event, info) => {
|
ipc.on('show-sticker-pack', (_event, info) => {
|
||||||
const { packId, packKey } = info;
|
const { packId, packKey } = info;
|
||||||
const { installStickerPack } = window.Events;
|
const { showStickerPack } = window.Events;
|
||||||
if (installStickerPack) {
|
if (showStickerPack) {
|
||||||
installStickerPack(packId, packKey);
|
showStickerPack(packId, packKey);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -306,6 +306,7 @@ window.moment.locale(locale);
|
||||||
const userDataPath = app.getPath('userData');
|
const userDataPath = app.getPath('userData');
|
||||||
window.baseAttachmentsPath = Attachments.getPath(userDataPath);
|
window.baseAttachmentsPath = Attachments.getPath(userDataPath);
|
||||||
window.baseStickersPath = Attachments.getStickersPath(userDataPath);
|
window.baseStickersPath = Attachments.getStickersPath(userDataPath);
|
||||||
|
window.baseTempPath = Attachments.getTempPath(userDataPath);
|
||||||
window.Signal = Signal.setup({
|
window.Signal = Signal.setup({
|
||||||
Attachments,
|
Attachments,
|
||||||
userDataPath,
|
userDataPath,
|
||||||
|
|
|
@ -3,7 +3,11 @@
|
||||||
.inbox,
|
.inbox,
|
||||||
.gutter {
|
.gutter {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
}
|
||||||
|
|
||||||
|
.inbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expired {
|
.expired {
|
||||||
|
@ -82,6 +86,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-stack {
|
.conversation-stack {
|
||||||
|
flex-grow: 1;
|
||||||
.conversation {
|
.conversation {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3376,6 +3376,14 @@
|
||||||
max-height: 20px;
|
max-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-sticker-picker__header__button__image--placeholder {
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
max-width: 20px;
|
||||||
|
max-height: 20px;
|
||||||
|
background-color: $color-gray-10;
|
||||||
|
}
|
||||||
|
|
||||||
.module-sticker-picker__body {
|
.module-sticker-picker__body {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -3553,6 +3561,11 @@
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
&__cover-placeholder {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: $color-gray-10;
|
||||||
|
}
|
||||||
|
|
||||||
&__meta {
|
&__meta {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -3681,9 +3694,23 @@
|
||||||
background: $color-gray-75;
|
background: $color-gray-75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
color: $color-core-red;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 80px 30px 80px;
|
||||||
|
font-family: Roboto;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
flex-shrink: 0;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
padding: 0 8px 0 16px;
|
padding: 0 8px 0 16px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -3727,6 +3754,18 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--placeholder {
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@include light-theme() {
|
||||||
|
background: $color-gray-05;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include dark-theme() {
|
||||||
|
background: $color-gray-60;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3913,6 +3952,11 @@
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
&__image-placeholder {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: $color-gray-10;
|
||||||
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
@ -3938,11 +3982,11 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
&__image {
|
// &__image {
|
||||||
width: 52px;
|
// width: 52px;
|
||||||
height: 52px;
|
// height: 52px;
|
||||||
background: #eaeaea;
|
// background: $color-gray-10;
|
||||||
}
|
// }
|
||||||
|
|
||||||
&__meta {
|
&__meta {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
|
@ -43,6 +43,8 @@ const packs = [
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
receivedPacks={[]}
|
receivedPacks={[]}
|
||||||
installedPacks={packs}
|
installedPacks={packs}
|
||||||
|
blessedPacks={[]}
|
||||||
|
knownPacks={[]}
|
||||||
onPickSticker={(packId, stickerId) =>
|
onPickSticker={(packId, stickerId) =>
|
||||||
console.log('onPickSticker', { packId, stickerId })
|
console.log('onPickSticker', { packId, stickerId })
|
||||||
}
|
}
|
||||||
|
@ -100,8 +102,10 @@ const packs = [
|
||||||
>
|
>
|
||||||
<StickerButton
|
<StickerButton
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
receivedPacks={[]}
|
receivedPacks={packs}
|
||||||
installedPacks={packs}
|
installedPacks={[]}
|
||||||
|
blessedPacks={[]}
|
||||||
|
knownPacks={[]}
|
||||||
onPickSticker={(packId, stickerId) =>
|
onPickSticker={(packId, stickerId) =>
|
||||||
console.log('onPickSticker', { packId, stickerId })
|
console.log('onPickSticker', { packId, stickerId })
|
||||||
}
|
}
|
||||||
|
@ -113,7 +117,75 @@ const packs = [
|
||||||
</util.ConversationContext>;
|
</util.ConversationContext>;
|
||||||
```
|
```
|
||||||
|
|
||||||
#### No Advertised Packs and No Installed Packs
|
#### Just known packs
|
||||||
|
|
||||||
|
Even with just known packs, the button should render.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const sticker1 = { id: 1, url: util.kitten164ObjectUrl, packId: 'foo' };
|
||||||
|
|
||||||
|
const packs = [
|
||||||
|
{
|
||||||
|
id: 'foo',
|
||||||
|
cover: sticker1,
|
||||||
|
stickers: Array(101)
|
||||||
|
.fill(0)
|
||||||
|
.map((n, id) => ({ ...sticker1, id })),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
<util.ConversationContext theme={util.theme}>
|
||||||
|
<StickerButton
|
||||||
|
i18n={util.i18n}
|
||||||
|
receivedPacks={[]}
|
||||||
|
installedPacks={[]}
|
||||||
|
knownPacks={packs}
|
||||||
|
blessedPacks={[]}
|
||||||
|
onPickSticker={(packId, stickerId) =>
|
||||||
|
console.log('onPickSticker', { packId, stickerId })
|
||||||
|
}
|
||||||
|
clearInstalledStickerPack={() => console.log('clearInstalledStickerPack')}
|
||||||
|
onClickAddPack={() => console.log('onClickAddPack')}
|
||||||
|
recentStickers={[]}
|
||||||
|
/>
|
||||||
|
</util.ConversationContext>;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Just blessed packs
|
||||||
|
|
||||||
|
Even with just blessed packs, the button should render.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const sticker1 = { id: 1, url: util.kitten164ObjectUrl, packId: 'foo' };
|
||||||
|
|
||||||
|
const packs = [
|
||||||
|
{
|
||||||
|
id: 'foo',
|
||||||
|
cover: sticker1,
|
||||||
|
stickers: Array(101)
|
||||||
|
.fill(0)
|
||||||
|
.map((n, id) => ({ ...sticker1, id })),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
<util.ConversationContext theme={util.theme}>
|
||||||
|
<StickerButton
|
||||||
|
i18n={util.i18n}
|
||||||
|
receivedPacks={[]}
|
||||||
|
installedPacks={[]}
|
||||||
|
blessedPacks={packs}
|
||||||
|
knownPacks={[]}
|
||||||
|
onPickSticker={(packId, stickerId) =>
|
||||||
|
console.log('onPickSticker', { packId, stickerId })
|
||||||
|
}
|
||||||
|
clearInstalledStickerPack={() => console.log('clearInstalledStickerPack')}
|
||||||
|
onClickAddPack={() => console.log('onClickAddPack')}
|
||||||
|
recentStickers={[]}
|
||||||
|
/>
|
||||||
|
</util.ConversationContext>;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### No packs at all
|
||||||
|
|
||||||
When there are no advertised packs and no installed packs the button should not render anything.
|
When there are no advertised packs and no installed packs the button should not render anything.
|
||||||
|
|
||||||
|
@ -123,6 +195,8 @@ When there are no advertised packs and no installed packs the button should not
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
receivedPacks={[]}
|
receivedPacks={[]}
|
||||||
installedPacks={[]}
|
installedPacks={[]}
|
||||||
|
blessedPacks={[]}
|
||||||
|
knownPacks={[]}
|
||||||
onPickSticker={(packId, stickerId) =>
|
onPickSticker={(packId, stickerId) =>
|
||||||
console.log('onPickSticker', { packId, stickerId })
|
console.log('onPickSticker', { packId, stickerId })
|
||||||
}
|
}
|
||||||
|
@ -188,6 +262,8 @@ const packs = [
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
receivedPacks={[]}
|
receivedPacks={[]}
|
||||||
installedPacks={packs}
|
installedPacks={packs}
|
||||||
|
blessedPacks={[]}
|
||||||
|
knownPacks={[]}
|
||||||
installedPack={packs[0]}
|
installedPack={packs[0]}
|
||||||
onPickSticker={(packId, stickerId) =>
|
onPickSticker={(packId, stickerId) =>
|
||||||
console.log('onPickSticker', { packId, stickerId })
|
console.log('onPickSticker', { packId, stickerId })
|
||||||
|
@ -257,6 +333,8 @@ const packs = [
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
receivedPacks={[]}
|
receivedPacks={[]}
|
||||||
installedPacks={packs}
|
installedPacks={packs}
|
||||||
|
blessedPacks={[]}
|
||||||
|
knownPacks={[]}
|
||||||
onPickSticker={(packId, stickerId) =>
|
onPickSticker={(packId, stickerId) =>
|
||||||
console.log('onPickSticker', { packId, stickerId })
|
console.log('onPickSticker', { packId, stickerId })
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ export type OwnProps = {
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
readonly receivedPacks: ReadonlyArray<StickerPackType>;
|
readonly receivedPacks: ReadonlyArray<StickerPackType>;
|
||||||
readonly installedPacks: ReadonlyArray<StickerPackType>;
|
readonly installedPacks: ReadonlyArray<StickerPackType>;
|
||||||
|
readonly blessedPacks: ReadonlyArray<StickerPackType>;
|
||||||
|
readonly knownPacks: ReadonlyArray<StickerPackType>;
|
||||||
readonly installedPack?: StickerPackType | null;
|
readonly installedPack?: StickerPackType | null;
|
||||||
readonly recentStickers: ReadonlyArray<StickerType>;
|
readonly recentStickers: ReadonlyArray<StickerType>;
|
||||||
readonly clearInstalledStickerPack: () => unknown;
|
readonly clearInstalledStickerPack: () => unknown;
|
||||||
|
@ -35,6 +37,8 @@ export const StickerButton = React.memo(
|
||||||
receivedPacks,
|
receivedPacks,
|
||||||
installedPack,
|
installedPack,
|
||||||
installedPacks,
|
installedPacks,
|
||||||
|
blessedPacks,
|
||||||
|
knownPacks,
|
||||||
showIntroduction,
|
showIntroduction,
|
||||||
clearShowIntroduction,
|
clearShowIntroduction,
|
||||||
showPickerHint,
|
showPickerHint,
|
||||||
|
@ -138,7 +142,12 @@ export const StickerButton = React.memo(
|
||||||
[installedPack, clearInstalledStickerPack]
|
[installedPack, clearInstalledStickerPack]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (installedPacks.length + receivedPacks.length === 0) {
|
const totalPacks =
|
||||||
|
knownPacks.length +
|
||||||
|
blessedPacks.length +
|
||||||
|
installedPacks.length +
|
||||||
|
receivedPacks.length;
|
||||||
|
if (totalPacks === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,11 +175,15 @@ export const StickerButton = React.memo(
|
||||||
role="button"
|
role="button"
|
||||||
onClick={clearInstalledStickerPack}
|
onClick={clearInstalledStickerPack}
|
||||||
>
|
>
|
||||||
<img
|
{installedPack.cover ? (
|
||||||
className="module-sticker-button__tooltip__image"
|
<img
|
||||||
src={installedPack.cover.url}
|
className="module-sticker-button__tooltip__image"
|
||||||
alt={installedPack.title}
|
src={installedPack.cover.url}
|
||||||
/>
|
alt={installedPack.title}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="module-sticker-button__tooltip__image-placeholder" />
|
||||||
|
)}
|
||||||
<span className="module-sticker-button__tooltip__text">
|
<span className="module-sticker-button__tooltip__text">
|
||||||
<span className="module-sticker-button__tooltip__text__title">
|
<span className="module-sticker-button__tooltip__text__title">
|
||||||
{installedPack.title}
|
{installedPack.title}
|
||||||
|
@ -202,7 +215,7 @@ export const StickerButton = React.memo(
|
||||||
role="button"
|
role="button"
|
||||||
onClick={handleClearIntroduction}
|
onClick={handleClearIntroduction}
|
||||||
>
|
>
|
||||||
<div className="module-sticker-button__tooltip--introduction__image" />
|
{/* <div className="module-sticker-button__tooltip--introduction__image" /> */}
|
||||||
<div className="module-sticker-button__tooltip--introduction__meta">
|
<div className="module-sticker-button__tooltip--introduction__meta">
|
||||||
<div className="module-sticker-button__tooltip--introduction__meta__title">
|
<div className="module-sticker-button__tooltip--introduction__meta__title">
|
||||||
{i18n('stickers--StickerManager--Introduction--Title')}
|
{i18n('stickers--StickerManager--Introduction--Title')}
|
||||||
|
|
|
@ -35,11 +35,11 @@ const packs = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const receivedPacks = packs.map(p => ({ ...p, status: 'advertised' }));
|
const receivedPacks = packs.map(p => ({ ...p, status: 'downloaded' }));
|
||||||
const installedPacks = packs.map(p => ({ ...p, status: 'installed' }));
|
const installedPacks = packs.map(p => ({ ...p, status: 'installed' }));
|
||||||
const blessedPacks = packs.map(p => ({
|
const blessedPacks = packs.map(p => ({
|
||||||
...p,
|
...p,
|
||||||
status: 'advertised',
|
status: 'downloaded',
|
||||||
isBlessed: true,
|
isBlessed: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -158,6 +158,64 @@ const noPacks = [];
|
||||||
</util.ConversationContext>;
|
</util.ConversationContext>;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### No with 'known'
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const sticker1 = { id: 1, url: util.kitten164ObjectUrl, packId: 'foo' };
|
||||||
|
const sticker2 = { id: 2, url: util.kitten264ObjectUrl, packId: 'bar' };
|
||||||
|
const sticker3 = { id: 3, url: util.kitten364ObjectUrl, packId: 'baz' };
|
||||||
|
|
||||||
|
const installedPacks = [
|
||||||
|
{
|
||||||
|
id: 'foo',
|
||||||
|
cover: sticker1,
|
||||||
|
title: 'Foo',
|
||||||
|
status: 'installed',
|
||||||
|
author: 'Foo McBarrington',
|
||||||
|
stickers: Array(101)
|
||||||
|
.fill(0)
|
||||||
|
.map((n, id) => ({ ...sticker1, id })),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const knownPacks = [
|
||||||
|
{
|
||||||
|
id: 'foo',
|
||||||
|
key: 'key1',
|
||||||
|
stickers: [],
|
||||||
|
state: 'known',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bar',
|
||||||
|
key: 'key2',
|
||||||
|
stickers: [],
|
||||||
|
state: 'known',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'baz',
|
||||||
|
key: 'key3',
|
||||||
|
stickers: [],
|
||||||
|
state: 'known',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const noPacks = [];
|
||||||
|
|
||||||
|
<util.ConversationContext theme={util.theme}>
|
||||||
|
<StickerManager
|
||||||
|
i18n={util.i18n}
|
||||||
|
installedPacks={installedPacks}
|
||||||
|
receivedPacks={noPacks}
|
||||||
|
blessedPacks={noPacks}
|
||||||
|
knownPacks={knownPacks}
|
||||||
|
installStickerPack={id => console.log('installStickerPack', id)}
|
||||||
|
downloadStickerPack={(packId, packKey, options) =>
|
||||||
|
console.log('downloadStickerPack', { packId, packKey, options })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</util.ConversationContext>;
|
||||||
|
```
|
||||||
|
|
||||||
#### No Packs at All
|
#### No Packs at All
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
|
|
|
@ -9,6 +9,8 @@ export type OwnProps = {
|
||||||
readonly installedPacks: ReadonlyArray<StickerPackType>;
|
readonly installedPacks: ReadonlyArray<StickerPackType>;
|
||||||
readonly receivedPacks: ReadonlyArray<StickerPackType>;
|
readonly receivedPacks: ReadonlyArray<StickerPackType>;
|
||||||
readonly blessedPacks: ReadonlyArray<StickerPackType>;
|
readonly blessedPacks: ReadonlyArray<StickerPackType>;
|
||||||
|
readonly knownPacks?: ReadonlyArray<StickerPackType>;
|
||||||
|
readonly downloadStickerPack: (packId: string, packKey: string) => unknown;
|
||||||
readonly installStickerPack: (packId: string, packKey: string) => unknown;
|
readonly installStickerPack: (packId: string, packKey: string) => unknown;
|
||||||
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
|
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
|
@ -20,7 +22,9 @@ export const StickerManager = React.memo(
|
||||||
({
|
({
|
||||||
installedPacks,
|
installedPacks,
|
||||||
receivedPacks,
|
receivedPacks,
|
||||||
|
knownPacks,
|
||||||
blessedPacks,
|
blessedPacks,
|
||||||
|
downloadStickerPack,
|
||||||
installStickerPack,
|
installStickerPack,
|
||||||
uninstallStickerPack,
|
uninstallStickerPack,
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -30,6 +34,15 @@ export const StickerManager = React.memo(
|
||||||
setPackToPreview,
|
setPackToPreview,
|
||||||
] = React.useState<StickerPackType | null>(null);
|
] = React.useState<StickerPackType | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!knownPacks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
knownPacks.forEach(pack => {
|
||||||
|
downloadStickerPack(pack.id, pack.key);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const clearPackToPreview = React.useCallback(
|
const clearPackToPreview = React.useCallback(
|
||||||
() => {
|
() => {
|
||||||
setPackToPreview(null);
|
setPackToPreview(null);
|
||||||
|
@ -51,6 +64,7 @@ export const StickerManager = React.memo(
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
pack={packToPreview}
|
pack={packToPreview}
|
||||||
onClose={clearPackToPreview}
|
onClose={clearPackToPreview}
|
||||||
|
downloadStickerPack={downloadStickerPack}
|
||||||
installStickerPack={installStickerPack}
|
installStickerPack={installStickerPack}
|
||||||
uninstallStickerPack={uninstallStickerPack}
|
uninstallStickerPack={uninstallStickerPack}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -91,11 +91,15 @@ export const StickerManagerPackRow = React.memo(
|
||||||
onClick={handleClickPreview}
|
onClick={handleClickPreview}
|
||||||
className="module-sticker-manager__pack-row"
|
className="module-sticker-manager__pack-row"
|
||||||
>
|
>
|
||||||
<img
|
{pack.cover ? (
|
||||||
src={pack.cover.url}
|
<img
|
||||||
alt={pack.title}
|
src={pack.cover.url}
|
||||||
className="module-sticker-manager__pack-row__cover"
|
alt={pack.title}
|
||||||
/>
|
className="module-sticker-manager__pack-row__cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="module-sticker-manager__pack-row__cover-placeholder" />
|
||||||
|
)}
|
||||||
<div className="module-sticker-manager__pack-row__meta">
|
<div className="module-sticker-manager__pack-row__meta">
|
||||||
<div className="module-sticker-manager__pack-row__meta__title">
|
<div className="module-sticker-manager__pack-row__meta__title">
|
||||||
{pack.title}
|
{pack.title}
|
||||||
|
@ -108,18 +112,18 @@ export const StickerManagerPackRow = React.memo(
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="module-sticker-manager__pack-row__controls">
|
<div className="module-sticker-manager__pack-row__controls">
|
||||||
{pack.status === 'advertised' ? (
|
{pack.status === 'installed' ? (
|
||||||
<StickerPackInstallButton
|
|
||||||
installed={false}
|
|
||||||
i18n={i18n}
|
|
||||||
onClick={handleInstall}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<StickerPackInstallButton
|
<StickerPackInstallButton
|
||||||
installed={true}
|
installed={true}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClick={handleUninstall}
|
onClick={handleUninstall}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<StickerPackInstallButton
|
||||||
|
installed={false}
|
||||||
|
i18n={i18n}
|
||||||
|
onClick={handleInstall}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -153,12 +153,16 @@ export const StickerPicker = React.memo(
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<img
|
{pack.cover ? (
|
||||||
className="module-sticker-picker__header__button__image"
|
<img
|
||||||
src={pack.cover.url}
|
className="module-sticker-picker__header__button__image"
|
||||||
alt={pack.title}
|
src={pack.cover.url}
|
||||||
title={pack.title}
|
alt={pack.title}
|
||||||
/>
|
title={pack.title}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="module-sticker-picker__header__button__image-placeholder" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,7 +9,7 @@ const pack = {
|
||||||
title: 'Foo',
|
title: 'Foo',
|
||||||
isBlessed: true,
|
isBlessed: true,
|
||||||
author: 'Foo McBarrington',
|
author: 'Foo McBarrington',
|
||||||
status: 'advertised',
|
status: 'downloaded',
|
||||||
stickers: Array(101)
|
stickers: Array(101)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map((n, id) => ({ ...abeSticker, id })),
|
.map((n, id) => ({ ...abeSticker, id })),
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
import { isNumber, range } from 'lodash';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { StickerPackInstallButton } from './StickerPackInstallButton';
|
import { StickerPackInstallButton } from './StickerPackInstallButton';
|
||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { LocalizerType } from '../../types/Util';
|
||||||
import { StickerPackType } from '../../state/ducks/stickers';
|
import { StickerPackType } from '../../state/ducks/stickers';
|
||||||
|
import { Spinner } from '../Spinner';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
readonly onClose: () => unknown;
|
readonly onClose: () => unknown;
|
||||||
|
readonly downloadStickerPack: (
|
||||||
|
packId: string,
|
||||||
|
packKey: string,
|
||||||
|
options?: { finalStatus?: 'installed' | 'downloaded' }
|
||||||
|
) => unknown;
|
||||||
readonly installStickerPack: (packId: string, packKey: string) => unknown;
|
readonly installStickerPack: (packId: string, packKey: string) => unknown;
|
||||||
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
|
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
|
||||||
readonly pack: StickerPackType;
|
readonly pack?: StickerPackType;
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,15 +29,57 @@ function focusRef(el: HTMLElement | null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderBody({ pack, i18n }: Props) {
|
||||||
|
if (pack && pack.status === 'error') {
|
||||||
|
return (
|
||||||
|
<div className="module-sticker-manager__preview-modal__container__error">
|
||||||
|
{i18n('stickers--StickerPreview--Error')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pack || pack.stickerCount === 0 || !isNumber(pack.stickerCount)) {
|
||||||
|
return <Spinner size="normal" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-sticker-manager__preview-modal__container__sticker-grid">
|
||||||
|
{pack.stickers.map(({ id, url }) => (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
className="module-sticker-manager__preview-modal__container__sticker-grid__cell"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="module-sticker-manager__preview-modal__container__sticker-grid__cell__image"
|
||||||
|
src={url}
|
||||||
|
alt={pack.title}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{range(pack.stickerCount - pack.stickers.length).map(i => (
|
||||||
|
<div
|
||||||
|
key={`placeholder-${i}`}
|
||||||
|
className={classNames(
|
||||||
|
'module-sticker-manager__preview-modal__container__sticker-grid__cell',
|
||||||
|
'module-sticker-manager__preview-modal__container__sticker-grid__cell--placeholder'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const StickerPreviewModal = React.memo(
|
export const StickerPreviewModal = React.memo(
|
||||||
// tslint:disable-next-line max-func-body-length
|
// tslint:disable-next-line max-func-body-length
|
||||||
({
|
(props: Props) => {
|
||||||
onClose,
|
const {
|
||||||
pack,
|
onClose,
|
||||||
i18n,
|
pack,
|
||||||
installStickerPack,
|
i18n,
|
||||||
uninstallStickerPack,
|
downloadStickerPack,
|
||||||
}: Props) => {
|
installStickerPack,
|
||||||
|
uninstallStickerPack,
|
||||||
|
} = props;
|
||||||
const [root, setRoot] = React.useState<HTMLElement | null>(null);
|
const [root, setRoot] = React.useState<HTMLElement | null>(null);
|
||||||
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
||||||
|
|
||||||
|
@ -40,15 +90,36 @@ export const StickerPreviewModal = React.memo(
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.body.removeChild(div);
|
document.body.removeChild(div);
|
||||||
setRoot(null);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isInstalled = pack.status === 'installed';
|
React.useEffect(() => {
|
||||||
|
if (pack && pack.status === 'known') {
|
||||||
|
downloadStickerPack(pack.id, pack.key);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
pack &&
|
||||||
|
pack.status === 'error' &&
|
||||||
|
(pack.attemptedStatus === 'downloaded' ||
|
||||||
|
pack.attemptedStatus === 'installed')
|
||||||
|
) {
|
||||||
|
downloadStickerPack(pack.id, pack.key, {
|
||||||
|
finalStatus: pack.attemptedStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isInstalled = Boolean(pack && pack.status === 'installed');
|
||||||
const handleToggleInstall = React.useCallback(
|
const handleToggleInstall = React.useCallback(
|
||||||
() => {
|
() => {
|
||||||
|
if (!pack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
setConfirmingUninstall(true);
|
setConfirmingUninstall(true);
|
||||||
|
} else if (pack.status === 'ephemeral') {
|
||||||
|
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
|
||||||
|
onClose();
|
||||||
} else {
|
} else {
|
||||||
installStickerPack(pack.id, pack.key);
|
installStickerPack(pack.id, pack.key);
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -59,6 +130,9 @@ export const StickerPreviewModal = React.memo(
|
||||||
|
|
||||||
const handleUninstall = React.useCallback(
|
const handleUninstall = React.useCallback(
|
||||||
() => {
|
() => {
|
||||||
|
if (!pack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
uninstallStickerPack(pack.id, pack.key);
|
uninstallStickerPack(pack.id, pack.key);
|
||||||
setConfirmingUninstall(false);
|
setConfirmingUninstall(false);
|
||||||
// onClose is called by the confirmation modal
|
// onClose is called by the confirmation modal
|
||||||
|
@ -119,42 +193,35 @@ export const StickerPreviewModal = React.memo(
|
||||||
className="module-sticker-manager__preview-modal__container__header__close-button"
|
className="module-sticker-manager__preview-modal__container__header__close-button"
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
<div className="module-sticker-manager__preview-modal__container__sticker-grid">
|
{renderBody(props)}
|
||||||
{pack.stickers.map(({ id, url }) => (
|
{pack && pack.status !== 'error' ? (
|
||||||
<div
|
<div className="module-sticker-manager__preview-modal__container__meta-overlay">
|
||||||
key={id}
|
<div className="module-sticker-manager__preview-modal__container__meta-overlay__info">
|
||||||
className="module-sticker-manager__preview-modal__container__sticker-grid__cell"
|
<h3 className="module-sticker-manager__preview-modal__container__meta-overlay__info__title">
|
||||||
>
|
{pack.title}
|
||||||
<img
|
{pack.isBlessed ? (
|
||||||
className="module-sticker-manager__preview-modal__container__sticker-grid__cell__image"
|
<span className="module-sticker-manager__preview-modal__container__meta-overlay__info__blessed-icon" />
|
||||||
src={url}
|
) : null}
|
||||||
alt={pack.title}
|
</h3>
|
||||||
/>
|
<h4 className="module-sticker-manager__preview-modal__container__meta-overlay__info__author">
|
||||||
|
{pack.author}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div className="module-sticker-manager__preview-modal__container__meta-overlay__install">
|
||||||
|
{pack.status === 'pending' ? (
|
||||||
|
<Spinner size="mini" />
|
||||||
|
) : (
|
||||||
|
<StickerPackInstallButton
|
||||||
|
ref={focusRef}
|
||||||
|
installed={isInstalled}
|
||||||
|
i18n={i18n}
|
||||||
|
onClick={handleToggleInstall}
|
||||||
|
blue={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="module-sticker-manager__preview-modal__container__meta-overlay">
|
|
||||||
<div className="module-sticker-manager__preview-modal__container__meta-overlay__info">
|
|
||||||
<h3 className="module-sticker-manager__preview-modal__container__meta-overlay__info__title">
|
|
||||||
{pack.title}
|
|
||||||
{pack.isBlessed ? (
|
|
||||||
<span className="module-sticker-manager__preview-modal__container__meta-overlay__info__blessed-icon" />
|
|
||||||
) : null}
|
|
||||||
</h3>
|
|
||||||
<h4 className="module-sticker-manager__preview-modal__container__meta-overlay__info__author">
|
|
||||||
{pack.author}
|
|
||||||
</h4>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="module-sticker-manager__preview-modal__container__meta-overlay__install">
|
) : null}
|
||||||
<StickerPackInstallButton
|
|
||||||
ref={focusRef}
|
|
||||||
installed={isInstalled}
|
|
||||||
i18n={i18n}
|
|
||||||
onClick={handleToggleInstall}
|
|
||||||
blue={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>,
|
</div>,
|
||||||
|
|
|
@ -4,10 +4,15 @@ import {
|
||||||
updateStickerLastUsed,
|
updateStickerLastUsed,
|
||||||
updateStickerPackStatus,
|
updateStickerPackStatus,
|
||||||
} from '../../../js/modules/data';
|
} from '../../../js/modules/data';
|
||||||
import { maybeDeletePack } from '../../../js/modules/stickers';
|
import {
|
||||||
|
downloadStickerPack as externalDownloadStickerPack,
|
||||||
|
maybeDeletePack,
|
||||||
|
} from '../../../js/modules/stickers';
|
||||||
import { sendStickerPackSync } from '../../shims/textsecure';
|
import { sendStickerPackSync } from '../../shims/textsecure';
|
||||||
import { trigger } from '../../shims/events';
|
import { trigger } from '../../shims/events';
|
||||||
|
|
||||||
|
import { NoopActionType } from './noop';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
export type StickerDBType = {
|
export type StickerDBType = {
|
||||||
|
@ -24,14 +29,20 @@ export type StickerPackDBType = {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly key: string;
|
readonly key: string;
|
||||||
|
|
||||||
readonly attemptedStatus: string;
|
readonly attemptedStatus: 'downloaded' | 'installed' | 'ephemeral';
|
||||||
readonly author: string;
|
readonly author: string;
|
||||||
readonly coverStickerId: number;
|
readonly coverStickerId: number;
|
||||||
readonly createdAt: number;
|
readonly createdAt: number;
|
||||||
readonly downloadAttempts: number;
|
readonly downloadAttempts: number;
|
||||||
readonly installedAt: number | null;
|
readonly installedAt: number | null;
|
||||||
readonly lastUsed: number;
|
readonly lastUsed: number;
|
||||||
readonly status: 'advertised' | 'installed' | 'pending' | 'error';
|
readonly status:
|
||||||
|
| 'known'
|
||||||
|
| 'ephemeral'
|
||||||
|
| 'downloaded'
|
||||||
|
| 'installed'
|
||||||
|
| 'pending'
|
||||||
|
| 'error';
|
||||||
readonly stickerCount: number;
|
readonly stickerCount: number;
|
||||||
readonly stickers: Dictionary<StickerDBType>;
|
readonly stickers: Dictionary<StickerDBType>;
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
|
@ -64,9 +75,16 @@ export type StickerPackType = {
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
readonly author: string;
|
readonly author: string;
|
||||||
readonly isBlessed: boolean;
|
readonly isBlessed: boolean;
|
||||||
readonly cover: StickerType;
|
readonly cover?: StickerType;
|
||||||
readonly lastUsed: number;
|
readonly lastUsed: number;
|
||||||
readonly status: 'advertised' | 'installed' | 'pending' | 'error';
|
readonly attemptedStatus?: 'downloaded' | 'installed' | 'ephemeral';
|
||||||
|
readonly status:
|
||||||
|
| 'known'
|
||||||
|
| 'ephemeral'
|
||||||
|
| 'downloaded'
|
||||||
|
| 'installed'
|
||||||
|
| 'pending'
|
||||||
|
| 'error';
|
||||||
readonly stickers: Array<StickerType>;
|
readonly stickers: Array<StickerType>;
|
||||||
readonly stickerCount: number;
|
readonly stickerCount: number;
|
||||||
};
|
};
|
||||||
|
@ -103,7 +121,7 @@ type ClearInstalledStickerPackAction = {
|
||||||
|
|
||||||
type UninstallStickerPackPayloadType = {
|
type UninstallStickerPackPayloadType = {
|
||||||
packId: string;
|
packId: string;
|
||||||
status: 'advertised';
|
status: 'downloaded';
|
||||||
installedAt: null;
|
installedAt: null;
|
||||||
recentStickers: Array<RecentStickerType>;
|
recentStickers: Array<RecentStickerType>;
|
||||||
};
|
};
|
||||||
|
@ -148,11 +166,13 @@ export type StickersActionType =
|
||||||
| UninstallStickerPackFulfilledAction
|
| UninstallStickerPackFulfilledAction
|
||||||
| StickerPackUpdatedAction
|
| StickerPackUpdatedAction
|
||||||
| StickerPackRemovedAction
|
| StickerPackRemovedAction
|
||||||
| UseStickerFulfilledAction;
|
| UseStickerFulfilledAction
|
||||||
|
| NoopActionType;
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
downloadStickerPack,
|
||||||
clearInstalledStickerPack,
|
clearInstalledStickerPack,
|
||||||
removeStickerPack,
|
removeStickerPack,
|
||||||
stickerAdded,
|
stickerAdded,
|
||||||
|
@ -191,6 +211,23 @@ function stickerPackAdded(payload: StickerPackDBType): StickerPackAddedAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadStickerPack(
|
||||||
|
packId: string,
|
||||||
|
packKey: string,
|
||||||
|
options?: { finalStatus?: 'installed' | 'downloaded' }
|
||||||
|
): NoopActionType {
|
||||||
|
const { finalStatus } = options || { finalStatus: undefined };
|
||||||
|
|
||||||
|
// We're just kicking this off, since it will generate more redux events
|
||||||
|
// tslint:disable-next-line:no-floating-promises
|
||||||
|
externalDownloadStickerPack(packId, packKey, { finalStatus });
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function installStickerPack(
|
function installStickerPack(
|
||||||
packId: string,
|
packId: string,
|
||||||
packKey: string,
|
packKey: string,
|
||||||
|
@ -246,7 +283,7 @@ async function doUninstallStickerPack(
|
||||||
): Promise<UninstallStickerPackPayloadType> {
|
): Promise<UninstallStickerPackPayloadType> {
|
||||||
const { fromSync } = options || { fromSync: false };
|
const { fromSync } = options || { fromSync: false };
|
||||||
|
|
||||||
const status = 'advertised';
|
const status = 'downloaded';
|
||||||
await updateStickerPackStatus(packId, status);
|
await updateStickerPackStatus(packId, status);
|
||||||
|
|
||||||
// If there are no more references, it should be removed
|
// If there are no more references, it should be removed
|
||||||
|
@ -277,6 +314,13 @@ function stickerPackUpdated(
|
||||||
packId: string,
|
packId: string,
|
||||||
patch: Partial<StickerPackDBType>
|
patch: Partial<StickerPackDBType>
|
||||||
): StickerPackUpdatedAction {
|
): StickerPackUpdatedAction {
|
||||||
|
const { status, attemptedStatus } = patch;
|
||||||
|
|
||||||
|
// We do this to trigger a toast, which is still done via Backbone
|
||||||
|
if (status === 'error' && attemptedStatus === 'installed') {
|
||||||
|
trigger('pack-install-failed');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'stickers/STICKER_PACK_UPDATED',
|
type: 'stickers/STICKER_PACK_UPDATED',
|
||||||
payload: {
|
payload: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { LocalizerType } from '../../types/Util';
|
||||||
export type UserStateType = {
|
export type UserStateType = {
|
||||||
attachmentsPath: string;
|
attachmentsPath: string;
|
||||||
stickersPath: string;
|
stickersPath: string;
|
||||||
|
tempPath: string;
|
||||||
ourNumber: string;
|
ourNumber: string;
|
||||||
regionCode: string;
|
regionCode: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
@ -45,6 +46,7 @@ function getEmptyState(): UserStateType {
|
||||||
return {
|
return {
|
||||||
attachmentsPath: 'missing',
|
attachmentsPath: 'missing',
|
||||||
stickersPath: 'missing',
|
stickersPath: 'missing',
|
||||||
|
tempPath: 'missing',
|
||||||
ourNumber: 'missing',
|
ourNumber: 'missing',
|
||||||
regionCode: 'missing',
|
regionCode: 'missing',
|
||||||
i18n: () => 'missing',
|
i18n: () => 'missing',
|
||||||
|
|
|
@ -20,13 +20,14 @@ import {
|
||||||
StickersStateType,
|
StickersStateType,
|
||||||
StickerType,
|
StickerType,
|
||||||
} from '../ducks/stickers';
|
} from '../ducks/stickers';
|
||||||
import { getStickersPath } from './user';
|
import { getStickersPath, getTempPath } from './user';
|
||||||
|
|
||||||
const getSticker = (
|
const getSticker = (
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
packId: string,
|
packId: string,
|
||||||
stickerId: number,
|
stickerId: number,
|
||||||
stickerPath: string
|
stickerPath: string,
|
||||||
|
tempPath: string
|
||||||
): StickerType | undefined => {
|
): StickerType | undefined => {
|
||||||
const pack = packs[packId];
|
const pack = packs[packId];
|
||||||
if (!pack) {
|
if (!pack) {
|
||||||
|
@ -38,20 +39,25 @@ const getSticker = (
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return translateStickerFromDB(sticker, stickerPath);
|
const isEphemeral = pack.status === 'ephemeral';
|
||||||
|
|
||||||
|
return translateStickerFromDB(sticker, stickerPath, tempPath, isEphemeral);
|
||||||
};
|
};
|
||||||
|
|
||||||
const translateStickerFromDB = (
|
const translateStickerFromDB = (
|
||||||
sticker: StickerDBType,
|
sticker: StickerDBType,
|
||||||
stickerPath: string
|
stickerPath: string,
|
||||||
|
tempPath: string,
|
||||||
|
isEphemeral: boolean
|
||||||
): StickerType => {
|
): StickerType => {
|
||||||
const { id, packId, emoji, path } = sticker;
|
const { id, packId, emoji, path } = sticker;
|
||||||
|
const prefix = isEphemeral ? tempPath : stickerPath;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
packId,
|
packId,
|
||||||
emoji,
|
emoji,
|
||||||
url: join(stickerPath, path),
|
url: join(prefix, path),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,9 +65,11 @@ export const translatePackFromDB = (
|
||||||
pack: StickerPackDBType,
|
pack: StickerPackDBType,
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string
|
stickersPath: string,
|
||||||
|
tempPath: string
|
||||||
) => {
|
) => {
|
||||||
const { id, stickers, coverStickerId } = pack;
|
const { id, stickers, status, coverStickerId } = pack;
|
||||||
|
const isEphemeral = status === 'ephemeral';
|
||||||
|
|
||||||
// Sometimes sticker packs have a cover which isn't included in their set of stickers.
|
// Sometimes sticker packs have a cover which isn't included in their set of stickers.
|
||||||
// We don't want to show cover-only images when previewing or picking from a pack.
|
// We don't want to show cover-only images when previewing or picking from a pack.
|
||||||
|
@ -70,13 +78,13 @@ export const translatePackFromDB = (
|
||||||
sticker => sticker.isCoverOnly
|
sticker => sticker.isCoverOnly
|
||||||
);
|
);
|
||||||
const translatedStickers = map(filteredStickers, sticker =>
|
const translatedStickers = map(filteredStickers, sticker =>
|
||||||
translateStickerFromDB(sticker, stickersPath)
|
translateStickerFromDB(sticker, stickersPath, tempPath, isEphemeral)
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...pack,
|
...pack,
|
||||||
isBlessed: Boolean(blessedPacks[id]),
|
isBlessed: Boolean(blessedPacks[id]),
|
||||||
cover: getSticker(packs, id, coverStickerId, stickersPath),
|
cover: getSticker(packs, id, coverStickerId, stickersPath, tempPath),
|
||||||
stickers: sortBy(translatedStickers, sticker => sticker.id),
|
stickers: sortBy(translatedStickers, sticker => sticker.id),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -86,18 +94,15 @@ const filterAndTransformPacks = (
|
||||||
packFilter: (sticker: StickerPackDBType) => boolean,
|
packFilter: (sticker: StickerPackDBType) => boolean,
|
||||||
packSort: (sticker: StickerPackDBType) => any,
|
packSort: (sticker: StickerPackDBType) => any,
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string
|
stickersPath: string,
|
||||||
|
tempPath: string
|
||||||
): Array<StickerPackType> => {
|
): Array<StickerPackType> => {
|
||||||
const list = filter(packs, packFilter);
|
const list = filter(packs, packFilter);
|
||||||
const sorted = orderBy<StickerPackDBType>(list, packSort, ['desc']);
|
const sorted = orderBy<StickerPackDBType>(list, packSort, ['desc']);
|
||||||
|
|
||||||
const ready = sorted.map(pack =>
|
return sorted.map(pack =>
|
||||||
translatePackFromDB(pack, packs, blessedPacks, stickersPath)
|
translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath)
|
||||||
);
|
);
|
||||||
|
|
||||||
// We're explicitly forcing pack.cover to be truthy here, but TypeScript doesn't
|
|
||||||
// understand that.
|
|
||||||
return ready.filter(pack => Boolean(pack.cover)) as Array<StickerPackType>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStickers = (state: StateType) => state.stickers;
|
const getStickers = (state: StateType) => state.stickers;
|
||||||
|
@ -121,14 +126,16 @@ export const getRecentStickers = createSelector(
|
||||||
getRecents,
|
getRecents,
|
||||||
getPacks,
|
getPacks,
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
|
getTempPath,
|
||||||
(
|
(
|
||||||
recents: Array<RecentStickerType>,
|
recents: Array<RecentStickerType>,
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
stickersPath: string
|
stickersPath: string,
|
||||||
|
tempPath: string
|
||||||
) => {
|
) => {
|
||||||
return compact(
|
return compact(
|
||||||
recents.map(({ packId, stickerId }) => {
|
recents.map(({ packId, stickerId }) => {
|
||||||
return getSticker(packs, packId, stickerId, stickersPath);
|
return getSticker(packs, packId, stickerId, stickersPath, tempPath);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -138,17 +145,20 @@ export const getInstalledStickerPacks = createSelector(
|
||||||
getPacks,
|
getPacks,
|
||||||
getBlessedPacks,
|
getBlessedPacks,
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
|
getTempPath,
|
||||||
(
|
(
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string
|
stickersPath: string,
|
||||||
|
tempPath: string
|
||||||
): Array<StickerPackType> => {
|
): Array<StickerPackType> => {
|
||||||
return filterAndTransformPacks(
|
return filterAndTransformPacks(
|
||||||
packs,
|
packs,
|
||||||
pack => pack.status === 'installed',
|
pack => pack.status === 'installed',
|
||||||
pack => pack.installedAt,
|
pack => pack.installedAt,
|
||||||
blessedPacks,
|
blessedPacks,
|
||||||
stickersPath
|
stickersPath,
|
||||||
|
tempPath
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -169,19 +179,22 @@ export const getReceivedStickerPacks = createSelector(
|
||||||
getPacks,
|
getPacks,
|
||||||
getBlessedPacks,
|
getBlessedPacks,
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
|
getTempPath,
|
||||||
(
|
(
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string
|
stickersPath: string,
|
||||||
|
tempPath: string
|
||||||
): Array<StickerPackType> => {
|
): Array<StickerPackType> => {
|
||||||
return filterAndTransformPacks(
|
return filterAndTransformPacks(
|
||||||
packs,
|
packs,
|
||||||
pack =>
|
pack =>
|
||||||
(pack.status === 'advertised' || pack.status === 'pending') &&
|
(pack.status === 'downloaded' || pack.status === 'pending') &&
|
||||||
!blessedPacks[pack.id],
|
!blessedPacks[pack.id],
|
||||||
pack => pack.createdAt,
|
pack => pack.createdAt,
|
||||||
blessedPacks,
|
blessedPacks,
|
||||||
stickersPath
|
stickersPath,
|
||||||
|
tempPath
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -190,17 +203,42 @@ export const getBlessedStickerPacks = createSelector(
|
||||||
getPacks,
|
getPacks,
|
||||||
getBlessedPacks,
|
getBlessedPacks,
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
|
getTempPath,
|
||||||
(
|
(
|
||||||
packs: Dictionary<StickerPackDBType>,
|
packs: Dictionary<StickerPackDBType>,
|
||||||
blessedPacks: Dictionary<boolean>,
|
blessedPacks: Dictionary<boolean>,
|
||||||
stickersPath: string
|
stickersPath: string,
|
||||||
|
tempPath: string
|
||||||
): Array<StickerPackType> => {
|
): Array<StickerPackType> => {
|
||||||
return filterAndTransformPacks(
|
return filterAndTransformPacks(
|
||||||
packs,
|
packs,
|
||||||
pack => blessedPacks[pack.id] && pack.status !== 'installed',
|
pack => blessedPacks[pack.id] && pack.status !== 'installed',
|
||||||
pack => pack.createdAt,
|
pack => pack.createdAt,
|
||||||
blessedPacks,
|
blessedPacks,
|
||||||
stickersPath
|
stickersPath,
|
||||||
|
tempPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getKnownStickerPacks = createSelector(
|
||||||
|
getPacks,
|
||||||
|
getBlessedPacks,
|
||||||
|
getStickersPath,
|
||||||
|
getTempPath,
|
||||||
|
(
|
||||||
|
packs: Dictionary<StickerPackDBType>,
|
||||||
|
blessedPacks: Dictionary<boolean>,
|
||||||
|
stickersPath: string,
|
||||||
|
tempPath: string
|
||||||
|
): Array<StickerPackType> => {
|
||||||
|
return filterAndTransformPacks(
|
||||||
|
packs,
|
||||||
|
pack => !blessedPacks[pack.id] && pack.status === 'known',
|
||||||
|
pack => pack.createdAt,
|
||||||
|
blessedPacks,
|
||||||
|
stickersPath,
|
||||||
|
tempPath
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,3 +31,8 @@ export const getStickersPath = createSelector(
|
||||||
getUser,
|
getUser,
|
||||||
(state: UserStateType): string => state.stickersPath
|
(state: UserStateType): string => state.stickersPath
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getTempPath = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): string => state.tempPath
|
||||||
|
);
|
||||||
|
|
|
@ -6,7 +6,9 @@ import { StateType } from '../reducer';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
|
getBlessedStickerPacks,
|
||||||
getInstalledStickerPacks,
|
getInstalledStickerPacks,
|
||||||
|
getKnownStickerPacks,
|
||||||
getReceivedStickerPacks,
|
getReceivedStickerPacks,
|
||||||
getRecentlyInstalledStickerPack,
|
getRecentlyInstalledStickerPack,
|
||||||
getRecentStickers,
|
getRecentStickers,
|
||||||
|
@ -15,6 +17,9 @@ import {
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
const receivedPacks = getReceivedStickerPacks(state);
|
const receivedPacks = getReceivedStickerPacks(state);
|
||||||
const installedPacks = getInstalledStickerPacks(state);
|
const installedPacks = getInstalledStickerPacks(state);
|
||||||
|
const blessedPacks = getBlessedStickerPacks(state);
|
||||||
|
const knownPacks = getKnownStickerPacks(state);
|
||||||
|
|
||||||
const recentStickers = getRecentStickers(state);
|
const recentStickers = getRecentStickers(state);
|
||||||
const installedPack = getRecentlyInstalledStickerPack(state);
|
const installedPack = getRecentlyInstalledStickerPack(state);
|
||||||
const showIntroduction = get(
|
const showIntroduction = get(
|
||||||
|
@ -29,6 +34,8 @@ const mapStateToProps = (state: StateType) => {
|
||||||
return {
|
return {
|
||||||
receivedPacks,
|
receivedPacks,
|
||||||
installedPack,
|
installedPack,
|
||||||
|
blessedPacks,
|
||||||
|
knownPacks,
|
||||||
installedPacks,
|
installedPacks,
|
||||||
recentStickers,
|
recentStickers,
|
||||||
showIntroduction,
|
showIntroduction,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { getIntl } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
getBlessedStickerPacks,
|
getBlessedStickerPacks,
|
||||||
getInstalledStickerPacks,
|
getInstalledStickerPacks,
|
||||||
|
getKnownStickerPacks,
|
||||||
getReceivedStickerPacks,
|
getReceivedStickerPacks,
|
||||||
} from '../selectors/stickers';
|
} from '../selectors/stickers';
|
||||||
|
|
||||||
|
@ -14,11 +15,13 @@ const mapStateToProps = (state: StateType) => {
|
||||||
const blessedPacks = getBlessedStickerPacks(state);
|
const blessedPacks = getBlessedStickerPacks(state);
|
||||||
const receivedPacks = getReceivedStickerPacks(state);
|
const receivedPacks = getReceivedStickerPacks(state);
|
||||||
const installedPacks = getInstalledStickerPacks(state);
|
const installedPacks = getInstalledStickerPacks(state);
|
||||||
|
const knownPacks = getKnownStickerPacks(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
blessedPacks,
|
blessedPacks,
|
||||||
receivedPacks,
|
receivedPacks,
|
||||||
installedPacks,
|
installedPacks,
|
||||||
|
knownPacks,
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { mapDispatchToProps } from '../actions';
|
||||||
import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal';
|
import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal';
|
||||||
import { StateType } from '../reducer';
|
import { StateType } from '../reducer';
|
||||||
|
|
||||||
import { getIntl, getStickersPath } from '../selectors/user';
|
import { getIntl, getStickersPath, getTempPath } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
getBlessedPacks,
|
getBlessedPacks,
|
||||||
getPacks,
|
getPacks,
|
||||||
|
@ -18,33 +18,17 @@ type ExternalProps = {
|
||||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
const { packId } = props;
|
const { packId } = props;
|
||||||
const stickersPath = getStickersPath(state);
|
const stickersPath = getStickersPath(state);
|
||||||
|
const tempPath = getTempPath(state);
|
||||||
|
|
||||||
const packs = getPacks(state);
|
const packs = getPacks(state);
|
||||||
const blessedPacks = getBlessedPacks(state);
|
const blessedPacks = getBlessedPacks(state);
|
||||||
const pack = packs[packId];
|
const pack = packs[packId];
|
||||||
|
|
||||||
if (!pack) {
|
|
||||||
throw new Error(`Cannot find pack ${packId}`);
|
|
||||||
}
|
|
||||||
const translated = translatePackFromDB(
|
|
||||||
pack,
|
|
||||||
packs,
|
|
||||||
blessedPacks,
|
|
||||||
stickersPath
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
pack: {
|
pack: pack
|
||||||
...translated,
|
? translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath)
|
||||||
cover: translated.cover
|
: undefined,
|
||||||
? translated.cover
|
|
||||||
: {
|
|
||||||
id: 0,
|
|
||||||
url: 'nonexistent',
|
|
||||||
packId,
|
|
||||||
emoji: 'WTF',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,6 +20,8 @@ import mkdirp from 'mkdirp';
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
import { app, BrowserWindow, dialog } from 'electron';
|
import { app, BrowserWindow, dialog } from 'electron';
|
||||||
|
|
||||||
|
import { getTempPath } from '../../app/attachments';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as packageJson from '../../package.json';
|
import * as packageJson from '../../package.json';
|
||||||
import { getSignatureFileName } from './signature';
|
import { getSignatureFileName } from './signature';
|
||||||
|
@ -269,7 +271,7 @@ function getGotOptions(): GotOptions<null> {
|
||||||
|
|
||||||
function getBaseTempDir() {
|
function getBaseTempDir() {
|
||||||
// We only use tmpdir() when this code is run outside of an Electron app (as in: tests)
|
// We only use tmpdir() when this code is run outside of an Electron app (as in: tests)
|
||||||
return app ? join(app.getPath('userData'), 'temp') : tmpdir();
|
return app ? getTempPath(app.getPath('userData')) : tmpdir();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTempDir() {
|
export async function createTempDir() {
|
||||||
|
@ -303,11 +305,6 @@ export function getPrintableError(error: Error) {
|
||||||
return error && error.stack ? error.stack : error;
|
return error && error.stack ? error.stack : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBaseTempDir() {
|
|
||||||
const baseTempDir = getBaseTempDir();
|
|
||||||
await rimrafPromise(baseTempDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCliOptions<T>(options: any): T {
|
export function getCliOptions<T>(options: any): T {
|
||||||
const parser = createParser({ options });
|
const parser = createParser({ options });
|
||||||
const cliOptions = parser.parse(process.argv);
|
const cliOptions = parser.parse(process.argv);
|
||||||
|
|
|
@ -3,12 +3,7 @@ import { BrowserWindow } from 'electron';
|
||||||
|
|
||||||
import { start as startMacOS } from './macos';
|
import { start as startMacOS } from './macos';
|
||||||
import { start as startWindows } from './windows';
|
import { start as startWindows } from './windows';
|
||||||
import {
|
import { LoggerType, MessagesType } from './common';
|
||||||
deleteBaseTempDir,
|
|
||||||
getPrintableError,
|
|
||||||
LoggerType,
|
|
||||||
MessagesType,
|
|
||||||
} from './common';
|
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
|
@ -39,15 +34,6 @@ export async function start(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteBaseTempDir();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(
|
|
||||||
'updater/start: Error deleting temp dir:',
|
|
||||||
getPrintableError(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform === 'win32') {
|
if (platform === 'win32') {
|
||||||
await startWindows(getMainWindow, messages, logger);
|
await startWindows(getMainWindow, messages, logger);
|
||||||
} else if (platform === 'darwin') {
|
} else if (platform === 'darwin') {
|
||||||
|
|
|
@ -227,11 +227,19 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-09-19T18:13:29.628Z"
|
"updated": "2018-09-19T18:13:29.628Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "js/modules/emojis.js",
|
||||||
|
"line": "async function load() {",
|
||||||
|
"lineNumber": 13,
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2019-05-23T22:27:53.554Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "js/modules/stickers.js",
|
"path": "js/modules/stickers.js",
|
||||||
"line": "async function load() {",
|
"line": "async function load() {",
|
||||||
"lineNumber": 53,
|
"lineNumber": 57,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-04-26T17:48:30.675Z"
|
"updated": "2019-04-26T17:48:30.675Z"
|
||||||
},
|
},
|
||||||
|
@ -6066,13 +6074,5 @@
|
||||||
"lineNumber": 60,
|
"lineNumber": 60,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-05-02T20:44:56.470Z"
|
"updated": "2019-05-02T20:44:56.470Z"
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-load(",
|
|
||||||
"path": "js/modules/emojis.js",
|
|
||||||
"line": "async function load() {",
|
|
||||||
"lineNumber": 13,
|
|
||||||
"reasonCategory": "falseMatch",
|
|
||||||
"updated": "2019-05-23T22:27:53.554Z"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
Loading…
Reference in a new issue