diff --git a/app/localization/translated/be.json b/app/localization/translated/be.json index 4a498b6d96..7b74a00974 100644 --- a/app/localization/translated/be.json +++ b/app/localization/translated/be.json @@ -107,6 +107,8 @@ "AsyncAutocomplete.notFound": "Нічога не знойдзена", "AttachmentModal.errorFileStructure": "Няправільная структура файла", "AttachmentModal.title": "Укладанне", + "Common.openInNewTab": "Адкрыць у новай ўкладцы", + "Common.download": "Спампаваць", "Attachments.noAttachmentsMessage": "Няма ўкладанняў для адлюстравання", "AttributeEditor.attributeKeyLengthHint": "Ключ атрыбуту павінен мець памер ад 1 да 128", "AttributeEditor.attributeValueLengthHint": "Значэнне атрыбуту павінна мець памер ад 1 да 128", diff --git a/app/localization/translated/ru.json b/app/localization/translated/ru.json index 407b072dae..e5064a84a4 100644 --- a/app/localization/translated/ru.json +++ b/app/localization/translated/ru.json @@ -107,7 +107,8 @@ "AsyncAutocomplete.notFound": "Ничего не найдено", "AttachmentModal.errorFileStructure": "Неверная структура файла", "AttachmentModal.title": "Вложение", - "Attachments.noAttachmentsMessage": "Нет вложений для отображения", + "Common.openInNewTab": "Открыть в новой вкладке", + "Common.download": "Скачать", "AttributeEditor.attributeKeyLengthHint": "Ключ атрибута должен быть длиной от 1 до 128", "AttributeEditor.attributeValueLengthHint": "Значение атрибута должно быть длиной от 1 до 128", "AttributeEditor.uniqueAttributeKeyHint": "Ключ атрибута должен быть уникальным", diff --git a/app/localization/translated/uk.json b/app/localization/translated/uk.json index 94ac00e4fc..dcbf77eba1 100644 --- a/app/localization/translated/uk.json +++ b/app/localization/translated/uk.json @@ -107,6 +107,8 @@ "AsyncAutocomplete.notFound": "Нічого не знайдено", "AttachmentModal.errorFileStructure": "Некоректна структура файлу", "AttachmentModal.title": "Вкладення", + "Common.openInNewTab": "Відкрити в новій вкладці", + "Common.download": "Завантажити", "Attachments.noAttachmentsMessage": "Немає вкладень для відображення", "AttributeEditor.attributeKeyLengthHint": "Ключ атрибута повинен бути довжиною від 1 до 128", "AttributeEditor.attributeValueLengthHint": "Значення атрибута повинно бути довжиною від 1 до 128", diff --git a/app/src/common/constants/fileTypes.js b/app/src/common/constants/fileTypes.js index 889c095ef3..a45bbd60ee 100644 --- a/app/src/common/constants/fileTypes.js +++ b/app/src/common/constants/fileTypes.js @@ -23,6 +23,7 @@ export const CSV = 'csv'; export const PHP = 'php'; export const HAR = 'har'; export const TXT = 'txt'; +export const PDF = 'pdf'; export const ZIP = 'zip'; export const RAR = 'rar'; export const TGZ = 'tgz'; diff --git a/app/src/common/constants/localization.js b/app/src/common/constants/localization.js index e0749a4e45..e71f41de7a 100644 --- a/app/src/common/constants/localization.js +++ b/app/src/common/constants/localization.js @@ -165,4 +165,12 @@ export const COMMON_LOCALE_KEYS = defineMessages({ id: 'Common.proceedValidItems', defaultMessage: 'Proceed Valid Items', }, + DOWNLOAD: { + id: 'Common.download', + defaultMessage: 'Download', + }, + OPEN_IN_NEW_TAB: { + id: 'Common.openInNewTab', + defaultMessage: 'Open in new tab', + }, }); diff --git a/app/src/common/img/open-in-inline.svg b/app/src/common/img/open-in-inline.svg new file mode 100644 index 0000000000..2a4446e2a2 --- /dev/null +++ b/app/src/common/img/open-in-inline.svg @@ -0,0 +1,13 @@ + + diff --git a/app/src/common/utils/downloadFile.js b/app/src/common/utils/downloadFile.js index 8bb4f8ad50..1480b86f74 100644 --- a/app/src/common/utils/downloadFile.js +++ b/app/src/common/utils/downloadFile.js @@ -16,11 +16,12 @@ import { fetch } from 'common/utils/fetch'; -export const downloadFile = (url) => { +export const downloadFile = (url, fileNameFallback) => { fetch(url, { responseType: 'blob' }, true).then((response) => { const data = response.data; const attachmentHeader = response.headers['content-disposition']; - const fileName = /filename=(.*?)(?:\s|$)/.exec(attachmentHeader)[1]; + const extractedFileName = /filename=(.*?)(?:\s|$)/.exec(attachmentHeader); + const fileName = extractedFileName ? extractedFileName[1] : fileNameFallback; const objectURL = URL.createObjectURL(data); if ('msSaveOrOpenBlob' in navigator) { navigator.msSaveOrOpenBlob(data, fileName); diff --git a/app/src/components/main/analytics/events/logPageEvents.js b/app/src/components/main/analytics/events/logPageEvents.js index 2125aa68d9..1d5600d5ee 100644 --- a/app/src/components/main/analytics/events/logPageEvents.js +++ b/app/src/components/main/analytics/events/logPageEvents.js @@ -120,9 +120,38 @@ export const LOG_PAGE_EVENTS = { label: 'Sort logs', }, ATTACHMENT_IN_LOG_MSG: { - category: LOG_PAGE, - action: 'Click on Attachment in Log Message', - label: 'Open Attachment', + OPEN_IN_MODAL: { + category: LOG_PAGE, + action: 'Click on Attachment in Log Message', + label: 'Open Attachment in modal', + }, + DOWNLOAD: { + category: LOG_PAGE, + action: 'Click on Download Attachment icon in Log Message', + label: 'Download Attachment', + }, + OPEN_IN_NEW_TAB: { + category: LOG_PAGE, + action: 'Click on Open Attachment in new tab icon in Log Message', + label: 'Open Attachment in new browser tab', + }, + }, + ATTACHMENT_IN_CAROUSEL: { + OPEN_IN_MODAL: { + category: LOG_PAGE, + action: 'Click on Attachment in Attachments section', + label: 'Open Attachment in modal', + }, + DOWNLOAD: { + category: LOG_PAGE, + action: 'Click on Download Attachment icon in Attachments section', + label: 'Download Attachment', + }, + OPEN_IN_NEW_TAB: { + category: LOG_PAGE, + action: 'Click on Open Attachment in new tab icon in Attachments section', + label: 'Open Attachment in new browser tab', + }, }, EXPAND_LOG_MSG: { category: LOG_PAGE, @@ -159,11 +188,6 @@ export const LOG_PAGE_EVENTS = { action: 'Click on icon Next Attachment', label: 'Show Next Attachment', }, - ATTACHMENT_CLICK: { - category: LOG_PAGE, - action: 'Click on opened Attachment', - label: 'Arise modal with Attachment', - }, ATTACHMENT_THUMBNAIL: { category: LOG_PAGE, action: 'Click on thumbnail of Attachment', @@ -174,11 +198,6 @@ export const LOG_PAGE_EVENTS = { action: 'Click on icon Close on Modal Attachment', label: 'Close Modal Attachment', }, - ROTATE_ICON_ATTACHMENT_MODAL: { - category: LOG_PAGE, - action: 'Click on icon Rotate on Modal Attachment', - label: 'Rotate Attachment', - }, CLOSE_BTN_ATTACHMENT_MODAL: { category: LOG_PAGE, action: 'Click on Btn Close on Modal Attachment', diff --git a/app/src/controllers/log/attachments/actionCreators.js b/app/src/controllers/log/attachments/actionCreators.js index d8e4731811..c8ec8f8f31 100644 --- a/app/src/controllers/log/attachments/actionCreators.js +++ b/app/src/controllers/log/attachments/actionCreators.js @@ -16,10 +16,12 @@ import { FETCH_ATTACHMENTS_CONCAT_ACTION, - OPEN_ATTACHMENT_ACTION, CLEAR_ATTACHMENTS_ACTION, FETCH_FIRST_ATTACHMENTS_ACTION, SET_ACTIVE_ATTACHMENT_ACTION, + DOWNLOAD_ATTACHMENT_ACTION, + OPEN_ATTACHMENT_IN_MODAL_ACTION, + OPEN_ATTACHMENT_IN_BROWSER_ACTION, } from './constants'; export const fetchAttachmentsConcatAction = (payload) => ({ @@ -36,12 +38,22 @@ export const clearAttachmentsAction = () => ({ type: CLEAR_ATTACHMENTS_ACTION, }); -export const openAttachmentAction = (payload) => ({ - type: OPEN_ATTACHMENT_ACTION, - payload, -}); - export const setActiveAttachmentAction = (attachmentId) => ({ type: SET_ACTIVE_ATTACHMENT_ACTION, payload: attachmentId, }); + +export const openAttachmentInModalAction = (payload) => ({ + type: OPEN_ATTACHMENT_IN_MODAL_ACTION, + payload, +}); + +export const downloadAttachmentAction = (payload) => ({ + type: DOWNLOAD_ATTACHMENT_ACTION, + payload, +}); + +export const openAttachmentInBrowserAction = (id) => ({ + type: OPEN_ATTACHMENT_IN_BROWSER_ACTION, + payload: id, +}); diff --git a/app/src/controllers/log/attachments/constants.js b/app/src/controllers/log/attachments/constants.js index d5a8912d12..a9f4f5ba08 100644 --- a/app/src/controllers/log/attachments/constants.js +++ b/app/src/controllers/log/attachments/constants.js @@ -26,15 +26,19 @@ import txt from 'common/img/attachments/txt.svg'; import archive from 'common/img/attachments/archive.svg'; import * as FILE_TYPES from 'common/constants/fileTypes'; +export const ALL_ALLOWED = '*'; export const ATTACHMENTS_NAMESPACE = 'log/attachments'; export const FETCH_ATTACHMENTS_CONCAT_ACTION = 'fetchAttachmentsConcatAction'; export const CLEAR_ATTACHMENTS_ACTION = 'clearAttachmentsAction'; -export const OPEN_ATTACHMENT_ACTION = 'openAttachmentAction'; export const ATTACHMENT_HAR_FILE_MODAL_ID = 'attachmentHarFileModal'; export const ATTACHMENT_CODE_MODAL_ID = 'attachmentCodeModal'; export const FETCH_FIRST_ATTACHMENTS_ACTION = 'fetchFirstAttachments'; export const SET_ACTIVE_ATTACHMENT_ACTION = 'setActiveAttachment'; +export const DOWNLOAD_ATTACHMENT_ACTION = 'downloadAttachmentAction'; +export const OPEN_ATTACHMENT_IN_MODAL_ACTION = 'openAttachmentInModalAction'; +export const OPEN_ATTACHMENT_IN_BROWSER_ACTION = 'openAttachmentInBrowserAction'; + export const FILE_PREVIEWS_MAP = { [FILE_TYPES.XML]: xml, [FILE_TYPES.JAVASCRIPT]: js, @@ -68,5 +72,32 @@ export const FILE_MODAL_IDS_MAP = { [FILE_TYPES.PHP]: ATTACHMENT_CODE_MODAL_ID, [FILE_TYPES.HAR]: ATTACHMENT_HAR_FILE_MODAL_ID, }; + +export const FILE_ACTIONS_MAP = { + [DOWNLOAD_ATTACHMENT_ACTION]: ALL_ALLOWED, + [OPEN_ATTACHMENT_IN_BROWSER_ACTION]: [ + FILE_TYPES.XML, + FILE_TYPES.JAVASCRIPT, + FILE_TYPES.JSON, + FILE_TYPES.CSS, + FILE_TYPES.PHP, + FILE_TYPES.HAR, + FILE_TYPES.TXT, + FILE_TYPES.PLAIN, + FILE_TYPES.HTML, + FILE_TYPES.CSV, + FILE_TYPES.PDF, + FILE_TYPES.IMAGE, + ], + [OPEN_ATTACHMENT_IN_MODAL_ACTION]: [ + FILE_TYPES.XML, + FILE_TYPES.JAVASCRIPT, + FILE_TYPES.JSON, + FILE_TYPES.CSS, + FILE_TYPES.PHP, + FILE_TYPES.HAR, + ], +}; + export const DEFAULT_PAGE_SIZE = 6; export const DEFAULT_LOADED_PAGES = 2; diff --git a/app/src/controllers/log/attachments/index.js b/app/src/controllers/log/attachments/index.js index 81118fb1da..38aee075fc 100644 --- a/app/src/controllers/log/attachments/index.js +++ b/app/src/controllers/log/attachments/index.js @@ -15,11 +15,13 @@ */ export { - openAttachmentAction, fetchAttachmentsConcatAction, clearAttachmentsAction, fetchFirstAttachmentsAction, setActiveAttachmentAction, + openAttachmentInModalAction, + downloadAttachmentAction, + openAttachmentInBrowserAction, } from './actionCreators'; export { FILE_PREVIEWS_MAP, @@ -27,8 +29,16 @@ export { ATTACHMENT_CODE_MODAL_ID, ATTACHMENT_HAR_FILE_MODAL_ID, ATTACHMENTS_NAMESPACE, + DOWNLOAD_ATTACHMENT_ACTION, + OPEN_ATTACHMENT_IN_BROWSER_ACTION, + OPEN_ATTACHMENT_IN_MODAL_ACTION, } from './constants'; -export { getFileIconSource, getAttachmentModalId, extractExtension } from './utils'; +export { + getFileIconSource, + getAttachmentModalId, + extractExtension, + isFileActionAllowed, +} from './utils'; export { attachmentItemsSelector, attachmentsLoadingSelector, diff --git a/app/src/controllers/log/attachments/sagas.js b/app/src/controllers/log/attachments/sagas.js index 2e51e5a9ed..979464d60e 100644 --- a/app/src/controllers/log/attachments/sagas.js +++ b/app/src/controllers/log/attachments/sagas.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import { takeLatest, call, put, all, select, take } from 'redux-saga/effects'; +import { takeLatest, takeEvery, call, put, all, select, take } from 'redux-saga/effects'; import { URLS } from 'common/urls'; import { fetch } from 'common/utils/fetch'; import { showModalAction } from 'controllers/modal'; @@ -28,19 +28,27 @@ import { activeLogIdSelector, } from 'controllers/log/selectors'; import { DETAILED_LOG_VIEW } from 'controllers/log/constants'; +import { downloadFile } from 'common/utils/downloadFile'; import { JSON as JSON_TYPE } from 'common/constants/fileTypes'; import { PAGE_KEY, SIZE_KEY } from 'controllers/pagination'; import { ATTACHMENT_CODE_MODAL_ID, ATTACHMENT_HAR_FILE_MODAL_ID, - OPEN_ATTACHMENT_ACTION, FETCH_ATTACHMENTS_CONCAT_ACTION, ATTACHMENTS_NAMESPACE, DEFAULT_PAGE_SIZE, DEFAULT_LOADED_PAGES, FETCH_FIRST_ATTACHMENTS_ACTION, + DOWNLOAD_ATTACHMENT_ACTION, + OPEN_ATTACHMENT_IN_MODAL_ACTION, + OPEN_ATTACHMENT_IN_BROWSER_ACTION, } from './constants'; -import { getAttachmentModalId, extractExtension, isTextWithJson } from './utils'; +import { + getAttachmentModalId, + extractExtension, + isTextWithJson, + createAttachmentName, +} from './utils'; function* getAttachmentURL() { const activeProject = yield select(activeProjectSelector); @@ -78,19 +86,19 @@ function* fetchFirstAttachments({ payload }) { yield call(fetchAttachmentsConcat, { payload: { params } }); } -export function fetchData({ projectId, binaryId }) { - return fetch(URLS.getFileById(projectId, binaryId)); +export function fetchFileData({ projectId, id }) { + return fetch(URLS.getFileById(projectId, id)); } /* HAR */ -function* openHarModalsWorker(data) { - const harData = yield call(fetchData, data); +function* openHarModalWorker(data) { + const harData = yield call(fetchFileData, data); yield put(showModalAction({ id: ATTACHMENT_HAR_FILE_MODAL_ID, data: { harData } })); } /* BINARY */ -function* openBinaryModalsWorker(data) { - const binaryData = yield call(fetchData, data); +function* openBinaryModalWorker(data) { + const binaryData = yield call(fetchFileData, data); const content = data.extension === JSON_TYPE && !isTextWithJson(data.contentType) ? JSON.stringify(binaryData, null, 4) @@ -98,34 +106,44 @@ function* openBinaryModalsWorker(data) { yield put( showModalAction({ id: ATTACHMENT_CODE_MODAL_ID, - data: { extension: data.extension, content }, + data: { extension: data.extension, content, id: data.id }, }), ); } const ATTACHMENT_MODAL_WORKERS = { - [ATTACHMENT_HAR_FILE_MODAL_ID]: openHarModalsWorker, - [ATTACHMENT_CODE_MODAL_ID]: openBinaryModalsWorker, + [ATTACHMENT_HAR_FILE_MODAL_ID]: openHarModalWorker, + [ATTACHMENT_CODE_MODAL_ID]: openBinaryModalWorker, }; -function* openAttachment({ payload: { id, contentType } }) { +function* openAttachmentInModal({ payload: { id, contentType } }) { const modalId = getAttachmentModalId(contentType); const projectId = yield select(activeProjectSelector); + if (modalId) { - const data = { projectId, binaryId: id, extension: extractExtension(contentType), contentType }; + const data = { projectId, id, extension: extractExtension(contentType), contentType }; try { yield call(ATTACHMENT_MODAL_WORKERS[modalId], data); } catch (e) {} // eslint-disable-line no-empty + } +} + +function* downloadAttachment({ payload: { id, contentType } }) { + const projectId = yield select(activeProjectSelector); + + downloadFile(URLS.getFileById(projectId, id), createAttachmentName(id, contentType)); +} + +function* openAttachmentInBrowser({ payload: id }) { + const projectId = yield select(activeProjectSelector); + const data = yield call(fetch, URLS.getFileById(projectId, id), { responseType: 'blob' }); + + if (window.navigator && window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob(data); } else { - const data = yield call(fetch, URLS.getFileById(projectId, id), { responseType: 'blob' }); - - if (window.navigator && window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveOrOpenBlob(data); - } else { - const url = URL.createObjectURL(data); - const newWindow = window.open(url); - newWindow.onbeforeunload = () => URL.revokeObjectURL(url); - } + const url = URL.createObjectURL(data); + const newWindow = window.open(url); + newWindow.onbeforeunload = () => URL.revokeObjectURL(url); } } @@ -137,10 +155,24 @@ function* watchFetchFirstAttachments() { yield takeLatest(FETCH_FIRST_ATTACHMENTS_ACTION, fetchFirstAttachments); } -function* watchOpenAttachment() { - yield takeLatest(OPEN_ATTACHMENT_ACTION, openAttachment); +function* watchOpenAttachmentInModal() { + yield takeLatest(OPEN_ATTACHMENT_IN_MODAL_ACTION, openAttachmentInModal); +} + +function* watchDownloadAttachment() { + yield takeEvery(DOWNLOAD_ATTACHMENT_ACTION, downloadAttachment); +} + +function* watchOpenAttachmentInBrowser() { + yield takeEvery(OPEN_ATTACHMENT_IN_BROWSER_ACTION, openAttachmentInBrowser); } export function* attachmentSagas() { - yield all([watchOpenAttachment(), watchFetchAttachments(), watchFetchFirstAttachments()]); + yield all([ + watchOpenAttachmentInModal(), + watchOpenAttachmentInBrowser(), + watchDownloadAttachment(), + watchFetchAttachments(), + watchFetchFirstAttachments(), + ]); } diff --git a/app/src/controllers/log/attachments/sagas.test.js b/app/src/controllers/log/attachments/sagas.test.js index 7a726db1d9..1affac41c5 100644 --- a/app/src/controllers/log/attachments/sagas.test.js +++ b/app/src/controllers/log/attachments/sagas.test.js @@ -15,7 +15,7 @@ */ import { fetch } from 'common/utils/fetch'; -import { fetchData } from './sagas'; +import { fetchFileData } from './sagas'; jest.mock('common/utils/fetch', () => ({ fetch: jest.fn(), @@ -23,12 +23,12 @@ jest.mock('common/utils/fetch', () => ({ const mockParams = { projectId: 'test_project', - binaryId: 'abcd', + id: 'abcd', }; describe('Attachments Sagas', () => { - test('fetchData resolves data', () => { - fetchData(mockParams); + test('fetchFileData resolves data', () => { + fetchFileData(mockParams); expect(fetch).toBeCalledWith('/api/v1/data/test_project/abcd'); }); }); diff --git a/app/src/controllers/log/attachments/utils.js b/app/src/controllers/log/attachments/utils.js index 7d4de56022..cc878ba66b 100644 --- a/app/src/controllers/log/attachments/utils.js +++ b/app/src/controllers/log/attachments/utils.js @@ -15,9 +15,15 @@ */ import { URLS } from 'common/urls'; -import { IMAGE } from 'common/constants/fileTypes'; +import { IMAGE, PLAIN, TXT } from 'common/constants/fileTypes'; import attachment from 'common/img/attachments/attachment.svg'; -import { FILE_PREVIEWS_MAP, FILE_MODAL_IDS_MAP, FILE_PATTERNS_MAP } from './constants'; +import { + FILE_PREVIEWS_MAP, + FILE_MODAL_IDS_MAP, + FILE_PATTERNS_MAP, + FILE_ACTIONS_MAP, + ALL_ALLOWED, +} from './constants'; const getAttachmentTypeConfig = (contentType) => (contentType && contentType.toLowerCase().split('/')) || ''; @@ -49,9 +55,10 @@ export const getFileIconSource = (item, projectId, loadThumbnail) => { }; export const getAttachmentModalId = (contentType) => { - const [fileType, extension] = getAttachmentTypeConfig(contentType); - const extensionFromPattern = getExtensionFromPattern(extension || fileType); - return FILE_MODAL_IDS_MAP[extension || fileType] || FILE_MODAL_IDS_MAP[extensionFromPattern]; + const extension = extractExtension(contentType); + const extensionFromPattern = getExtensionFromPattern(extension); + + return FILE_MODAL_IDS_MAP[extension] || FILE_MODAL_IDS_MAP[extensionFromPattern]; }; export const createAttachment = (item, projectId) => { @@ -66,3 +73,28 @@ export const createAttachment = (item, projectId) => { isImage, }; }; + +export const createAttachmentName = (id, contentType) => { + const extension = extractExtension(contentType); + + return `attachment_${id}.${extension === PLAIN ? TXT : extension}`; +}; + +export const isFileActionAllowed = (contentType, action) => { + const allowedFileTypes = FILE_ACTIONS_MAP[action] || []; + + if (allowedFileTypes === ALL_ALLOWED) { + return true; + } + + const [fileType] = getAttachmentTypeConfig(contentType); + + if (fileType === IMAGE) { + return allowedFileTypes.includes(IMAGE); + } + + const extension = extractExtension(contentType); + const extensionFromPattern = getExtensionFromPattern(extension); + + return !!allowedFileTypes.some((type) => type === extension || type === extensionFromPattern); +}; diff --git a/app/src/pages/inside/logsPage/attachmentActions/actionsItem/actionsItem.jsx b/app/src/pages/inside/logsPage/attachmentActions/actionsItem/actionsItem.jsx new file mode 100644 index 0000000000..edcf9863c2 --- /dev/null +++ b/app/src/pages/inside/logsPage/attachmentActions/actionsItem/actionsItem.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Parser from 'html-react-parser'; +import classNames from 'classnames/bind'; +import styles from './actionsItem.scss'; + +const cx = classNames.bind(styles); + +export const ActionsItem = ({ caption, hidden, icon, action, showCaption }) => ( + + {Parser(icon)} + {showCaption && {caption}} + +); +ActionsItem.propTypes = { + caption: PropTypes.string, + hidden: PropTypes.bool, + showCaption: PropTypes.bool, + icon: PropTypes.any, + action: PropTypes.func, +}; +ActionsItem.defaultProps = { + caption: '', + hidden: false, + showCaption: false, + icon: '', + action: null, +}; diff --git a/app/src/pages/inside/logsPage/attachmentActions/actionsItem/actionsItem.scss b/app/src/pages/inside/logsPage/attachmentActions/actionsItem/actionsItem.scss new file mode 100644 index 0000000000..07a3949c6c --- /dev/null +++ b/app/src/pages/inside/logsPage/attachmentActions/actionsItem/actionsItem.scss @@ -0,0 +1,18 @@ +.actions-item { + cursor: pointer; + + &.hidden { + visibility: hidden; + } +} + +.icon { + display: inline-block; + width: 15px; +} + +.caption { + margin-left: 5px; + font-size: 13px; + color: $COLOR--topaz; +} diff --git a/app/src/pages/inside/logsPage/attachmentActions/actionsItem/index.js b/app/src/pages/inside/logsPage/attachmentActions/actionsItem/index.js new file mode 100644 index 0000000000..d157720e7e --- /dev/null +++ b/app/src/pages/inside/logsPage/attachmentActions/actionsItem/index.js @@ -0,0 +1 @@ +export { ActionsItem } from './actionsItem'; diff --git a/app/src/pages/inside/logsPage/attachmentActions/attachmentActions.jsx b/app/src/pages/inside/logsPage/attachmentActions/attachmentActions.jsx new file mode 100644 index 0000000000..a9db833834 --- /dev/null +++ b/app/src/pages/inside/logsPage/attachmentActions/attachmentActions.jsx @@ -0,0 +1,100 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import track from 'react-tracking'; +import { injectIntl } from 'react-intl'; +import PropTypes from 'prop-types'; +import classNames from 'classnames/bind'; +import { COMMON_LOCALE_KEYS } from 'common/constants/localization'; +import { + DOWNLOAD_ATTACHMENT_ACTION, + downloadAttachmentAction, + isFileActionAllowed, + OPEN_ATTACHMENT_IN_BROWSER_ACTION, + openAttachmentInBrowserAction, +} from 'controllers/log/attachments'; +import OpenInIcon from 'common/img/open-in-inline.svg'; +import DownloadIcon from 'common/img/download-inline.svg'; +import { ActionsItem } from './actionsItem'; +import styles from './attachmentActions.scss'; + +const cx = classNames.bind(styles); + +@connect(null, { + downloadAttachmentAction, + openAttachmentInBrowserAction, +}) +@track() +@injectIntl +export class AttachmentActions extends Component { + static propTypes = { + intl: PropTypes.object.isRequired, + value: PropTypes.object, + className: PropTypes.string, + downloadAttachmentAction: PropTypes.func, + openAttachmentInBrowserAction: PropTypes.func, + showCaptions: PropTypes.bool, + tracking: PropTypes.shape({ + trackEvent: PropTypes.func, + getTrackingData: PropTypes.func, + }).isRequired, + events: PropTypes.object, + }; + + static defaultProps = { + value: {}, + className: '', + showCaptions: false, + events: {}, + }; + + constructor(props) { + super(props); + const { + intl: { formatMessage }, + value, + } = props; + + this.actionsConfig = [ + { + id: OPEN_ATTACHMENT_IN_BROWSER_ACTION, + icon: OpenInIcon, + caption: formatMessage(COMMON_LOCALE_KEYS.OPEN_IN_NEW_TAB), + action: this.openAttachmentInNewTab, + hidden: !isFileActionAllowed(value.contentType, OPEN_ATTACHMENT_IN_BROWSER_ACTION), + }, + { + id: DOWNLOAD_ATTACHMENT_ACTION, + icon: DownloadIcon, + caption: formatMessage(COMMON_LOCALE_KEYS.DOWNLOAD), + action: this.downloadAttachment, + hidden: !isFileActionAllowed(value.contentType, DOWNLOAD_ATTACHMENT_ACTION), + }, + ]; + } + + downloadAttachment = () => { + const { events, tracking, value } = this.props; + + tracking.trackEvent(events.DOWNLOAD); + this.props.downloadAttachmentAction(value); + }; + + openAttachmentInNewTab = () => { + const { events, tracking, value } = this.props; + + tracking.trackEvent(events.OPEN_IN_NEW_TAB); + this.props.openAttachmentInBrowserAction(value.id); + }; + + render() { + const { className, showCaptions } = this.props; + + return ( +