@mohsinonxrm/pptb-fetchxml-studio 1.0.0 → 1.0.1

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.
Files changed (77) hide show
  1. package/LICENSE +661 -0
  2. package/dist/assets/index-DD9fzkbz.js +232 -0
  3. package/dist/index.html +70 -0
  4. package/index.html +0 -1
  5. package/npm-shrinkwrap.json +6329 -0
  6. package/package.json +95 -51
  7. package/CHANGELOG.md +0 -120
  8. package/eslint.config.js +0 -23
  9. package/public/vite.svg +0 -1
  10. package/src/app/AppShell.tsx +0 -1733
  11. package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
  12. package/src/assets/react.svg +0 -1
  13. package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
  14. package/src/features/fetchxml/api/excelExport.ts +0 -318
  15. package/src/features/fetchxml/api/formattedValues.ts +0 -107
  16. package/src/features/fetchxml/api/pptbClient.ts +0 -2635
  17. package/src/features/fetchxml/model/displaySettings.ts +0 -18
  18. package/src/features/fetchxml/model/fetchxml.ts +0 -346
  19. package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
  20. package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
  21. package/src/features/fetchxml/model/layoutxml.ts +0 -372
  22. package/src/features/fetchxml/model/nodes.ts +0 -255
  23. package/src/features/fetchxml/model/operators.ts +0 -624
  24. package/src/features/fetchxml/model/treeUtils.ts +0 -174
  25. package/src/features/fetchxml/state/builderStore.tsx +0 -1160
  26. package/src/features/fetchxml/state/cache.ts +0 -393
  27. package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
  28. package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
  29. package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
  30. package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
  31. package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
  32. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
  33. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
  34. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
  35. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
  36. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
  37. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
  38. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
  39. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
  40. package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
  41. package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
  42. package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
  43. package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
  44. package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
  45. package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
  46. package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
  47. package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
  48. package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
  49. package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
  50. package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
  51. package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
  52. package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
  53. package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
  54. package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
  55. package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
  56. package/src/main.tsx +0 -11
  57. package/src/shared/components/AttributePicker.tsx +0 -138
  58. package/src/shared/components/BetweenInputPicker.tsx +0 -120
  59. package/src/shared/components/BooleanValuePicker.tsx +0 -165
  60. package/src/shared/components/DateTimeInputPicker.tsx +0 -170
  61. package/src/shared/components/DynamicValueList.tsx +0 -128
  62. package/src/shared/components/ExcelIcon.tsx +0 -30
  63. package/src/shared/components/MultiValuePicker.tsx +0 -178
  64. package/src/shared/components/NumericInputPicker.tsx +0 -156
  65. package/src/shared/components/OptionSetValuePicker.tsx +0 -150
  66. package/src/shared/components/RelationshipPicker.tsx +0 -268
  67. package/src/shared/contexts/ThemeContext.tsx +0 -19
  68. package/src/shared/hooks/useAccessMode.ts +0 -77
  69. package/src/shared/hooks/useLazyMetadata.ts +0 -132
  70. package/src/shared/hooks/usePptbContext.ts +0 -170
  71. package/src/shared/hooks/usePublisherFilter.ts +0 -214
  72. package/src/shared/hooks/useSolutionFilter.ts +0 -162
  73. package/src/shared/utils/debug.ts +0 -87
  74. package/tsconfig.app.json +0 -28
  75. package/tsconfig.json +0 -7
  76. package/tsconfig.node.json +0 -26
  77. package/vite.config.ts +0 -7
