This commit is contained in:
nick-delirium 2025-06-03 13:25:39 +02:00
parent 1d9ba3de6d
commit 55bd28e383
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
7 changed files with 129 additions and 23 deletions

View file

@ -18,6 +18,7 @@ import {
LaunchStateShortcut,
LaunchXRaShortcut,
} from 'Components/Session_/Player/Controls/components/KeyboardHelp';
import { signalService } from 'App/services';
import {
CONSOLE,
GRAPHQL,
@ -29,19 +30,21 @@ import {
STACKEVENTS,
STORAGE,
BACKENDLOGS,
LONG_TASK
} from "App/mstore/uiPlayerStore";
} from 'App/mstore/uiPlayerStore';
import { Icon } from 'UI';
import LogsButton from 'App/components/Session/Player/SharedComponents/BackendLogs/LogsButton';
import { CodeOutlined, DashboardOutlined, ClusterOutlined } from '@ant-design/icons';
import { ArrowDownUp, ListCollapse, Merge, Waypoints, Timer } from 'lucide-react'
import {
CodeOutlined,
DashboardOutlined,
ClusterOutlined,
} from '@ant-design/icons';
import { ArrowDownUp, ListCollapse, Merge, Waypoints } from 'lucide-react';
import ControlButton from './ControlButton';
import Timeline from './Timeline';
import PlayerControls from './components/PlayerControls';
import styles from './controls.module.css';
import { useTranslation } from 'react-i18next';
import SummaryButton from './SummaryButton';
export const SKIP_INTERVALS = {
2: 2e3,
@ -56,15 +59,30 @@ export const SKIP_INTERVALS = {
function getStorageName(type: any) {
switch (type) {
case STORAGE_TYPES.REDUX:
return { name: 'Redux', icon: <Icon name='integrations/redux' size={14} /> };
return {
name: 'Redux',
icon: <Icon name="integrations/redux" size={14} />,
};
case STORAGE_TYPES.MOBX:
return { name: 'Mobx', icon: <Icon name='integrations/mobx' size={14} /> };
return {
name: 'Mobx',
icon: <Icon name="integrations/mobx" size={14} />,
};
case STORAGE_TYPES.VUEX:
return { name: 'Vuex', icon: <Icon name='integrations/vuejs' size={14} /> };
return {
name: 'Vuex',
icon: <Icon name="integrations/vuejs" size={14} />,
};
case STORAGE_TYPES.NGRX:
return { name: 'NgRx', icon: <Icon name='integrations/ngrx' size={14} /> };
return {
name: 'NgRx',
icon: <Icon name="integrations/ngrx" size={14} />,
};
case STORAGE_TYPES.ZUSTAND:
return { name: 'Zustand', icon: <Icon name='integrations/zustand' size={14} /> };
return {
name: 'Zustand',
icon: <Icon name="integrations/zustand" size={14} />,
};
case STORAGE_TYPES.NONE:
return { name: 'State', icon: <ClusterOutlined size={14} /> };
default:
@ -81,6 +99,7 @@ function Controls({ setActiveTab, activeTab }: any) {
sessionStore,
userStore,
} = useStore();
const [mounted, setMounted] = React.useState(false);
const permissions = userStore.account.permissions || [];
const disableDevtools =
userStore.isEnterprise &&
@ -115,6 +134,7 @@ function Controls({ setActiveTab, activeTab }: any) {
const disabled =
disableDevtools || messagesLoading || inspectorMode || markedTargets;
const sessionTz = session?.timezone;
const sessionId = session?.sessionId;
const nextHandler = () => {
history.push(withSiteId(sessionRoute(nextSessionId), siteId));
@ -135,19 +155,63 @@ function Controls({ setActiveTab, activeTab }: any) {
disableDevtools,
});
React.useEffect(() => {
setMounted(true);
}, []);
React.useEffect(() => {
if (mounted) {
signalService.send(
{
source: 'speed',
value: speed,
},
sessionId,
);
}
}, [speed]);
const forthTenSeconds = () => {
// @ts-ignore
player.jumpInterval(SKIP_INTERVALS[skipInterval]);
signalService.send(
{
source: 'fast_forward',
},
sessionId,
);
};
const backTenSeconds = () => {
// @ts-ignore
player.jumpInterval(-SKIP_INTERVALS[skipInterval]);
signalService.send(
{
source: 'rewind',
},
sessionId,
);
};
const toggleBottomTools = (blockName: number) => {
player.toggleInspectorMode(false);
toggleBottomBlock(blockName);
signalService.send(
{
source: getBlockLabel(blockName)!,
},
sessionId,
);
};
const togglePlay = () => {
player.togglePlay();
signalService.send(
{
source: playing ? 'pause' : 'play',
},
sessionId,
);
};
const state = completed
@ -175,7 +239,7 @@ function Controls({ setActiveTab, activeTab }: any) {
playButton={
<PlayButton
state={state}
togglePlay={player.togglePlay}
togglePlay={togglePlay}
iconSize={36}
/>
}
@ -265,7 +329,7 @@ const DevtoolsButtons = observer(
const integratedServices =
integrationsStore.integrations.backendLogIntegrations;
const showIcons = activeTab === 'EXPORT'
const showIcons = activeTab === 'EXPORT';
const labels = {
console: {
icon: <CodeOutlined size={14} />,
@ -295,9 +359,10 @@ const DevtoolsButtons = observer(
icon: <Timer size={14} strokeWidth={2} />,
label: t('Long Tasks'),
},
}
};
// @ts-ignore
const getLabel = (block: string) => labels[block][showIcons ? 'icon' : 'label']
const getLabel = (block: string) =>
labels[block][showIcons ? 'icon' : 'label'];
return (
<>
<SummaryButton onClick={showSummary} />
@ -360,14 +425,6 @@ const DevtoolsButtons = observer(
label={getLabel('performance')}
/>
<ControlButton
customKey="longTask"
disabled={disableButtons}
onClick={() => toggleBottomTools(LONG_TASK)}
active={bottomBlock === LONG_TASK && !inspectorMode}
label={getLabel('longTask')}
/>
{showGraphql && (
<ControlButton
disabled={disableButtons}
@ -431,6 +488,37 @@ const DevtoolsButtons = observer(
},
);
export function SummaryButton({
onClick,
withToggle,
onToggle,
toggleValue,
}: {
onClick?: () => void;
withToggle?: boolean;
onToggle?: () => void;
toggleValue?: boolean;
}) {
const { t } = useTranslation();
const [isHovered, setHovered] = React.useState(false);
return (
<div style={gradientButton} onClick={onClick}>
<div
style={isHovered ? onHoverFillStyle : fillStyle}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{withToggle ? (
<Switch size="small" checked={toggleValue} onChange={onToggle} />
) : null}
<Icon name="sparkles" size={16} />
<div className="font-semibold text-main">{t('Summary AI')}</div>
</div>
</div>
);
}
export const gradientButton = {
border: 'double 1px transparent',
borderRadius: '60px',

View file

@ -16,6 +16,7 @@ import {
import stl from './timeline.module.css'
import TooltipContainer from './components/TooltipContainer';
import CustomDragLayer, { OnDragCallback } from './components/CustomDragLayer';
import { signalService } from 'App/services';
function Timeline({ isMobile }: { isMobile: boolean }) {
const { player, store } = useContext(PlayerContext);
@ -32,6 +33,7 @@ function Timeline({ isMobile }: { isMobile: boolean }) {
const highlightEnabled = uiPlayerStore.highlightSelection.enabled;
const { playing, skipToIssue, ready, endTime, devtoolsLoading, domLoading } =
store.get();
const sessionId = sessionStore.current.sessionId;
const progressRef = useRef<HTMLDivElement>(null);
const timelineRef = useRef<HTMLDivElement>(null);
@ -67,6 +69,10 @@ function Timeline({ isMobile }: { isMobile: boolean }) {
const time = Math.max(Math.round(p * endTime), 0);
debouncedJump(time);
hideTimeTooltip();
signalService.send({
source: 'jump',
value: time,
}, sessionId)
if (playing) {
setWasPlaying(true);
player.pause();

View file

@ -5,7 +5,6 @@ import React from 'react';
import { Link2 } from 'lucide-react';
import copy from 'copy-to-clipboard';
import { toast } from 'react-toastify';
import { PlayerContext } from 'App/components/Session/playerContext';
import {
CONSOLE,

View file

@ -0,0 +1,5 @@
function SimilarSessionsButton() {
return null
}
export default SimilarSessionsButton;

View file

@ -27,6 +27,7 @@ import QueueControls from './QueueControls';
import HighlightButton from './Highlight/HighlightButton';
import ShareModal from '../shared/SharePopup/SharePopup';
import { useTranslation } from 'react-i18next';
import SimilarSessionsButton from './SimilarSessions/SimilarSessionsButton';
const disableDevtools = 'or_devtools_uxt_toggle';
@ -167,6 +168,7 @@ function SubHeader(props) {
)}
style={{ width: 'max-content' }}
>
<SimilarSessionsButton />
<Tooltip title={t('Share Session')} placement="bottom">
<AntButton
size="small"

View file

@ -0,0 +1,5 @@
function Survey() {
return null;
}
export default Survey;

View file

@ -0,0 +1 @@
export { default } from './Survey';