From 4444ecf6e06943e6c851adbb9752192799906d44 Mon Sep 17 00:00:00 2001 From: Delirium Date: Wed, 24 Apr 2024 09:39:50 +0200 Subject: [PATCH] Sess header fixes (#2124) * feat ui: cross dead tabs, some ui fixes * ? lost changes? --- .../Player/SharedComponents/SessionTabs.tsx | 10 +- .../Session/Player/SharedComponents/Tab.tsx | 6 +- .../SelectDateRange/SelectDateRange.tsx | 2 +- .../components/Notes/NoteTags.tsx | 102 +++++++++--------- .../components/SessionSort/SessionSort.tsx | 50 ++++++--- .../components/SessionTags/SessionTags.tsx | 6 +- frontend/app/player/web/MessageLoader.ts | 6 +- frontend/app/player/web/MessageManager.ts | 24 +++++ frontend/app/player/web/TabManager.ts | 2 + .../player/web/managers/TabClosingManager.ts | 22 ++++ 10 files changed, 150 insertions(+), 80 deletions(-) create mode 100644 frontend/app/player/web/managers/TabClosingManager.ts diff --git a/frontend/app/components/Session/Player/SharedComponents/SessionTabs.tsx b/frontend/app/components/Session/Player/SharedComponents/SessionTabs.tsx index bc0e3ffab..cb685a87e 100644 --- a/frontend/app/components/Session/Player/SharedComponents/SessionTabs.tsx +++ b/frontend/app/components/Session/Player/SharedComponents/SessionTabs.tsx @@ -38,9 +38,13 @@ function Modal({ tabs, currentTab, changeTab, hideModal }: Props) { function SessionTabs({ isLive }: { isLive?: boolean }) { const { showModal, hideModal } = useModal(); const { player, store } = React.useContext(PlayerContext); - const { tabs = new Set('back-compat'), currentTab } = store.get(); + const { tabs = new Set('back-compat'), currentTab, closedTabs } = store.get(); - const tabsArr = Array.from(tabs).map((tab, idx) => ({ tab, idx })); + const tabsArr = Array.from(tabs).map((tab, idx) => ({ + tab, + idx, + isClosed: closedTabs.includes(tab) + })); const shouldTruncate = tabsArr.length > 10; const actualTabs = shouldTruncate ? tabsArr.slice(0, 10) : tabsArr; @@ -49,6 +53,7 @@ function SessionTabs({ isLive }: { isLive?: boolean }) { ? actualTabs : actualTabs.concat({ tab: currentTab, + isClosed: false, idx: tabsArr.findIndex((tEl) => tEl.tab === currentTab), }); const changeTab = (tab: string) => { @@ -74,6 +79,7 @@ function SessionTabs({ isLive }: { isLive?: boolean }) { currentTab={actualTabs.length === 1 ? tab.tab : currentTab} changeTab={changeTab} isLive={isLive} + isClosed={tab.isClosed} /> ))} diff --git a/frontend/app/components/Session/Player/SharedComponents/Tab.tsx b/frontend/app/components/Session/Player/SharedComponents/Tab.tsx index f0980424a..4d3e0f036 100644 --- a/frontend/app/components/Session/Player/SharedComponents/Tab.tsx +++ b/frontend/app/components/Session/Player/SharedComponents/Tab.tsx @@ -7,9 +7,10 @@ interface Props { currentTab: string; changeTab?: (tab: string) => void; isLive?: boolean; + isClosed?: boolean; } -function Tab({ i, tab, currentTab, changeTab, isLive }: Props) { +function Tab({ i, tab, currentTab, changeTab, isLive, isClosed }: Props) { return (
Tab {i + 1} diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index afde5005d..706f2d0fc 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -84,7 +84,7 @@ function SelectDateRange(props: Props) { defaultValue={selectedValue?.value ?? undefined} >
{isCustomRange ? customRange : selectedValue?.label}
diff --git a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx index 3463b5356..572139c36 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteTags.tsx @@ -1,34 +1,27 @@ -import { DownOutlined } from "@ant-design/icons"; -import { Dropdown, Segmented } from 'antd'; +import { Segmented } from 'antd'; import { observer } from 'mobx-react-lite'; import React from 'react'; - - import { useStore } from 'App/mstore'; import { TAGS, iTag } from 'App/services/NotesService'; - - - -import Select from 'Shared/Select'; - +import { SortDropdown } from '../SessionSort/SessionSort'; const sortOptionsMap = { 'createdAt-DESC': 'Newest', 'createdAt-ASC': 'Oldest', }; -const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label })); +const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ + value, + label, +})); const notesOwner = [ { value: '0', label: 'All Notes' }, { value: '1', label: 'My Notes' }, ]; function toTitleCase(str) { - return str.replace( - /\w\S*/g, - function(txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - } - ); + return str.replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); } function NoteTags() { const { notesStore } = useStore(); @@ -40,55 +33,56 @@ function NoteTags() { options={[ { value: 'ALL', - label: 'All', + label: ( +
+ All +
+ ), }, ...TAGS.map((tag: iTag) => ({ value: tag, - label: toTitleCase(tag), + label: ( +
+ {toTitleCase(tag)} +
+ ), })), ]} onChange={(value: iTag) => notesStore.toggleTag(value)} />
- ({ key: value, label })), - onClick: ({ key }) => { - notesStore.toggleShared(key === '1'); - }, + ({ + key: value, + label, + }))} + onSort={({ key }) => { + notesStore.toggleShared(key === '1'); }} - > -
-
- {notesStore.ownOnly ? notesOwner[1].label : notesOwner[0].label} -
- -
-
+ current={notesStore.ownOnly ? notesOwner[1].label : notesOwner[0].label} + />
- ({ key: value, label })), - onClick: ({ key }) => { - notesStore.toggleSort(key); - }, + ({ + key: value, + label, + }))} + onSort={({ key }) => { + notesStore.toggleSort(key); }} - > -
-
- {notesStore.order === "DESC" ? "Newest" : "Oldest"} -
- -
-
+ current={notesStore.order === 'DESC' ? 'Newest' : 'Oldest'} + />
); } diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx index 5d25232ba..57a258660 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionSort/SessionSort.tsx @@ -26,6 +26,34 @@ interface Props { sort: (sort: string, sign: number) => void; } +export function SortDropdown({ defaultOption, onSort, sortOptions, current }: { + defaultOption?: string, + onSort: ({ key, item }: { key: string, item: T }) => void, + sortOptions: any, + current: string +}) { + + return ( + +
+
{current}
+ +
+
+ ) +} + function SessionSort(props: Props) { const { sort, order } = props.filter; const onSort = ({ key }: { key: string }) => { @@ -38,22 +66,12 @@ function SessionSort(props: Props) { const defaultOption = `${sort}-${order}`; return ( - -
-
{sortOptionsMap[defaultOption]}
- -
-
+ ); } diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx index c81c5cd35..388b92cec 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx @@ -32,11 +32,11 @@ const SessionTags: React.FC = memo(({ activeTab, tags, total, setActiveTa label:
{tag.icon ? : null} -
{tag.name}
+
{tag.name}
, value: tag.type, disabled: disable && tag.type !== 'all', diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts index 7413a985d..35dbcfe4d 100644 --- a/frontend/app/player/web/MessageLoader.ts +++ b/frontend/app/player/web/MessageLoader.ts @@ -1,4 +1,4 @@ -import type { PlayerMsg, SessionFilesInfo, Store } from 'Player'; + import type { PlayerMsg, SessionFilesInfo, Store } from 'Player'; import unpackTar from 'Player/common/tarball'; import unpack from 'Player/common/unpack'; import IOSMessageManager from 'Player/mobile/IOSMessageManager'; @@ -132,7 +132,9 @@ export default class MessageLoader { this.messageManager.distributeMessage(msg); }); logger.info('Messages count: ', msgs.length, msgs, file); - + if (file === 'd:dom 2' && 'createTabCloseEvents' in this.messageManager) { + this.messageManager.createTabCloseEvents(); + } this.messageManager.sortDomRemoveMessages(msgs); this.messageManager.setMessagesLoading(false); }; diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 9a26ffa27..122b80faa 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -8,6 +8,7 @@ import ListWalker from '../common/ListWalker'; import MouseMoveManager from './managers/MouseMoveManager'; import ActivityManager from './managers/ActivityManager'; +import TabClosingManager from "./managers/TabClosingManager"; import { MouseThrashing, MType } from './messages'; import type { Message, MouseClick } from './messages'; @@ -63,6 +64,7 @@ export interface State extends ScreenState { currentTab: string; tabs: Set; tabChangeEvents: TabChangeEvent[]; + closedTabs: string[]; sessionStart: number; } @@ -91,6 +93,7 @@ export default class MessageManager { currentTab: '', tabs: new Set(), tabChangeEvents: [], + closedTabs: [], sessionStart: 0, }; @@ -99,6 +102,7 @@ export default class MessageManager { private activityManager: ActivityManager | null = null; private mouseMoveManager: MouseMoveManager; private activeTabManager = new ActiveTabManager(); + private tabCloseManager = new TabClosingManager(); public readonly decoder = new Decoder(); @@ -177,6 +181,19 @@ export default class MessageManager { this.state.update({ messagesProcessed: true }); }; + public createTabCloseEvents = () => { + const lastMsgArr: [string, number][] = [] + Object.entries(this.tabs).forEach((entry, i) => { + const [tabId, tab] = entry + const { lastMessageTs } = tab + if (lastMessageTs && tabId) lastMsgArr.push([tabId, lastMessageTs]) + }) + lastMsgArr.sort((a, b) => a[1] - b[1]) + lastMsgArr.forEach(([tabId, lastMessageTs]) => { + this.tabCloseManager.append({ tabId, time: lastMessageTs }) + }) + } + public startLoading = () => { this.waitingForFiles = true; this.state.update({ messagesProcessed: false }); @@ -195,6 +212,12 @@ export default class MessageManager { move(t: number): any { // usually means waiting for messages from live session if (Object.keys(this.tabs).length === 0) return; + this.tabCloseManager.moveReady(t).then(m => { + if (m) { + const closedTabs = this.tabCloseManager.closedTabs + this.state.update({ closedTabs: Array.from(closedTabs) }) + } + }) this.activeTabManager.moveReady(t).then((tabId) => { // Moving mouse and setting :hover classes on ready view this.mouseMoveManager.move(t); @@ -210,6 +233,7 @@ export default class MessageManager { if (!this.activeTab) { this.activeTab = this.state.get().currentTab ?? Object.keys(this.tabs)[0]; } + if (tabId) { if (this.activeTab !== tabId) { this.state.update({ currentTab: tabId }); diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index 6fec1e3be..e049a8878 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -71,6 +71,7 @@ export default class TabSessionManager { private scrollManager: ListWalker = new ListWalker(); public readonly decoder = new Decoder(); + public lastMessageTs = 0; private lists: Lists; private navigationStartOffset = 0; private canvasManagers: { @@ -167,6 +168,7 @@ export default class TabSessionManager { } distributeMessage(msg: Message): void { + this.lastMessageTs = msg.time switch (msg.tp) { case MType.CanvasNode: const managerId = `${msg.timestamp}_${msg.nodeId}`; diff --git a/frontend/app/player/web/managers/TabClosingManager.ts b/frontend/app/player/web/managers/TabClosingManager.ts new file mode 100644 index 000000000..7bf3cf53c --- /dev/null +++ b/frontend/app/player/web/managers/TabClosingManager.ts @@ -0,0 +1,22 @@ +import ListWalker from '../../common/ListWalker'; + +export default class TabClosingManager extends ListWalker<{ tabId: string, time: number }> { + currentTime = 0; + closedTabs: Set = new Set(); + + moveReady(t: number): Promise { + if (t < this.currentTime) { + this.reset() + } + this.currentTime = t + const msg = this.moveGetLast(t) + + if (msg) { + const ids = this.listNow.map(m => m.tabId); + this.closedTabs = new Set(ids) + return Promise.resolve(msg.tabId) + } else { + return Promise.resolve(null); + } + } +}