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;
}>();
/**
*
*/
export const [pluginPanelRoute, regPluginPanelRoute] = buildRegList<{
name: string;
path: string;
component: React.ComponentType;
}>();
export interface BasePluginPanelActionProps {
/**
*

@ -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(() => {
/>
<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('未知的面板')} />
</Routes>
</MainProvider>

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

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

@ -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==}

@ -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"
}
}

@ -12,50 +12,23 @@ interface LivekitViewProps {
url: string;
}
const _LivekitView: React.FC<LivekitViewProps> = 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 (
<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
roomName={props.roomName}
onJoin={handleJoin}
onLeave={handleLeave}
/>
);
});
_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<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 */
export type PreJoinProps = Omit<
React.HTMLAttributes<HTMLDivElement>,
'onSubmit'
'onSubmit' | 'onError'
> & {
roomName: string;
/** This function is called with the `LocalUserChoices` if validation is passed. */

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

@ -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': '当前浏览器不支持视图全屏',
'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 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: <T extends (this: any, ...args: any[]) => 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: <T>(state: T) => React.MutableRefObject<T>;
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<any>;
@ -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;
}

@ -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', '*'); // 允许跨域
},

Loading…
Cancel
Save