|
@@ -1,16 +1,15 @@
|
|
|
import {Fragment, useEffect, useState} from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
|
|
|
+import {openModal} from 'app/actionCreators/modal';
|
|
|
import {Client} from 'app/api';
|
|
|
import Role from 'app/components/acl/role';
|
|
|
import MenuItemActionLink from 'app/components/actions/menuItemActionLink';
|
|
|
import Button from 'app/components/button';
|
|
|
import ButtonBar from 'app/components/buttonBar';
|
|
|
import DropdownLink from 'app/components/dropdownLink';
|
|
|
-import ImageViewer from 'app/components/events/attachmentViewers/imageViewer';
|
|
|
-import LoadingIndicator from 'app/components/loadingIndicator';
|
|
|
import {Panel, PanelBody, PanelFooter} from 'app/components/panels';
|
|
|
-import {IconDownload, IconEllipsis} from 'app/icons';
|
|
|
+import {IconEllipsis} from 'app/icons';
|
|
|
import {t} from 'app/locale';
|
|
|
import space from 'app/styles/space';
|
|
|
import {EventAttachment, Organization, Project} from 'app/types';
|
|
@@ -19,8 +18,8 @@ import withApi from 'app/utils/withApi';
|
|
|
|
|
|
import DataSection from '../dataSection';
|
|
|
|
|
|
-import EmptyState from './emptyState';
|
|
|
-import {platformsMobileWithAttachmentsFeature} from './utils';
|
|
|
+import ImageVisualization from './imageVisualization';
|
|
|
+import Modal, {modalCss} from './modal';
|
|
|
|
|
|
type Props = {
|
|
|
event: Event;
|
|
@@ -33,7 +32,6 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
|
|
|
const [attachments, setAttachments] = useState<EventAttachment[]>([]);
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
const orgSlug = organization.slug;
|
|
|
- const eventPlatform = event.platform;
|
|
|
|
|
|
useEffect(() => {
|
|
|
fetchData();
|
|
@@ -59,35 +57,53 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- function hasPreview(attachment: EventAttachment) {
|
|
|
- switch (attachment.mimetype) {
|
|
|
- case 'image/jpeg':
|
|
|
- case 'image/png':
|
|
|
- case 'image/gif':
|
|
|
- return true;
|
|
|
- default:
|
|
|
- return false;
|
|
|
- }
|
|
|
+ function hasScreenshot(attachment: EventAttachment) {
|
|
|
+ const {mimetype} = attachment;
|
|
|
+ return mimetype === 'image/jpeg' || mimetype === 'image/png';
|
|
|
}
|
|
|
|
|
|
- function renderContent() {
|
|
|
- if (isLoading) {
|
|
|
- return <LoadingIndicator mini />;
|
|
|
- }
|
|
|
-
|
|
|
- const firstAttachmenteWithPreview = attachments.find(hasPreview);
|
|
|
+ async function handleDelete(screenshotAttachmentId: string, downloadUrl: string) {
|
|
|
+ try {
|
|
|
+ await api.requestPromise(downloadUrl.split('/api/0')[1], {
|
|
|
+ method: 'DELETE',
|
|
|
+ });
|
|
|
|
|
|
- if (!firstAttachmenteWithPreview) {
|
|
|
- return <EmptyState platform={eventPlatform} />;
|
|
|
+ setAttachments(
|
|
|
+ attachments.filter(attachment => attachment.id !== screenshotAttachmentId)
|
|
|
+ );
|
|
|
+ } catch (_err) {
|
|
|
+ // TODO: Error-handling
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- const downloadUrl = `/api/0/projects/${organization.slug}/${projectSlug}/events/${event.id}/attachments/${firstAttachmenteWithPreview.id}/`;
|
|
|
+ function handleOpenVisualizationModal(
|
|
|
+ eventAttachment: EventAttachment,
|
|
|
+ downloadUrl: string
|
|
|
+ ) {
|
|
|
+ openModal(
|
|
|
+ modalProps => (
|
|
|
+ <Modal
|
|
|
+ {...modalProps}
|
|
|
+ event={event}
|
|
|
+ orgSlug={orgSlug}
|
|
|
+ projectSlug={projectSlug}
|
|
|
+ eventAttachment={eventAttachment}
|
|
|
+ downloadUrl={downloadUrl}
|
|
|
+ onDelete={() => handleDelete(eventAttachment.id, downloadUrl)}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ {modalCss}
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderContent(screenshotAttachment: EventAttachment) {
|
|
|
+ const downloadUrl = `/api/0/projects/${organization.slug}/${projectSlug}/events/${event.id}/attachments/${screenshotAttachment.id}/`;
|
|
|
|
|
|
return (
|
|
|
<Fragment>
|
|
|
<StyledPanelBody>
|
|
|
- <StyledImageViewer
|
|
|
- attachment={firstAttachmenteWithPreview}
|
|
|
+ <ImageVisualization
|
|
|
+ attachment={screenshotAttachment}
|
|
|
orgId={orgSlug}
|
|
|
projectId={projectSlug}
|
|
|
event={event}
|
|
@@ -95,7 +111,17 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
|
|
|
</StyledPanelBody>
|
|
|
<StyledPanelFooter>
|
|
|
<StyledButtonbar gap={1}>
|
|
|
- <Button size="xsmall">{t('View screenshot')}</Button>
|
|
|
+ <Button
|
|
|
+ size="xsmall"
|
|
|
+ onClick={() =>
|
|
|
+ handleOpenVisualizationModal(
|
|
|
+ screenshotAttachment,
|
|
|
+ `${downloadUrl}?download=1`
|
|
|
+ )
|
|
|
+ }
|
|
|
+ >
|
|
|
+ {t('View screenshot')}
|
|
|
+ </Button>
|
|
|
<DropdownLink
|
|
|
caret={false}
|
|
|
customTitle={
|
|
@@ -109,12 +135,22 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
|
|
|
>
|
|
|
<MenuItemActionLink
|
|
|
shouldConfirm={false}
|
|
|
- icon={<IconDownload size="xs" />}
|
|
|
title={t('Download')}
|
|
|
href={`${downloadUrl}?download=1`}
|
|
|
>
|
|
|
{t('Download')}
|
|
|
</MenuItemActionLink>
|
|
|
+ <MenuItemActionLink
|
|
|
+ shouldConfirm
|
|
|
+ title={t('Delete')}
|
|
|
+ onAction={() => handleDelete(screenshotAttachment.id, downloadUrl)}
|
|
|
+ header={t(
|
|
|
+ 'Screenshots help identify what the user saw when the event happened'
|
|
|
+ )}
|
|
|
+ message={t('Are you sure you wish to delete this screenshot?')}
|
|
|
+ >
|
|
|
+ {t('Delete')}
|
|
|
+ </MenuItemActionLink>
|
|
|
</DropdownLink>
|
|
|
</StyledButtonbar>
|
|
|
</StyledPanelFooter>
|
|
@@ -122,30 +158,23 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // the UI should only render the screenshots feature in events with platforms that support screenshots
|
|
|
- if (
|
|
|
- !eventPlatform ||
|
|
|
- !platformsMobileWithAttachmentsFeature.includes(eventPlatform as any)
|
|
|
- ) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
return (
|
|
|
<Role role={organization.attachmentsRole}>
|
|
|
{({hasRole}) => {
|
|
|
- if (!hasRole) {
|
|
|
- // if the user has no access to the attachments,
|
|
|
- // the UI shall not display the screenshot section
|
|
|
+ const screenshotAttachment = attachments.find(hasScreenshot);
|
|
|
+
|
|
|
+ if (!hasRole || isLoading || !screenshotAttachment) {
|
|
|
return null;
|
|
|
}
|
|
|
+
|
|
|
return (
|
|
|
<DataSection
|
|
|
title={t('Screenshots')}
|
|
|
description={t(
|
|
|
- 'Screenshots help identify what the user saw when the exception happened'
|
|
|
+ 'Screenshots help identify what the user saw when the event happened'
|
|
|
)}
|
|
|
>
|
|
|
- <StyledPanel>{renderContent()}</StyledPanel>
|
|
|
+ <StyledPanel>{renderContent(screenshotAttachment)}</StyledPanel>
|
|
|
</DataSection>
|
|
|
);
|
|
|
}}
|
|
@@ -161,14 +190,19 @@ const StyledPanel = styled(Panel)`
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
margin-bottom: 0;
|
|
|
- min-width: 175px;
|
|
|
min-height: 200px;
|
|
|
+ min-width: 175px;
|
|
|
`;
|
|
|
|
|
|
const StyledPanelBody = styled(PanelBody)`
|
|
|
height: 175px;
|
|
|
- width: 100%;
|
|
|
overflow: hidden;
|
|
|
+ border: 1px solid ${p => p.theme.border};
|
|
|
+ border-radius: ${p => p.theme.borderRadius};
|
|
|
+ margin: -1px;
|
|
|
+ width: calc(100% + 2px);
|
|
|
+ border-bottom-left-radius: 0;
|
|
|
+ border-bottom-right-radius: 0;
|
|
|
`;
|
|
|
|
|
|
const StyledPanelFooter = styled(PanelFooter)`
|
|
@@ -182,14 +216,3 @@ const StyledButtonbar = styled(ButtonBar)`
|
|
|
height: 24px;
|
|
|
}
|
|
|
`;
|
|
|
-
|
|
|
-const StyledImageViewer = styled(ImageViewer)`
|
|
|
- padding: 0;
|
|
|
- height: 100%;
|
|
|
- img {
|
|
|
- width: auto;
|
|
|
- height: 100%;
|
|
|
- object-fit: cover;
|
|
|
- flex: 1;
|
|
|
- }
|
|
|
-`;
|