use-variable-tree.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { useCallback, useMemo } from 'react';
  2. import {
  3. ASTFactory,
  4. ASTKind,
  5. type BaseType,
  6. type UnionJSON,
  7. useScopeAvailable,
  8. ASTMatch,
  9. } from '@flowgram.ai/free-layout-editor';
  10. import { createASTFromJSONSchema } from '../utils';
  11. import { ArrayIcons, VariableTypeIcons } from '../icons';
  12. import { type JsonSchema } from '../../../typings';
  13. type VariableField = any;
  14. interface HooksParams<TreeData> {
  15. // filter target type
  16. targetSchemas?: JsonSchema[];
  17. // Is it strongly type-checked?
  18. strongEqual?: boolean;
  19. // ignore global Config
  20. ignoreReadonly?: boolean;
  21. // render tree node
  22. getTreeData: (props: {
  23. key: string;
  24. icon: JSX.Element | undefined;
  25. variable: VariableField;
  26. parentFields: VariableField[];
  27. disabled?: boolean;
  28. children?: TreeData[];
  29. }) => TreeData;
  30. }
  31. export function useVariableTree<TreeData>({
  32. targetSchemas = [],
  33. strongEqual = false,
  34. ignoreReadonly = false,
  35. getTreeData,
  36. }: HooksParams<TreeData>): TreeData[] {
  37. const available = useScopeAvailable();
  38. const getVariableTypeIcon = useCallback((variable: VariableField) => {
  39. const _type = variable.type;
  40. if (ASTMatch.isArray(_type)) {
  41. return (
  42. (ArrayIcons as any)[_type.items?.kind.toLowerCase()] ||
  43. VariableTypeIcons[ASTKind.Array.toLowerCase()]
  44. );
  45. }
  46. if (ASTMatch.isCustomType(_type)) {
  47. return VariableTypeIcons[_type.typeName.toLowerCase()];
  48. }
  49. return (VariableTypeIcons as any)[variable.type?.kind.toLowerCase()];
  50. }, []);
  51. const targetTypeAST: UnionJSON = useMemo(
  52. () =>
  53. ASTFactory.createUnion({
  54. types: targetSchemas.map((_targetSchema) => {
  55. const typeAst = createASTFromJSONSchema(_targetSchema)!;
  56. return strongEqual ? typeAst : { ...typeAst, weak: true };
  57. }),
  58. }),
  59. [strongEqual, ...targetSchemas]
  60. );
  61. const checkTypeFiltered = useCallback(
  62. (type?: BaseType) => {
  63. if (!type) {
  64. return true;
  65. }
  66. if (targetTypeAST.types?.length) {
  67. return !type.isTypeEqual(targetTypeAST);
  68. }
  69. return false;
  70. },
  71. [strongEqual, targetTypeAST]
  72. );
  73. const renderVariable = (
  74. variable: VariableField,
  75. parentFields: VariableField[] = []
  76. ): TreeData | null => {
  77. let type = variable?.type;
  78. const isTypeFiltered = checkTypeFiltered(type);
  79. let children: TreeData[] | undefined;
  80. if (ASTMatch.isObject(type)) {
  81. children = (type.properties || [])
  82. .map((_property) => renderVariable(_property as VariableField, [...parentFields, variable]))
  83. .filter(Boolean) as TreeData[];
  84. }
  85. if (isTypeFiltered && !children?.length) {
  86. return null;
  87. }
  88. const currPath = [
  89. ...parentFields.map((_field) => _field.meta?.titleKey || _field.key),
  90. variable.meta?.titleKey || variable.key,
  91. ].join('.');
  92. return getTreeData({
  93. key: currPath,
  94. icon: getVariableTypeIcon(variable),
  95. variable,
  96. parentFields,
  97. children,
  98. disabled: isTypeFiltered,
  99. });
  100. };
  101. return [
  102. ...available.variables
  103. .filter((_v) => {
  104. if (ignoreReadonly) {
  105. return !_v.meta?.readonly;
  106. }
  107. return true;
  108. })
  109. .slice(0)
  110. .reverse(),
  111. ]
  112. .map((_variable) => renderVariable(_variable as VariableField))
  113. .filter(Boolean) as TreeData[];
  114. }