[MM-47801][MM-45980] Added support for security-scoped bookmarks to allow the MAS build to save files wherever needed (#2315)
* First pass * [MM-47801] Added support for security-scoped bookmarks to allow the MAS build to save files wherever needed
This commit is contained in:
parent
0f51a628f0
commit
635a41f998
|
@ -38,5 +38,9 @@
|
||||||
<string>UQ8HT4Q2XM.Mattermost.Desktop</string>
|
<string>UQ8HT4Q2XM.Mattermost.Desktop</string>
|
||||||
<key>com.apple.developer.team-identifier</key>
|
<key>com.apple.developer.team-identifier</key>
|
||||||
<string>UQ8HT4Q2XM</string>
|
<string>UQ8HT4Q2XM</string>
|
||||||
|
<key>com.apple.security.files.bookmarks.app-scope</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.bookmarks.document-scope</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
const jq = require('node-jq');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const jq = require('node-jq');
|
||||||
|
|
||||||
jq.run(
|
jq.run(
|
||||||
'.scripts.install = "node-gyp rebuild"',
|
'.scripts.install = "node-gyp rebuild"',
|
||||||
'./node_modules/macos-notification-state/package.json',
|
'./node_modules/macos-notification-state/package.json',
|
||||||
|
|
|
@ -148,6 +148,7 @@ export const REQUEST_HAS_DOWNLOADS = 'request-has-downloads';
|
||||||
export const DOWNLOADS_DROPDOWN_FOCUSED = 'downloads-dropdown-focused';
|
export const DOWNLOADS_DROPDOWN_FOCUSED = 'downloads-dropdown-focused';
|
||||||
export const RECEIVE_DOWNLOADS_DROPDOWN_SIZE = 'receive-downloads-dropdown-size';
|
export const RECEIVE_DOWNLOADS_DROPDOWN_SIZE = 'receive-downloads-dropdown-size';
|
||||||
export const SEND_DOWNLOADS_DROPDOWN_SIZE = 'send-downloads-dropdown-size';
|
export const SEND_DOWNLOADS_DROPDOWN_SIZE = 'send-downloads-dropdown-size';
|
||||||
|
export const GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION = 'get-downloaded-image-thumbnail-location';
|
||||||
|
|
||||||
export const OPEN_DOWNLOADS_DROPDOWN_MENU = 'open-downloads-dropdown-menu';
|
export const OPEN_DOWNLOADS_DROPDOWN_MENU = 'open-downloads-dropdown-menu';
|
||||||
export const CLOSE_DOWNLOADS_DROPDOWN_MENU = 'close-downloads-dropdown-menu';
|
export const CLOSE_DOWNLOADS_DROPDOWN_MENU = 'close-downloads-dropdown-menu';
|
||||||
|
|
|
@ -58,6 +58,7 @@ const downloadsSchema = Joi.object<DownloadedItems>().pattern(
|
||||||
addedAt: Joi.number().min(0),
|
addedAt: Joi.number().min(0),
|
||||||
receivedBytes: Joi.number().min(0),
|
receivedBytes: Joi.number().min(0),
|
||||||
totalBytes: Joi.number().min(0),
|
totalBytes: Joi.number().min(0),
|
||||||
|
bookmark: Joi.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const configDataSchemaV0 = Joi.object<ConfigV0>({
|
const configDataSchemaV0 = Joi.object<ConfigV0>({
|
||||||
|
|
|
@ -117,6 +117,7 @@ const item = {
|
||||||
getStartTime: () => nowSeconds,
|
getStartTime: () => nowSeconds,
|
||||||
getTotalBytes: () => 4242,
|
getTotalBytes: () => 4242,
|
||||||
getSavePath: () => locationMock,
|
getSavePath: () => locationMock,
|
||||||
|
getURL: () => 'http://some-url.com/some-text.txt',
|
||||||
hasUserGesture: jest.fn().mockReturnValue(true),
|
hasUserGesture: jest.fn().mockReturnValue(true),
|
||||||
setSavePath: jest.fn(),
|
setSavePath: jest.fn(),
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
|
@ -144,7 +145,8 @@ describe('main/downloadsManager', () => {
|
||||||
it('should handle a new download', () => {
|
it('should handle a new download', () => {
|
||||||
const dl = new DownloadsManager({});
|
const dl = new DownloadsManager({});
|
||||||
path.parse.mockImplementation(() => ({base: 'file.txt'}));
|
path.parse.mockImplementation(() => ({base: 'file.txt'}));
|
||||||
dl.handleNewDownload({}, item, {id: 0, getURL: jest.fn()});
|
dl.willDownloadURLs.set('http://some-url.com/some-text.txt', {filePath: locationMock});
|
||||||
|
dl.handleNewDownload({preventDefault: jest.fn()}, item, {id: 0, getURL: jest.fn(), downloadURL: jest.fn()});
|
||||||
expect(dl).toHaveProperty('downloads', {'file.txt': {
|
expect(dl).toHaveProperty('downloads', {'file.txt': {
|
||||||
addedAt: nowSeconds * 1000,
|
addedAt: nowSeconds * 1000,
|
||||||
filename: 'file.txt',
|
filename: 'file.txt',
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
import {DownloadItem, Event, WebContents, FileFilter, ipcMain, dialog, shell, Menu} from 'electron';
|
import {DownloadItem, Event, WebContents, FileFilter, ipcMain, dialog, shell, Menu, app} from 'electron';
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
import {ProgressInfo} from 'electron-updater';
|
import {ProgressInfo} from 'electron-updater';
|
||||||
|
|
||||||
|
@ -51,12 +51,17 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
progressingItems: Map<string, DownloadItem>;
|
progressingItems: Map<string, DownloadItem>;
|
||||||
downloads: DownloadedItems;
|
downloads: DownloadedItems;
|
||||||
|
|
||||||
|
willDownloadURLs: Map<string, {filePath: string; bookmark?: string}>;
|
||||||
|
bookmarks: Map<string, {originalPath: string; bookmark: string}>;
|
||||||
|
|
||||||
constructor(file: string) {
|
constructor(file: string) {
|
||||||
super(file);
|
super(file);
|
||||||
|
|
||||||
this.open = false;
|
this.open = false;
|
||||||
this.fileSizes = new Map();
|
this.fileSizes = new Map();
|
||||||
this.progressingItems = new Map();
|
this.progressingItems = new Map();
|
||||||
|
this.willDownloadURLs = new Map();
|
||||||
|
this.bookmarks = new Map();
|
||||||
this.autoCloseTimeout = null;
|
this.autoCloseTimeout = null;
|
||||||
this.downloads = {};
|
this.downloads = {};
|
||||||
|
|
||||||
|
@ -73,6 +78,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
this.saveAll({});
|
this.saveAll({});
|
||||||
}
|
}
|
||||||
this.checkForDeletedFiles();
|
this.checkForDeletedFiles();
|
||||||
|
this.reloadFilesForMAS();
|
||||||
|
|
||||||
ipcMain.handle(REQUEST_HAS_DOWNLOADS, () => {
|
ipcMain.handle(REQUEST_HAS_DOWNLOADS, () => {
|
||||||
return this.hasDownloads();
|
return this.hasDownloads();
|
||||||
|
@ -84,26 +90,44 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
ipcMain.on(NO_UPDATE_AVAILABLE, this.noUpdateAvailable);
|
ipcMain.on(NO_UPDATE_AVAILABLE, this.noUpdateAvailable);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNewDownload = (event: Event, item: DownloadItem, webContents: WebContents) => {
|
handleNewDownload = async (event: Event, item: DownloadItem, webContents: WebContents) => {
|
||||||
log.debug('DownloadsManager.handleNewDownload', {item, sourceURL: webContents.getURL()});
|
log.debug('DownloadsManager.handleNewDownload', {item, sourceURL: webContents.getURL()});
|
||||||
|
|
||||||
const shouldShowSaveDialog = this.shouldShowSaveDialog(item, Config.downloadLocation);
|
const url = item.getURL();
|
||||||
if (shouldShowSaveDialog) {
|
|
||||||
const saveDialogSuccess = this.showSaveDialog(item);
|
if (this.willDownloadURLs.has(url)) {
|
||||||
if (!saveDialogSuccess) {
|
const info = this.willDownloadURLs.get(url)!;
|
||||||
item.cancel();
|
this.willDownloadURLs.delete(url);
|
||||||
return;
|
|
||||||
}
|
if (info.bookmark) {
|
||||||
|
item.setSavePath(path.resolve(app.getPath('temp'), path.basename(info.filePath)));
|
||||||
|
this.bookmarks.set(this.getFileId(item), {originalPath: info.filePath, bookmark: info.bookmark!});
|
||||||
} else {
|
} else {
|
||||||
const filename = this.createFilename(item);
|
item.setSavePath(info.filePath);
|
||||||
const savePath = this.getSavePath(`${Config.downloadLocation}`, filename);
|
|
||||||
item.setSavePath(savePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.upsertFileToDownloads(item, 'progressing');
|
this.upsertFileToDownloads(item, 'progressing');
|
||||||
this.progressingItems.set(this.getFileId(item), item);
|
this.progressingItems.set(this.getFileId(item), item);
|
||||||
this.handleDownloadItemEvents(item, webContents);
|
this.handleDownloadItemEvents(item, webContents);
|
||||||
this.openDownloadsDropdown();
|
this.openDownloadsDropdown();
|
||||||
this.toggleAppMenuDownloadsEnabled(true);
|
this.toggleAppMenuDownloadsEnabled(true);
|
||||||
|
} else {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (this.shouldShowSaveDialog(item, Config.downloadLocation)) {
|
||||||
|
const saveDialogResult = await this.showSaveDialog(item);
|
||||||
|
if (saveDialogResult.canceled || !saveDialogResult.filePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.willDownloadURLs.set(url, {filePath: saveDialogResult.filePath, bookmark: saveDialogResult.bookmark});
|
||||||
|
} else {
|
||||||
|
const filename = this.createFilename(item);
|
||||||
|
const savePath = this.getSavePath(`${Config.downloadLocation}`, filename);
|
||||||
|
this.willDownloadURLs.set(url, {filePath: savePath});
|
||||||
|
}
|
||||||
|
|
||||||
|
webContents.downloadURL(url);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,6 +149,27 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
cb({});
|
cb({});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reloadFilesForMAS = () => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
if (!__IS_MAC_APP_STORE__) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of Object.values(this.downloads)) {
|
||||||
|
if (file.bookmark) {
|
||||||
|
this.bookmarks.set(this.getDownloadedFileId(file), {originalPath: file.location, bookmark: file.bookmark});
|
||||||
|
|
||||||
|
if (file.mimeType?.toLowerCase().startsWith('image/')) {
|
||||||
|
const func = app.startAccessingSecurityScopedResource(file.bookmark);
|
||||||
|
fs.copyFileSync(file.location, path.resolve(app.getPath('temp'), path.basename(file.location)));
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
checkForDeletedFiles = () => {
|
checkForDeletedFiles = () => {
|
||||||
log.debug('DownloadsManager.checkForDeletedFiles');
|
log.debug('DownloadsManager.checkForDeletedFiles');
|
||||||
|
|
||||||
|
@ -208,10 +253,16 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(item.location)) {
|
if (fs.existsSync(item.location)) {
|
||||||
|
let func;
|
||||||
|
const bookmark = this.bookmarks.get(this.getDownloadedFileId(item));
|
||||||
|
if (bookmark) {
|
||||||
|
func = app.startAccessingSecurityScopedResource(bookmark.bookmark);
|
||||||
|
}
|
||||||
shell.openPath(item.location).catch((err) => {
|
shell.openPath(item.location).catch((err) => {
|
||||||
log.debug('DownloadsDropdownView.openFileError', {err});
|
log.debug('DownloadsDropdownView.openFileError', {err});
|
||||||
this.showFileInFolder(item);
|
this.showFileInFolder(item);
|
||||||
});
|
});
|
||||||
|
func?.();
|
||||||
} else {
|
} else {
|
||||||
log.debug('DownloadsDropdownView.openFile', 'COULD_NOT_OPEN_FILE');
|
log.debug('DownloadsDropdownView.openFile', 'COULD_NOT_OPEN_FILE');
|
||||||
this.markFileAsDeleted(item);
|
this.markFileAsDeleted(item);
|
||||||
|
@ -353,17 +404,12 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
const fileElements = filename.split('.');
|
const fileElements = filename.split('.');
|
||||||
const filters = this.getFileFilters(fileElements);
|
const filters = this.getFileFilters(fileElements);
|
||||||
|
|
||||||
const newPath = dialog.showSaveDialogSync({
|
return dialog.showSaveDialog({
|
||||||
title: filename,
|
title: filename,
|
||||||
defaultPath: filename,
|
defaultPath: filename,
|
||||||
filters,
|
filters,
|
||||||
|
securityScopedBookmarks: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newPath) {
|
|
||||||
item.setSavePath(newPath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private closeDownloadsDropdown = () => {
|
private closeDownloadsDropdown = () => {
|
||||||
|
@ -383,11 +429,11 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private upsertFileToDownloads = (item: DownloadItem, state: DownloadItemState) => {
|
private upsertFileToDownloads = (item: DownloadItem, state: DownloadItemState, overridePath?: string) => {
|
||||||
const fileId = this.getFileId(item);
|
const fileId = this.getFileId(item);
|
||||||
log.debug('DownloadsManager.upsertFileToDownloads', {fileId});
|
log.debug('DownloadsManager.upsertFileToDownloads', {fileId});
|
||||||
|
|
||||||
const formattedItem = this.formatDownloadItem(item, state);
|
const formattedItem = this.formatDownloadItem(item, state, overridePath);
|
||||||
this.save(fileId, formattedItem);
|
this.save(fileId, formattedItem);
|
||||||
this.checkIfMaxFilesReached();
|
this.checkIfMaxFilesReached();
|
||||||
};
|
};
|
||||||
|
@ -447,7 +493,14 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
displayDownloadCompleted(path.basename(item.savePath), item.savePath, WindowManager.getServerNameByWebContentsId(webContents.id) || '');
|
displayDownloadCompleted(path.basename(item.savePath), item.savePath, WindowManager.getServerNameByWebContentsId(webContents.id) || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.upsertFileToDownloads(item, state);
|
const bookmark = this.bookmarks.get(this.getFileId(item));
|
||||||
|
if (bookmark) {
|
||||||
|
const func = app.startAccessingSecurityScopedResource(bookmark?.bookmark);
|
||||||
|
fs.copyFileSync(path.resolve(app.getPath('temp'), path.basename(bookmark.originalPath)), bookmark.originalPath);
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.upsertFileToDownloads(item, state, bookmark?.originalPath);
|
||||||
|
|
||||||
this.fileSizes.delete(item.getFilename());
|
this.fileSizes.delete(item.getFilename());
|
||||||
this.progressingItems.delete(this.getFileId(item));
|
this.progressingItems.delete(this.getFileId(item));
|
||||||
|
@ -508,7 +561,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
/**
|
/**
|
||||||
* Internal utils
|
* Internal utils
|
||||||
*/
|
*/
|
||||||
private formatDownloadItem = (item: DownloadItem, state: DownloadItemState): DownloadedItem => {
|
private formatDownloadItem = (item: DownloadItem, state: DownloadItemState, overridePath?: string): DownloadedItem => {
|
||||||
const totalBytes = this.getFileSize(item);
|
const totalBytes = this.getFileSize(item);
|
||||||
const receivedBytes = item.getReceivedBytes();
|
const receivedBytes = item.getReceivedBytes();
|
||||||
const progress = getPercentage(receivedBytes, totalBytes);
|
const progress = getPercentage(receivedBytes, totalBytes);
|
||||||
|
@ -517,15 +570,20 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
addedAt: doubleSecToMs(item.getStartTime()),
|
addedAt: doubleSecToMs(item.getStartTime()),
|
||||||
filename: this.getFileId(item),
|
filename: this.getFileId(item),
|
||||||
mimeType: item.getMimeType(),
|
mimeType: item.getMimeType(),
|
||||||
location: item.getSavePath(),
|
location: overridePath ?? item.getSavePath(),
|
||||||
progress,
|
progress,
|
||||||
receivedBytes,
|
receivedBytes,
|
||||||
state,
|
state,
|
||||||
totalBytes,
|
totalBytes,
|
||||||
type: DownloadItemTypeEnum.FILE,
|
type: DownloadItemTypeEnum.FILE,
|
||||||
|
bookmark: this.getBookmark(item),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getBookmark = (item: DownloadItem) => {
|
||||||
|
return this.bookmarks.get(this.getFileId(item))?.bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
private getFileSize = (item: DownloadItem) => {
|
private getFileSize = (item: DownloadItem) => {
|
||||||
const itemTotalBytes = item.getTotalBytes();
|
const itemTotalBytes = item.getTotalBytes();
|
||||||
if (!itemTotalBytes) {
|
if (!itemTotalBytes) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
START_UPGRADE,
|
START_UPGRADE,
|
||||||
TOGGLE_DOWNLOADS_DROPDOWN_MENU,
|
TOGGLE_DOWNLOADS_DROPDOWN_MENU,
|
||||||
UPDATE_DOWNLOADS_DROPDOWN,
|
UPDATE_DOWNLOADS_DROPDOWN,
|
||||||
|
GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
|
|
||||||
console.log('preloaded for the downloadsDropdown!');
|
console.log('preloaded for the downloadsDropdown!');
|
||||||
|
@ -28,6 +29,10 @@ contextBridge.exposeInMainWorld('process', {
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('mas', {
|
||||||
|
getThumbnailLocation: (location) => ipcRenderer.invoke(GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION, location),
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('click', () => {
|
window.addEventListener('click', () => {
|
||||||
ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN_MENU);
|
ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN_MENU);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import {BrowserView, BrowserWindow, ipcMain, IpcMainEvent} from 'electron';
|
import path from 'path';
|
||||||
|
|
||||||
|
import {app, BrowserView, BrowserWindow, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||||
|
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
|
|
||||||
|
@ -17,6 +19,7 @@ import {
|
||||||
REQUEST_DOWNLOADS_DROPDOWN_INFO,
|
REQUEST_DOWNLOADS_DROPDOWN_INFO,
|
||||||
UPDATE_DOWNLOADS_DROPDOWN,
|
UPDATE_DOWNLOADS_DROPDOWN,
|
||||||
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
|
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
|
||||||
|
GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import {TAB_BAR_HEIGHT, DOWNLOADS_DROPDOWN_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT, DOWNLOADS_DROPDOWN_FULL_WIDTH} from 'common/utils/constants';
|
import {TAB_BAR_HEIGHT, DOWNLOADS_DROPDOWN_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT, DOWNLOADS_DROPDOWN_FULL_WIDTH} from 'common/utils/constants';
|
||||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
|
@ -66,6 +69,7 @@ export default class DownloadsDropdownView {
|
||||||
ipcMain.on(DOWNLOADS_DROPDOWN_SHOW_FILE_IN_FOLDER, this.showFileInFolder);
|
ipcMain.on(DOWNLOADS_DROPDOWN_SHOW_FILE_IN_FOLDER, this.showFileInFolder);
|
||||||
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN, this.updateDownloads);
|
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN, this.updateDownloads);
|
||||||
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM, this.updateDownloadsDropdownMenuItem);
|
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM, this.updateDownloadsDropdownMenuItem);
|
||||||
|
ipcMain.handle(GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION, this.getDownloadImageThumbnailLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDownloads = (event: IpcMainEvent, downloads: DownloadedItems) => {
|
updateDownloads = (event: IpcMainEvent, downloads: DownloadedItems) => {
|
||||||
|
@ -198,4 +202,15 @@ export default class DownloadsDropdownView {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.view.webContents.destroy();
|
this.view.webContents.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDownloadImageThumbnailLocation = (event: IpcMainInvokeEvent, location: string) => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
if (!__IS_MAC_APP_STORE__) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.resolve(app.getPath('temp'), path.basename(location));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {DownloadedItem} from 'types/downloads';
|
import {DownloadedItem} from 'types/downloads';
|
||||||
|
|
||||||
import {CheckCircleIcon, CloseCircleIcon} from '@mattermost/compass-icons/components';
|
import {CheckCircleIcon, CloseCircleIcon} from '@mattermost/compass-icons/components';
|
||||||
|
@ -19,6 +19,8 @@ const colorRed = '#D24B4E';
|
||||||
const isWin = window.process.platform === 'win32';
|
const isWin = window.process.platform === 'win32';
|
||||||
|
|
||||||
const Thumbnail = ({item}: OwnProps) => {
|
const Thumbnail = ({item}: OwnProps) => {
|
||||||
|
const [imageUrl, setImageUrl] = useState<string | undefined>();
|
||||||
|
|
||||||
const showBadge = (state: DownloadedItem['state']) => {
|
const showBadge = (state: DownloadedItem['state']) => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'completed':
|
case 'completed':
|
||||||
|
@ -42,15 +44,27 @@ const Thumbnail = ({item}: OwnProps) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchThumbnail = async () => {
|
||||||
|
const imageUrl = await window.mas.getThumbnailLocation(item.location);
|
||||||
|
setImageUrl(imageUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchThumbnail();
|
||||||
|
}, [item]);
|
||||||
|
|
||||||
const showImagePreview = isImageFile(item) && item.state === 'completed';
|
const showImagePreview = isImageFile(item) && item.state === 'completed';
|
||||||
|
if (showImagePreview && !imageUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='DownloadsDropdown__Thumbnail__Container'>
|
<div className='DownloadsDropdown__Thumbnail__Container'>
|
||||||
{showImagePreview ?
|
{showImagePreview && imageUrl ?
|
||||||
<div
|
<div
|
||||||
className='DownloadsDropdown__Thumbnail preview'
|
className='DownloadsDropdown__Thumbnail preview'
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url("${isWin ? `file:///${item.location.replaceAll('\\', '/')}` : item.location}")`,
|
backgroundImage: `url("${isWin ? `file:///${imageUrl.replaceAll('\\', '/')}` : imageUrl}")`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
}}
|
}}
|
||||||
/> :
|
/> :
|
||||||
|
|
|
@ -17,6 +17,7 @@ export type DownloadedItem = {
|
||||||
addedAt: number;
|
addedAt: number;
|
||||||
receivedBytes: number;
|
receivedBytes: number;
|
||||||
totalBytes: number;
|
totalBytes: number;
|
||||||
|
bookmark?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DownloadedItems = Record<string, DownloadedItem>;
|
export type DownloadedItems = Record<string, DownloadedItem>;
|
||||||
|
|
|
@ -20,5 +20,8 @@ declare global {
|
||||||
timers: {
|
timers: {
|
||||||
setImmediate: typeof setImmediate;
|
setImmediate: typeof setImmediate;
|
||||||
};
|
};
|
||||||
|
mas: {
|
||||||
|
getThumbnailLocation: (location: string) => Promise<string>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue