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 (
+
+
+
+ )
+}
+
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);
+ }
+ }
+}