diff --git a/frontend/.babelrc b/frontend/.babelrc index 9816ce275..4ec9226ab 100644 --- a/frontend/.babelrc +++ b/frontend/.babelrc @@ -10,6 +10,11 @@ [ "@babel/plugin-transform-runtime", { "regenerator": true } ], [ "@babel/plugin-proposal-decorators", { "legacy":true } ], [ "@babel/plugin-transform-class-properties", { "loose":true } ], - [ "@babel/plugin-transform-private-methods", { "loose": true }] + [ "@babel/plugin-transform-private-methods", { "loose": true }], + ["prismjs", { + "languages": ["javascript", "css", "bash", "typescript", "jsx", "kotlin", "swift"], + "theme": "default", + "css": true + }] ] } \ No newline at end of file diff --git a/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js b/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js index e6c5f8e5d..dd94f90e6 100644 --- a/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js +++ b/frontend/app/components/Client/Integrations/AssistDoc/AssistDoc.js @@ -1,9 +1,8 @@ import React from 'react'; -import Highlight from 'react-highlight'; import DocLink from 'Shared/DocLink/DocLink'; import AssistScript from './AssistScript'; import AssistNpm from './AssistNpm'; -import { Tabs } from 'UI'; +import { Tabs, CodeBlock } from 'UI'; import { useState } from 'react'; import { connect } from 'react-redux'; @@ -38,7 +37,7 @@ const AssistDoc = (props) => {
Installation
- {`npm i @openreplay/tracker-assist`} +
Usage
diff --git a/frontend/app/components/Client/Integrations/AssistDoc/AssistNpm.tsx b/frontend/app/components/Client/Integrations/AssistDoc/AssistNpm.tsx index ddec99aa5..706a1831c 100644 --- a/frontend/app/components/Client/Integrations/AssistDoc/AssistNpm.tsx +++ b/frontend/app/components/Client/Integrations/AssistDoc/AssistNpm.tsx @@ -1,18 +1,11 @@ import React from 'react'; -import Highlight from 'react-highlight' -import ToggleContent from 'Shared/ToggleContent' + +import { CodeBlock } from 'UI'; + +import ToggleContent from 'Shared/ToggleContent'; function AssistNpm(props) { - return ( -
-

Initialize the tracker then load the @openreplay/tracker-assist plugin.

- -
Usage
- - {`import OpenReplay from '@openreplay/tracker'; + const usage = `import OpenReplay from '@openreplay/tracker'; import trackerAssist from '@openreplay/tracker-assist'; const tracker = new OpenReplay({ projectKey: '${props.projectKey}', @@ -20,12 +13,8 @@ const tracker = new OpenReplay({ // .start() returns a promise tracker.start().then(sessionData => ... ).catch(e => ... ) -tracker.use(trackerAssist(options)); // check the list of available options below`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; +tracker.use(trackerAssist(options)); // check the list of available options below`; + const usageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; import trackerAssist from '@openreplay/tracker-assist/cjs'; const tracker = new OpenReplay({ projectKey: '${props.projectKey}' @@ -38,14 +27,8 @@ function MyApp() { tracker.start().then(sessionData => ... ).catch(e => ... ) }, []) //... -}`} - - } - /> - -
Options
- - {`trackerAssist({ +}`; + const options = `trackerAssist({ onAgentConnect: StartEndCallback; onCallStart: StartEndCallback; onRemoteControlStart: StartEndCallback; @@ -75,8 +58,22 @@ type ButtonOptions = HTMLButtonElement | string | { innerHTML?: string, // to pass an svg string or text style?: StyleObject, // style object (i.e {color: 'red', borderRadius: '10px'}) } -`} - +` + return ( +
+

+ Initialize the tracker then load the @openreplay/tracker-assist plugin. +

+ +
Usage
+ } + second={} + /> + +
Options
+
); } diff --git a/frontend/app/components/Client/Integrations/AssistDoc/AssistScript.tsx b/frontend/app/components/Client/Integrations/AssistDoc/AssistScript.tsx index 28b55e6fa..23ce8ff02 100644 --- a/frontend/app/components/Client/Integrations/AssistDoc/AssistScript.tsx +++ b/frontend/app/components/Client/Integrations/AssistDoc/AssistScript.tsx @@ -1,14 +1,8 @@ import React from 'react'; -import Highlight from 'react-highlight' +import { CodeBlock } from "UI"; function AssistScript(props) { - return ( -
-

If your OpenReplay tracker is set up using the JS snippet, then simply replace the .../openreplay.js occurrence with .../openreplay-assist.js. Below is an example of how the script should like after the change:

-
- - - {` + const scriptCode = ` `} - +` + return ( +
+

If your OpenReplay tracker is set up using the JS snippet, then simply replace the .../openreplay.js occurrence with .../openreplay-assist.js. Below is an example of how the script should like after the change:

+
+ +
); } diff --git a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js index a5b184b01..18c78d368 100644 --- a/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js +++ b/frontend/app/components/Client/Integrations/GraphQLDoc/GraphQLDoc.js @@ -1,37 +1,12 @@ import React from 'react'; -import Highlight from 'react-highlight'; +import { CodeBlock } from "UI"; import DocLink from 'Shared/DocLink/DocLink'; import ToggleContent from 'Shared/ToggleContent'; import { connect } from 'react-redux'; const GraphQLDoc = (props) => { const { projectKey } = props; - return ( -
-

GraphQL

-
-

- This plugin allows you to capture GraphQL requests and inspect them later on while replaying session recordings. This is very - useful for understanding and fixing issues. -

-

GraphQL plugin is compatible with Apollo and Relay implementations.

- -
Installation
- {`npm i @openreplay/tracker-graphql --save`} - -
Usage
-

- The plugin call will return the function, which receives four variables operationKind, operationName, variables and result. It - returns result without changes. -

- -
- - - {`import OpenReplay from '@openreplay/tracker'; + const usage = `import OpenReplay from '@openreplay/tracker'; import trackerGraphQL from '@openreplay/tracker-graphql'; //... const tracker = new OpenReplay({ @@ -40,12 +15,8 @@ const tracker = new OpenReplay({ // .start() returns a promise tracker.start().then(sessionData => ... ).catch(e => ... ) //... -export const recordGraphQL = tracker.use(trackerGraphQL());`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; +export const recordGraphQL = tracker.use(trackerGraphQL());` + const usageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; import trackerGraphQL from '@openreplay/tracker-graphql/cjs'; //... const tracker = new OpenReplay({ @@ -59,8 +30,35 @@ function SomeFunctionalComponent() { }, []) } //... -export const recordGraphQL = tracker.use(trackerGraphQL());`} - +export const recordGraphQL = tracker.use(trackerGraphQL());` + return ( +
+

GraphQL

+
+

+ This plugin allows you to capture GraphQL requests and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +

+

GraphQL plugin is compatible with Apollo and Relay implementations.

+ +
Installation
+ + +
Usage
+

+ The plugin call will return the function, which receives four variables operationKind, operationName, variables and result. It + returns result without changes. +

+ +
+ + + } + second={ + } /> diff --git a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js index fc34bb8f5..65705b1e0 100644 --- a/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js +++ b/frontend/app/components/Client/Integrations/MobxDoc/MobxDoc.js @@ -1,36 +1,13 @@ import React from 'react'; -import Highlight from 'react-highlight'; import ToggleContent from 'Shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; import { connect } from 'react-redux'; +import { CodeBlock } from "UI"; const MobxDoc = (props) => { const { projectKey } = props; - return ( -
-

MobX

-
-
- This plugin allows you to capture MobX events and inspect them later on while replaying session recordings. This is very useful - for understanding and fixing issues. -
-
Installation
- {`npm i @openreplay/tracker-mobx --save`} - -
Usage
-

- Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated middleware into your Redux - chain. -

-
- -
Usage
- - {`import OpenReplay from '@openreplay/tracker'; + const mobxUsage = `import OpenReplay from '@openreplay/tracker'; import trackerMobX from '@openreplay/tracker-mobx'; //... const tracker = new OpenReplay({ @@ -39,12 +16,9 @@ const tracker = new OpenReplay({ tracker.use(trackerMobX()); // check list of available options below // .start() returns a promise tracker.start().then(sessionData => ... ).catch(e => ... ); -`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; +` + + const mobxUsageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; import trackerMobX from '@openreplay/tracker-mobx/cjs'; //... const tracker = new OpenReplay({ @@ -57,9 +31,32 @@ function SomeFunctionalComponent() { // .start() returns a promise tracker.start().then(sessionData => ... ).catch(e => ... ) }, []) -}`} - - } +}` + + return ( +
+

MobX

+
+
+ This plugin allows you to capture MobX events and inspect them later on while replaying session recordings. This is very useful + for understanding and fixing issues. +
+ +
Installation
+ + +
Usage
+

+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated middleware into your Redux + chain. +

+
+ +
Usage
+ } + second={} /> diff --git a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js index 64173d2ca..8097a4618 100644 --- a/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js +++ b/frontend/app/components/Client/Integrations/NgRxDoc/NgRxDoc.js @@ -1,33 +1,12 @@ import React from 'react'; -import Highlight from 'react-highlight'; +import { CodeBlock } from "UI"; import ToggleContent from 'Shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; import { connect } from 'react-redux'; const NgRxDoc = (props) => { const { projectKey } = props; - return ( -
-

NgRx

-
-
- This plugin allows you to capture NgRx actions/state and inspect them later on while replaying session recordings. This is very - useful for understanding and fixing issues. -
- -
Installation
- {`npm i @openreplay/tracker-ngrx --save`} - -
Usage
-

Add the generated meta-reducer into your imports. See NgRx documentation for more details.

-
- -
Usage
- - {`import { StoreModule } from '@ngrx/store'; + const usage = `import { StoreModule } from '@ngrx/store'; import { reducers } from './reducers'; import OpenReplay from '@openreplay/tracker'; import trackerNgRx from '@openreplay/tracker-ngrx'; @@ -43,12 +22,8 @@ const metaReducers = [tracker.use(trackerNgRx())]; // check list of ava @NgModule({ imports: [StoreModule.forRoot(reducers, { metaReducers })] }) -export class AppModule {}`} - - } - second={ - - {`import { StoreModule } from '@ngrx/store'; +export class AppModule {}` + const usageCjs = `import { StoreModule } from '@ngrx/store'; import { reducers } from './reducers'; import OpenReplay from '@openreplay/tracker/cjs'; import trackerNgRx from '@openreplay/tracker-ngrx/cjs'; @@ -69,8 +44,31 @@ const metaReducers = [tracker.use(trackerNgRx())]; // check list of ava imports: [StoreModule.forRoot(reducers, { metaReducers })] }) export class AppModule {} -}`} - +}` + return ( +
+

NgRx

+
+
+ This plugin allows you to capture NgRx actions/state and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +
+ +
Installation
+ + +
Usage
+

Add the generated meta-reducer into your imports. See NgRx documentation for more details.

+
+ +
Usage
+ + } + second={ + } /> diff --git a/frontend/app/components/Client/Integrations/PiniaDoc/PiniaDoc.tsx b/frontend/app/components/Client/Integrations/PiniaDoc/PiniaDoc.tsx index e4b9902c2..a0a9c157e 100644 --- a/frontend/app/components/Client/Integrations/PiniaDoc/PiniaDoc.tsx +++ b/frontend/app/components/Client/Integrations/PiniaDoc/PiniaDoc.tsx @@ -1,35 +1,12 @@ import React from 'react'; -import Highlight from 'react-highlight'; +import { CodeBlock } from "UI"; import ToggleContent from '../../../shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; import { connect } from 'react-redux'; const PiniaDoc = (props) => { const { projectKey } = props; - return ( -
-

VueX

-
-
- This plugin allows you to capture Pinia mutations + state and inspect them later on while - replaying session recordings. This is very useful for understanding and fixing issues. -
- -
Installation
- {`npm i @openreplay/tracker-vuex --save`} - -
Usage
-

- Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put - the generated plugin into your plugins field of your store. -

-
- - - {`import Vuex from 'vuex' + const usage = `import Vuex from 'vuex' import OpenReplay from '@openreplay/tracker'; import trackerVuex from '@openreplay/tracker-vuex'; //... @@ -51,12 +28,8 @@ piniaStorePlugin(examplePiniaStore) // now you can use examplePiniaStore as // usual pinia store // (destructure values or return it as a whole etc) -`} - - } - second={ - - {`import Vuex from 'vuex' +` + const usageCjs = `import Vuex from 'vuex' import OpenReplay from '@openreplay/tracker/cjs'; import trackerVuex from '@openreplay/tracker-vuex/cjs'; //... @@ -82,8 +55,33 @@ piniaStorePlugin(examplePiniaStore) // now you can use examplePiniaStore as // usual pinia store // (destructure values or return it as a whole etc) -}`} - +}` + return ( +
+

VueX

+
+
+ This plugin allows you to capture Pinia mutations + state and inspect them later on while + replaying session recordings. This is very useful for understanding and fixing issues. +
+ +
Installation
+ + +
Usage
+

+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put + the generated plugin into your plugins field of your store. +

+
+ + + } + second={ + } /> diff --git a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js index 9562682cf..eb7ad3999 100644 --- a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js +++ b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.js @@ -1,33 +1,15 @@ import React from 'react'; -import Highlight from 'react-highlight'; -import ToggleContent from 'Shared/ToggleContent'; -import DocLink from 'Shared/DocLink/DocLink'; import { connect } from 'react-redux'; +import { CodeBlock } from 'UI'; + +import DocLink from 'Shared/DocLink/DocLink'; +import ToggleContent from 'Shared/ToggleContent'; + const ProfilerDoc = (props) => { - const { projectKey } = props; - return ( -
-

Profiler

-
-
- The profiler plugin allows you to measure your JS functions' performance and capture both arguments and result for each function - call. -
+ const { projectKey } = props; -
Installation
- {`npm i @openreplay/tracker-profiler --save`} - -
Usage
-

Initialize the tracker and load the plugin into it. Then decorate any function inside your code with the generated function.

-
- -
Usage
- - {`import OpenReplay from '@openreplay/tracker'; + const usage = `import OpenReplay from '@openreplay/tracker'; import trackerProfiler from '@openreplay/tracker-profiler'; //... const tracker = new OpenReplay({ @@ -40,12 +22,8 @@ export const profiler = tracker.use(trackerProfiler()); //... const fn = profiler('call_name')(() => { //... -}, thisArg); // thisArg is optional`} - - } - second={ - - {`import OpenReplay from '@openreplay/tracker/cjs'; +}, thisArg); // thisArg is optional`; + const usageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; import trackerProfiler from '@openreplay/tracker-profiler/cjs'; //... const tracker = new OpenReplay({ @@ -63,15 +41,48 @@ export const profiler = tracker.use(trackerProfiler()); const fn = profiler('call_name')(() => { //... }, thisArg); // thisArg is optional -}`} - - } - /> - - -
+}`; + return ( +
+

Profiler

+
+
+ The profiler plugin allows you to measure your JS functions' + performance and capture both arguments and result for each function + call.
- ); + +
Installation
+ + +
Usage
+

+ Initialize the tracker and load the plugin into it. Then decorate any + function inside your code with the generated function. +

+
+ +
Usage
+ } + second={} + /> + + +
+
+ ); }; ProfilerDoc.displayName = 'ProfilerDoc'; @@ -80,6 +91,8 @@ export default connect((state) => { const siteId = state.getIn(['integrations', 'siteId']); const sites = state.getIn(['site', 'list']); return { - projectKey: sites.find((site) => site.get('id') === siteId).get('projectKey'), + projectKey: sites + .find((site) => site.get('id') === siteId) + .get('projectKey'), }; })(ProfilerDoc); diff --git a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js index f4e133ab4..3566bb82d 100644 --- a/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js +++ b/frontend/app/components/Client/Integrations/ReduxDoc/ReduxDoc.js @@ -1,32 +1,13 @@ import React from 'react'; -import Highlight from 'react-highlight'; +import { CodeBlock } from 'UI' import ToggleContent from '../../../shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; import { connect } from 'react-redux'; const ReduxDoc = (props) => { const { projectKey } = props; - return ( -
-

Redux

-
-
- This plugin allows you to capture Redux actions/state and inspect them later on while replaying session recordings. This is very - useful for understanding and fixing issues. -
- -
Installation
- {`npm i @openreplay/tracker-redux --save`} - -
Usage
-

Initialize the tracker then put the generated middleware into your Redux chain.

-
- - {`import { applyMiddleware, createStore } from 'redux'; + const usage = `import { applyMiddleware, createStore } from 'redux'; import OpenReplay from '@openreplay/tracker'; import trackerRedux from '@openreplay/tracker-redux'; //... @@ -39,12 +20,8 @@ tracker.start().then(sessionData => ... ).catch(e => ... ) const store = createStore( reducer, applyMiddleware(tracker.use(trackerRedux())) // check list of available options below -);`} - - } - second={ - - {`import { applyMiddleware, createStore } from 'redux'; +);` + const usageCjs = `import { applyMiddleware, createStore } from 'redux'; import OpenReplay from '@openreplay/tracker/cjs'; import trackerRedux from '@openreplay/tracker-redux/cjs'; //... @@ -62,8 +39,30 @@ const store = createStore( reducer, applyMiddleware(tracker.use(trackerRedux())) // check list of available options below ); -}`} - +}` + return ( +
+

Redux

+ +
+
+ This plugin allows you to capture Redux actions/state and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +
+ +
Installation
+ + +
Usage
+

Initialize the tracker then put the generated middleware into your Redux chain.

+
+ + } + second={ + } /> diff --git a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js index d2371c9ef..2a9e2e2a6 100644 --- a/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js +++ b/frontend/app/components/Client/Integrations/VueDoc/VueDoc.js @@ -1,35 +1,13 @@ import React from 'react'; -import Highlight from 'react-highlight'; +import { CodeBlock } from "UI"; import ToggleContent from '../../../shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; import { connect } from 'react-redux'; const VueDoc = (props) => { const { projectKey, siteId } = props; - return ( -
-

VueX

-
-
- This plugin allows you to capture VueX mutations/state and inspect them later on while - replaying session recordings. This is very useful for understanding and fixing issues. -
-
Installation
- {`npm i @openreplay/tracker-vuex --save`} - -
Usage
-

- Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put - the generated plugin into your plugins field of your store. -

-
- - - {`import Vuex from 'vuex' + const usage = `import Vuex from 'vuex' import OpenReplay from '@openreplay/tracker'; import trackerVuex from '@openreplay/tracker-vuex'; //... @@ -42,12 +20,8 @@ tracker.start().then(sessionData => ... ).catch(e => ... ) const store = new Vuex.Store({ //... plugins: [tracker.use(trackerVuex())] // check list of available options below -});`} - - } - second={ - - {`import Vuex from 'vuex' +});` + const usageCjs = `import Vuex from 'vuex' import OpenReplay from '@openreplay/tracker/cjs'; import trackerVuex from '@openreplay/tracker-vuex/cjs'; //... @@ -65,8 +39,33 @@ const store = new Vuex.Store({ //... plugins: [tracker.use(trackerVuex())] // check list of available options below }); -}`} - +}` + return ( +
+

VueX

+
+
+ This plugin allows you to capture VueX mutations/state and inspect them later on while + replaying session recordings. This is very useful for understanding and fixing issues. +
+ +
Installation
+ + +
Usage
+

+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put + the generated plugin into your plugins field of your store. +

+
+ + + } + second={ + } /> diff --git a/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js b/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js index c77f68780..9ed446cdc 100644 --- a/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js +++ b/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js @@ -1,35 +1,13 @@ import React from 'react'; -import Highlight from 'react-highlight'; +import { CodeBlock } from "UI"; import ToggleContent from '../../../shared/ToggleContent'; import DocLink from 'Shared/DocLink/DocLink'; import { connect } from 'react-redux'; const ZustandDoc = (props) => { const { projectKey } = props; - return ( -
-

Zustand

-
-
- This plugin allows you to capture Zustand mutations/state and inspect them later on while replaying session recordings. This is very - useful for understanding and fixing issues. -
-
Installation
- {`npm i @openreplay/tracker-zustand --save`} - -
Usage
-

- Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated plugin into your plugins - field of your store. -

-
- - - {`import create from "zustand"; + const usage = `import create from "zustand"; import Tracker from '@openreplay/tracker'; import trackerZustand, { StateLogger } from '@openreplay/tracker-zustand'; @@ -55,12 +33,8 @@ const useBearStore = create( 'bear_store' ) ) -`} - - } - second={ - - {`import create from "zustand"; +` + const usageCjs =`import create from "zustand"; import Tracker from '@openreplay/tracker/cjs'; import trackerZustand, { StateLogger } from '@openreplay/tracker-zustand/cjs'; @@ -85,8 +59,33 @@ const useBearStore = create( // and is randomly generated if undefined 'bear_store' ) -)`} - +)` + return ( +
+

Zustand

+
+
+ This plugin allows you to capture Zustand mutations/state and inspect them later on while replaying session recordings. This is very + useful for understanding and fixing issues. +
+ +
Installation
+ + +
Usage
+

+ Initialize the @openreplay/tracker package as usual and load the plugin into it. Then put the generated plugin into your plugins + field of your store. +

+
+ + + } + second={ + } /> diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx index 67684ad10..d7be1c9f3 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx @@ -20,6 +20,7 @@ function ByComponent({ title, rows, lineWidth, onCard, type }: { displayName: r.label, sessionCount: r.value })).slice(0, 4); + return ( editHandler(true) }, - // { icon: 'text-paragraph', text: `${!isTitlePresent ? 'Add' : 'Edit'} Description`, onClick: () => editHandler(false) }, { icon: 'users', text: 'Visibility & Access', onClick: editHandler }, { icon: 'trash', text: 'Delete', onClick: deleteHandler }, { icon: 'pdf-download', text: 'Download Report', onClick: renderReport, disabled: !isEnterprise, tooltipTitle: ENTERPRISE_REQUEIRED } diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx index 145a215ab..b208358b8 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx @@ -1,6 +1,6 @@ import React from 'react'; import cn from 'classnames'; -import moment from 'moment'; +import { DateTime } from 'luxon' import { IGNORED, RESOLVED } from 'Types/errorInfo'; import ErrorName from '../ErrorName'; import ErrorLabel from '../ErrorLabel'; @@ -76,9 +76,10 @@ export default ErrorListItem; const CustomTooltip = ({ active, payload, label }: any) => { if (active) { const p = payload[0].payload; + const dateStr = p.timestamp ? DateTime.fromMillis(p.timestamp).toFormat('l') : '' return (
-

{`${p.timestamp ? moment(p.timestamp).format('l') : ''}`}

+

{dateStr}

Sessions: {p.count}

); diff --git a/frontend/app/components/Errors/Error/Trend.js b/frontend/app/components/Errors/Error/Trend.js index 2e4a6e8b7..2767e6e8d 100644 --- a/frontend/app/components/Errors/Error/Trend.js +++ b/frontend/app/components/Errors/Error/Trend.js @@ -1,14 +1,15 @@ import React from 'react'; import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, BarChart, Bar } from 'recharts'; import domain from "Components/Dashboard/Widgets/common/domain"; -import moment from 'moment'; +import { DateTime } from 'luxon' const CustomTooltip = ({ active, payload, label, timeFormat = 'hh:mm a' }) => { if (active) { const p = payload[0].payload; + const dateStr = DateTime.fromMillis(p.timestamp).toFormat(timeFormat) return (
-

{`${moment(p.timestamp).format(timeFormat)}`}

+

{dateStr}

Sessions: {p.count}

); diff --git a/frontend/app/components/Errors/List/ListItem/ListItem.js b/frontend/app/components/Errors/List/ListItem/ListItem.js index 51e416b81..0e4474567 100644 --- a/frontend/app/components/Errors/List/ListItem/ListItem.js +++ b/frontend/app/components/Errors/List/ListItem/ListItem.js @@ -1,7 +1,7 @@ import React from 'react'; import { BarChart, Bar, YAxis, Tooltip, XAxis } from 'recharts'; import cn from 'classnames'; -import moment from 'moment'; +import { DateTime } from 'luxon' import { diffFromNowString } from 'App/date'; import { error as errorRoute } from 'App/routes'; import { IGNORED, RESOLVED } from 'Types/errorInfo'; @@ -14,9 +14,10 @@ import { Styles } from '../../../Dashboard/Widgets/common'; const CustomTooltip = ({ active, payload, label }) => { if (active) { const p = payload[0].payload; + const dateStr = p.timestamp ? DateTime.fromMillis(p.timestamp).toFormat('l') : '' return (
-

{`${moment(p.timestamp).format('l')}`}

+

{dateStr}

Sessions: {p.count}

); diff --git a/frontend/app/components/FFlags/NewFFlag/HowTo.tsx b/frontend/app/components/FFlags/NewFFlag/HowTo.tsx index bd63fa8d4..155a13ac5 100644 --- a/frontend/app/components/FFlags/NewFFlag/HowTo.tsx +++ b/frontend/app/components/FFlags/NewFFlag/HowTo.tsx @@ -1,16 +1,8 @@ import React from 'react'; -// @ts-ignore -import Highlight from 'react-highlight'; -import { PageTitle } from 'UI'; +import { PageTitle, CodeBlock } from 'UI'; function HowTo() { - return ( -
- - -
- - {` + const code = ` // can be imported from @openreplay/tracker interface IFeatureFlag { key: string @@ -36,8 +28,13 @@ tracker.getFeatureFlag('my_flag') // reload flags from server // (in case if any user data changed during the session) tracker.reloadFlags() -`} - +` + return ( +
+ + +
+
Documentation
diff --git a/frontend/app/components/Login/Login.tsx b/frontend/app/components/Login/Login.tsx index 15e51c194..a9362e533 100644 --- a/frontend/app/components/Login/Login.tsx +++ b/frontend/app/components/Login/Login.tsx @@ -109,6 +109,7 @@ const Login: React.FC = ({ handleSpotLogin(resp.spotJwt); } loginSuccess(resp) + setJwt(resp.jwt) }) }; diff --git a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/AndroidInstallDocs.tsx b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/AndroidInstallDocs.tsx index cfc153583..c554868d5 100644 --- a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/AndroidInstallDocs.tsx +++ b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/AndroidInstallDocs.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import stl from './installDocs.module.css'; import cn from 'classnames'; -import Highlight from 'react-highlight'; +import React from 'react'; + +import { CodeBlock, CopyButton } from 'UI'; + import CircleNumber from '../../CircleNumber'; -import { CopyButton } from 'UI'; +import stl from './installDocs.module.css'; export const installationCommand = `// Add it in your root build.gradle at the end of repositories: dependencyResolutionManagement { @@ -50,111 +51,117 @@ let wifiOnly: Bool`; const touches = `class MainActivity : ComponentActivity() { // ... OpenReplay.setupGestureDetector(this) -}` +}`; const sensitive = `import com.openreplay.tracker.OpenReplay OpenReplay.addIgnoredView(view) -` +`; const inputs = `import com.openreplay.tracker.OpenReplay val passwordEditText = binding.password -passwordEditText.trackTextInput(label = "password", masked = true)` +passwordEditText.trackTextInput(label = "password", masked = true)`; function AndroidInstallDocs({ site, ingestPoint }: any) { - let _usageCode = usageCode.replace('PROJECT_KEY', site.projectKey).replace('INGEST_POINT', ingestPoint); + let _usageCode = usageCode + .replace('PROJECT_KEY', site.projectKey) + .replace('INGEST_POINT', ingestPoint); - return ( -
-
-
- - Install the SDK -
-
-
- -
- {installationCommand} -
-
- -
- - Add to your app -
-
-
-
-
- -
- {_usageCode} -
-
-
- -
- - Configuration -
-
-
-
- {configuration} -
By default, all options equals true
-
-
-
- -
- - Set up touch events listener -
-
-
-
-
- -
- {touches} -
-
-
- -
- - Hide sensitive views -
-
-
-
-
- -
- {sensitive} -
-
-
- -
- - Track inputs -
-
-
-
-
- -
- {inputs} -
-
-
+ return ( +
+
+
+ + Install the SDK
- ); +
+
+ +
+ +
+
+ +
+ + Add to your app +
+
+
+
+
+ +
+ +
+
+
+ +
+ + Configuration +
+
+
+
+ +
+ By default, all options equals{' '} + + true + +
+
+
+
+ +
+ + Set up touch events listener +
+
+
+
+
+ +
+ +
+
+
+ +
+ + Hide sensitive views +
+
+
+
+
+ +
+ +
+
+
+ +
+ + Track inputs +
+
+
+
+
+ +
+ +
+
+
+
+ ); } export default AndroidInstallDocs; diff --git a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js index e75a38e69..d6114e96d 100644 --- a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js +++ b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js @@ -1,10 +1,8 @@ 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 { CopyButton, CodeBlock } from 'UI'; import { Toggler } from 'UI'; const installationCommand = 'npm i @openreplay/tracker'; @@ -47,7 +45,7 @@ function InstallDocs({ site }) {
- {installationCommand} +
@@ -80,7 +78,7 @@ function InstallDocs({ site }) {
- {_usageCode} +
)} @@ -97,7 +95,7 @@ function InstallDocs({ site }) {
- {_usageCodeSST} +
)} @@ -120,7 +118,7 @@ function InstallDocs({ site }) {
- {`$ npm i @openreplay/tracker-assist`} +
@@ -131,7 +129,7 @@ function InstallDocs({ site }) {
- {`tracker.use(trackerAssist(options));`} +
Read more about available options
- {installationCommand} +
@@ -100,7 +99,7 @@ function MobileInstallDocs({ site, ingestPoint }: any) {
- {_usageCode} +
@@ -112,7 +111,7 @@ function MobileInstallDocs({ site, ingestPoint }: any) {
- {configuration} +
By default, all options equals true
@@ -128,7 +127,7 @@ function MobileInstallDocs({ site, ingestPoint }: any) {
- {touches} +
@@ -143,7 +142,7 @@ function MobileInstallDocs({ site, ingestPoint }: any) {
- {sensitive} +
@@ -158,7 +157,7 @@ function MobileInstallDocs({ site, ingestPoint }: any) {
- {inputs} +
diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js index 9323c637b..e638ad339 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js @@ -22,20 +22,8 @@ class EventGroupWrapper extends React.Component { this.props.toggleLoadInfo(); }; - componentDidUpdate(prevProps) { - if ( - prevProps.showLoadInfo !== this.props.showLoadInfo || - prevProps.query !== this.props.query || - prevProps.event.timestamp !== this.props.event.timestamp || - prevProps.isNote !== this.props.isNote - ) { - this.props.mesureHeight(); - } - } - componentDidMount() { this.props.toggleLoadInfo(this.props.isFirst); - this.props.mesureHeight(); } onEventClick = (e) => this.props.onEventClick(e, this.props.event); diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 312a9b673..e30d55e36 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -1,20 +1,21 @@ +import Session, { mergeEventLists, sortEvents } from 'Types/session'; +import { TYPES } from 'Types/session/event'; +import { InjectedEvent } from 'Types/session/event'; +import cn from 'classnames'; +import { observer } from 'mobx-react-lite'; import React from 'react'; import { connect } from 'react-redux'; -import cn from 'classnames'; -import { Icon } from 'UI'; -import { List, AutoSizer, CellMeasurer } from 'react-virtualized'; -import { TYPES } from 'Types/session/event'; -import { setEventFilter, filterOutNote } from 'Duck/sessions'; -import EventGroupWrapper from './EventGroupWrapper'; -import styles from './eventsBlock.module.css'; -import EventSearch from './EventSearch/EventSearch'; +import { VList, VListHandle } from 'virtua'; + import { PlayerContext } from 'App/components/Session/playerContext'; -import { observer } from 'mobx-react-lite'; import { RootStore } from 'App/duck'; -import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'; -import { InjectedEvent } from 'Types/session/event'; -import Session, { mergeEventLists, sortEvents } from 'Types/session'; import { useStore } from 'App/mstore'; +import { filterOutNote, setEventFilter } from 'Duck/sessions'; +import { Icon } from 'UI'; + +import EventGroupWrapper from './EventGroupWrapper'; +import EventSearch from './EventSearch/EventSearch'; +import styles from './eventsBlock.module.css'; interface IProps { setEventFilter: (filter: { query: string }) => void; @@ -33,16 +34,18 @@ interface IProps { function EventsBlock(props: IProps) { const { notesStore, uxtestingStore } = useStore(); - const [mouseOver, setMouseOver] = React.useState(true); - const scroller = React.useRef(null); - const cache = useCellMeasurerCache({ - fixedWidth: true, - defaultHeight: 300, - }); + const [mouseOver, setMouseOver] = React.useState(false); + const scroller = React.useRef(null); const { store, player } = React.useContext(PlayerContext); - const { time, endTime, playing, tabStates, tabChangeEvents = [] } = store.get(); + const { + time, + endTime, + playing, + tabStates, + tabChangeEvents = [], + } = store.get(); const { filteredEvents, @@ -99,48 +102,50 @@ function EventsBlock(props: IProps) { props.zoomStartTs, props.zoomEndTs, ]); - const findLastFitting = React.useCallback((time: number) => { - if (!usedEvents.length) return 0; - let i = usedEvents.length - 1; - if (time > endTime / 2) { - while (i >= 0) { - const event = usedEvents[i]; - if ('time' in event && event.time <= time) break; - i--; + const findLastFitting = React.useCallback( + (time: number) => { + if (!usedEvents.length) return 0; + let i = usedEvents.length - 1; + if (time > endTime / 2) { + while (i >= 0) { + const event = usedEvents[i]; + if ('time' in event && event.time <= time) break; + i--; + } + return i; + } else { + let l = 0; + while (l < i) { + const event = usedEvents[l]; + if ('time' in event && event.time >= time) break; + l++; + } + return l; } - return i; - } else { - let l = 0; - while (l < i) { - const event = usedEvents[l]; - if ('time' in event && event.time >= time) break; - l++; - } - return l; - } - }, [usedEvents, time, endTime]); - const currentTimeEventIndex = findLastFitting(time) + }, + [usedEvents, time, endTime] + ); + const currentTimeEventIndex = findLastFitting(time); - const write = ({ target: { value } }: React.ChangeEvent) => { + const write = ({ + target: { value }, + }: React.ChangeEvent) => { props.setEventFilter({ query: value }); setTimeout(() => { if (!scroller.current) return; - scroller.current.scrollToRow(0); + scroller.current.scrollToIndex(0); }, 100); }; const clearSearch = () => { props.setEventFilter({ query: '' }); - if (scroller.current) { - scroller.current.forceUpdateGrid(); - } setTimeout(() => { if (!scroller.current) return; - scroller.current.scrollToRow(0); + scroller.current.scrollToIndex(0); }, 100); }; @@ -151,9 +156,9 @@ function EventsBlock(props: IProps) { }, []); React.useEffect(() => { if (scroller.current) { - scroller.current.forceUpdateGrid(); if (!mouseOver) { - scroller.current.scrollToRow(currentTimeEventIndex); + console.log('scrolling to index', currentTimeEventIndex, scroller.current); + scroller.current.scrollToIndex(currentTimeEventIndex, { align: 'center' }); } } }, [currentTimeEventIndex]); @@ -167,45 +172,33 @@ function EventsBlock(props: IProps) { const renderGroup = ({ index, - key, - style, - parent, }: { index: number; - key: string; - style: React.CSSProperties; - parent: any; }) => { const isLastEvent = index === usedEvents.length - 1; - const isLastInGroup = isLastEvent || usedEvents[index + 1]?.type === TYPES.LOCATION; + const isLastInGroup = + isLastEvent || usedEvents[index + 1]?.type === TYPES.LOCATION; const event = usedEvents[index]; const isNote = 'noteId' in event; const isTabChange = 'type' in event && event.type === 'TABCHANGE'; const isCurrent = index === currentTimeEventIndex; const isPrev = index < currentTimeEventIndex; return ( - - {({ measure, registerChild }) => ( -
- -
- )} -
+ ); }; @@ -226,7 +219,11 @@ function EventsBlock(props: IProps) { width={240} />
No video @@ -234,7 +231,11 @@ function EventsBlock(props: IProps) {
) : null}
- +
{eventsText}
@@ -251,23 +252,16 @@ function EventsBlock(props: IProps) { No Matching Results
)} - - {({ height }) => ( - + + {usedEvents.map((_, i) => { + return renderGroup({ index: i }) + } )} - +
); diff --git a/frontend/app/components/Session_/Issues/ContentRender.js b/frontend/app/components/Session_/Issues/ContentRender.js index e7f715abe..c09b0b517 100644 --- a/frontend/app/components/Session_/Issues/ContentRender.js +++ b/frontend/app/components/Session_/Issues/ContentRender.js @@ -1,67 +1,83 @@ import React from 'react'; + +import { CodeBlock } from 'UI'; + import stl from './contentRender.module.css'; -import Highlight from 'react-highlight' const elType = { - PARAGRAPH: 'paragraph', - TEXT: 'text', - QUOTE: 'blockquote', - CODE_BLOCK: 'codeBlock', - MENTION: 'mention', - RULE: 'rule', - HARD_BREAK: 'hardBreak', -} + PARAGRAPH: 'paragraph', + TEXT: 'text', + QUOTE: 'blockquote', + CODE_BLOCK: 'codeBlock', + MENTION: 'mention', + RULE: 'rule', + HARD_BREAK: 'hardBreak', +}; const renderElement = (el, provider) => { - if (provider === 'github') - return el - - switch(el.type) { - case elType.PARAGRAPH: - return

; - case elType.QUOTE: - return
; - case elType.CODE_BLOCK: - return { codeRender(el.content)[0] }; - // return - case elType.MENTION: - return { `@${el.attrs.text}` }; - case elType.RULE: - return
- case elType.HARD_BREAK: - return
- case elType.RULE: - return
- case elType.TEXT: - return el.text; - } - return ; -} + if (provider === 'github') return el; -const codeRender = (content) => content.map(el => el.text); + switch (el.type) { + case elType.PARAGRAPH: + return ( +

+ +

+ ); + case elType.QUOTE: + return ( +
+ +
+ ); + case elType.CODE_BLOCK: + return ( + + ); + // return + case elType.MENTION: + return {`@${el.attrs.text}`}; + case elType.RULE: + return
; + case elType.HARD_BREAK: + return
; + case elType.RULE: + return
; + case elType.TEXT: + return el.text; + } + return ; +}; -const ContentRender = props => { - const { message, provider } = props; - return ( - - { provider === 'github' ? message : - message && message.content && message.content.map(el => ( - { renderElement(el, provider) } - )) - } - - ); +const codeRender = (content) => content.map((el) => el.text); + +const ContentRender = (props) => { + const { message, provider } = props; + return ( + + {provider === 'github' + ? message + : message && + message.content && + message.content.map((el) => ( + {renderElement(el, provider)} + ))} + + ); }; export default ContentRender; diff --git a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js index 0e1ea0747..d84c5c9f4 100644 --- a/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js +++ b/frontend/app/components/Session_/OverviewPanel/components/StackEventModal/components/Sentry/Sentry.js @@ -3,65 +3,80 @@ import { getIn, get } from 'immutable'; import cn from 'classnames'; import { withRequest } from 'HOCs'; import { Loader, Icon, JSONTree } from 'UI'; -import { Accordion } from 'semantic-ui-react'; +import { Collapse } from 'antd'; import stl from './sentry.module.css'; +const { Panel } = Collapse; + @withRequest({ - endpoint: (props) => `/integrations/sentry/events/${props.event.id}`, - dataName: 'detailedEvent', - loadOnInitialize: true, + endpoint: props => `/integrations/sentry/events/${props.event.id}`, + dataName: "detailedEvent", + loadOnInitialize: true }) export default class SentryEventInfo extends React.PureComponent { + makePanelsFromStackTrace(stacktrace) { return get(stacktrace, 'frames', []).map(({ filename, function: method, lineNo, context = [] }) => ({ key: `${filename}_${method}_${lineNo}`, - title: { - content: ( - - {filename} - {' in '} - {method} - {' at line '} - {lineNo} - - ), - }, - content: { - content: ( -
    - {context.map(([ctxLineNo, codeText]) => ( -
  1. {codeText}
  2. - ))} -
- ), - }, + header: ( + + {filename} + {' in '} + {method} + {' at line '} + {lineNo} + + ), + content: ( +
    + {context.map(([ctxLineNo, codeText]) => ( +
  1. + {codeText} +
  2. + ))} +
+ ) })); } renderBody() { const { detailedEvent, requestError, event } = this.props; - const exceptionEntry = get(detailedEvent, ['entries'], []).find(({ type }) => type === 'exception'); + const exceptionEntry = get(detailedEvent, ['entries'], []).find(({ type }) => type === "exception"); const stacktraces = getIn(exceptionEntry, ['data', 'values']); if (!stacktraces) { - return ; + return } return stacktraces.map(({ type, value, stacktrace }) => ( -
-
{type}
-

{value}

- -
+
+
{type}
+

+ {value} +

+ + {this.makePanelsFromStackTrace(stacktrace).map(panel => ( + + {panel.content} + + ))} + +
)); } render() { - const { open, toggleOpen, loading } = this.props; + const { + open, + toggleOpen, + loading, + } = this.props; return ( -
- - {this.renderBody()} -
+
+ + + {this.renderBody()} + +
); } } diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx index 39de770b7..5d6e5da2a 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx @@ -2,7 +2,7 @@ import React from 'react'; import stl from './SelectorCard.module.css'; import cn from 'classnames'; import type { MarkedTarget } from 'Player'; -import { Tooltip } from 'react-tippy'; +import { Tooltip } from 'antd' import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { diff --git a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx index 1146637cf..10f4595ee 100644 --- a/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx +++ b/frontend/app/components/Session_/ScreenRecorder/ScreenRecorder.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { screenRecorder } from 'App/utils/screenRecorder'; -import { Tooltip } from 'react-tippy'; +import { Tooltip } from 'antd' import { connect } from 'react-redux'; import { Button } from 'UI'; import { SessionRecordingStatus } from 'Player'; diff --git a/frontend/app/components/Session_/StackEvents/UserEvent/Sentry.js b/frontend/app/components/Session_/StackEvents/UserEvent/Sentry.js index 873b9a78f..2bb0e2acb 100644 --- a/frontend/app/components/Session_/StackEvents/UserEvent/Sentry.js +++ b/frontend/app/components/Session_/StackEvents/UserEvent/Sentry.js @@ -3,11 +3,13 @@ import { getIn, get } from 'immutable'; import cn from 'classnames'; import { withRequest } from 'HOCs'; import { Loader, Icon, JSONTree } from 'UI'; -import { Accordion } from 'semantic-ui-react' +import { Collapse } from 'antd'; import stl from './sentry.module.css'; +const { Panel } = Collapse; + @withRequest({ - endpoint: props => `/integrations/sentry/events/${ props.event.id }`, + endpoint: props => `/integrations/sentry/events/${props.event.id}`, dataName: "detailedEvent", loadOnInitialize: true }) @@ -15,28 +17,25 @@ export default class SentryEventInfo extends React.PureComponent { makePanelsFromStackTrace(stacktrace) { return get(stacktrace, 'frames', []).map(({ filename, function: method, lineNo, context = [] }) => ({ - key: `${ filename }_${ method }_${ lineNo }`, - title: { - content: ( - - { filename } - { ' in ' } - { method } - { ' at line '} - { lineNo } - - ), - }, - content: { - content: ( -
    - { context.map(([ ctxLineNo, codeText ]) => ( -
  1. - { codeText } -
  2. - ))} -
- )} + key: `${filename}_${method}_${lineNo}`, + header: ( + + {filename} + {' in '} + {method} + {' at line '} + {lineNo} + + ), + content: ( +
    + {context.map(([ctxLineNo, codeText]) => ( +
  1. + {codeText} +
  2. + ))} +
+ ) })); } @@ -46,32 +45,38 @@ export default class SentryEventInfo extends React.PureComponent { const exceptionEntry = get(detailedEvent, ['entries'], []).find(({ type }) => type === "exception"); const stacktraces = getIn(exceptionEntry, ['data', 'values']); if (!stacktraces) { - return + return } return stacktraces.map(({ type, value, stacktrace }) => ( -
-
{ type }
+
+
{type}

- { value } + {value}

- + + {this.makePanelsFromStackTrace(stacktrace).map(panel => ( + + {panel.content} + + ))} +
)); } render() { - const { + const { open, toggleOpen, loading, } = this.props; return ( -
- - - { this.renderBody() } +
+ + + {this.renderBody()}
); } -} \ No newline at end of file +} diff --git a/frontend/app/components/Session_/TimeTable/TimeTable.tsx b/frontend/app/components/Session_/TimeTable/TimeTable.tsx index f4c619d56..86a55055a 100644 --- a/frontend/app/components/Session_/TimeTable/TimeTable.tsx +++ b/frontend/app/components/Session_/TimeTable/TimeTable.tsx @@ -1,14 +1,16 @@ -import React from 'react'; -import { List, AutoSizer } from 'react-virtualized'; import cn from 'classnames'; -import { Duration } from "luxon"; -import { NoContent, Button } from 'UI'; -import { percentOf } from 'App/utils'; +import { Duration } from 'luxon'; +import React from 'react'; +import { VList, VListHandle } from 'virtua'; +import { percentOf } from 'App/utils'; +import { Button, NoContent } from 'UI'; + +import autoscrollStl from '../autoscroll.module.css'; import BarRow from './BarRow'; import stl from './timeTable.module.css'; -import autoscrollStl from '../autoscroll.module.css'; //aaa +//aaa type Timed = { time: number; @@ -24,7 +26,8 @@ type CanBeRed = { }; interface Row extends Timed, Durationed, CanBeRed { - [key: string]: any, key: string + [key: string]: any; + key: string; } type Line = { @@ -37,7 +40,7 @@ type Column = { label: string; width: number; dataKey?: string; - render?: (row: any) => void + render?: (row: any) => void; referenceLines?: Array; style?: React.CSSProperties; } & RenderOrKey; @@ -49,25 +52,25 @@ type Column = { // } type RenderOrKey = | { - render?: (row: Row) => React.ReactNode; - key?: string; - } + render?: (row: Row) => React.ReactNode; + key?: string; + } | { - dataKey: string; - }; + dataKey: string; + }; type Props = { className?: string; rows: Array; children: Array; - tableHeight?: number - activeIndex?: number - renderPopup?: boolean - navigation?: boolean - referenceLines?: any[] - additionalHeight?: number - hoverable?: boolean - onRowClick?: (row: any, index: number) => void + tableHeight?: number; + activeIndex?: number; + renderPopup?: boolean; + navigation?: boolean; + referenceLines?: any[]; + additionalHeight?: number; + hoverable?: boolean; + onRowClick?: (row: any, index: number) => void; }; type TimeLineInfo = { @@ -86,15 +89,26 @@ const TIME_SECTIONS_COUNT = 8; const ZERO_TIMEWIDTH = 1000; function formatTime(ms: number) { if (ms < 0) return ''; - if (ms < 1000) return Duration.fromMillis(ms).toFormat('0.SSS') + if (ms < 1000) return Duration.fromMillis(ms).toFormat('0.SSS'); return Duration.fromMillis(ms).toFormat('mm:ss'); } -function computeTimeLine(rows: Array, firstVisibleRowIndex: number, visibleCount: number): TimeLineInfo { - const visibleRows = rows.slice(firstVisibleRowIndex, firstVisibleRowIndex + visibleCount + _additionalHeight); - let timestart = visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0; +function computeTimeLine( + rows: Array, + firstVisibleRowIndex: number, + visibleCount: number +): TimeLineInfo { + const visibleRows = rows.slice( + firstVisibleRowIndex, + firstVisibleRowIndex + visibleCount + _additionalHeight + ); + let timestart = + visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0; // TODO: GraphQL requests do not have a duration, so their timeline is borked. Assume a duration of 0.2s for every GraphQL request - const timeend = visibleRows.length > 0 ? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200))) : 0; + const timeend = + visibleRows.length > 0 + ? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200))) + : 0; let timewidth = timeend - timestart; const offset = timewidth / 70; if (timestart >= offset) { @@ -116,7 +130,11 @@ const initialState = { export default class TimeTable extends React.PureComponent { state = { - ...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount), + ...computeTimeLine( + this.props.rows, + initialState.firstVisibleRowIndex, + this.visibleCount + ), ...initialState, }; @@ -128,47 +146,70 @@ export default class TimeTable extends React.PureComponent { return Math.ceil(this.tableHeight / ROW_HEIGHT); } - scroller = React.createRef(); + scroller = React.createRef(); autoScroll = true; componentDidMount() { if (this.scroller.current) { - this.scroller.current.scrollToRow(this.props.activeIndex); + this.scroller.current.scrollToIndex(this.props.activeIndex); } } componentDidUpdate(prevProps: any, prevState: any) { if ( prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex || - (this.props.rows.length <= this.visibleCount + _additionalHeight && prevProps.rows.length !== this.props.rows.length) + (this.props.rows.length <= this.visibleCount + _additionalHeight && + prevProps.rows.length !== this.props.rows.length) ) { this.setState({ - ...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount), + ...computeTimeLine( + this.props.rows, + this.state.firstVisibleRowIndex, + this.visibleCount + ), }); } - if (this.props.activeIndex && this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current) { - this.scroller.current.scrollToRow(this.props.activeIndex); + if ( + this.props.activeIndex && + this.props.activeIndex >= 0 && + prevProps.activeIndex !== this.props.activeIndex && + this.scroller.current + ) { + this.scroller.current.scrollToIndex(this.props.activeIndex); } } - onScroll = ({ scrollTop, scrollHeight, clientHeight }: { scrollTop: number; scrollHeight: number; clientHeight: number }): void => { + onScroll = ({ + scrollTop, + scrollHeight, + clientHeight, + }: { + scrollTop: number; + scrollHeight: number; + clientHeight: number; + }): void => { const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33); if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) { - this.autoScroll = scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2; + this.autoScroll = + scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2; this.setState({ firstVisibleRowIndex }); } }; - renderRow = ({ index, key, style: rowStyle }: any) => { + renderRow = (index: number) => { const { activeIndex } = this.props; - const { children: columns, rows, renderPopup, hoverable, onRowClick } = this.props; + const { + children: columns, + rows, + renderPopup, + hoverable, + onRowClick, + } = this.props; const { timestart, timewidth } = this.state; const row = rows[index]; return (
{ [stl.activeRow]: activeIndex === index, // [stl.inactiveRow]: !activeIndex || index > activeIndex, })} - onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined} + onClick={ + typeof onRowClick === 'function' + ? () => onRowClick(row, index) + : undefined + } id="table-row" > {columns.map((column, key) => ( -
- {column.render ? column.render(row) : row[column.dataKey || ''] || {'empty'}} +
+ {column.render + ? column.render(row) + : row[column.dataKey || ''] || ( + {'empty'} + )}
))}
- +
); @@ -200,25 +258,37 @@ export default class TimeTable extends React.PureComponent { } } if (this.scroller.current != null) { - this.scroller.current.scrollToRow(prevRedIndex); + this.scroller.current.scrollToIndex(prevRedIndex); } }; onNextClick = () => { let prevRedIndex = -1; - for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) { + for ( + let i = this.state.firstVisibleRowIndex + 1; + i < this.props.rows.length; + i++ + ) { if (this.props.rows[i].isRed) { prevRedIndex = i; break; } } if (this.scroller.current != null) { - this.scroller.current.scrollToRow(prevRedIndex); + this.scroller.current.scrollToIndex(prevRedIndex); } }; render() { - const { className, rows, children: columns, navigation = false, referenceLines = [], additionalHeight = 0, activeIndex } = this.props; + const { + className, + rows, + children: columns, + navigation = false, + referenceLines = [], + additionalHeight = 0, + activeIndex, + } = this.props; const { timewidth, timestart } = this.state; _additionalHeight = additionalHeight; @@ -231,7 +301,9 @@ export default class TimeTable extends React.PureComponent { } } - const visibleRefLines = referenceLines.filter(({ time }) => time > timestart && time < timestart + timewidth); + const visibleRefLines = referenceLines.filter( + ({ time }) => time > timestart && time < timestart + timewidth + ); const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0); @@ -262,7 +334,11 @@ export default class TimeTable extends React.PureComponent {
{columns.map(({ label, width }) => ( -
+
{label}
))} @@ -278,42 +354,37 @@ export default class TimeTable extends React.PureComponent {
-
+
{timeColumns.map((_, index) => (
))} {visibleRefLines.map((line, key) => (
))}
- - {({ width }: { width: number }) => ( - - )} - + + {this.props.rows.map((_, i) => this.renderRow(i))} +
); } -} \ No newline at end of file +} diff --git a/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx b/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx index 6c7aa4404..2bbf6aa49 100644 --- a/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx +++ b/frontend/app/components/Spots/SpotPlayer/components/Panels/SpotConsole.tsx @@ -1,8 +1,7 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; -import { AutoSizer, CellMeasurer, List } from 'react-virtualized'; +import { VList, VListHandle } from 'virtua'; -import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'; import BottomBlock from 'Components/shared/DevTools/BottomBlock'; import { TABS, @@ -16,13 +15,11 @@ import spotPlayerStore from '../../spotPlayerStore'; function SpotConsole({ onClose }: { onClose: () => void }) { const [activeTab, setActiveTab] = React.useState(TABS[0]); - const _list = React.useRef(null); - const cache = useCellMeasurerCache(); + const _list = React.useRef(null); const onTabClick = (tab: any) => { setActiveTab(tab); }; const logs = spotPlayerStore.logs; - console.log(logs) const filteredList = React.useMemo(() => { return logs.filter((log) => { const tabType = activeTab.text.toLowerCase(); @@ -30,36 +27,10 @@ function SpotConsole({ onClose }: { onClose: () => void }) { return tabType.includes(log.level); }); }, [activeTab]); + const jump = (t: number) => { spotPlayerStore.setTime(t); }; - const _rowRenderer = ({ index, key, parent, style }: any) => { - const item = filteredList[index]; - - return ( - // @ts-ignore - - {({ measure, registerChild }) => ( - // @ts-ignore -
- -
- )} -
- ); - }; return ( @@ -85,22 +56,21 @@ function SpotConsole({ onClose }: { onClose: () => void }) { size="small" show={filteredList.length === 0} > - - {({ height, width }: any) => ( - + {filteredList.map((log, index) => ( + - )} - + ))} + diff --git a/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx b/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx index 84c98f63a..2b44f4eb4 100644 --- a/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx +++ b/frontend/app/components/Spots/SpotPlayer/components/SpotVideoContainer.tsx @@ -1,4 +1,3 @@ -import Hls from 'hls.js'; import { observer } from 'mobx-react-lite'; import React from 'react'; @@ -34,69 +33,71 @@ function SpotVideoContainer({ const hlsRef = React.useRef(null); React.useEffect(() => { - if (Hls.isSupported() && videoRef.current) { - videoRef.current.addEventListener('loadeddata', () => { - setLoaded(true); - }); - if (streamFile) { - const hls = new Hls({ - enableWorker: false, - // workerPath: '/hls-worker.js', - // 1MB buffer -- we have small videos anyways - maxBufferSize: 1000 * 1000, - }); - const url = URL.createObjectURL(base64toblob(streamFile)); - if (url && videoRef.current) { - hls.loadSource(url); - hls.attachMedia(videoRef.current); - if (spotPlayerStore.isPlaying) { - void videoRef.current.play(); - } - hlsRef.current = hls; - } else { - if (videoRef.current) { - videoRef.current.src = videoURL; - if (spotPlayerStore.isPlaying) { - void videoRef.current.play(); - } - } - } - } else { - const check = () => { - fetch(videoLink).then((r) => { - if (r.ok && r.status === 200) { - if (videoRef.current) { - videoRef.current.src = ''; - setTimeout(() => { - videoRef.current!.src = videoURL; - }, 0); - } - - return true; - } else { - setTimeout(() => { - check(); - }, 1000); - } - }); - }; - check(); - videoRef.current.src = videoURL; - if (spotPlayerStore.isPlaying) { - void videoRef.current.play(); - } - } - } else { - if (videoRef.current) { + import('hls.js').then(({ default: Hls }) => { + if (Hls.isSupported() && videoRef.current) { videoRef.current.addEventListener('loadeddata', () => { setLoaded(true); }); - videoRef.current.src = videoURL; - if (spotPlayerStore.isPlaying) { - void videoRef.current.play(); + if (streamFile) { + const hls = new Hls({ + enableWorker: false, + // workerPath: '/hls-worker.js', + // 1MB buffer -- we have small videos anyways + maxBufferSize: 1000 * 1000, + }); + const url = URL.createObjectURL(base64toblob(streamFile)); + if (url && videoRef.current) { + hls.loadSource(url); + hls.attachMedia(videoRef.current); + if (spotPlayerStore.isPlaying) { + void videoRef.current.play(); + } + hlsRef.current = hls; + } else { + if (videoRef.current) { + videoRef.current.src = videoURL; + if (spotPlayerStore.isPlaying) { + void videoRef.current.play(); + } + } + } + } else { + const check = () => { + fetch(videoLink).then((r) => { + if (r.ok && r.status === 200) { + if (videoRef.current) { + videoRef.current.src = ''; + setTimeout(() => { + videoRef.current!.src = videoURL; + }, 0); + } + + return true; + } else { + setTimeout(() => { + check(); + }, 1000); + } + }); + }; + check(); + videoRef.current.src = videoURL; + if (spotPlayerStore.isPlaying) { + void videoRef.current.play(); + } + } + } else { + if (videoRef.current) { + videoRef.current.addEventListener('loadeddata', () => { + setLoaded(true); + }); + videoRef.current.src = videoURL; + if (spotPlayerStore.isPlaying) { + void videoRef.current.play(); + } } } - } + }); return () => { hlsRef.current?.destroy(); }; @@ -125,8 +126,8 @@ function SpotVideoContainer({ }, 100); if (videoRef.current) { videoRef.current.addEventListener('ended', () => { - spotPlayerStore.onComplete() - }) + spotPlayerStore.onComplete(); + }); } return () => clearInterval(int); }, []); diff --git a/frontend/app/components/hocs/withReport.tsx b/frontend/app/components/hocs/withReport.tsx index bbe34bc33..ce33182a1 100644 --- a/frontend/app/components/hocs/withReport.tsx +++ b/frontend/app/components/hocs/withReport.tsx @@ -1,6 +1,5 @@ import React, { useEffect } from 'react'; import { convertElementToImage } from 'App/utils'; -import { jsPDF } from 'jspdf'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import { connect } from 'react-redux'; @@ -50,90 +49,92 @@ export default function withReport

(WrappedComponent: React.Comp const processReport = () => { document.body.scrollIntoView(); - const doc = new jsPDF('p', 'mm', 'a4'); - const now = new Date().toISOString(); + import('jspdf').then(({ jsPDF }) => { + const doc = new jsPDF('p', 'mm', 'a4'); + const now = new Date().toISOString(); - doc.addMetadata('Author', 'OpenReplay'); - doc.addMetadata('Title', 'OpenReplay Report'); - doc.addMetadata('Subject', 'OpenReplay Report'); - doc.addMetadata('Keywords', 'OpenReplay Report'); - doc.addMetadata('Creator', 'OpenReplay'); - doc.addMetadata('Producer', 'OpenReplay'); - doc.addMetadata('CreationDate', now); + doc.addMetadata('Author', 'OpenReplay'); + doc.addMetadata('Title', 'OpenReplay Report'); + doc.addMetadata('Subject', 'OpenReplay Report'); + doc.addMetadata('Keywords', 'OpenReplay Report'); + doc.addMetadata('Creator', 'OpenReplay'); + doc.addMetadata('Producer', 'OpenReplay'); + doc.addMetadata('CreationDate', now); - const parentElement = document.getElementById('report') as HTMLElement; - const pageHeight = 1200; - const pagesCount = parentElement.offsetHeight / pageHeight; - const pages: Array = []; - for (let i = 0; i < pagesCount; i++) { - const page = document.createElement('div'); - page.classList.add('page'); - page.style.height = `${pageHeight}px`; - page.style.whiteSpace = 'no-wrap !important'; + const parentElement = document.getElementById('report') as HTMLElement; + const pageHeight = 1200; + const pagesCount = parentElement.offsetHeight / pageHeight; + const pages: Array = []; + for (let i = 0; i < pagesCount; i++) { + const page = document.createElement('div'); + page.classList.add('page'); + page.style.height = `${pageHeight}px`; + page.style.whiteSpace = 'no-wrap !important'; - const childrens = Array.from(parentElement.children).filter((child) => { - const rect = child.getBoundingClientRect(); - const parentRect = parentElement.getBoundingClientRect(); - const top = rect.top - parentRect.top; - return top >= i * pageHeight && top < (i + 1) * pageHeight; - }); - if (childrens.length > 0) { - pages.push(childrens); - } - } - - const rportLayer = document.getElementById('report-layer'); - - pages.forEach(async (page, index) => { - const pageDiv = document.createElement('div'); - pageDiv.classList.add( - 'grid', - 'gap-4', - 'grid-cols-4', - 'items-start', - 'pb-10', - 'auto-rows-min', - 'printable-report' - ); - pageDiv.id = `page-${index}`; - pageDiv.style.backgroundColor = '#f6f6f6'; - pageDiv.style.gridAutoRows = 'min-content'; - pageDiv.style.padding = '50px'; - pageDiv.style.height = '490mm'; - - if (index > 0) { - pageDiv.style.paddingTop = '100px'; - } - - if (index === 0) { - const header = document.getElementById('report-header')?.cloneNode(true) as HTMLElement; - header.classList.add('col-span-4'); - header.style.display = 'block'; - pageDiv.appendChild(header); - } - page.forEach((child: any) => { - pageDiv.appendChild(child.cloneNode(true)); - }); - rportLayer?.appendChild(pageDiv); - }); - - setTimeout(async () => { - for (let i = 0; i < pages.length; i++) { - const pageDiv = document.getElementById(`page-${i}`) as HTMLElement; - const pageImage = await convertElementToImage(pageDiv); - doc.addImage(pageImage, 'PNG', 0, 0, 210, 0); - if (i === pages.length - 1) { - addFooters(doc); - doc.save(fileNameFormat(dashboard.name + '_Report_' + Date.now(), '.pdf')); - rportLayer!.innerHTML = ''; - setRendering(false); - toast.dismiss(); - toast.success(TEXT_SUCCESS); - } else { - doc.addPage(); + const childrens = Array.from(parentElement.children).filter((child) => { + const rect = child.getBoundingClientRect(); + const parentRect = parentElement.getBoundingClientRect(); + const top = rect.top - parentRect.top; + return top >= i * pageHeight && top < (i + 1) * pageHeight; + }); + if (childrens.length > 0) { + pages.push(childrens); } } - }, 100); + + const rportLayer = document.getElementById('report-layer'); + + pages.forEach(async (page, index) => { + const pageDiv = document.createElement('div'); + pageDiv.classList.add( + 'grid', + 'gap-4', + 'grid-cols-4', + 'items-start', + 'pb-10', + 'auto-rows-min', + 'printable-report' + ); + pageDiv.id = `page-${index}`; + pageDiv.style.backgroundColor = '#f6f6f6'; + pageDiv.style.gridAutoRows = 'min-content'; + pageDiv.style.padding = '50px'; + pageDiv.style.height = '490mm'; + + if (index > 0) { + pageDiv.style.paddingTop = '100px'; + } + + if (index === 0) { + const header = document.getElementById('report-header')?.cloneNode(true) as HTMLElement; + header.classList.add('col-span-4'); + header.style.display = 'block'; + pageDiv.appendChild(header); + } + page.forEach((child: any) => { + pageDiv.appendChild(child.cloneNode(true)); + }); + rportLayer?.appendChild(pageDiv); + }); + + setTimeout(async () => { + for (let i = 0; i < pages.length; i++) { + const pageDiv = document.getElementById(`page-${i}`) as HTMLElement; + const pageImage = await convertElementToImage(pageDiv); + doc.addImage(pageImage, 'PNG', 0, 0, 210, 0); + if (i === pages.length - 1) { + addFooters(doc); + doc.save(fileNameFormat(dashboard.name + '_Report_' + Date.now(), '.pdf')); + rportLayer!.innerHTML = ''; + setRendering(false); + toast.dismiss(); + toast.success(TEXT_SUCCESS); + } else { + doc.addPage(); + } + } + }, 100); + }) }; return ( @@ -155,9 +156,9 @@ export default function withReport

(WrappedComponent: React.Comp

{dashboard && dashboard.name}
{period && - period.range.start.format('MMM Do YY') + + period.range.start.toFormat('MMM Do YY') + ' - ' + - period.range.end.format('MMM Do YY')} + period.range.end.toFormat('MMM Do YY')}
{dashboard && dashboard.description && ( diff --git a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx index d720f8758..2e56995a3 100644 --- a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx +++ b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { CopyButton } from 'UI'; -import Highlight from 'react-highlight'; +import { CopyButton, CodeBlock } from 'UI'; const inputModeOptions = [ { label: 'Record all inputs', value: 'plain' }, @@ -53,9 +52,7 @@ function CodeSnippet(props: Props) {
- - {codeSnippet} - +
); } diff --git a/frontend/app/components/shared/DatePicker/index.tsx b/frontend/app/components/shared/DatePicker/index.tsx index 93bf874e2..d29e66915 100644 --- a/frontend/app/components/shared/DatePicker/index.tsx +++ b/frontend/app/components/shared/DatePicker/index.tsx @@ -1,152 +1,13 @@ // @ts-nocheck import { DatePicker } from 'antd'; import { PickerTimeProps } from 'antd/es/time-picker'; -import moment from 'moment'; -import type { Moment } from 'moment'; import React from 'react'; +import luxonGenerateConfig from 'rc-picker/lib/generate/luxon'; -const generateConfig = { - // get - getNow: () => moment(), - getFixedDate: (string: string) => moment(string, 'YYYY-MM-DD'), - getEndDate: (date) => { - const clone = date.clone(); - return clone.endOf('month'); - }, - getWeekDay: (date) => { - const clone = date.clone().locale('en_US'); - return clone.weekday() + clone.localeData().firstDayOfWeek(); - }, - getYear: (date) => date.year(), - getMonth: (date) => date.month(), - getDate: (date) => date.date(), - getHour: (date) => date.hour(), - getMinute: (date) => date.minute(), - getSecond: (date) => date.second(), - getMillisecond: (date) => date.millisecond(), - - // set - addYear: (date, diff) => { - const clone = date.clone(); - return clone.add(diff, 'year'); - }, - addMonth: (date, diff) => { - const clone = date.clone(); - return clone.add(diff, 'month'); - }, - addDate: (date, diff) => { - const clone = date.clone(); - return clone.add(diff, 'day'); - }, - setYear: (date, year) => { - const clone = date.clone(); - return clone.year(year); - }, - setMonth: (date, month) => { - const clone = date.clone(); - return clone.month(month); - }, - setDate: (date, num) => { - const clone = date.clone(); - return clone.date(num); - }, - setHour: (date, hour) => { - const clone = date.clone(); - return clone.hour(hour); - }, - setMinute: (date, minute) => { - const clone = date.clone(); - return clone.minute(minute); - }, - setSecond: (date, second) => { - const clone = date.clone(); - return clone.second(second); - }, - setMillisecond: (date, millisecond) => { - const clone = date.clone(); - return clone.millisecond(millisecond); - }, - - // Compare - isAfter: (date1, date2) => date1.isAfter(date2), - isValidate: (date) => date.isValid(), - - locale: { - getWeekFirstDay: (locale) => { - const date = moment().locale(locale); - return date.localeData().firstDayOfWeek(); - }, - getWeekFirstDate: (locale, date) => { - const clone = date.clone(); - const result = clone.locale(locale); - return result.weekday(0); - }, - getWeek: (locale, date) => { - const clone = date.clone(); - const result = clone.locale(locale); - return result.week(); - }, - getShortWeekDays: (locale) => { - const date = moment().locale(locale); - return date.localeData().weekdaysMin(); - }, - getShortMonths: (locale) => { - const date = moment().locale(locale); - return date.localeData().monthsShort(); - }, - format: (locale, date, format) => { - const clone = date.clone(); - const result = clone.locale(locale); - return result.format(format); - }, - parse: (locale, text, formats) => { - const fallbackFormatList: string[] = []; - - for (let i = 0; i < formats.length; i += 1) { - let format = formats[i]; - let formatText = text; - - if (format.includes('wo') || format.includes('Wo')) { - format = format.replace(/wo/g, 'w').replace(/Wo/g, 'W'); - const matchFormat = format.match(/[-YyMmDdHhSsWwGg]+/g); - const matchText = formatText.match(/[-\d]+/g); - - if (matchFormat && matchText) { - format = matchFormat.join(''); - formatText = matchText.join(''); - } else { - fallbackFormatList.push(format.replace(/o/g, '')); - } - } - - const date = moment(formatText, format, locale, true); - if (date.isValid()) { - return date; - } - } - - // Fallback to fuzzy matching, this should always not reach match or need fire a issue - for (let i = 0; i < fallbackFormatList.length; i += 1) { - const date = moment(text, fallbackFormatList[i], locale, false); - - /* istanbul ignore next */ - if (date.isValid()) { - console.error( - 'Not match any format strictly and fallback to fuzzy match. Please help to fire a issue about this.' - ); - return date; - } - } - - return null; - }, - }, -}; - -const CustomPicker = DatePicker.generatePicker(generateConfig); +const CustomPicker = DatePicker.generatePicker(luxonGenerateConfig); export interface TimePickerProps - extends Omit, 'picker'> {} + extends Omit, 'picker'> {} const TimePicker = React.forwardRef((props, ref) => ( diff --git a/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js b/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js index ec84feb61..af13a9ce6 100644 --- a/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js +++ b/frontend/app/components/shared/DateRangeDropdown/DateRangePopup.js @@ -1,113 +1,115 @@ +import { Button } from 'antd'; import React from 'react'; -import DateRangePicker from 'react-daterange-picker' -import { getDateRangeFromValue, getDateRangeLabel, dateRangeValues, CUSTOM_RANGE, moment, DATE_RANGE_VALUES } from 'App/dateRange'; -import { Button } from 'antd' -import { TimePicker } from 'App/components/shared/DatePicker' +import DateRangePicker from '@wojtekmaj/react-daterange-picker'; +import '@wojtekmaj/react-daterange-picker/dist/DateRangePicker.css'; +import 'react-calendar/dist/Calendar.css'; + +import { TimePicker } from 'App/components/shared/DatePicker'; +import { + CUSTOM_RANGE, + DATE_RANGE_VALUES, + dateRangeValues, + getDateRangeFromValue, + getDateRangeLabel, +} from 'App/dateRange'; +import { DateTime, Interval } from 'luxon'; import styles from './dateRangePopup.module.css'; export default class DateRangePopup extends React.PureComponent { state = { - range: this.props.selectedDateRange || moment.range(), + range: this.props.selectedDateRange || Interval.fromDateTimes(DateTime.now(), DateTime.now()), value: null, - } + }; selectCustomRange = (range) => { - range.end.endOf('day'); + console.log(range) + const updatedRange = Interval.fromDateTimes(DateTime.fromJSDate(range[0]), DateTime.fromJSDate(range[1])); this.setState({ - range, + range: updatedRange, value: CUSTOM_RANGE, }); - } + }; - setRangeTimeStart = value => { - if (value.isAfter(this.state.range.end)) { + setRangeTimeStart = (value) => { + if (value > this.state.range.end) { return; } this.setState({ - range: moment.range( - value, - this.state.range.end, - ), + range: Interval.fromDateTimes(value, this.state.range.end), value: CUSTOM_RANGE, }); - } - setRangeTimeEnd = value => { - if (value && value.isBefore(this.state.range.start)) { + }; + + setRangeTimeEnd = (value) => { + if (value && value < this.state.range.start) { return; } this.setState({ - range: moment.range( - this.state.range.start, - value, - ), + range: Interval.fromDateTimes(this.state.range.start, value), value: CUSTOM_RANGE, }); - } + }; selectValue = (value) => { const range = getDateRangeFromValue(value); this.setState({ range, value }); - } + }; - onApply = () => this.props.onApply(this.state.range, this.state.value) + onApply = () => this.props.onApply(this.state.range, this.state.value); render() { const { onCancel, onApply } = this.props; const { range } = this.state; - - const rangeForDisplay = range.clone(); - rangeForDisplay.start.startOf('day'); - rangeForDisplay.end.startOf('day'); - - const selectionRange = { - startDate: new Date(), - endDate: new Date(), - key: 'selection', - } + const rangeForDisplay = [range.start.startOf('day').ts, range.end.startOf('day').ts] return ( -
-
-
- { dateRangeValues.filter(value => value !== CUSTOM_RANGE && value !== DATE_RANGE_VALUES.LAST_30_MINUTES).map(value => ( -
this.selectValue(value) } - > - { getDateRangeLabel(value) } -
- )) - } +
+
+
+ {dateRangeValues + .filter( + (value) => + value !== CUSTOM_RANGE && + value !== DATE_RANGE_VALUES.LAST_30_MINUTES + ) + .map((value) => ( +
this.selectValue(value)}> + {getDateRangeLabel(value)} +
+ ))}
onChange + // numberOfCalendars={2} + // selectionType="range" + // maximumDate={new Date()} + // singleDateRange={true} + onChange={this.selectCustomRange} + shouldCloseCalendar={() => false} + isOpen + maxDate={new Date()} + value={rangeForDisplay} />
- {range.start.format("DD/MM")} + {range.start.toFormat('dd/MM')} - {range.end.format("DD/MM")} + {range.end.toFormat('dd/MM')}
- - + +
diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index bf0ed7113..386ca8de8 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -6,14 +6,13 @@ import cn from 'classnames'; import ConsoleRow from '../ConsoleRow'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; -import { List, CellMeasurer, AutoSizer } from 'react-virtualized'; import { useStore } from 'App/mstore'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import { useModal } from 'App/components/Modal'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; -import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'; import { connect } from 'react-redux'; +import { VList, VListHandle } from "virtua"; const ALL = 'ALL'; const INFO = 'INFO'; @@ -82,6 +81,7 @@ function ConsolePanel({ sessionStore: { devTools }, } = useStore(); + const _list = useRef(null); const filter = devTools[INDEX_KEY].filter; const activeTab = devTools[INDEX_KEY].activeTab; // Why do we need to keep index in the store? if we could get read of it it would simplify the code @@ -115,10 +115,6 @@ function ConsolePanel({ filteredList = useTabListFilterMemo(filteredList, (l) => LEVEL_TAB[l.level], ALL, activeTab); React.useEffect(() => { - setTimeout(() => { - cache.clearAll(); - _list.current?.recomputeRowHeights(); - }, 0); }, [activeTab, filter]); const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); const onFilterChange = ({ target: { value } }: any) => @@ -139,16 +135,13 @@ function ConsolePanel({ timeoutStartAutoscroll(); }; - const _list = useRef(null); // TODO: fix react-virtualized types & encapsulate scrollToRow logic useEffect(() => { if (_list.current) { // @ts-ignore - _list.current.scrollToRow(activeIndex); + _list.current.scrollToIndex(activeIndex); } }, [activeIndex]); - const cache = useCellMeasurerCache(); - const showDetails = (log: any) => { setIsDetailsModalActive(true); showModal(, { @@ -162,27 +155,6 @@ function ConsolePanel({ devTools.update(INDEX_KEY, { index: filteredList.indexOf(log) }); stopAutoscroll(); }; - const _rowRenderer = ({ index, key, parent, style }: any) => { - const item = filteredList[index]; - - return ( - // @ts-ignore - - {({ measure, registerChild }) => ( -
- showDetails(item)} - recalcHeight={measure} - /> -
- )} -
- ); - }; return ( @@ -215,25 +187,17 @@ function ConsolePanel({ size="small" show={filteredList.length === 0} > - {/* @ts-ignore */} - - {({ height, width }: any) => ( - // @ts-ignore - + {filteredList.map((log) => ( + showDetails(log)} /> - )} - + ))} + {/* @ts-ignore */} diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx index d6af1d9b8..83de3bb6b 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/MobileConsolePanel.tsx @@ -6,13 +6,12 @@ import cn from 'classnames'; import ConsoleRow from '../ConsoleRow'; import { IOSPlayerContext, MobilePlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; -import { List, CellMeasurer, AutoSizer } from 'react-virtualized'; +import { VList, VListHandle } from 'virtua'; import { useStore } from 'App/mstore'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import { useModal } from 'App/components/Modal'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; -import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'; const ALL = 'ALL'; const INFO = 'INFO'; @@ -87,12 +86,6 @@ function MobileConsolePanel() { let filteredList = useRegExListFilterMemo(list, (l) => l.value, filter); filteredList = useTabListFilterMemo(filteredList, (l) => LEVEL_TAB[l.level], ALL, activeTab); - React.useEffect(() => { - setTimeout(() => { - cache.clearAll(); - _list.current?.recomputeRowHeights(); - }, 0); - }, [activeTab, filter]); const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); const onFilterChange = ({ target: { value } }: any) => devTools.update(INDEX_KEY, { filter: value }); @@ -112,16 +105,14 @@ function MobileConsolePanel() { timeoutStartAutoscroll(); }; - const _list = useRef(null); // TODO: fix react-virtualized types & encapsulate scrollToRow logic + const _list = useRef(null); // TODO: fix react-virtualized types & encapsulate scrollToRow logic useEffect(() => { if (_list.current) { // @ts-ignore - _list.current.scrollToRow(activeIndex); + _list.current.scrollToIndex(activeIndex); } }, [activeIndex]); - const cache = useCellMeasurerCache(); - const showDetails = (log: any) => { setIsDetailsModalActive(true); showModal(, { @@ -135,27 +126,6 @@ function MobileConsolePanel() { devTools.update(INDEX_KEY, { index: filteredList.indexOf(log) }); stopAutoscroll(); }; - const _rowRenderer = ({ index, key, parent, style }: any) => { - const item = filteredList[index]; - - return ( - // @ts-ignore - - {({ measure, registerChild }) => ( -
- showDetails(item)} - recalcHeight={measure} - /> -
- )} -
- ); - }; return ( - {/* @ts-ignore */}
Console @@ -178,9 +147,7 @@ function MobileConsolePanel() { onChange={onFilterChange} value={filter} /> - {/* @ts-ignore */} - {/* @ts-ignore */} - {/* @ts-ignore */} - - {({ height, width }: any) => ( - // @ts-ignore - + {filteredList.map((log, index) => ( + showDetails(log)} /> - )} - + ))} + - {/* @ts-ignore */} ); diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index f57e4d05c..2a2cbaf21 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import cn from 'classnames'; -import { Icon, TextEllipsis } from 'UI'; +import { Icon } from 'UI'; import JumpButton from 'Shared/DevTools/JumpButton'; interface Props { @@ -9,23 +9,15 @@ interface Props { jump?: any; renderWithNL?: any; style?: any; - recalcHeight?: () => void; onClick?: () => void; } function ConsoleRow(props: Props) { - const { log, iconProps, jump, renderWithNL, style, recalcHeight } = props; + const { log, iconProps, jump, renderWithNL, style } = props; const [expanded, setExpanded] = useState(false); const lines = log.value?.split('\n').filter((l: any) => !!l) || []; const canExpand = lines.length > 1; const clickable = canExpand || !!log.errorId; - React.useEffect(() => { - recalcHeight?.(); - }, [expanded]) - React.useEffect(() => { - recalcHeight?.(); - }, []) - const toggleExpand = () => { setExpanded(!expanded); }; diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index d027f4a35..068a1498f 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -2,7 +2,6 @@ import { Timed } from 'Player'; import React, { useEffect, useMemo, useState } from 'react'; import { observer } from 'mobx-react-lite'; import { Tabs, Input, NoContent, Icon } from 'UI'; -import { List, CellMeasurer, AutoSizer } from 'react-virtualized'; import { PlayerContext, MobilePlayerContext } from 'App/components/Session/playerContext'; import BottomBlock from '../BottomBlock'; import { useModal } from 'App/components/Modal'; @@ -13,8 +12,8 @@ import StackEventRow from 'Shared/DevTools/StackEventRow'; import StackEventModal from '../StackEventModal'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; -import useCellMeasurerCache from 'App/hooks/useCellMeasurerCache'; import { connect } from 'react-redux'; +import { VList, VListHandle } from 'virtua'; const mapNames = (type: string) => { if (type === 'openreplay') return 'OpenReplay'; @@ -153,8 +152,6 @@ function EventsPanel({ timeoutStartAutoscroll(); }; - const cache = useCellMeasurerCache(); - const showDetails = (item: any) => { setIsDetailsModalActive(true); showModal(, { @@ -169,38 +166,13 @@ function EventsPanel({ stopAutoscroll(); }; - const _list = React.useRef(); + const _list = React.useRef(null); useEffect(() => { if (_list.current) { - // @ts-ignore - _list.current.scrollToRow(activeIndex); + _list.current.scrollToIndex(activeIndex); } }, [activeIndex]); - const _rowRenderer = ({ index, key, parent, style }: any) => { - const item = filteredList[index]; - - return ( - // @ts-ignore - - {() => ( - { - stopAutoscroll(); - devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); - jump(item.time); - }} - onClick={() => showDetails(item)} - /> - )} - - ); - }; - return ( @@ -235,21 +207,24 @@ function EventsPanel({ size="small" show={filteredList.length === 0} > - - {({ height, width }: any) => ( - + {filteredList.map((item, index) => ( + { + stopAutoscroll(); + devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); + jump(item.time); + }} + onClick={() => showDetails(item)} /> - )} - + ))} + diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx index 89c381252..2de1f4c60 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { List, AutoSizer } from 'react-virtualized'; +import { VList, VListHandle } from 'virtua'; import cn from 'classnames'; import { Duration } from 'luxon'; import { NoContent, Button } from 'UI'; @@ -140,7 +140,7 @@ export default class TimeTable extends React.PureComponent { return Math.ceil(this.tableHeight / ROW_HEIGHT); } - scroller = React.createRef(); + scroller = React.createRef(); autoScroll = true; adjustScroll(prevActiveIndex: number) { @@ -150,7 +150,7 @@ export default class TimeTable extends React.PureComponent { prevActiveIndex !== this.props.activeIndex && this.scroller.current ) { - this.scroller.current.scrollToRow(this.props.activeIndex); + this.scroller.current.scrollToIndex(this.props.activeIndex); } } @@ -191,15 +191,13 @@ export default class TimeTable extends React.PureComponent { } }; - renderRow = ({ index, key, style: rowStyle }: any) => { + renderRow = (index: number) => { const { activeIndex } = this.props; const { children: columns, rows, renderPopup, hoverable, onRowClick } = this.props; const { timestart, timewidth } = this.state; const row = rows[index]; return (
{ } } if (this.scroller.current != null) { - this.scroller.current.scrollToRow(prevRedIndex); + this.scroller.current.scrollToIndex(prevRedIndex); } }; @@ -253,15 +251,13 @@ export default class TimeTable extends React.PureComponent { } } if (this.scroller.current != null) { - this.scroller.current.scrollToRow(prevRedIndex); + this.scroller.current.scrollToIndex(prevRedIndex); } }; onColumnClick = (dataKey: string, onClick: any) => { if (typeof onClick === 'function') { - // this.scroller.current.scrollToRow(0); onClick(dataKey); - this.scroller.current.forceUpdateGrid(); } }; @@ -359,24 +355,9 @@ export default class TimeTable extends React.PureComponent { /> ))}
- - {({ width }: { width: number }) => ( - - )} - + + {this.props.rows.map((_, index) => this.renderRow(index))} +
diff --git a/frontend/app/components/shared/ErrorsBadge/ErrorsBadge.js b/frontend/app/components/shared/ErrorsBadge/ErrorsBadge.js index 6b4ac0fd2..bd84e0f49 100644 --- a/frontend/app/components/shared/ErrorsBadge/ErrorsBadge.js +++ b/frontend/app/components/shared/ErrorsBadge/ErrorsBadge.js @@ -15,7 +15,7 @@ function ErrorsBadge({ errorsStats = {}, fetchNewErrorsCount, projects }) { useEffect(() => { if (projects.size === 0 || !!intervalId) return; - const params = { startTimestamp: weekRange.start.unix() * 1000, endTimestamp: weekRange.end.unix() * 1000 }; + const params = { startTimestamp: weekRange.start.ts, endTimestamp: weekRange.end.ts }; fetchNewErrorsCount(params) intervalId = setInterval(() => { diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index 017ddab56..3d278e4c9 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -1,14 +1,16 @@ -import React from 'react'; import { DownOutlined } from '@ant-design/icons'; import Period from 'Types/app/period'; -import { Dropdown, Button, Menu, Space, MenuProps } from 'antd'; +import { Dropdown, Button } from 'antd'; import cn from 'classnames'; import { observer } from 'mobx-react-lite'; +import React from 'react'; +import { components } from 'react-select'; import { CUSTOM_RANGE, DATE_RANGE_OPTIONS } from 'App/dateRange'; import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import Select from 'Shared/Select'; interface Props { period: any; @@ -23,7 +25,7 @@ interface Props { [x: string]: any; } -const SelectDateRange: React.FC = (props: Props) => { +function SelectDateRange(props: Props) { const [isCustom, setIsCustom] = React.useState(false); const { right = false, period, disableCustom = false, timezone, useButtonStyle = false } = props; let selectedValue = DATE_RANGE_OPTIONS.find( @@ -33,21 +35,21 @@ const SelectDateRange: React.FC = (props: Props) => { disableCustom ? obj.value !== CUSTOM_RANGE : true ); - const onChange = (e: any) => { - if (e.key === CUSTOM_RANGE) { + const onChange = (value: any) => { + if (value === CUSTOM_RANGE) { setTimeout(() => { setIsCustom(true); }, 1); } else { - props.onChange(Period({ rangeName: e.key })); + props.onChange(new Period({ rangeName: value })); } }; const onApplyDateRange = (value: any) => { - const range = Period({ + const range = new Period({ rangeName: CUSTOM_RANGE, start: value.start, - end: value.end + end: value.end, }); props.onChange(range); setIsCustom(false); @@ -56,27 +58,91 @@ const SelectDateRange: React.FC = (props: Props) => { const isCustomRange = period.rangeName === CUSTOM_RANGE; const customRange = isCustomRange ? period.rangeFormatted() : ''; - const menuItems: MenuProps['items'] = options.map((opt) => ({ - key: opt.value, - label: opt.label, - className: opt.value === selectedValue?.value ? 'ant-select-item-option-selected' : '', - })); + if (props.isAnt) { + const menuProps = { + items: options.map((opt) => ({ + label: opt.label, + key: opt.value, + })), + defaultSelectedKeys: selectedValue?.value ? [selectedValue.value] : undefined, + onClick: (e: any) => { + onChange(e.key); + }, + }; + + return ( +
+ + {useButtonStyle ? ( + + ) : ( +
+ {isCustomRange ? customRange : selectedValue?.label} + +
+ )} +
+ {isCustom && ( + { + if ( + e.target.parentElement.parentElement.classList.contains( + 'rc-time-picker-panel-select' + ) || + e.target.parentElement.parentElement.classList[0]?.includes( + '-menu' + ) || + e.target.className.includes('ant-picker') + ) { + return false; + } + setIsCustom(false); + }} + > +
+ setIsCustom(false)} + selectedDateRange={period.range} + /> +
+
+ )} +
+ ); + } return ( -
- - {useButtonStyle ? ( - - ) : ( -
- {isCustomRange ? customRange : selectedValue?.label} - -
- )} -
+
+