Sess header fixes (#2124)

* feat ui: cross dead tabs, some ui fixes

* ? lost changes?
This commit is contained in:
Delirium 2024-04-24 09:39:50 +02:00 committed by GitHub
parent 690ec9bf34
commit 4444ecf6e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 150 additions and 80 deletions

View file

@ -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}
/>
</React.Fragment>
))}

View file

@ -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 (
<div
key={tab}
@ -20,7 +21,8 @@ function Tab({ i, tab, currentTab, changeTab, isLive }: Props) {
changeTab && !isLive ? 'cursor-pointer' : 'cursor-default',
currentTab === tab
? 'border-gray-lighter border-t border-l border-r !border-b-white bg-white rounded-tl rounded-tr font-semibold'
: 'cursor-pointer border-gray-lighter !border-b !border-t-transparent !border-l-transparent !border-r-transparent'
: 'cursor-pointer border-gray-lighter !border-b !border-t-transparent !border-l-transparent !border-r-transparent',
isClosed ? 'line-through': ''
)}
>
Tab {i + 1}

View file

@ -84,7 +84,7 @@ function SelectDateRange(props: Props) {
defaultValue={selectedValue?.value ?? undefined}
>
<div
className={'cursor-pointer font-semibold flex items-center gap-2'}
className={'cursor-pointer flex items-center gap-2'}
>
<div>{isCustomRange ? customRange : selectedValue?.label}</div>
<DownOutlined />

View file

@ -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: (
<div
className={
notesStore.activeTags.includes('ALL') ||
notesStore.activeTags.length === 0
? 'text-main'
: ''
}
>
All
</div>
),
},
...TAGS.map((tag: iTag) => ({
value: tag,
label: toTitleCase(tag),
label: (
<div
className={
notesStore.activeTags.includes(tag) ? 'text-main' : ''
}
>
{toTitleCase(tag)}
</div>
),
})),
]}
onChange={(value: iTag) => notesStore.toggleTag(value)}
/>
<div className="ml-auto" />
<Dropdown
menu={{
items: notesOwner.map(({ value, label }) => ({ key: value, label })),
onClick: ({ key }) => {
notesStore.toggleShared(key === '1');
},
<SortDropdown
sortOptions={notesOwner.map(({ value, label }) => ({
key: value,
label,
}))}
onSort={({ key }) => {
notesStore.toggleShared(key === '1');
}}
>
<div
className={
'cursor-pointer flex items-center justify-end gap-2 font-semibold'
}
>
<div>
{notesStore.ownOnly ? notesOwner[1].label : notesOwner[0].label}
</div>
<DownOutlined />
</div>
</Dropdown>
current={notesStore.ownOnly ? notesOwner[1].label : notesOwner[0].label}
/>
<div className="ml-2 w-2" />
<Dropdown
menu={{
items: sortOptions.map(({ value, label }) => ({ key: value, label })),
onClick: ({ key }) => {
notesStore.toggleSort(key);
},
<SortDropdown
sortOptions={sortOptions.map(({ value, label }) => ({
key: value,
label,
}))}
onSort={({ key }) => {
notesStore.toggleSort(key);
}}
>
<div
className={
'cursor-pointer flex items-center justify-end gap-2 font-semibold'
}
>
<div>
{notesStore.order === "DESC" ? "Newest" : "Oldest"}
</div>
<DownOutlined />
</div>
</Dropdown>
current={notesStore.order === 'DESC' ? 'Newest' : 'Oldest'}
/>
</div>
);
}

View file

@ -26,6 +26,34 @@ interface Props {
sort: (sort: string, sign: number) => void;
}
export function SortDropdown<T>({ defaultOption, onSort, sortOptions, current }: {
defaultOption?: string,
onSort: ({ key, item }: { key: string, item: T }) => void,
sortOptions: any,
current: string
}) {
return (
<Dropdown
menu={{
items: sortOptions,
defaultSelectedKeys: defaultOption ? [defaultOption] : undefined,
// @ts-ignore
onClick: onSort,
}}
>
<div
className={
'cursor-pointer flex items-center justify-end gap-2'
}
>
<div>{current}</div>
<DownOutlined />
</div>
</Dropdown>
)
}
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 (
<Dropdown
menu={{
items: sortOptions,
defaultSelectedKeys: [defaultOption],
onClick: onSort,
}}
>
<div
className={
'cursor-pointer flex items-center justify-end gap-2 font-semibold'
}
>
<div>{sortOptionsMap[defaultOption]}</div>
<DownOutlined />
</div>
</Dropdown>
<SortDropdown
defaultOption={defaultOption}
onSort={onSort}
sortOptions={sortOptions}
current={sortOptionsMap[defaultOption]}
/>
);
}

View file

@ -32,11 +32,11 @@ const SessionTags: React.FC<Props> = memo(({ activeTab, tags, total, setActiveTa
label: <div className={'flex items-center gap-2'}>
{tag.icon ? <Icon
name={tag.icon}
color={activeTab.type === tag.type ? "teal" : "gray-medium"}
color={activeTab.type === tag.type ? "main" : undefined}
size="14"
className={cn("group-hover:fill-teal mr-2")}
className={cn("group-hover:fill-teal")}
/> : null}
<div>{tag.name}</div>
<div className={activeTab.type === tag.type ? 'text-main' : ''}>{tag.name}</div>
</div>,
value: tag.type,
disabled: disable && tag.type !== 'all',

View file

@ -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);
};

View file

@ -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<string>;
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 });

View file

@ -71,6 +71,7 @@ export default class TabSessionManager {
private scrollManager: ListWalker<SetViewportScroll> = 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}`;

View file

@ -0,0 +1,22 @@
import ListWalker from '../../common/ListWalker';
export default class TabClosingManager extends ListWalker<{ tabId: string, time: number }> {
currentTime = 0;
closedTabs: Set<string> = new Set();
moveReady(t: number): Promise<string | null> {
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);
}
}
}