123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- import {useState} from 'react';
- import LazyLoad from 'react-lazyload';
- import styled from '@emotion/styled';
- import {openModal} from 'sentry/actionCreators/modal';
- import MenuItemActionLink from 'sentry/components/actions/menuItemActionLink';
- import {Button} from 'sentry/components/button';
- import Card from 'sentry/components/card';
- import {DateTime} from 'sentry/components/dateTime';
- import DropdownLink from 'sentry/components/dropdownLink';
- import ImageVisualization from 'sentry/components/events/eventTagsAndScreenshot/screenshot/imageVisualization';
- import Modal, {
- modalCss,
- } from 'sentry/components/events/eventTagsAndScreenshot/screenshot/modal';
- import Link from 'sentry/components/links/link';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import PanelBody from 'sentry/components/panels/panelBody';
- import {IconEllipsis} from 'sentry/icons/iconEllipsis';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {IssueAttachment, Project} from 'sentry/types';
- import {trackAnalytics} from 'sentry/utils/analytics';
- import useOrganization from 'sentry/utils/useOrganization';
- type Props = {
- attachmentIndex: number;
- attachments: IssueAttachment[];
- eventAttachment: IssueAttachment;
- eventId: string;
- groupId: string;
- onDelete: (attachmentId: string) => void;
- projectSlug: Project['slug'];
- pageLinks?: string | null | undefined;
- };
- export function ScreenshotCard({
- eventAttachment,
- projectSlug,
- eventId,
- groupId,
- onDelete,
- pageLinks,
- attachmentIndex,
- attachments,
- }: Props) {
- const organization = useOrganization();
- const [loadingImage, setLoadingImage] = useState(true);
- const downloadUrl = `/api/0/projects/${organization.slug}/${projectSlug}/events/${eventId}/attachments/${eventAttachment.id}/?download=1`;
- function handleDelete() {
- trackAnalytics('issue_details.attachment_tab.screenshot_modal_deleted', {
- organization,
- });
- onDelete(eventAttachment.id);
- }
- function openVisualizationModal() {
- trackAnalytics('issue_details.attachment_tab.screenshot_modal_opened', {
- organization,
- });
- openModal(
- modalProps => (
- <Modal
- {...modalProps}
- orgSlug={organization.slug}
- projectSlug={projectSlug}
- eventAttachment={eventAttachment}
- downloadUrl={downloadUrl}
- onDelete={handleDelete}
- pageLinks={pageLinks}
- attachments={attachments}
- attachmentIndex={attachmentIndex}
- groupId={groupId}
- enablePagination
- onDownload={() =>
- trackAnalytics('issue_details.attachment_tab.screenshot_modal_download', {
- organization,
- })
- }
- />
- ),
- {modalCss}
- );
- }
- const baseEventsPath = `/organizations/${organization.slug}/issues/${groupId}/events/`;
- return (
- <Card>
- <CardHeader>
- <CardContent>
- <Title
- onClick={() =>
- trackAnalytics('issue_details.attachment_tab.screenshot_title_clicked', {
- organization,
- })
- }
- to={`${baseEventsPath}${eventId}/`}
- >
- {eventId}
- </Title>
- <Detail>
- <DateTime date={eventAttachment.dateCreated} />
- </Detail>
- </CardContent>
- </CardHeader>
- <CardBody>
- <StyledPanelBody
- onClick={() => openVisualizationModal()}
- data-test-id={`screenshot-${eventAttachment.id}`}
- >
- <LazyLoad>
- <StyledImageVisualization
- attachment={eventAttachment}
- orgId={organization.slug}
- projectSlug={projectSlug}
- eventId={eventId}
- onLoad={() => setLoadingImage(false)}
- onError={() => setLoadingImage(false)}
- />
- {loadingImage && (
- <StyledLoadingIndicator>
- <LoadingIndicator mini />
- </StyledLoadingIndicator>
- )}
- </LazyLoad>
- </StyledPanelBody>
- </CardBody>
- <CardFooter>
- <div>{eventAttachment.name}</div>
- <DropdownLink
- caret={false}
- customTitle={
- <Button
- aria-label={t('Actions')}
- size="xs"
- icon={<IconEllipsis direction="down" size="sm" />}
- borderless
- />
- }
- anchorRight
- >
- <MenuItemActionLink shouldConfirm={false} href={`${downloadUrl}`}>
- {t('Download')}
- </MenuItemActionLink>
- <MenuItemActionLink
- shouldConfirm
- confirmPriority="danger"
- confirmLabel={t('Delete')}
- onAction={() => onDelete(eventAttachment.id)}
- header={t('This image was captured around the time that the event occurred.')}
- message={t('Are you sure you wish to delete this image?')}
- >
- {t('Delete')}
- </MenuItemActionLink>
- </DropdownLink>
- </CardFooter>
- </Card>
- );
- }
- const Title = styled(Link)`
- ${p => p.theme.overflowEllipsis};
- font-weight: ${p => p.theme.fontWeightNormal};
- `;
- const Detail = styled('div')`
- font-family: ${p => p.theme.text.familyMono};
- font-size: ${p => p.theme.fontSizeSmall};
- color: ${p => p.theme.gray300};
- ${p => p.theme.overflowEllipsis};
- line-height: 1.5;
- `;
- const CardHeader = styled('div')`
- display: flex;
- padding: ${space(1.5)} ${space(2)};
- `;
- const CardBody = styled('div')`
- background: ${p => p.theme.gray100};
- padding: ${space(1.5)} ${space(2)};
- max-height: 250px;
- min-height: 250px;
- overflow: hidden;
- border-bottom: 1px solid ${p => p.theme.gray100};
- `;
- const CardFooter = styled('div')`
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: ${space(1)} ${space(2)};
- .dropdown {
- height: 24px;
- }
- `;
- const CardContent = styled('div')`
- flex-grow: 1;
- overflow: hidden;
- margin-right: ${space(1)};
- `;
- const StyledPanelBody = styled(PanelBody)`
- height: 100%;
- min-height: 48px;
- overflow: hidden;
- cursor: pointer;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- flex: 1;
- `;
- const StyledLoadingIndicator = styled('div')`
- position: absolute;
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
- `;
- const StyledImageVisualization = styled(ImageVisualization)`
- height: 100%;
- z-index: 1;
- border: 0;
- `;
|