feat: add livekit panel for converse dm meeting

chore/devcontainer
moonrailgun 2 years ago
parent 6ea7ca8008
commit 0be2bf6c47

@ -219,6 +219,15 @@ export const [pluginRootRoute, regPluginRootRoute] = buildRegList<{
component: React.ComponentType; component: React.ComponentType;
}>(); }>();
/**
*
*/
export const [pluginPanelRoute, regPluginPanelRoute] = buildRegList<{
name: string;
path: string;
component: React.ComponentType;
}>();
export interface BasePluginPanelActionProps { export interface BasePluginPanelActionProps {
/** /**
* *

@ -9,6 +9,7 @@ import { GroupDetail } from '@/components/modals/GroupDetail';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { NotFound } from '@/components/NotFound'; import { NotFound } from '@/components/NotFound';
import { Group } from '../Main/Content/Group'; import { Group } from '../Main/Content/Group';
import { pluginPanelRoute } from '@/plugin/common';
const GroupDetailRoute = React.memo(() => { const GroupDetailRoute = React.memo(() => {
const { groupId } = useParams<{ groupId: string }>(); const { groupId } = useParams<{ groupId: string }>();
@ -39,6 +40,21 @@ const PanelRoute: React.FC = React.memo(() => {
/> />
<Route path="/group/main/:groupId/*" element={<Group />} /> <Route path="/group/main/:groupId/*" element={<Group />} />
<Route
path="/plugin/*"
element={
<Routes>
{pluginPanelRoute.map((r, i) => (
<Route
key={r.name}
path={r.path ?? `/fallback${i}`}
element={React.createElement(r.component)}
/>
))}
</Routes>
}
/>
<Route path="/*" element={t('未知的面板')} /> <Route path="/*" element={t('未知的面板')} />
</Routes> </Routes>
</MainProvider> </MainProvider>

@ -72,7 +72,7 @@ class PanelWindowManager {
return this.openedPanelWindows[url]; return this.openedPanelWindows[url];
} }
const win = openInNewWindow(url); const win = openInNewWindow(url, options);
if (!win) { if (!win) {
throw new Error('Create window failed'); throw new Error('Create window failed');
} }

@ -100,6 +100,8 @@ declare module '@capital/common' {
export const postMessageEvent: any; export const postMessageEvent: any;
export const panelWindowManager: any;
export const getServiceUrl: () => string; export const getServiceUrl: () => string;
export const getCachedUserInfo: ( export const getCachedUserInfo: (
@ -192,6 +194,10 @@ declare module '@capital/common' {
export const isDevelopment: boolean; export const isDevelopment: boolean;
export const setWebviewKernel: any;
export const resetWebviewKernel: any;
export const navigate: any; export const navigate: any;
export const useLocation: any; export const useLocation: any;
@ -306,6 +312,10 @@ declare module '@capital/common' {
export const regPluginRootRoute: any; export const regPluginRootRoute: any;
export const pluginPanelRoute: any;
export const regPluginPanelRoute: any;
export const pluginPanelActions: any; export const pluginPanelActions: any;
export const regPluginPanelAction: ( export const regPluginPanelAction: (
@ -384,6 +394,10 @@ declare module '@capital/common' {
export const regLoginAction: any; export const regLoginAction: any;
export const pluginChatInputPasteHandler: any;
export const regChatInputPasteHandler: any;
export const useGroupIdContext: () => string; export const useGroupIdContext: () => string;
export const useGroupPanelContext: () => { export const useGroupPanelContext: () => {
@ -607,4 +621,8 @@ declare module '@capital/component' {
export const NotFound: any; export const NotFound: any;
export const withKeepAliveOverlay: any; export const withKeepAliveOverlay: any;
export const AvatarUploader: any;
export const ImageUploader: any;
} }

@ -2047,6 +2047,9 @@ importers:
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0 version: 18.2.0
react-router:
specifier: ^6.8.1
version: 6.11.0(react@18.2.0)
styled-components: styled-components:
specifier: ^5.3.6 specifier: ^5.3.6
version: 5.3.10(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) 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: /@remix-run/router@1.6.0:
resolution: {integrity: sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w==} resolution: {integrity: sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w==}
engines: {node: '>=14'} engines: {node: '>=14'}
dev: false
/@rollup/plugin-babel@5.3.1(@babel/core@7.21.0)(rollup@2.79.1): /@rollup/plugin-babel@5.3.1(@babel/core@7.21.0)(rollup@2.79.1):
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
@ -29372,7 +29374,6 @@ packages:
dependencies: dependencies:
'@remix-run/router': 1.6.0 '@remix-run/router': 1.6.0
react: 18.2.0 react: 18.2.0
dev: false
/react-side-effect@2.1.2(react@18.2.0): /react-side-effect@2.1.2(react@18.2.0):
resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==} resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==}

@ -19,6 +19,7 @@
"@types/lodash": "^4.14.196", "@types/lodash": "^4.14.196",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"react": "18.2.0", "react": "18.2.0",
"react-router": "^6.8.1",
"styled-components": "^5.3.6" "styled-components": "^5.3.6"
} }
} }

@ -12,50 +12,23 @@ interface LivekitViewProps {
url: string; url: string;
} }
const _LivekitView: React.FC<LivekitViewProps> = React.memo((props) => { const _LivekitView: React.FC<LivekitViewProps> = React.memo((props) => {
const [preJoinChoices, setPreJoinChoices] = useState<
LocalUserChoices | undefined
>(undefined);
const { setActive, setDeactive } = useLivekitState(); const { setActive, setDeactive } = useLivekitState();
const handleError = useEvent((err: Error) => { const handleJoin = useEvent(async () => {
showErrorToasts('error while setting up prejoin');
console.log('error while setting up prejoin', err);
});
const handleJoin = useEvent(async (userChoices: LocalUserChoices) => {
await setDeactive(); // 先退出之前的房间 await setDeactive(); // 先退出之前的房间
setPreJoinChoices(userChoices);
setActive(props.url); setActive(props.url);
}); });
const handleLeave = useEvent(() => { const handleLeave = useEvent(() => {
setPreJoinChoices(undefined);
setDeactive(); setDeactive();
}); });
return ( return (
<LivekitContainer> <PureLivekitView
{props.roomName && preJoinChoices ? ( roomName={props.roomName}
<ActiveRoom onJoin={handleJoin}
roomName={props.roomName} onLeave={handleLeave}
userChoices={preJoinChoices} />
onLeave={handleLeave}
/>
) : (
<div style={{ display: 'grid', placeItems: 'center', height: '100%' }}>
<PreJoinView
roomName={props.roomName}
onError={handleError}
defaults={{
videoEnabled: false,
audioEnabled: false,
}}
onSubmit={handleJoin}
/>
</div>
)}
</LivekitContainer>
); );
}); });
_LivekitView.displayName = 'LivekitView'; _LivekitView.displayName = 'LivekitView';
@ -63,3 +36,62 @@ _LivekitView.displayName = 'LivekitView';
export const LivekitView = withKeepAliveOverlay(_LivekitView, { export const LivekitView = withKeepAliveOverlay(_LivekitView, {
cacheId: (props) => props.url, cacheId: (props) => props.url,
}); });
interface PureLivekitViewProps {
roomName: string;
onJoin?: () => Promise<void>;
onLeave?: () => void;
}
/**
* Without context just for meeting view
*/
export const PureLivekitView: React.FC<PureLivekitViewProps> = 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 (
<LivekitContainer>
{props.roomName && preJoinChoices ? (
<ActiveRoom
roomName={props.roomName}
userChoices={preJoinChoices}
onLeave={handleLeave}
/>
) : (
<div
style={{ display: 'grid', placeItems: 'center', height: '100%' }}
>
<PreJoinView
roomName={props.roomName}
onError={handleError}
defaults={{
videoEnabled: false,
audioEnabled: false,
}}
onSubmit={handleJoin}
/>
</div>
)}
</LivekitContainer>
);
}
);
PureLivekitView.displayName = 'PureLivekitView';

