11import { Fragment , useEffect , useState } from 'react' ;
22import styled from '@emotion/styled' ;
33
4+ import { openModal } from 'app/actionCreators/modal' ;
45import { Client } from 'app/api' ;
56import Role from 'app/components/acl/role' ;
67import MenuItemActionLink from 'app/components/actions/menuItemActionLink' ;
78import Button from 'app/components/button' ;
89import ButtonBar from 'app/components/buttonBar' ;
910import DropdownLink from 'app/components/dropdownLink' ;
10- import ImageViewer from 'app/components/events/attachmentViewers/imageViewer' ;
11- import LoadingIndicator from 'app/components/loadingIndicator' ;
1211import { Panel , PanelBody , PanelFooter } from 'app/components/panels' ;
13- import { IconDownload , IconEllipsis } from 'app/icons' ;
12+ import { IconEllipsis } from 'app/icons' ;
1413import { t } from 'app/locale' ;
1514import space from 'app/styles/space' ;
1615import { EventAttachment , Organization , Project } from 'app/types' ;
@@ -19,8 +18,8 @@ import withApi from 'app/utils/withApi';
1918
2019import DataSection from '../dataSection' ;
2120
22- import EmptyState from './emptyState ' ;
23- import { platformsMobileWithAttachmentsFeature } from './utils ' ;
21+ import ImageVisualization from './imageVisualization ' ;
22+ import Modal , { modalCss } from './modal ' ;
2423
2524type Props = {
2625 event : Event ;
@@ -33,7 +32,6 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
3332 const [ attachments , setAttachments ] = useState < EventAttachment [ ] > ( [ ] ) ;
3433 const [ isLoading , setIsLoading ] = useState ( false ) ;
3534 const orgSlug = organization . slug ;
36- const eventPlatform = event . platform ;
3735
3836 useEffect ( ( ) => {
3937 fetchData ( ) ;
@@ -59,43 +57,71 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
5957 }
6058 }
6159
62- function hasPreview ( attachment : EventAttachment ) {
63- switch ( attachment . mimetype ) {
64- case 'image/jpeg' :
65- case 'image/png' :
66- case 'image/gif' :
67- return true ;
68- default :
69- return false ;
70- }
60+ function hasScreenshot ( attachment : EventAttachment ) {
61+ const { mimetype} = attachment ;
62+ return mimetype === 'image/jpeg' || mimetype === 'image/png' ;
7163 }
7264
73- function renderContent ( ) {
74- if ( isLoading ) {
75- return < LoadingIndicator mini /> ;
76- }
77-
78- const firstAttachmenteWithPreview = attachments . find ( hasPreview ) ;
65+ async function handleDelete ( screenshotAttachmentId : string , downloadUrl : string ) {
66+ try {
67+ await api . requestPromise ( downloadUrl . split ( '/api/0' ) [ 1 ] , {
68+ method : 'DELETE' ,
69+ } ) ;
7970
80- if ( ! firstAttachmenteWithPreview ) {
81- return < EmptyState platform = { eventPlatform } /> ;
71+ setAttachments (
72+ attachments . filter ( attachment => attachment . id !== screenshotAttachmentId )
73+ ) ;
74+ } catch ( _err ) {
75+ // TODO: Error-handling
8276 }
77+ }
8378
84- const downloadUrl = `/api/0/projects/${ organization . slug } /${ projectSlug } /events/${ event . id } /attachments/${ firstAttachmenteWithPreview . id } /` ;
79+ function handleOpenVisualizationModal (
80+ eventAttachment : EventAttachment ,
81+ downloadUrl : string
82+ ) {
83+ openModal (
84+ modalProps => (
85+ < Modal
86+ { ...modalProps }
87+ event = { event }
88+ orgSlug = { orgSlug }
89+ projectSlug = { projectSlug }
90+ eventAttachment = { eventAttachment }
91+ downloadUrl = { downloadUrl }
92+ onDelete = { ( ) => handleDelete ( eventAttachment . id , downloadUrl ) }
93+ />
94+ ) ,
95+ { modalCss}
96+ ) ;
97+ }
98+
99+ function renderContent ( screenshotAttachment : EventAttachment ) {
100+ const downloadUrl = `/api/0/projects/${ organization . slug } /${ projectSlug } /events/${ event . id } /attachments/${ screenshotAttachment . id } /` ;
85101
86102 return (
87103 < Fragment >
88104 < StyledPanelBody >
89- < StyledImageViewer
90- attachment = { firstAttachmenteWithPreview }
105+ < ImageVisualization
106+ attachment = { screenshotAttachment }
91107 orgId = { orgSlug }
92108 projectId = { projectSlug }
93109 event = { event }
94110 />
95111 </ StyledPanelBody >
96112 < StyledPanelFooter >
97113 < StyledButtonbar gap = { 1 } >
98- < Button size = "xsmall" > { t ( 'View screenshot' ) } </ Button >
114+ < Button
115+ size = "xsmall"
116+ onClick = { ( ) =>
117+ handleOpenVisualizationModal (
118+ screenshotAttachment ,
119+ `${ downloadUrl } ?download=1`
120+ )
121+ }
122+ >
123+ { t ( 'View screenshot' ) }
124+ </ Button >
99125 < DropdownLink
100126 caret = { false }
101127 customTitle = {
@@ -109,43 +135,46 @@ function Screenshot({event, api, organization, projectSlug}: Props) {
109135 >
110136 < MenuItemActionLink
111137 shouldConfirm = { false }
112- icon = { < IconDownload size = "xs" /> }
113138 title = { t ( 'Download' ) }
114139 href = { `${ downloadUrl } ?download=1` }
115140 >
116141 { t ( 'Download' ) }
117142 </ MenuItemActionLink >
143+ < MenuItemActionLink
144+ shouldConfirm
145+ title = { t ( 'Delete' ) }
146+ onAction = { ( ) => handleDelete ( screenshotAttachment . id , downloadUrl ) }
147+ header = { t (
148+ 'Screenshots help identify what the user saw when the event happened'
149+ ) }
150+ message = { t ( 'Are you sure you wish to delete this screenshot?' ) }
151+ >
152+ { t ( 'Delete' ) }
153+ </ MenuItemActionLink >
118154 </ DropdownLink >
119155 </ StyledButtonbar >
120156 </ StyledPanelFooter >
121157 </ Fragment >
122158 ) ;
123159 }
124160
125- // the UI should only render the screenshots feature in events with platforms that support screenshots
126- if (
127- ! eventPlatform ||
128- ! platformsMobileWithAttachmentsFeature . includes ( eventPlatform as any )
129- ) {
130- return null ;
131- }
132-
133161 return (
134162 < Role role = { organization . attachmentsRole } >
135163 { ( { hasRole} ) => {
136- if ( ! hasRole ) {
137- // if the user has no access to the attachments,
138- // the UI shall not display the screenshot section
164+ const screenshotAttachment = attachments . find ( hasScreenshot ) ;
165+
166+ if ( ! hasRole || isLoading || ! screenshotAttachment ) {
139167 return null ;
140168 }
169+
141170 return (
142171 < DataSection
143172 title = { t ( 'Screenshots' ) }
144173 description = { t (
145- 'Screenshots help identify what the user saw when the exception happened'
174+ 'Screenshots help identify what the user saw when the event happened'
146175 ) }
147176 >
148- < StyledPanel > { renderContent ( ) } </ StyledPanel >
177+ < StyledPanel > { renderContent ( screenshotAttachment ) } </ StyledPanel >
149178 </ DataSection >
150179 ) ;
151180 } }
@@ -161,14 +190,19 @@ const StyledPanel = styled(Panel)`
161190 justify-content: center;
162191 align-items: center;
163192 margin-bottom: 0;
164- min-width: 175px;
165193 min-height: 200px;
194+ min-width: 175px;
166195` ;
167196
168197const StyledPanelBody = styled ( PanelBody ) `
169198 height: 175px;
170- width: 100%;
171199 overflow: hidden;
200+ border: 1px solid ${ p => p . theme . border } ;
201+ border-radius: ${ p => p . theme . borderRadius } ;
202+ margin: -1px;
203+ width: calc(100% + 2px);
204+ border-bottom-left-radius: 0;
205+ border-bottom-right-radius: 0;
172206` ;
173207
174208const StyledPanelFooter = styled ( PanelFooter ) `
@@ -182,14 +216,3 @@ const StyledButtonbar = styled(ButtonBar)`
182216 height: 24px;
183217 }
184218` ;
185-
186- const StyledImageViewer = styled ( ImageViewer ) `
187- padding: 0;
188- height: 100%;
189- img {
190- width: auto;
191- height: 100%;
192- object-fit: cover;
193- flex: 1;
194- }
195- ` ;
0 commit comments