@@ -1,604 +0,0 @@
1
- /**
2
- * Tree view for FetchXML query builder
3
- * Displays hierarchical structure: Entity -> Attributes, Orders, Filters, Link-Entities
4
- */
5
-
6
- import { useState, useEffect } from "react";
7
- import {
8
- Tree,
9
- TreeItem,
10
- TreeItemLayout,
11
- Button,
12
- makeStyles,
13
- Menu,
14
- MenuTrigger,
15
- MenuPopover,
16
- MenuList,
17
- MenuItem,
18
- tokens,
19
- type TreeOpenChangeData,
20
- } from "@fluentui/react-components";
21
- import {
22
- Table20Regular,
23
- Column20Regular,
24
- Filter20Regular,
25
- ArrowSort20Regular,
26
- Link20Regular,
27
- Add20Regular,
28
- Delete20Regular,
29
- } from "@fluentui/react-icons";
30
- import type {
31
- FetchNode,
32
- EntityNode,
33
- AttributeNode,
34
- AllAttributesNode,
35
- OrderNode,
36
- FilterNode,
37
- ConditionNode,
38
- LinkEntityNode,
39
- NodeId,
40
- } from "../../model/nodes";
41
- import { operatorRequiresValue } from "../../model/operators";
42
- import { debugLog, debugWarn } from "../../../../shared/utils/debug";
43
-
44
- /**
45
- * Format a value for display, avoiding scientific notation for numbers
46
- */
47
- function formatDisplayValue(val: unknown): string {
48
- if (typeof val === "number") {
49
- if (Math.abs(val) < 1e-10 && val !== 0) {
50
- // Very small numbers - use high precision
51
- return val.toFixed(20).replace(/\.?0+$/, "");
52
- } else if (Number.isInteger(val)) {
53
- // Integer - no decimal point needed
54
- return val.toString();
55
- } else {
56
- // Decimal - preserve precision but remove trailing zeros
57
- return val.toFixed(10).replace(/\.?0+$/, "");
58
- }
59
- }
60
- if (Array.isArray(val)) {
61
- // Format array values (for between, in, etc.)
62
- return val.map((v) => formatDisplayValue(v)).join(", ");
63
- }
64
- return String(val);
65
- }
66
-
67
- const useStyles = makeStyles({
68
- container: {
69
- flex: 1,
70
- overflow: "auto",
71
- padding: "8px",
72
- minHeight: 0, // Important for flex child with overflow
73
- // Ensure scrollbars are always visible when content overflows
74
- scrollbarGutter: "stable",
75
- },
76
- treeItem: {
77
- "&:hover": {
78
- backgroundColor: tokens.colorNeutralBackground1Hover,
79
- },
80
- },
81
- actions: {
82
- display: "flex",
83
- gap: "4px",
84
- opacity: 0,
85
- transition: "opacity 0.2s",
86
- "&:focus-within": {
87
- opacity: 1,
88
- },
89
- },
90
- treeItemWithActions: {
91
- "&:hover .actions": {
92
- opacity: 1,
93
- },
94
- },
95
- });
96
-
97
- interface TreeViewProps {
98
- fetchQuery: FetchNode;
99
- selectedNodeId: NodeId | null;
100
- onNodeSelect: (nodeId: NodeId) => void;
101
- onAddAttribute: (parentId: NodeId) => void;
102
- onAddAllAttributes: (parentId: NodeId) => void;
103
- onAddOrder: (parentId: NodeId) => void;
104
- onAddFilter: (parentId: NodeId) => void;
105
- onAddSubfilter: (filterId: NodeId) => void;
106
- onAddCondition: (filterId: NodeId) => void;
107
- onAddLinkEntity: (parentId: NodeId) => void;
108
- onRemoveNode: (nodeId: NodeId) => void;
109
- }
110
-
111
- export function TreeView({
112
- fetchQuery,
113
- selectedNodeId,
114
- onNodeSelect,
115
- onAddAttribute,
116
- onAddAllAttributes,
117
- onAddOrder,
118
- onAddFilter,
119
- onAddSubfilter,
120
- onAddCondition,
121
- onAddLinkEntity,
122
- onRemoveNode,
123
- }: TreeViewProps) {
124
- const styles = useStyles();
125
-
126
- // Manage which tree items are open/expanded
127
- const [openItems, setOpenItems] = useState<Set<NodeId>>(
128
- new Set([fetchQuery.id, fetchQuery.entity.id])
129
- );
130
-
131
- // Helper to find path from root to a node (all ancestor IDs)
132
- const findPathToNode = (targetId: NodeId): NodeId[] => {
133
- const path: NodeId[] = [];
134
-
135
- const findInNode = (node: unknown, target: NodeId): boolean => {
136
- if (!node || typeof node !== "object") return false;
137
-
138
- const n = node as Record<string, unknown>;
139
-
140
- if (n.id === target) {
141
- debugLog("treeExpansion", "Found target node:", n.id);
142
- // Don't add the target to path, only ancestors
143
- return true;
144
- }
145
-
146
- // Add current node to path before searching children
147
- const currentId = n.id as NodeId;
148
- debugLog("treeExpansion", "Checking node:", currentId, "type:", n.type);
149
-
150
- // Search in all possible child arrays
151
- const arrayProps = ["attributes", "orders", "filters", "conditions", "subfilters", "links"];
152
- for (const prop of arrayProps) {
153
- if (Array.isArray(n[prop])) {
154
- for (const item of n[prop]) {
155
- if (findInNode(item, target)) {
156
- // Found in this branch, add current to path
157
- path.unshift(currentId);
158
- return true;
159
- }
160
- }
161
- }
162
- }
163
-
164
- // Search in nested entity
165
- if (n.entity && findInNode(n.entity, target)) {
166
- // Found in entity, add current to path
167
- path.unshift(currentId);
168
- return true;
169
- }
170
-
171
- return false;
172
- };
173
-
174
- findInNode(fetchQuery, targetId);
175
- debugLog("treeExpansion", "Final path:", path);
176
- return path;
177
- };
178
-
179
- // Auto-expand ancestors when a new node is selected
180
- useEffect(() => {
181
- if (selectedNodeId) {
182
- debugLog("treeExpansion", "Node selected, expanding path:", selectedNodeId);
183
- const pathToNode = findPathToNode(selectedNodeId);
184
- debugLog("treeExpansion", "Path to node:", pathToNode);
185
-
186
- if (pathToNode.length === 0) {
187
- debugWarn("treeExpansion", "Path is empty! Node might not be in tree yet.");
188
- return;
189
- }
190
-
191
- setOpenItems((prev) => {
192
- const newSet = new Set(prev);
193
- const sizeBefore = newSet.size;
194
- // Add all ancestors to open items (except the leaf node itself)
195
- pathToNode.forEach((id) => newSet.add(id));
196
- const itemsAdded = Array.from(newSet).filter((id) => !prev.has(id));
197
- debugLog("treeExpansion", "Updated openItems:", {
198
- sizeBefore,
199
- sizeAfter: newSet.size,
200
- added: itemsAdded,
201
- addedCount: itemsAdded.length,
202
- allOpen: Array.from(newSet),
203
- });
204
-
205
- // Always return the new set to ensure re-render even if no new items
206
- return newSet;
207
- });
208
-
209
- // Scroll the selected node into view after a short delay to allow DOM updates
210
- // Note: Fluent UI Tree doesn't set aria-selected="true" reliably, so we use
211
- // the last tree item as the target (newly added nodes appear at the end)
212
- setTimeout(() => {
213
- const allTreeItems = document.querySelectorAll('[role="treeitem"]');
214
-
215
- if (allTreeItems.length > 0) {
216
- const targetElement = allTreeItems[allTreeItems.length - 1];
217
- targetElement.scrollIntoView({ behavior: "smooth", block: "nearest" });
218
- debugLog(
219
- "treeExpansion",
220
- `Scrolled to item ${allTreeItems.length} of ${allTreeItems.length}`
221
- );
222
- } else {
223
- debugWarn("treeExpansion", "No tree items found in DOM for scrolling");
224
- }
225
- }, 250);
226
- }
227
- }, [selectedNodeId]); // eslint-disable-line react-hooks/exhaustive-deps
228
-
229
- const handleOpenChange = (_event: unknown, data: TreeOpenChangeData) => {
230
- debugLog("treeExpansion", "handleOpenChange called:", {
231
- open: data.open,
232
- value: data.value,
233
- type: data.type,
234
- openItemsCount: data.openItems.size,
235
- openItems: Array.from(data.openItems),
236
- });
237
- // data.openItems is already the updated Set from Fluent UI
238
- // Use it directly to respect user's expand/collapse actions
239
- setOpenItems(data.openItems as Set<NodeId>);
240
- };
241
-
242
- const renderFetchNode = (fetch: FetchNode) => {
243
- return (
244
- <TreeItem
245
- key={fetch.id}
246
- itemType="branch"
247
- value={fetch.id}
248
- className={styles.treeItemWithActions}
249
- >
250
- <TreeItemLayout iconBefore={<Table20Regular />} onClick={() => onNodeSelect(fetch.id)}>
251
- Fetch Query
252
- </TreeItemLayout>
253
-
254
- <Tree>{renderEntityNode(fetch.entity, true)}</Tree>
255
- </TreeItem>
256
- );
257
- };
258
-
259
- const renderEntityNode = (entity: EntityNode, isRootEntity: boolean = true) => {
260
- // Only allow one top-level filter per entity
261
- const hasFilter = entity.filters.length > 0;
262
-
263
- return (
264
- <TreeItem
265
- key={entity.id}
266
- itemType="branch"
267
- value={entity.id}
268
- className={styles.treeItemWithActions}
269
- >
270
- <TreeItemLayout
271
- iconBefore={<Table20Regular />}
272
- actions={
273
- <div className="actions">
274
- <Menu>
275
- <MenuTrigger disableButtonEnhancement>
276
- <Button appearance="subtle" size="small" icon={<Add20Regular />} title="Add..." />
277
- </MenuTrigger>
278
- <MenuPopover>
279
- <MenuList>
280
- <MenuItem onClick={() => onAddAllAttributes(entity.id)}>
281
- All Attributes
282
- </MenuItem>
283
- <MenuItem onClick={() => onAddAttribute(entity.id)}>Attribute</MenuItem>
284
- <MenuItem onClick={() => onAddOrder(entity.id)}>Order By</MenuItem>
285
- <MenuItem onClick={() => onAddFilter(entity.id)} disabled={hasFilter}>
286
- Filter
287
- </MenuItem>
288
- <MenuItem onClick={() => onAddLinkEntity(entity.id)}>Link-Entity</MenuItem>
289
- </MenuList>
290
- </MenuPopover>
291
- </Menu>
292
- {!isRootEntity && (
293
- <Button
294
- appearance="subtle"
295
- size="small"
296
- icon={<Delete20Regular />}
297
- onClick={(e) => {
298
- e.stopPropagation();
299
- onRemoveNode(entity.id);
300
- }}
301
- title="Remove"
302
- />
303
- )}
304
- </div>
305
- }
306
- onClick={() => onNodeSelect(entity.id)}
307
- >
308
- {entity.name}
309
- </TreeItemLayout>
310
-
311
- <Tree>
312
- {/* All Attributes */}
313
- {entity.allAttributes?.enabled && renderAllAttributesNode(entity.allAttributes)}
314
-
315
- {/* Attributes */}
316
- {entity.attributes.map((attr) => renderAttributeNode(attr))}
317
-
318
- {/* Orders */}
319
- {entity.orders.map((order) => renderOrderNode(order))}
320
-
321
- {/* Filters */}
322
- {entity.filters.map((filter) => renderFilterNode(filter))}
323
-
324
- {/* Link-Entities */}
325
- {entity.links.map((link) => renderLinkEntityNode(link))}
326
- </Tree>
327
- </TreeItem>
328
- );
329
- };
330
-
331
- const renderAllAttributesNode = (node: AllAttributesNode) => {
332
- return (
333
- <TreeItem key={node.id} itemType="leaf" value={node.id} className={styles.treeItem}>
334
- <TreeItemLayout
335
- iconBefore={<Column20Regular />}
336
- actions={
337
- <Button
338
- appearance="subtle"
339
- size="small"
340
- icon={<Delete20Regular />}
341
- onClick={(e) => {
342
- e.stopPropagation();
343
- onRemoveNode(node.id);
344
- }}
345
- title="Remove"
346
- />
347
- }
348
- onClick={() => onNodeSelect(node.id)}
349
- >
350
- All Attributes
351
- </TreeItemLayout>
352
- </TreeItem>
353
- );
354
- };
355
-
356
- const renderAttributeNode = (attr: AttributeNode) => {
357
- const label = attr.alias ? `${attr.name} (as ${attr.alias})` : attr.name;
358
- const suffix = attr.aggregate ? ` [${attr.aggregate}]` : "";
359
-
360
- return (
361
- <TreeItem
362
- key={attr.id}
363
- itemType="leaf"
364
- value={attr.id}
365
- className={styles.treeItemWithActions}
366
- >
367
- <TreeItemLayout
368
- iconBefore={<Column20Regular />}
369
- actions={
370
- <div className="actions">
371
- <Button
372
- appearance="subtle"
373
- size="small"
374
- icon={<Delete20Regular />}
375
- onClick={(e) => {
376
- e.stopPropagation();
377
- onRemoveNode(attr.id);
378
- }}
379
- title="Remove Attribute"
380
- />
381
- </div>
382
- }
383
- onClick={() => onNodeSelect(attr.id)}
384
- >
385
- {label}
386
- {suffix}
387
- </TreeItemLayout>
388
- </TreeItem>
389
- );
390
- };
391
-
392
- const renderOrderNode = (order: OrderNode) => {
393
- const direction = order.descending ? "↓" : "↑";
394
- // Show entityname prefix for link-entity orders
395
- const displayName = order.entityname
396
- ? `${order.entityname}.${order.attribute}`
397
- : order.attribute;
398
- return (
399
- <TreeItem
400
- key={order.id}
401
- itemType="leaf"
402
- value={order.id}
403
- className={styles.treeItemWithActions}
404
- >
405
- <TreeItemLayout
406
- iconBefore={<ArrowSort20Regular />}
407
- actions={
408
- <div className="actions">
409
- <Button
410
- appearance="subtle"
411
- size="small"
412
- icon={<Delete20Regular />}
413
- onClick={(e) => {
414
- e.stopPropagation();
415
- onRemoveNode(order.id);
416
- }}
417
- title="Remove Order"
418
- />
419
- </div>
420
- }
421
- onClick={() => onNodeSelect(order.id)}
422
- >
423
- {displayName} {direction}
424
- </TreeItemLayout>
425
- </TreeItem>
426
- );
427
- };
428
-
429
- const renderFilterNode = (filter: FilterNode) => {
430
- return (
431
- <TreeItem
432
- key={filter.id}
433
- itemType="branch"
434
- value={filter.id}
435
- className={styles.treeItemWithActions}
436
- >
437
- <TreeItemLayout
438
- iconBefore={<Filter20Regular />}
439
- actions={
440
- <div className="actions">
441
- <Menu>
442
- <MenuTrigger disableButtonEnhancement>
443
- <Button appearance="subtle" size="small" icon={<Add20Regular />} title="Add..." />
444
- </MenuTrigger>
445
- <MenuPopover>
446
- <MenuList>
447
- <MenuItem onClick={() => onAddCondition(filter.id)}>Condition</MenuItem>
448
- <MenuItem onClick={() => onAddSubfilter(filter.id)}>Sub-Filter</MenuItem>
449
- <MenuItem onClick={() => onAddLinkEntity(filter.id)}>
450
- Link-Entity (any/not any/all)
451
- </MenuItem>
452
- </MenuList>
453
- </MenuPopover>
454
- </Menu>
455
- <Button
456
- appearance="subtle"
457
- size="small"
458
- icon={<Delete20Regular />}
459
- onClick={(e) => {
460
- e.stopPropagation();
461
- onRemoveNode(filter.id);
462
- }}
463
- title="Remove Filter"
464
- />
465
- </div>
466
- }
467
- onClick={() => onNodeSelect(filter.id)}
468
- >
469
- Filter ({filter.conjunction})
470
- </TreeItemLayout>
471
-
472
- <Tree>
473
- {filter.conditions.map((cond) => renderConditionNode(cond))}
474
- {filter.subfilters.map((subfilter) => renderFilterNode(subfilter))}
475
- {filter.links?.map((link) => renderLinkEntityNode(link))}
476
- </Tree>
477
- </TreeItem>
478
- );
479
- };
480
-
481
- const renderConditionNode = (condition: ConditionNode) => {
482
- // Format the display text based on operator and value
483
- const requiresValue = operatorRequiresValue(condition.operator);
484
- let displayText = `${condition.attribute} ${condition.operator}`;
485
-
486
- if (
487
- requiresValue &&
488
- condition.value !== undefined &&
489
- condition.value !== null &&
490
- condition.value !== ""
491
- ) {
492
- displayText += ` = ${formatDisplayValue(condition.value)}`;
493
- }
494
-
495
- return (
496
- <TreeItem
497
- key={condition.id}
498
- itemType="leaf"
499
- value={condition.id}
500
- className={styles.treeItemWithActions}
501
- >
502
- <TreeItemLayout
503
- actions={
504
- <div className="actions">
505
- <Button
506
- appearance="subtle"
507
- size="small"
508
- icon={<Delete20Regular />}
509
- onClick={(e) => {
510
- e.stopPropagation();
511
- onRemoveNode(condition.id);
512
- }}
513
- title="Remove Condition"
514
- />
515
- </div>
516
- }
517
- onClick={() => onNodeSelect(condition.id)}
518
- >
519
- {displayText}
520
- </TreeItemLayout>
521
- </TreeItem>
522
- );
523
- };
524
-
525
- const renderLinkEntityNode = (link: LinkEntityNode) => {
526
- // Attributes are not allowed in "not any" and "not all" link types
527
- const canHaveAttributes = link.linkType !== "not any" && link.linkType !== "not all";
528
- // Only allow one top-level filter per link-entity
529
- const hasFilter = link.filters.length > 0;
530
-
531
- return (
532
- <TreeItem
533
- key={link.id}
534
- itemType="branch"
535
- value={link.id}
536
- className={styles.treeItemWithActions}
537
- >
538
- <TreeItemLayout
539
- iconBefore={<Link20Regular />}
540
- actions={
541
- <div className="actions">
542
- <Menu>
543
- <MenuTrigger disableButtonEnhancement>
544
- <Button appearance="subtle" size="small" icon={<Add20Regular />} title="Add..." />
545
- </MenuTrigger>
546
- <MenuPopover>
547
- <MenuList>
548
- <MenuItem
549
- onClick={() => onAddAllAttributes(link.id)}
550
- disabled={!canHaveAttributes}
551
- >
552
- All Attributes
553
- </MenuItem>
554
- <MenuItem onClick={() => onAddAttribute(link.id)} disabled={!canHaveAttributes}>
555
- Attribute
556
- </MenuItem>
557
- <MenuItem onClick={() => onAddOrder(link.id)}>Order By</MenuItem>
558
- <MenuItem onClick={() => onAddFilter(link.id)} disabled={hasFilter}>
559
- Filter
560
- </MenuItem>
561
- <MenuItem onClick={() => onAddLinkEntity(link.id)}>Link-Entity</MenuItem>
562
- </MenuList>
563
- </MenuPopover>
564
- </Menu>
565
- <Button
566
- appearance="subtle"
567
- size="small"
568
- icon={<Delete20Regular />}
569
- onClick={(e) => {
570
- e.stopPropagation();
571
- onRemoveNode(link.id);
572
- }}
573
- title="Remove Link"
574
- />
575
- </div>
576
- }
577
- onClick={() => onNodeSelect(link.id)}
578
- >
579
- {link.alias || link.name} ({link.linkType})
580
- </TreeItemLayout>
581
-
582
- <Tree>
583
- {link.allAttributes?.enabled && renderAllAttributesNode(link.allAttributes)}
584
- {link.attributes.map((attr) => renderAttributeNode(attr))}
585
- {link.orders.map((order) => renderOrderNode(order))}
586
- {link.filters.map((filter) => renderFilterNode(filter))}
587
- {link.links.map((nestedLink) => renderLinkEntityNode(nestedLink))}
588
- </Tree>
589
- </TreeItem>
590
- );
591
- };
592
-
593
- return (
594
- <div className={styles.container}>
595
- <Tree
596
- aria-label="FetchXML Query Structure"
597
- openItems={openItems}
598
- onOpenChange={handleOpenChange}
599
- >
600
- {renderFetchNode(fetchQuery)}
601
- </Tree>
602
- </div>
603
- );
604
- }