From 0be2bf6c470cb202acae90c4be3194d98b8549d4 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sat, 18 Nov 2023 16:56:28 +0800 Subject: [PATCH] feat: add livekit panel for converse dm meeting --- client/web/src/plugin/common/reg.ts | 9 ++ client/web/src/routes/Panel/index.tsx | 16 +++ client/web/src/utils/window-helper.ts | 2 +- client/web/tailchat.d.ts | 18 ++++ pnpm-lock.yaml | 5 +- .../plugins/com.msgbyte.livekit/package.json | 1 + .../src/components/LivekitView.tsx | 98 ++++++++++++------- .../src/components/lib/PreJoinView.tsx | 2 +- .../plugins/com.msgbyte.livekit/src/index.tsx | 41 +++++++- .../src/panel/LivekitMeetingPanel.tsx | 16 +++ .../com.msgbyte.livekit/src/translate.ts | 4 + .../com.msgbyte.livekit/types/tailchat.d.ts | 51 +++++++++- server/services/core/gateway.service.ts | 2 +- 13 files changed, 220 insertions(+), 45 deletions(-) create mode 100644 server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/panel/LivekitMeetingPanel.tsx diff --git a/client/web/src/plugin/common/reg.ts b/client/web/src/plugin/common/reg.ts index 3302010bdbd..c8b95e5b862 100644 --- a/client/web/src/plugin/common/reg.ts +++ b/client/web/src/plugin/common/reg.ts @@ -219,6 +219,15 @@ export const [pluginRootRoute, regPluginRootRoute] = buildRegList<{ component: React.ComponentType; }>(); +/** + * 注册独立面板路由 + */ +export const [pluginPanelRoute, regPluginPanelRoute] = buildRegList<{ + name: string; + path: string; + component: React.ComponentType; +}>(); + export interface BasePluginPanelActionProps { /** * 唯一标识 diff --git a/client/web/src/routes/Panel/index.tsx b/client/web/src/routes/Panel/index.tsx index 7ed1987c52e..a073f90fb50 100644 --- a/client/web/src/routes/Panel/index.tsx +++ b/client/web/src/routes/Panel/index.tsx @@ -9,6 +9,7 @@ import { GroupDetail } from '@/components/modals/GroupDetail'; import { useParams } from 'react-router'; import { NotFound } from '@/components/NotFound'; import { Group } from '../Main/Content/Group'; +import { pluginPanelRoute } from '@/plugin/common'; const GroupDetailRoute = React.memo(() => { const { groupId } = useParams<{ groupId: string }>(); @@ -39,6 +40,21 @@ const PanelRoute: React.FC = React.memo(() => { /> } /> + + {pluginPanelRoute.map((r, i) => ( + + ))} + + } + /> + diff --git a/client/web/src/utils/window-helper.ts b/client/web/src/utils/window-helper.ts index dcd8829f74d..98623f65a17 100644 --- a/client/web/src/utils/window-helper.ts +++ b/client/web/src/utils/window-helper.ts @@ -72,7 +72,7 @@ class PanelWindowManager { return this.openedPanelWindows[url]; } - const win = openInNewWindow(url); + const win = openInNewWindow(url, options); if (!win) { throw new Error('Create window failed'); } diff --git a/client/web/tailchat.d.ts b/client/web/tailchat.d.ts index a0332ea687e..abeca8663ad 100644 --- a/client/web/tailchat.d.ts +++ b/client/web/tailchat.d.ts @@ -100,6 +100,8 @@ declare module '@capital/common' { export const postMessageEvent: any; + export const panelWindowManager: any; + export const getServiceUrl: () => string; export const getCachedUserInfo: ( @@ -192,6 +194,10 @@ declare module '@capital/common' { export const isDevelopment: boolean; + export const setWebviewKernel: any; + + export const resetWebviewKernel: any; + export const navigate: any; export const useLocation: any; @@ -306,6 +312,10 @@ declare module '@capital/common' { export const regPluginRootRoute: any; + export const pluginPanelRoute: any; + + export const regPluginPanelRoute: any; + export const pluginPanelActions: any; export const regPluginPanelAction: ( @@ -384,6 +394,10 @@ declare module '@capital/common' { export const regLoginAction: any; + export const pluginChatInputPasteHandler: any; + + export const regChatInputPasteHandler: any; + export const useGroupIdContext: () => string; export const useGroupPanelContext: () => { @@ -607,4 +621,8 @@ declare module '@capital/component' { export const NotFound: any; export const withKeepAliveOverlay: any; + + export const AvatarUploader: any; + + export const ImageUploader: any; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf14b3a4b6e..0ae83f69f7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2047,6 +2047,9 @@ importers: react: specifier: 18.2.0 version: 18.2.0 + react-router: + specifier: ^6.8.1 + version: 6.11.0(react@18.2.0) styled-components: specifier: ^5.3.6 version: 5.3.10(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) @@ -9605,7 +9608,6 @@ packages: /@remix-run/router@1.6.0: resolution: {integrity: sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w==} engines: {node: '>=14'} - dev: false /@rollup/plugin-babel@5.3.1(@babel/core@7.21.0)(rollup@2.79.1): resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} @@ -29372,7 +29374,6 @@ packages: dependencies: '@remix-run/router': 1.6.0 react: 18.2.0 - dev: false /react-side-effect@2.1.2(react@18.2.0): resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==} diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json index 061d036dae9..94de5fa2f17 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/package.json @@ -19,6 +19,7 @@ "@types/lodash": "^4.14.196", "@types/styled-components": "^5.1.26", "react": "18.2.0", + "react-router": "^6.8.1", "styled-components": "^5.3.6" } } diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx index 1ee4c5d0934..a6311d54d66 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/LivekitView.tsx @@ -12,50 +12,23 @@ interface LivekitViewProps { url: string; } const _LivekitView: React.FC = React.memo((props) => { - const [preJoinChoices, setPreJoinChoices] = useState< - LocalUserChoices | undefined - >(undefined); const { setActive, setDeactive } = useLivekitState(); - const handleError = useEvent((err: Error) => { - showErrorToasts('error while setting up prejoin'); - console.log('error while setting up prejoin', err); - }); - - const handleJoin = useEvent(async (userChoices: LocalUserChoices) => { + const handleJoin = useEvent(async () => { await setDeactive(); // 先退出之前的房间 - - setPreJoinChoices(userChoices); setActive(props.url); }); const handleLeave = useEvent(() => { - setPreJoinChoices(undefined); setDeactive(); }); return ( - - {props.roomName && preJoinChoices ? ( - - ) : ( -
- -
- )} -
+ ); }); _LivekitView.displayName = 'LivekitView'; @@ -63,3 +36,62 @@ _LivekitView.displayName = 'LivekitView'; export const LivekitView = withKeepAliveOverlay(_LivekitView, { cacheId: (props) => props.url, }); + +interface PureLivekitViewProps { + roomName: string; + onJoin?: () => Promise; + onLeave?: () => void; +} +/** + * Without context just for meeting view + */ +export const PureLivekitView: React.FC = React.memo( + (props) => { + const [preJoinChoices, setPreJoinChoices] = useState< + LocalUserChoices | undefined + >(undefined); + + const handleError = useEvent((err: Error) => { + showErrorToasts('error while setting up prejoin'); + console.log('error while setting up prejoin', err); + }); + + const handleJoin = useEvent(async (userChoices: LocalUserChoices) => { + await props.onJoin?.(); + + setPreJoinChoices(userChoices); + }); + + const handleLeave = useEvent(() => { + props.onLeave?.(); + setPreJoinChoices(undefined); + }); + + return ( + + {props.roomName && preJoinChoices ? ( + + ) : ( +
+ +
+ )} +
+ ); + } +); +PureLivekitView.displayName = 'PureLivekitView'; diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx index 9e6d17399d4..8d04eae85db 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/components/lib/PreJoinView.tsx @@ -42,7 +42,7 @@ const DEFAULT_USER_CHOICES = { /** @public */ export type PreJoinProps = Omit< React.HTMLAttributes, - 'onSubmit' + 'onSubmit' | 'onError' > & { roomName: string; /** This function is called with the `LocalUserChoices` if validation is passed. */ diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx index 3ea9fc4eb19..759cc51849e 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/index.tsx @@ -2,6 +2,9 @@ import { regCustomPanel, regGroupPanel, regGroupPanelBadge, + regPluginPanelAction, + regPluginPanelRoute, + panelWindowManager, } from '@capital/common'; import { Loadable } from '@capital/component'; import { useIconIsShow } from './navbar/useIconIsShow'; @@ -11,13 +14,22 @@ const PLUGIN_ID = 'com.msgbyte.livekit'; console.log(`Plugin ${PLUGIN_ID} is loaded`); +const LivekitPanel = Loadable(() => import('./group/LivekitPanel'), { + componentName: `${PLUGIN_ID}:LivekitPanel`, +}); + +const LivekitMeetingPanel = Loadable( + () => import('./panel/LivekitMeetingPanel'), + { + componentName: `${PLUGIN_ID}:LivekitMeetingPanel`, + } +); + regGroupPanel({ name: `${PLUGIN_ID}/livekitPanel`, label: Translate.voiceChannel, provider: PLUGIN_ID, - render: Loadable(() => import('./group/LivekitPanel'), { - componentName: `${PLUGIN_ID}:LivekitPanel`, - }), + render: LivekitPanel, }); regGroupPanelBadge({ @@ -45,3 +57,26 @@ regCustomPanel({ ), useIsShow: useIconIsShow, }); + +regPluginPanelRoute({ + name: `${PLUGIN_ID}/livekitPanel`, + path: `/${PLUGIN_ID}/meeting/:meetingId`, + component: LivekitMeetingPanel, +}); + +// 发起私信会议 +regPluginPanelAction({ + name: `${PLUGIN_ID}/groupAction`, + label: Translate.startCall, + position: 'dm', + icon: 'mdi:video-box', + onClick: ({ converseId }) => { + panelWindowManager.open( + `/panel/plugin/${PLUGIN_ID}/meeting/${converseId}`, + { + width: 1280, + height: 768, + } + ); + }, +}); diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/panel/LivekitMeetingPanel.tsx b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/panel/LivekitMeetingPanel.tsx new file mode 100644 index 00000000000..33cd0c923f6 --- /dev/null +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/panel/LivekitMeetingPanel.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { PureLivekitView } from '../components/LivekitView'; +import { useParams } from 'react-router'; + +const LivekitMeetingPanel: React.FC = React.memo(() => { + const { meetingId } = useParams<{ meetingId: string }>(); + + return ( +
+ +
+ ); +}); +LivekitMeetingPanel.displayName = 'LivekitMeetingPanel'; + +export default LivekitMeetingPanel; diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts index e3d26d797f4..996705f8331 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/src/translate.ts @@ -65,4 +65,8 @@ export const Translate = { 'zh-CN': '当前浏览器不支持视图全屏', 'en-US': 'Current browser does not support DOM full screen', }), + startCall: localTrans({ + 'zh-CN': '发起/加入通话', + 'en-US': 'Start/Join Call', + }), }; diff --git a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts index 0a5ac1de0df..abeca8663ad 100644 --- a/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts +++ b/server/plugins/com.msgbyte.livekit/web/plugins/com.msgbyte.livekit/types/tailchat.d.ts @@ -100,6 +100,8 @@ declare module '@capital/common' { export const postMessageEvent: any; + export const panelWindowManager: any; + export const getServiceUrl: () => string; export const getCachedUserInfo: ( @@ -153,7 +155,9 @@ declare module '@capital/common' { deps?: React.DependencyList ) => [{ loading: boolean; value?: any }, T]; - export const useEvent: any; + export const useEvent: any>( + fn: T + ) => T; export const uploadFile: any; @@ -184,6 +188,16 @@ declare module '@capital/common' { export const useWatch: any; + export const parseUrlStr: (originUrl: string) => string; + + export const useUpdateRef: (state: T) => React.MutableRefObject; + + export const isDevelopment: boolean; + + export const setWebviewKernel: any; + + export const resetWebviewKernel: any; + export const navigate: any; export const useLocation: any; @@ -202,7 +216,12 @@ declare module '@capital/common' { export const getTextColorHex: any; - export const useCurrentUserInfo: any; + export const useCurrentUserInfo: () => { + email?: string; + nickname?: string; + discriminator: string; + avatar?: string; + }; export const createPluginRequest: (pluginName: string) => { get: (actionName: string, config?: any) => Promise; @@ -211,6 +230,8 @@ declare module '@capital/common' { export const postRequest: any; + export const BaseCardPayload: any; + export const pluginCustomPanel: any; export const regCustomPanel: (info: { @@ -221,13 +242,15 @@ declare module '@capital/common' { | 'navbar-more' | 'navbar-group' | 'navbar-personal'; - icon: string; + icon: string | React.ComponentType; name: string; label: string; render: React.ComponentType; /** * hooks determine whether to render * + * Only available in position: `navbar-more` | `navbar-group` | `navbar-personal` + * * @default * () => true */ @@ -289,6 +312,10 @@ declare module '@capital/common' { export const regPluginRootRoute: any; + export const pluginPanelRoute: any; + + export const regPluginPanelRoute: any; + export const pluginPanelActions: any; export const regPluginPanelAction: ( @@ -355,6 +382,10 @@ declare module '@capital/common' { export const regPluginInboxItemMap: any; + export const pluginCardItemMap: any; + + export const regPluginCardItem: any; + export const pluginGroupConfigItems: any; export const regPluginGroupConfigItem: any; @@ -363,6 +394,10 @@ declare module '@capital/common' { export const regLoginAction: any; + export const pluginChatInputPasteHandler: any; + + export const regChatInputPasteHandler: any; + export const useGroupIdContext: () => string; export const useGroupPanelContext: () => { @@ -455,6 +490,8 @@ declare module '@capital/component' { export const useChatInputActionContext: any; + export const GroupPanelContainer: any; + export const GroupExtraDataPanel: any; export const Image: any; @@ -581,5 +618,11 @@ declare module '@capital/component' { export const NoData: any; - export const GroupPanelContainer: any; + export const NotFound: any; + + export const withKeepAliveOverlay: any; + + export const AvatarUploader: any; + + export const ImageUploader: any; } diff --git a/server/services/core/gateway.service.ts b/server/services/core/gateway.service.ts index 2f2144f70b7..c17072945c1 100644 --- a/server/services/core/gateway.service.ts +++ b/server/services/core/gateway.service.ts @@ -318,7 +318,7 @@ export default class ApiService extends TcService { use: [ serve('public', { cacheControl: true, - maxAge: '1d', // 1 day for public file + maxAge: '1d', // 1 day for public file, include plugins setHeaders(res: ServerResponse, path: string, stat: any) { res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域 },