tachybase_todo/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/utils.tsx
jack zhang 705b7449f0
feat: new plugin manager, supports adding plugins through UI (#2430)
* refactor: plugin manager page

* fix: bug

* feat: addByNpm api

* fix: improve the addByNpm

* feat: improve applicationPlugins:list api

* fix: re-download npm package when restart app

* fix: plugin delete api

* feat: plugin detail api

* feat: zipUrl add api

* fix: upload api bug

* fix: plugin detail info

* feat: upgrade api

* fix: upload api

* feat: handle plugin load error

* feat: support authToken

* feat: muti lang

* fix: build error

* fix: self review

* Update plugin-manager.ts

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bugs

* fix: detail click and remove isOfficial

* fix: upgrade no refresh

* fix: file size and type check

* fix: bug

* fix: upgrade error

* fix: bug

* fix: bug

* fix: plugin card layout

* fix: handling exceptional cases

* fix: tgz file support

* fix: macos compress file

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: add upgrade npm type

* fix: bugs

* fix: bug

* fix: change plugins static expose url

* fix: api prefix

* fix: bug

* fix: add nginx `/static/plugin/` path

* fix: bugs and pr docker build no dts

* fix: bug

* fix: build tools bug

* fix: improve code

* fix: build bug

* feat: improve plugin info

* fix: ui bug

* fix: plugin document bug

* feat: improve code

* feat: improve code

* feat: process dev deps check

* feat: improve code

* feat: process.env.IS_DEV_CMD

* fix: do not delete the plugin package

* feat: plugin symlink

* fix: tsx watch --ignore=./storage/plugins/**

* fix: test error

* fix: improve code

* fix: improve code

* fix: emitStartedEvent

* fix: improve code

* fix: type error

* fix: test error

* test: console.log

* fix: createStoragePluginSymLink

* fix: clientStaticMiddleware rename to clientStaticUtils

* feat: build tools support plugins folder

* fix: 350px

* fix: error

* feat: client dev support plugin folder

* fix: clear cli options

* fix: typeError: Converting circular structure to JSON

* fix: plugin name

* chore: restart application after command

* feat: upgrade error & docs

* Update v14-changelog.md

* Update v14-changelog.md

* Update v14-changelog.md

* fix: gateway test

* refactor(plugin-workflow): add ready state for gracefully tearing down

* Revert "chore: restart application after command"

This reverts commit 5015274f8e4e06e506e15754b672330330e8c7f8.

* chore: stop application whe restart

* T 1218 change plugin folder (#2629)

* feat: change folder name

* feat: change `pm create` command

* feat:  revert plugin name change

* fix: delete samples

* feat: change plugins folder

* fix: pm create

* feat: update docs

* fix: link package error

* fix: docs

* fix: create command

* fix: pm add error

* fix: create  add build

* fix: pm creatre + add

* feat: add tar command

* fix: docs

* fix: bug

* fix: docs

---------

Co-authored-by: chenos <chenlinxh@gmail.com>

* feat: docs

* Update your-fisrt-plugin.md

* Update your-fisrt-plugin.md

* chore: application reload

* chore: test

* fix: pm add error

* chore: preset install skip exists plugin

* fix: createIfNotExists

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
Co-authored-by: chareice <chareice@live.com>
Co-authored-by: Zhou <zhou.working@gmail.com>
Co-authored-by: mytharcher <mytharcher@gmail.com>
2023-09-12 22:39:23 +08:00

559 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import lodash from 'lodash';
import { useTranslation } from 'react-i18next';
const { groupBy, reduce, uniq, uniqBy } = lodash;
const shape = {
ER: 'er-rect',
EDGE: 'edge',
};
export const useGCMTranslation = () => {
return useTranslation('graph-collection-manager');
};
export const getInheritCollections = (collections, name) => {
const parents = [];
const getParents = (name) => {
const collection = collections?.find((collection) => collection.name === name);
if (collection) {
const { inherits } = collection;
if (inherits) {
for (let index = 0; index < inherits.length; index++) {
const collectionKey = inherits[index];
parents.push(collectionKey);
getParents(collectionKey);
}
}
}
return uniq(parents);
};
return getParents(name);
};
export const getChildrenCollections = (collections, name) => {
const childrens = [];
const getChildrens = (name) => {
const inheritCollections = collections.filter((v) => {
return v.inherits?.includes(name);
});
inheritCollections.forEach((v) => {
const collectionKey = v.name;
childrens.push(v);
return getChildrens(collectionKey);
});
return childrens;
};
return getChildrens(name);
};
export const formatData = (data) => {
const edgeData = [];
const targetTablekeys = [];
const tableData = data.map((item, index) => {
const ports = [];
const totalFields = [...item.fields];
const inheritCollections = getInheritCollections(data, item.name);
const inheritedFields = reduce(
inheritCollections,
(result, value) => {
const arr = result;
const parentFields = data
.find((k) => k.name === value)
?.fields.map((v) => {
return { ...v, sourceCollectionName: item.name };
});
return parentFields ? arr.concat(parentFields) : arr;
},
[],
);
uniqBy(totalFields.concat(inheritedFields), 'name').forEach((field) => {
field.uiSchema &&
ports.push({
id: field.name,
group: 'list',
...field,
});
['obo', 'oho', 'o2o', 'o2m', 'm2o', 'm2m', 'linkTo'].includes(field.interface) && edgeData.push(field);
});
targetTablekeys.push(item.name);
const portsData = formatPortData(ports);
return {
id: item.name,
shape: shape.ER,
name: item.name,
title: item.title,
width: 250,
// height: 40 * portsData.initPorts?.length || 40,
ports: [...(portsData.initPorts || []), ...(portsData.morePorts || [])],
item: item,
};
});
const edges = formatRelationEdgeData(edgeData, targetTablekeys, tableData);
const inheritEdges = formatInheritEdgeData(data);
return { nodesData: tableData, edgesData: edges, inheritEdges };
};
export const formatPortData = (ports) => {
const portsData = groupBy(ports, (v) => {
if (
v.isForeignKey ||
v.primaryKey ||
['obo', 'oho', 'o2o', 'o2m', 'm2o', 'm2m', 'linkTo', 'id'].includes(v.interface)
) {
return 'initPorts';
} else {
return 'morePorts';
}
});
return portsData;
};
export const formatInheritEdgeData = (collections) => {
const commonAttrs = {
attrs: {
line: {
strokeWidth: 1,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
stroke: '#ddd',
sourceMarker: null,
},
},
router: {
name: 'smooth',
args: {
direction: 'H',
},
},
labels: [
{
markup: [
{
tagName: 'ellipse',
selector: 'labelBody',
style: {
cursor: 'pointer',
},
},
{
tagName: 'text',
selector: 'labelText',
style: {
cursor: 'pointer',
},
},
],
attrs: {
labelText: {
text: 'inherits',
fill: 'rgba(0, 0, 0, 0.3)',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
labelBody: {
ref: 'labelText',
refWidth: '100%',
refHeight: '100%',
fill: 'var(--nb-box-bg)',
},
},
position: {
distance: 0.5,
args: {
keepGradient: true,
ensureLegibility: true,
},
},
},
],
};
const inheritEdges = [];
collections.forEach((v) => {
const parentCollectonKeys = v.inherits || [];
if (parentCollectonKeys.length) {
parentCollectonKeys.forEach((k) => {
inheritEdges.push({
id: v.name + k,
source: {
cell: v.name,
connectionPoint: 'rect',
},
target: {
cell: k,
connectionPoint: 'rect',
},
connector: {
name: 'normal',
zIndex: 1000,
},
connectionType: 'inherited',
...commonAttrs,
});
});
}
});
return inheritEdges;
};
const formatRelationEdgeData = (data, targetTables, tableData) => {
const edges = [];
for (let i = 0; i < data.length; i++) {
if (targetTables.includes(data[i].target)) {
const targetTable = tableData.find((v) => v.name === data[i].target);
const sourceTable = tableData.find((v) => v.name === (data[i].sourceCollectionName || data[i].collectionName));
const commonAttrs = {
attrs: {
line: {
strokeWidth: 1,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
stroke: '#ddd',
sourceMarker: null,
targetMarker: null,
},
},
router:
sourceTable.id === targetTable.id
? {
name: 'oneSide',
args: {
side: 'left',
},
}
: {
name: 'er',
args: {
direction: 'H',
},
},
labels: [
{
markup: [
{
tagName: 'ellipse',
selector: 'labelBody',
style: {
cursor: 'pointer',
},
},
{
tagName: 'text',
selector: 'labelText',
style: {
cursor: 'pointer',
},
},
],
attrs: {
labelText: {
text: getRelationship(data[i].interface)[0],
fill: 'rgba(0, 0, 0, 0.3)',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
labelBody: {
ref: 'labelText',
refWidth: '100%',
refHeight: '100%',
stroke: '#ddd',
fill: 'var(--nb-box-bg)',
strokeWidth: 1,
rx: 10,
ry: 10,
},
},
position: {
distance: 0.3,
args: {
keepGradient: true,
ensureLegibility: true,
},
},
},
{
markup: [
{
tagName: 'ellipse',
selector: 'labelBody',
style: {
cursor: 'pointer',
},
},
{
tagName: 'text',
selector: 'labelText',
style: {
cursor: 'pointer',
},
},
],
attrs: {
labelText: {
text: getRelationship(data[i].interface)[1],
fill: 'rgba(0, 0, 0, 0.3)',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
labelBody: {
ref: 'labelText',
refWidth: '100%',
refHeight: '100%',
stroke: '#ddd',
fill: 'var(--nb-box-bg)',
rx: 10,
ry: 10,
strokeWidth: 1,
},
},
position: {
distance: 0.7,
args: {
keepGradient: true,
ensureLegibility: true,
},
},
},
],
connector: {
name: 'normal',
zIndex: 1000,
},
};
const isuniq = (id) => {
const targetEdge = edges.find((v) => v.id === id);
if (targetEdge) {
targetEdge.associated.push(data[i].name);
return false;
}
return true;
};
if (['m2m', 'linkTo'].includes(data[i].interface)) {
const throughTable = tableData.find((v) => v.name === data[i].through);
if (throughTable) {
const sCellId1 = sourceTable.id;
const tCellId1 = throughTable.id;
const sPortId1 = sourceTable.ports.find((v) => v.name === data[i].sourceKey)?.id;
const tPortId1 = throughTable.ports.find((v) => v.name === data[i].foreignKey)?.id;
const sCellId2 = targetTable.id;
const tCellId2 = throughTable.id;
const sPortId2 = targetTable.ports.find((v) => v.name === data[i].targetKey)?.id;
const tPortId2 = throughTable.ports.find((v) => v.name === data[i].otherKey)?.id;
const id1 = sCellId1 + sPortId1 + tCellId1 + tPortId1;
const id2 = sCellId2 + sPortId2 + tCellId2 + tPortId2;
edges.push({
id: id1,
source: {
cell: sCellId1,
port: sPortId1,
anchor: {
name: 'right',
},
},
target: {
cell: tCellId1,
port: tPortId1,
anchor: {
name: 'left',
},
},
associated: [data[i].name],
m2m: [id1, id2],
...commonAttrs,
});
edges.push({
id: id2,
source: {
cell: sCellId2,
port: sPortId2,
anchor: {
name: 'right',
},
},
target: {
cell: tCellId2,
port: tPortId2,
anchor: {
name: 'left',
},
},
associated: [data[i].name],
m2m: [id1, id2],
...commonAttrs,
});
}
} else {
const isLegalEdge = tableData
.find((v) => v.name == (data[i].sourceCollectionName || data[i].collectionName))
.ports.find((v) => v.name === data[i].foreignKey);
const sCellId1 = sourceTable.id;
const tCellId1 = targetTable.id;
const sPortId1 = isLegalEdge?.id;
const tPortId1 = targetTable.ports.find((v) => v.name === data[i].targetKey)?.id;
const sCellId2 = sourceTable.id;
const tCellId2 = targetTable.id;
const sPortId2 = sourceTable.ports.find((v) => v.name === data[i].sourceKey)?.id;
const tPortId2 = targetTable.ports.find((v) => v.name === data[i].foreignKey)?.id;
const id1 = sCellId1 + sPortId1 + tCellId1 + tPortId1;
const id2 = sCellId2 + sPortId2 + tCellId2 + tPortId2;
isuniq(tCellId1 + tPortId1 + sCellId1 + sPortId1) &&
isLegalEdge &&
tPortId1 &&
edges.push({
id: id1,
source: {
cell: sCellId1,
port: sPortId1,
anchor: {
name: 'right',
},
},
target: {
cell: tCellId1,
port: tPortId1,
anchor: {
name: 'left',
},
},
associated: [data[i].name],
...commonAttrs,
});
isuniq(tCellId2 + tPortId2 + sCellId2 + sPortId2) &&
sPortId2 &&
tPortId2 &&
edges.push({
id: id2,
source: {
cell: sCellId2,
port: sPortId2,
anchor: {
name: 'right',
},
},
target: {
cell: tCellId2,
port: tPortId2,
anchor: {
name: 'left',
},
},
associated: [data[i].name],
...commonAttrs,
});
}
}
}
return uniqBy(edges, 'id');
};
const getRelationship = (relatioship) => {
switch (relatioship) {
case 'm2m':
case 'linkTo':
return ['1', 'N'];
case 'o2m':
return ['1', 'N'];
case 'm2o':
return ['N', '1'];
case 'obo':
case 'oho':
return ['1', '1'];
default:
return [];
}
};
export const getDiffNode = (newNodes, oldNodes) => {
const arr = [];
const length1 = newNodes.length;
const length2 = oldNodes.length;
for (let i = 0; i < length1; i++) {
if (!oldNodes.find((v) => v.id === newNodes[i].id)) {
arr.push({
status: 'add',
node: newNodes[i],
});
} else {
const oldNode = oldNodes.find((v) => v.id === newNodes[i].id);
const oldPorts = oldNode?.ports.items;
const newPorts = newNodes[i].ports;
if (oldNode) {
for (let h = 0; h < newPorts.length; h++) {
if (!oldPorts.find((v) => v.id === newPorts[h].id)) {
arr.push({
status: 'insertPort',
node: newNodes[i],
port: { index: h, port: newPorts[h] },
});
}
}
for (let k = 0; k < oldPorts.length; k++) {
if (!newPorts.find((v) => v.id === oldPorts[k].id)) {
arr.push({
status: 'deletePort',
node: newNodes[i],
port: oldPorts[k],
});
}
}
if (oldNode.title !== newNodes[i].title) {
arr.push({
status: 'updateNode',
node: newNodes[i],
});
}
}
}
}
for (let i = 0; i < length2; i++) {
if (!newNodes.find((v) => v.id === oldNodes[i].id)) {
arr.push({
status: 'delete',
node: oldNodes[i],
});
}
}
return arr;
};
export const getDiffEdge = (newEdges, oldEdges) => {
const length1 = newEdges.length;
const length2 = oldEdges?.length;
const edges = [];
for (let i = 0; i < length1; i++) {
if (!oldEdges.find((v) => v.id === newEdges[i].id)) {
edges.push({
status: 'add',
edge: newEdges[i],
});
}
}
for (let i = 0; i < length2; i++) {
if (!newEdges.find((v) => v.id === oldEdges[i].id)) {
edges.push({
status: 'delete',
edge: oldEdges[i],
});
}
}
return edges;
};
let graphContainer;
/**
* 所有的 getPopupContainer 都需要保证返回的是唯一的 div。React 18 concurrent 下会反复调用该方法
* 参考https://ant.design/docs/react/migration-v5-cn#%E5%8D%87%E7%BA%A7%E5%87%86%E5%A4%87
*/
export const getPopupContainer = () => {
if (graphContainer) {
return graphContainer;
}
return (graphContainer = document.getElementById('graph_container'));
};
export const cleanGraphContainer = () => {
graphContainer = null;
};