chore(hera-core): 迁移 hera-core 的通知、移动端入口到其他插件 (#1571)
Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#1571
This commit is contained in:
parent
f411efb1bc
commit
2425e43621
@ -26,6 +26,8 @@ import {
|
||||
} from '../..';
|
||||
import { Plugin } from '../../application/Plugin';
|
||||
import { VariablesProvider } from '../../variables';
|
||||
import { PageTab } from '../page-style/PageTab';
|
||||
import { usePageStyle } from '../page-style/usePageStyle';
|
||||
import { NoticeArea } from './NoticeArea';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => {
|
||||
@ -407,6 +409,7 @@ export const InternalAdminLayout = (props: any) => {
|
||||
const app = useApp();
|
||||
const params = useParams<any>();
|
||||
const { styles } = useStyles();
|
||||
const pageStyle = usePageStyle();
|
||||
return (
|
||||
<Layout>
|
||||
<GlobalStyleForAdminLayout />
|
||||
@ -447,7 +450,7 @@ export const InternalAdminLayout = (props: any) => {
|
||||
{params.name && <Layout.Sider className={styles.sider} theme={'light'} ref={sideMenuRef}></Layout.Sider>}
|
||||
<Layout.Content className={styles.main}>
|
||||
<header className={styles.mainHeader}></header>
|
||||
<Outlet />
|
||||
{params.name && pageStyle === 'tab' ? <PageTab /> : <Outlet />}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
);
|
||||
|
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Navigate, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useAPIClient } from '../api-client';
|
||||
import { Application, AttachmentPreviewManager, NoticeLevel, useApp } from '../application';
|
||||
import { Application, NoticeLevel, useApp } from '../application';
|
||||
import { Plugin } from '../application/Plugin';
|
||||
import { BlockSchemaComponentPlugin } from '../block-provider';
|
||||
import { CollectionPlugin } from '../collection-manager';
|
||||
@ -24,15 +24,14 @@ import { ACLPlugin } from './acl';
|
||||
import { AdminLayoutPlugin } from './admin-layout';
|
||||
import { PluginAssistant } from './assistant';
|
||||
import { AttachmentPreviewPlugin } from './attachment-preview';
|
||||
import { PluginBuiltInCollections } from './built-in-collections';
|
||||
import { PluginContextMenu } from './context-menu';
|
||||
import { RemoteDocumentTitlePlugin } from './document-title';
|
||||
import { LocalePlugin } from './locale/LocalePlugin';
|
||||
import { PluginPageStyle } from './page-style';
|
||||
import { PinnedListPlugin } from './pinned-list';
|
||||
import { PMPlugin } from './pm';
|
||||
import { SystemSettingsPlugin } from './system-settings';
|
||||
import { UserSettingsPlugin } from './user-settings';
|
||||
import { PluginWebNotification } from './web-notification/PluginWebNotification';
|
||||
|
||||
export { AdminProvider, NoticeArea } from './admin-layout';
|
||||
export * from './context-menu';
|
||||
@ -358,5 +357,6 @@ export class BuiltInPlugin extends Plugin {
|
||||
await this.app.pm.add(PluginAssistant, { name: 'drag-assistant' });
|
||||
await this.app.pm.add(UserSettingsPlugin, { name: 'user-settings' });
|
||||
await this.app.pm.add(AttachmentPreviewPlugin, { name: 'attachment-preview' });
|
||||
await this.app.pm.add(PluginPageStyle, { name: 'page-style' });
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { createContext, useEffect, useState } from 'react';
|
||||
import { useCurrentUserContext, useCurrentUserSettingsMenu } from '@tachybase/client';
|
||||
|
||||
import { TabsProps } from 'antd';
|
||||
|
||||
import { useCurrentUserContext, useCurrentUserSettingsMenu } from '../../user';
|
||||
import { useTabSettings } from './useTabSettings';
|
||||
|
||||
export interface PageStyleContextValue {
|
@ -1,9 +1,11 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { css, RemoteSchemaComponent, useDocumentTitle } from '@tachybase/client';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import { Tabs } from 'antd';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { RemoteSchemaComponent } from '../../schema-component';
|
||||
import { useDocumentTitle } from '../document-title';
|
||||
import { PageStyleContext } from './PageStyle.provider';
|
||||
|
||||
export const PageTab = () => {
|
@ -1,5 +1,4 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
|
||||
import { Plugin } from '../../application/Plugin';
|
||||
import { PageStyleProvider } from './PageStyle.provider';
|
||||
|
||||
export class PluginPageStyle extends Plugin {
|
@ -1,8 +1,11 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectWithTitle, useAPIClient, useCurrentUserContext } from '@tachybase/client';
|
||||
import { error } from '@tachybase/utils/client';
|
||||
|
||||
import { useTranslation } from '../../locale';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAPIClient } from '../../api-client';
|
||||
import { SelectWithTitle } from '../../common';
|
||||
import { useCurrentUserContext } from '../../user';
|
||||
|
||||
export const useTabSettings = () => {
|
||||
return {
|
@ -38,8 +38,8 @@ const useStyles = createStyles(({ css }) => {
|
||||
|
||||
export const PinnedPluginList = () => {
|
||||
const { allowAll, snippets } = useACLRoleContext();
|
||||
const getSnippetsAllow = (aclKey) => {
|
||||
return allowAll || snippets?.includes(aclKey);
|
||||
const getSnippetsAllow = (aclKey, isPublic = false) => {
|
||||
return allowAll || isPublic || snippets?.includes(aclKey);
|
||||
};
|
||||
const { styles } = useStyles();
|
||||
const ctx = useContext(PinnedPluginListContext);
|
||||
@ -48,7 +48,7 @@ export const PinnedPluginList = () => {
|
||||
<div className={styles.container}>
|
||||
{Object.keys(ctx.items)
|
||||
.sort((a, b) => ctx.items[a].order - ctx.items[b].order)
|
||||
.filter((key) => getSnippetsAllow(ctx.items[key].snippet))
|
||||
.filter((key) => getSnippetsAllow(ctx.items[key].snippet, ctx.items[key].isPublic))
|
||||
.map((key) => {
|
||||
const Action = get(components, ctx.items[key].component);
|
||||
return Action ? <Action key={key} /> : null;
|
||||
|
@ -845,5 +845,8 @@
|
||||
"Full Screen":"Full Screen",
|
||||
"Designer Mode":"Designer Mode",
|
||||
"Embedded page": "Embedded page",
|
||||
"Page style": "Page Style",
|
||||
"classical": "classical",
|
||||
"tabs": "tabs",
|
||||
"There are no full screen blocks available at the current location":"There are no full screen blocks available at the current location"
|
||||
}
|
||||
|
@ -961,5 +961,8 @@
|
||||
"User settings":"个人设置",
|
||||
"Import collection": "导入数据表",
|
||||
"Embedded page": "嵌入页面",
|
||||
"Page style": "页面样式",
|
||||
"classical": "经典",
|
||||
"tabs": "标签",
|
||||
"Demonstration text": "演示文本"
|
||||
}
|
||||
|
@ -8,7 +8,5 @@ export * from './custom-components/CustomField';
|
||||
export * from './fields/AssociatedField';
|
||||
export * from './fields/CalcResult';
|
||||
export * from './fields/Expression';
|
||||
export * from './system/MobileLink';
|
||||
export * from './system/Notifications';
|
||||
export * from './system/OnlineUserProvider';
|
||||
export * from './excel-table/ExcelFile';
|
||||
|
@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import { css } from '@tachybase/client';
|
||||
|
||||
import { MobileOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
|
||||
import { useTranslation } from '../../locale';
|
||||
|
||||
export const MobileLink = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
.ant-btn {
|
||||
border: 0;
|
||||
height: var(--tb-header-height);
|
||||
width: 46px;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
color: rgba(255, 255, 255, 0.65) !important;
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
}
|
||||
`}
|
||||
style={{ display: 'inline-block' }}
|
||||
>
|
||||
<Tooltip title={t('Mobile UI')} placement="bottom">
|
||||
<Button role="button" href="/mobile">
|
||||
<MobileOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,104 +0,0 @@
|
||||
import React from 'react';
|
||||
import { css, useRequest } from '@tachybase/client';
|
||||
import { dayjs } from '@tachybase/utils/client';
|
||||
|
||||
import { BellOutlined } from '@ant-design/icons';
|
||||
import { Badge, Button, List, Popover } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useInitializationLinkKey, useLinkKey } from '../../hooks/useNotifications';
|
||||
|
||||
export const Notifications = () => {
|
||||
// 注册链接关联,链接管理使用tachybase代码方式注册数据表
|
||||
useInitializationLinkKey();
|
||||
const linkDetail = useLinkKey();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { data, run } = useRequest<{ data: any }>({
|
||||
url: `/system_notifications:get`,
|
||||
});
|
||||
const getNotificationList = data?.data || [];
|
||||
const { run: updateRead } = useRequest<{ data: any }>(
|
||||
{
|
||||
url: `/system_notifications:update`,
|
||||
params: {
|
||||
ids: getNotificationList.map((item) => item.id),
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
run();
|
||||
},
|
||||
},
|
||||
);
|
||||
const content = (
|
||||
<List
|
||||
style={{ width: '400px' }}
|
||||
itemLayout="horizontal"
|
||||
dataSource={getNotificationList}
|
||||
footer={
|
||||
<Button.Group style={{ width: '100%' }}>
|
||||
<Button
|
||||
block
|
||||
onClick={() => {
|
||||
updateRead();
|
||||
}}
|
||||
>
|
||||
全部已读
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
onClick={() => {
|
||||
navigate(linkDetail?.link);
|
||||
}}
|
||||
>
|
||||
查看更多
|
||||
</Button>
|
||||
</Button.Group>
|
||||
}
|
||||
renderItem={(item: any) => (
|
||||
<List.Item>
|
||||
<List.Item.Meta title={item.title} description={dayjs(item.createdAt).calendar()} />
|
||||
{item.content}
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
.ant-btn {
|
||||
border: 0;
|
||||
height: var(--tb-header-height);
|
||||
width: 46px;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
color: rgba(255, 255, 255, 0.65) !important;
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
}
|
||||
a {
|
||||
padding-top: 12px;
|
||||
}
|
||||
.ant-badge-count {
|
||||
min-width: 10px;
|
||||
height: 10px;
|
||||
line-height: 10px;
|
||||
font-size: 9px;
|
||||
border-radius: 5px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
`}
|
||||
style={{ display: 'inline-block' }}
|
||||
>
|
||||
<Popover content={content}>
|
||||
<Badge count={getNotificationList.length} size="small" offset={[-15, 15]}>
|
||||
<Button role="button">
|
||||
<BellOutlined />
|
||||
</Button>
|
||||
</Badge>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
|
||||
import { useCurrentUserSettingsMenu } from '@tachybase/client';
|
||||
|
||||
import { usePluginVersion } from '../../hooks/usePluginVersion';
|
||||
import { useTabSettings } from '../page-style/useTabSettings';
|
||||
|
||||
const useHeraVersion = () => {
|
||||
const version = usePluginVersion();
|
||||
@ -16,10 +15,9 @@ const useHeraVersion = () => {
|
||||
export const HeraVersionProvider = ({ children }) => {
|
||||
const { addMenuItem } = useCurrentUserSettingsMenu();
|
||||
const heraVersion = useHeraVersion();
|
||||
const tabItem = useTabSettings();
|
||||
useEffect(() => {
|
||||
addMenuItem(heraVersion, { before: 'divider_1' });
|
||||
}, [addMenuItem, tabItem, heraVersion]);
|
||||
}, [addMenuItem, heraVersion]);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
@ -21,7 +21,6 @@ import { EmbedPlugin } from './features/embed';
|
||||
import { PluginFieldAppends } from './features/field-appends';
|
||||
import { PluginHeraVersion } from './features/hera-version';
|
||||
import { PluginOutbound } from './features/outbound';
|
||||
import { PluginPageStyle } from './features/page-style';
|
||||
import { PluginPDF } from './features/pdf';
|
||||
import { PluginSheet } from './features/sheet';
|
||||
import { useGetCustomAssociatedComponents } from './hooks/useGetCustomAssociatedComponents';
|
||||
@ -36,7 +35,7 @@ import {
|
||||
} from './interfaces';
|
||||
import { TstzrangeFieldInterface } from './interfaces/TstzrangeFieldInterface';
|
||||
import { Locale, tval } from './locale';
|
||||
import { AdminLayout, DetailsPage, PageLayout } from './pages';
|
||||
import { DetailsPage, PageLayout } from './pages';
|
||||
import { CreateSubmitActionInitializer, SettingBlockInitializer } from './schema-initializer';
|
||||
import { useCreateActionProps } from './schema-initializer/actions/hooks/useCreateActionProps';
|
||||
import { EditTitle, IsTablePageSize, PageModeSetting, usePaginationVisible } from './schema-settings';
|
||||
@ -59,7 +58,6 @@ export class PluginCoreClient extends Plugin {
|
||||
await this.app.pm.add(PluginGroupBlock);
|
||||
await this.app.pm.add(EmbedPlugin);
|
||||
await this.app.pm.add(DepartmentsPlugin);
|
||||
await this.app.pm.add(PluginPageStyle);
|
||||
await this.app.pm.add(PluginHeraVersion);
|
||||
await this.app.pm.add(PluginPDF);
|
||||
await this.app.pm.add(PluginOutbound);
|
||||
@ -115,7 +113,6 @@ export class PluginCoreClient extends Plugin {
|
||||
});
|
||||
|
||||
this.app.addComponents({
|
||||
AdminLayout,
|
||||
AssociatedField,
|
||||
CalcResult,
|
||||
CodeMirror,
|
||||
|
@ -1,384 +0,0 @@
|
||||
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
AdminProvider,
|
||||
css,
|
||||
CurrentUser,
|
||||
findByUid,
|
||||
findMenuItem,
|
||||
NoticeArea,
|
||||
PinnedPluginList,
|
||||
SchemaComponent,
|
||||
useACLRoleContext,
|
||||
useAdminSchemaUid,
|
||||
useApp,
|
||||
useDocumentTitle,
|
||||
useRequest,
|
||||
useSystemSettings,
|
||||
useToken,
|
||||
} from '@tachybase/client';
|
||||
|
||||
import { useSessionStorageState } from 'ahooks';
|
||||
import { App, Layout } from 'antd';
|
||||
import { Link, Outlet, useMatch, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { MobileLink } from '../components/system/MobileLink';
|
||||
import { Notifications } from '../components/system/Notifications';
|
||||
import { OnlineUserDropdown } from '../components/system/OnlineUserProvider';
|
||||
import { PageTab } from '../features/page-style/PageTab';
|
||||
import { usePageStyle } from '../features/page-style/usePageStyle';
|
||||
|
||||
const filterByACL = (schema, options) => {
|
||||
const { allowAll, allowMenuItemIds = [] } = options;
|
||||
if (allowAll) {
|
||||
return schema;
|
||||
}
|
||||
const filterSchema = (s) => {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
for (const key in s.properties) {
|
||||
if (Object.prototype.hasOwnProperty.call(s.properties, key)) {
|
||||
const element = s.properties[key];
|
||||
if (element['x-uid'] && !allowMenuItemIds.includes(element['x-uid'])) {
|
||||
delete s.properties[key];
|
||||
}
|
||||
if (element['x-uid']) {
|
||||
filterSchema(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
filterSchema(schema);
|
||||
return schema;
|
||||
};
|
||||
|
||||
const SchemaIdContext = createContext(null);
|
||||
const useMenuProps = () => {
|
||||
const defaultSelectedUid = useContext(SchemaIdContext);
|
||||
return {
|
||||
selectedUid: defaultSelectedUid,
|
||||
defaultSelectedUid,
|
||||
};
|
||||
};
|
||||
|
||||
const MenuEditor = (props) => {
|
||||
const { notification } = App.useApp();
|
||||
const [hasNotice, setHasNotice] = useSessionStorageState('plugin-notice', { defaultValue: false });
|
||||
const { setTitle } = useDocumentTitle();
|
||||
const navigate = useNavigate();
|
||||
const params = useParams<any>();
|
||||
const isMatchAdmin = useMatch('/admin');
|
||||
const isMatchAdminName = useMatch('/admin/:name');
|
||||
const defaultSelectedUid = params.name;
|
||||
const { sideMenuRef } = props;
|
||||
const ctx = useACLRoleContext();
|
||||
const [current, setCurrent] = useState(null);
|
||||
const onSelect = ({ item }) => {
|
||||
const schema = item.props.schema;
|
||||
setTitle(schema.title);
|
||||
setCurrent(schema);
|
||||
navigate(`/admin/${schema['x-uid']}`);
|
||||
};
|
||||
const adminSchemaUid = useAdminSchemaUid();
|
||||
const { data, loading } = useRequest<{
|
||||
data: any;
|
||||
}>(
|
||||
{
|
||||
url: `/uiSchemas:getJsonSchema/${adminSchemaUid}`,
|
||||
},
|
||||
{
|
||||
refreshDeps: [adminSchemaUid],
|
||||
onSuccess(data) {
|
||||
const schema = filterByACL(data?.data, ctx);
|
||||
// url 为 `/admin` 的情况
|
||||
if (isMatchAdmin) {
|
||||
const s = findMenuItem(schema);
|
||||
if (s) {
|
||||
navigate(`/admin/${s['x-uid']}`);
|
||||
setTitle(s.title);
|
||||
} else {
|
||||
navigate(`/admin/`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// url 不为 `/admin/xxx` 的情况,不做处理
|
||||
if (!isMatchAdminName) return;
|
||||
|
||||
// url 为 `admin/xxx` 的情况
|
||||
const s = findByUid(schema, defaultSelectedUid);
|
||||
if (s) {
|
||||
setTitle(s.title);
|
||||
} else {
|
||||
const s = findMenuItem(schema);
|
||||
if (s) {
|
||||
navigate(`/admin/${s['x-uid']}`);
|
||||
setTitle(s.title);
|
||||
} else {
|
||||
navigate(`/admin/`);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const properties = Object.values(current?.root?.properties || {}).shift()?.['properties'] || data?.data?.properties;
|
||||
if (properties && sideMenuRef.current) {
|
||||
const pageType = Object.values(properties).find(
|
||||
(item) => item['x-uid'] === params.name && item['x-component'] === 'Menu.Item',
|
||||
);
|
||||
if (pageType) {
|
||||
sideMenuRef.current.style.display = 'none';
|
||||
} else {
|
||||
sideMenuRef.current.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}, [data?.data, params.name, sideMenuRef]);
|
||||
|
||||
const schema = useMemo(() => {
|
||||
const s = filterByACL(data?.data, ctx);
|
||||
if (s?.['x-component-props']) {
|
||||
s['x-component-props']['useProps'] = useMenuProps;
|
||||
}
|
||||
return s;
|
||||
}, [data?.data]);
|
||||
|
||||
useRequest(
|
||||
{
|
||||
url: 'applicationPlugins:list',
|
||||
params: {
|
||||
sort: 'id',
|
||||
paginate: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: ({ data }) => {
|
||||
setHasNotice(true);
|
||||
const errorPlugins = data.filter((item) => !item.isCompatible);
|
||||
if (errorPlugins.length) {
|
||||
notification.error({
|
||||
message: 'Plugin dependencies check failed',
|
||||
description: (
|
||||
<div>
|
||||
<div>
|
||||
These plugins failed dependency checks. Please go to the{' '}
|
||||
<Link to="/admin/pm/list/local/">plugin management page</Link> for more details.{' '}
|
||||
</div>
|
||||
<ul>
|
||||
{errorPlugins.map((item) => (
|
||||
<li key={item.id}>
|
||||
{item.displayName} - {item.packageName}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
manual: true,
|
||||
// ready: !hasNotice,
|
||||
},
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return void 0;
|
||||
}
|
||||
return (
|
||||
<SchemaIdContext.Provider value={defaultSelectedUid}>
|
||||
<SchemaComponent memoized scope={{ useMenuProps, onSelect, sideMenuRef, defaultSelectedUid }} schema={schema} />
|
||||
</SchemaIdContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const InternalAdminLayout = () => {
|
||||
const sideMenuRef = useRef<HTMLDivElement>();
|
||||
const result = useSystemSettings();
|
||||
const params = useParams<{ name?: string }>();
|
||||
const app = useApp();
|
||||
const { token } = useToken();
|
||||
const pageStyle = usePageStyle();
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header
|
||||
className={css`
|
||||
.ant-menu.ant-menu-dark .ant-menu-item-selected,
|
||||
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,
|
||||
.ant-menu-submenu-horizontal.ant-menu-submenu-selected {
|
||||
background-color: ${token.colorBgHeaderMenuActive};
|
||||
color: ${token.colorTextHeaderMenuActive};
|
||||
}
|
||||
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover {
|
||||
background-color: ${token.colorBgHeaderMenuHover};
|
||||
color: ${token.colorTextHeaderMenuHover};
|
||||
}
|
||||
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--tb-header-height);
|
||||
line-height: var(--tb-header-height);
|
||||
padding: 0;
|
||||
z-index: 100;
|
||||
background-color: ${token.colorBgHeader};
|
||||
|
||||
.ant-menu {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ant-menu-item {
|
||||
color: ${token.colorTextHeaderMenu};
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
color: #fff;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
padding: 0 12px 0 12px;
|
||||
cursor: pointer;
|
||||
`}
|
||||
onClick={() => {
|
||||
location.href = app.adminUrl;
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={css`
|
||||
object-fit: contain;
|
||||
height: 28px;
|
||||
`}
|
||||
src={result?.data?.data?.logo?.url}
|
||||
/>
|
||||
<h1
|
||||
className={css`
|
||||
color: #fff;
|
||||
height: 32px;
|
||||
margin: 0 0 0 12px;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 32px;
|
||||
`}
|
||||
>
|
||||
{result?.data?.data?.title}
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className={css`
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
display: flex;
|
||||
`}
|
||||
>
|
||||
<NoticeArea
|
||||
className={css`
|
||||
flex: 1;
|
||||
`}
|
||||
/>
|
||||
<MenuEditor sideMenuRef={sideMenuRef} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={css`
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
`}
|
||||
>
|
||||
<PinnedPluginList />
|
||||
<MobileLink />
|
||||
<Notifications />
|
||||
<OnlineUserDropdown />
|
||||
<CurrentUser />
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Header>
|
||||
{params.name && (
|
||||
<Layout.Sider
|
||||
theme={'light'}
|
||||
ref={sideMenuRef}
|
||||
style={{ height: '100vh' }}
|
||||
className={css`
|
||||
.ant-layout-sider-children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<header
|
||||
className={css`
|
||||
height: var(--tb-header-height);
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
`}
|
||||
></header>
|
||||
</Layout.Sider>
|
||||
)}
|
||||
<Layout.Content
|
||||
className={css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
> div {
|
||||
position: relative;
|
||||
}
|
||||
.ant-layout-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<header
|
||||
className={css`
|
||||
flex-shrink: 0;
|
||||
height: var(--tb-header-height);
|
||||
line-height: var(--tb-header-height);
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
`}
|
||||
></header>
|
||||
<>{params.name && pageStyle === 'tab' ? <PageTab /> : <Outlet />}</>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export const AdminLayout = (props) => {
|
||||
const AdminComponent = (
|
||||
<AdminProvider>
|
||||
<InternalAdminLayout {...props} />
|
||||
</AdminProvider>
|
||||
);
|
||||
return AdminComponent;
|
||||
};
|
@ -1,4 +1,3 @@
|
||||
export * from './AdminLayout';
|
||||
export * from './DetailsPage';
|
||||
export * from '../features/outbound/OutboundPage';
|
||||
export * from './PageLayout';
|
||||
|
@ -3,11 +3,16 @@
|
||||
"version": "0.22.4",
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {
|
||||
"antd": "5.19.4"
|
||||
"@ant-design/icons": "~5.3.7",
|
||||
"antd": "5.19.4",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-router-dom": "~6.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
"@tachybase/database": "workspace:*",
|
||||
"@tachybase/plugin-workflow": "workspace:*",
|
||||
"@tachybase/schema": "workspace:*",
|
||||
"@tachybase/server": "workspace:*",
|
||||
"@tachybase/test": "workspace:*"
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
createTableBlockSchema,
|
||||
SchemaInitializerItem,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@tachybase/client';
|
||||
import { ISchema } from '@tachybase/schema';
|
||||
|
||||
import { BellOutlined } from '@ant-design/icons';
|
||||
|
||||
import { useTranslation } from './locale';
|
||||
|
||||
export const MessageBlockInitializer = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { t } = useTranslation();
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
|
||||
const schema = createTableBlockSchema({
|
||||
collection: 'messages',
|
||||
rowKey: 'id',
|
||||
tableActionInitializers: 'MessageTable:configureActions',
|
||||
tableColumnInitializers: 'MessageTable:configureColumns',
|
||||
tableActionColumnInitializers: 'MessageTable:configureItemActions',
|
||||
tableBlockProvider: 'MessageBlockProvider',
|
||||
disableTemplate: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<SchemaInitializerItem
|
||||
icon={<BellOutlined />}
|
||||
onClick={() => {
|
||||
insert(schema as ISchema);
|
||||
}}
|
||||
title={t('Messages')}
|
||||
{...itemConfig}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { ExtendCollectionsProvider, TableBlockProvider } from '@tachybase/client';
|
||||
|
||||
import { useAuditChangesCollection, useCollectionsCollection, useMessageCollection } from './collections';
|
||||
|
||||
export const MessageBlockProvider = ({ children, ...restProps }) => {
|
||||
const auditChangesCollection = useAuditChangesCollection();
|
||||
const MessageCollection = useMessageCollection();
|
||||
const collectionsCollection = useCollectionsCollection();
|
||||
|
||||
return (
|
||||
<ExtendCollectionsProvider collections={[MessageCollection, auditChangesCollection, collectionsCollection]}>
|
||||
<TableBlockProvider name="message" {...restProps}>
|
||||
{children}
|
||||
</TableBlockProvider>
|
||||
</ExtendCollectionsProvider>
|
||||
);
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { PinnedPluginListProvider, SchemaComponentOptions, useToken } from '@tachybase/client';
|
||||
|
||||
import { BellOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useTranslation } from './locale';
|
||||
|
||||
const NotificationLink = () => {
|
||||
const navigate = useNavigate();
|
||||
const { token } = useToken();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Tooltip title={t('Messages')}>
|
||||
<Button
|
||||
icon={<BellOutlined style={{ color: token.colorTextHeaderMenu }} />}
|
||||
title={t('Messages')}
|
||||
onClick={() => {
|
||||
navigate('/admin/messages');
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const MessageNotificationProvider = (props) => {
|
||||
return (
|
||||
<PinnedPluginListProvider
|
||||
items={{
|
||||
nl: { order: 220, component: 'NotificationLink', pin: true, isPublic: true },
|
||||
}}
|
||||
>
|
||||
<SchemaComponentOptions components={{ NotificationLink }}>{props.children}</SchemaComponentOptions>
|
||||
</PinnedPluginListProvider>
|
||||
);
|
||||
};
|
@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { Page, SchemaComponent, SchemaComponentContext, SchemaComponentOptions } from '@tachybase/client';
|
||||
|
||||
export const MessagePage = () => {
|
||||
return (
|
||||
<SchemaComponentContext.Provider value={{ designable: false }}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
'x-component': 'Page',
|
||||
properties: {
|
||||
messages: {
|
||||
type: 'void',
|
||||
'x-decorator': 'MessageBlockProvider',
|
||||
'x-acl-action': 'messages:list',
|
||||
'x-decorator-props': {
|
||||
collection: 'messages',
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 20,
|
||||
},
|
||||
rowKey: 'id',
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
disableTemplate: true,
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:table',
|
||||
'x-component': 'CardItem',
|
||||
'x-filter-targets': [],
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-initializer': 'MessageTable:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 'var(--tb-spacing)',
|
||||
},
|
||||
},
|
||||
},
|
||||
smvxfjqmkl7: {
|
||||
type: 'array',
|
||||
'x-initializer': 'MessageTable:configureColumns',
|
||||
'x-component': 'TableV2',
|
||||
'x-use-component-props': 'useTableBlockProps',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
title: '{{ t("Actions") }}',
|
||||
'x-action-column': 'actions',
|
||||
'x-decorator': 'TableV2.Column.ActionBar',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
},
|
||||
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||
'x-initializer': 'MessageTable:configureItemActions',
|
||||
properties: {
|
||||
he7o0zao83h: {
|
||||
type: 'void',
|
||||
'x-decorator': 'DndContext',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
split: '|',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
></SchemaComponent>
|
||||
</SchemaComponentContext.Provider>
|
||||
);
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { SchemaComponentOptions } from '@tachybase/client';
|
||||
|
||||
import { MessageViewActionInitializer } from './components/MessageViewActionInitializer';
|
||||
import { Message } from './deplicated/Message';
|
||||
import { MessageTableActionColumnInitializer } from './initializers/MessageTableActionColumnInitializer';
|
||||
import { MessageBlockInitializer } from './MessageBlockInitializer';
|
||||
import { MessageBlockProvider } from './MessageBlockProvider';
|
||||
|
||||
export const MessageProvider = (props: any) => {
|
||||
return (
|
||||
<SchemaComponentOptions
|
||||
components={{
|
||||
Message,
|
||||
MessageBlockProvider,
|
||||
MessageBlockInitializer,
|
||||
MessageViewActionInitializer,
|
||||
MessageTableActionColumnInitializer,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
};
|
@ -0,0 +1,151 @@
|
||||
import { tval } from './locale';
|
||||
|
||||
export const useMessageCollection = () => {
|
||||
return {
|
||||
name: 'messages',
|
||||
title: tval('Messages'),
|
||||
fields: [
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'date',
|
||||
interface: 'createdAt',
|
||||
uiSchema: {
|
||||
type: 'datetime',
|
||||
title: tval('Created at'),
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
showTime: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
interface: 'input',
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
allowNull: false,
|
||||
unique: false,
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
title: '{{t("Title")}}',
|
||||
'x-component': 'Input',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
interface: 'input',
|
||||
type: 'string',
|
||||
name: 'content',
|
||||
allowNull: false,
|
||||
unique: false,
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
title: '{{t("Content")}}',
|
||||
'x-component': 'Input',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'string',
|
||||
interface: 'select',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
title: tval('Action type'),
|
||||
'x-component': 'Select',
|
||||
'x-component-props': { ellipsis: true },
|
||||
'x-read-pretty': true,
|
||||
enum: [
|
||||
{ label: tval('Create record'), value: 'create', color: 'lime' },
|
||||
{ label: tval('Update record'), value: 'update', color: 'gold' },
|
||||
{ label: tval('Delete record'), value: 'destroy', color: 'magenta' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'recordId',
|
||||
type: 'string',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
title: tval('Record ID'),
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': { ellipsis: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
collectionName: 'messages',
|
||||
name: 'collection',
|
||||
type: 'belongsTo',
|
||||
interface: 'm2o',
|
||||
target: 'collections',
|
||||
targetKey: 'name',
|
||||
sourceKey: 'id',
|
||||
foreignKey: 'collectionName',
|
||||
uiSchema: {
|
||||
type: 'object',
|
||||
title: tval('Collection'),
|
||||
'x-component': 'AssociationField',
|
||||
'x-component-props': { fieldNames: { value: 'name', label: 'title' }, ellipsis: true },
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
collectionName: 'messages',
|
||||
type: 'belongsTo',
|
||||
interface: 'createdBy',
|
||||
targetKey: 'id',
|
||||
foreignKey: 'createdById',
|
||||
target: 'users',
|
||||
uiSchema: {
|
||||
type: 'object',
|
||||
title: tval('User'),
|
||||
'x-component': 'AssociationField',
|
||||
'x-component-props': { fieldNames: { value: 'id', label: 'nickname' }, ellipsis: true },
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const useAuditChangesCollection = () => {
|
||||
return {
|
||||
name: 'auditChanges',
|
||||
title: tval('Audit Changes'),
|
||||
fields: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const useCollectionsCollection = () => {
|
||||
return {
|
||||
name: 'collections',
|
||||
title: tval('Collections'),
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
title: tval('Collection name'),
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': { ellipsis: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
title: tval('Collection display name'),
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': { ellipsis: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
@ -0,0 +1,300 @@
|
||||
import React from 'react';
|
||||
import { ActionInitializer } from '@tachybase/client';
|
||||
import { uid } from '@tachybase/utils/client';
|
||||
|
||||
import { tval } from '../locale';
|
||||
|
||||
export const MessageViewActionInitializer = () => {
|
||||
const changes = {
|
||||
type: 'string',
|
||||
'x-component': 'AssociationField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'Message.changes',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
multiple: true,
|
||||
fieldNames: {
|
||||
value: 'id',
|
||||
label: 'id',
|
||||
},
|
||||
mode: 'SubTable',
|
||||
},
|
||||
properties: {
|
||||
block: {
|
||||
type: 'void',
|
||||
'x-component': 'AssociationField.SubTable',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const schema = {
|
||||
type: 'void',
|
||||
title: tval('View'),
|
||||
'x-action': 'view',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:view',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: tval('View record'),
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: tval('Details'),
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
'7ypqrxaqysp': {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
'1fc2l8dwe7m': {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
'5ley6xifrsb': {
|
||||
type: 'void',
|
||||
'x-acl-action': 'Message:get',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-decorator-props': {
|
||||
resource: 'messages',
|
||||
collection: 'messages',
|
||||
readPretty: true,
|
||||
action: 'get',
|
||||
params: {
|
||||
appends: ['collection', 'user', 'changes'],
|
||||
},
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
bv710pbf9w6: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
g4c24abnbd9: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
fkt9dj5lu1k: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'Message.createdAt',
|
||||
'x-component-props': {},
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
l267bkv423v: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
tehaepm5xqy: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'Message.type',
|
||||
'x-component-props': {},
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
zhmn2tkzdh2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
yuwyej17i64: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
recordId: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'Message.recordId',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'6q933ic06mj': {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
nwtbkqd99zl2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
'collection.title': {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'Message.collection.title',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
nwtbkqd99zl1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
'collection.name': {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'Message.collection.name',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
xd4kzulndqk: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
k3sd72n83zd: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
user: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'Message.user',
|
||||
'x-component-props': {},
|
||||
'x-read-pretty': true,
|
||||
properties: {
|
||||
viewer: {
|
||||
type: 'void',
|
||||
title: tval('View record'),
|
||||
'x-component': 'RecordPicker.Viewer',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: tval('Details'),
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'popup:common:addBlock',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'3lghgclbtcg': {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
'8l1v1auwlx0': {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
changes,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return <ActionInitializer schema={schema} />;
|
||||
};
|
@ -0,0 +1,513 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import {
|
||||
ExtendCollectionsProvider,
|
||||
FormProvider,
|
||||
SchemaComponent,
|
||||
TableBlockProvider,
|
||||
useCollection_deprecated,
|
||||
useCompile,
|
||||
useRecord,
|
||||
} from '@tachybase/client';
|
||||
import { ArrayTable } from '@tachybase/components';
|
||||
import { observer, useField } from '@tachybase/schema';
|
||||
|
||||
import { tval } from '../locale';
|
||||
import { MessageDesigner } from './MessageDesigner';
|
||||
|
||||
const collection = {
|
||||
name: 'messages',
|
||||
title: tval('Messages'),
|
||||
fields: [
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'date',
|
||||
interface: 'createdAt',
|
||||
uiSchema: {
|
||||
type: 'datetime',
|
||||
title: tval('Created at'),
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
showTime: true,
|
||||
},
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'string',
|
||||
interface: 'select',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
title: tval('Action type'),
|
||||
'x-component': 'Select',
|
||||
'x-read-pretty': true,
|
||||
enum: [
|
||||
{ label: tval('Create record'), value: 'create', color: 'lime' },
|
||||
{ label: tval('Update record'), value: 'update', color: 'gold' },
|
||||
{ label: tval('Delete record'), value: 'destroy', color: 'magenta' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const Username = observer(
|
||||
() => {
|
||||
const field = useField<any>();
|
||||
return <div>{field?.value?.nickname || field.value?.id}</div>;
|
||||
},
|
||||
{ displayName: 'Username' },
|
||||
);
|
||||
|
||||
const Collection = observer(
|
||||
() => {
|
||||
const field = useField<any>();
|
||||
const { title, name } = field.value || {};
|
||||
const compile = useCompile();
|
||||
return <div>{title ? compile(title) : name}</div>;
|
||||
},
|
||||
{ displayName: 'Collection' },
|
||||
);
|
||||
|
||||
const Field = observer(
|
||||
() => {
|
||||
const field = useField<any>();
|
||||
const compile = useCompile();
|
||||
if (!field.value) {
|
||||
return null;
|
||||
}
|
||||
return <div>{field.value?.uiSchema?.title ? compile(field.value?.uiSchema?.title) : field.value.name}</div>;
|
||||
},
|
||||
{ displayName: 'Field' },
|
||||
);
|
||||
|
||||
const Value = observer(
|
||||
() => {
|
||||
const field = useField<any>();
|
||||
const record = ArrayTable.useRecord();
|
||||
if (record.field?.uiSchema) {
|
||||
return (
|
||||
<FormProvider>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
name: record.field.name,
|
||||
...record.field?.uiSchema,
|
||||
default: field.value,
|
||||
'x-read-pretty': true,
|
||||
}}
|
||||
/>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
return <div>{field.value ? JSON.stringify(field.value) : null}</div>;
|
||||
},
|
||||
{ displayName: 'Value' },
|
||||
);
|
||||
|
||||
const IsAssociationBlock = createContext(null);
|
||||
IsAssociationBlock.displayName = 'IsAssociationBlock';
|
||||
|
||||
export const Message: any = () => {
|
||||
const isAssoc = useContext(IsAssociationBlock);
|
||||
const ext = {};
|
||||
if (!isAssoc) {
|
||||
ext['column31'] = {
|
||||
type: 'void',
|
||||
'x-component': 'TableV2.Column',
|
||||
title: tval('Record ID'),
|
||||
properties: {
|
||||
recordId: {
|
||||
'x-component': 'Input',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return (
|
||||
<SchemaComponent
|
||||
memoized
|
||||
components={{ ArrayTable, Username, Collection, Field, Value }}
|
||||
schema={{
|
||||
type: 'void',
|
||||
name: 'lfm4trkw8j3',
|
||||
'x-component': 'div',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
filter: {
|
||||
type: 'void',
|
||||
title: tval('Filter'),
|
||||
'x-action': 'filter',
|
||||
'x-component': 'Filter.Action',
|
||||
'x-component-props': {
|
||||
icon: 'FilterOutlined',
|
||||
useProps: '{{ useFilterActionProps }}',
|
||||
},
|
||||
'x-align': 'left',
|
||||
},
|
||||
},
|
||||
},
|
||||
y84dlntcaup: {
|
||||
type: 'array',
|
||||
'x-component': 'TableV2',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
useProps: '{{ useTableBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
title: tval('Actions'),
|
||||
'x-action-column': 'actions',
|
||||
'x-decorator': 'TableV2.Column.ActionBar',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||
'x-initializer': 'table:configureItemActions',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-decorator': 'DndContext',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
split: '|',
|
||||
},
|
||||
properties: {
|
||||
o80rypwmeeg: {
|
||||
type: 'void',
|
||||
title: tval('View'),
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:view',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: tval('View record'),
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
tdlav8o9o17: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
'7bsnaf47i6g': {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
'6s2qbihe3tu': {
|
||||
type: 'void',
|
||||
'x-acl-action': 'Message:get',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-decorator-props': {
|
||||
resource: 'messages',
|
||||
collection: 'messages',
|
||||
readPretty: true,
|
||||
action: 'get',
|
||||
params: {
|
||||
appends: ['collection', 'user', 'changes'],
|
||||
},
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
mevpuonrda0: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col11: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
collection: {
|
||||
type: 'string',
|
||||
title: tval('Collection display name'),
|
||||
'x-component': 'Collection',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col31: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row4: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col41: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
user: {
|
||||
type: 'string',
|
||||
title: tval('User'),
|
||||
'x-component': 'Username',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row5: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col51: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
changes: {
|
||||
type: 'array',
|
||||
title: tval('Field value changes'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayTable',
|
||||
'x-component-props': {
|
||||
pagination: { pageSize: 10 },
|
||||
scroll: { x: '100%' },
|
||||
},
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
column3: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': {
|
||||
width: 200,
|
||||
title: tval('Field'),
|
||||
},
|
||||
properties: {
|
||||
field: {
|
||||
type: 'string',
|
||||
//'x-decorator': 'Editable',
|
||||
'x-component': 'Field',
|
||||
},
|
||||
},
|
||||
},
|
||||
column4: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': {
|
||||
width: 200,
|
||||
title: tval('Before change'),
|
||||
},
|
||||
properties: {
|
||||
before: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
},
|
||||
},
|
||||
column5: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': {
|
||||
width: 200,
|
||||
title: tval('After change'),
|
||||
},
|
||||
properties: {
|
||||
after: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
column1: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {
|
||||
createdAt: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
column2: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {
|
||||
type: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
column3: {
|
||||
type: 'void',
|
||||
'x-component': 'TableV2.Column',
|
||||
title: tval('Collection display name'),
|
||||
properties: {
|
||||
collection: {
|
||||
'x-component': 'Collection',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...ext,
|
||||
column4: {
|
||||
type: 'void',
|
||||
'x-component': 'TableV2.Column',
|
||||
title: tval('User'),
|
||||
properties: {
|
||||
user: {
|
||||
'x-component': 'Username',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Message.Decorator = observer(
|
||||
(props: any) => {
|
||||
const parent = useCollection_deprecated();
|
||||
const record = useRecord();
|
||||
let filter = props?.params?.filter;
|
||||
if (parent.name) {
|
||||
const filterByTk = record?.[parent.filterTargetKey || 'id'];
|
||||
if (filter) {
|
||||
filter = {
|
||||
$and: [
|
||||
filter,
|
||||
{
|
||||
collectionName: parent.name,
|
||||
recordId: `${filterByTk}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
filter = {
|
||||
collectionName: parent.name,
|
||||
recordId: `${filterByTk}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
const defaults = {
|
||||
collection: 'messages',
|
||||
resource: 'messages',
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 20,
|
||||
appends: ['collection', 'user'],
|
||||
...props.params,
|
||||
filter,
|
||||
sort: '-createdAt',
|
||||
},
|
||||
rowKey: 'id',
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
};
|
||||
return (
|
||||
<IsAssociationBlock.Provider value={!!parent.name}>
|
||||
<ExtendCollectionsProvider collections={[collection]}>
|
||||
<TableBlockProvider {...defaults}>{props.children}</TableBlockProvider>
|
||||
</ExtendCollectionsProvider>
|
||||
</IsAssociationBlock.Provider>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Message.Decorator' },
|
||||
);
|
||||
|
||||
Message.Designer = MessageDesigner;
|
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
GeneralSchemaDesigner,
|
||||
SchemaSettingsDataScope,
|
||||
SchemaSettingsDivider,
|
||||
SchemaSettingsRemove,
|
||||
SchemaSettingsSelectItem,
|
||||
useCollection_deprecated,
|
||||
useDesignable,
|
||||
useFormBlockContext,
|
||||
useTableBlockContext,
|
||||
} from '@tachybase/client';
|
||||
import { useField, useFieldSchema } from '@tachybase/schema';
|
||||
|
||||
import { useTranslation } from '../locale';
|
||||
|
||||
export const MessageDesigner = () => {
|
||||
const { name, title } = useCollection_deprecated();
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { form } = useFormBlockContext();
|
||||
const { service } = useTableBlockContext();
|
||||
const { t } = useTranslation();
|
||||
const { dn } = useDesignable();
|
||||
return (
|
||||
<GeneralSchemaDesigner title={title || name}>
|
||||
<SchemaSettingsDataScope
|
||||
collectionName={name}
|
||||
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||
form={form}
|
||||
onSubmit={({ filter }) => {
|
||||
const params = field.decoratorProps.params || {};
|
||||
params.filter = filter;
|
||||
field.decoratorProps.params = params;
|
||||
fieldSchema['x-decorator-props']['params'] = params;
|
||||
service.run({ ...service.params?.[0], filter, page: 1 });
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SchemaSettingsSelectItem
|
||||
title={t('Records per page')}
|
||||
value={field.decoratorProps?.params?.pageSize || 20}
|
||||
options={[
|
||||
{ label: '10', value: 10 },
|
||||
{ label: '20', value: 20 },
|
||||
{ label: '50', value: 50 },
|
||||
{ label: '100', value: 100 },
|
||||
{ label: '200', value: 200 },
|
||||
]}
|
||||
onChange={(pageSize) => {
|
||||
const params = field.decoratorProps.params || {};
|
||||
params.pageSize = pageSize;
|
||||
field.decoratorProps.params = params;
|
||||
fieldSchema['x-decorator-props']['params'] = params;
|
||||
service.run({ ...service.params?.[0], pageSize, page: 1 });
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SchemaSettingsDivider />
|
||||
<SchemaSettingsRemove
|
||||
removeParentsIfNoChildren
|
||||
breakRemoveOn={{
|
||||
'x-component': 'Grid',
|
||||
}}
|
||||
/>
|
||||
</GeneralSchemaDesigner>
|
||||
);
|
||||
};
|
@ -1,6 +1,14 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
import { Plugin, SchemaInitializerItemType } from '@tachybase/client';
|
||||
import PluginWorkflow from '@tachybase/plugin-workflow/client';
|
||||
|
||||
import { lang } from './locale';
|
||||
import { MessageTableColumnInitializers } from './initializers/MessageColumnInitializers';
|
||||
import { MessageTableActionColumnInitializers } from './initializers/MessageTableActionColumnInitializers';
|
||||
import { MessageTableActionInitializers } from './initializers/MessageTableActionInitializers';
|
||||
import { MessageInstruction } from './instructions/MessageInstruction';
|
||||
import { lang, tval } from './locale';
|
||||
import { MessageNotificationProvider } from './MessageNotificationProvider';
|
||||
import { MessagePage } from './MessagePage';
|
||||
import { MessageProvider } from './MessageProvider';
|
||||
import { SubscriptionManager } from './SubscriptionManager';
|
||||
|
||||
interface MessageType {
|
||||
@ -32,6 +40,31 @@ export class PluginMessagesClient extends Plugin {
|
||||
|
||||
// You can get and modify the app instance here
|
||||
async load() {
|
||||
this.app.addComponents({
|
||||
Messages: MessagePage,
|
||||
});
|
||||
this.app.use(MessageProvider);
|
||||
this.app.schemaInitializerManager.add(MessageTableActionInitializers);
|
||||
this.app.schemaInitializerManager.add(MessageTableActionColumnInitializers);
|
||||
this.app.schemaInitializerManager.add(MessageTableColumnInitializers);
|
||||
|
||||
const workflowPlugin = this.pm.get(PluginWorkflow);
|
||||
workflowPlugin.registerInstruction('message-instruction', MessageInstruction);
|
||||
|
||||
const blockInitializers = this.app.schemaInitializerManager.get('page:addBlock');
|
||||
const recordBlockInitializers = this.app.schemaInitializerManager.get('popup:common:addBlock');
|
||||
const messages: Omit<SchemaInitializerItemType, 'name'> = {
|
||||
title: tval('messages'),
|
||||
Component: 'MessageBlockInitializer',
|
||||
};
|
||||
blockInitializers.add('otherBlocks.messages', messages);
|
||||
recordBlockInitializers.add('otherBlocks.messages', messages);
|
||||
|
||||
this.app.use(MessageNotificationProvider);
|
||||
this.app.router.add('admin.messages', {
|
||||
path: '/admin/messages',
|
||||
Component: MessagePage,
|
||||
});
|
||||
this.userSettingsManager.add('sub-manager', {
|
||||
title: lang('Subscription management'),
|
||||
icon: 'BellOutlined',
|
||||
|
@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
SchemaInitializer,
|
||||
SchemaInitializerChildren,
|
||||
useAssociatedTableColumnInitializerFields,
|
||||
useCompile,
|
||||
useInheritsTableColumnInitializerFields,
|
||||
useTableColumnInitializerFields,
|
||||
} from '@tachybase/client';
|
||||
|
||||
import { tval, useTranslation } from '../locale';
|
||||
|
||||
// 表格列配置
|
||||
const ParentCollectionFields = () => {
|
||||
const inheritFields = useInheritsTableColumnInitializerFields();
|
||||
const { t } = useTranslation();
|
||||
const compile = useCompile();
|
||||
if (!inheritFields?.length) return null;
|
||||
const res = [];
|
||||
inheritFields.forEach((inherit) => {
|
||||
Object.values(inherit)[0].length &&
|
||||
res.push({
|
||||
type: 'itemGroup',
|
||||
divider: true,
|
||||
title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
|
||||
children: Object.values(inherit)[0].filter((v: any) => !v?.field?.isForeignKey),
|
||||
});
|
||||
});
|
||||
return <SchemaInitializerChildren>{res}</SchemaInitializerChildren>;
|
||||
};
|
||||
|
||||
const AssociatedFields = () => {
|
||||
const associatedFields = useAssociatedTableColumnInitializerFields();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!associatedFields?.length) return null;
|
||||
const schema: any = [
|
||||
{
|
||||
type: 'itemGroup',
|
||||
divider: true,
|
||||
title: t('Display association fields'),
|
||||
children: associatedFields,
|
||||
},
|
||||
];
|
||||
return <SchemaInitializerChildren>{schema}</SchemaInitializerChildren>;
|
||||
};
|
||||
|
||||
export const MessageTableColumnInitializers = new SchemaInitializer({
|
||||
name: 'MessageTable:configureColumns',
|
||||
insertPosition: 'beforeEnd',
|
||||
icon: 'SettingOutlined',
|
||||
title: tval('Configure columns'),
|
||||
wrap(s) {
|
||||
if (s['x-action-column']) {
|
||||
return s;
|
||||
}
|
||||
return {
|
||||
type: 'void',
|
||||
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:TableColumn',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {
|
||||
[s.name]: {
|
||||
...s,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
items: [
|
||||
{
|
||||
name: 'displayFields',
|
||||
type: 'itemGroup',
|
||||
title: tval('Display fields'),
|
||||
useChildren: useTableColumnInitializerFields,
|
||||
},
|
||||
{
|
||||
name: 'parentCollectionFields',
|
||||
Component: ParentCollectionFields,
|
||||
},
|
||||
{
|
||||
name: 'associationFields',
|
||||
Component: AssociatedFields,
|
||||
},
|
||||
{
|
||||
name: 'actionColumn',
|
||||
title: tval('Action column'),
|
||||
Component: 'MessageTableActionColumnInitializer',
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { InitializerWithSwitch, useSchemaInitializerItem } from '@tachybase/client';
|
||||
|
||||
import { tval } from '../locale';
|
||||
|
||||
export const MessageTableActionColumnInitializer = () => {
|
||||
const schema = {
|
||||
type: 'void',
|
||||
title: tval('Actions'),
|
||||
'x-decorator': 'TableV2.Column.ActionBar',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||
'x-initializer': 'MessageTable:configureItemActions',
|
||||
'x-action-column': 'actions',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-decorator': 'DndContext',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
split: '|',
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
return <InitializerWithSwitch {...itemConfig} schema={schema} item={itemConfig} type={'x-action-column'} />;
|
||||
};
|
@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { createDesignable, Resizable, SchemaInitializer, useAPIClient, useDesignable } from '@tachybase/client';
|
||||
import { useFieldSchema } from '@tachybase/schema';
|
||||
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
|
||||
import { tval, useTranslation } from '../locale';
|
||||
|
||||
export const MessageTableActionColumnInitializers = new SchemaInitializer({
|
||||
name: 'MessageTable:configureItemActions',
|
||||
insertPosition: 'beforeEnd',
|
||||
Component: (props: any) => <MenuOutlined {...props} style={{ cursor: 'pointer' }} />,
|
||||
useInsert() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const api = useAPIClient();
|
||||
const { refresh } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (schema) => {
|
||||
const spaceSchema = fieldSchema.reduceProperties((buf, schema) => {
|
||||
if (schema['x-component'] === 'Space') {
|
||||
return schema;
|
||||
}
|
||||
return buf;
|
||||
}, null);
|
||||
if (!spaceSchema) {
|
||||
return;
|
||||
}
|
||||
const dn = createDesignable({
|
||||
t,
|
||||
api,
|
||||
refresh,
|
||||
current: spaceSchema,
|
||||
});
|
||||
dn.loadAPIClientEvents();
|
||||
dn.insertBeforeEnd(schema);
|
||||
};
|
||||
},
|
||||
items: [
|
||||
{
|
||||
name: 'enableActions',
|
||||
type: 'itemGroup',
|
||||
title: tval('Enable actions'),
|
||||
children: [
|
||||
{
|
||||
name: 'view',
|
||||
type: 'item',
|
||||
title: tval('View'),
|
||||
Component: 'MessageViewActionInitializer',
|
||||
schema: {
|
||||
'x-component': 'Action.Link',
|
||||
'x-action': 'view',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
name: 'columnWidth',
|
||||
type: 'item',
|
||||
title: tval('Column width'),
|
||||
Component: Resizable,
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,39 @@
|
||||
import { SchemaInitializer } from '@tachybase/client';
|
||||
|
||||
import { tval } from '../locale';
|
||||
|
||||
export type { ButtonProps } from 'antd';
|
||||
|
||||
export const MessageTableActionInitializers = new SchemaInitializer({
|
||||
name: 'MessageTable:configureActions',
|
||||
title: tval('Configure actions'),
|
||||
icon: 'SettingOutlined',
|
||||
style: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: tval('Enable actions'),
|
||||
name: 'enableActions',
|
||||
children: [
|
||||
{
|
||||
name: 'filter',
|
||||
title: tval('Filter'),
|
||||
Component: 'FilterActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'left',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'refresh',
|
||||
title: tval('Refresh'),
|
||||
Component: 'RefreshActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
import { Instruction, VariableOption } from '@tachybase/plugin-workflow/client';
|
||||
|
||||
export class MessageInstruction extends Instruction {
|
||||
title = 'Messages';
|
||||
type = 'message-instruction';
|
||||
group = 'extended';
|
||||
fieldset = {
|
||||
digit: {
|
||||
type: 'number',
|
||||
title: 'Digit',
|
||||
name: 'digit',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
min: 1,
|
||||
max: 10,
|
||||
},
|
||||
default: 6,
|
||||
},
|
||||
};
|
||||
useVariables(node, options): VariableOption {
|
||||
return {
|
||||
value: node.key,
|
||||
label: node.title,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { i18n, tval as nTval } from '@tachybase/client';
|
||||
import { useTranslation as useT } from 'react-i18next';
|
||||
|
||||
export const NAMESPACE = 'messages';
|
||||
|
||||
export function lang(key: string) {
|
||||
return i18n.t(key, { ns: NAMESPACE });
|
||||
}
|
||||
|
||||
export function useTranslation() {
|
||||
return useT(NAMESPACE);
|
||||
}
|
||||
|
||||
export const tval = (key: string) => nTval(key, { ns: NAMESPACE });
|
@ -0,0 +1,20 @@
|
||||
import { FlowNodeModel, IJob, Instruction, InstructionResult, JOB_STATUS, Processor } from '@tachybase/plugin-workflow';
|
||||
|
||||
export class MessageInstruction extends Instruction {
|
||||
async run(node: FlowNodeModel, input: any, processor: Processor): Promise<IJob> {
|
||||
console.log('my instruction runs!');
|
||||
|
||||
const msgRepo = node.db.getRepository('messages');
|
||||
|
||||
await msgRepo.create({
|
||||
values: {
|
||||
title: 'testing',
|
||||
content: 'testing',
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
status: JOB_STATUS.RESOLVED,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,11 +1,17 @@
|
||||
import { PluginWorkflow } from '@tachybase/plugin-workflow';
|
||||
import { Plugin } from '@tachybase/server';
|
||||
|
||||
import { MessageInstruction } from './instructions/message-instruction';
|
||||
|
||||
export class PluginMessagesServer extends Plugin {
|
||||
async afterAdd() {}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
async load() {}
|
||||
async load() {
|
||||
const workflowPlugin = this.app.getPlugin<PluginWorkflow>(PluginWorkflow);
|
||||
workflowPlugin.registerInstruction('message-instruction', MessageInstruction);
|
||||
}
|
||||
|
||||
async install() {}
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { css, PinnedPluginListProvider, SchemaComponentOptions, useToken } from '@tachybase/client';
|
||||
|
||||
import { MobileOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useTranslation } from './locale';
|
||||
|
||||
const MobileLink = () => {
|
||||
const navigate = useNavigate();
|
||||
const { token } = useToken();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Tooltip title={t('Mobile UI')}>
|
||||
<Button
|
||||
icon={<MobileOutlined style={{ color: token.colorTextHeaderMenu }} />}
|
||||
title={t('Mobile UI')}
|
||||
onClick={() => {
|
||||
navigate('/mobile');
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileLinkProvider = (props) => {
|
||||
return (
|
||||
<PinnedPluginListProvider
|
||||
items={{
|
||||
me: { order: 210, component: 'MobileLink', pin: true, isPublic: true },
|
||||
}}
|
||||
>
|
||||
<SchemaComponentOptions components={{ MobileLink }}>{props.children}</SchemaComponentOptions>
|
||||
</PinnedPluginListProvider>
|
||||
);
|
||||
};
|
@ -17,6 +17,7 @@ import {
|
||||
import { NAMESPACE } from './locale';
|
||||
import { MAppNotFound } from './MAppNotFound';
|
||||
import { MobileClientProvider } from './MobileClientProvider';
|
||||
import { MobileLinkProvider } from './MobileLinkProvider';
|
||||
import MApplication from './router/Application';
|
||||
|
||||
export class MobileClientPlugin extends Plugin {
|
||||
@ -26,6 +27,7 @@ export class MobileClientPlugin extends Plugin {
|
||||
this.addRoutes();
|
||||
this.addSettings();
|
||||
this.app.use(MobileClientProvider);
|
||||
this.app.use(MobileLinkProvider);
|
||||
this.app.schemaInitializerManager.add(mBlockInitializers_deprecated);
|
||||
this.app.schemaInitializerManager.add(mBlockInitializers);
|
||||
this.app.schemaInitializerManager.add(ImageSearchConfigureFields);
|
||||
|
@ -47,5 +47,6 @@
|
||||
"AllProducts": "AllProducts",
|
||||
"sort":"sort",
|
||||
"Add new":"Add new",
|
||||
"Mobile UI": "Mobile UI",
|
||||
"please enter the date": "please enter the date"
|
||||
}
|
||||
|
@ -50,5 +50,6 @@
|
||||
"TabSearch":"表格搜索",
|
||||
"ImageSearch":"图片搜索",
|
||||
"Add new":"添加",
|
||||
"Mobile UI": "移动端页面",
|
||||
"please enter the date": "请输入日期"
|
||||
}
|
||||
|
@ -3355,12 +3355,21 @@ importers:
|
||||
|
||||
packages/plugins/@tachybase/plugin-messages:
|
||||
dependencies:
|
||||
'@ant-design/icons':
|
||||
specifier: ~5.3.7
|
||||
version: 5.3.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tachybase/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/client
|
||||
'@tachybase/database':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/database
|
||||
'@tachybase/plugin-workflow':
|
||||
specifier: workspace:*
|
||||
version: link:../plugin-workflow
|
||||
'@tachybase/schema':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/schema
|
||||
'@tachybase/server':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/server
|
||||
@ -3370,6 +3379,12 @@ importers:
|
||||
antd:
|
||||
specifier: 5.19.4
|
||||
version: 5.19.4(date-fns@3.6.0)(luxon@3.5.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-i18next:
|
||||
specifier: ^14.1.2
|
||||
version: 14.1.2(i18next@23.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-router-dom:
|
||||
specifier: ~6.25.1
|
||||
version: 6.25.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
packages/plugins/@tachybase/plugin-mobile-client:
|
||||
dependencies:
|
||||
|
Loading…
Reference in New Issue
Block a user