@ -42,7 +42,7 @@ const DEFAULT_USER_CHOICES = {
/** @public */ /** @public */
export type PreJoinProps = Omit< export type PreJoinProps = Omit<
React.HTMLAttributes<HTMLDivElement>, React.HTMLAttributes<HTMLDivElement>,
'onSubmit' 'onSubmit' | 'onError'
> & { > & {
roomName: string; roomName: string;
/** This function is called with the `LocalUserChoices` if validation is passed. */ /** This function is called with the `LocalUserChoices` if validation is passed. */

@ -2,6 +2,9 @@ import {
regCustomPanel, regCustomPanel,
regGroupPanel, regGroupPanel,
regGroupPanelBadge, regGroupPanelBadge,
regPluginPanelAction,
regPluginPanelRoute,
panelWindowManager,
} from '@capital/common'; } from '@capital/common';
import { Loadable } from '@capital/component'; import { Loadable } from '@capital/component';
import { useIconIsShow } from './navbar/useIconIsShow'; import { useIconIsShow } from './navbar/useIconIsShow';
@ -11,13 +14,22 @@ const PLUGIN_ID = 'com.msgbyte.livekit';
console.log(`Plugin ${PLUGIN_ID} is loaded`); 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({ regGroupPanel({
name: `${PLUGIN_ID}/livekitPanel`, name: `${PLUGIN_ID}/livekitPanel`,
label: Translate.voiceChannel, label: Translate.voiceChannel,
provider: PLUGIN_ID, provider: PLUGIN_ID,
render: Loadable(() => import('./group/LivekitPanel'), { render: LivekitPanel,
componentName: `${PLUGIN_ID}:LivekitPanel`,
}),
}); });
regGroupPanelBadge({ regGroupPanelBadge({
@ -45,3 +57,26 @@ regCustomPanel({
), ),
useIsShow: useIconIsShow, 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,
}
);
},
});

@ -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 (
<div className="w-full h-full">
<PureLivekitView roomName={meetingId} />
</div>
);
});
LivekitMeetingPanel.displayName = 'LivekitMeetingPanel';
export default LivekitMeetingPanel;

