From 5ea745c14b9e96b48c34ba0a58385aab37115bdd Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 21 Aug 2023 12:01:43 +0200 Subject: [PATCH] feat(ui/tracker): display user time in sessions --- frontend/app/components/Session/WebPlayer.tsx | 4 +- .../Session_/Player/Controls/Controls.tsx | 16 ++++- .../Session_/Player/Controls/Time.tsx | 10 +++- .../Session_/Player/Controls/Timeline.tsx | 9 ++- .../Controls/components/PlayerControls.tsx | 20 +++++-- .../Controls/components/PlayingTime.tsx | 58 ++++++++++++++----- .../Controls/components/TimeTooltip.tsx | 23 +++++--- frontend/app/duck/sessions.ts | 2 +- frontend/app/mstore/sessionStore.ts | 9 ++- frontend/app/types/session/session.ts | 2 + tracker/tracker/src/main/app/index.ts | 9 +++ 11 files changed, 122 insertions(+), 40 deletions(-) diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 584b05a5e..907a4f1bc 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -31,7 +31,7 @@ function WebPlayer(props: any) { fullscreen, fetchList, } = props; - const { notesStore } = useStore(); + const { notesStore, sessionStore } = useStore(); const [activeTab, setActiveTab] = useState(''); const [noteItem, setNoteItem] = useState(undefined); const [visuallyAdjusted, setAdjusted] = useState(false); @@ -43,7 +43,7 @@ function WebPlayer(props: any) { playerInst = undefined if (!session.sessionId || contextValue.player !== undefined) return; fetchList('issues'); - + sessionStore.setUserTimezone(session.timezone) const [WebPlayerInst, PlayerStore] = createWebPlayer( session, (state) => makeAutoObservable(state), diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index 7608506b0..dfe693a02 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import { selectStorageType, STORAGE_TYPES, StorageType } from 'Player'; +import {MarkedTarget, selectStorageType, STORAGE_TYPES, StorageType} from 'Player'; import { PlayButton, PlayingState, FullScreenButton } from 'App/player-ui' import { Tooltip } from 'UI'; @@ -84,7 +84,7 @@ function Controls(props: any) { } = props; const disabled = disabledRedux || messagesLoading || inspectorMode || markedTargets; - + const sessionTz = session?.timezone; const onKeyDown = (e: any) => { if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { return; @@ -149,6 +149,7 @@ function Controls(props: any) {
void; + bottomBlock: number; + markedTargets: MarkedTarget[] | null; + messagesLoading: boolean; +} + +function DevtoolsButtons({ showStorageRedux, toggleBottomTools, bottomBlock, disabledRedux, messagesLoading, markedTargets }: IDevtoolsButtons) { const { store } = React.useContext(PlayerContext); const { diff --git a/frontend/app/components/Session_/Player/Controls/Time.tsx b/frontend/app/components/Session_/Player/Controls/Time.tsx index 25c79fc9e..b4247a5b7 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.tsx +++ b/frontend/app/components/Session_/Player/Controls/Time.tsx @@ -26,6 +26,14 @@ const RealReplayTimeConnected: React.FC<{startedAt: number}> = observer(({ start return }) +const RealUserReplayTimeConnected: React.FC<{startedAt: number, sessionTz?: string}> = observer(({ startedAt, sessionTz }) => { + if (!sessionTz) return null; + const { store } = React.useContext(PlayerContext) + const time = store.get().time || 0 + + return +}) + ReduxTime.displayName = "ReduxTime"; -export { ReduxTime, RealReplayTimeConnected }; +export { ReduxTime, RealReplayTimeConnected, RealUserReplayTimeConnected }; diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index 4b64a0735..ba3e2c558 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -22,6 +22,7 @@ interface IProps { setTimelineHoverTime: (t: number) => void startedAt: number tooltipVisible: boolean + timezone?: string } function Timeline(props: IProps) { @@ -36,7 +37,7 @@ function Timeline(props: IProps) { devtoolsLoading, domLoading, } = store.get() - const { issues } = props; + const { issues, timezone } = props; const progressRef = useRef(null) const timelineRef = useRef(null) @@ -86,9 +87,12 @@ function Timeline(props: IProps) { if (!time) return; const tz = settingsStore.sessionSettings.timezone.value const timeStr = DateTime.fromMillis(props.startedAt + time).setZone(tz).toFormat(`hh:mm:ss a`) + const userTimeStr = timezone ? DateTime.fromMillis(props.startedAt + time).setZone(timezone).toFormat(`hh:mm:ss a`) : undefined + const timeLineTooltip = { time: Duration.fromMillis(time).toFormat(`mm:ss`), - timeStr, + localTime: timeStr, + userTime: userTimeStr, offset: e.nativeEvent.pageX, isVisible: true, }; @@ -173,6 +177,7 @@ export default connect( (state: any) => ({ issues: state.getIn(['sessions', 'current']).issues || [], startedAt: state.getIn(['sessions', 'current']).startedAt || 0, + timezone: state.getIn(['sessions', 'current']).timezone, tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), }), { setTimelinePointer, setTimelineHoverTime } diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index 60827520b..792266ef4 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -1,3 +1,4 @@ +import * as constants from "constants"; import React from 'react'; import { Icon, Tooltip, Popover } from 'UI'; import cn from 'classnames'; @@ -21,8 +22,16 @@ interface Props { forthTenSeconds: () => void; toggleSpeed: (speedIndex: number) => void; toggleSkip: () => void; + sessionTz?: string; } +export const TimeMode = { + Real: 'real', + UserReal: 'user_real', + Timestamp: 'current', +} as const +export type ITimeMode = typeof TimeMode[keyof typeof TimeMode] + function PlayerControls(props: Props) { const { skip, @@ -37,17 +46,18 @@ function PlayerControls(props: Props) { setSkipInterval, currentInterval, startedAt, + sessionTz, } = props; const [showTooltip, setShowTooltip] = React.useState(false); - const [isUniTime, setUniTime] = React.useState(localStorage.getItem('__or_player_time_mode') === 'real'); + const [timeMode, setTimeMode] = React.useState(localStorage.getItem('__or_player_time_mode') as ITimeMode); const speedRef = React.useRef(null); const arrowBackRef = React.useRef(null); const arrowForwardRef = React.useRef(null); const skipRef = React.useRef(null); - const setIsUniTime = (isUniTime: boolean) => { - localStorage.setItem('__or_player_time_mode', isUniTime ? 'real' : 'current'); - setUniTime(isUniTime); + const saveTimeMode = (mode: ITimeMode) => { + localStorage.setItem('__or_player_time_mode', mode); + setTimeMode(mode); } React.useEffect(() => { @@ -83,7 +93,7 @@ function PlayerControls(props: Props) {
diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayingTime.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayingTime.tsx index 13d1083d0..f42598e02 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayingTime.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayingTime.tsx @@ -1,14 +1,16 @@ +import {ITimeMode, TimeMode} from "Components/Session_/Player/Controls/components/PlayerControls"; import React from 'react' import { Popover } from 'UI' -import { RealReplayTimeConnected, ReduxTime } from "Components/Session_/Player/Controls/Time"; +import { RealReplayTimeConnected, ReduxTime, RealUserReplayTimeConnected } from "Components/Session_/Player/Controls/Time"; interface Props { - isUniTime: boolean; + timeMode: ITimeMode; startedAt: number; - setIsUniTime: (isUniTime: boolean) => void; + setTimeMode: (mode: ITimeMode) => void; + sessionTz?: string; } -function PlayingTime({ isUniTime, setIsUniTime, startedAt }: Props) { +function PlayingTime({ timeMode, setTimeMode, startedAt, sessionTz }: Props) { return ( ( + render={({ close }) => (
Playback Time Mode
Current / Session Duration
-
{ - setIsUniTime(false); - close(); - }}> +
{ + setTimeMode(TimeMode.Timestamp); + close(); + }} + > /
-
{ - setIsUniTime(true); - close(); - }}> + {sessionTz ? +
{ + setTimeMode(TimeMode.UserReal); + close(); + }} + > +
User's time
+
+ +
+
+ : null} +
{ + setTimeMode(TimeMode.Real); + close(); + }} + >
Based on your settings
-
+
+ +
)} >
- {isUniTime ? ( + {timeMode === TimeMode.Real ? ( + ) : timeMode === TimeMode.UserReal ? ( + ) : ( <> @@ -53,7 +79,7 @@ function PlayingTime({ isUniTime, setIsUniTime, startedAt }: Props) { )}
- ) + ); } export default PlayingTime diff --git a/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx b/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx index 5f746f336..02a6b6ad5 100644 --- a/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx @@ -1,6 +1,4 @@ import React from 'react'; -// @ts-ignore -import { Duration } from 'luxon'; import { connect } from 'react-redux'; import stl from './styles.module.css'; @@ -8,14 +6,16 @@ interface Props { time: number; offset: number; isVisible: boolean; - timeStr: string; + localTime: string; + userTime?: string; } function TimeTooltip({ time, offset, isVisible, - timeStr, + localTime, + userTime }: Props) { return (
{!time ? 'Loading' : time} - {timeStr ? ( + {localTime ? ( <>
- ({timeStr}) + local: {localTime} + + ) : null} + {userTime ? ( + <> +
+ user: {userTime} ) : null}
@@ -41,6 +47,7 @@ function TimeTooltip({ } export default connect((state) => { - const { time = 0, offset = 0, isVisible, timeStr } = state.getIn(['sessions', 'timeLineTooltip']); - return { time, offset, isVisible, timeStr }; + // @ts-ignore + const { time = 0, offset = 0, isVisible, localTime, userTime } = state.getIn(['sessions', 'timeLineTooltip']); + return { time, offset, isVisible, localTime, userTime }; })(TimeTooltip); diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index e1800676f..64d070deb 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -82,7 +82,7 @@ const initObj = { timelinePointer: null, sessionPath: {}, lastPlayedSessionId: null, - timeLineTooltip: { time: 0, offset: 0, isVisible: false, timeStr: '' }, + timeLineTooltip: { time: 0, offset: 0, isVisible: false, localTime: '', userTime: '' }, createNoteTooltip: { time: 0, isVisible: false, isEdit: false, note: null }, fetchFailed: false, } diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index 1a3bc6426..ec52cfbf9 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -120,10 +120,11 @@ export default class SessionStore { timelinePointer = {} sessionPath = {} lastPlayedSessionId: string - timeLineTooltip = { time: 0, offset: 0, isVisible: false, timeStr: '' } + timeLineTooltip = { time: 0, offset: 0, isVisible: false, localTime: '', userTime: '' } createNoteTooltip = { time: 0, isVisible: false, isEdit: false, note: null } previousId = '' nextId = '' + userTimezone = '' constructor() { makeAutoObservable(this, { @@ -132,6 +133,10 @@ export default class SessionStore { }); } + setUserTimezone(timezone: string) { + this.userTimezone = timezone; + } + resetUserFilter() { this.userFilter = new UserFilter(); } @@ -345,7 +350,7 @@ export default class SessionStore { this.timelinePointer = pointer } - setTimelineTooltip(tp: { time: number, offset: number, isVisible: boolean, timeStr: string }) { + setTimelineTooltip(tp: { time: number, offset: number, isVisible: boolean, localTime: string, userTime?: string }) { this.timeLineTooltip = tp } diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index 736702421..0301d54ce 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -118,6 +118,7 @@ export interface ISession { userID: string; userUUID: string; userEvents: any[]; + timezone?: string; } const emptyValues = { @@ -196,6 +197,7 @@ export default class Session { notes: ISession['notes']; notesWithEvents: ISession['notesWithEvents']; frustrations: Array + timezone?: ISession['timezone']; fileKey: ISession['fileKey']; durationSeconds: number; diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index b4c22fc27..b6d9e7e07 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -95,6 +95,14 @@ export type Options = AppOptions & ObserverOptions & SanitizerOptions // TODO: use backendHost only export const DEFAULT_INGEST_POINT = 'https://api.openreplay.com/ingest' +function getTimezone() { + const offset = new Date().getTimezoneOffset() * -1 + const sign = offset >= 0 ? '+' : '-' + const hours = Math.floor(Math.abs(offset) / 60) + const minutes = Math.abs(offset) % 60 + return `UTC${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}` +} + export default class App { readonly nodes: Nodes readonly ticker: Ticker @@ -525,6 +533,7 @@ export default class App { token: isNewSession ? undefined : sessionToken, deviceMemory, jsHeapSizeLimit, + timezone: getTimezone(), }), }) .then((r) => {