change(ui) - player improvements (#1164)
* change(ui) - player - back button spacing * change(ui) - onboarding - changes * change(ui) - onboarding - changes * change(ui) - integrations gap-4 * change(ui) - install script copy button styles * change(ui) - copy button in account settings * fix(ui) - error details modal loader position * change(ui) - share popup styles * change(ui) - player improvements * change(ui) - player improvements - playback speed with menu * change(ui) - player improvements - current timezone * change(ui) - player improvements - autoplay options
This commit is contained in:
parent
c4cc3ed234
commit
d0bcae82f2
34 changed files with 400 additions and 330 deletions
|
|
@ -87,7 +87,7 @@ function Integrations(props: Props) {
|
|||
<div className="mb-4 p-5">
|
||||
{!hideHeader && <PageTitle title={<div>Integrations</div>} />}
|
||||
{integrations.map((cat: any) => (
|
||||
<div className="grid grid-cols-6 border-b last:border-none">
|
||||
<div className="grid grid-cols-6 border-b last:border-none gap-4">
|
||||
<div
|
||||
className={cn('col-span-4 mb-2 py-3', cat.docs ? 'col-span-4' : 'col-span-6')}
|
||||
key={cat.key}
|
||||
|
|
@ -192,7 +192,7 @@ const integrations = [
|
|||
'Sync your backend errors with sessions replays and see what happened front-to-back.',
|
||||
docs: () => (
|
||||
<DocCard
|
||||
title="Why use integrations"
|
||||
title="Why use integrations?"
|
||||
icon="question-lg"
|
||||
iconBgColor="bg-red-lightest"
|
||||
iconColor="red"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import copy from 'copy-to-clipboard';
|
||||
import { connect } from 'react-redux';
|
||||
import styles from './profileSettings.module.css';
|
||||
import { Form, Input, Button } from 'UI';
|
||||
import { Form, Input, Button, CopyButton } from 'UI';
|
||||
|
||||
@connect(state => ({
|
||||
apiKey: state.getIn([ 'user', 'account', 'apiKey' ]),
|
||||
|
|
@ -36,14 +36,7 @@ export default class Api extends React.PureComponent {
|
|||
readOnly={ true }
|
||||
value={ apiKey }
|
||||
leadingButton={
|
||||
<Button
|
||||
type="button"
|
||||
variant="text-primary"
|
||||
role="button"
|
||||
onClick={ this.copyHandler }
|
||||
>
|
||||
{ copied ? 'copied' : 'copy' }
|
||||
</Button>
|
||||
<CopyButton content={ apiKey } />
|
||||
}
|
||||
/>
|
||||
</Form.Field>
|
||||
|
|
|
|||
|
|
@ -11,52 +11,56 @@ import AddUserButton from './components/AddUserButton';
|
|||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
interface Props {
|
||||
isOnboarding?: boolean;
|
||||
account: any;
|
||||
isEnterprise: boolean;
|
||||
isOnboarding?: boolean;
|
||||
account: any;
|
||||
isEnterprise: boolean;
|
||||
}
|
||||
function UsersView(props: Props) {
|
||||
const { account, isEnterprise, isOnboarding = false } = props;
|
||||
const { userStore, roleStore } = useStore();
|
||||
const userCount = useObserver(() => userStore.list.length);
|
||||
const roles = useObserver(() => roleStore.list);
|
||||
const { showModal } = useModal();
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
const { account, isEnterprise, isOnboarding = false } = props;
|
||||
const { userStore, roleStore } = useStore();
|
||||
const userCount = useObserver(() => userStore.list.length);
|
||||
const roles = useObserver(() => roleStore.list);
|
||||
const { showModal } = useModal();
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
|
||||
const editHandler = (user: any = null) => {
|
||||
userStore.initUser(user).then(() => {
|
||||
showModal(<UserForm />, { right: true });
|
||||
});
|
||||
};
|
||||
const editHandler = (user: any = null) => {
|
||||
userStore.initUser(user).then(() => {
|
||||
showModal(<UserForm />, { right: true });
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (roles.length === 0 && isEnterprise) {
|
||||
roleStore.fetchRoles();
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (roles.length === 0 && isEnterprise) {
|
||||
roleStore.fetchRoles();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between px-5 pt-5">
|
||||
<PageTitle
|
||||
title={
|
||||
<div>
|
||||
Team <span className="color-gray-medium">{userCount}</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<AddUserButton isAdmin={isAdmin} onClick={() => editHandler(null)} />
|
||||
<div className="mx-2" />
|
||||
<UserSearch />
|
||||
</div>
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between px-5 pt-5">
|
||||
<PageTitle
|
||||
title={
|
||||
<div>
|
||||
Team <span className="color-gray-medium">{userCount}</span>
|
||||
</div>
|
||||
<UserList isEnterprise={isEnterprise} isOnboarding={isOnboarding} />
|
||||
}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<AddUserButton
|
||||
btnVariant={isOnboarding ? 'outline' : 'primary'}
|
||||
isAdmin={isAdmin}
|
||||
onClick={() => editHandler(null)}
|
||||
/>
|
||||
<div className="mx-2" />
|
||||
{!isOnboarding && <UserSearch />}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
<UserList isEnterprise={isEnterprise} isOnboarding={isOnboarding} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect((state: any) => ({
|
||||
account: state.getIn(['user', 'account']),
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
account: state.getIn(['user', 'account']),
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
}))(withPageTitle('Team - OpenReplay Preferences')(UsersView));
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useObserver } from 'mobx-react-lite';
|
|||
const PERMISSION_WARNING = 'You don’t have the permissions to perform this action.';
|
||||
const LIMIT_WARNING = 'You have reached users limit.';
|
||||
|
||||
function AddUserButton({ isAdmin = false, onClick }: any) {
|
||||
function AddUserButton({ isAdmin = false, onClick, btnVariant = 'primary' }: any) {
|
||||
const { userStore } = useStore();
|
||||
const limtis = useObserver(() => userStore.limits);
|
||||
const cannAddUser = useObserver(
|
||||
|
|
@ -17,7 +17,7 @@ function AddUserButton({ isAdmin = false, onClick }: any) {
|
|||
title={`${!isAdmin ? PERMISSION_WARNING : !cannAddUser ? LIMIT_WARNING : 'Add team member'}`}
|
||||
disabled={isAdmin || cannAddUser}
|
||||
>
|
||||
<Button disabled={!cannAddUser || !isAdmin} variant="primary" onClick={onClick}>
|
||||
<Button disabled={!cannAddUser || !isAdmin} variant={btnVariant} onClick={onClick}>
|
||||
Add Team Member
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -21,20 +21,17 @@ function UserList(props: Props) {
|
|||
const searchQuery = useObserver(() => userStore.searchQuery);
|
||||
const { showModal } = useModal();
|
||||
|
||||
// const filterList = (list) => {
|
||||
// const filterRE = getRE(searchQuery, 'i');
|
||||
// let _list = list.filter((w) => {
|
||||
// return filterRE.test(w.email) || filterRE.test(w.roleName);
|
||||
// });
|
||||
// return _list;
|
||||
// };
|
||||
const getList = (list) => filterList(list, searchQuery, ['email', 'roleName', 'name'])
|
||||
const getList = (list: any) => filterList(list, searchQuery, ['email', 'roleName', 'name'])
|
||||
|
||||
const list: any = searchQuery !== '' ? getList(users) : users;
|
||||
const length = list.length;
|
||||
|
||||
useEffect(() => {
|
||||
userStore.fetchUsers();
|
||||
|
||||
return () => {
|
||||
userStore.updateKey('page', 1)
|
||||
}
|
||||
}, []);
|
||||
|
||||
const editHandler = (user: any) => {
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ export default class ErrorInfo extends React.PureComponent {
|
|||
subtext="Please try to find existing one."
|
||||
show={!loading && errorIdInStore == null}
|
||||
>
|
||||
<div className="flex">
|
||||
<Loader loading={loading} className="w-9/12">
|
||||
<div className="flex w-full">
|
||||
<Loader loading={loading} className="w-full">
|
||||
<MainSection className="w-9/12" />
|
||||
<SideSection className="w-3/12" />
|
||||
</Loader>
|
||||
|
|
|
|||
|
|
@ -5,15 +5,24 @@ import { HighlightCode, Icon, Button } from 'UI';
|
|||
import DocCard from 'Shared/DocCard/DocCard';
|
||||
import withOnboarding, { WithOnboardingProps } from '../withOnboarding';
|
||||
import { OB_TABS } from 'App/routes';
|
||||
import withPageTitle from 'App/components/hocs/withPageTitle';
|
||||
|
||||
interface Props extends WithOnboardingProps {}
|
||||
|
||||
function IdentifyUsersTab(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<h1 className="flex items-center px-4 py-3 border-b text-2xl">
|
||||
<span>🕵️♂️</span>
|
||||
<div className="ml-3">Identify Users</div>
|
||||
<h1 className="flex items-center px-4 py-3 border-b justify-between">
|
||||
<div className="flex items-center text-2xl">
|
||||
<span>🕵️♂️</span>
|
||||
<div className="ml-3">Identify Users</div>
|
||||
</div>
|
||||
|
||||
<a href="https://docs.openreplay.com/en/v1.10.0/installation/identify-user/" target="_blank">
|
||||
<Button variant="text-primary" icon="question-circle" className="ml-2">
|
||||
See Documentation
|
||||
</Button>
|
||||
</a>
|
||||
</h1>
|
||||
<div className="grid grid-cols-6 gap-4 w-full p-4">
|
||||
<div className="col-span-4">
|
||||
|
|
@ -31,9 +40,24 @@ function IdentifyUsersTab(props: Props) {
|
|||
</div>
|
||||
|
||||
<HighlightCode className="js" text={`tracker.setUserID('john@doe.com');`} />
|
||||
<div className="border-t my-8" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<DocCard
|
||||
title="Why to identify users?"
|
||||
icon="question-lg"
|
||||
iconBgColor="bg-red-lightest"
|
||||
iconColor="red"
|
||||
>
|
||||
Make it easy to search and filter replays by user id. OpenReplay allows you to associate
|
||||
your internal-user-id with the recording.
|
||||
</DocCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-8" />
|
||||
<div className="border-t my-6" />
|
||||
|
||||
<div className="grid grid-cols-6 gap-4 w-full p-4">
|
||||
<div className="col-span-4">
|
||||
<div>
|
||||
<div className="font-medium mb-2 text-lg">Identify users by adding metadata</div>
|
||||
<p>
|
||||
|
|
@ -48,7 +72,7 @@ function IdentifyUsersTab(props: Props) {
|
|||
<div className="my-6" />
|
||||
<div className="flex items-start">
|
||||
<CircleNumber text="2" />
|
||||
<div className="pt-1">
|
||||
<div className="pt-1 w-full">
|
||||
<span className="font-bold">Inject metadata when recording sessions</span>
|
||||
<div className="my-2">
|
||||
Use the <span className="highlight-blue">setMetadata</span> method in your code to
|
||||
|
|
@ -59,18 +83,7 @@ function IdentifyUsersTab(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<DocCard
|
||||
title="Why to identify users?"
|
||||
icon="question-lg"
|
||||
iconBgColor="bg-red-lightest"
|
||||
iconColor="red"
|
||||
>
|
||||
Make it easy to search and filter replays by user id. OpenReplay allows you to associate
|
||||
your internal-user-id with the recording.
|
||||
</DocCard>
|
||||
|
||||
<DocCard title="What is Metadata?" icon="lightbulb">
|
||||
Additional information about users can be provided with metadata (also known as traits
|
||||
or user variables). They take the form of key/value pairs, and are useful for filtering
|
||||
|
|
@ -96,4 +109,4 @@ function IdentifyUsersTab(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default withOnboarding(IdentifyUsersTab);
|
||||
export default withOnboarding(withPageTitle("Identify Users - OpenReplay")(IdentifyUsersTab));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Button, Icon } from 'UI';
|
|||
import withOnboarding from '../withOnboarding';
|
||||
import { WithOnboardingProps } from '../withOnboarding';
|
||||
import { OB_TABS } from 'App/routes';
|
||||
import withPageTitle from 'App/components/hocs/withPageTitle';
|
||||
|
||||
interface Props extends WithOnboardingProps {}
|
||||
|
||||
|
|
@ -20,9 +21,10 @@ function InstallOpenReplayTab(props: Props) {
|
|||
<ProjectFormButton />
|
||||
</div>
|
||||
</div>
|
||||
<a className="flex items-center link" href="https://docs.openreplay.com/en/installation/javascript-sdk/" target="_blank">
|
||||
<Icon name="book" color="blue" className="mr-2" size={16} />
|
||||
<span>Setup Guide</span>
|
||||
<a href="https://docs.openreplay.com/en/installation/javascript-sdk/" target="_blank">
|
||||
<Button variant="text-primary" icon="question-circle" className="ml-2">
|
||||
See Documentation
|
||||
</Button>
|
||||
</a>
|
||||
</h1>
|
||||
<div className="p-4">
|
||||
|
|
@ -46,4 +48,4 @@ function InstallOpenReplayTab(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default withOnboarding(InstallOpenReplayTab);
|
||||
export default withOnboarding(withPageTitle("Project Setup - OpenReplay")(InstallOpenReplayTab));
|
||||
|
|
|
|||
|
|
@ -2,32 +2,25 @@ import React from 'react';
|
|||
import { Button } from 'UI';
|
||||
import Integrations from 'App/components/Client/Integrations/Integrations';
|
||||
import withOnboarding, { WithOnboardingProps } from '../withOnboarding';
|
||||
import withPageTitle from 'App/components/hocs/withPageTitle';
|
||||
|
||||
interface Props extends WithOnboardingProps {}
|
||||
function IntegrationsTab(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<h1 className="flex items-center px-4 py-3 border-b text-2xl">
|
||||
<span>🔌</span>
|
||||
<div className="ml-3">Integrations</div>
|
||||
</h1>
|
||||
<Integrations hideHeader={true} />
|
||||
{/* <div className="py-6 w-4/12">
|
||||
<div className="p-5 bg-gray-lightest mb-4">
|
||||
<div className="font-bold mb-2">Why Use Plugins?</div>
|
||||
<div className="text-sm">
|
||||
Reproduce issues as if they happened in your own browser. Plugins help capture your
|
||||
application’s store, HTTP requests, GraphQL queries and more.
|
||||
</div>
|
||||
<h1 className="flex items-center px-4 py-3 border-b justify-between">
|
||||
<div className="flex items-center text-2xl">
|
||||
<span>🔌</span>
|
||||
<div className="ml-3">Integrations</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 bg-gray-lightest mb-4">
|
||||
<div className="font-bold mb-2">Why Use Integrations?</div>
|
||||
<div className="text-sm">
|
||||
Sync your backend errors with sessions replays and see what happened front-to-back.
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
<a href="https://docs.openreplay.com/en/v1.10.0/integrations/" target="_blank">
|
||||
<Button variant="text-primary" icon="question-circle" className="ml-2">
|
||||
See Documentation
|
||||
</Button>
|
||||
</a>
|
||||
</h1>
|
||||
<Integrations hideHeader={true} />
|
||||
<div className="border-t px-4 py-3 flex justify-end">
|
||||
<Button variant="primary" className="" onClick={() => (props.skip ? props.skip() : null)}>
|
||||
Complete Setup
|
||||
|
|
@ -37,4 +30,4 @@ function IntegrationsTab(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default withOnboarding(IntegrationsTab);
|
||||
export default withOnboarding(withPageTitle("Integrations - OpenReplay")(IntegrationsTab));
|
||||
|
|
|
|||
|
|
@ -4,15 +4,27 @@ import React from 'react';
|
|||
import { Button, Icon } from 'UI';
|
||||
import withOnboarding, { WithOnboardingProps } from '../withOnboarding';
|
||||
import { OB_TABS } from 'App/routes';
|
||||
import withPageTitle from 'App/components/hocs/withPageTitle';
|
||||
|
||||
interface Props extends WithOnboardingProps {}
|
||||
|
||||
function ManageUsersTab(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<h1 className="flex items-center px-4 py-3 border-b text-2xl">
|
||||
<span>👨💻</span>
|
||||
<div className="ml-3">Invite Collaborators</div>
|
||||
<h1 className="flex items-center px-4 py-3 border-b justify-between">
|
||||
<div className="flex items-center text-2xl">
|
||||
<span>👨💻</span>
|
||||
<div className="ml-3">Invite Collaborators</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://docs.openreplay.com/en/tutorials/adding-users/"
|
||||
target="_blank"
|
||||
>
|
||||
<Button variant="text-primary" icon="question-circle" className="ml-2">
|
||||
See Documentation
|
||||
</Button>
|
||||
</a>
|
||||
</h1>
|
||||
<div className="grid grid-cols-6 gap-4 p-4">
|
||||
<div className="col-span-4">
|
||||
|
|
@ -50,4 +62,4 @@ function ManageUsersTab(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default withOnboarding(ManageUsersTab);
|
||||
export default withOnboarding(withPageTitle('Invite Collaborators - OpenReplay')(ManageUsersTab));
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import React, { useState } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import stl from './installDocs.module.css'
|
||||
import cn from 'classnames'
|
||||
import Highlight from 'react-highlight'
|
||||
import CircleNumber from '../../CircleNumber'
|
||||
import { CopyButton } from 'UI'
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import stl from './installDocs.module.css';
|
||||
import cn from 'classnames';
|
||||
import Highlight from 'react-highlight';
|
||||
import CircleNumber from '../../CircleNumber';
|
||||
import { CopyButton } from 'UI';
|
||||
import { Toggler } from 'UI';
|
||||
|
||||
const installationCommand = 'npm i @openreplay/tracker'
|
||||
const installationCommand = 'npm i @openreplay/tracker';
|
||||
const usageCode = `import Tracker from '@openreplay/tracker';
|
||||
|
||||
const tracker = new Tracker({
|
||||
projectKey: "PROJECT_KEY",
|
||||
ingestPoint: "https://${window.location.hostname}/ingest",
|
||||
});
|
||||
tracker.start();`
|
||||
tracker.start();`;
|
||||
const usageCodeSST = `import Tracker from '@openreplay/tracker/cjs';
|
||||
|
||||
const tracker = new Tracker({
|
||||
|
|
@ -28,12 +28,12 @@ function MyApp() {
|
|||
}, []);
|
||||
|
||||
//...
|
||||
}`
|
||||
}`;
|
||||
|
||||
function InstallDocs({ site }) {
|
||||
const _usageCode = usageCode.replace('PROJECT_KEY', site.projectKey)
|
||||
const _usageCodeSST = usageCodeSST.replace('PROJECT_KEY', site.projectKey)
|
||||
const [isSpa, setIsSpa] = useState(true)
|
||||
const _usageCode = usageCode.replace('PROJECT_KEY', site.projectKey);
|
||||
const _usageCodeSST = usageCodeSST.replace('PROJECT_KEY', site.projectKey);
|
||||
const [isSpa, setIsSpa] = useState(true);
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
|
|
@ -41,61 +41,69 @@ function InstallDocs({ site }) {
|
|||
<CircleNumber text="1" />
|
||||
Install the npm package.
|
||||
</div>
|
||||
<div className={ cn(stl.snippetWrapper, 'ml-10') }>
|
||||
<CopyButton content={installationCommand} className={cn(stl.codeCopy, 'mt-2 mr-2')} />
|
||||
<Highlight className="cli">
|
||||
{installationCommand}
|
||||
</Highlight>
|
||||
<div className={cn(stl.snippetWrapper, 'ml-10')}>
|
||||
<div className="absolute mt-1 mr-2 right-0">
|
||||
<CopyButton content={installationCommand} />
|
||||
</div>
|
||||
<Highlight className="cli">{installationCommand}</Highlight>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold mb-2 flex items-center">
|
||||
<CircleNumber text="2" />
|
||||
Continue with one of the following options.
|
||||
Continue with one of the following options.
|
||||
</div>
|
||||
|
||||
<div className="flex items-center ml-10 cursor-pointer">
|
||||
<div className="mr-2" onClick={() => setIsSpa(!isSpa)}>Server-Side-Rendered (SSR)?</div>
|
||||
<div className="mr-2" onClick={() => setIsSpa(!isSpa)}>
|
||||
Server-Side-Rendered (SSR)?
|
||||
</div>
|
||||
<Toggler
|
||||
checked={!isSpa}
|
||||
name="sessionsLive"
|
||||
onChange={ () => setIsSpa(!isSpa) }
|
||||
onChange={() => setIsSpa(!isSpa)}
|
||||
// style={{ lineHeight: '23px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex ml-10 mt-4">
|
||||
<div className="w-full">
|
||||
{isSpa && (
|
||||
<div>
|
||||
<div className="mb-2 text-sm">If your website is a <strong>Single Page Application (SPA)</strong> use the below code:</div>
|
||||
<div className={ cn(stl.snippetWrapper) }>
|
||||
<CopyButton content={_usageCode} className={cn(stl.codeCopy, 'mt-2 mr-2')} />
|
||||
<Highlight className="js">
|
||||
{_usageCode}
|
||||
</Highlight>
|
||||
<div className="mb-2 text-sm">
|
||||
If your website is a <strong>Single Page Application (SPA)</strong> use the below
|
||||
code:
|
||||
</div>
|
||||
<div className={cn(stl.snippetWrapper)}>
|
||||
<div className="absolute mt-1 mr-2 right-0">
|
||||
<CopyButton content={_usageCode} />
|
||||
</div>
|
||||
<Highlight className="js">{_usageCode}</Highlight>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isSpa && (
|
||||
<div>
|
||||
<div className="mb-2 text-sm">Otherwise, if your web app is <strong>Server-Side-Rendered (SSR)</strong> (i.e. NextJS, NuxtJS) use this snippet:</div>
|
||||
<div className={ cn(stl.snippetWrapper) }>
|
||||
<CopyButton content={_usageCodeSST} className={cn(stl.codeCopy, 'mt-2 mr-2')} />
|
||||
<Highlight className="js">
|
||||
{_usageCodeSST}
|
||||
</Highlight>
|
||||
<div className="mb-2 text-sm">
|
||||
Otherwise, if your web app is <strong>Server-Side-Rendered (SSR)</strong> (i.e.
|
||||
NextJS, NuxtJS) use this snippet:
|
||||
</div>
|
||||
<div className={cn(stl.snippetWrapper)}>
|
||||
<div className="absolute mt-1 mr-2 right-0">
|
||||
<CopyButton content={_usageCodeSST} />
|
||||
</div>
|
||||
<Highlight className="js">{_usageCodeSST}</Highlight>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
site: state.getIn([ 'site', 'instance' ]),
|
||||
}))(InstallDocs)
|
||||
export default connect((state) => ({
|
||||
site: state.getIn(['site', 'instance']),
|
||||
}))(InstallDocs);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { editGDPR, saveGDPR, init } from 'Duck/site';
|
||||
import { Checkbox, Toggler } from 'UI';
|
||||
import { Checkbox, Loader, Toggler } from 'UI';
|
||||
import GDPR from 'Types/site/gdpr';
|
||||
import cn from 'classnames';
|
||||
import stl from './projectCodeSnippet.module.css';
|
||||
|
|
@ -23,6 +23,7 @@ const ProjectCodeSnippet = (props) => {
|
|||
const { gdpr } = props.site;
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [isAssistEnabled, setAssistEnabled] = useState(false);
|
||||
const [showLoader, setShowLoader] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const site = props.sites.find((s) => s.id === props.siteId);
|
||||
|
|
@ -50,6 +51,14 @@ const ProjectCodeSnippet = (props) => {
|
|||
saveGDPR(_gdpr);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// show loader for 500 milliseconds
|
||||
setShowLoader(true);
|
||||
setTimeout(() => {
|
||||
setShowLoader(false);
|
||||
}, 200);
|
||||
}, [isAssistEnabled]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
|
|
@ -130,15 +139,21 @@ const ProjectCodeSnippet = (props) => {
|
|||
<span>{' tag of your page.'}</span>
|
||||
</div>
|
||||
<div className={cn(stl.snippetsWrapper, 'ml-10')}>
|
||||
<CodeSnippet
|
||||
isAssistEnabled={isAssistEnabled}
|
||||
host={site && site.host}
|
||||
projectKey={site && site.projectKey}
|
||||
ingestPoint={`"https://${window.location.hostname}/ingest"`}
|
||||
defaultInputMode={gdpr.defaultInputMode}
|
||||
obscureTextNumbers={gdpr.maskNumbers}
|
||||
obscureTextEmails={gdpr.maskEmails}
|
||||
/>
|
||||
{showLoader ? (
|
||||
<div style={{ height: '474px' }}>
|
||||
<Loader loading={true} />
|
||||
</div>
|
||||
) : (
|
||||
<CodeSnippet
|
||||
isAssistEnabled={isAssistEnabled}
|
||||
host={site && site.host}
|
||||
projectKey={site && site.projectKey}
|
||||
ingestPoint={`"https://${window.location.hostname}/ingest"`}
|
||||
defaultInputMode={gdpr.defaultInputMode}
|
||||
obscureTextNumbers={gdpr.maskNumbers}
|
||||
obscureTextEmails={gdpr.maskEmails}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const ProjectFormButton = ({ sites, siteId, init }) => {
|
|||
return (
|
||||
<>
|
||||
<span
|
||||
className="text-2xl font-bold ml-2 color-teal underline-dashed cursor-pointer"
|
||||
className="text-2xl font-bold ml-2 color-teal underline decoration-dotted cursor-pointer"
|
||||
onClick={(e) => openModal(e)}
|
||||
>
|
||||
{site && site.name}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function SideMenu(props: Props) {
|
|||
</div>
|
||||
|
||||
<SideMenuitem
|
||||
title="Install OpenReplay"
|
||||
title="Setup OpenReplay"
|
||||
iconName="tools"
|
||||
active={activeTab === OB_TABS.INSTALLING}
|
||||
onClick={() => props.onClick(OB_TABS.INSTALLING)}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ function UserCard({ className, request, session, width, height, similarSessions,
|
|||
<span className="mx-1 font-bold text-xl">·</span>
|
||||
<Popover
|
||||
render={() => (
|
||||
<div className="text-left bg-white">
|
||||
<div className="text-left bg-white rounded">
|
||||
<SessionInfoItem
|
||||
comp={<CountryFlag country={userCountry} height={11} />}
|
||||
label={countries[userCountry]}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ function PlayerBlockHeader(props: any) {
|
|||
onClick={backHandler}
|
||||
>
|
||||
{/* @ts-ignore TODO */}
|
||||
<BackLink label="Back" className="h-full" />
|
||||
<BackLink label="Back" className="h-full ml-2" />
|
||||
<div className={stl.divider} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
.header {
|
||||
height: 50px;
|
||||
border-bottom: solid thin $gray-light;
|
||||
padding-left: 15px;
|
||||
padding-right: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ function Controls(props: any) {
|
|||
disabled={disabled}
|
||||
backTenSeconds={backTenSeconds}
|
||||
forthTenSeconds={forthTenSeconds}
|
||||
toggleSpeed={() => player.toggleSpeed()}
|
||||
toggleSpeed={(speedIndex) => player.toggleSpeed(speedIndex)}
|
||||
toggleSkip={() => player.toggleSkip()}
|
||||
playButton={<PlayButton state={state} togglePlay={player.togglePlay} iconSize={36} />}
|
||||
skipIntervals={SKIP_INTERVALS}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import cn from 'classnames';
|
|||
import { ReduxTime } from '../Time';
|
||||
// @ts-ignore
|
||||
import styles from '../controls.module.css';
|
||||
import { SkipButton } from 'App/player-ui'
|
||||
import { SkipButton } from 'App/player-ui';
|
||||
import { SPEED_OPTIONS } from 'App/player/player/Player';
|
||||
|
||||
interface Props {
|
||||
skip: boolean;
|
||||
|
|
@ -16,7 +17,7 @@ interface Props {
|
|||
setSkipInterval: (interval: number) => void;
|
||||
backTenSeconds: () => void;
|
||||
forthTenSeconds: () => void;
|
||||
toggleSpeed: () => void;
|
||||
toggleSpeed: (speedIndex: number) => void;
|
||||
toggleSkip: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +80,6 @@ function PlayerControls(props: Props) {
|
|||
<ReduxTime isCustom name="endTime" format="mm:ss" />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="rounded ml-4 bg-active-blue border border-active-blue-border flex items-stretch">
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip
|
||||
|
|
@ -87,10 +87,7 @@ function PlayerControls(props: Props) {
|
|||
title={`Rewind ${currentInterval}s`}
|
||||
placement="top"
|
||||
>
|
||||
<button
|
||||
ref={arrowBackRef}
|
||||
className="h-full bg-transparent"
|
||||
>
|
||||
<button ref={arrowBackRef} className="h-full bg-transparent">
|
||||
<SkipButton
|
||||
size={18}
|
||||
onClick={backTenSeconds}
|
||||
|
|
@ -146,10 +143,7 @@ function PlayerControls(props: Props) {
|
|||
title={`Rewind ${currentInterval}s`}
|
||||
placement="top"
|
||||
>
|
||||
<button
|
||||
ref={arrowForwardRef}
|
||||
className="h-full bg-transparent"
|
||||
>
|
||||
<button ref={arrowForwardRef} className="h-full bg-transparent">
|
||||
<SkipButton
|
||||
size={18}
|
||||
onClick={forthTenSeconds}
|
||||
|
|
@ -159,34 +153,65 @@ function PlayerControls(props: Props) {
|
|||
</Tooltip>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="mx-2" />
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip title="Control play back speed (↑↓)" placement="top">
|
||||
<button
|
||||
ref={speedRef}
|
||||
className={cn(styles.speedButton, 'focus:border focus:border-blue')}
|
||||
onClick={toggleSpeed}
|
||||
data-disabled={disabled}
|
||||
>
|
||||
<div>{speed + 'x'}</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<div className="mx-2" />
|
||||
<button
|
||||
className={cn(styles.skipIntervalButton, {
|
||||
[styles.withCheckIcon]: skip,
|
||||
[styles.active]: skip,
|
||||
})}
|
||||
onClick={toggleSkip}
|
||||
data-disabled={disabled}
|
||||
>
|
||||
{skip && <Icon name="check" size="24" className="mr-1" />}
|
||||
{'Skip Inactivity'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="mx-2" />
|
||||
{/* @ts-ignore */}
|
||||
<Popover
|
||||
// @ts-ignore
|
||||
theme="nopadding"
|
||||
animation="none"
|
||||
duration={0}
|
||||
className="cursor-pointer select-none"
|
||||
distance={20}
|
||||
render={({ close }: any) => (
|
||||
<div className="flex flex-col bg-white border border-borderColor-gray-light-shade text-figmaColors-text-primary rounded">
|
||||
<div className="font-semibold py-2 px-4 w-full text-left">
|
||||
Playback speed
|
||||
</div>
|
||||
{Object.keys(SPEED_OPTIONS).map((index: any) => (
|
||||
<div
|
||||
key={SPEED_OPTIONS[index]}
|
||||
onClick={() => {
|
||||
close();
|
||||
toggleSpeed(index);
|
||||
}}
|
||||
className={cn(
|
||||
'py-2 px-4 cursor-pointer w-full text-left font-semibold',
|
||||
'hover:bg-active-blue border-t border-borderColor-gray-light-shade'
|
||||
)}
|
||||
>
|
||||
{SPEED_OPTIONS[index]}
|
||||
<span className="text-disabled-text">x</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div onClick={toggleTooltip} ref={skipRef} className="cursor-pointer select-none">
|
||||
<Tooltip disabled={showTooltip} title="Set default skip duration">
|
||||
<button
|
||||
ref={speedRef}
|
||||
className={cn(styles.speedButton, 'focus:border focus:border-blue')}
|
||||
data-disabled={disabled}
|
||||
>
|
||||
<div>{speed + 'x'}</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Popover>
|
||||
<div className="mx-2" />
|
||||
<button
|
||||
className={cn(styles.skipIntervalButton, {
|
||||
[styles.withCheckIcon]: skip,
|
||||
[styles.active]: skip,
|
||||
})}
|
||||
onClick={toggleSkip}
|
||||
data-disabled={disabled}
|
||||
>
|
||||
{skip && <Icon name="check" size="24" className="mr-1" />}
|
||||
{'Skip Inactivity'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Button, Link, Icon } from 'UI';
|
|||
import { session as sessionRoute, withSiteId } from 'App/routes';
|
||||
import stl from './AutoplayTimer.module.css';
|
||||
import clsOv from './overlay.module.css';
|
||||
import AutoplayToggle from 'Shared/AutoplayToggle';
|
||||
|
||||
interface IProps extends RouteComponentProps {
|
||||
nextId: number;
|
||||
|
|
@ -40,20 +41,28 @@ function AutoplayTimer({ nextId, siteId, history }: IProps) {
|
|||
|
||||
return (
|
||||
<div className={cn(clsOv.overlay, stl.overlayBg)}>
|
||||
<div className="border p-6 shadow-lg bg-white rounded">
|
||||
<div className="py-4">Next recording will be played in {counter}s</div>
|
||||
<div className="flex items-center">
|
||||
<Button primary="outline" onClick={cancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<div className="px-3" />
|
||||
<Link to={sessionRoute(nextId)} disabled={!nextId}>
|
||||
<Button variant="primary">Play Now</Button>
|
||||
</Link>
|
||||
<div className="border p-5 shadow-lg bg-white rounded">
|
||||
<div className="mb-5">
|
||||
Autoplaying next session in <span className="font-medium">{counter}</span> seconds
|
||||
</div>
|
||||
<div className="mt-2 flex items-center color-gray-dark">
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mr-10">
|
||||
<AutoplayToggle />
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Button variant="text-primary" onClick={cancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<div className="px-2" />
|
||||
<Link to={sessionRoute(nextId)} disabled={!nextId}>
|
||||
<Button variant="outline">Play Now</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="mt-2 flex items-center color-gray-dark">
|
||||
Turn on/off auto-replay in <Icon name="ellipsis-v" className="mx-1" /> More options
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ function SubHeader(props) {
|
|||
{location && (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center cursor-pointer color-gray-medium text-sm p-1 hover:bg-gray-light-shade rounded-md"
|
||||
className="flex items-center cursor-pointer color-gray-medium text-sm p-1 hover:bg-active-blue rounded-md"
|
||||
onClick={() => {
|
||||
copy(currentLocation);
|
||||
setCopied(true);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Button } from 'UI';
|
||||
import { Button, Icon } from 'UI';
|
||||
import styles from './menu.module.css';
|
||||
import cn from 'classnames';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
|
|
@ -42,8 +42,11 @@ export default class ItemMenu extends React.PureComponent<Props> {
|
|||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<OutsideClickDetectingDiv onClickOutside={this.closeMenu}>
|
||||
<Button variant="text" icon="ellipsis-v" onClick={this.toggleMenu}>
|
||||
More
|
||||
<Button variant="text" onClick={this.toggleMenu}>
|
||||
<div className="flex items-center">
|
||||
<Icon name="ellipsis-v" size={18} className="mr-1" />
|
||||
<span>More</span>
|
||||
</div>
|
||||
</Button>
|
||||
<div className={cn(styles.menu, styles.menuDim)} data-displayed={displayed}>
|
||||
{items.map((item) =>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import { Button } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import { setCreateNoteTooltip } from 'Duck/sessions';
|
||||
import GuidePopup from 'Shared/GuidePopup';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
||||
function NotePopup({
|
||||
|
|
@ -12,7 +11,7 @@ function NotePopup({
|
|||
setCreateNoteTooltip: (args: any) => void;
|
||||
tooltipActive: boolean;
|
||||
}) {
|
||||
const { player, store } = React.useContext(PlayerContext)
|
||||
const { player, store } = React.useContext(PlayerContext);
|
||||
|
||||
const toggleNotePopup = () => {
|
||||
if (tooltipActive) return;
|
||||
|
|
@ -25,14 +24,9 @@ function NotePopup({
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<GuidePopup
|
||||
title="Introducing Notes"
|
||||
description={'Annotate session replays and share your feedback with the rest of your team.'}
|
||||
>
|
||||
<Button icon="quotes" variant="text" disabled={tooltipActive} onClick={toggleNotePopup}>
|
||||
Add Note
|
||||
</Button>
|
||||
</GuidePopup>
|
||||
<Button icon="quotes" variant="text" disabled={tooltipActive} onClick={toggleNotePopup}>
|
||||
Add Note
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
}
|
||||
|
||||
white-space: nowrap;
|
||||
z-index: 20;
|
||||
z-index: 9999;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 37px;
|
||||
|
|
|
|||
|
|
@ -3,20 +3,16 @@ import { Toggler } from 'UI';
|
|||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
interface Props {
|
||||
toggleAutoplay: () => void;
|
||||
autoplay: boolean;
|
||||
}
|
||||
function AutoplayToggle(props: Props) {
|
||||
function AutoplayToggle() {
|
||||
const { player, store } = React.useContext(PlayerContext)
|
||||
|
||||
const { autoplay } = store.get()
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => player.toggleAutoplay()}
|
||||
className="cursor-pointer flex items-center mr-2 hover:bg-gray-light-shade rounded-md p-2"
|
||||
>
|
||||
<Toggler name="sessionsLive" onChange={props.toggleAutoplay} checked={autoplay} />
|
||||
<Toggler name="sessionsLive" onChange={() => player.toggleAutoplay()} checked={autoplay} />
|
||||
<span className="ml-2 whitespace-nowrap">Auto-Play</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,47 +6,78 @@ import { Timezone } from 'App/mstore/types/sessionSettings';
|
|||
import { useObserver } from 'mobx-react-lite';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
type TimezonesDropdown = Timezone[]
|
||||
type TimezonesDropdown = Timezone[];
|
||||
|
||||
function DefaultTimezone() {
|
||||
const [changed, setChanged] = React.useState(false);
|
||||
const { settingsStore } = useStore();
|
||||
const timezoneOptions: TimezonesDropdown = settingsStore.sessionSettings.defaultTimezones;
|
||||
const [timezone, setTimezone] = React.useState(settingsStore.sessionSettings.timezone);
|
||||
const sessionSettings = useObserver(() => settingsStore.sessionSettings);
|
||||
const [changed, setChanged] = React.useState(false);
|
||||
const { settingsStore } = useStore();
|
||||
const timezoneOptions: TimezonesDropdown = settingsStore.sessionSettings.defaultTimezones;
|
||||
const [timezone, setTimezone] = React.useState(settingsStore.sessionSettings.timezone);
|
||||
const sessionSettings = useObserver(() => settingsStore.sessionSettings);
|
||||
|
||||
useEffect(() => {
|
||||
if (!timezone) setTimezone({ label: 'Local Timezone', value: 'system' });
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!timezone) setTimezone({ label: 'Local Timezone', value: 'system' });
|
||||
}, []);
|
||||
|
||||
const onSelectChange = ({ value }: { value: Timezone }) => {
|
||||
setTimezone(value);
|
||||
setChanged(true);
|
||||
}
|
||||
const onTimezoneSave = () => {
|
||||
setChanged(false);
|
||||
sessionSettings.updateKey('timezone', timezone);
|
||||
toast.success("Default timezone saved successfully");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-lg">Default Timezone</h3>
|
||||
<div className="my-1">Session Time</div>
|
||||
<div className="mt-2 flex items-center" style={{ width: "265px" }}>
|
||||
<Select
|
||||
options={timezoneOptions}
|
||||
defaultValue={timezone.value}
|
||||
className="w-full"
|
||||
onChange={onSelectChange}
|
||||
/>
|
||||
<div className="col-span-3 ml-3">
|
||||
<Button disabled={!changed} variant="outline" size="medium" onClick={onTimezoneSave}>Update</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm mt-3">This change will impact the timestamp on session card and player.</div>
|
||||
</>
|
||||
const getCurrentTimezone = () => {
|
||||
const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const timezoneOffset = new Date().getTimezoneOffset() / -60;
|
||||
const timezoneValue = `UTC${
|
||||
(timezoneOffset >= 0 ? '+' : '-') + timezoneOffset.toString().padStart(2, '0')
|
||||
}`;
|
||||
const selectedTimezone = timezoneOptions.find(
|
||||
(option) => option.label.includes(currentTimezone) || option.value === timezoneValue
|
||||
);
|
||||
return selectedTimezone ? selectedTimezone : null;
|
||||
};
|
||||
|
||||
const setCurrentTimezone = () => {
|
||||
const selectedTimezone = getCurrentTimezone();
|
||||
console.log('selectedTimezone', selectedTimezone);
|
||||
if (selectedTimezone) {
|
||||
setTimezone(selectedTimezone);
|
||||
sessionSettings.updateKey('timezone', selectedTimezone);
|
||||
toast.success('Default timezone saved successfully');
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectChange = ({ value }: { value: Timezone }) => {
|
||||
setTimezone(value);
|
||||
setChanged(true);
|
||||
};
|
||||
|
||||
const onTimezoneSave = () => {
|
||||
setChanged(false);
|
||||
sessionSettings.updateKey('timezone', timezone);
|
||||
toast.success('Default timezone saved successfully');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-lg">Default Timezone</h3>
|
||||
<div className="my-1">
|
||||
Set the timezone for this project. All Sessions, Charts will be referenced to this.
|
||||
</div>
|
||||
<div className="mt-2 flex items-center" style={{ width: '265px' }}>
|
||||
<Select
|
||||
options={timezoneOptions}
|
||||
defaultValue={timezone.value}
|
||||
className="w-full"
|
||||
value={timezoneOptions.find((option) => option.value === timezone.value)}
|
||||
onChange={onSelectChange}
|
||||
/>
|
||||
<div className="col-span-3 ml-3">
|
||||
<Button disabled={!changed} variant="outline" size="medium" onClick={onTimezoneSave}>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onClick={setCurrentTimezone} className="mt-3 link">
|
||||
Apply my current timezone
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DefaultTimezone;
|
||||
|
|
|
|||
|
|
@ -78,10 +78,7 @@ export default class SharePopup extends React.PureComponent {
|
|||
};
|
||||
|
||||
handleSuccess = (endpoint) => {
|
||||
const obj =
|
||||
endpoint === 'Slack'
|
||||
? { loadingSlack: false }
|
||||
: { loadingTeams: false };
|
||||
const obj = endpoint === 'Slack' ? { loadingSlack: false } : { loadingTeams: false };
|
||||
this.setState(obj);
|
||||
toast.success(`Sent to ${endpoint}.`);
|
||||
};
|
||||
|
|
@ -114,7 +111,7 @@ export default class SharePopup extends React.PureComponent {
|
|||
<div className={styles.wrapper}>
|
||||
{this.state.loadingTeams || this.state.loadingSlack ? (
|
||||
<Loader loading />
|
||||
) :(
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.header}>
|
||||
<div className={cn(styles.title, 'text-lg')}>
|
||||
|
|
@ -139,24 +136,21 @@ export default class SharePopup extends React.PureComponent {
|
|||
{slackOptions.length > 0 && (
|
||||
<>
|
||||
<span>Share to slack</span>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
<Select
|
||||
options={slackOptions}
|
||||
defaultValue={channelId}
|
||||
onChange={this.changeSlackChannel}
|
||||
className="mr-4"
|
||||
className="col-span-4"
|
||||
/>
|
||||
{this.state.channelId && (
|
||||
<Button onClick={this.shareToSlack} variant="primary">
|
||||
<div className="flex items-center">
|
||||
<Icon
|
||||
name="integrations/slack-bw"
|
||||
color="white"
|
||||
size="18"
|
||||
marginRight="10"
|
||||
/>
|
||||
{loadingSlack ? 'Sending...' : 'Send'}
|
||||
</div>
|
||||
<Button
|
||||
onClick={this.shareToSlack}
|
||||
icon="integrations/slack-bw"
|
||||
variant="outline"
|
||||
className="col-span-2"
|
||||
>
|
||||
{loadingSlack ? 'Sending...' : 'Send'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -164,25 +158,22 @@ export default class SharePopup extends React.PureComponent {
|
|||
)}
|
||||
{msTeamsOptions.length > 0 && (
|
||||
<>
|
||||
<span>Share to MS Teams</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mt-4">Share to MS Teams</div>
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
<Select
|
||||
options={msTeamsOptions}
|
||||
defaultValue={teamsChannel}
|
||||
onChange={this.changeTeamsChannel}
|
||||
className="mr-4"
|
||||
className="col-span-4"
|
||||
/>
|
||||
{this.state.teamsChannel && (
|
||||
<Button onClick={this.shareToMSTeams} variant="primary">
|
||||
<div className="flex items-center">
|
||||
<Icon
|
||||
name="integrations/teams-white"
|
||||
color="white"
|
||||
size="18"
|
||||
marginRight="10"
|
||||
/>
|
||||
{loadingTeams ? 'Sending...' : 'Send'}
|
||||
</div>
|
||||
<Button
|
||||
onClick={this.shareToMSTeams}
|
||||
icon="integrations/teams-white"
|
||||
variant="outline"
|
||||
className="col-span-2"
|
||||
>
|
||||
{loadingTeams ? 'Sending...' : 'Send'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ function UserSessionsModal(props: Props) {
|
|||
useEffect(fetchData, [filter.page, filter.startDate, filter.endDate]);
|
||||
|
||||
return (
|
||||
<div className="h-screen overflow-y-auto bg-white">
|
||||
<div className="bg-white pb-6 h-screen">
|
||||
<div className="flex items-center justify-between w-full px-5 py-3">
|
||||
<div className="text-lg flex items-center">
|
||||
<Avatar isActive={false} seed={hash} isAssist={false} className={''} />
|
||||
|
|
@ -66,7 +66,7 @@ function UserSessionsModal(props: Props) {
|
|||
<div className="text-center text-gray-600">No recordings found.</div>
|
||||
</div>
|
||||
}>
|
||||
<div className="border rounded m-5">
|
||||
<div className="border rounded m-5 overflow-y-auto" style={{ maxHeight: 'calc(100vh - 85px)'}}>
|
||||
<Loader loading={loading}>
|
||||
{data.sessions.map((session: any) => (
|
||||
<div className="border-b last:border-none" key={session.sessionId}>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function CopyButton({ content, variant="text-primary", className = '', btnText
|
|||
return (
|
||||
<Button
|
||||
variant={variant}
|
||||
className={ className }
|
||||
className={ className + ' capitalize' }
|
||||
onClick={ copyHandler }
|
||||
>
|
||||
{ copied ? 'copied' : btnText }
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react'
|
||||
import Highlight from 'react-highlight'
|
||||
import stl from './highlightCode.module.css'
|
||||
import cn from 'classnames'
|
||||
import { CopyButton } from 'UI'
|
||||
|
||||
function HighlightCode({ className = 'js', text = ''}) {
|
||||
return (
|
||||
<div className={stl.snippetWrapper}>
|
||||
<CopyButton content={text} className={cn(stl.codeCopy, 'mt-2 mr-2')} />
|
||||
<div className="absolute mt-1 mr-2 right-0">
|
||||
<CopyButton content={text} />
|
||||
</div>
|
||||
<Highlight className={className}>
|
||||
{text}
|
||||
</Highlight>
|
||||
|
|
|
|||
|
|
@ -2,24 +2,6 @@
|
|||
|
||||
.snippetWrapper {
|
||||
position: relative;
|
||||
& .codeCopy {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: -3px;
|
||||
z-index: $codeSnippet;
|
||||
padding: 5px 10px;
|
||||
color: $teal;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
transition: all 0.4s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
& .snippet {
|
||||
overflow: hidden;
|
||||
line-height: 20px;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import * as typedLocalStorage from './localStorage';
|
|||
import type { Moveable, Cleanable, Store } from '../common/types';
|
||||
import Animator from './Animator';
|
||||
import type { GetState as AnimatorGetState } from './Animator';
|
||||
export const SPEED_OPTIONS = [0.5, 1, 2, 4, 8, 16]
|
||||
|
||||
|
||||
/* == separate this == */
|
||||
|
|
@ -13,7 +14,7 @@ const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__"
|
|||
const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__"
|
||||
const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__"
|
||||
const storedSpeed: number = typedLocalStorage.number(SPEED_STORAGE_KEY)
|
||||
const initialSpeed = [0.5, 1, 2, 4, 8, 16].includes(storedSpeed) ? storedSpeed : 1
|
||||
const initialSpeed = SPEED_OPTIONS.includes(storedSpeed) ? storedSpeed : 1
|
||||
const initialSkip = typedLocalStorage.boolean(SKIP_STORAGE_KEY)
|
||||
const initialSkipToIssue = typedLocalStorage.boolean(SKIP_TO_ISSUE_STORAGE_KEY)
|
||||
const initialAutoplay = typedLocalStorage.boolean(AUTOPLAY_STORAGE_KEY)
|
||||
|
|
@ -89,9 +90,10 @@ export default class Player extends Animator {
|
|||
this.pState.update({ speed })
|
||||
}
|
||||
|
||||
toggleSpeed() {
|
||||
const { speed } = this.pState.get()
|
||||
this.updateSpeed(speed < HIGHEST_SPEED ? speed * 2 : 0.5)
|
||||
toggleSpeed(index: number | null) {
|
||||
const { speed } = this.pState.get();
|
||||
const newSpeedIndex = index === null ? null : Math.max(0, Math.min(SPEED_OPTIONS.length - 1, index));
|
||||
this.updateSpeed(newSpeedIndex === null ? speed * 2 : SPEED_OPTIONS[newSpeedIndex]);
|
||||
}
|
||||
|
||||
speedUp() {
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@
|
|||
}
|
||||
|
||||
.hljs {
|
||||
padding: 10px !important;
|
||||
padding: 12px !important;
|
||||
border-radius: 6px !important;
|
||||
background-color: $gray-lightest !important;
|
||||
font-size: 12px !important;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
}
|
||||
}
|
||||
.side-menu-margined {
|
||||
margin-left: 220px;
|
||||
margin-left: 250px;
|
||||
}
|
||||
|
||||
.top-header {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue