feat: add fe ui in wxpusher plugin

pull/90/head
moonrailgun 2 years ago
parent a146ab0d41
commit 44595b351f

@ -56,6 +56,7 @@ export { GroupPanelSelector } from '@/components/GroupPanelSelector';
export { Emoji } from '@/components/Emoji';
export { PortalAdd, PortalRemove } from '@/components/Portal';
export { ErrorBoundary } from '@/components/ErrorBoundary';
export { ErrorView } from '@/components/ErrorView';
export { UserAvatar } from '@/components/UserAvatar';
export { UserName } from '@/components/UserName';
export { Markdown } from '@/components/Markdown';

@ -410,6 +410,8 @@ declare module '@capital/component' {
export const MessageAckContainer: any;
export const GroupExtraDataPanel: any;
export const Image: any;
export const IconBtn: React.FC<{
@ -497,6 +499,10 @@ declare module '@capital/component' {
export const ErrorBoundary: any;
export const ErrorView: React.FC<{
error: Error;
}>;
export const UserAvatar: React.FC<{
userId: string;
className?: string;
@ -512,6 +518,8 @@ declare module '@capital/component' {
export const Markdown: any;
export const MarkdownEditor: any;
export const Webview: any;
export const WebviewKeepAlive: any;

@ -114,9 +114,9 @@ class WxpusherService extends TcService {
return null;
}
const uid = user.wxpusherUserId;
const wxpusherUserId = user.wxpusherUserId;
return uid;
return wxpusherUserId;
}
/**

@ -0,0 +1,92 @@
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPluginRequest, useAsync } from '@capital/common';
import { LoadingSpinner, ErrorView } from '@capital/component';
import { Translate } from './translate';
const request = createPluginRequest('com.msgbyte.wxpusher');
const SettingsPanel: React.FC = React.memo(() => {
const [wxpusherUserId, setWxpusherUserId] = useState('');
const { loading, error } = useAsync(async () => {
const { data } = await request.get('getWXPusherUserId');
setWxpusherUserId(data);
}, []);
if (loading) {
return <LoadingSpinner tip={Translate.loadingState} />;
}
if (error) {
return <ErrorView error={error} />;
}
return wxpusherUserId ? (
<div>
<div>{Translate.binded}</div>
<div>
{Translate.currentWXPusherId}: {wxpusherUserId}
</div>
</div>
) : (
<QRCode
onBindSuccess={(wxpusherUserId) => {
setWxpusherUserId(wxpusherUserId);
}}
/>
);
});
SettingsPanel.displayName = 'SettingsPanel';
export const QRCode: React.FC<{
onBindSuccess: (wxpusherUserId: string) => void;
}> = React.memo((props) => {
const {
loading,
error,
value: url,
} = useAsync(async () => {
const { data } = await request.post('createQRCode');
return data.data.url;
}, []);
const onBindSuccessRef = useRef(props.onBindSuccess);
onBindSuccessRef.current = props.onBindSuccess;
useLayoutEffect(() => {
let timer: number;
async function loop() {
const { data: wxpusherUserId } = await request.get('getWXPusherUserId');
if (wxpusherUserId) {
onBindSuccessRef.current(wxpusherUserId);
} else {
timer = window.setTimeout(loop, 4 * 1000); // 4s loop
}
}
loop();
return () => {
if (timer) {
window.clearTimeout(timer);
}
};
}, []);
if (loading) {
return <LoadingSpinner tip={Translate.loadingQRCode} />;
}
if (error) {
return <ErrorView error={error} />;
}
return (
<div>
<div>{Translate.useWechatBindTip}</div>
<img width={260} src={url} />
</div>
);
});
QRCode.displayName = 'QRCode';
export default SettingsPanel;

@ -1 +1,12 @@
import { regCustomPanel } from '@capital/common';
import { Loadable } from '@capital/component';
console.log('Plugin wxpusher is loaded');
regCustomPanel({
position: 'setting',
icon: '',
name: 'com.msgbyte.wxpusher/settings',
label: 'WxPusher',
render: Loadable(() => import('./SettingsPanel')),
});

@ -0,0 +1,24 @@
import { localTrans } from '@capital/common';
export const Translate = {
loadingState: localTrans({
'zh-CN': '正在检查绑定状态',
'en-US': 'Checking binding status',
}),
binded: localTrans({
'zh-CN': '已绑定',
'en-US': 'Binded',
}),
currentWXPusherId: localTrans({
'zh-CN': '当前 wxpusher ID',
'en-US': 'Current wxpusher uid',
}),
loadingQRCode: localTrans({
'zh-CN': '正在加载绑定二维码',
'en-US': 'Binding QR code is loading',
}),
useWechatBindTip: localTrans({
'zh-CN': '使用微信扫码绑定 wxpusher',
'en-US': 'Use wechat scan QRCode to bind wxpusher',
}),
};

@ -1,2 +1,532 @@
declare module '@capital/common';
declare module '@capital/component';
/* eslint-disable @typescript-eslint/no-explicit-any */
/// <reference types="react" />
/**
* Tailchat
*
* : pnpm run plugins:declaration:generate
*/
/**
* Tailchat
*/
declare module '@capital/common' {
export const useGroupPanelParams: any;
/**
*
* @deprecated @capital/component
*/
export const openModal: (
content: React.ReactNode,
props?: {
/**
*
* @default false
*/
closable?: boolean;
/**
*
*/
maskClosable?: boolean;
/**
* modal
*/
onCloseModal?: () => void;
}
) => number;
/**
* @deprecated @capital/component
*/
export const closeModal: any;
/**
* @deprecated @capital/component
*/
export const ModalWrapper: any;
/**
* @deprecated @capital/component
*/
export const useModalContext: any;
/**
* @deprecated @capital/component
*/
export const openConfirmModal: any;
/**
* @deprecated @capital/component
*/
export const openReconfirmModal: any;
/**
* @deprecated @capital/component
*/
export const Loadable: any;
export const getGlobalState: any;
export const useGlobalSocketEvent: <T>(
eventName: string,
callback: (data: T) => void
) => void;
export const getJWTUserInfo: () => Promise<{
_id?: string;
nickname?: string;
email?: string;
avatar?: string;
}>;
export const dataUrlToFile: any;
export const urlSearchStringify: any;
export const urlSearchParse: any;
export const appendUrlSearch: any;
export const getServiceWorkerRegistration: any;
export const getServiceUrl: () => string;
export const getCachedUserInfo: (
userId: string,
refetch?: boolean
) => Promise<{
_id: string;
email: string;
nickname: string;
discriminator: string;
avatar: string | null;
temporary: boolean;
}>;
export const getCachedConverseInfo: any;
export const getCachedBaseGroupInfo: any;
export const getCachedUserSettings: any;
/**
*
* @example
* localTrans({'zh-CN': '你好', 'en-US': 'Hello'});
*
* @param trans
*/
export const localTrans: (trans: Record<'zh-CN' | 'en-US', string>) => string;
export const getLanguage: any;
export const sharedEvent: any;
export const useAsync: <T extends (...args: any[]) => Promise<any>>(
fn: T,
deps?: React.DependencyList
) => { loading: boolean; value?: any; error?: Error };
export const useAsyncFn: <T extends (...args: any[]) => Promise<any>>(
fn: T,
deps?: React.DependencyList
) => [{ loading: boolean; value?: any; error?: Error }, T];
export const useAsyncRefresh: <T extends (...args: any[]) => Promise<any>>(
fn: T,
deps?: React.DependencyList
) => { loading: boolean; value?: any; error?: Error; refresh: () => void };
export const useAsyncRequest: <T extends (...args: any[]) => Promise<any>>(
fn: T,
deps?: React.DependencyList
) => [{ loading: boolean; value?: any }, T];
export const uploadFile: any;
export const showToasts: (
message: string,
type?: 'info' | 'success' | 'error' | 'warning'
) => void;
export const showSuccessToasts: any;
export const showErrorToasts: (error: any) => void;
export const fetchAvailableServices: any;
export const isValidStr: (str: any) => str is string;
export const useGroupPanelInfo: any;
export const sendMessage: any;
export const showMessageTime: any;
export const joinArray: any;
export const navigate: any;
export const useLocation: any;
export const useNavigate: any;
/**
* @deprecated please use createMetaFormSchema from @capital/component
*/
export const createFastFormSchema: any;
/**
* @deprecated please use metaFormFieldSchema from @capital/component
*/
export const fieldSchema: any;
export const useCurrentUserInfo: any;
export const createPluginRequest: (pluginName: string) => {
get: (actionName: string, config?: any) => Promise<any>;
post: (actionName: string, data?: any, config?: any) => Promise<any>;
};
export const postRequest: any;
export const pluginCustomPanel: any;
export const regCustomPanel: (info: {
position:
| 'personal'
| 'setting'
| 'groupdetail'
| 'navbar-more'
| 'navbar-group'
| 'navbar-personal';
icon: string;
name: string;
label: string;
render: React.ComponentType;
}) => void;
export const pluginGroupPanel: any;
export const regGroupPanel: any;
export const messageInterpreter: {
name?: string;
explainMessage: (message: string) => React.ReactNode;
}[];
export const regMessageInterpreter: (interpreter: {
name?: string;
explainMessage: (message: string) => React.ReactNode;
}) => void;
export const getMessageRender: (message: string) => React.ReactNode;
export const regMessageRender: (
render: (message: string) => React.ReactNode
) => void;
export const getMessageTextDecorators: any;
export const regMessageTextDecorators: any;
export const ChatInputActionContextProps: any;
export const pluginChatInputActions: any;
export const regChatInputAction: any;
export const regSocketEventListener: (item: {
eventName: string;
eventFn: (...args: any[]) => void;
}) => void;
export const pluginColorScheme: any;
export const regPluginColorScheme: any;
export const pluginInspectServices: any;
export const regInspectService: any;
export const pluginMessageExtraParsers: any;
export const regMessageExtraParser: any;
export const pluginRootRoute: any;
export const regPluginRootRoute: any;
export const pluginPanelActions: any;
export const regPluginPanelAction: (
action:
| {
name: string;
label: string;
icon: string;
position: 'group';
onClick: (ctx: { groupId: string; panelId: string }) => void;
}
| {
name: string;
label: string;
icon: string;
position: 'dm';
onClick: (ctx: { converseId: string }) => void;
}
) => void;
export const pluginPermission: any;
export const regPluginPermission: (permission: {
/**
* key,
* , : plugin.com.msgbyte.github.manage
*/
key: string;
/**
*
*/
title: string;
/**
*
*/
desc: string;
/**
*
*/
default: boolean;
/**
*
*/
required?: string[];
}) => void;
export const pluginGroupPanelBadges: any;
export const regGroupPanelBadge: any;
export const pluginGroupTextPanelExtraMenus: any;
export const regPluginGroupTextPanelExtraMenu: any;
export const pluginUserExtraInfo: any;
export const regUserExtraInfo: any;
export const pluginSettings: any;
export const regPluginSettings: any;
export const pluginInboxItemMap: any;
export const regPluginInboxItemMap: any;
export const useGroupIdContext: () => string;
export const useGroupPanelContext: () => {
groupId: string;
panelId: string;
} | null;
export const useSocketContext: any;
}
/**
* Tailchat
*/
declare module '@capital/component' {
export const Button: any;
export const Checkbox: any;
export const Input: any;
export const Divider: any;
export const Space: any;
export const Menu: any;
export const Table: any;
export const Switch: any;
export const Tooltip: any;
/**
* @link https://ant.design/components/notification-cn/
*/
export const notification: any;
export const Empty: React.FC<
React.PropsWithChildren<{
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
imageStyle?: React.CSSProperties;
image?: React.ReactNode;
description?: React.ReactNode;
}>
>;
export const TextArea: any;
export const Avatar: any;
export const SensitiveText: React.FC<{ className?: string; text: string }>;
export const Icon: React.FC<{ icon: string } & React.SVGProps<SVGSVGElement>>;
export const CopyableText: React.FC<{
className?: string;
style?: React.CSSProperties;
config?:
| boolean
| {
text?: string;
onCopy?: (event?: React.MouseEvent<HTMLDivElement>) => void;
icon?: React.ReactNode;
tooltips?: boolean | React.ReactNode;
format?: 'text/plain' | 'text/html';
};
}>;
export const WebFastForm: any;
export const WebMetaForm: any;
export const createMetaFormSchema: any;
export const metaFormFieldSchema: any;
export const Link: any;
export const MessageAckContainer: any;
export const Image: any;
export const IconBtn: React.FC<{
icon: string;
className?: string;
iconClassName?: string;
size?: 'small' | 'middle' | 'large';
shape?: 'circle' | 'square';
title?: string;
danger?: boolean;
active?: boolean;
disabled?: boolean;
onClick?: React.MouseEventHandler<HTMLElement>;
}>;
export const PillTabs: any;
export const PillTabPane: any;
export const LoadingSpinner: React.FC<{ tip?: string }>;
export const FullModalField: any;
export const DefaultFullModalInputEditorRender: any;
export const DefaultFullModalTextAreaEditorRender: any;
export const openModal: (
content: React.ReactNode,
props?: {
/**
*
* @default false
*/
closable?: boolean;
/**
*
*/
maskClosable?: boolean;
/**
* modal
*/
onCloseModal?: () => void;
}
) => number;
export const closeModal: any;
export const ModalWrapper: any;
export const useModalContext: any;
export const openConfirmModal: any;
export const openReconfirmModal: any;
export const Loadable: any;
export const Loading: React.FC<{
spinning: boolean;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}>;
export const LoadingOnFirst: React.FC<{
spinning: boolean;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}>;
export const SidebarView: any;
export const GroupPanelSelector: any;
export const Emoji: any;
export const PortalAdd: any;
export const PortalRemove: any;
export const ErrorBoundary: any;
export const ErrorView: React.FC<{
error: Error;
}>;
export const UserAvatar: React.FC<{
userId: string;
className?: string;
style?: React.CSSProperties;
size?: 'large' | 'small' | 'default' | number;
}>;
export const UserName: React.FC<{
userId: string;
className?: string;
style?: React.CSSProperties;
}>;
export const Markdown: any;
export const Webview: any;
export const WebviewKeepAlive: any;
export const Card: any;
export const Problem: any;
export const JumpToButton: any;
export const JumpToGroupPanelButton: any;
export const JumpToConverseButton: any;
}

Loading…
Cancel
Save