@comet/admin-generator 8.0.0-beta.4 → 8.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  require("ts-node").register({
3
4
  require: ["tsconfig-paths/register"],
5
+ transpileOnly: true,
4
6
  });
7
+
5
8
  require("../lib/adminGenerator");
@@ -0,0 +1 @@
1
+ export declare function parseConfig(file: string): Promise<any>;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.parseConfig = parseConfig;
46
+ const fs_1 = require("fs");
47
+ const path_1 = require("path");
48
+ const transformConfig_1 = require("./transformConfig");
49
+ function parseConfig(file) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ //1. parse config file using TypeScript Complier Api and transform it (replace imports and functions that can't be executed)
52
+ const transformedConfig = (0, transformConfig_1.transformConfigFile)(file, yield fs_1.promises.readFile(file, "utf-8"));
53
+ //2. save modified config file to temp file
54
+ const tempFileName = `${(0, path_1.dirname)(file)}/.temp-${(0, path_1.basename)(file)}`;
55
+ yield fs_1.promises.writeFile(tempFileName, transformedConfig, "utf-8");
56
+ //3. import (=execute) temp modified config file
57
+ let executedConfig;
58
+ try {
59
+ const configFile = yield Promise.resolve(`${tempFileName.replace(/\.tsx?$/, "")}`).then(s => __importStar(require(s)));
60
+ if (!configFile.default) {
61
+ throw new Error(`No default export found in ${file}`);
62
+ }
63
+ executedConfig = configFile.default;
64
+ }
65
+ catch (e) {
66
+ console.error(e);
67
+ throw new Error(`Error executing config file ${file}: ${e}`);
68
+ }
69
+ yield fs_1.promises.rm(tempFileName);
70
+ return executedConfig;
71
+ });
72
+ }
@@ -0,0 +1 @@
1
+ export declare function transformConfigFile(fileName: string, sourceText: string): string;
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.transformConfigFile = transformConfigFile;
37
+ const ts = __importStar(require("typescript"));
38
+ const supportedImportPaths = [
39
+ "[type=grid].columns.filterOperators",
40
+ "[type=grid].columns.block",
41
+ "[type=grid].columns.component",
42
+ // TODO implement in generator "[type=grid].columns.renderCell",
43
+ //support in up to 5 levels of nested fields (eg. fieldSet)
44
+ ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.validate`),
45
+ ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.block`),
46
+ ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.component`),
47
+ ];
48
+ const supportedInlineCodePaths = [
49
+ // TODO implement in generator "[type=grid].columns.filterOperators",
50
+ "[type=grid].columns.renderCell",
51
+ //support in up to 5 levels of nested fields (eg. fieldSet)
52
+ ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.validate`),
53
+ ];
54
+ // transform the config file to replace all imports and inline code with a { code, imports } object
55
+ // this is needed to be able to execute the config file in a node environment
56
+ function transformConfigFile(fileName, sourceText) {
57
+ const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.ES2024, // language version
58
+ true);
59
+ const importedIdentifiers = collectImports(sourceFile);
60
+ function configTransformer() {
61
+ return (context) => {
62
+ const visit = (node, path) => {
63
+ if (ts.isArrowFunction(node)) {
64
+ if (supportedInlineCodePaths.includes(path)) {
65
+ let code = node.getText();
66
+ if (code.endsWith(","))
67
+ code = code.slice(0, -1); // for some unknown reason node can contain the trailing comma
68
+ const imports = findUsedImports(node.body, importedIdentifiers); //find all imports used in the function body
69
+ // replace inline code with { code, imports } object
70
+ return ts.factory.createObjectLiteralExpression([
71
+ ts.factory.createPropertyAssignment("code", ts.factory.createStringLiteral(code)),
72
+ ts.factory.createPropertyAssignment("imports", ts.factory.createArrayLiteralExpression(imports.map((imprt) => {
73
+ return ts.factory.createObjectLiteralExpression([
74
+ ts.factory.createPropertyAssignment("name", ts.factory.createStringLiteral(imprt.name)),
75
+ ts.factory.createPropertyAssignment("import", ts.factory.createStringLiteral(imprt.import)),
76
+ ]);
77
+ }))),
78
+ ], true);
79
+ }
80
+ else {
81
+ throw new Error(`Inline Function is not allowed here and calling the function is not supported: ${path}`);
82
+ }
83
+ }
84
+ else if (ts.isIdentifier(node)) {
85
+ const imported = importedIdentifiers.get(node.text);
86
+ if (imported) {
87
+ if (supportedImportPaths.includes(path)) {
88
+ // replace imported identifier with { name, import } object
89
+ return ts.factory.createObjectLiteralExpression([
90
+ ts.factory.createPropertyAssignment("name", ts.factory.createStringLiteral(node.text)),
91
+ ts.factory.createPropertyAssignment("import", ts.factory.createStringLiteral(imported.import)),
92
+ ], true);
93
+ }
94
+ else {
95
+ throw new Error(`Following the import is not supported: ${path} ${node.text}`);
96
+ }
97
+ }
98
+ }
99
+ const transformKinds = [
100
+ ts.SyntaxKind.Identifier,
101
+ ts.SyntaxKind.ArrayLiteralExpression,
102
+ ts.SyntaxKind.ObjectLiteralExpression,
103
+ ts.SyntaxKind.TaggedTemplateExpression,
104
+ ts.SyntaxKind.SpreadElement,
105
+ ts.SyntaxKind.PropertyAssignment,
106
+ ts.SyntaxKind.ShorthandPropertyAssignment,
107
+ ];
108
+ if (!transformKinds.includes(node.kind)) {
109
+ // if the node is not one of the transformKinds, stop transformation at this level return it as is
110
+ return node;
111
+ }
112
+ let newPath = path;
113
+ if (path == "") {
114
+ // first entry of path is the type, then property names (. separated) are added
115
+ if (ts.isObjectLiteralExpression(node)) {
116
+ const typeProperty = getTypePropertyFromObjectLiteral(node);
117
+ newPath = typeProperty ? `[type=${typeProperty}]` : "";
118
+ }
119
+ }
120
+ else {
121
+ if (ts.isPropertyAssignment(node)) {
122
+ newPath = `${path}.${node.name.getText()}`;
123
+ }
124
+ }
125
+ return ts.visitEachChild(node, (child) => {
126
+ return visit(child, newPath);
127
+ }, context);
128
+ };
129
+ return (node) => ts.visitNode(node, (child) => visit(child, ""));
130
+ };
131
+ }
132
+ const configNode = findConfigNode(sourceFile);
133
+ const transformedConfigNode = ts.transform(configNode, [configTransformer()]).transformed[0];
134
+ const updatedSource = ts.transform(sourceFile, [
135
+ (context) => {
136
+ const visitor = (node) => {
137
+ if (node === configNode)
138
+ return transformedConfigNode;
139
+ return ts.visitEachChild(node, visitor, context);
140
+ };
141
+ return (node) => ts.visitNode(node, visitor);
142
+ },
143
+ ]).transformed[0];
144
+ const printer = ts.createPrinter();
145
+ return printer.printFile(updatedSource);
146
+ }
147
+ // finds the config node in the source file (=default export, might be wrapped in defineConfig or uses satisfies)
148
+ function findConfigNode(sourceFile) {
149
+ let ret;
150
+ sourceFile.forEachChild((node) => {
151
+ if (ts.isExportAssignment(node)) {
152
+ const exportedNode = node.expression;
153
+ if (ts.isCallExpression(exportedNode) && exportedNode.expression.getText() == "defineConfig") {
154
+ //export default defineConfig<Foo>({ ... });
155
+ const args = exportedNode.arguments;
156
+ if (args.length != 1) {
157
+ throw new Error(`Expected exactly one argument for defineConfig`);
158
+ }
159
+ ret = args[0];
160
+ return false;
161
+ }
162
+ else if (ts.isSatisfiesExpression(exportedNode)) {
163
+ //export default { ... } satisfies GeneratorConfig<Foo>;
164
+ if (ts.isObjectLiteralExpression(exportedNode.expression)) {
165
+ ret = exportedNode.expression;
166
+ return false;
167
+ }
168
+ }
169
+ else if (ts.isObjectLiteralExpression(exportedNode)) {
170
+ //export default { ... };
171
+ ret = exportedNode;
172
+ return false;
173
+ }
174
+ }
175
+ });
176
+ if (!ret) {
177
+ throw new Error(`No default export found, please export the GeneratorConfig as default, preferrable using defineConfig helper.`);
178
+ }
179
+ return ret;
180
+ }
181
+ // simple helper that extracts the value of the type property from a object literal ({ type: "grid" } returns "grid")
182
+ function getTypePropertyFromObjectLiteral(node) {
183
+ for (const property of node.properties) {
184
+ if (ts.isPropertyAssignment(property)) {
185
+ if (property.name.getText() == "type") {
186
+ const propertyAssignmentInitializer = property.initializer;
187
+ if (ts.isStringLiteral(propertyAssignmentInitializer)) {
188
+ return propertyAssignmentInitializer.text;
189
+ }
190
+ }
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ // visits ast and collects all imports statements in the source file
196
+ function collectImports(rootNode) {
197
+ const importedIdentifiers = new Map();
198
+ function visit(node) {
199
+ if (ts.isImportDeclaration(node) &&
200
+ node.importClause &&
201
+ node.importClause.namedBindings &&
202
+ ts.isNamedImports(node.importClause.namedBindings)) {
203
+ const moduleSpecifier = node.moduleSpecifier.text;
204
+ for (const element of node.importClause.namedBindings.elements) {
205
+ const localName = element.name.text;
206
+ const originalName = element.propertyName ? element.propertyName.text : localName;
207
+ importedIdentifiers.set(localName, {
208
+ name: originalName,
209
+ import: moduleSpecifier,
210
+ });
211
+ }
212
+ }
213
+ ts.forEachChild(node, visit);
214
+ }
215
+ visit(rootNode);
216
+ return importedIdentifiers;
217
+ }
218
+ // visits ast and collects all identifiers that are an import
219
+ function findUsedImports(rootNode, importedIdentifiers) {
220
+ const imports = [];
221
+ const usedNames = new Set();
222
+ // Collect all identifiers used in the rootNode
223
+ function collectUsedIdentifiers(node) {
224
+ if (ts.isIdentifier(node)) {
225
+ usedNames.add(node.text);
226
+ }
227
+ ts.forEachChild(node, collectUsedIdentifiers);
228
+ }
229
+ collectUsedIdentifiers(rootNode);
230
+ // Match used identifiers to imported ones
231
+ // NOTE: this is not 100% correct as it doesn't recognize cases where a import is overwritten by a local variable. But it is fast.
232
+ for (const name of usedNames) {
233
+ const imported = importedIdentifiers.get(name);
234
+ if (imported) {
235
+ imports.push(imported);
236
+ }
237
+ }
238
+ return imports;
239
+ }
@@ -106,15 +106,6 @@ export type FormConfig<T extends {
106
106
  createMutation?: string;
107
107
  fields: (FormFieldConfig<T> | FormLayoutConfig<T> | ComponentFormFieldConfig)[];
108
108
  };
109
- type TabsConfig<T extends {
110
- __typename?: string;
111
- }> = {
112
- type: "tabs";
113
- tabs: {
114
- name: string;
115
- content: GeneratorConfig<T>;
116
- }[];
117
- };
118
109
  type BaseColumnConfig = Pick<GridColDef, "headerName" | "width" | "minWidth" | "maxWidth" | "flex" | "pinned" | "disableExport"> & {
119
110
  headerInfoTooltip?: string;
120
111
  visible?: ColumnVisibleOption;
@@ -130,6 +121,8 @@ export type GridColumnConfig<T extends GridValidRowModel> = ({
130
121
  renderCell?: (params: GridRenderCellParams<T, any, any>) => JSX.Element;
131
122
  } | {
132
123
  type: "number";
124
+ currency?: string;
125
+ decimals?: number;
133
126
  renderCell?: (params: GridRenderCellParams<T, any, any>) => JSX.Element;
134
127
  } | {
135
128
  type: "boolean";
@@ -193,10 +186,14 @@ export type GridConfig<T extends {
193
186
  newEntryText?: string;
194
187
  rowActionProp?: boolean;
195
188
  selectionProps?: "multiSelect" | "singleSelect";
189
+ rowReordering?: {
190
+ enabled: boolean;
191
+ dragPreviewField?: UsableFields<T>;
192
+ };
196
193
  };
197
194
  export type GeneratorConfig<T extends {
198
195
  __typename?: string;
199
- }> = FormConfig<T> | GridConfig<T> | TabsConfig<T>;
196
+ }> = FormConfig<T> | GridConfig<T>;
200
197
  export declare function defineConfig<T extends {
201
198
  __typename?: string;
202
199
  }>(config: GeneratorConfig<T>): GeneratorConfig<T>;
@@ -20,9 +20,9 @@ const fs_1 = require("fs");
20
20
  const glob_1 = require("glob");
21
21
  const graphql_1 = require("graphql");
22
22
  const path_1 = require("path");
23
+ const parseConfig_1 = require("./config/parseConfig");
23
24
  const generateForm_1 = require("./generateForm/generateForm");
24
25
  const generateGrid_1 = require("./generateGrid/generateGrid");
25
- const tsMorphHelper_1 = require("./utils/tsMorphHelper");
26
26
  const writeGenerated_1 = require("./utils/writeGenerated");
27
27
  function isFormFieldConfig(arg) {
28
28
  return !isFormLayoutConfig(arg);
@@ -50,13 +50,12 @@ function runGenerate() {
50
50
  const targetDirectory = `${(0, path_1.dirname)(file)}/generated`;
51
51
  const baseOutputFilename = (0, path_1.basename)(file).replace(/\.cometGen\.tsx?$/, "");
52
52
  console.log(`generating ${file}`);
53
- const config = (0, tsMorphHelper_1.configFromSourceFile)((0, tsMorphHelper_1.morphTsSource)(file));
53
+ const config = yield (0, parseConfig_1.parseConfig)(file);
54
+ const codeOuputFilename = `${targetDirectory}/${(0, path_1.basename)(file.replace(/\.cometGen\.tsx?$/, ""))}.tsx`;
55
+ yield fs_1.promises.rm(codeOuputFilename, { force: true });
54
56
  const exportName = (_a = file.match(/([^/]+)\.cometGen\.tsx?$/)) === null || _a === void 0 ? void 0 : _a[1];
55
57
  if (!exportName)
56
58
  throw new Error("Can not determine exportName");
57
- //const configs = await import(`${process.cwd()}/${file.replace(/\.ts$/, "")}`);
58
- const codeOuputFilename = `${targetDirectory}/${(0, path_1.basename)(file.replace(/\.cometGen\.tsx?$/, ""))}.tsx`;
59
- yield fs_1.promises.rm(codeOuputFilename, { force: true });
60
59
  let generated;
61
60
  if (config.type == "form") {
62
61
  generated = (0, generateForm_1.generateForm)({ exportName, gqlIntrospection, baseOutputFilename, targetDirectory }, config);
@@ -65,7 +64,7 @@ function runGenerate() {
65
64
  generated = (0, generateGrid_1.generateGrid)({ exportName, gqlIntrospection, baseOutputFilename, targetDirectory }, config);
66
65
  }
67
66
  else {
68
- throw new Error(`Unknown config type: ${config.type}`);
67
+ throw new Error(`Unknown config type`);
69
68
  }
70
69
  outputCode += generated.code;
71
70
  for (const queryName in generated.gqlDocuments) {
@@ -83,6 +82,7 @@ function runGenerate() {
83
82
  `;
84
83
  yield (0, writeGenerated_1.writeGenerated)(gqlDocumentsOuputFilename, gqlDocumentsOutputCode);
85
84
  }
85
+ console.log("");
86
86
  }
87
87
  });
88
88
  }
@@ -48,7 +48,43 @@ config) {
48
48
  const instanceGqlType = gqlType[0].toLowerCase() + gqlType.substring(1);
49
49
  const formFragmentName = (_a = config.fragmentName) !== null && _a !== void 0 ? _a : `${gqlType}Form`;
50
50
  const gqlDocuments = {};
51
- const imports = [];
51
+ const imports = [
52
+ { name: "FormattedMessage", importPath: "react-intl" },
53
+ { name: "useApolloClient", importPath: "@apollo/client" },
54
+ { name: "useQuery", importPath: "@apollo/client" },
55
+ { name: "gql", importPath: "@apollo/client" },
56
+ { name: "AsyncSelectField", importPath: "@comet/admin" },
57
+ { name: "CheckboxField", importPath: "@comet/admin" },
58
+ { name: "Field", importPath: "@comet/admin" },
59
+ { name: "filterByFragment", importPath: "@comet/admin" },
60
+ { name: "FinalForm", importPath: "@comet/admin" },
61
+ { name: "FinalFormInput", importPath: "@comet/admin" },
62
+ { name: "FinalFormRangeInput", importPath: "@comet/admin" },
63
+ { name: "FinalFormSelect", importPath: "@comet/admin" },
64
+ { name: "FinalFormSubmitEvent", importPath: "@comet/admin" },
65
+ { name: "Loading", importPath: "@comet/admin" },
66
+ { name: "NumberField", importPath: "@comet/admin" },
67
+ { name: "RadioGroupField", importPath: "@comet/admin" },
68
+ { name: "TextAreaField", importPath: "@comet/admin" },
69
+ { name: "TextField", importPath: "@comet/admin" },
70
+ { name: "useFormApiRef", importPath: "@comet/admin" },
71
+ { name: "useStackSwitchApi", importPath: "@comet/admin" },
72
+ { name: "ArrowLeft", importPath: "@comet/admin-icons" },
73
+ { name: "Lock", importPath: "@comet/admin-icons" },
74
+ { name: "DateTimeField", importPath: "@comet/admin-date-time" },
75
+ { name: "FinalFormDatePicker", importPath: "@comet/admin-date-time" },
76
+ { name: "BlockState", importPath: "@comet/cms-admin" },
77
+ { name: "createFinalFormBlock", importPath: "@comet/cms-admin" },
78
+ { name: "queryUpdatedAt", importPath: "@comet/cms-admin" },
79
+ { name: "resolveHasSaveConflict", importPath: "@comet/cms-admin" },
80
+ { name: "useFormSaveConflict", importPath: "@comet/cms-admin" },
81
+ { name: "FileUploadField", importPath: "@comet/cms-admin" },
82
+ { name: "IconButton", importPath: "@mui/material" },
83
+ { name: "MenuItem", importPath: "@mui/material" },
84
+ { name: "InputAdornment", importPath: "@mui/material" },
85
+ { name: "FormApi", importPath: "final-form" },
86
+ { name: "useMemo", importPath: "react" },
87
+ ];
52
88
  const props = [];
53
89
  const mode = (_b = config.mode) !== null && _b !== void 0 ? _b : "all";
54
90
  const editMode = mode === "edit" || mode == "all";
@@ -219,10 +255,12 @@ config) {
219
255
  name: `GQL${documentName}${type[0].toUpperCase() + type.substring(1)}`,
220
256
  importPath: `./${baseOutputFilename}.gql.generated`,
221
257
  });
222
- imports.push({
223
- name: `GQL${documentName}${type[0].toUpperCase() + type.substring(1)}Variables`,
224
- importPath: `./${baseOutputFilename}.gql.generated`,
225
- });
258
+ if (type !== "fragment") {
259
+ imports.push({
260
+ name: `GQL${documentName}${type[0].toUpperCase() + type.substring(1)}Variables`,
261
+ importPath: `./${baseOutputFilename}.gql.generated`,
262
+ });
263
+ }
226
264
  }
227
265
  const finalFormSubscription = Object.keys((_e = (_d = generatedFields.finalFormConfig) === null || _d === void 0 ? void 0 : _d.subscription) !== null && _e !== void 0 ? _e : {});
228
266
  const finalFormRenderProps = Object.keys((_g = (_f = generatedFields.finalFormConfig) === null || _f === void 0 ? void 0 : _f.renderProps) !== null && _g !== void 0 ? _g : {});
@@ -248,34 +286,10 @@ config) {
248
286
  }`;
249
287
  filterByFragmentType = `${formFragmentName}Fragment`;
250
288
  }
251
- const code = `import { useApolloClient, useQuery, gql } from "@apollo/client";
252
- import {
253
- AsyncSelectField,
254
- CheckboxField,
255
- Field,
256
- filterByFragment,
257
- FinalForm,
258
- FinalFormInput,
259
- FinalFormRangeInput,
260
- FinalFormSelect,
261
- FinalFormSubmitEvent,
262
- Loading,
263
- NumberField,
264
- RadioGroupField,
265
- TextAreaField,
266
- TextField,
267
- useFormApiRef,
268
- useStackSwitchApi,
269
- } from "@comet/admin";
270
- import { ArrowLeft, Lock } from "@comet/admin-icons";
271
- import { DateTimeField, FinalFormDatePicker } from "@comet/admin-date-time";
272
- import { BlockState, createFinalFormBlock, queryUpdatedAt, resolveHasSaveConflict, useFormSaveConflict, FileUploadField } from "@comet/cms-admin";
273
- import { FormControlLabel, IconButton, MenuItem, InputAdornment } from "@mui/material";
274
- import { FormApi } from "final-form";
275
- import isEqual from "lodash.isequal";
276
- import { useMemo } from "react";
277
- import { FormattedMessage } from "react-intl";
289
+ const code = `
278
290
  ${(0, generateImportsCode_1.generateImportsCode)(imports)}
291
+ import isEqual from "lodash.isequal";
292
+
279
293
  ${rootBlockFields.length > 0
280
294
  ? `const rootBlocks = {
281
295
  ${rootBlockFields.map((field) => `${String(field.name)}: ${field.block.name}`)}
@@ -284,11 +298,11 @@ config) {
284
298
 
285
299
  ${customFilterByFragment}
286
300
 
287
- type FormValues = ${formValuesConfig.filter((config) => !!config.omitFromFragmentType).length > 0
288
- ? `Omit<${filterByFragmentType}, ${formValuesConfig
289
- .filter((config) => !!config.omitFromFragmentType)
290
- .map((config) => `"${config.omitFromFragmentType}"`)
291
- .join(" | ")}>`
301
+ type FormValues = ${formValuesConfig.filter((config) => !!config.omitFromFragmentType).length > 0 || rootBlockFields.length > 0
302
+ ? `Omit<${filterByFragmentType}, ${[
303
+ ...(rootBlockFields.length > 0 ? ["keyof typeof rootBlocks"] : []),
304
+ ...formValuesConfig.filter((config) => !!config.omitFromFragmentType).map((config) => `"${config.omitFromFragmentType}"`),
305
+ ].join(" | ")}>`
292
306
  : `${filterByFragmentType}`} ${formValuesConfig.filter((config) => !!config.typeCode).length > 0
293
307
  ? `& {
294
308
  ${formValuesConfig
@@ -140,6 +140,7 @@ function generateFormField({ gqlIntrospection, baseOutputFilename, config, formC
140
140
  let formValueToGqlInputCode = "";
141
141
  let formFragmentField = name;
142
142
  if (config.type == "text") {
143
+ const required = config.required !== undefined ? config.required : false; // don't use inferred from gql here, non-nullable textinput allows empty string
143
144
  const TextInputComponent = config.multiline ? "TextAreaField" : "TextField";
144
145
  code = `
145
146
  <${TextInputComponent}
@@ -115,7 +115,7 @@ const getValueOptionsLabelData = (messageId, label) => {
115
115
  };
116
116
  };
117
117
  function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntrospection, }, config) {
118
- var _a, _b;
118
+ var _a, _b, _c, _d, _e, _f, _g, _h;
119
119
  const gqlType = config.gqlType;
120
120
  const gqlTypePlural = (0, pluralize_1.plural)(gqlType);
121
121
  //const title = config.title ?? camelCaseToHumanReadable(gqlType);
@@ -123,8 +123,58 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
123
123
  const instanceGqlTypePlural = gqlTypePlural[0].toLowerCase() + gqlTypePlural.substring(1);
124
124
  const gridQuery = config.query ? config.query : instanceGqlType != instanceGqlTypePlural ? instanceGqlTypePlural : `${instanceGqlTypePlural}List`;
125
125
  const gqlDocuments = {};
126
- const imports = [];
127
- const iconsToImport = ["Add", "Edit"];
126
+ const imports = [
127
+ { name: "FormattedMessage", importPath: "react-intl" },
128
+ { name: "FormattedNumber", importPath: "react-intl" },
129
+ { name: "useIntl", importPath: "react-intl" },
130
+ { name: "ReactNode", importPath: "react" },
131
+ { name: "gql", importPath: "@apollo/client" },
132
+ { name: "useApolloClient", importPath: "@apollo/client" },
133
+ { name: "useQuery", importPath: "@apollo/client" },
134
+ { name: "Button", importPath: "@comet/admin" },
135
+ { name: "CrudContextMenu", importPath: "@comet/admin" },
136
+ { name: "CrudMoreActionsMenu", importPath: "@comet/admin" },
137
+ { name: "DataGridToolbar", importPath: "@comet/admin" },
138
+ { name: "ExportApi", importPath: "@comet/admin" },
139
+ { name: "filterByFragment", importPath: "@comet/admin" },
140
+ { name: "GridFilterButton", importPath: "@comet/admin" },
141
+ { name: "GridCellContent", importPath: "@comet/admin" },
142
+ { name: "GridColDef", importPath: "@comet/admin" },
143
+ { name: "dataGridDateTimeColumn", importPath: "@comet/admin" },
144
+ { name: "dataGridDateColumn", importPath: "@comet/admin" },
145
+ { name: "renderStaticSelectCell", importPath: "@comet/admin" },
146
+ { name: "messages", importPath: "@comet/admin" },
147
+ { name: "muiGridFilterToGql", importPath: "@comet/admin" },
148
+ { name: "muiGridSortToGql", importPath: "@comet/admin" },
149
+ { name: "StackLink", importPath: "@comet/admin" },
150
+ { name: "FillSpace", importPath: "@comet/admin" },
151
+ { name: "Tooltip", importPath: "@comet/admin" },
152
+ { name: "useBufferedRowCount", importPath: "@comet/admin" },
153
+ { name: "useDataGridExcelExport", importPath: "@comet/admin" },
154
+ { name: "useDataGridRemote", importPath: "@comet/admin" },
155
+ { name: "usePersistentColumnState", importPath: "@comet/admin" },
156
+ { name: "BlockPreviewContent", importPath: "@comet/cms-admin" },
157
+ { name: "useContentScope", importPath: "@comet/cms-admin" },
158
+ { name: "Alert", importPath: "@mui/material" },
159
+ { name: "Box", importPath: "@mui/material" },
160
+ { name: "IconButton", importPath: "@mui/material" },
161
+ { name: "Typography", importPath: "@mui/material" },
162
+ { name: "useTheme", importPath: "@mui/material" },
163
+ { name: "Menu", importPath: "@mui/material" },
164
+ { name: "MenuItem", importPath: "@mui/material" },
165
+ { name: "ListItemIcon", importPath: "@mui/material" },
166
+ { name: "ListItemText", importPath: "@mui/material" },
167
+ { name: "CircularProgress", importPath: "@mui/material" },
168
+ { name: "DataGridPro", importPath: "@mui/x-data-grid-pro" },
169
+ { name: "GridLogicOperator", importPath: "@mui/x-data-grid-pro" },
170
+ { name: "GridRenderCellParams", importPath: "@mui/x-data-grid-pro" },
171
+ { name: "GridSlotsComponent", importPath: "@mui/x-data-grid-pro" },
172
+ { name: "GridToolbarProps", importPath: "@mui/x-data-grid-pro" },
173
+ { name: "GridColumnHeaderTitle", importPath: "@mui/x-data-grid-pro" },
174
+ { name: "GridToolbarQuickFilter", importPath: "@mui/x-data-grid-pro" },
175
+ { name: "GridRowOrderChangeParams", importPath: "@mui/x-data-grid-pro" },
176
+ ];
177
+ const iconsToImport = ["Add", "Edit", "Info", "Excel"];
128
178
  const props = [];
129
179
  const fieldList = (0, generateGqlFieldList_1.generateGqlFieldList)({
130
180
  columns: config.columns.filter((column) => {
@@ -161,12 +211,29 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
161
211
  });
162
212
  const gridQueryType = (0, findQueryType_1.findQueryTypeOrThrow)(gridQuery, gqlIntrospection);
163
213
  const createMutationType = (0, findMutationType_1.findMutationType)(`create${gqlType}`, gqlIntrospection);
214
+ const updateMutationType = (0, findMutationType_1.findMutationType)(`update${gqlType}`, gqlIntrospection);
164
215
  const hasDeleteMutation = !!(0, findMutationType_1.findMutationType)(`delete${gqlType}`, gqlIntrospection);
165
216
  const hasCreateMutation = !!createMutationType;
217
+ const hasUpdateMutation = !!updateMutationType;
166
218
  const allowCopyPaste = (typeof config.copyPaste === "undefined" || config.copyPaste === true) && !config.readOnly && hasCreateMutation;
167
219
  const allowAdding = (typeof config.add === "undefined" || config.add === true) && !config.readOnly;
168
220
  const allowEditing = (typeof config.edit === "undefined" || config.edit === true) && !config.readOnly;
169
221
  const allowDeleting = (typeof config.delete === "undefined" || config.delete === true) && !config.readOnly && hasDeleteMutation;
222
+ const allowRowReordering = typeof ((_a = config.rowReordering) === null || _a === void 0 ? void 0 : _a.enabled) !== "undefined" && ((_b = config.rowReordering) === null || _b === void 0 ? void 0 : _b.enabled) && hasUpdateMutation;
223
+ const updateInputArg = updateMutationType === null || updateMutationType === void 0 ? void 0 : updateMutationType.args.find((arg) => arg.name === "input");
224
+ if (allowRowReordering && updateInputArg) {
225
+ const inputType = (0, findInputObjectType_1.findInputObjectType)(updateInputArg, gqlIntrospection);
226
+ if (!inputType)
227
+ throw new Error("Can't find update input type");
228
+ if (!((_c = inputType.inputFields) === null || _c === void 0 ? void 0 : _c.find((field) => field.name === "position"))) {
229
+ throw new Error("Position field is needed when using 'rowReordering'");
230
+ }
231
+ }
232
+ const hasRowReorderingOnDragField = allowRowReordering && typeof ((_d = config.rowReordering) === null || _d === void 0 ? void 0 : _d.dragPreviewField) !== "undefined";
233
+ if (hasRowReorderingOnDragField &&
234
+ !config.columns.find((column) => { var _a; return column.type !== "actions" && (column === null || column === void 0 ? void 0 : column.name) === ((_a = config.rowReordering) === null || _a === void 0 ? void 0 : _a.dragPreviewField); })) {
235
+ throw new Error(`rowReorderingOnDragField '${(_e = config.rowReordering) === null || _e === void 0 ? void 0 : _e.dragPreviewField}' must exist in columns`);
236
+ }
170
237
  const forwardRowAction = allowEditing && config.rowActionProp;
171
238
  const showActionsColumn = allowCopyPaste || allowEditing || allowDeleting;
172
239
  const showCrudContextMenuInActionsColumn = allowCopyPaste || allowDeleting;
@@ -175,9 +242,9 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
175
242
  const { imports: forwardedGqlArgsImports, props: forwardedGqlArgsProps, gqlArgs, } = (0, getForwardedGqlArgs_1.getForwardedGqlArgs)([gridQueryType, ...(createMutationType ? [createMutationType] : [])]);
176
243
  imports.push(...forwardedGqlArgsImports);
177
244
  props.push(...forwardedGqlArgsProps);
178
- const renderToolbar = (_a = config.toolbar) !== null && _a !== void 0 ? _a : true;
245
+ const renderToolbar = (_f = config.toolbar) !== null && _f !== void 0 ? _f : true;
179
246
  const filterArg = gridQueryType.args.find((arg) => arg.name === "filter");
180
- const hasFilter = !!filterArg && renderToolbar;
247
+ const hasFilter = !!filterArg && renderToolbar && !allowRowReordering;
181
248
  let hasFilterProp = false;
182
249
  let filterFields = [];
183
250
  if (filterArg) {
@@ -223,8 +290,11 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
223
290
  if (!sortInputEnum)
224
291
  throw new Error("Can't find sortInputEnum");
225
292
  sortFields = sortInputEnum.enumValues.map((v) => v.name.replace(/_/g, "."));
293
+ if (allowRowReordering && !sortFields.includes("position")) {
294
+ throw new Error("Sort argument must include 'position' field for row reordering");
295
+ }
226
296
  }
227
- const hasSearch = gridQueryType.args.some((arg) => arg.name === "search");
297
+ const hasSearch = gridQueryType.args.some((arg) => arg.name === "search") && !allowRowReordering;
228
298
  const hasScope = gridQueryType.args.some((arg) => arg.name === "scope");
229
299
  const schemaEntity = gqlIntrospection.__schema.types.find((type) => type.kind === "OBJECT" && type.name === gqlType);
230
300
  if (!schemaEntity)
@@ -247,7 +317,7 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
247
317
  return true;
248
318
  });
249
319
  const actionsColumnConfig = config.columns.find((column) => column.type === "actions");
250
- const _c = actionsColumnConfig !== null && actionsColumnConfig !== void 0 ? actionsColumnConfig : {}, { component: actionsColumnComponent, type: actionsColumnType, headerName: actionsColumnHeaderName, pinned: actionsColumnPinned = "right", width: actionsColumnWidth = defaultActionsColumnWidth, visible: actionsColumnVisible = undefined } = _c, restActionsColumnConfig = __rest(_c, ["component", "type", "headerName", "pinned", "width", "visible"]);
320
+ const _j = actionsColumnConfig !== null && actionsColumnConfig !== void 0 ? actionsColumnConfig : {}, { component: actionsColumnComponent, type: actionsColumnType, headerName: actionsColumnHeaderName, pinned: actionsColumnPinned = "right", width: actionsColumnWidth = defaultActionsColumnWidth, visible: actionsColumnVisible = undefined } = _j, restActionsColumnConfig = __rest(_j, ["component", "type", "headerName", "pinned", "width", "visible"]);
251
321
  if (actionsColumnComponent) {
252
322
  if (!(0, runtimeTypeGuards_1.isGeneratorConfigImport)(actionsColumnComponent)) {
253
323
  throw new Error("Unsupported actionsColumnComponent, only imports are supported");
@@ -281,6 +351,12 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
281
351
  }
282
352
  else if (type == "number") {
283
353
  gridType = "number";
354
+ const defaultDecimals = column.currency ? 2 : 0;
355
+ const decimals = typeof column.decimals === "number" ? column.decimals : defaultDecimals;
356
+ const currencyProps = column.currency ? `style="currency" currency="${column.currency}"` : "";
357
+ renderCell = `({ value }) => {
358
+ return (typeof value === "number") ? <FormattedNumber value={value} ${currencyProps} minimumFractionDigits={${decimals}} maximumFractionDigits={${decimals}} /> : "";
359
+ }`;
284
360
  }
285
361
  else if (type == "boolean") {
286
362
  gridType = "boolean";
@@ -419,7 +495,7 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
419
495
  createMutationInputFields = inputType.inputFields.filter((field) => fieldsToLoad.some((gridColumnField) => gridColumnField.name == field.name));
420
496
  }
421
497
  }
422
- const fragmentName = (_b = config.fragmentName) !== null && _b !== void 0 ? _b : `${gqlTypePlural}Form`;
498
+ const fragmentName = (_g = config.fragmentName) !== null && _g !== void 0 ? _g : `${gqlTypePlural}Form`;
423
499
  if (forwardRowAction) {
424
500
  props.push({
425
501
  name: "rowAction",
@@ -458,7 +534,7 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
458
534
  : ""}
459
535
  ${config.initialFilter
460
536
  ? `initialFilter:{ ${config.initialFilter.linkOperator
461
- ? `linkOperator: GridLinkOperator.${config.initialFilter.linkOperator === "or" ? "Or" : "And"},`
537
+ ? `linkOperator: GridLogicOperator.${config.initialFilter.linkOperator === "or" ? "Or" : "And"},`
462
538
  : ""}
463
539
  items: [${config.initialFilter.items
464
540
  .map((item) => {
@@ -469,47 +545,17 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
469
545
  ${config.queryParamsPrefix ? `queryParamsPrefix: "${config.queryParamsPrefix}",` : ""}
470
546
  }`
471
547
  : "";
472
- const code = `import { gql, useApolloClient, useQuery } from "@apollo/client";
473
- import {
474
- Button,
475
- CrudContextMenu,
476
- CrudMoreActionsMenu,
477
- DataGridToolbar,
478
- ExportApi,
479
- filterByFragment,
480
- GridFilterButton,
481
- GridCellContent,
482
- GridColDef,
483
- dataGridDateTimeColumn,
484
- dataGridDateColumn,
485
- renderStaticSelectCell,
486
- messages,
487
- muiGridFilterToGql,
488
- muiGridSortToGql,
489
- StackLink,
490
- FillSpace,
491
- Tooltip,
492
- useBufferedRowCount,
493
- useDataGridExcelExport,
494
- useDataGridRemote,
495
- usePersistentColumnState,
496
- } from "@comet/admin";
497
- import { Add as AddIcon, Edit, Info, MoreVertical, Excel } from "@comet/admin-icons";
498
- import { BlockPreviewContent } from "@comet/cms-admin";
499
- import { Alert, Box, IconButton, Typography, useTheme, Menu, MenuItem, ListItemIcon, ListItemText, CircularProgress } from "@mui/material";
500
- import { DataGridPro, GridLinkOperator, GridRenderCellParams, GridSlotsComponent, GridToolbarProps, GridColumnHeaderTitle, GridToolbarQuickFilter } from "@mui/x-data-grid-pro";
501
- import { useContentScope } from "@src/common/ContentScopeProvider";
502
- import {
548
+ const code = `import {
503
549
  GQL${gqlTypePlural}GridQuery,
504
550
  GQL${gqlTypePlural}GridQueryVariables,
505
551
  GQL${fragmentName}Fragment,
506
552
  GQLCreate${gqlType}Mutation,
507
553
  GQLCreate${gqlType}MutationVariables,
554
+ GQLUpdate${gqlType}PositionMutation,
555
+ GQLUpdate${gqlType}PositionMutationVariables,
508
556
  GQLDelete${gqlType}Mutation,
509
557
  GQLDelete${gqlType}MutationVariables
510
558
  } from "./${baseOutputFilename}.generated";
511
- import { ReactNode } from "react";
512
- import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
513
559
  ${(0, generateImportsCode_1.generateImportsCode)(imports)}
514
560
 
515
561
  const ${instanceGqlTypePlural}Fragment = gql\`
@@ -545,6 +591,17 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
545
591
  \${${instanceGqlTypePlural}Fragment}
546
592
  \`;
547
593
 
594
+ ${allowRowReordering
595
+ ? `const update${gqlType}PositionMutation = gql\`
596
+ mutation Update${gqlType}Position($id: ID!, $input: ${gqlType}UpdateInput!) {
597
+ update${gqlType}(id: $id, input: $input) {
598
+ id
599
+ position
600
+ updatedAt
601
+ }
602
+ }
603
+ \`;`
604
+ : ""}
548
605
 
549
606
  ${allowDeleting
550
607
  ? `const delete${gqlType}Mutation = gql\`
@@ -600,6 +657,8 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
600
657
  ${hasScope ? `const { scope } = useContentScope();` : ""}
601
658
  ${gridNeedsTheme ? `const theme = useTheme();` : ""}
602
659
 
660
+ ${generateHandleRowOrderChange(allowRowReordering, gqlType, instanceGqlTypePlural)}
661
+
603
662
  const columns: GridColDef<GQL${fragmentName}Fragment>[] = [
604
663
  ${gridColumnFields
605
664
  .map((column) => {
@@ -630,7 +689,7 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
630
689
  <Tooltip
631
690
  title={<FormattedMessage id="${instanceGqlType}.${column.name}.tooltip" defaultMessage="${column.headerInfoTooltip}" />}
632
691
  >
633
- <Info sx={{ marginLeft: 1 }} />
692
+ <InfoIcon sx={{ marginLeft: 1 }} />
634
693
  </Tooltip>
635
694
  </>
636
695
  )`
@@ -639,8 +698,8 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
639
698
  ? `intl.formatMessage({ id: "${instanceGqlType}.${column.name}", defaultMessage: "${column.headerName || (0, camelCaseToHumanReadable_1.camelCaseToHumanReadable)(column.name)}" })`
640
699
  : undefined,
641
700
  type: column.gridType ? `"${column.gridType}"` : undefined,
642
- filterable: !column.filterOperators && !filterFields.includes(column.name) ? `false` : undefined,
643
- sortable: !sortFields.includes(column.name) ? `false` : undefined,
701
+ filterable: (!column.filterOperators && !filterFields.includes(column.name)) || allowRowReordering ? `false` : undefined,
702
+ sortable: !sortFields.includes(column.name) || allowRowReordering ? `false` : undefined,
644
703
  valueGetter: column.valueGetter,
645
704
  valueFormatter: column.valueFormatter,
646
705
  valueOptions: column.valueOptions,
@@ -753,17 +812,20 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
753
812
  : []
754
813
  : []),
755
814
  ...(hasSearch ? ["search: gqlSearch"] : []),
756
- ...[
757
- `offset: dataGridProps.paginationModel.page * dataGridProps.paginationModel.pageSize`,
758
- `limit: dataGridProps.paginationModel.pageSize`,
759
- `sort: muiGridSortToGql(dataGridProps.sortModel)`,
760
- ],
815
+ ...(!allowRowReordering
816
+ ? [
817
+ `offset: dataGridProps.paginationModel.page * dataGridProps.paginationModel.pageSize`,
818
+ `limit: dataGridProps.paginationModel.pageSize`,
819
+ `sort: muiGridSortToGql(dataGridProps.sortModel)`,
820
+ ]
821
+ : // TODO: offset and limit should not be necessary for row reordering but not yet possible to disable in the api generator
822
+ [`offset: 0`, `limit: 100`, `sort: { field: "position", direction: "ASC" }`]),
761
823
  ].join(", ")}
762
824
  },
763
825
  });
764
826
  const rowCount = useBufferedRowCount(data?.${gridQuery}.totalCount);
765
827
  if (error) throw error;
766
- const rows = data?.${gridQuery}.nodes ?? [];
828
+ const rows = ${!allowRowReordering ? `data?.${gridQuery}.nodes` : generateRowReorderingRows(gridQuery, fieldList, (_h = config.rowReordering) === null || _h === void 0 ? void 0 : _h.dragPreviewField)} ?? [];
767
829
 
768
830
  ${generateGridExportApi(config.excelExport, gqlTypePlural, gridQuery)}
769
831
 
@@ -779,6 +841,11 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
779
841
  toolbar: ${gridToolbarComponentName} as GridSlotsComponent["toolbar"],
780
842
  }}
781
843
  ${getDataGridSlotProps(gridToolbarComponentName, forwardToolbarAction, config.excelExport)}`
844
+ : ""}
845
+ ${allowRowReordering
846
+ ? `rowReordering
847
+ onRowOrderChange={handleRowOrderChange}
848
+ hideFooterPagination`
782
849
  : ""}
783
850
  />
784
851
  );
@@ -789,6 +856,14 @@ function generateGrid({ exportName, baseOutputFilename, targetDirectory, gqlIntr
789
856
  gqlDocuments,
790
857
  };
791
858
  }
859
+ const generateRowReorderingRows = (gridQuery, fieldList, dragPreviewField) => {
860
+ const fields = fieldList.split(" ");
861
+ const reorderField = dragPreviewField || fields.find((field) => field === "title" || field === "name") || "id";
862
+ return `data?.${gridQuery}.nodes.map((node) => ({
863
+ ...node,
864
+ __reorder__: node.${reorderField}
865
+ }))`;
866
+ };
792
867
  const getDefaultActionsColumnWidth = (showCrudContextMenu, showEdit) => {
793
868
  let numberOfActions = 0;
794
869
  if (showCrudContextMenu) {
@@ -830,3 +905,16 @@ const generateGridExportApi = (excelExport, gqlTypePlural, gridQuery) => {
830
905
  },
831
906
  });`;
832
907
  };
908
+ const generateHandleRowOrderChange = (allowRowReordering, gqlType, instanceGqlTypePlural) => {
909
+ if (!allowRowReordering) {
910
+ return "";
911
+ }
912
+ return `const handleRowOrderChange = async ({ row: { id }, targetIndex }: GridRowOrderChangeParams) => {
913
+ await client.mutate<GQLUpdate${gqlType}PositionMutation, GQLUpdate${gqlType}PositionMutationVariables>({
914
+ mutation: update${gqlType}PositionMutation,
915
+ variables: { id, input: { position: targetIndex + 1 } },
916
+ awaitRefetchQueries: true,
917
+ refetchQueries: [${instanceGqlTypePlural}Query]
918
+ });
919
+ };`;
920
+ };
@@ -59,7 +59,7 @@ const renderToolbarActions = ({ forwardToolbarAction, addItemText, excelExport,
59
59
  ${excelExport
60
60
  ? `{
61
61
  label: <FormattedMessage {...messages.downloadAsExcel} />,
62
- icon: exportApi.loading ? <CircularProgress size={20} /> : <Excel />,
62
+ icon: exportApi.loading ? <CircularProgress size={20} /> : <ExcelIcon />,
63
63
  onClick: () => exportApi.exportGrid(),
64
64
  disabled: exportApi.loading,
65
65
  }`
@@ -46,39 +46,78 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
48
  exports.writeGenerated = writeGenerated;
49
- const chalk_1 = __importDefault(require("chalk"));
50
- const eslint_1 = require("eslint");
51
49
  const fs_1 = require("fs");
52
50
  const path = __importStar(require("path"));
51
+ const typescript_1 = __importDefault(require("typescript"));
53
52
  function writeGenerated(filePath, contents) {
54
53
  return __awaiter(this, void 0, void 0, function* () {
55
54
  const header = `// This file has been generated by comet admin-generator.
56
55
  // You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
57
56
  `;
58
57
  yield fs_1.promises.mkdir(path.dirname(filePath), { recursive: true });
59
- const eslint = new eslint_1.ESLint({
60
- cwd: process.cwd(),
61
- fix: true,
58
+ const sourceFile = typescript_1.default.createSourceFile(filePath, header + contents, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TSX);
59
+ const result = typescript_1.default.transform(sourceFile, [removeUnusedImports()]);
60
+ const printer = typescript_1.default.createPrinter({
61
+ newLine: typescript_1.default.NewLineKind.LineFeed,
62
+ removeComments: false,
62
63
  });
63
- // Write not linted generated code into file. This is necessary to avoid linting errors like: Parsing error: file/path/file.tsx was not found by the project service.
64
- yield fs_1.promises.writeFile(filePath, header + contents);
65
- const lintResult = yield eslint.lintText(header + contents, {
66
- filePath,
67
- });
68
- if (lintResult[0].errorCount > 0 || lintResult[0].fatalErrorCount > 0) {
69
- const errorMessage = lintResult[0].messages
70
- .map((message) => {
71
- return message.message;
72
- })
73
- .join(".");
74
- console.error(chalk_1.default.red(`Linting error in ${filePath}: \n${errorMessage}`));
75
- }
76
- else if (lintResult[0].output != null) {
77
- yield fs_1.promises.writeFile(filePath, lintResult[0].output);
78
- }
79
- else {
80
- console.error(chalk_1.default.red("No output from linting"));
81
- }
64
+ const prettyCode = printer.printFile(result.transformed[0]).replace(/gql `/g, "gql`");
65
+ yield fs_1.promises.writeFile(filePath, prettyCode);
82
66
  console.log(`generated ${filePath}`);
83
67
  });
84
68
  }
69
+ // copy from @comet/api-generator
70
+ function removeUnusedImports() {
71
+ return (context) => {
72
+ function visit(sourceFile) {
73
+ const usedIdentifiers = new Set();
74
+ // Step 1: Collect all used identifiers
75
+ function collectUsage(node) {
76
+ if (typescript_1.default.isIdentifier(node)) {
77
+ usedIdentifiers.add(node.text);
78
+ }
79
+ else if (typescript_1.default.isImportDeclaration(node)) {
80
+ // Skip import declarations (those identifiers are not usages)
81
+ return;
82
+ }
83
+ typescript_1.default.forEachChild(node, collectUsage);
84
+ }
85
+ typescript_1.default.forEachChild(sourceFile, collectUsage);
86
+ // Step 2: Visit and update the import declarations
87
+ function visitor(node) {
88
+ if (typescript_1.default.isImportDeclaration(node) && node.importClause) {
89
+ const { name, namedBindings } = node.importClause;
90
+ const updatedBindings = [];
91
+ if (name && usedIdentifiers.has(name.text)) {
92
+ // default import is used
93
+ }
94
+ else if (name) {
95
+ // default import is unused
96
+ return undefined;
97
+ }
98
+ if (namedBindings && typescript_1.default.isNamedImports(namedBindings)) {
99
+ for (const specifier of namedBindings.elements) {
100
+ const importName = specifier.name.text;
101
+ if (usedIdentifiers.has(importName)) {
102
+ updatedBindings.push(specifier);
103
+ }
104
+ }
105
+ if (updatedBindings.length === 0 && !name) {
106
+ // nothing left in this import
107
+ return undefined;
108
+ }
109
+ return typescript_1.default.factory.updateImportDeclaration(node, node.modifiers, typescript_1.default.factory.updateImportClause(node.importClause, node.importClause.isTypeOnly, name, updatedBindings.length > 0 ? typescript_1.default.factory.updateNamedImports(namedBindings, updatedBindings) : undefined), node.moduleSpecifier, node.assertClause);
110
+ }
111
+ if (!namedBindings && !name) {
112
+ // empty import, probably a side-effect import
113
+ return node;
114
+ }
115
+ return node;
116
+ }
117
+ return typescript_1.default.visitEachChild(node, visitor, context);
118
+ }
119
+ return typescript_1.default.visitNode(sourceFile, visitor);
120
+ }
121
+ return (sourceFile) => visit(sourceFile);
122
+ };
123
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comet/admin-generator",
3
- "version": "8.0.0-beta.4",
3
+ "version": "8.0.0-beta.5",
4
4
  "repository": {
5
5
  "directory": "packages/admin/admin-generator",
6
6
  "type": "git",
@@ -19,24 +19,22 @@
19
19
  "dependencies": {
20
20
  "@graphql-tools/graphql-file-loader": "^7.5.17",
21
21
  "@graphql-tools/load": "^7.8.14",
22
- "chalk": "^5.4.1",
23
22
  "change-case": "^4.1.2",
24
23
  "commander": "^9.5.0",
25
24
  "glob": "^10.4.5",
26
25
  "graphql": "^16.10.0",
27
26
  "object-path": "^0.11.8",
28
27
  "pluralize": "^8.0.0",
29
- "ts-morph": "^25.0.1",
30
28
  "ts-node": "^10.9.2"
31
29
  },
32
30
  "devDependencies": {
33
- "@mui/material": "^7.0.0",
34
- "@mui/x-data-grid": "^7.28.1",
31
+ "@mui/material": "^7.1.0",
32
+ "@mui/x-data-grid": "^7.29.4",
35
33
  "@types/jest": "^29.5.14",
36
- "@types/node": "^22.14.0",
34
+ "@types/node": "^22.15.21",
37
35
  "@types/object-path": "^0.11.4",
38
36
  "@types/pluralize": "^0.0.33",
39
- "@types/react": "^18.3.20",
37
+ "@types/react": "^18.3.22",
40
38
  "eslint": "^9.22.0",
41
39
  "final-form": "^4.20.10",
42
40
  "jest": "^29.7.0",
@@ -47,10 +45,10 @@
47
45
  "rimraf": "^6.0.1",
48
46
  "ts-jest": "^29.2.6",
49
47
  "typescript": "^5.7.3",
50
- "@comet/admin": "8.0.0-beta.4",
51
- "@comet/admin-icons": "8.0.0-beta.4",
52
- "@comet/cms-admin": "8.0.0-beta.4",
53
- "@comet/eslint-config": "8.0.0-beta.4"
48
+ "@comet/admin": "8.0.0-beta.5",
49
+ "@comet/admin-icons": "8.0.0-beta.5",
50
+ "@comet/cms-admin": "8.0.0-beta.5",
51
+ "@comet/eslint-config": "8.0.0-beta.5"
54
52
  },
55
53
  "engines": {
56
54
  "node": ">=22.0.0"
@@ -67,7 +65,7 @@
67
65
  "lint:eslint": "eslint --max-warnings 0 src/ **/*.json --no-warn-ignored",
68
66
  "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'",
69
67
  "lint:tsc": "tsc",
70
- "test": "jest --passWithNoTests",
71
- "test:watch": "jest --watch"
68
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
69
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
72
70
  }
73
71
  }
@@ -1,4 +0,0 @@
1
- import { type SourceFile } from "ts-morph";
2
- import { type GeneratorConfig } from "../generate-command";
3
- export declare function morphTsSource(path: string): SourceFile;
4
- export declare function configFromSourceFile(sourceFile: SourceFile): GeneratorConfig<any>;
@@ -1,216 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.morphTsSource = morphTsSource;
4
- exports.configFromSourceFile = configFromSourceFile;
5
- const ts_morph_1 = require("ts-morph");
6
- const project = new ts_morph_1.Project({
7
- tsConfigFilePath: "tsconfig.json",
8
- skipAddingFilesFromTsConfig: true,
9
- });
10
- function morphTsSource(path) {
11
- let tsSource = project.getSourceFile(path);
12
- if (!tsSource)
13
- tsSource = project.addSourceFileAtPath(path);
14
- return tsSource;
15
- }
16
- const supportedImportPaths = [
17
- "[type=grid].columns.filterOperators",
18
- "[type=grid].columns.block",
19
- "[type=grid].columns.component",
20
- // TODO implement in generator "[type=grid].columns.renderCell",
21
- //support in up to 5 levels of nested fields (eg. fieldSet)
22
- ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.validate`),
23
- ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.block`),
24
- ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.component`),
25
- ];
26
- const supportedInlineCodePaths = [
27
- // TODO implement in generator "[type=grid].columns.filterOperators",
28
- "[type=grid].columns.renderCell",
29
- //support in up to 5 levels of nested fields (eg. fieldSet)
30
- ...Array.from(Array(5).keys()).map((i) => `[type=form]${".fields".repeat(i + 1)}.validate`),
31
- ];
32
- function configFromSourceFile(sourceFile) {
33
- for (const [name, declarations] of Array.from(sourceFile.getExportedDeclarations().entries())) {
34
- if (name == "default") {
35
- if (declarations.length != 1) {
36
- throw new Error(`Expected exactly one declaration for ${name}`);
37
- }
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- const config = exportedDeclarationToJson(declarations[0]);
40
- //console.dir(config, { depth: 10 });
41
- return config;
42
- }
43
- else {
44
- //ignore this export
45
- }
46
- }
47
- throw new Error(`No default export found, please export the GeneratorConfig as default, preferrable using defineConfig helper.`);
48
- }
49
- function findUsedImports(node) {
50
- const imports = [];
51
- node.getDescendantsOfKind(ts_morph_1.SyntaxKind.Identifier).forEach((identifier) => {
52
- var _a;
53
- for (const referencedSymbol of identifier.findReferences()) {
54
- for (const reference of referencedSymbol.getReferences()) {
55
- const referenceNode = reference.getNode();
56
- const importDeclaration = referenceNode.getFirstAncestorByKind(ts_morph_1.SyntaxKind.ImportDeclaration);
57
- if (importDeclaration) {
58
- for (const namedImport of importDeclaration.getNamedImports()) {
59
- if (((_a = (namedImport.getAliasNode() || namedImport.getNameNode())) === null || _a === void 0 ? void 0 : _a.getText()) == referenceNode.getText()) {
60
- imports.push({
61
- name: namedImport.getNameNode().getText(),
62
- import: importDeclaration.getModuleSpecifierValue(),
63
- });
64
- }
65
- }
66
- }
67
- }
68
- }
69
- });
70
- return imports;
71
- }
72
- function getTypePropertyFromObjectLiteral(node) {
73
- for (const property of node.getProperties()) {
74
- if (property.getKind() == ts_morph_1.SyntaxKind.PropertyAssignment) {
75
- const propertyAssignment = property;
76
- if (propertyAssignment.getName() == "type") {
77
- const propertyAssignmentInitializer = propertyAssignment.getInitializer();
78
- if ((propertyAssignmentInitializer === null || propertyAssignmentInitializer === void 0 ? void 0 : propertyAssignmentInitializer.getKind()) == ts_morph_1.SyntaxKind.StringLiteral) {
79
- return propertyAssignmentInitializer.getLiteralValue();
80
- }
81
- }
82
- }
83
- }
84
- return null;
85
- }
86
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
- function astNodeToJson(node, path) {
88
- var _a;
89
- if (node.getKind() == ts_morph_1.SyntaxKind.ObjectLiteralExpression) {
90
- if (path == "") {
91
- // first entry of path is the type, then property names (. separated) are added
92
- const typeProperty = getTypePropertyFromObjectLiteral(node);
93
- path = typeProperty ? `[type=${typeProperty}]` : "";
94
- }
95
- const ret = {};
96
- for (const property of node.getProperties()) {
97
- if (property.getKind() == ts_morph_1.SyntaxKind.PropertyAssignment) {
98
- const propertyAssignment = property;
99
- const propertyAssignmentInitializer = propertyAssignment.getInitializer();
100
- if (propertyAssignmentInitializer) {
101
- ret[propertyAssignment.getName()] = astNodeToJson(propertyAssignmentInitializer, `${path}.${propertyAssignment.getName()}`);
102
- }
103
- else {
104
- throw new Error(`Initializer is required for propertyAssignment '${propertyAssignment.getName()}'`);
105
- }
106
- }
107
- else {
108
- throw new Error(`Unsupported ObjectLiteralExpression property Kind ${property.getKindName()}`);
109
- }
110
- }
111
- return ret;
112
- }
113
- else if (node.getKind() == ts_morph_1.SyntaxKind.StringLiteral) {
114
- return node.getLiteralValue();
115
- }
116
- else if (node.getKind() == ts_morph_1.SyntaxKind.NumericLiteral) {
117
- return node.getLiteralValue();
118
- }
119
- else if (node.getKind() == ts_morph_1.SyntaxKind.FalseKeyword) {
120
- return false;
121
- }
122
- else if (node.getKind() == ts_morph_1.SyntaxKind.TrueKeyword) {
123
- return true;
124
- }
125
- else if (node.getKind() == ts_morph_1.SyntaxKind.BinaryExpression) {
126
- //what is this?
127
- return node.getText();
128
- }
129
- else if (node.getKind() == ts_morph_1.SyntaxKind.ArrowFunction) {
130
- if (supportedInlineCodePaths.includes(path)) {
131
- const body = node.getBody();
132
- return {
133
- code: node.getText(),
134
- imports: findUsedImports(body),
135
- };
136
- }
137
- else {
138
- throw new Error(`Inline Function is not allowed here and calling the function is not supported: ${path}`);
139
- }
140
- }
141
- else if (node.getKind() == ts_morph_1.SyntaxKind.ArrayLiteralExpression) {
142
- const ret = [];
143
- for (const element of node.getElements()) {
144
- ret.push(astNodeToJson(element, path));
145
- }
146
- return ret;
147
- }
148
- else if (node.getKind() == ts_morph_1.SyntaxKind.Identifier) {
149
- for (const referencedSymbol of node.findReferences()) {
150
- for (const reference of referencedSymbol.getReferences()) {
151
- const referenceNode = reference.getNode();
152
- const importDeclaration = referenceNode.getFirstAncestorByKind(ts_morph_1.SyntaxKind.ImportDeclaration);
153
- const variableDeclaration = referenceNode.getFirstAncestorByKind(ts_morph_1.SyntaxKind.VariableDeclaration);
154
- if (importDeclaration) {
155
- for (const namedImport of importDeclaration.getNamedImports()) {
156
- if (((_a = (namedImport.getAliasNode() || namedImport.getNameNode())) === null || _a === void 0 ? void 0 : _a.getText()) == referenceNode.getText()) {
157
- if (supportedImportPaths.includes(path)) {
158
- return {
159
- name: namedImport.getNameNode().getText(),
160
- import: importDeclaration.getModuleSpecifierValue(),
161
- };
162
- }
163
- else {
164
- throw new Error(`Following the import is not supported: ${path} ${namedImport.getText()}`);
165
- }
166
- }
167
- }
168
- }
169
- else if (variableDeclaration) {
170
- if (variableDeclaration.getName() == node.getText()) {
171
- const variableInitializer = variableDeclaration.getInitializer();
172
- if (variableInitializer) {
173
- return astNodeToJson(variableInitializer, path);
174
- }
175
- else {
176
- throw new Error(`Initializer is required for variableDeclaration '${variableDeclaration.getName()}'`);
177
- }
178
- }
179
- }
180
- }
181
- }
182
- throw new Error(`Unsupported identifier '${node.getText()}', only imports and are variable declarations with initializer are supported`);
183
- }
184
- else {
185
- throw new Error(`Unsupported expression Kind ${node.getKindName()}`);
186
- }
187
- }
188
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
- function exportedDeclarationToJson(node) {
190
- if (node.getKind() == ts_morph_1.SyntaxKind.VariableDeclaration) {
191
- const variableDeclaration = node;
192
- const initializer = variableDeclaration.getInitializer();
193
- if (initializer) {
194
- return astNodeToJson(initializer, "");
195
- }
196
- else {
197
- throw new Error(`Initializer is required for variableDeclaration '${variableDeclaration.getName()}'`);
198
- }
199
- }
200
- else if (node.getKind() == ts_morph_1.SyntaxKind.CallExpression) {
201
- const callExpression = node;
202
- if (callExpression.getExpression().getText() == "defineConfig") {
203
- const args = callExpression.getArguments();
204
- if (args.length != 1) {
205
- throw new Error(`Expected exactly one argument for defineConfig`);
206
- }
207
- return astNodeToJson(args[0], "");
208
- }
209
- else {
210
- throw new Error(`Only direct call to defineConfig is supported`);
211
- }
212
- }
213
- else {
214
- throw new Error(`Unsupported declaration kind '${node.getKindName()}'`);
215
- }
216
- }