feat: 通知与个人页面改版(WIP) (#1455)
Co-authored-by: sealday <sealday@gmail.com> Co-authored-by: wangjiahui <wwwjh0710@163.com> Reviewed-on: daoyoucloud/tachybase#1455
This commit is contained in:
parent
d78017ac40
commit
bf2cae92b0
@ -8,7 +8,7 @@ VERDACCIO_PORT=10104
|
||||
|
||||
################# TACHYBASE APPLICATION #################
|
||||
|
||||
# !!! When `APP_ENV=production`, opening http://localhost:13000/ will show "Not Found".
|
||||
# !!! When `APP_ENV=production`, opening http://localhost:3000/ will show "Not Found".
|
||||
# !!! It is recommended to use nginx to proxy static files. For example https://github.com/tachybase/tachybase/blob/main/docker/tachybase/tachybase.conf
|
||||
APP_ENV=development
|
||||
APP_PORT=20000
|
||||
|
@ -1,7 +1,7 @@
|
||||
################# TACHYBASE APPLICATION #################
|
||||
|
||||
APP_ENV=development
|
||||
APP_PORT=13000
|
||||
APP_PORT=3000
|
||||
APP_KEY=test-key
|
||||
|
||||
API_BASE_PATH=/api/
|
||||
|
@ -54,7 +54,7 @@ server {
|
||||
}
|
||||
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://127.0.0.1:13000/api/;
|
||||
proxy_pass http://127.0.0.1:3000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
@ -67,7 +67,7 @@ server {
|
||||
}
|
||||
|
||||
location ^~ /adapters/ {
|
||||
proxy_pass http://127.0.0.1:13000/adapters/;
|
||||
proxy_pass http://127.0.0.1:3000/adapters/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
@ -80,7 +80,7 @@ server {
|
||||
}
|
||||
|
||||
location ^~ /static/plugins/ {
|
||||
proxy_pass http://127.0.0.1:13000/static/plugins/;
|
||||
proxy_pass http://127.0.0.1:3000/static/plugins/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
@ -93,7 +93,7 @@ server {
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:13000/ws;
|
||||
proxy_pass http://127.0.0.1:3000/ws;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
|
@ -67,7 +67,7 @@ describe.skip('cli', () => {
|
||||
test('install', async () => {
|
||||
const database = await createDatabase();
|
||||
const port = await getPortPromise({
|
||||
port: 13000,
|
||||
port: 3000,
|
||||
});
|
||||
console.log(process.env.DB_DIALECT, port);
|
||||
const dbFile = `storage/tests/db/tachybase-${uid()}.sqlite`;
|
||||
@ -97,7 +97,7 @@ describe.skip('cli', () => {
|
||||
console.log(process.env.DB_DIALECT);
|
||||
const database = await createDatabase();
|
||||
const port = await getPortPromise({
|
||||
port: 13000,
|
||||
port: 3000,
|
||||
});
|
||||
const dbFile = `storage/tests/db/tachybase-${uid()}.sqlite`;
|
||||
const env = {
|
||||
@ -127,7 +127,7 @@ describe.skip('cli', () => {
|
||||
console.log(process.env.DB_DIALECT);
|
||||
const database = await createDatabase();
|
||||
const port = await getPortPromise({
|
||||
port: 13000,
|
||||
port: 3000,
|
||||
});
|
||||
const dbFile = `storage/tests/db/tachybase-${uid()}.sqlite`;
|
||||
const env = {
|
||||
@ -153,7 +153,7 @@ describe.skip('cli', () => {
|
||||
console.log(process.env.DB_DIALECT);
|
||||
const database = await createDatabase();
|
||||
const port = await getPortPromise({
|
||||
port: 13000,
|
||||
port: 3000,
|
||||
});
|
||||
const dbFile = `storage/tests/db/tachybase-${uid()}.sqlite`;
|
||||
const env = {
|
||||
@ -183,7 +183,7 @@ describe.skip('cli', () => {
|
||||
console.log(process.env.DB_DIALECT);
|
||||
const database = await createDatabase();
|
||||
const port = await getPortPromise({
|
||||
port: 13000,
|
||||
port: 3000,
|
||||
});
|
||||
const dbFile = `storage/tests/db/tachybase-${uid()}.sqlite`;
|
||||
const env = {
|
||||
@ -208,7 +208,7 @@ describe.skip('cli', () => {
|
||||
console.log(process.env.DB_DIALECT);
|
||||
const database = await createDatabase();
|
||||
const port = await getPortPromise({
|
||||
port: 13000,
|
||||
port: 3000,
|
||||
});
|
||||
const dbFile = `storage/tests/db/tachybase-${uid()}.sqlite`;
|
||||
const env = {
|
||||
|
@ -320,7 +320,7 @@ export function initEnv() {
|
||||
const env = {
|
||||
APP_ENV: 'development',
|
||||
APP_KEY: 'test-jwt-secret',
|
||||
APP_PORT: 13000,
|
||||
APP_PORT: 3000,
|
||||
API_BASE_PATH: '/api/',
|
||||
DB_DIALECT: 'sqlite',
|
||||
DB_STORAGE: 'storage/db/tachybase.sqlite',
|
||||
|
@ -54,7 +54,6 @@
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-i18next": "^14.1.2",
|
||||
"react-iframe": "~1.8.5",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-intersection-observer": "^9.10.3",
|
||||
"react-js-cron": "^3.1.0",
|
||||
"react-quill": "^2.0.0",
|
||||
|
@ -28,6 +28,7 @@ import { ComponentTypeAndString, RouterManager, RouterOptions } from './RouterMa
|
||||
import { SchemaInitializer, SchemaInitializerManager } from './schema-initializer';
|
||||
import * as schemaInitializerComponents from './schema-initializer/components';
|
||||
import { SchemaSettings, SchemaSettingsManager } from './schema-settings';
|
||||
import { UserSettingOptions, UserSettingsManager } from './UserSettingsManager';
|
||||
import { compose, normalizeContainer } from './utils';
|
||||
import { defineGlobalDeps } from './utils/globalDeps';
|
||||
import { getRequireJs, type RequireJS } from './utils/requirejs';
|
||||
@ -36,6 +37,7 @@ import { WebSocketClient, WebSocketClientOptions } from './WebSocketClient';
|
||||
declare global {
|
||||
interface Window {
|
||||
define: RequireJS['define'];
|
||||
require: RequireJS['require'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,6 +55,7 @@ export interface ApplicationOptions {
|
||||
scopes?: Record<string, any>;
|
||||
router?: RouterOptions;
|
||||
pluginSettings?: Record<string, PluginSettingOptions>;
|
||||
userSettings?: Record<string, UserSettingOptions>;
|
||||
schemaSettings?: SchemaSettings[];
|
||||
schemaInitializers?: SchemaInitializer[];
|
||||
designable?: boolean;
|
||||
@ -77,6 +80,7 @@ export class Application {
|
||||
};
|
||||
public pluginManager: PluginManager;
|
||||
public pluginSettingsManager: PluginSettingsManager;
|
||||
public userSettingsManager: UserSettingsManager;
|
||||
public devDynamicImport: DevDynamicImport;
|
||||
public requirejs: RequireJS;
|
||||
public notification;
|
||||
@ -129,6 +133,7 @@ export class Application {
|
||||
this.addProviders(options.providers || []);
|
||||
this.ws = new WebSocketClient(options.ws, this);
|
||||
this.pluginSettingsManager = new PluginSettingsManager(options.pluginSettings, this);
|
||||
this.userSettingsManager = new UserSettingsManager(options.userSettings, this);
|
||||
this.addRoutes();
|
||||
this.name = this.options.name || getSubAppName(options.publicPath) || 'main';
|
||||
this.pluginContextMenu = new PluginContextMenu(options.pluginMenuItems, this);
|
||||
@ -138,7 +143,7 @@ export class Application {
|
||||
this.requirejs = getRequireJs();
|
||||
defineGlobalDeps(this.requirejs);
|
||||
window.define = this.requirejs.define;
|
||||
window.require = this.requirejs.require;
|
||||
window.require = this.requirejs.require as RequireJS['require'] & NodeRequire;
|
||||
}
|
||||
|
||||
private addDefaultProviders() {
|
||||
|
@ -27,6 +27,10 @@ export class Plugin<T = any> {
|
||||
return this.app.pluginSettingsManager;
|
||||
}
|
||||
|
||||
get userSettingsManager() {
|
||||
return this.app.userSettingsManager;
|
||||
}
|
||||
|
||||
get schemaInitializerManager() {
|
||||
return this.app.schemaInitializerManager;
|
||||
}
|
||||
|
161
packages/core/client/src/application/UserSettingsManager.ts
Normal file
161
packages/core/client/src/application/UserSettingsManager.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import React, { createElement } from 'react';
|
||||
|
||||
import { set } from 'lodash';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { Icon } from '../icon';
|
||||
import type { Application } from './Application';
|
||||
import type { RouteType } from './RouterManager';
|
||||
|
||||
export const USER_SETTINGS_KEY = 'admin.profilers.';
|
||||
export const USER_SETTINGS_PATH = '/admin/profilers/';
|
||||
export const SNIPPET_PREFIX = 'pm.';
|
||||
|
||||
export interface UserSettingOptions {
|
||||
title: any;
|
||||
/**
|
||||
* @default Outlet
|
||||
*/
|
||||
Component?: RouteType['Component'];
|
||||
icon?: string;
|
||||
/**
|
||||
* sort, the smaller the number, the higher the priority
|
||||
* @default 0
|
||||
*/
|
||||
sort?: number;
|
||||
aclSnippet?: string;
|
||||
[index: string]: any;
|
||||
}
|
||||
|
||||
export interface UserSettingsPageType {
|
||||
label?: string | React.ReactElement;
|
||||
title: string | React.ReactElement;
|
||||
key: string;
|
||||
icon: any;
|
||||
path: string;
|
||||
sort?: number;
|
||||
name?: string;
|
||||
isAllow?: boolean;
|
||||
topLevelName?: string;
|
||||
aclSnippet: string;
|
||||
children?: UserSettingsPageType[];
|
||||
[index: string]: any;
|
||||
}
|
||||
|
||||
export class UserSettingsManager {
|
||||
protected settings: Record<string, UserSettingOptions> = {};
|
||||
protected aclSnippets: string[] = [];
|
||||
public app: Application;
|
||||
private cachedList = {};
|
||||
|
||||
constructor(_userSettings: Record<string, UserSettingOptions>, app: Application) {
|
||||
this.app = app;
|
||||
Object.entries(_userSettings || {}).forEach(([name, userSettingOptions]) => {
|
||||
this.add(name, userSettingOptions);
|
||||
});
|
||||
}
|
||||
|
||||
setAclSnippets(aclSnippets: string[]) {
|
||||
this.aclSnippets = aclSnippets;
|
||||
}
|
||||
|
||||
getAclSnippet(name: string) {
|
||||
const setting = this.settings[name];
|
||||
return setting?.aclSnippet ? setting.aclSnippet : `${SNIPPET_PREFIX}${name}`;
|
||||
}
|
||||
|
||||
getRouteName(name: string) {
|
||||
return `${USER_SETTINGS_KEY}${name}`;
|
||||
}
|
||||
|
||||
getRoutePath(name: string) {
|
||||
return `${USER_SETTINGS_PATH}${name.replaceAll('.', '/')}`;
|
||||
}
|
||||
|
||||
add(name: string, options: UserSettingOptions) {
|
||||
const nameArr = name.split('.');
|
||||
const topLevelName = nameArr[0];
|
||||
this.settings[name] = {
|
||||
...this.settings[name],
|
||||
Component: Outlet,
|
||||
...options,
|
||||
name,
|
||||
topLevelName: options.topLevelName || topLevelName,
|
||||
};
|
||||
// add children
|
||||
if (nameArr.length > 1) {
|
||||
set(this.settings, nameArr.join('.children.'), this.settings[name]);
|
||||
}
|
||||
|
||||
// add route
|
||||
this.app.router.add(this.getRouteName(name), {
|
||||
path: this.getRoutePath(name),
|
||||
Component: this.settings[name].Component,
|
||||
});
|
||||
}
|
||||
|
||||
remove(name: string) {
|
||||
// delete self and children
|
||||
Object.keys(this.settings).forEach((key) => {
|
||||
if (key.startsWith(name)) {
|
||||
delete this.settings[key];
|
||||
this.app.router.remove(`${ADMIN_SETTINGS_KEY}${key}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hasAuth(name: string) {
|
||||
if (this.aclSnippets.includes(`!${this.getAclSnippet('*')}`)) return false;
|
||||
return this.aclSnippets.includes(`!${this.getAclSnippet(name)}`) === false;
|
||||
}
|
||||
|
||||
getSetting(name: string) {
|
||||
return this.settings[name];
|
||||
}
|
||||
|
||||
has(name: string) {
|
||||
const hasAuth = this.hasAuth(name);
|
||||
if (!hasAuth) return false;
|
||||
return !!this.getSetting(name);
|
||||
}
|
||||
|
||||
get(name: string, filterAuth = true): UserSettingsPageType {
|
||||
const isAllow = this.hasAuth(name);
|
||||
const userSetting = this.getSetting(name);
|
||||
if ((filterAuth && !isAllow) || !userSetting) return null;
|
||||
const children = Object.keys(userSetting.children || {})
|
||||
.sort((a, b) => a.localeCompare(b)) // sort by name
|
||||
.map((key) => this.get(userSetting.children[key].name, filterAuth))
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||
const { title, icon, aclSnippet, ...others } = userSetting;
|
||||
return {
|
||||
...others,
|
||||
aclSnippet: this.getAclSnippet(name),
|
||||
title,
|
||||
isAllow,
|
||||
label: title,
|
||||
icon: typeof icon === 'string' ? createElement(Icon, { type: icon }) : icon,
|
||||
path: this.getRoutePath(name),
|
||||
key: name,
|
||||
children: children.length ? children : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
getList(filterAuth = true): UserSettingsPageType[] {
|
||||
const cacheKey = JSON.stringify(filterAuth);
|
||||
if (this.cachedList[cacheKey]) return this.cachedList[cacheKey];
|
||||
|
||||
return (this.cachedList[cacheKey] = Array.from(
|
||||
new Set(Object.values(this.settings).map((item) => item.topLevelName)),
|
||||
)
|
||||
.sort((a, b) => a.localeCompare(b)) // sort by name
|
||||
.map((name) => this.get(name, filterAuth))
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => (a.sort || 0) - (b.sort || 0)));
|
||||
}
|
||||
|
||||
getAclSnippets() {
|
||||
return Object.keys(this.settings).map((name) => this.getAclSnippet(name));
|
||||
}
|
||||
}
|
@ -30,6 +30,8 @@ import { LocalePlugin } from './locale/LocalePlugin';
|
||||
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';
|
||||
@ -352,7 +354,8 @@ export class BuiltInPlugin extends Plugin {
|
||||
await this.app.pm.add(RemoteDocumentTitlePlugin, { name: 'remote-document-title' });
|
||||
await this.app.pm.add(PMPlugin, { name: 'builtin-pm' });
|
||||
await this.app.pm.add(CollectionPlugin, { name: 'builtin-collection' });
|
||||
await this.app.pm.add(PluginContextMenu);
|
||||
await this.app.pm.add(PluginAssistant);
|
||||
await this.app.pm.add(PluginContextMenu, { name: 'context-menu' });
|
||||
await this.app.pm.add(PluginAssistant, { name: 'drag-assistant' });
|
||||
await this.app.pm.add(UserSettingsPlugin, { name: 'user-settings' });
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
|
||||
import { ADMIN_SETTINGS_PATH } from '../../application';
|
||||
import { Plugin } from '../../application/Plugin';
|
||||
import { USER_SETTINGS_PATH } from '../../application/UserSettingsManager';
|
||||
import { BlockTemplatesPane } from '../../schema-templates';
|
||||
import { SystemSettingsPane } from '../system-settings';
|
||||
import { PluginManager } from './PluginManager';
|
||||
|
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Outlet } from 'react-router';
|
||||
|
||||
export const UserSettingsLayout = () => {
|
||||
return <Outlet />;
|
||||
};
|
14
packages/core/client/src/built-in/user-settings/index.tsx
Normal file
14
packages/core/client/src/built-in/user-settings/index.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Plugin } from '../../application/Plugin';
|
||||
import { USER_SETTINGS_PATH } from '../../application/UserSettingsManager';
|
||||
import { UserSettingsLayout } from './UserSettingsLayout';
|
||||
|
||||
export class UserSettingsPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('admin.profilers', {
|
||||
path: USER_SETTINGS_PATH,
|
||||
element: <UserSettingsLayout />,
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { Plugin } from '../../application/Plugin';
|
||||
|
||||
export class PluginWebNotification extends Plugin {
|
||||
async afterLoad() {
|
||||
// 请求用户授予权限
|
||||
if (Notification.permission !== 'denied') {
|
||||
await Notification.requestPermission();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const notification = new Notification('通知标题:', {
|
||||
body: '通知内容',
|
||||
icon: 'https://www.example.com/icon.png', // 通知的图标 URL
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Lightbox } from '@tachybase/components';
|
||||
import { connect, mapReadPretty } from '@tachybase/schema';
|
||||
|
||||
import { DeleteOutlined, DownloadOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
@ -6,9 +7,6 @@ import { Upload as AntdUpload, Button, Progress, Space, UploadFile } from 'antd'
|
||||
import cls from 'classnames';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Lightbox from 'react-image-lightbox';
|
||||
|
||||
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
|
||||
|
||||
import { ReadPretty } from '../upload/ReadPretty';
|
||||
import { isImage, toFileList, useUploadProps } from '../upload/shared';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Lightbox } from '@tachybase/components';
|
||||
import { Field, useField } from '@tachybase/schema';
|
||||
import { isString } from '@tachybase/utils/client';
|
||||
|
||||
@ -8,7 +9,6 @@ import useUploadStyle from 'antd/es/upload/style';
|
||||
import cls from 'classnames';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Lightbox from 'react-image-lightbox';
|
||||
|
||||
import { useRecord } from '../../../record-provider';
|
||||
import { isImage, isPdf, toArr, toImages } from './shared';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Lightbox } from '@tachybase/components';
|
||||
import { connect, mapProps, mapReadPretty } from '@tachybase/schema';
|
||||
|
||||
import { DeleteOutlined, DownloadOutlined, InboxOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
@ -6,9 +7,6 @@ import { Upload as AntdUpload, Button, Modal, Progress, Space, UploadFile } from
|
||||
import cls from 'classnames';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LightBox from 'react-image-lightbox';
|
||||
|
||||
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
|
||||
|
||||
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
||||
import { useProps } from '../../hooks/useProps';
|
||||
@ -201,7 +199,7 @@ Upload.Attachment = connect((props: UploadProps) => {
|
||||
</div>
|
||||
{/* 预览图片的弹框 */}
|
||||
{visible && fileType === 'image' && (
|
||||
<LightBox
|
||||
<Lightbox
|
||||
// discourageDownloads={true}
|
||||
mainSrc={images[fileIndex]?.imageUrl}
|
||||
nextSrc={images[(fileIndex + 1) % images.length]?.imageUrl}
|
||||
|
@ -23,6 +23,8 @@
|
||||
"antd": "5.19.4",
|
||||
"classnames": "^2.5.1",
|
||||
"dayjs": "^1.11.11",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-sticky-box": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -35,3 +35,4 @@ export * from './tree-select';
|
||||
export * from './upload';
|
||||
export * from './__builtins__';
|
||||
export * from './code-mirror';
|
||||
export * from './lightbox';
|
||||
|
39
packages/core/components/src/lightbox/constant.ts
Normal file
39
packages/core/components/src/lightbox/constant.ts
Normal file
@ -0,0 +1,39 @@
|
||||
// Min image zoom level
|
||||
export const MIN_ZOOM_LEVEL = 0;
|
||||
|
||||
// Max image zoom level
|
||||
export const MAX_ZOOM_LEVEL = 300;
|
||||
|
||||
// Size ratio between previous and next zoom levels
|
||||
export const ZOOM_RATIO = 1.007;
|
||||
|
||||
// How much to increase/decrease the zoom level when the zoom buttons are clicked
|
||||
export const ZOOM_BUTTON_INCREMENT_SIZE = 100;
|
||||
|
||||
// Used to judge the amount of horizontal scroll needed to initiate a image move
|
||||
export const WHEEL_MOVE_X_THRESHOLD = 200;
|
||||
|
||||
// Used to judge the amount of vertical scroll needed to initiate a zoom action
|
||||
export const WHEEL_MOVE_Y_THRESHOLD = 1;
|
||||
|
||||
export const KEYS = {
|
||||
ESC: 27,
|
||||
LEFT_ARROW: 37,
|
||||
RIGHT_ARROW: 39,
|
||||
};
|
||||
|
||||
// Actions
|
||||
export const ACTION_NONE = 0;
|
||||
export const ACTION_MOVE = 1;
|
||||
export const ACTION_SWIPE = 2;
|
||||
export const ACTION_PINCH = 3;
|
||||
export const ACTION_ROTATE = 4;
|
||||
|
||||
// Events source
|
||||
export const SOURCE_ANY = 0;
|
||||
export const SOURCE_MOUSE = 1;
|
||||
export const SOURCE_TOUCH = 2;
|
||||
export const SOURCE_POINTER = 3;
|
||||
|
||||
// Minimal swipe distance
|
||||
export const MIN_SWIPE_DISTANCE = 200;
|
3
packages/core/components/src/lightbox/index.ts
Normal file
3
packages/core/components/src/lightbox/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Lightbox from './react-image-lightbox';
|
||||
|
||||
export { Lightbox };
|
1678
packages/core/components/src/lightbox/react-image-lightbox.tsx
Normal file
1678
packages/core/components/src/lightbox/react-image-lightbox.tsx
Normal file
File diff suppressed because it is too large
Load Diff
344
packages/core/components/src/lightbox/style.css
Normal file
344
packages/core/components/src/lightbox/style.css
Normal file
@ -0,0 +1,344 @@
|
||||
@keyframes closeWindow {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ril__outer {
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
outline: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-ms-content-zooming: none;
|
||||
-ms-user-select: none;
|
||||
-ms-touch-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.ril__outerClosing {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ril__inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.ril__image,
|
||||
.ril__imagePrev,
|
||||
.ril__imageNext {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
max-width: none;
|
||||
-ms-content-zooming: none;
|
||||
-ms-user-select: none;
|
||||
-ms-touch-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.ril__imageDiscourager {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.ril__navButtons {
|
||||
border: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 20px;
|
||||
height: 34px;
|
||||
padding: 40px 30px;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.ril__navButtons:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.ril__navButtons:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ril__navButtonPrev {
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.2)
|
||||
url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDE5LDMgLTIsLTIgLTE2LDE2IDE2LDE2IDEsLTEgLTE1LC0xNSAxNSwtMTUgeiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==')
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__navButtonNext {
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.2)
|
||||
url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDEsMyAyLC0yIDE2LDE2IC0xNiwxNiAtMSwtMSAxNSwtMTUgLTE1LC0xNSB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+')
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__downloadBlocker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.ril__caption,
|
||||
.ril__toolbar {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ril__caption {
|
||||
bottom: 0;
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ril__captionContent {
|
||||
padding: 10px 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ril__toolbar {
|
||||
top: 0;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.ril__toolbarSide {
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ril__toolbarLeftSide {
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ril__toolbarRightSide {
|
||||
padding-left: 0;
|
||||
padding-right: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.ril__toolbarItem {
|
||||
display: inline-block;
|
||||
line-height: 50px;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
font-size: 120%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ril__toolbarItemChild {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ril__builtinButton {
|
||||
width: 40px;
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.ril__builtinButton:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.ril__builtinButton:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ril__builtinButtonDisabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.ril__builtinButtonDisabled:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.ril__closeButton {
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj48cGF0aCBkPSJtIDEsMyAxLjI1LC0xLjI1IDcuNSw3LjUgNy41LC03LjUgMS4yNSwxLjI1IC03LjUsNy41IDcuNSw3LjUgLTEuMjUsMS4yNSAtNy41LC03LjUgLTcuNSw3LjUgLTEuMjUsLTEuMjUgNy41LC03LjUgLTcuNSwtNy41IHoiIGZpbGw9IiNGRkYiLz48L3N2Zz4=')
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__zoomInButton {
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PHBhdGggZD0iTTEyIDV2NiIvPjwvZz48Y2lyY2xlIGN4PSIxMiIgY3k9IjgiIHI9IjciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+')
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__zoomOutButton {
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PC9nPjxjaXJjbGUgY3g9IjEyIiBjeT0iOCIgcj0iNyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=')
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__outerAnimating {
|
||||
animation-name: closeWindow;
|
||||
}
|
||||
|
||||
@keyframes pointFade {
|
||||
0%,
|
||||
19.999%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ril__loadingCircle {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ril__loadingCirclePoint {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.ril__loadingCirclePoint::before {
|
||||
content: '';
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 11%;
|
||||
height: 30%;
|
||||
background-color: #fff;
|
||||
border-radius: 30%;
|
||||
animation: pointFade 800ms infinite ease-in-out both;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(1) {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(1)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(7)::before {
|
||||
animation-delay: -800ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(2) {
|
||||
transform: rotate(30deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(8) {
|
||||
transform: rotate(210deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(2)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(8)::before {
|
||||
animation-delay: -666ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(3) {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(9) {
|
||||
transform: rotate(240deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(3)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(9)::before {
|
||||
animation-delay: -533ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(4) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(10) {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(4)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(10)::before {
|
||||
animation-delay: -400ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(5) {
|
||||
transform: rotate(120deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(11) {
|
||||
transform: rotate(300deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(5)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(11)::before {
|
||||
animation-delay: -266ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(6) {
|
||||
transform: rotate(150deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(12) {
|
||||
transform: rotate(330deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(6)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(12)::before {
|
||||
animation-delay: -133ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(13) {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(13)::before {
|
||||
animation-delay: 0ms;
|
||||
}
|
||||
|
||||
.ril__loadingContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.ril__imagePrev .ril__loadingContainer,
|
||||
.ril__imageNext .ril__loadingContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ril__errorContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
.ril__imagePrev .ril__errorContainer,
|
||||
.ril__imageNext .ril__errorContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ril__loadingContainer__icon {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
52
packages/core/components/src/lightbox/util.ts
Normal file
52
packages/core/components/src/lightbox/util.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Placeholder for future translate functionality
|
||||
*/
|
||||
export function translate(str, replaceStrings = null) {
|
||||
if (!str) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let translated = str;
|
||||
if (replaceStrings) {
|
||||
Object.keys(replaceStrings).forEach((placeholder) => {
|
||||
translated = translated.replace(placeholder, replaceStrings[placeholder]);
|
||||
});
|
||||
}
|
||||
|
||||
return translated;
|
||||
}
|
||||
|
||||
export function getWindowWidth() {
|
||||
return typeof global.window !== 'undefined' ? global.window.innerWidth : 0;
|
||||
}
|
||||
|
||||
export function getWindowHeight() {
|
||||
return typeof global.window !== 'undefined' ? global.window.innerHeight : 0;
|
||||
}
|
||||
|
||||
const isCrossOriginFrame = () => {
|
||||
try {
|
||||
return global.window.location.hostname !== global.window.parent.location.hostname;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Get the highest window context that isn't cross-origin
|
||||
// (When in an iframe)
|
||||
export function getHighestSafeWindowContext(self = global.window.self) {
|
||||
// If we reached the top level, return self
|
||||
if (self === global.window.top) {
|
||||
return self;
|
||||
}
|
||||
|
||||
// If parent is the same origin, we can move up one context
|
||||
// Reference: https://stackoverflow.com/a/21965342/1601953
|
||||
if (!isCrossOriginFrame()) {
|
||||
return getHighestSafeWindowContext(self.parent);
|
||||
}
|
||||
|
||||
// If a different origin, we consider the current level
|
||||
// as the top reachable one
|
||||
return self;
|
||||
}
|
@ -130,7 +130,7 @@ class AppGenerator extends Generator {
|
||||
dependencies: dependencies.join(`,\n `),
|
||||
envs: envs.join(`\n`),
|
||||
env: {
|
||||
APP_PORT: 13000,
|
||||
APP_PORT: 3000,
|
||||
APP_ENV: 'development',
|
||||
DB_DIALECT: dbDialect,
|
||||
APP_KEY: crypto.randomBytes(256).toString('base64'),
|
||||
|
@ -1,6 +1,6 @@
|
||||
################# TACHYBASE APPLICATION #################
|
||||
|
||||
# !!! When `APP_ENV=production`, opening http://localhost:13000/ will show "Not Found".
|
||||
# !!! When `APP_ENV=production`, opening http://localhost:3000/ will show "Not Found".
|
||||
# !!! It is recommended to use nginx to proxy static files. For example https://github.com/tachybase/tachybase/blob/main/docker/tachybase/tachybase.conf
|
||||
APP_ENV=development
|
||||
APP_PORT=20000
|
||||
|
@ -1,5 +1,5 @@
|
||||
APP_ENV=development
|
||||
APP_PORT=13000
|
||||
APP_PORT=3000
|
||||
APP_KEY=test-key
|
||||
|
||||
API_BASE_PATH=/api/
|
||||
|
@ -304,7 +304,7 @@ export class Gateway extends EventEmitter {
|
||||
await this.watch();
|
||||
|
||||
const startOptions = this.getStartOptions();
|
||||
const port = startOptions.port || process.env.APP_PORT || 13000;
|
||||
const port = startOptions.port || process.env.APP_PORT || 3000;
|
||||
const host = startOptions.host || process.env.APP_HOST || '0.0.0.0';
|
||||
|
||||
this.start({
|
||||
|
@ -5,7 +5,7 @@ import { Configuration } from './Configuration';
|
||||
|
||||
export class PluginAPIKeysClient extends Plugin {
|
||||
async load() {
|
||||
this.pluginSettingsManager.add(NAMESPACE, {
|
||||
this.userSettingsManager.add(NAMESPACE, {
|
||||
icon: 'KeyOutlined',
|
||||
title: this.t('API keys'),
|
||||
Component: Configuration,
|
||||
|
@ -11,7 +11,6 @@
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^5.3.7",
|
||||
"@hapi/topo": "^6.0.0",
|
||||
"@koa/multer": "^3.0.2",
|
||||
"@tachybase/components": "workspace:*",
|
||||
"@types/archiver": "^5.3.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
|
@ -32,7 +32,7 @@ export const Options = () => {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
tooltip: generateNTemplate(
|
||||
'The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:13000',
|
||||
'The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:3000',
|
||||
),
|
||||
},
|
||||
required: true,
|
||||
|
@ -3,5 +3,5 @@
|
||||
"User will be registered automatically if not exists.": "User will be registered automatically if not exists.",
|
||||
"Sign up automatically when the user does not exist": "Sign up automatically when the user does not exist",
|
||||
"Service domain": "Service domain",
|
||||
"The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:13000": "The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:13000"
|
||||
"The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:3000": "The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:3000"
|
||||
}
|
||||
|
@ -3,6 +3,6 @@
|
||||
"User will be registered automatically if not exists.": "사용자가 존재하지 않으면 자동으로 등록됩니다.",
|
||||
"Sign up automatically when the user does not exist": "사용자가 존재하지 않을 경우 자동으로 가입",
|
||||
"Service domain": "서비스 도메인",
|
||||
"The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:13000":
|
||||
"도메인은 일반적으로 서버의 주소입니다. 로컬 개발에서는 로컬 머신의 주소를 사용할 수 있습니다. 예: http://localhost:13000"
|
||||
"The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:3000":
|
||||
"도메인은 일반적으로 서버의 주소입니다. 로컬 개발에서는 로컬 머신의 주소를 사용할 수 있습니다. 예: http://localhost:3000"
|
||||
}
|
||||
|
@ -3,5 +3,5 @@
|
||||
"User will be registered automatically if not exists.": "用户不存在时将自动注册。",
|
||||
"Sign up automatically when the user does not exist": "用户不存在时自动注册",
|
||||
"Service domain": "服务域名",
|
||||
"The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:13000": "域名通常是你的服务器地址,在本地开发时,可以使用你本机的地址,例如:http://localhost:13000"
|
||||
"The domain is usually the address of your server, in local development, you can use the address of your local machine, such as: http://localhost:3000": "域名通常是你的服务器地址,在本地开发时,可以使用你本机的地址,例如:http://localhost:3000"
|
||||
}
|
||||
|
@ -12,7 +12,6 @@
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^5.3.7",
|
||||
"@aws-sdk/client-s3": "^3.245.0",
|
||||
"@koa/multer": "^3.0.0",
|
||||
"@tachybase/components": "workspace:*",
|
||||
"@tachybase/schema": "workspace:*",
|
||||
"@types/koa-multer": "^1.0.1",
|
||||
|
@ -4,7 +4,7 @@ import path from 'path';
|
||||
import { getApp } from '.';
|
||||
import { FILE_FIELD_NAME, STORAGE_TYPE_LOCAL } from '../constants';
|
||||
|
||||
const { LOCAL_STORAGE_BASE_URL, LOCAL_STORAGE_DEST = 'storage/uploads', APP_PORT = '13000' } = process.env;
|
||||
const { LOCAL_STORAGE_BASE_URL, LOCAL_STORAGE_DEST = 'storage/uploads', APP_PORT = '3000' } = process.env;
|
||||
|
||||
const DEFAULT_LOCAL_BASE_URL = LOCAL_STORAGE_BASE_URL || `/storage/uploads`;
|
||||
|
||||
|
@ -40,6 +40,6 @@ export default class extends Migration {
|
||||
}
|
||||
const c = this.db.getCollection('attachments');
|
||||
const table = c.getTableNameWithSchemaAsString();
|
||||
await this.db.sequelize.query(`update ${table} set url = replace(url, 'http://localhost:13000', '')`);
|
||||
await this.db.sequelize.query(`update ${table} set url = replace(url, 'http://localhost:3000', '')`);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
"main": "./dist/server/index.js",
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "~5.3.7",
|
||||
"@koa/multer": "^3.0.2",
|
||||
"@tachybase/components": "workspace:*",
|
||||
"@tachybase/schema": "workspace:*",
|
||||
"@types/node-xlsx": "^0.15.1",
|
||||
|
2
packages/plugins/@tachybase/plugin-messages/.npmignore
Normal file
2
packages/plugins/@tachybase/plugin-messages/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/src
|
1
packages/plugins/@tachybase/plugin-messages/README.md
Normal file
1
packages/plugins/@tachybase/plugin-messages/README.md
Normal file
@ -0,0 +1 @@
|
||||
# @tachybase/plugin-messages
|
2
packages/plugins/@tachybase/plugin-messages/client.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-messages/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/client';
|
||||
export { default } from './dist/client';
|
1
packages/plugins/@tachybase/plugin-messages/client.js
Normal file
1
packages/plugins/@tachybase/plugin-messages/client.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client/index.js');
|
12
packages/plugins/@tachybase/plugin-messages/package.json
Normal file
12
packages/plugins/@tachybase/plugin-messages/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@tachybase/plugin-messages",
|
||||
"version": "0.21.82",
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
"@tachybase/database": "workspace:*",
|
||||
"@tachybase/server": "workspace:*",
|
||||
"@tachybase/test": "workspace:*"
|
||||
}
|
||||
}
|
2
packages/plugins/@tachybase/plugin-messages/server.d.ts
vendored
Normal file
2
packages/plugins/@tachybase/plugin-messages/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/server';
|
||||
export { default } from './dist/server';
|
1
packages/plugins/@tachybase/plugin-messages/server.js
Normal file
1
packages/plugins/@tachybase/plugin-messages/server.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/server/index.js');
|
@ -0,0 +1,21 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
|
||||
export class PluginMessagesClient extends Plugin {
|
||||
async afterAdd() {
|
||||
// await this.app.pm.add()
|
||||
}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
// You can get and modify the app instance here
|
||||
async load() {
|
||||
console.log(this.app);
|
||||
// this.app.addComponents({})
|
||||
// this.app.addScopes({})
|
||||
// this.app.addProvider()
|
||||
// this.app.addProviders()
|
||||
// this.app.router.add()
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginMessagesClient;
|
2
packages/plugins/@tachybase/plugin-messages/src/index.ts
Normal file
2
packages/plugins/@tachybase/plugin-messages/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './server';
|
||||
export { default } from './server';
|
@ -0,0 +1,79 @@
|
||||
import { defineCollection } from '@tachybase/database';
|
||||
|
||||
export default defineCollection({
|
||||
dumpRules: 'required',
|
||||
name: 'messages',
|
||||
sortable: true,
|
||||
createdBy: true,
|
||||
updatedBy: true,
|
||||
logging: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'bigInt',
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
interface: 'id',
|
||||
},
|
||||
{
|
||||
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: 'read',
|
||||
type: 'boolean',
|
||||
interface: 'checkbox',
|
||||
description: '{{ t("Read") }}',
|
||||
uiSchema: {
|
||||
'x-component-props': { showUnchecked: true },
|
||||
type: 'boolean',
|
||||
'x-component': 'Checkbox',
|
||||
title: '{{ t("Read") }}',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'userId',
|
||||
type: 'bigInt',
|
||||
interface: 'integer',
|
||||
isForeignKey: true,
|
||||
uiSchema: { type: 'number', title: 'userId', 'x-component': 'InputNumber', 'x-read-pretty': true },
|
||||
},
|
||||
{
|
||||
foreignKey: 'userId',
|
||||
onDelete: 'CASCADE',
|
||||
name: 'user',
|
||||
type: 'belongsTo',
|
||||
interface: 'm2o',
|
||||
uiSchema: {
|
||||
'x-component': 'AssociationField',
|
||||
'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } },
|
||||
title: '用户',
|
||||
},
|
||||
target: 'users',
|
||||
targetKey: 'id',
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
import { extendCollection } from '@tachybase/database';
|
||||
|
||||
export default extendCollection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'json',
|
||||
// 订阅偏好设置
|
||||
name: 'subPrefs',
|
||||
allowNull: false,
|
||||
defaultValue: {},
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
@ -0,0 +1,19 @@
|
||||
import { Plugin } from '@tachybase/server';
|
||||
|
||||
export class PluginMessagesServer extends Plugin {
|
||||
async afterAdd() {}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
async load() {}
|
||||
|
||||
async install() {}
|
||||
|
||||
async afterEnable() {}
|
||||
|
||||
async afterDisable() {}
|
||||
|
||||
async remove() {}
|
||||
}
|
||||
|
||||
export default PluginMessagesServer;
|
@ -10,7 +10,7 @@ English | [中文](./README.zh-CN.md)
|
||||
|
||||
### 1、启用主题插件
|
||||
|
||||
首先把 TachyBase 更新到最新版本,然后会在[插件管理页面](http://localhost:13000/admin/pm/list/local/)看到 `theme-editor` 的卡片,点击卡片右下角的 `启用` 按钮等待页面刷新。
|
||||
首先把 TachyBase 更新到最新版本,然后会在[插件管理页面](http://localhost:3000/admin/pm/list/local/)看到 `theme-editor` 的卡片,点击卡片右下角的 `启用` 按钮等待页面刷新。
|
||||
|
||||
<a href="https://sm.ms/image/EkiMxUpngzAb8yo" target="_blank"><img src="https://s2.loli.net/2023/07/19/EkiMxUpngzAb8yo.png" height="400" style="object-fit: contain;"></a>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
### 1、启用主题插件
|
||||
|
||||
首先把 TachyBase 更新到最新版本,然后会在[插件管理页面](http://localhost:13000/admin/pm/list/local/)看到 `theme-editor` 的卡片,点击卡片右下角的 `启用` 按钮等待页面刷新。
|
||||
首先把 TachyBase 更新到最新版本,然后会在[插件管理页面](http://localhost:3000/admin/pm/list/local/)看到 `theme-editor` 的卡片,点击卡片右下角的 `启用` 按钮等待页面刷新。
|
||||
|
||||
<a href="https://sm.ms/image/EkiMxUpngzAb8yo" target="_blank"><img src="https://s2.loli.net/2023/07/19/EkiMxUpngzAb8yo.png" height="400" style="object-fit: contain;"></a>
|
||||
|
||||
|
@ -37,6 +37,7 @@
|
||||
"@tachybase/plugin-localization-management": "workspace:*",
|
||||
"@tachybase/plugin-logger": "workspace:*",
|
||||
"@tachybase/plugin-map": "workspace:*",
|
||||
"@tachybase/plugin-messages": "workspace:*",
|
||||
"@tachybase/plugin-mobile-client": "workspace:*",
|
||||
"@tachybase/plugin-mock-collections": "workspace:*",
|
||||
"@tachybase/plugin-multi-app-manager": "workspace:*",
|
||||
|
@ -16,6 +16,7 @@ export class PresetTachyBase extends Plugin {
|
||||
'verification',
|
||||
'users',
|
||||
'acl',
|
||||
'messages',
|
||||
// optional plugins, default enabled
|
||||
'action-bulk-edit',
|
||||
'action-bulk-update',
|
||||
|
@ -614,9 +614,6 @@ importers:
|
||||
react-iframe:
|
||||
specifier: ~1.8.5
|
||||
version: 1.8.5(react@18.3.1)
|
||||
react-image-lightbox:
|
||||
specifier: ^5.1.4
|
||||
version: 5.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-intersection-observer:
|
||||
specifier: ^9.10.3
|
||||
version: 9.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@ -726,6 +723,12 @@ importers:
|
||||
dayjs:
|
||||
specifier: 1.11.10
|
||||
version: 1.11.10
|
||||
prop-types:
|
||||
specifier: ^15.8.1
|
||||
version: 15.8.1
|
||||
react-modal:
|
||||
specifier: ^3.16.1
|
||||
version: 3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-sticky-box:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(react@18.3.1)
|
||||
@ -2171,9 +2174,6 @@ importers:
|
||||
'@hapi/topo':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.2
|
||||
'@koa/multer':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2(multer@1.4.5-lts.1)
|
||||
'@tachybase/components':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/components
|
||||
@ -2873,9 +2873,6 @@ importers:
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: ^3.245.0
|
||||
version: 3.474.0
|
||||
'@koa/multer':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.2(multer@1.4.5-lts.1)
|
||||
'@tachybase/components':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/components
|
||||
@ -3129,9 +3126,6 @@ importers:
|
||||
'@ant-design/icons':
|
||||
specifier: ~5.3.7
|
||||
version: 5.3.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@koa/multer':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2(multer@1.4.5-lts.1)
|
||||
'@tachybase/components':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/components
|
||||
@ -3356,6 +3350,21 @@ importers:
|
||||
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-messages:
|
||||
dependencies:
|
||||
'@tachybase/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/client
|
||||
'@tachybase/database':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/database
|
||||
'@tachybase/server':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/server
|
||||
'@tachybase/test':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/test
|
||||
|
||||
packages/plugins/@tachybase/plugin-mobile-client:
|
||||
dependencies:
|
||||
'@tachybase/client':
|
||||
@ -4464,6 +4473,9 @@ importers:
|
||||
'@tachybase/plugin-map':
|
||||
specifier: workspace:*
|
||||
version: link:../../plugins/@tachybase/plugin-map
|
||||
'@tachybase/plugin-messages':
|
||||
specifier: workspace:*
|
||||
version: link:../../plugins/@tachybase/plugin-messages
|
||||
'@tachybase/plugin-mobile-client':
|
||||
specifier: workspace:*
|
||||
version: link:../../plugins/@tachybase/plugin-mobile-client
|
||||
@ -17408,13 +17420,6 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>=16.x.x'
|
||||
|
||||
react-image-lightbox@5.1.4:
|
||||
resolution: {integrity: sha512-kTiAODz091bgT7SlWNHab0LSMZAPJtlNWDGKv7pLlLY1krmf7FuG1zxE0wyPpeA8gPdwfr3cu6sPwZRqWsc3Eg==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
peerDependencies:
|
||||
react: 16.x || 17.x
|
||||
react-dom: 16.x || 17.x
|
||||
|
||||
react-intersection-observer@9.10.3:
|
||||
resolution: {integrity: sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA==}
|
||||
peerDependencies:
|
||||
@ -36110,13 +36115,6 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
react: 18.3.1
|
||||
|
||||
react-image-lightbox@5.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-modal: 3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
react-intersection-observer@9.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
Loading…
Reference in New Issue
Block a user