* start moving ui to redux tlk * remove unused reducer * changes for gdpr and site types * ui: migrating duck/roles to mobx * ui: drop unreferenced types * ui: drop unreferenced types * ui: move player slice reducer to mobx family * ui: move assignments to issueReportingStore.ts * remove issues store * some fixes after issues store * remove errors reducer, drop old components * finish removing errors reducer * start moving integrations state to mobx * change(ui): funnel duck cleanup * change(ui): custom fields * change(ui): customMetrics cleanup * change(ui): customMetrics cleanup * change(ui): duck/filters minor cleanup * change(ui): duck/filters cleanup * change(ui): duck/customMetrics cleanup and upgrades * fix integrations service, fix babel config to >.25 + not ie * refactoring integrations reducers etc WIP * finish removing integrations state * some fixes for integrated check * start of projects refactoring * move api and "few" files to new project store * new batch for site -> projects * fix setid context * move all critical components, drop site duck * remove all duck/site refs, remove old components * fixup for SessionTags.tsx, remove duck/sources (?) * move session store * init sessionstore outside of context * fix userfilter * replace simple actions for session store * sessions sotre * Rtm temp (#2597) * change(ui): duck/search wip * change(ui): duck/search wip * change(ui): duck/search wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): duck/searchLive wip * change(ui): search states * change(ui): search states * change(ui): search states * change(ui): fix savedSearch store * change(ui): fix savedSearch store * some fixes for session connector * change(ui): fix savedSearch store * change(ui): fix searchLive * change(ui): fix searchLive * fixes for session replay * change(ui): bookmark fetch * last components for sessions * add fetchautoplaylist * finish session reducer, remove deleted reducers * change(ui): fix the search fetch * change(ui): fix the search fetch * fix integrations call ctx * ensure ctx for sessionstore * fix(ui): checking for latest sessions path * start removing user reducer * removing user reducer pt2... * finish user store * remove rand log * fix crashes * tinkering workflow file for tracker test * making sure prefetched sessions work properly * fix conflict * fix router redirects during loading --------- Co-authored-by: Shekar Siri <sshekarsiri@gmail.com>
256 lines
6.8 KiB
TypeScript
256 lines
6.8 KiB
TypeScript
import {
|
|
ArrowLeftOutlined,
|
|
CommentOutlined,
|
|
CopyOutlined,
|
|
DeleteOutlined,
|
|
DownloadOutlined,
|
|
MoreOutlined,
|
|
SettingOutlined,
|
|
UserSwitchOutlined,
|
|
} from '@ant-design/icons';
|
|
import {
|
|
Badge,
|
|
Button,
|
|
Dropdown,
|
|
MenuProps,
|
|
Popover,
|
|
Tooltip,
|
|
message,
|
|
} from 'antd';
|
|
import copy from 'copy-to-clipboard';
|
|
import { observer } from 'mobx-react-lite';
|
|
import React, { useState } from 'react';
|
|
import { useHistory } from 'react-router-dom';
|
|
|
|
import Tabs from 'App/components/Session/Tabs';
|
|
import { useStore } from 'App/mstore';
|
|
import { spotsList } from 'App/routes';
|
|
import { hashString } from 'App/types/session/session';
|
|
import { Avatar, Icon } from 'UI';
|
|
|
|
import { TABS, Tab } from '../consts';
|
|
import AccessModal from './AccessModal';
|
|
|
|
const spotLink = spotsList();
|
|
|
|
function SpotPlayerHeader({
|
|
activeTab,
|
|
setActiveTab,
|
|
title,
|
|
user,
|
|
date,
|
|
browserVersion,
|
|
resolution,
|
|
platform,
|
|
}: {
|
|
activeTab: Tab | null;
|
|
setActiveTab: (tab: Tab) => void;
|
|
title: string;
|
|
user: string;
|
|
date: string;
|
|
browserVersion: string | null;
|
|
resolution: string | null;
|
|
platform: string | null;
|
|
}) {
|
|
const { spotStore, userStore } = useStore();
|
|
const isLoggedIn = !!userStore.jwt;
|
|
const hasShareAccess = userStore.isEnterprise ? userStore.account.permissions.includes('SPOT_PUBLIC') : true;
|
|
const comments = spotStore.currentSpot?.comments ?? [];
|
|
|
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
const history = useHistory();
|
|
|
|
const onCopy = () => {
|
|
copy(window.location.href);
|
|
message.success('Internal sharing link copied to clipboard');
|
|
};
|
|
|
|
const navigateToSpotsList = () => {
|
|
history.push(spotLink);
|
|
};
|
|
|
|
const items: MenuProps['items'] = [
|
|
{
|
|
key: '1',
|
|
icon: <DownloadOutlined />,
|
|
label: 'Download Video',
|
|
},
|
|
{
|
|
key: '2',
|
|
icon: <DeleteOutlined />,
|
|
label: 'Delete',
|
|
},
|
|
];
|
|
|
|
const onMenuClick = async ({ key }: { key: string }) => {
|
|
if (key === '1') {
|
|
const { url } = await spotStore.getVideo(spotStore.currentSpot!.spotId);
|
|
await downloadFile(url, `${spotStore.currentSpot!.title}.webm`);
|
|
} else if (key === '2') {
|
|
spotStore.deleteSpot([spotStore.currentSpot!.spotId]).then(() => {
|
|
history.push(spotsList());
|
|
message.success('Spot successfully deleted');
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={'flex items-center gap-1 p-2 py-1 w-full bg-white border-b'}
|
|
>
|
|
<div>
|
|
{isLoggedIn ? (
|
|
<Button
|
|
type="text"
|
|
onClick={navigateToSpotsList}
|
|
icon={<ArrowLeftOutlined />}
|
|
className="px-2"
|
|
>
|
|
All Spots
|
|
</Button>
|
|
) : (
|
|
<>
|
|
<a href="https://openreplay.com/spot" target="_blank">
|
|
<Button
|
|
type="text"
|
|
className="orSpotBranding flex gap-1 items-center py-2"
|
|
>
|
|
<Icon name={'orSpot'} size={28} />
|
|
<div className="flex flex-col justify-start text-start">
|
|
<div className={'text-lg font-semibold'}>Spot</div>
|
|
<div className={'text-disabled-text text-xs -mt-1'}>
|
|
by OpenReplay
|
|
</div>
|
|
</div>
|
|
</Button>
|
|
</a>
|
|
</>
|
|
)}
|
|
</div>
|
|
<div className={'h-full rounded-xl border-l mr-2'} style={{ width: 1 }} />
|
|
<div className={'flex items-center gap-2'}>
|
|
<Avatar seed={hashString(user)} />
|
|
<div>
|
|
<Tooltip title={title}>
|
|
<div className="w-9/12 text-ellipsis truncate cursor-normal">
|
|
{title}
|
|
</div>
|
|
</Tooltip>
|
|
<div className={'flex items-center gap-2 text-black/50 text-sm'}>
|
|
<div>{user}</div>
|
|
<div>·</div>
|
|
<div className="capitalize">{date}</div>
|
|
{browserVersion && (
|
|
<>
|
|
<div>·</div>
|
|
<div className="capitalize">Chrome v{browserVersion}</div>
|
|
</>
|
|
)}
|
|
{resolution && (
|
|
<>
|
|
<div>·</div>
|
|
<div>{resolution}</div>
|
|
</>
|
|
)}
|
|
{platform && (
|
|
<>
|
|
<div>·</div>
|
|
<div className="capitalize">{platform}</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className={'ml-auto'} />
|
|
{isLoggedIn ? (
|
|
<>
|
|
<Button
|
|
size={'small'}
|
|
onClick={onCopy}
|
|
type={'default'}
|
|
icon={<CopyOutlined />}
|
|
>
|
|
Copy
|
|
</Button>
|
|
{hasShareAccess ? (
|
|
<Popover trigger={'click'} content={<AccessModal />}>
|
|
<Button
|
|
size={'small'}
|
|
icon={<SettingOutlined />}
|
|
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
>
|
|
Manage Access
|
|
</Button>
|
|
</Popover>
|
|
) : null}
|
|
<Dropdown
|
|
menu={{ items, onClick: onMenuClick }}
|
|
placement="bottomRight"
|
|
>
|
|
<Button icon={<MoreOutlined />} size={'small'}></Button>
|
|
</Dropdown>
|
|
<div
|
|
className={'h-full rounded-xl border-l mx-2'}
|
|
style={{ width: 1 }}
|
|
/>
|
|
</>
|
|
) : null}
|
|
<Tabs
|
|
className={'!w-fit !border-b-0'}
|
|
tabs={[
|
|
{
|
|
key: TABS.ACTIVITY,
|
|
text: 'Activity',
|
|
iconComp: <div className={'mr-1'}><UserSwitchOutlined /></div>,
|
|
},
|
|
{
|
|
key: TABS.COMMENTS,
|
|
iconComp: <div className={'mr-1'}><CommentOutlined /></div>,
|
|
text: (
|
|
<div>
|
|
Comments{' '}
|
|
{comments.length > 0 && (
|
|
<Badge
|
|
count={comments.length}
|
|
className="mr-2"
|
|
style={{ fontSize: '10px' }}
|
|
size="small"
|
|
color="#454545"
|
|
/>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
]}
|
|
active={activeTab}
|
|
onClick={(k) =>
|
|
k === activeTab ? setActiveTab(null) : setActiveTab(k)
|
|
}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
async function downloadFile(url: string, fileName: string) {
|
|
try {
|
|
const response = await fetch(url);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const blobUrl = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = blobUrl;
|
|
a.download = fileName;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(blobUrl);
|
|
} catch (error) {
|
|
console.error('Error downloading file:', error);
|
|
}
|
|
}
|
|
|
|
export default observer(SpotPlayerHeader);
|