rrweb extension implementation (#1044)
* feat: add rrweb web-extension package * refactor: make the extension suitable for manifest v3 * update tsconfig.json * use version_name rather than recorder_version in manifest.json * update manifest.json * enable to keep recording after changing tabs * enable to record between tabs and urls * fix CI error * try to fix CI error * feat: add pause and resume buttons * feat: add a link to new session after recording * improve session list * refactor: migrate session storage from chrome local storage to indexedDB * feat: add pagination to session list * fix: multiple recorders are started after pausing and resuming process * fix: can't stop recording on firefox browser * update type import of 'eventWithTime' * fix CI error * doc: add readme * Apply suggestions from Justin's code review Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com> * refactor: make use of webNavigation API to implement recording consistent during page navigation * fix firefox compatibility issue and add title to pages * add mouseleave listener to enhance the recording liability * fix firefox compatibility issue and improve the experience of recording resume after closing tabs * update tsconfig * upgrade vite-plugin-web-extension config to fix some bugs on facebook web page * update import links * refactor: cross tab recording mechanism apply Justin's suggestion * refactor: slipt util/index.ts into multiple files * implement cross-origin iframe recording * fix: regression of issue: ShadowHost can't be a string (issue 941) * refactor shadow dom recording to make tests cover key code * Apply formatting changes * increase the node memory limitation to avoid CI failure * Create lovely-pears-cross.md * Apply formatting changes * Update packages/web-extension/package.json * Update .changeset/lovely-pears-cross.md * update change logs * delete duplicated property --------- Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
This commit is contained in:
33
packages/web-extension/src/components/CircleButton.tsx
Normal file
33
packages/web-extension/src/components/CircleButton.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Button, ButtonProps } from '@chakra-ui/react';
|
||||
|
||||
interface CircleButtonProps extends ButtonProps {
|
||||
diameter: number;
|
||||
onClick?: () => void;
|
||||
children?: React.ReactNode;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function CircleButton({
|
||||
diameter,
|
||||
onClick,
|
||||
children,
|
||||
title,
|
||||
...rest
|
||||
}: CircleButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
w={`${diameter}rem`}
|
||||
h={`${diameter}rem`}
|
||||
padding={`${diameter / 2}rem`}
|
||||
borderRadius={9999}
|
||||
textAlign="center"
|
||||
bgColor="gray.100"
|
||||
boxSizing="content-box"
|
||||
onClick={onClick}
|
||||
title={title}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
290
packages/web-extension/src/components/SidebarWithHeader.tsx
Normal file
290
packages/web-extension/src/components/SidebarWithHeader.tsx
Normal file
@@ -0,0 +1,290 @@
|
||||
import { ReactNode } from 'react';
|
||||
import {
|
||||
IconButton,
|
||||
Box,
|
||||
CloseButton,
|
||||
Flex,
|
||||
HStack,
|
||||
Icon,
|
||||
Image,
|
||||
useColorModeValue,
|
||||
Link,
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
useDisclosure,
|
||||
BoxProps,
|
||||
FlexProps,
|
||||
Heading,
|
||||
Stack,
|
||||
Text,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from '@chakra-ui/react';
|
||||
import { FiChevronRight, FiMenu } from 'react-icons/fi';
|
||||
import type { IconType } from 'react-icons';
|
||||
import Browser from 'webextension-polyfill';
|
||||
|
||||
export interface SideBarItem {
|
||||
label: string;
|
||||
icon: IconType;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface HeadBarItem {
|
||||
label: string;
|
||||
subLabel?: string;
|
||||
children?: Array<HeadBarItem>;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export default function SidebarWithHeader({
|
||||
children,
|
||||
title,
|
||||
headBarItems,
|
||||
sideBarItems,
|
||||
}: {
|
||||
title?: string;
|
||||
sideBarItems: SideBarItem[];
|
||||
headBarItems: SideBarItem[];
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
return (
|
||||
<Box minH="100vh">
|
||||
<SidebarContent
|
||||
sideBarItems={sideBarItems}
|
||||
onClose={() => onClose}
|
||||
display={{ base: 'none', md: 'block' }}
|
||||
title={title}
|
||||
/>
|
||||
<Drawer
|
||||
autoFocus={false}
|
||||
isOpen={isOpen}
|
||||
placement="left"
|
||||
onClose={onClose}
|
||||
returnFocusOnClose={false}
|
||||
onOverlayClick={onClose}
|
||||
size="full"
|
||||
>
|
||||
<DrawerContent>
|
||||
<SidebarContent
|
||||
onClose={onClose}
|
||||
sideBarItems={sideBarItems}
|
||||
title={title}
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<MobileNav onOpen={onOpen}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
display={{ base: 'none', md: 'flex' }}
|
||||
ml={10}
|
||||
>
|
||||
<DesktopNav headBarItems={headBarItems} />
|
||||
</Flex>
|
||||
</MobileNav>
|
||||
<Box ml={{ base: 0, md: 60 }}>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
interface SidebarProps extends BoxProps {
|
||||
onClose: () => void;
|
||||
title?: string;
|
||||
sideBarItems: SideBarItem[];
|
||||
}
|
||||
|
||||
const SidebarContent = ({
|
||||
onClose,
|
||||
sideBarItems,
|
||||
title,
|
||||
...rest
|
||||
}: SidebarProps) => {
|
||||
return (
|
||||
<Box
|
||||
transition="3s ease"
|
||||
bg={useColorModeValue('white', 'gray.900')}
|
||||
borderRight="1px"
|
||||
borderRightColor={useColorModeValue('gray.200', 'gray.700')}
|
||||
w={{ base: 'full', md: 60 }}
|
||||
pos="fixed"
|
||||
h="full"
|
||||
{...rest}
|
||||
>
|
||||
<Flex h="20" alignItems="center" mx="8" justify="flex-start" gap="3">
|
||||
<Link href="https://github.com/rrweb-io/rrweb" target="_blank">
|
||||
<Image
|
||||
borderRadius="md"
|
||||
boxSize="2rem"
|
||||
src={Browser.runtime.getURL('assets/icon128.png')}
|
||||
alt="RRWeb Logo"
|
||||
/>
|
||||
</Link>
|
||||
{title && (
|
||||
<Heading as="h4" size="md">
|
||||
{title}
|
||||
</Heading>
|
||||
)}
|
||||
<CloseButton display={{ base: 'flex', md: 'none' }} onClick={onClose} />
|
||||
</Flex>
|
||||
{sideBarItems.map((link) => (
|
||||
<NavItem key={link.label} icon={link.icon} href={link.href}>
|
||||
{link.label}
|
||||
</NavItem>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface NavItemProps extends FlexProps {
|
||||
icon: IconType;
|
||||
href: string;
|
||||
children: string;
|
||||
}
|
||||
const NavItem = ({ icon, href, children, ...rest }: NavItemProps) => {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
style={{ textDecoration: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
fontSize="lg"
|
||||
fontWeight={500}
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
p="4"
|
||||
mx="4"
|
||||
borderRadius="lg"
|
||||
role="group"
|
||||
cursor="pointer"
|
||||
_hover={{
|
||||
bg: 'gray.200',
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<>
|
||||
{icon && <Icon mr="4" fontSize="16" as={icon} />}
|
||||
{children}
|
||||
</>
|
||||
</Flex>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
interface MobileProps extends FlexProps {
|
||||
onOpen: () => void;
|
||||
}
|
||||
const MobileNav = ({ onOpen, ...rest }: MobileProps) => {
|
||||
return (
|
||||
<Flex
|
||||
ml={{ base: 0, md: 60 }}
|
||||
px={{ base: 4, md: 4 }}
|
||||
height="20"
|
||||
alignItems="center"
|
||||
bg={useColorModeValue('white', 'gray.900')}
|
||||
borderBottomWidth="1px"
|
||||
borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
|
||||
justifyContent={{ base: 'space-between', md: 'flex-start' }}
|
||||
{...rest}
|
||||
>
|
||||
<IconButton
|
||||
display={{ base: 'flex', md: 'none' }}
|
||||
onClick={onOpen}
|
||||
variant="outline"
|
||||
aria-label="open menu"
|
||||
icon={<FiMenu />}
|
||||
/>
|
||||
|
||||
<HStack spacing={{ base: '0', md: '6' }}>
|
||||
{rest.children && rest.children}
|
||||
</HStack>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const DesktopNav = ({ headBarItems }: { headBarItems: HeadBarItem[] }) => {
|
||||
const linkColor = useColorModeValue('gray.600', 'gray.200');
|
||||
const linkHoverColor = useColorModeValue('gray.800', 'white');
|
||||
const popoverContentBgColor = useColorModeValue('white', 'gray.800');
|
||||
|
||||
return (
|
||||
<Stack direction={'row'} spacing={4}>
|
||||
{headBarItems.map((navItem) => (
|
||||
<Box key={navItem.label}>
|
||||
<Popover trigger={'hover'} placement={'bottom-start'}>
|
||||
<PopoverTrigger>
|
||||
<Link
|
||||
p={2}
|
||||
href={navItem.href ?? '#'}
|
||||
fontSize={'sm'}
|
||||
fontWeight={500}
|
||||
color={linkColor}
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
color: linkHoverColor,
|
||||
}}
|
||||
>
|
||||
{navItem.label}
|
||||
</Link>
|
||||
</PopoverTrigger>
|
||||
|
||||
{navItem.children && (
|
||||
<PopoverContent
|
||||
border={0}
|
||||
boxShadow={'xl'}
|
||||
bg={popoverContentBgColor}
|
||||
p={4}
|
||||
rounded={'xl'}
|
||||
minW={'sm'}
|
||||
>
|
||||
<Stack>
|
||||
{navItem.children.map((child) => (
|
||||
<DesktopSubNav key={child.label} {...child} />
|
||||
))}
|
||||
</Stack>
|
||||
</PopoverContent>
|
||||
)}
|
||||
</Popover>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const DesktopSubNav = ({ label, href, subLabel }: HeadBarItem) => {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
role={'group'}
|
||||
display={'block'}
|
||||
p={2}
|
||||
rounded={'md'}
|
||||
_hover={{ bg: useColorModeValue('pink.50', 'gray.900') }}
|
||||
>
|
||||
<Stack direction={'row'} align={'center'}>
|
||||
<Box>
|
||||
<Text
|
||||
transition={'all .3s ease'}
|
||||
_groupHover={{ color: 'pink.400' }}
|
||||
fontWeight={500}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fontSize={'sm'}>{subLabel}</Text>
|
||||
</Box>
|
||||
<Flex
|
||||
transition={'all .3s ease'}
|
||||
transform={'translateX(-10px)'}
|
||||
opacity={0}
|
||||
_groupHover={{ opacity: '100%', transform: 'translateX(0)' }}
|
||||
justify={'flex-end'}
|
||||
align={'center'}
|
||||
flex={1}
|
||||
>
|
||||
<Icon color={'pink.400'} w={5} h={5} as={FiChevronRight} />
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user