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
35 changed files with 925 additions and 249 deletions
|
@ -1835,6 +1835,12 @@
|
|||
"message": "Sticker Pack",
|
||||
"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": {
|
||||
"message": "No emoji found",
|
||||
"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_STICKERS_KEY = 'erase-stickers';
|
||||
const ERASE_TEMP_KEY = 'erase-temp';
|
||||
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
||||
|
||||
async function initialize({ configDir, cleanupOrphanedAttachments }) {
|
||||
|
@ -22,6 +23,18 @@ async function initialize({ configDir, cleanupOrphanedAttachments }) {
|
|||
|
||||
const attachmentsDir = Attachments.getPath(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 => {
|
||||
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 STICKER_PATH = 'stickers.noindex';
|
||||
const TEMP_PATH = 'temp';
|
||||
|
||||
exports.getAllAttachments = async userDataPath => {
|
||||
const dir = exports.getPath(userDataPath);
|
||||
|
@ -42,6 +43,20 @@ exports.getStickersPath = userDataPath => {
|
|||
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 ->
|
||||
// RelativePath ->
|
||||
// IO (Promise ArrayBuffer)
|
||||
|
|
69
app/sql.js
69
app/sql.js
|
@ -1193,14 +1193,14 @@ async function updateConversation(data) {
|
|||
|
||||
await db.run(
|
||||
`UPDATE conversations SET
|
||||
json = $json,
|
||||
json = $json,
|
||||
|
||||
active_at = $active_at,
|
||||
type = $type,
|
||||
members = $members,
|
||||
name = $name,
|
||||
profileName = $profileName
|
||||
WHERE id = $id;`,
|
||||
active_at = $active_at,
|
||||
type = $type,
|
||||
members = $members,
|
||||
name = $name,
|
||||
profileName = $profileName
|
||||
WHERE id = $id;`,
|
||||
{
|
||||
$id: id,
|
||||
$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(
|
||||
`INSERT OR REPLACE INTO sticker_packs (
|
||||
`INSERT INTO sticker_packs (
|
||||
attemptedStatus,
|
||||
author,
|
||||
coverStickerId,
|
||||
|
@ -1907,20 +1945,7 @@ async function createOrUpdateStickerPack(pack) {
|
|||
$stickerCount,
|
||||
$title
|
||||
)`,
|
||||
{
|
||||
$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,
|
||||
}
|
||||
payload
|
||||
);
|
||||
}
|
||||
async function updateStickerPackStatus(id, status, options) {
|
||||
|
|
|
@ -302,22 +302,25 @@
|
|||
await window.Signal.Data.shutdown();
|
||||
},
|
||||
|
||||
installStickerPack: async (id, key) => {
|
||||
const status = window.Signal.Stickers.getStickerPackStatus(id);
|
||||
showStickerPack: async (packId, key) => {
|
||||
// Kick off the download
|
||||
window.Signal.Stickers.downloadEphemeralPack(packId, key);
|
||||
|
||||
if (status === 'installed') {
|
||||
return;
|
||||
}
|
||||
const props = {
|
||||
packId,
|
||||
onClose: async () => {
|
||||
stickerPreviewModalView.remove();
|
||||
await window.Signal.Stickers.removeEphemeralPack(packId);
|
||||
},
|
||||
};
|
||||
|
||||
if (status === 'advertised') {
|
||||
await window.reduxActions.stickers.installStickerPack(id, key, {
|
||||
fromSync: true,
|
||||
});
|
||||
} else {
|
||||
await window.Signal.Stickers.downloadStickerPack(id, key, {
|
||||
finalStatus: 'installed',
|
||||
});
|
||||
}
|
||||
const stickerPreviewModalView = new Whisper.ReactWrapperView({
|
||||
className: 'sticker-preview-modal-wrapper',
|
||||
JSX: Signal.State.Roots.createStickerPreviewModal(
|
||||
window.reduxStore,
|
||||
props
|
||||
),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -464,6 +467,7 @@
|
|||
user: {
|
||||
attachmentsPath: window.baseAttachmentsPath,
|
||||
stickersPath: window.baseStickersPath,
|
||||
tempPath: window.baseTempPath,
|
||||
regionCode: window.storage.get('regionCode'),
|
||||
ourNumber: textsecure.storage.user.getNumber(),
|
||||
i18n: window.i18n,
|
||||
|
@ -1056,7 +1060,7 @@
|
|||
fromSync: true,
|
||||
});
|
||||
} else if (isInstall) {
|
||||
if (status === 'advertised') {
|
||||
if (status === 'downloaded') {
|
||||
window.reduxActions.stickers.installStickerPack(id, key, {
|
||||
fromSync: true,
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
const {
|
||||
copyStickerToAttachments,
|
||||
deletePackReference,
|
||||
downloadStickerPack,
|
||||
savePackMetadata,
|
||||
getStickerPackStatus,
|
||||
} = window.Signal.Stickers;
|
||||
const { addStickerPackReference } = window.Signal.Data;
|
||||
|
@ -1467,7 +1467,7 @@
|
|||
const status = getStickerPackStatus(packId);
|
||||
let data;
|
||||
|
||||
if (status && status !== 'pending' && status !== 'error') {
|
||||
if (status && (status === 'downloaded' || status === 'installed')) {
|
||||
try {
|
||||
const copiedSticker = await copyStickerToAttachments(
|
||||
packId,
|
||||
|
@ -1492,8 +1492,8 @@
|
|||
});
|
||||
}
|
||||
if (!status) {
|
||||
// kick off the download without waiting
|
||||
downloadStickerPack(packId, packKey, { messageId });
|
||||
// Save the packId/packKey for future download/install
|
||||
savePackMetadata(packId, packKey, { messageId });
|
||||
} else {
|
||||
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>;
|
||||
export function updateStickerPackStatus(
|
||||
packId: string,
|
||||
status: 'advertised' | 'installed' | 'error' | 'pending',
|
||||
status: 'known' | 'downloaded' | 'installed' | 'error' | 'pending',
|
||||
options?: { timestamp: number }
|
||||
): Promise<void>;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ const SQL_CHANNEL_KEY = 'sql-channel';
|
|||
const ERASE_SQL_KEY = 'erase-sql-key';
|
||||
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
|
||||
const ERASE_STICKERS_KEY = 'erase-stickers';
|
||||
const ERASE_TEMP_KEY = 'erase-temp';
|
||||
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
||||
|
||||
const _jobs = Object.create(null);
|
||||
|
@ -965,6 +966,7 @@ async function removeOtherData() {
|
|||
callChannel(ERASE_SQL_KEY),
|
||||
callChannel(ERASE_ATTACHMENTS_KEY),
|
||||
callChannel(ERASE_STICKERS_KEY),
|
||||
callChannel(ERASE_TEMP_KEY),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,7 @@ function initializeMigrations({
|
|||
const {
|
||||
getPath,
|
||||
getStickersPath,
|
||||
getTempPath,
|
||||
createReader,
|
||||
createAbsolutePathGetter,
|
||||
createWriterForNew,
|
||||
|
@ -161,6 +162,12 @@ function initializeMigrations({
|
|||
const deleteSticker = Attachments.createDeleter(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 {
|
||||
attachmentsPath,
|
||||
copyIntoAttachmentsDirectory,
|
||||
|
@ -170,6 +177,7 @@ function initializeMigrations({
|
|||
deleteOnDisk,
|
||||
}),
|
||||
deleteSticker,
|
||||
deleteTempFile,
|
||||
getAbsoluteAttachmentPath,
|
||||
getAbsoluteStickerPath,
|
||||
getPlaceholderMigrations,
|
||||
|
@ -181,6 +189,7 @@ function initializeMigrations({
|
|||
loadStickerData,
|
||||
readAttachmentData,
|
||||
readStickerData,
|
||||
readTempData,
|
||||
run,
|
||||
processNewAttachment: attachment =>
|
||||
MessageType.processNewAttachment(attachment, {
|
||||
|
@ -200,6 +209,13 @@ function initializeMigrations({
|
|||
getImageDimensions,
|
||||
logger,
|
||||
}),
|
||||
processNewEphemeralSticker: stickerData =>
|
||||
MessageType.processNewSticker(stickerData, {
|
||||
writeNewStickerData: writeNewTempData,
|
||||
getAbsoluteStickerPath: getAbsoluteTempPath,
|
||||
getImageDimensions,
|
||||
logger,
|
||||
}),
|
||||
upgradeMessageSchema: (message, 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 downloadStickerPack(
|
||||
packId: string,
|
||||
packKey: string,
|
||||
options?: {
|
||||
finalStatus?: 'installed' | 'downloaded';
|
||||
messageId?: string;
|
||||
fromSync?: boolean;
|
||||
}
|
||||
): Promise<void>;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
textsecure,
|
||||
Signal,
|
||||
log,
|
||||
navigator,
|
||||
reduxStore,
|
||||
reduxActions,
|
||||
URL
|
||||
|
@ -9,7 +10,7 @@
|
|||
|
||||
const BLESSED_PACKS = {};
|
||||
|
||||
const { isNumber, pick, reject, groupBy } = require('lodash');
|
||||
const { isNumber, pick, reject, groupBy, values } = require('lodash');
|
||||
const pMap = require('p-map');
|
||||
const Queue = require('p-queue');
|
||||
const qs = require('qs');
|
||||
|
@ -34,6 +35,7 @@ module.exports = {
|
|||
deletePack,
|
||||
deletePackReference,
|
||||
downloadStickerPack,
|
||||
downloadEphemeralPack,
|
||||
getDataFromLink,
|
||||
getInitialState,
|
||||
getInstalledStickerPacks,
|
||||
|
@ -44,6 +46,8 @@ module.exports = {
|
|||
maybeDeletePack,
|
||||
downloadQueuedPacks,
|
||||
redactPackId,
|
||||
removeEphemeralPack,
|
||||
savePackMetadata,
|
||||
};
|
||||
|
||||
let initialState = null;
|
||||
|
@ -88,8 +92,8 @@ function getInstalledStickerPacks() {
|
|||
return [];
|
||||
}
|
||||
|
||||
const values = Object.values(packs);
|
||||
return values.filter(pack => pack.status === 'installed');
|
||||
const items = Object.values(packs);
|
||||
return items.filter(pack => pack.status === 'installed');
|
||||
}
|
||||
|
||||
function downloadQueuedPacks() {
|
||||
|
@ -113,7 +117,7 @@ function capturePacksToDownload(existingPackLookup) {
|
|||
const existing = existingPackLookup[id];
|
||||
if (
|
||||
!existing ||
|
||||
(existing.status !== 'advertised' && existing.status !== 'installed')
|
||||
(existing.status !== 'downloaded' && existing.status !== 'installed')
|
||||
) {
|
||||
toDownload[id] = {
|
||||
id,
|
||||
|
@ -130,6 +134,18 @@ function capturePacksToDownload(existingPackLookup) {
|
|||
}
|
||||
|
||||
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)) {
|
||||
toDownload[id] = {
|
||||
id,
|
||||
|
@ -147,14 +163,23 @@ function doesPackNeedDownload(pack) {
|
|||
return true;
|
||||
}
|
||||
|
||||
const stickerCount = Object.keys(pack.stickers || {}).length;
|
||||
return (
|
||||
!pack.status ||
|
||||
pack.status === 'error' ||
|
||||
pack.status === 'pending' ||
|
||||
!pack.stickerCount ||
|
||||
stickerCount < pack.stickerCount
|
||||
);
|
||||
const { status, stickerCount } = pack;
|
||||
const stickersDownloaded = Object.keys(pack.stickers || {}).length;
|
||||
|
||||
if (
|
||||
(status === 'installed' || status === 'downloaded') &&
|
||||
stickerCount > 0 &&
|
||||
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() {
|
||||
|
@ -209,10 +234,15 @@ async function decryptSticker(packKey, ciphertext) {
|
|||
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 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 {
|
||||
...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 = {}) {
|
||||
// This will ensure that only one download process is in progress at any given time
|
||||
return downloadQueue.add(async () => {
|
||||
|
@ -244,7 +424,12 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
|||
installStickerPack,
|
||||
} = 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);
|
||||
if (!doesPackNeedDownload(existing)) {
|
||||
|
@ -256,7 +441,10 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
|||
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) {
|
||||
log.warn(
|
||||
`Refusing to attempt another download for pack ${redactPackId(
|
||||
|
@ -280,6 +468,16 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
|||
let nonCoverStickers;
|
||||
|
||||
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(
|
||||
packId
|
||||
);
|
||||
|
@ -307,8 +505,10 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
|||
coverIncludedInList = nonCoverStickers.length < stickerCount;
|
||||
|
||||
// status can be:
|
||||
// - 'known'
|
||||
// - 'ephemeral' (should not hit database)
|
||||
// - 'pending'
|
||||
// - 'advertised'
|
||||
// - 'downloaded'
|
||||
// - 'error'
|
||||
// - 'installed'
|
||||
const pack = {
|
||||
|
@ -365,6 +565,13 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
|||
// Then the rest
|
||||
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') {
|
||||
await installStickerPack(packId, packKey, { fromSync });
|
||||
} else {
|
||||
|
@ -380,11 +587,12 @@ async function doDownloadStickerPack(packId, packKey, options = {}) {
|
|||
error && error.stack ? error.stack : error
|
||||
);
|
||||
|
||||
const errorState = 'error';
|
||||
await updateStickerPackStatus(packId, errorState);
|
||||
const errorStatus = 'error';
|
||||
await updateStickerPackStatus(packId, errorStatus);
|
||||
if (stickerPackUpdated) {
|
||||
stickerPackUpdated(packId, {
|
||||
state: errorState,
|
||||
attemptedStatus: finalStatus,
|
||||
status: errorStatus,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1331,15 +1331,19 @@
|
|||
dialog.focusCancel();
|
||||
},
|
||||
|
||||
showStickerPackPreview(packId) {
|
||||
showStickerPackPreview(packId, packKey) {
|
||||
if (!window.ENABLE_STICKER_SEND) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.Signal.Stickers.downloadEphemeralPack(packId, packKey);
|
||||
|
||||
const props = {
|
||||
packId,
|
||||
onClose: () => {
|
||||
onClose: async () => {
|
||||
this.stickerPreviewModalView.remove();
|
||||
this.stickerPreviewModalView = null;
|
||||
await window.Signal.Stickers.removeEphemeralPack(packId);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1349,9 +1353,6 @@
|
|||
window.reduxStore,
|
||||
props
|
||||
),
|
||||
onClose: () => {
|
||||
this.stickerPreviewModalView = null;
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1364,8 +1365,8 @@
|
|||
}
|
||||
const sticker = message.get('sticker');
|
||||
if (sticker) {
|
||||
const { packId } = sticker;
|
||||
this.showStickerPackPreview(packId);
|
||||
const { packId, packKey } = sticker;
|
||||
this.showStickerPackPreview(packId, packKey);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1992,17 +1993,25 @@
|
|||
},
|
||||
|
||||
async getStickerPackPreview(url) {
|
||||
const isPackDownloaded = pack =>
|
||||
pack && (pack.status === 'downloaded' || pack.status === 'installed');
|
||||
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 {
|
||||
const { id, key } = window.Signal.Stickers.getDataFromLink(url);
|
||||
({ id, key } = window.Signal.Stickers.getDataFromLink(url));
|
||||
const keyBytes = window.Signal.Crypto.bytesFromHexString(key);
|
||||
const keyBase64 = window.Signal.Crypto.arrayBufferToBase64(keyBytes);
|
||||
|
||||
const existing = window.Signal.Stickers.getStickerPack(id);
|
||||
if (!isPackValid(existing)) {
|
||||
await window.Signal.Stickers.downloadStickerPack(id, keyBase64);
|
||||
if (!isPackDownloaded(existing)) {
|
||||
await window.Signal.Stickers.downloadEphemeralPack(id, keyBase64);
|
||||
}
|
||||
|
||||
const pack = window.Signal.Stickers.getStickerPack(id);
|
||||
|
@ -2015,9 +2024,10 @@
|
|||
|
||||
const { title, coverStickerId } = pack;
|
||||
const sticker = pack.stickers[coverStickerId];
|
||||
const data = await window.Signal.Migrations.readStickerData(
|
||||
sticker.path
|
||||
);
|
||||
const data =
|
||||
pack.status === 'ephemeral'
|
||||
? await window.Signal.Migrations.readTempData(sticker.path)
|
||||
: await window.Signal.Migrations.readStickerData(sticker.path);
|
||||
|
||||
return {
|
||||
title,
|
||||
|
@ -2035,6 +2045,10 @@
|
|||
error && error.stack ? error.stack : error
|
||||
);
|
||||
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({
|
||||
configDir: userDataPath,
|
||||
cleanupOrphanedAttachments,
|
||||
|
@ -1034,7 +1042,7 @@ function handleSgnlLink(incomingUrl) {
|
|||
if (command === 'addstickers' && mainWindow && mainWindow.webContents) {
|
||||
const { pack_id: packId, pack_key: packKeyHex } = args;
|
||||
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 {
|
||||
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');
|
||||
|
||||
// Waiting for clients to implement changes on receive side
|
||||
window.ENABLE_STICKER_SEND = false;
|
||||
window.ENABLE_STICKER_SEND = true;
|
||||
window.TIMESTAMP_VALIDATION = false;
|
||||
window.PAD_ALL_ATTACHMENTS = 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 { installStickerPack } = window.Events;
|
||||
if (installStickerPack) {
|
||||
installStickerPack(packId, packKey);
|
||||
const { showStickerPack } = window.Events;
|
||||
if (showStickerPack) {
|
||||
showStickerPack(packId, packKey);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -306,6 +306,7 @@ window.moment.locale(locale);
|
|||
const userDataPath = app.getPath('userData');
|
||||
window.baseAttachmentsPath = Attachments.getPath(userDataPath);
|
||||
window.baseStickersPath = Attachments.getStickersPath(userDataPath);
|
||||
window.baseTempPath = Attachments.getTempPath(userDataPath);
|
||||
window.Signal = Signal.setup({
|
||||
Attachments,
|
||||
userDataPath,
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
.inbox,
|
||||
.gutter {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inbox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.expired {
|
||||
|
@ -82,6 +86,7 @@
|
|||
}
|
||||
|
||||
.conversation-stack {
|
||||
flex-grow: 1;
|
||||
.conversation {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -3376,6 +3376,14 @@
|
|||
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 {
|
||||
position: relative;
|
||||
|
||||
|
@ -3553,6 +3561,11 @@
|
|||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
&__cover-placeholder {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: $color-gray-10;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
flex-grow: 1;
|
||||
|
@ -3681,9 +3694,23 @@
|
|||
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 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
height: 36px;
|
||||
padding: 0 8px 0 16px;
|
||||
justify-content: space-between;
|
||||
|
@ -3727,6 +3754,18 @@
|
|||
width: 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;
|
||||
height: 20px;
|
||||
}
|
||||
&__image-placeholder {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: $color-gray-10;
|
||||
}
|
||||
|
||||
&__text {
|
||||
margin-left: 4px;
|
||||
|
@ -3938,11 +3982,11 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__image {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background: #eaeaea;
|
||||
}
|
||||
// &__image {
|
||||
// width: 52px;
|
||||
// height: 52px;
|
||||
// background: $color-gray-10;
|
||||
// }
|
||||
|
||||
&__meta {
|
||||
flex-grow: 1;
|
||||
|
|
|
@ -43,6 +43,8 @@ const packs = [
|
|||
i18n={util.i18n}
|
||||
receivedPacks={[]}
|
||||
installedPacks={packs}
|
||||
blessedPacks={[]}
|
||||
knownPacks={[]}
|
||||
onPickSticker={(packId, stickerId) =>
|
||||
console.log('onPickSticker', { packId, stickerId })
|
||||
}
|
||||
|
@ -100,8 +102,10 @@ const packs = [
|
|||
>
|
||||
<StickerButton
|
||||
i18n={util.i18n}
|
||||
receivedPacks={[]}
|
||||
installedPacks={packs}
|
||||
receivedPacks={packs}
|
||||
installedPacks={[]}
|
||||
blessedPacks={[]}
|
||||
knownPacks={[]}
|
||||
onPickSticker={(packId, stickerId) =>
|
||||
console.log('onPickSticker', { packId, stickerId })
|
||||
}
|
||||
|
@ -113,7 +117,75 @@ const packs = [
|
|||
</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.
|
||||
|
||||
|
@ -123,6 +195,8 @@ When there are no advertised packs and no installed packs the button should not
|
|||
i18n={util.i18n}
|
||||
receivedPacks={[]}
|
||||
installedPacks={[]}
|
||||
blessedPacks={[]}
|
||||
knownPacks={[]}
|
||||
onPickSticker={(packId, stickerId) =>
|
||||
console.log('onPickSticker', { packId, stickerId })
|
||||
}
|
||||
|
@ -188,6 +262,8 @@ const packs = [
|
|||
i18n={util.i18n}
|
||||
receivedPacks={[]}
|
||||
installedPacks={packs}
|
||||
blessedPacks={[]}
|
||||
knownPacks={[]}
|
||||
installedPack={packs[0]}
|
||||
onPickSticker={(packId, stickerId) =>
|
||||
console.log('onPickSticker', { packId, stickerId })
|
||||
|
@ -257,6 +333,8 @@ const packs = [
|
|||
i18n={util.i18n}
|
||||
receivedPacks={[]}
|
||||
installedPacks={packs}
|
||||
blessedPacks={[]}
|
||||
knownPacks={[]}
|
||||
onPickSticker={(packId, stickerId) =>
|
||||
console.log('onPickSticker', { packId, stickerId })
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ export type OwnProps = {
|
|||
readonly i18n: LocalizerType;
|
||||
readonly receivedPacks: ReadonlyArray<StickerPackType>;
|
||||
readonly installedPacks: ReadonlyArray<StickerPackType>;
|
||||
readonly blessedPacks: ReadonlyArray<StickerPackType>;
|
||||
readonly knownPacks: ReadonlyArray<StickerPackType>;
|
||||
readonly installedPack?: StickerPackType | null;
|
||||
readonly recentStickers: ReadonlyArray<StickerType>;
|
||||
readonly clearInstalledStickerPack: () => unknown;
|
||||
|
@ -35,6 +37,8 @@ export const StickerButton = React.memo(
|
|||
receivedPacks,
|
||||
installedPack,
|
||||
installedPacks,
|
||||
blessedPacks,
|
||||
knownPacks,
|
||||
showIntroduction,
|
||||
clearShowIntroduction,
|
||||
showPickerHint,
|
||||
|
@ -138,7 +142,12 @@ export const StickerButton = React.memo(
|
|||
[installedPack, clearInstalledStickerPack]
|
||||
);
|
||||
|
||||
if (installedPacks.length + receivedPacks.length === 0) {
|
||||
const totalPacks =
|
||||
knownPacks.length +
|
||||
blessedPacks.length +
|
||||
installedPacks.length +
|
||||
receivedPacks.length;
|
||||
if (totalPacks === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -166,11 +175,15 @@ export const StickerButton = React.memo(
|
|||
role="button"
|
||||
onClick={clearInstalledStickerPack}
|
||||
>
|
||||
<img
|
||||
className="module-sticker-button__tooltip__image"
|
||||
src={installedPack.cover.url}
|
||||
alt={installedPack.title}
|
||||
/>
|
||||
{installedPack.cover ? (
|
||||
<img
|
||||
className="module-sticker-button__tooltip__image"
|
||||
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__title">
|
||||
{installedPack.title}
|
||||
|
@ -202,7 +215,7 @@ export const StickerButton = React.memo(
|
|||
role="button"
|
||||
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__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 blessedPacks = packs.map(p => ({
|
||||
...p,
|
||||
status: 'advertised',
|
||||
status: 'downloaded',
|
||||
isBlessed: true,
|
||||
}));
|
||||
|
||||
|
@ -158,6 +158,64 @@ const noPacks = [];
|
|||
</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
|
||||
|
||||
```jsx
|
||||
|
|
|
@ -9,6 +9,8 @@ export type OwnProps = {
|
|||
readonly installedPacks: ReadonlyArray<StickerPackType>;
|
||||
readonly receivedPacks: 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 uninstallStickerPack: (packId: string, packKey: string) => unknown;
|
||||
readonly i18n: LocalizerType;
|
||||
|
@ -20,7 +22,9 @@ export const StickerManager = React.memo(
|
|||
({
|
||||
installedPacks,
|
||||
receivedPacks,
|
||||
knownPacks,
|
||||
blessedPacks,
|
||||
downloadStickerPack,
|
||||
installStickerPack,
|
||||
uninstallStickerPack,
|
||||
i18n,
|
||||
|
@ -30,6 +34,15 @@ export const StickerManager = React.memo(
|
|||
setPackToPreview,
|
||||
] = React.useState<StickerPackType | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!knownPacks) {
|
||||
return;
|
||||
}
|
||||
knownPacks.forEach(pack => {
|
||||
downloadStickerPack(pack.id, pack.key);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearPackToPreview = React.useCallback(
|
||||
() => {
|
||||
setPackToPreview(null);
|
||||
|
@ -51,6 +64,7 @@ export const StickerManager = React.memo(
|
|||
i18n={i18n}
|
||||
pack={packToPreview}
|
||||
onClose={clearPackToPreview}
|
||||
downloadStickerPack={downloadStickerPack}
|
||||
installStickerPack={installStickerPack}
|
||||
uninstallStickerPack={uninstallStickerPack}
|
||||
/>
|
||||
|
|
|
@ -91,11 +91,15 @@ export const StickerManagerPackRow = React.memo(
|
|||
onClick={handleClickPreview}
|
||||
className="module-sticker-manager__pack-row"
|
||||
>
|
||||
<img
|
||||
src={pack.cover.url}
|
||||
alt={pack.title}
|
||||
className="module-sticker-manager__pack-row__cover"
|
||||
/>
|
||||
{pack.cover ? (
|
||||
<img
|
||||
src={pack.cover.url}
|
||||
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__title">
|
||||
{pack.title}
|
||||
|
@ -108,18 +112,18 @@ export const StickerManagerPackRow = React.memo(
|
|||
</div>
|
||||
</div>
|
||||
<div className="module-sticker-manager__pack-row__controls">
|
||||
{pack.status === 'advertised' ? (
|
||||
<StickerPackInstallButton
|
||||
installed={false}
|
||||
i18n={i18n}
|
||||
onClick={handleInstall}
|
||||
/>
|
||||
) : (
|
||||
{pack.status === 'installed' ? (
|
||||
<StickerPackInstallButton
|
||||
installed={true}
|
||||
i18n={i18n}
|
||||
onClick={handleUninstall}
|
||||
/>
|
||||
) : (
|
||||
<StickerPackInstallButton
|
||||
installed={false}
|
||||
i18n={i18n}
|
||||
onClick={handleInstall}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -153,12 +153,16 @@ export const StickerPicker = React.memo(
|
|||
}
|
||||
)}
|
||||
>
|
||||
<img
|
||||
className="module-sticker-picker__header__button__image"
|
||||
src={pack.cover.url}
|
||||
alt={pack.title}
|
||||
title={pack.title}
|
||||
/>
|
||||
{pack.cover ? (
|
||||
<img
|
||||
className="module-sticker-picker__header__button__image"
|
||||
src={pack.cover.url}
|
||||
alt={pack.title}
|
||||
title={pack.title}
|
||||
/>
|
||||
) : (
|
||||
<div className="module-sticker-picker__header__button__image-placeholder" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@ const pack = {
|
|||
title: 'Foo',
|
||||
isBlessed: true,
|
||||
author: 'Foo McBarrington',
|
||||
status: 'advertised',
|
||||
status: 'downloaded',
|
||||
stickers: Array(101)
|
||||
.fill(0)
|
||||
.map((n, id) => ({ ...abeSticker, id })),
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
import * as React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { isNumber, range } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import { StickerPackInstallButton } from './StickerPackInstallButton';
|
||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { StickerPackType } from '../../state/ducks/stickers';
|
||||
import { Spinner } from '../Spinner';
|
||||
|
||||
export type OwnProps = {
|
||||
readonly onClose: () => unknown;
|
||||
readonly downloadStickerPack: (
|
||||
packId: string,
|
||||
packKey: string,
|
||||
options?: { finalStatus?: 'installed' | 'downloaded' }
|
||||
) => unknown;
|
||||
readonly installStickerPack: (packId: string, packKey: string) => unknown;
|
||||
readonly uninstallStickerPack: (packId: string, packKey: string) => unknown;
|
||||
readonly pack: StickerPackType;
|
||||
readonly pack?: StickerPackType;
|
||||
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(
|
||||
// tslint:disable-next-line max-func-body-length
|
||||
({
|
||||
onClose,
|
||||
pack,
|
||||
i18n,
|
||||
installStickerPack,
|
||||
uninstallStickerPack,
|
||||
}: Props) => {
|
||||
(props: Props) => {
|
||||
const {
|
||||
onClose,
|
||||
pack,
|
||||
i18n,
|
||||
downloadStickerPack,
|
||||
installStickerPack,
|
||||
uninstallStickerPack,
|
||||
} = props;
|
||||
const [root, setRoot] = React.useState<HTMLElement | null>(null);
|
||||
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
||||
|
||||
|
@ -40,15 +90,36 @@ export const StickerPreviewModal = React.memo(
|
|||
|
||||
return () => {
|
||||
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(
|
||||
() => {
|
||||
if (!pack) {
|
||||
return;
|
||||
}
|
||||
if (isInstalled) {
|
||||
setConfirmingUninstall(true);
|
||||
} else if (pack.status === 'ephemeral') {
|
||||
downloadStickerPack(pack.id, pack.key, { finalStatus: 'installed' });
|
||||
onClose();
|
||||
} else {
|
||||
installStickerPack(pack.id, pack.key);
|
||||
onClose();
|
||||
|
@ -59,6 +130,9 @@ export const StickerPreviewModal = React.memo(
|
|||
|
||||
const handleUninstall = React.useCallback(
|
||||
() => {
|
||||
if (!pack) {
|
||||
return;
|
||||
}
|
||||
uninstallStickerPack(pack.id, pack.key);
|
||||
setConfirmingUninstall(false);
|
||||
// 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"
|
||||
/>
|
||||
</header>
|
||||
<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}
|
||||
/>
|
||||
{renderBody(props)}
|
||||
{pack && pack.status !== 'error' ? (
|
||||
<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 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 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 className="module-sticker-manager__preview-modal__container__meta-overlay__install">
|
||||
<StickerPackInstallButton
|
||||
ref={focusRef}
|
||||
installed={isInstalled}
|
||||
i18n={i18n}
|
||||
onClick={handleToggleInstall}
|
||||
blue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
|
|
|
@ -4,10 +4,15 @@ import {
|
|||
updateStickerLastUsed,
|
||||
updateStickerPackStatus,
|
||||
} 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 { trigger } from '../../shims/events';
|
||||
|
||||
import { NoopActionType } from './noop';
|
||||
|
||||
// State
|
||||
|
||||
export type StickerDBType = {
|
||||
|
@ -24,14 +29,20 @@ export type StickerPackDBType = {
|
|||
readonly id: string;
|
||||
readonly key: string;
|
||||
|
||||
readonly attemptedStatus: string;
|
||||
readonly attemptedStatus: 'downloaded' | 'installed' | 'ephemeral';
|
||||
readonly author: string;
|
||||
readonly coverStickerId: number;
|
||||
readonly createdAt: number;
|
||||
readonly downloadAttempts: number;
|
||||
readonly installedAt: number | null;
|
||||
readonly lastUsed: number;
|
||||
readonly status: 'advertised' | 'installed' | 'pending' | 'error';
|
||||
readonly status:
|
||||
| 'known'
|
||||
| 'ephemeral'
|
||||
| 'downloaded'
|
||||
| 'installed'
|
||||
| 'pending'
|
||||
| 'error';
|
||||
readonly stickerCount: number;
|
||||
readonly stickers: Dictionary<StickerDBType>;
|
||||
readonly title: string;
|
||||
|
@ -64,9 +75,16 @@ export type StickerPackType = {
|
|||
readonly title: string;
|
||||
readonly author: string;
|
||||
readonly isBlessed: boolean;
|
||||
readonly cover: StickerType;
|
||||
readonly cover?: StickerType;
|
||||
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 stickerCount: number;
|
||||
};
|
||||
|
@ -103,7 +121,7 @@ type ClearInstalledStickerPackAction = {
|
|||
|
||||
type UninstallStickerPackPayloadType = {
|
||||
packId: string;
|
||||
status: 'advertised';
|
||||
status: 'downloaded';
|
||||
installedAt: null;
|
||||
recentStickers: Array<RecentStickerType>;
|
||||
};
|
||||
|
@ -148,11 +166,13 @@ export type StickersActionType =
|
|||
| UninstallStickerPackFulfilledAction
|
||||
| StickerPackUpdatedAction
|
||||
| StickerPackRemovedAction
|
||||
| UseStickerFulfilledAction;
|
||||
| UseStickerFulfilledAction
|
||||
| NoopActionType;
|
||||
|
||||
// Action Creators
|
||||
|
||||
export const actions = {
|
||||
downloadStickerPack,
|
||||
clearInstalledStickerPack,
|
||||
removeStickerPack,
|
||||
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(
|
||||
packId: string,
|
||||
packKey: string,
|
||||
|
@ -246,7 +283,7 @@ async function doUninstallStickerPack(
|
|||
): Promise<UninstallStickerPackPayloadType> {
|
||||
const { fromSync } = options || { fromSync: false };
|
||||
|
||||
const status = 'advertised';
|
||||
const status = 'downloaded';
|
||||
await updateStickerPackStatus(packId, status);
|
||||
|
||||
// If there are no more references, it should be removed
|
||||
|
@ -277,6 +314,13 @@ function stickerPackUpdated(
|
|||
packId: string,
|
||||
patch: Partial<StickerPackDBType>
|
||||
): 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 {
|
||||
type: 'stickers/STICKER_PACK_UPDATED',
|
||||
payload: {
|
||||
|
|
|
@ -6,6 +6,7 @@ import { LocalizerType } from '../../types/Util';
|
|||
export type UserStateType = {
|
||||
attachmentsPath: string;
|
||||
stickersPath: string;
|
||||
tempPath: string;
|
||||
ourNumber: string;
|
||||
regionCode: string;
|
||||
i18n: LocalizerType;
|
||||
|
@ -45,6 +46,7 @@ function getEmptyState(): UserStateType {
|
|||
return {
|
||||
attachmentsPath: 'missing',
|
||||
stickersPath: 'missing',
|
||||
tempPath: 'missing',
|
||||
ourNumber: 'missing',
|
||||
regionCode: 'missing',
|
||||
i18n: () => 'missing',
|
||||
|
|
|
@ -20,13 +20,14 @@ import {
|
|||
StickersStateType,
|
||||
StickerType,
|
||||
} from '../ducks/stickers';
|
||||
import { getStickersPath } from './user';
|
||||
import { getStickersPath, getTempPath } from './user';
|
||||
|
||||
const getSticker = (
|
||||
packs: Dictionary<StickerPackDBType>,
|
||||
packId: string,
|
||||
stickerId: number,
|
||||
stickerPath: string
|
||||
stickerPath: string,
|
||||
tempPath: string
|
||||
): StickerType | undefined => {
|
||||
const pack = packs[packId];
|
||||
if (!pack) {
|
||||
|
@ -38,20 +39,25 @@ const getSticker = (
|
|||
return;
|
||||
}
|
||||
|
||||
return translateStickerFromDB(sticker, stickerPath);
|
||||
const isEphemeral = pack.status === 'ephemeral';
|
||||
|
||||
return translateStickerFromDB(sticker, stickerPath, tempPath, isEphemeral);
|
||||
};
|
||||
|
||||
const translateStickerFromDB = (
|
||||
sticker: StickerDBType,
|
||||
stickerPath: string
|
||||
stickerPath: string,
|
||||
tempPath: string,
|
||||
isEphemeral: boolean
|
||||
): StickerType => {
|
||||
const { id, packId, emoji, path } = sticker;
|
||||
const prefix = isEphemeral ? tempPath : stickerPath;
|
||||
|
||||
return {
|
||||
id,
|
||||
packId,
|
||||
emoji,
|
||||
url: join(stickerPath, path),
|
||||
url: join(prefix, path),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -59,9 +65,11 @@ export const translatePackFromDB = (
|
|||
pack: StickerPackDBType,
|
||||
packs: Dictionary<StickerPackDBType>,
|
||||
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.
|
||||
// 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
|
||||
);
|
||||
const translatedStickers = map(filteredStickers, sticker =>
|
||||
translateStickerFromDB(sticker, stickersPath)
|
||||
translateStickerFromDB(sticker, stickersPath, tempPath, isEphemeral)
|
||||
);
|
||||
|
||||
return {
|
||||
...pack,
|
||||
isBlessed: Boolean(blessedPacks[id]),
|
||||
cover: getSticker(packs, id, coverStickerId, stickersPath),
|
||||
cover: getSticker(packs, id, coverStickerId, stickersPath, tempPath),
|
||||
stickers: sortBy(translatedStickers, sticker => sticker.id),
|
||||
};
|
||||
};
|
||||
|
@ -86,18 +94,15 @@ const filterAndTransformPacks = (
|
|||
packFilter: (sticker: StickerPackDBType) => boolean,
|
||||
packSort: (sticker: StickerPackDBType) => any,
|
||||
blessedPacks: Dictionary<boolean>,
|
||||
stickersPath: string
|
||||
stickersPath: string,
|
||||
tempPath: string
|
||||
): Array<StickerPackType> => {
|
||||
const list = filter(packs, packFilter);
|
||||
const sorted = orderBy<StickerPackDBType>(list, packSort, ['desc']);
|
||||
|
||||
const ready = sorted.map(pack =>
|
||||
translatePackFromDB(pack, packs, blessedPacks, stickersPath)
|
||||
return sorted.map(pack =>
|
||||
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;
|
||||
|
@ -121,14 +126,16 @@ export const getRecentStickers = createSelector(
|
|||
getRecents,
|
||||
getPacks,
|
||||
getStickersPath,
|
||||
getTempPath,
|
||||
(
|
||||
recents: Array<RecentStickerType>,
|
||||
packs: Dictionary<StickerPackDBType>,
|
||||
stickersPath: string
|
||||
stickersPath: string,
|
||||
tempPath: string
|
||||
) => {
|
||||
return compact(
|
||||
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,
|
||||
getBlessedPacks,
|
||||
getStickersPath,
|
||||
getTempPath,
|
||||
(
|
||||
packs: Dictionary<StickerPackDBType>,
|
||||
blessedPacks: Dictionary<boolean>,
|
||||
stickersPath: string
|
||||
stickersPath: string,
|
||||
tempPath: string
|
||||
): Array<StickerPackType> => {
|
||||
return filterAndTransformPacks(
|
||||
packs,
|
||||
pack => pack.status === 'installed',
|
||||
pack => pack.installedAt,
|
||||
blessedPacks,
|
||||
stickersPath
|
||||
stickersPath,
|
||||
tempPath
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -169,19 +179,22 @@ export const getReceivedStickerPacks = createSelector(
|
|||
getPacks,
|
||||
getBlessedPacks,
|
||||
getStickersPath,
|
||||
getTempPath,
|
||||
(
|
||||
packs: Dictionary<StickerPackDBType>,
|
||||
blessedPacks: Dictionary<boolean>,
|
||||
stickersPath: string
|
||||
stickersPath: string,
|
||||
tempPath: string
|
||||
): Array<StickerPackType> => {
|
||||
return filterAndTransformPacks(
|
||||
packs,
|
||||
pack =>
|
||||
(pack.status === 'advertised' || pack.status === 'pending') &&
|
||||
(pack.status === 'downloaded' || pack.status === 'pending') &&
|
||||
!blessedPacks[pack.id],
|
||||
pack => pack.createdAt,
|
||||
blessedPacks,
|
||||
stickersPath
|
||||
stickersPath,
|
||||
tempPath
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -190,17 +203,42 @@ export const getBlessedStickerPacks = createSelector(
|
|||
getPacks,
|
||||
getBlessedPacks,
|
||||
getStickersPath,
|
||||
getTempPath,
|
||||
(
|
||||
packs: Dictionary<StickerPackDBType>,
|
||||
blessedPacks: Dictionary<boolean>,
|
||||
stickersPath: string
|
||||
stickersPath: string,
|
||||
tempPath: string
|
||||
): Array<StickerPackType> => {
|
||||
return filterAndTransformPacks(
|
||||
packs,
|
||||
pack => blessedPacks[pack.id] && pack.status !== 'installed',
|
||||
pack => pack.createdAt,
|
||||
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,
|
||||
(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 {
|
||||
getBlessedStickerPacks,
|
||||
getInstalledStickerPacks,
|
||||
getKnownStickerPacks,
|
||||
getReceivedStickerPacks,
|
||||
getRecentlyInstalledStickerPack,
|
||||
getRecentStickers,
|
||||
|
@ -15,6 +17,9 @@ import {
|
|||
const mapStateToProps = (state: StateType) => {
|
||||
const receivedPacks = getReceivedStickerPacks(state);
|
||||
const installedPacks = getInstalledStickerPacks(state);
|
||||
const blessedPacks = getBlessedStickerPacks(state);
|
||||
const knownPacks = getKnownStickerPacks(state);
|
||||
|
||||
const recentStickers = getRecentStickers(state);
|
||||
const installedPack = getRecentlyInstalledStickerPack(state);
|
||||
const showIntroduction = get(
|
||||
|
@ -29,6 +34,8 @@ const mapStateToProps = (state: StateType) => {
|
|||
return {
|
||||
receivedPacks,
|
||||
installedPack,
|
||||
blessedPacks,
|
||||
knownPacks,
|
||||
installedPacks,
|
||||
recentStickers,
|
||||
showIntroduction,
|
||||
|
|
|
@ -7,6 +7,7 @@ import { getIntl } from '../selectors/user';
|
|||
import {
|
||||
getBlessedStickerPacks,
|
||||
getInstalledStickerPacks,
|
||||
getKnownStickerPacks,
|
||||
getReceivedStickerPacks,
|
||||
} from '../selectors/stickers';
|
||||
|
||||
|
@ -14,11 +15,13 @@ const mapStateToProps = (state: StateType) => {
|
|||
const blessedPacks = getBlessedStickerPacks(state);
|
||||
const receivedPacks = getReceivedStickerPacks(state);
|
||||
const installedPacks = getInstalledStickerPacks(state);
|
||||
const knownPacks = getKnownStickerPacks(state);
|
||||
|
||||
return {
|
||||
blessedPacks,
|
||||
receivedPacks,
|
||||
installedPacks,
|
||||
knownPacks,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ import { mapDispatchToProps } from '../actions';
|
|||
import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal';
|
||||
import { StateType } from '../reducer';
|
||||
|
||||
import { getIntl, getStickersPath } from '../selectors/user';
|
||||
import { getIntl, getStickersPath, getTempPath } from '../selectors/user';
|
||||
import {
|
||||
getBlessedPacks,
|
||||
getPacks,
|
||||
|
@ -18,33 +18,17 @@ type ExternalProps = {
|
|||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||
const { packId } = props;
|
||||
const stickersPath = getStickersPath(state);
|
||||
const tempPath = getTempPath(state);
|
||||
|
||||
const packs = getPacks(state);
|
||||
const blessedPacks = getBlessedPacks(state);
|
||||
const pack = packs[packId];
|
||||
|
||||
if (!pack) {
|
||||
throw new Error(`Cannot find pack ${packId}`);
|
||||
}
|
||||
const translated = translatePackFromDB(
|
||||
pack,
|
||||
packs,
|
||||
blessedPacks,
|
||||
stickersPath
|
||||
);
|
||||
|
||||
return {
|
||||
...props,
|
||||
pack: {
|
||||
...translated,
|
||||
cover: translated.cover
|
||||
? translated.cover
|
||||
: {
|
||||
id: 0,
|
||||
url: 'nonexistent',
|
||||
packId,
|
||||
emoji: 'WTF',
|
||||
},
|
||||
},
|
||||
pack: pack
|
||||
? translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath)
|
||||
: undefined,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,6 +20,8 @@ import mkdirp from 'mkdirp';
|
|||
import rimraf from 'rimraf';
|
||||
import { app, BrowserWindow, dialog } from 'electron';
|
||||
|
||||
import { getTempPath } from '../../app/attachments';
|
||||
|
||||
// @ts-ignore
|
||||
import * as packageJson from '../../package.json';
|
||||
import { getSignatureFileName } from './signature';
|
||||
|
@ -269,7 +271,7 @@ function getGotOptions(): GotOptions<null> {
|
|||
|
||||
function getBaseTempDir() {
|
||||
// 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() {
|
||||
|
@ -303,11 +305,6 @@ export function getPrintableError(error: 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 {
|
||||
const parser = createParser({ options });
|
||||
const cliOptions = parser.parse(process.argv);
|
||||
|
|
|
@ -3,12 +3,7 @@ import { BrowserWindow } from 'electron';
|
|||
|
||||
import { start as startMacOS } from './macos';
|
||||
import { start as startWindows } from './windows';
|
||||
import {
|
||||
deleteBaseTempDir,
|
||||
getPrintableError,
|
||||
LoggerType,
|
||||
MessagesType,
|
||||
} from './common';
|
||||
import { LoggerType, MessagesType } from './common';
|
||||
|
||||
let initialized = false;
|
||||
|
||||
|
@ -39,15 +34,6 @@ export async function start(
|
|||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteBaseTempDir();
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'updater/start: Error deleting temp dir:',
|
||||
getPrintableError(error)
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
await startWindows(getMainWindow, messages, logger);
|
||||
} else if (platform === 'darwin') {
|
||||
|
|
|
@ -227,11 +227,19 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"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(",
|
||||
"path": "js/modules/stickers.js",
|
||||
"line": "async function load() {",
|
||||
"lineNumber": 53,
|
||||
"lineNumber": 57,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T17:48:30.675Z"
|
||||
},
|
||||
|
@ -6066,13 +6074,5 @@
|
|||
"lineNumber": 60,
|
||||
"reasonCategory": "falseMatch",
|
||||
"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