nocobase/packages/core/client/src/application/PluginSettingsManager.ts
jack zhang 61e9dd5cc1
feat: plugin mobile v2 (#4777)
* feat: init

* fix: mobile layout

* feat: more code

* feat: improve navigate bar

* fix: mobile title

* feat: improve code

* fix: add settings and initailzer

* fix: settings

* fix: tabbar items settings

* feat: tabbar initializer

* fix: api

* fix: styles

* feat: navbar

* feat: navigate bar tabs initializer

* feat: navigate bar tab settings

* feat: navigation bar actions

* fix: bug

* fix: bug

* fix: bug

* fix: tabbar active

* fix: bug

* fix: mobile login and layout

* fix: update version

* fix: build error

* feat: plugin settings support link

* fix: add mobile meta

* fix: desktop mode

* fix: remove old code and change collection name and mobile path

* fix: tabbar and tabs initialer layout

* fix: initializer style

* fix: adjust schema position

* fix: mobile style

* fix: delete relation resource and home page bug

* fix: support multi app

* fix: not found page

* fix: js bridge

* fix: bug

* fix: navigation bar schema flat

* fix: navigation bar action style

* fix: change version

* fix: mobile meta and real mobile test

* refactor: folder and name

* fix: navigation bar sticky and zIndex

* fix: full mobile schema

* fix: mobile readme and package.json

* fix: e2e bug

* fix: bug

* fix: tabbar style on productino

* fix: bug

* fix: rename MobileTabBar.Page

* fix: support tabbar sort

* fix: support  page tabs sort

* fix: i18n

* fix: settings utils import bug

* docs: api doc

* fix: qrcode refresh

* test: unit tests

* fix: bug

* fix: unit test

* fix: build bug

* fix: e2e test

* fix: overflow scroll

* fix: bug

* fix: scroll and overflow

* fix: bug

* fix: e2e expect await

* fix: e2e bug

* fix: bug

* fix: change name

* fix: add more e2e

* fix: page header

* fix: tab support icon

* fix: bug

* fix: bug

* fix: docs

* fix(T-4811): scroll bar too long

* fix(T-4810): desktop mode

* fix: e2e

* fix(T-4812): title empty

* fix: unit test

* feat: hide Open mode option in mobile mode

* feat: change default value of Open mode on mobile

* feat: add OpenModeProvider

* feat: support page mode

* fix: fix build

* test: update unit tests

* chore: remove pro-plugins

* fix: bug

* fix(T-4812): title is required

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* refactor: remove z-index

* refactor: make better for subpages

* fix: drag bug

* fix: bug

* fix: theme bug

* fix(T-4859): create tab bar title empty

* fix(T-4857): action too long

* fix: e2e bug

* fix: remove comment

* fix: bug

* fix: theme bug

* fix: should provider modal component

* fix: bug

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
Co-authored-by: Zeke Zhang <958414905@qq.com>
2024-07-22 14:06:36 +08:00

181 lines
5.0 KiB
TypeScript

/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { set } from 'lodash';
import React, { createElement } from 'react';
import { Outlet } from 'react-router-dom';
import { Icon } from '../icon';
import type { Application } from './Application';
import type { RouteType } from './RouterManager';
export const ADMIN_SETTINGS_KEY = 'admin.settings.';
export const ADMIN_SETTINGS_PATH = '/admin/settings/';
export const SNIPPET_PREFIX = 'pm.';
export interface PluginSettingOptions {
title: any;
/**
* @default Outlet
*/
Component?: RouteType['Component'];
icon?: string;
/**
* sort, the smaller the number, the higher the priority
* @default 0
*/
sort?: number;
aclSnippet?: string;
link?: string;
[index: string]: any;
}
export interface PluginSettingsPageType {
label?: string | React.ReactElement;
title: string | React.ReactElement;
link?: string;
key: string;
icon: any;
path: string;
sort?: number;
name?: string;
isAllow?: boolean;
topLevelName?: string;
aclSnippet: string;
children?: PluginSettingsPageType[];
[index: string]: any;
}
export class PluginSettingsManager {
protected settings: Record<string, PluginSettingOptions> = {};
protected aclSnippets: string[] = [];
public app: Application;
private cachedList = {};
constructor(_pluginSettings: Record<string, PluginSettingOptions>, app: Application) {
this.app = app;
Object.entries(_pluginSettings || {}).forEach(([name, pluginSettingOptions]) => {
this.add(name, pluginSettingOptions);
});
}
clearCache() {
this.cachedList = {};
}
setAclSnippets(aclSnippets: string[]) {
this.aclSnippets = aclSnippets;
}
getAclSnippet(name: string) {
const setting = this.settings[name];
if (setting?.skipAclConfigure) {
return null;
}
return setting?.aclSnippet ? setting.aclSnippet : `${SNIPPET_PREFIX}${name}`;
}
getRouteName(name: string) {
return `${ADMIN_SETTINGS_KEY}${name}`;
}
getRoutePath(name: string) {
return `${ADMIN_SETTINGS_PATH}${name.replaceAll('.', '/')}`;
}
add(name: string, options: PluginSettingOptions) {
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): PluginSettingsPageType {
const isAllow = this.hasAuth(name);
const pluginSetting = this.getSetting(name);
if ((filterAuth && !isAllow) || !pluginSetting) return null;
const children = Object.keys(pluginSetting.children || {})
.sort((a, b) => a.localeCompare(b)) // sort by name
.map((key) => this.get(pluginSetting.children[key].name, filterAuth))
.filter(Boolean)
.sort((a, b) => (a.sort || 0) - (b.sort || 0));
const { title, icon, aclSnippet, ...others } = pluginSetting;
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): PluginSettingsPageType[] {
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))
.filter(Boolean);
}
}