@ -65,4 +65,8 @@ export const Translate = {
'zh-CN': '当前浏览器不支持视图全屏', 'zh-CN': '当前浏览器不支持视图全屏',
'en-US': 'Current browser does not support DOM full screen', 'en-US': 'Current browser does not support DOM full screen',
}), }),
startCall: localTrans({
'zh-CN': '发起/加入通话',
'en-US': 'Start/Join Call',
}),
}; };

@ -100,6 +100,8 @@ declare module '@capital/common' {
export const postMessageEvent: any; export const postMessageEvent: any;
export const panelWindowManager: any;
export const getServiceUrl: () => string; export const getServiceUrl: () => string;
export const getCachedUserInfo: ( export const getCachedUserInfo: (
@ -153,7 +155,9 @@ declare module '@capital/common' {
deps?: React.DependencyList deps?: React.DependencyList
) => [{ loading: boolean; value?: any }, T]; ) => [{ loading: boolean; value?: any }, T];
export const useEvent: any; export const useEvent: <T extends (this: any, ...args: any[]) => any>(
fn: T
) => T;
export const uploadFile: any; export const uploadFile: any;
@ -184,6 +188,16 @@ declare module '@capital/common' {
export const useWatch: any; export const useWatch: any;
export const parseUrlStr: (originUrl: string) => string;
export const useUpdateRef: <T>(state: T) => React.MutableRefObject<T>;
export const isDevelopment: boolean;
export const setWebviewKernel: any;
export const resetWebviewKernel: any;
export const navigate: any; export const navigate: any;
export const useLocation: any; export const useLocation: any;
@ -202,7 +216,12 @@ declare module '@capital/common' {
export const getTextColorHex: any; export const getTextColorHex: any;
export const useCurrentUserInfo: any; export const useCurrentUserInfo: () => {
email?: string;
nickname?: string;
discriminator: string;
avatar?: string;
};
export const createPluginRequest: (pluginName: string) => { export const createPluginRequest: (pluginName: string) => {
get: (actionName: string, config?: any) => Promise<any>; get: (actionName: string, config?: any) => Promise<any>;
@ -211,6 +230,8 @@ declare module '@capital/common' {
export const postRequest: any; export const postRequest: any;
export const BaseCardPayload: any;
export const pluginCustomPanel: any; export const pluginCustomPanel: any;
export const regCustomPanel: (info: { export const regCustomPanel: (info: {
@ -221,13 +242,15 @@ declare module '@capital/common' {
| 'navbar-more' | 'navbar-more'
| 'navbar-group' | 'navbar-group'
| 'navbar-personal'; | 'navbar-personal';
icon: string; icon: string | React.ComponentType;
name: string; name: string;
label: string; label: string;
render: React.ComponentType; render: React.ComponentType;
/** /**
* hooks determine whether to render * hooks determine whether to render
* *
* Only available in position: `navbar-more` | `navbar-group` | `navbar-personal`
*
* @default * @default
* () => true * () => true
*/ */
@ -289,6 +312,10 @@ declare module '@capital/common' {
export const regPluginRootRoute: any; export const regPluginRootRoute: any;
export const pluginPanelRoute: any;
export const regPluginPanelRoute: any;
export const pluginPanelActions: any; export const pluginPanelActions: any;
export const regPluginPanelAction: ( export const regPluginPanelAction: (
@ -355,6 +382,10 @@ declare module '@capital/common' {
export const regPluginInboxItemMap: any; export const regPluginInboxItemMap: any;
export const pluginCardItemMap: any;
export const regPluginCardItem: any;
export const pluginGroupConfigItems: any; export const pluginGroupConfigItems: any;
export const regPluginGroupConfigItem: any; export const regPluginGroupConfigItem: any;
@ -363,6 +394,10 @@ declare module '@capital/common' {
export const regLoginAction: any; export const regLoginAction: any;
export const pluginChatInputPasteHandler: any;
export const regChatInputPasteHandler: any;
export const useGroupIdContext: () => string; export const useGroupIdContext: () => string;
export const useGroupPanelContext: () => { export const useGroupPanelContext: () => {
@ -455,6 +490,8 @@ declare module '@capital/component' {
export const useChatInputActionContext: any; export const useChatInputActionContext: any;
export const GroupPanelContainer: any;
export const GroupExtraDataPanel: any; export const GroupExtraDataPanel: any;
export const Image: any; export const Image: any;
@ -581,5 +618,11 @@ declare module '@capital/component' {
export const NoData: any; export const NoData: any;
export const GroupPanelContainer: any; export const NotFound: any;
export const withKeepAliveOverlay: any;
export const AvatarUploader: any;
export const ImageUploader: any;
} }

@ -318,7 +318,7 @@ export default class ApiService extends TcService {
use: [ use: [
serve('public', { serve('public', {
cacheControl: true, 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) { setHeaders(res: ServerResponse, path: string, stat: any) {
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域 res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域
}, },

Loading…
Cancel
Save