From b71945c29fd2df52e30bde5e5eb105d2b6383c22 Mon Sep 17 00:00:00 2001 From: pengx17 Date: Thu, 8 Aug 2024 09:14:46 +0000 Subject: [PATCH] chore: tracking events for app tabs header (#7778) fix AF-1194 --- packages/frontend/core/src/mixpanel/events.ts | 27 ++++++ .../app-tabs-header/views/app-tabs-header.tsx | 90 ++++++++++++++++++- .../src/main/windows-manager/context-menu.ts | 21 +++++ .../src/main/windows-manager/tab-views.ts | 2 +- 4 files changed, 137 insertions(+), 3 deletions(-) diff --git a/packages/frontend/core/src/mixpanel/events.ts b/packages/frontend/core/src/mixpanel/events.ts index df79c8abf8..97f5239f0f 100644 --- a/packages/frontend/core/src/mixpanel/events.ts +++ b/packages/frontend/core/src/mixpanel/events.ts @@ -18,6 +18,7 @@ type NavigationEvents = | 'openInSplitView' | 'switchTab' | 'switchSplitView' + | 'tabAction' | 'navigate' | 'goBack' | 'goForward' @@ -229,6 +230,9 @@ const PageEvents = { storage: ['viewPlans'], aiAction: ['viewPlans'], }, + appTabsHeader: { + $: ['tabAction'], + }, header: { actions: [ 'createDoc', @@ -319,6 +323,24 @@ type PaymentEventArgs = { recurring: string; }; +type TabActionControlType = + | 'click' + | 'dnd' + | 'midClick' + | 'xButton' + | 'contextMenu'; +type TabActionType = + | 'pin' + | 'unpin' + | 'close' + | 'refresh' + | 'moveTab' + | 'openInSplitView' + | 'openInNewTab' + | 'switchSplitView' + | 'switchTab' + | 'separateTabs'; + export type EventArgs = { createWorkspace: { flavour: string }; oauth: { provider: string }; @@ -342,6 +364,11 @@ export type EventArgs = { orderOrganizeItem: OrganizeItemArgs; openInNewTab: { type: OrganizeItemType }; openInSplitView: { type: OrganizeItemType }; + tabAction: { + type?: OrganizeItemType; + control: TabActionControlType; + action: TabActionType; + }; toggleFavorite: OrganizeItemArgs & { on: boolean }; createDoc: { mode?: 'edgeless' | 'page' }; switchPageMode: { mode: 'edgeless' | 'page' }; diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx index dd684d3597..b8e300df9b 100644 --- a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx +++ b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx @@ -14,6 +14,7 @@ import { appSidebarWidthAtom } from '@affine/core/components/app-sidebar/index.j import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; +import { track } from '@affine/core/mixpanel'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { apis, events } from '@affine/electron-api'; import { useI18n } from '@affine/i18n'; @@ -82,20 +83,78 @@ const WorkbenchTab = ({ const activeViewIndex = workbench.activeViewIndex ?? 0; const onContextMenu = useAsyncCallback( async (viewIdx: number) => { - await tabsHeaderService.showContextMenu?.(workbench.id, viewIdx); + const action = await tabsHeaderService.showContextMenu?.( + workbench.id, + viewIdx + ); + switch (action?.type) { + case 'open-in-split-view': { + track.$.appTabsHeader.$.tabAction({ + control: 'contextMenu', + action: 'openInSplitView', + }); + break; + } + case 'separate-view': { + track.$.appTabsHeader.$.tabAction({ + control: 'contextMenu', + action: 'separateTabs', + }); + break; + } + case 'pin-tab': { + if (action.payload.shouldPin) { + track.$.appTabsHeader.$.tabAction({ + control: 'contextMenu', + action: 'pin', + }); + } else { + track.$.appTabsHeader.$.tabAction({ + control: 'contextMenu', + action: 'unpin', + }); + } + break; + } + // fixme: when close tab the view may already be gc'ed + case 'close-tab': { + track.$.appTabsHeader.$.tabAction({ + control: 'contextMenu', + action: 'close', + }); + break; + } + default: + break; + } }, [tabsHeaderService, workbench.id] ); const onActivateView = useAsyncCallback( async (viewIdx: number) => { await tabsHeaderService.activateView?.(workbench.id, viewIdx); + if (tabActive) { + track.$.appTabsHeader.$.tabAction({ + control: 'click', + action: 'switchSplitView', + }); + } else { + track.$.appTabsHeader.$.tabAction({ + control: 'click', + action: 'switchTab', + }); + } }, - [tabsHeaderService, workbench.id] + [tabActive, tabsHeaderService, workbench.id] ); const handleAuxClick: MouseEventHandler = useCatchEventCallback( async e => { if (e.button === 1) { await tabsHeaderService.closeTab?.(workbench.id); + track.$.appTabsHeader.$.tabAction({ + control: 'midClick', + action: 'close', + }); } }, [tabsHeaderService, workbench.id] @@ -103,6 +162,10 @@ const WorkbenchTab = ({ const handleCloseTab = useCatchEventCallback(async () => { await tabsHeaderService.closeTab?.(workbench.id); + track.$.appTabsHeader.$.tabAction({ + control: 'xButton', + action: 'close', + }); }, [tabsHeaderService, workbench.id]); const { dropTargetRef, closestEdge } = useDropTarget( @@ -243,6 +306,10 @@ export const AppTabsHeader = ({ const onAddTab = useAsyncCallback(async () => { await tabsHeaderService.onAddTab?.(); + track.$.appTabsHeader.$.tabAction({ + control: 'click', + action: 'openInNewTab', + }); }, [tabsHeaderService]); const onToggleRightSidebar = useAsyncCallback(async () => { @@ -268,6 +335,10 @@ export const AppTabsHeader = ({ if (targetId === data.source.data.from.tabId) { return; } + track.$.appTabsHeader.$.tabAction({ + control: 'dnd', + action: 'moveTab', + }); return await tabsHeaderService.moveTab?.( data.source.data.from.tabId, targetId, @@ -276,6 +347,11 @@ export const AppTabsHeader = ({ } if (data.source.data.entity?.type === 'doc') { + track.$.appTabsHeader.$.tabAction({ + control: 'dnd', + action: 'openInNewTab', + type: 'doc', + }); return await tabsHeaderService.onAddDocTab?.( data.source.data.entity.id, targetId, @@ -284,6 +360,11 @@ export const AppTabsHeader = ({ } if (data.source.data.entity?.type === 'tag') { + track.$.appTabsHeader.$.tabAction({ + type: 'tag', + control: 'dnd', + action: 'openInNewTab', + }); return await tabsHeaderService.onAddTagTab?.( data.source.data.entity.id, targetId, @@ -292,6 +373,11 @@ export const AppTabsHeader = ({ } if (data.source.data.entity?.type === 'collection') { + track.$.appTabsHeader.$.tabAction({ + type: 'collection', + control: 'dnd', + action: 'openInNewTab', + }); return await tabsHeaderService.onAddCollectionTab?.( data.source.data.entity.id, targetId, diff --git a/packages/frontend/electron/src/main/windows-manager/context-menu.ts b/packages/frontend/electron/src/main/windows-manager/context-menu.ts index 94f1c9612f..93aff35998 100644 --- a/packages/frontend/electron/src/main/windows-manager/context-menu.ts +++ b/packages/frontend/electron/src/main/windows-manager/context-menu.ts @@ -5,6 +5,7 @@ import { addTab, closeTab, reloadView, + type TabAction, WebContentViewsManager, } from './tab-views'; @@ -15,6 +16,8 @@ export const showTabContextMenu = async (tabId: string, viewIndex: number) => { return; } + const { resolve, promise } = Promise.withResolvers(); + const template: Parameters[0] = [ tabMeta.pinned ? { @@ -90,4 +93,22 @@ export const showTabContextMenu = async (tabId: string, viewIndex: number) => { ]; const menu = Menu.buildFromTemplate(template); menu.popup(); + // eslint-disable-next-line prefer-const + let unsub: (() => void) | undefined; + const subscription = WebContentViewsManager.instance.tabAction$.subscribe( + action => { + resolve(action); + unsub?.(); + } + ); + menu.on('menu-will-close', () => { + setTimeout(() => { + resolve(null); + unsub?.(); + }); + }); + unsub = () => { + subscription.unsubscribe(); + }; + return promise; }; diff --git a/packages/frontend/electron/src/main/windows-manager/tab-views.ts b/packages/frontend/electron/src/main/windows-manager/tab-views.ts index a0ee61cad6..45ab653409 100644 --- a/packages/frontend/electron/src/main/windows-manager/tab-views.ts +++ b/packages/frontend/electron/src/main/windows-manager/tab-views.ts @@ -108,7 +108,7 @@ type OpenInSplitViewAction = { }; }; -type TabAction = +export type TabAction = | AddTabAction | CloseTabAction | PinTabAction