lhcb-ntuple-wizard-test 2.1.0 → 2.2.0

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 (178) hide show
  1. package/dist/components/AddTupleToolButton.d.ts +1 -0
  2. package/dist/components/AddTupleToolButton.js +16 -0
  3. package/dist/components/BookkeepingPathDropdown.d.ts +1 -1
  4. package/dist/components/BookkeepingPathDropdown.js +1 -4
  5. package/dist/components/DatasetEventTypeBadge.js +1 -1
  6. package/dist/components/DatasetInfo.js +3 -3
  7. package/dist/components/DecayCard.d.ts +1 -1
  8. package/dist/components/DecayCard.js +12 -40
  9. package/dist/components/DecayLatex.d.ts +1 -1
  10. package/dist/components/DecayLatex.js +4 -87
  11. package/dist/components/DecayListItem.js +2 -2
  12. package/dist/components/DecayTagBadge.d.ts +1 -1
  13. package/dist/components/DecayTagBadge.js +0 -3
  14. package/dist/components/DecayTreeCard.d.ts +1 -7
  15. package/dist/components/DecayTreeCard.js +13 -18
  16. package/dist/components/DecayTreeCardHeader.d.ts +2 -3
  17. package/dist/components/DecayTreeCardHeader.js +2 -2
  18. package/dist/components/DecayTreeGraph.d.ts +1 -4
  19. package/dist/components/DecayTreeGraph.js +66 -41
  20. package/dist/components/DeleteButton.d.ts +2 -1
  21. package/dist/components/DeleteButton.js +2 -2
  22. package/dist/components/DttNameInput.d.ts +9 -0
  23. package/dist/components/DttNameInput.js +43 -0
  24. package/dist/components/GuideLinkButton.d.ts +15 -0
  25. package/dist/components/GuideLinkButton.js +16 -0
  26. package/dist/components/NtupleWizard.js +1 -7
  27. package/dist/components/ParticleDropdown.d.ts +11 -1
  28. package/dist/components/ParticleDropdown.js +3 -6
  29. package/dist/components/ParticleLatex.d.ts +6 -0
  30. package/dist/components/ParticleLatex.js +13 -0
  31. package/dist/components/ParticleTagBadge.d.ts +2 -1
  32. package/dist/components/ParticleTagBadge.js +5 -7
  33. package/dist/components/ParticleTagFilters.d.ts +1 -6
  34. package/dist/components/ParticleTagFilters.js +16 -17
  35. package/dist/components/RequestButtonGroup.d.ts +1 -1
  36. package/dist/components/RequestButtonGroup.js +0 -3
  37. package/dist/components/RequestRow.d.ts +1 -1
  38. package/dist/components/RequestRow.js +4 -9
  39. package/dist/components/StrippingLineDropdown.js +14 -16
  40. package/dist/components/StrippingLineInfo.d.ts +5 -5
  41. package/dist/components/StrippingLineInfo.js +14 -4
  42. package/dist/components/StrippingLineInfoButton.d.ts +6 -0
  43. package/dist/components/StrippingLineInfoButton.js +7 -0
  44. package/dist/components/StrippingLineVersionBadge.d.ts +7 -0
  45. package/dist/components/{StrippingLineBadge.js → StrippingLineVersionBadge.js} +5 -5
  46. package/dist/components/StrippingLinesCountBadge.d.ts +8 -0
  47. package/dist/components/StrippingLinesCountBadge.js +11 -0
  48. package/dist/components/TagDropdown.d.ts +3 -3
  49. package/dist/components/TagDropdown.js +2 -2
  50. package/dist/components/TupleToolClassDropdown.js +11 -14
  51. package/dist/components/TupleToolDocsAccordion.d.ts +1 -1
  52. package/dist/components/TupleToolDocsAccordion.js +4 -8
  53. package/dist/components/TupleToolGroupsAccordion.d.ts +5 -0
  54. package/dist/components/TupleToolGroupsAccordion.js +31 -0
  55. package/dist/components/TupleToolLabel.d.ts +1 -1
  56. package/dist/components/TupleToolLabel.js +2 -5
  57. package/dist/components/TupleToolsAccordion.d.ts +1 -0
  58. package/dist/components/TupleToolsAccordion.js +22 -0
  59. package/dist/components/modals/AddTupleToolModal.js +3 -2
  60. package/dist/components/modals/ConfigureTupleToolModal.js +1 -1
  61. package/dist/components/modals/UploadDttConfigModal.d.ts +1 -1
  62. package/dist/components/modals/UploadDttConfigModal.js +1 -4
  63. package/dist/config.d.ts +1 -0
  64. package/dist/config.js +3 -2
  65. package/dist/models/bkPath.js +1 -1
  66. package/dist/models/dtt.d.ts +5 -2
  67. package/dist/models/dtt.js +37 -18
  68. package/dist/models/rowData.d.ts +1 -1
  69. package/dist/models/yamlFile.js +1 -1
  70. package/dist/pages/DecaySearchPage.js +4 -9
  71. package/dist/pages/DecayTreeConfigPage.js +4 -38
  72. package/dist/pages/RequestPage.js +2 -4
  73. package/dist/providers/DttProvider.d.ts +3 -3
  74. package/dist/providers/DttProvider.js +30 -55
  75. package/dist/providers/MetadataProvider.d.ts +1 -1
  76. package/dist/providers/MetadataProvider.js +11 -5
  77. package/dist/providers/RequestProvider.js +2 -2
  78. package/dist/providers/RowProvider.d.ts +2 -2
  79. package/dist/providers/RowProvider.js +10 -9
  80. package/dist/providers/RowsProvider.js +0 -3
  81. package/dist/tests/components/BookkeepingPathDropdown.test.d.ts +1 -0
  82. package/dist/tests/components/BookkeepingPathDropdown.test.js +118 -0
  83. package/dist/tests/components/DatasetInfo.test.d.ts +1 -0
  84. package/dist/tests/components/DatasetInfo.test.js +38 -0
  85. package/dist/tests/components/DecayCard.test.d.ts +1 -0
  86. package/dist/tests/components/DecayCard.test.js +115 -0
  87. package/dist/tests/components/DecayLatex.test.d.ts +1 -0
  88. package/dist/tests/components/DecayLatex.test.js +31 -0
  89. package/dist/tests/components/DecayList.test.d.ts +1 -0
  90. package/dist/tests/components/DecayList.test.js +76 -0
  91. package/dist/tests/components/DecayListItem.test.d.ts +1 -0
  92. package/dist/tests/components/DecayListItem.test.js +51 -0
  93. package/dist/tests/components/DecayTreeCard.test.d.ts +1 -0
  94. package/dist/tests/components/DecayTreeCard.test.js +119 -0
  95. package/dist/tests/components/DecayTreeGraph.test.d.ts +1 -0
  96. package/dist/tests/components/DecayTreeGraph.test.js +125 -0
  97. package/dist/tests/components/DeleteButton.test.d.ts +1 -0
  98. package/dist/tests/components/DeleteButton.test.js +45 -0
  99. package/dist/tests/components/DttNameInput.test.d.ts +1 -0
  100. package/dist/tests/components/DttNameInput.test.js +75 -0
  101. package/dist/tests/components/NtupleWizard.test.d.ts +1 -0
  102. package/dist/tests/components/NtupleWizard.test.js +57 -0
  103. package/dist/tests/components/ParticleDropdown.test.d.ts +1 -0
  104. package/dist/tests/components/ParticleDropdown.test.js +23 -0
  105. package/dist/tests/components/ParticleTagFilters.test.d.ts +1 -0
  106. package/dist/tests/components/ParticleTagFilters.test.js +87 -0
  107. package/dist/tests/components/RequestButtonGroup.test.d.ts +1 -0
  108. package/dist/tests/components/RequestButtonGroup.test.js +132 -0
  109. package/dist/tests/components/RequestRow.test.d.ts +1 -0
  110. package/dist/tests/components/RequestRow.test.js +58 -0
  111. package/dist/tests/components/StrippingLineDropdown.test.d.ts +1 -0
  112. package/dist/tests/components/StrippingLineDropdown.test.js +88 -0
  113. package/dist/tests/components/badges.test.d.ts +1 -0
  114. package/dist/tests/components/badges.test.js +120 -0
  115. package/dist/tests/components/dropdowns.test.d.ts +1 -0
  116. package/dist/tests/components/dropdowns.test.js +110 -0
  117. package/dist/tests/components/dttComponents.test.d.ts +1 -0
  118. package/dist/tests/components/dttComponents.test.js +287 -0
  119. package/dist/tests/components/formInputs.test.d.ts +1 -0
  120. package/dist/tests/components/formInputs.test.js +96 -0
  121. package/dist/tests/components/metadataComponents.test.d.ts +1 -0
  122. package/dist/tests/components/metadataComponents.test.js +137 -0
  123. package/dist/tests/components/miscComponents.test.d.ts +1 -0
  124. package/dist/tests/components/miscComponents.test.js +134 -0
  125. package/dist/tests/components/modals.test.d.ts +1 -0
  126. package/dist/tests/components/modals.test.js +554 -0
  127. package/dist/tests/components/tupleToolParams.test.d.ts +1 -0
  128. package/dist/tests/components/tupleToolParams.test.js +213 -0
  129. package/dist/tests/config.test.d.ts +1 -0
  130. package/dist/tests/config.test.js +31 -0
  131. package/dist/tests/mockSetup.d.ts +1 -0
  132. package/dist/tests/mockSetup.js +30 -0
  133. package/dist/tests/models/BkPath.test.d.ts +1 -0
  134. package/dist/tests/models/BkPath.test.js +87 -0
  135. package/dist/tests/models/Dtt.test.d.ts +1 -0
  136. package/dist/tests/models/Dtt.test.js +376 -0
  137. package/dist/tests/models/TupleTool.test.d.ts +1 -0
  138. package/dist/tests/models/TupleTool.test.js +80 -0
  139. package/dist/tests/models/YamlFile.test.d.ts +1 -0
  140. package/dist/tests/models/YamlFile.test.js +123 -0
  141. package/dist/tests/pages/DecaySearchPage.test.d.ts +1 -0
  142. package/dist/tests/pages/DecaySearchPage.test.js +228 -0
  143. package/dist/tests/pages/DecayTreeConfigPage.test.d.ts +1 -0
  144. package/dist/tests/pages/DecayTreeConfigPage.test.js +105 -0
  145. package/dist/tests/pages/RequestPage.test.d.ts +1 -0
  146. package/dist/tests/pages/RequestPage.test.js +439 -0
  147. package/dist/tests/providers/DttProvider.test.d.ts +1 -0
  148. package/dist/tests/providers/DttProvider.test.js +105 -0
  149. package/dist/tests/providers/MetadataProvider.test.d.ts +1 -0
  150. package/dist/tests/providers/MetadataProvider.test.js +129 -0
  151. package/dist/tests/providers/RequestProvider.test.d.ts +1 -0
  152. package/dist/tests/providers/RequestProvider.test.js +306 -0
  153. package/dist/tests/providers/RowProvider.test.d.ts +1 -0
  154. package/dist/tests/providers/RowProvider.test.js +110 -0
  155. package/dist/tests/providers/RowsProvider.test.d.ts +1 -0
  156. package/dist/tests/providers/RowsProvider.test.js +84 -0
  157. package/dist/tests/providers/WizardConfigProvider.test.d.ts +1 -0
  158. package/dist/tests/providers/WizardConfigProvider.test.js +36 -0
  159. package/dist/tests/setupTests.d.ts +1 -0
  160. package/dist/tests/setupTests.js +15 -0
  161. package/dist/tests/testUtils.d.ts +33 -0
  162. package/dist/tests/testUtils.js +196 -0
  163. package/dist/tests/utils/latexUtils.test.d.ts +1 -0
  164. package/dist/tests/utils/latexUtils.test.js +62 -0
  165. package/dist/tests/utils/utils.test.d.ts +1 -0
  166. package/dist/tests/utils/utils.test.js +394 -0
  167. package/dist/utils/latexUtils.d.ts +13 -0
  168. package/dist/utils/latexUtils.js +86 -0
  169. package/dist/utils/utils.d.ts +1 -0
  170. package/dist/utils/utils.js +4 -1
  171. package/package.json +16 -7
  172. package/dist/components/NumStrippingLinesBadge.d.ts +0 -8
  173. package/dist/components/NumStrippingLinesBadge.js +0 -10
  174. package/dist/components/StrippingLineBadge.d.ts +0 -7
  175. package/dist/components/TupleToolGroup.d.ts +0 -5
  176. package/dist/components/TupleToolGroup.js +0 -22
  177. package/dist/components/TupleToolList.d.ts +0 -6
  178. package/dist/components/TupleToolList.js +0 -42
@@ -0,0 +1 @@
1
+ export declare function AddTupleToolButton(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { PlusLg } from "react-bootstrap-icons";
3
+ import { Button } from "react-bootstrap";
4
+ import { AddTupleToolModal } from "./modals/AddTupleToolModal";
5
+ import { useState } from "react";
6
+ import { ConfigureTupleToolModal } from "./modals/ConfigureTupleToolModal";
7
+ export function AddTupleToolButton() {
8
+ const [showAddModal, setShowAddModal] = useState(false);
9
+ const [showConfigModal, setShowConfigModal] = useState(null);
10
+ return (_jsxs(_Fragment, { children: [showAddModal && (_jsx(AddTupleToolModal, { onClose: (tool) => {
11
+ setShowAddModal(false);
12
+ if (tool?.class === "LoKi__Hybrid__TupleTool") {
13
+ setShowConfigModal({ tool });
14
+ }
15
+ } })), showConfigModal && (_jsx(ConfigureTupleToolModal, { tool: showConfigModal.tool, deleteIfCancelled: true, onClose: () => setShowConfigModal(null) })), _jsxs(Button, { variant: "success", size: "sm", className: "d-flex align-items-center gap-1 mt-1 w-100 justify-content-center", onClick: () => setShowAddModal(true), children: [_jsx(PlusLg, {}), "Add tool"] })] }));
16
+ }
@@ -1 +1 @@
1
- export declare function BookkeepingPathDropdown(): import("react/jsx-runtime").JSX.Element | null;
1
+ export declare function BookkeepingPathDropdown(): import("react/jsx-runtime").JSX.Element;
@@ -9,9 +9,6 @@ export function BookkeepingPathDropdown() {
9
9
  const metadata = useMetadata();
10
10
  const { row, validateBkPath, updateBkPaths } = useRow();
11
11
  const { updateRow } = useRows();
12
- if (!metadata) {
13
- return null;
14
- }
15
12
  const getOptionFromPath = useCallback((path) => ({
16
13
  value: path,
17
14
  label: _jsx(DatasetInfo, { pathString: path }),
@@ -31,5 +28,5 @@ export function BookkeepingPathDropdown() {
31
28
  pathOptions: [...r.pathOptions, path],
32
29
  }));
33
30
  }, [row.id, validateBkPath, updateRow]);
34
- return (_jsx(Creatable, { isMulti: true, options: defaultPaths, value: row.paths.map(getOptionFromPath), isDisabled: !row.lines.length, placeholder: "Bookkeeping path", onChange: (v) => updateBkPaths(v.map((i) => i.value)), onCreateOption: handleCreatePath, closeMenuOnSelect: false }));
31
+ return (_jsx(Creatable, { isMulti: true, options: defaultPaths, value: row.paths.map(getOptionFromPath), isDisabled: !row.line, placeholder: "Choose dataset", onChange: (v) => updateBkPaths(v.map((i) => i.value)), onCreateOption: handleCreatePath, closeMenuOnSelect: false }));
35
32
  }
@@ -11,7 +11,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
11
11
  \*****************************************************************************/
12
12
  import { Badge } from "react-bootstrap";
13
13
  export function DatasetEventTypeBadge({ eventType }) {
14
- const content = "" + eventType === "90000000" ? "Data" : "" + eventType;
14
+ const content = eventType === "90000000" ? "Data" : eventType;
15
15
  const linkProps = content === "Data"
16
16
  ? {}
17
17
  : {
@@ -12,16 +12,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  import { Stack } from "react-bootstrap";
13
13
  import { YearBadge } from "./YearBadge.js";
14
14
  import { PolarityBadge } from "./PolarityBadge.js";
15
- import { StrippingLineBadge } from "./StrippingLineBadge";
15
+ import { StrippingLineVersionBadge } from "./StrippingLineVersionBadge";
16
16
  import { DatasetEventTypeBadge } from "./DatasetEventTypeBadge";
17
17
  import { BkPath } from "../models/bkPath.js";
18
18
  export function DatasetInfo({ pathString }) {
19
19
  try {
20
20
  const path = new BkPath(pathString);
21
- return (_jsxs("span", { children: [path.fileName, _jsx("br", {}), _jsxs(Stack, { direction: "horizontal", style: { flexWrap: "wrap" }, gap: 1, children: [_jsx(DatasetEventTypeBadge, { eventType: path.eventType }), _jsx(YearBadge, { year: path.year }), _jsx(PolarityBadge, { polarity: path.polarity || "?" }), _jsx(StrippingLineBadge, { version: path.strippingVersion })] })] }));
21
+ return (_jsxs("span", { children: [path.fileName, _jsx("br", {}), _jsxs(Stack, { direction: "horizontal", style: { flexWrap: "wrap" }, gap: 1, children: [_jsx(DatasetEventTypeBadge, { eventType: path.eventType }), _jsx(YearBadge, { year: path.year }), _jsx(PolarityBadge, { polarity: path.polarity || "?" }), _jsx(StrippingLineVersionBadge, { version: path.strippingVersion })] })] }));
22
22
  }
23
23
  catch (e) {
24
- console.error("Error parsing bookkeeping path:", e);
24
+ console.error("Error parsing dataset:", e);
25
25
  return null;
26
26
  }
27
27
  }
@@ -1 +1 @@
1
- export declare function DecayCard(): import("react/jsx-runtime").JSX.Element | null;
1
+ export declare function DecayCard(): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Button, ButtonGroup, Card, FormControl, InputGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, ButtonGroup, Card, OverlayTrigger, Tooltip } from "react-bootstrap";
3
3
  import { useState } from "react";
4
4
  import { Download, ExclamationCircle, GearWideConnected, PlusLg, Upload } from "react-bootstrap-icons";
5
5
  import { DeleteButton } from "./DeleteButton";
@@ -14,40 +14,25 @@ import { UploadDttConfigModal } from "./modals/UploadDttConfigModal";
14
14
  import { VARIABLES_PATH } from "../constants";
15
15
  import { useRow } from "../providers/RowProvider";
16
16
  import { useWizardConfig } from "../providers/WizardConfigProvider";
17
+ import { DttNameInput } from "./DttNameInput";
18
+ import { GuideLinkButton } from "./GuideLinkButton";
17
19
  export function DecayCard() {
18
20
  const { row } = useRow();
19
- const { rows, setRows, updateRow } = useRows();
21
+ const { setRows, updateRow } = useRows();
20
22
  const navigate = useNavigate();
21
23
  const metadata = useMetadata();
22
24
  const { basePath, variant } = useWizardConfig();
23
25
  const [showUploadModal, setShowUploadModal] = useState(false);
24
- const [dttName, setDttName] = useState(row.dtt?.getName() || "");
25
26
  const [dttNameValid, setDttNameValid] = useState(true);
26
- const [autoFocus, setAutoFocus] = useState(false);
27
- if (!metadata) {
28
- return null;
29
- }
30
- function allNameCharsValid(name) {
31
- const pattern = /^[a-zA-Z0-9_:-]+$/;
32
- return pattern.test(name);
33
- }
34
- function validateName() {
35
- const trimmedName = dttName.trim();
36
- const isNameEmpty = trimmedName === "";
37
- const areCharsValid = allNameCharsValid(trimmedName);
38
- const otherRowNames = rows.filter((r) => r.id !== row.id).map((row) => row.dtt?.getName() || "");
39
- const isNameDuplicate = otherRowNames.includes(trimmedName);
40
- const valid = !isNameEmpty && areCharsValid && !isNameDuplicate;
41
- setDttNameValid(valid);
42
- return valid;
43
- }
27
+ const [autoFocusRow, setAutoFocusRow] = useState(null);
44
28
  const handleCreateDTT = () => {
45
- const dtt = Dtt.create(row.decay.descriptors.template, row.decay.descriptors.mapped_list.flatMap((item) => (Array.isArray(item) ? item : [item])), [], "", metadata.tupleTools.tupleTools);
29
+ const dtt = Dtt.create(row.decay.descriptors.template, row.decay.descriptors.mapped_list.flatMap((item) => (Array.isArray(item) ? item : [item])), [], `MyDecayTree${row.id}`, metadata.tupleTools.tupleTools);
30
+ // Only focus the name input if the DTT was just created
31
+ setAutoFocusRow(row.id);
46
32
  updateRow(row.id, (r) => ({
47
33
  ...r,
48
- dtt: dtt.withInputsFromLines(row.lines),
34
+ dtt: row.line ? dtt.withInputFromLine(row.line) : dtt,
49
35
  }));
50
- setAutoFocus(true);
51
36
  };
52
37
  const handleConfigureDtt = () => {
53
38
  setRows((rows) => rows.map((r) => (r.id === row.id ? { ...r, editTree: true } : { ...r, editTree: false })));
@@ -55,21 +40,8 @@ export function DecayCard() {
55
40
  };
56
41
  const handleDeleteDtt = () => {
57
42
  updateRow(row.id, (r) => ({ ...r, dtt: null }));
58
- setDttName("");
59
43
  };
60
- return (_jsxs(Card, { children: [_jsxs(Card.Header, { className: "d-flex justify-content-between align-items-start gap-2", children: [row.dtt && (_jsxs(InputGroup, { hasValidation: true, children: [_jsx(FormControl, { autoFocus: autoFocus, placeholder: "DecayTree", value: dttName, onBlur: () => {
61
- if (!validateName()) {
62
- return;
63
- }
64
- updateRow(row.id, (r) => ({ ...r, dtt: r.dtt.withName(dttName) }));
65
- }, onChange: (event) => {
66
- setDttName(event.target.value);
67
- setDttNameValid(true);
68
- }, isInvalid: !dttNameValid }), _jsx(FormControl.Feedback, { type: "invalid", children: dttName.trim() === ""
69
- ? "Name cannot be empty"
70
- : !allNameCharsValid(dttName.trim())
71
- ? "Name contains invalid characters"
72
- : "Name is already taken" })] })), _jsx(ButtonGroup, { children: row.dtt ? (_jsxs(_Fragment, { children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Configure this DecayTreeTuple" }), children: _jsx(Button, { type: "submit", variant: "secondary", onClick: handleConfigureDtt, disabled: !dttNameValid, className: "align-items-center d-flex", children: _jsx(GearWideConnected, {}) }) }), variant === "standalone" && (_jsx(_Fragment, { children: _jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Download DecayTreeTuple YAML configuration file" }), children: _jsx(Button, { className: "align-items-center d-flex ms-auto", type: "button", onClick: () => {
44
+ return (_jsxs(Card, { children: [_jsxs(Card.Header, { className: "d-flex justify-content-between align-items-start gap-2", children: [row.dtt && (_jsx(DttNameInput, { dtt: row.dtt, rowId: row.id, autoFocus: autoFocusRow === row.id, onNameValidated: (valid) => setDttNameValid(valid) })), _jsx(ButtonGroup, { children: row.dtt ? (_jsxs(_Fragment, { children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Configure this DecayTreeTuple" }), children: _jsx(Button, { type: "submit", variant: "secondary", onClick: handleConfigureDtt, disabled: !dttNameValid, className: "align-items-center d-flex", children: _jsx(GearWideConnected, {}) }) }), variant === "standalone" && (_jsx(_Fragment, { children: _jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Download DecayTreeTuple YAML configuration file" }), children: _jsx(Button, { className: "align-items-center d-flex ms-auto", type: "button", onClick: () => {
73
45
  downloadText(YamlFile.fromDtt(row.dtt));
74
- }, disabled: !dttNameValid, children: _jsx(Download, {}) }) }) })), _jsx(DeleteButton, { action: handleDeleteDtt, popupMessage: "Delete DecayTreeTuple" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "d-flex align-items-center gap-2", children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Please add a DecayTreeTuple in order to complete the production configuration" }), children: _jsx(ExclamationCircle, { width: 20, height: 20, className: "text-danger me-2" }) }), _jsxs(ButtonGroup, { children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Add a DecayTreeTuple" }), children: _jsx(Button, { type: "submit", variant: "success", onClick: handleCreateDTT, disabled: !metadata, children: _jsx(PlusLg, {}) }) }), variant === "standalone" && (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Upload DecayTreeTuple configuration file" }), children: _jsx(Button, { className: "ms-auto", type: "button", onClick: () => setShowUploadModal(true), children: _jsx(Upload, {}) }) }))] })] }), showUploadModal && (_jsx(UploadDttConfigModal, { currentRow: row, onClose: () => setShowUploadModal(false) }))] })) })] }), _jsx(Card.Body, { style: { overflowX: "auto" }, children: _jsx(DecayLatex, { decay: row.decay }) })] }));
46
+ }, disabled: !dttNameValid, children: _jsx(Download, {}) }) }) })), _jsx(DeleteButton, { action: handleDeleteDtt, popupMessage: "Delete DecayTreeTuple" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "d-flex align-items-center gap-2", children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Please add a DecayTreeTuple in order to complete the production configuration" }), children: _jsx(ExclamationCircle, { width: 20, height: 20, className: "text-danger me-2" }) }), _jsx(GuideLinkButton, { page: "ntupleCreation", topic: "Creating an ntuple" }), _jsxs(ButtonGroup, { className: "mx-2", children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Add a DecayTreeTuple" }), children: _jsx(Button, { type: "submit", variant: "success", onClick: handleCreateDTT, children: _jsx(PlusLg, {}) }) }), variant === "standalone" && (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Upload DecayTreeTuple configuration file" }), children: _jsx(Button, { className: "ms-auto", type: "button", onClick: () => setShowUploadModal(true), children: _jsx(Upload, {}) }) }))] })] }), showUploadModal && (_jsx(UploadDttConfigModal, { currentRow: row, onClose: () => setShowUploadModal(false) }))] })) })] }), _jsx(Card.Body, { style: { overflowX: "auto" }, children: _jsx(DecayLatex, { decay: row.decay }) })] }));
75
47
  }
@@ -3,5 +3,5 @@ interface Props {
3
3
  decay: DecayData;
4
4
  selection?: string[];
5
5
  }
6
- export declare function DecayLatex({ decay, selection }: Props): import("react/jsx-runtime").JSX.Element | null;
6
+ export declare function DecayLatex({ decay, selection }: Props): import("react/jsx-runtime").JSX.Element;
7
7
  export {};
@@ -9,95 +9,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  * granted to it by virtue of its status as an Intergovernmental Organization *
10
10
  * or submit itself to any jurisdiction. *
11
11
  \*****************************************************************************/
12
- import { MathJax } from "better-react-mathjax";
13
12
  import { OverlayTrigger, Popover } from "react-bootstrap";
14
13
  import { useMetadata } from "../providers/MetadataProvider";
14
+ import { selectionDescriptorToLatex } from "../utils/latexUtils";
15
+ import { InlineMath } from "react-katex";
15
16
  export function DecayLatex({ decay, selection }) {
16
17
  const metadata = useMetadata();
17
- if (!metadata) {
18
- return null;
19
- }
20
- const LOKI_MARKERS = {
21
- SELECTION_PREFIX: "^",
22
- ARROW: "->",
23
- };
24
- const LATEX_COMMANDS = {
25
- ARROW: "\\to",
26
- TEXT: (content) => `\\text{${content}}`,
27
- COLOR: (color, content) => `\\textcolor{${color}}{${content}}`,
28
- };
29
- /**
30
- * Removes leading and trailing braces from LoKi selector string
31
- */
32
- const cleanLoKiSelector = (selector) => {
33
- return selector.replace(/^\[/, "").replace(/^\^\[/, "^").replace(/\]CC$/, "");
34
- };
35
- /**
36
- * Counts occurrences of a pattern in a string
37
- */
38
- const countMatches = (text, pattern) => {
39
- return (text.match(pattern) || []).length;
40
- };
41
- /**
42
- * Extracts and processes braces from particle names for sub-decay handling
43
- */
44
- const extractBraces = (name) => {
45
- const openCount = countMatches(name, /\(/g);
46
- const closeCount = countMatches(name, /\)/g);
47
- if (name.startsWith("(")) {
48
- return {
49
- cleanName: name.replace(/^\(/, ""),
50
- openBrace: "(",
51
- closeBrace: "",
52
- };
53
- }
54
- if (name.endsWith(")") && closeCount > openCount) {
55
- const extraClosingBraces = closeCount - openCount;
56
- return {
57
- cleanName: name.replace(/\)+$/, ""),
58
- openBrace: "",
59
- closeBrace: ")".repeat(extraClosingBraces),
60
- };
61
- }
62
- return { cleanName: name, openBrace: "", closeBrace: "" };
63
- };
64
- /**
65
- * Converts a LoKi decay selector string to LaTeX representation
66
- */
67
- const displaySelectionDescriptor = (selection) => {
68
- // Generate LoKi selector by marking selected particles with '^'
69
- const decaySelector = decay.descriptors.template.replace(/\${(\w+)}/g, (_, variableName) => {
70
- return selection.includes(variableName) ? LOKI_MARKERS.SELECTION_PREFIX : "";
71
- });
72
- const cleanedSelector = cleanLoKiSelector(decaySelector);
73
- const particles = cleanedSelector.split(" ");
74
- const latexTokens = particles.map((particle) => convertParticleToLatex(particle));
75
- return latexTokens.join(" ");
76
- };
77
- /**
78
- * Converts a single particle token to LaTeX representation
79
- */
80
- const convertParticleToLatex = (particle) => {
81
- // Handle arrow tokens
82
- if (particle === LOKI_MARKERS.ARROW) {
83
- return LATEX_COMMANDS.ARROW;
84
- }
85
- const isMarked = particle.startsWith(LOKI_MARKERS.SELECTION_PREFIX);
86
- const particleName = particle.replace(/^\^/, "");
87
- // Handle sub-decay braces
88
- const { cleanName, openBrace, closeBrace } = extractBraces(particleName);
89
- // Validate particle exists in metadata
90
- if (!metadata.particleProperties[cleanName]) {
91
- console.error(`${cleanName} not found in particle property metadata!`);
92
- return LATEX_COMMANDS.TEXT(particle);
93
- }
94
- // Get base LaTeX representation
95
- let latex = metadata.particleProperties[cleanName].latex;
96
- // Underline marked particles
97
- if (isMarked) {
98
- latex = LATEX_COMMANDS.COLOR("#4169E1", latex);
99
- }
100
- return `${openBrace}${latex}${closeBrace}`;
101
- };
102
- return (_jsx(OverlayTrigger, { overlay: _jsxs(Popover, { children: [_jsx(Popover.Header, { children: "LoKi descriptor" }), _jsx(Popover.Body, { children: _jsx("code", { children: decay.descriptors.plain }) })] }), children: _jsx("span", { children: _jsx(MathJax, { inline: true, children: `\\(${selection && selection.length > 0 ? displaySelectionDescriptor(selection) : decay.descriptors.latex}\\)` }) }) }));
18
+ const math = selection?.length ? selectionDescriptorToLatex(metadata, decay, selection) : decay.descriptors.latex;
19
+ return (_jsx(OverlayTrigger, { overlay: _jsxs(Popover, { children: [_jsx(Popover.Header, { children: "LoKi descriptor" }), _jsx(Popover.Body, { children: _jsx("code", { children: decay.descriptors.plain }) })] }), children: _jsx("span", { children: _jsx(InlineMath, { math: math }) }) }));
103
20
  }
@@ -13,11 +13,11 @@ import { useState } from "react";
13
13
  import { InputGroup, ListGroup } from "react-bootstrap";
14
14
  import { DecayLatex } from "./DecayLatex";
15
15
  import { DecayTagBadge } from "./DecayTagBadge.js";
16
- import { NumStrippingLinesBadge } from "./NumStrippingLinesBadge.js";
16
+ import { StrippingLinesCountBadge } from "./StrippingLinesCountBadge";
17
17
  export function DecayListItem({ decay, initiallySelected = false, onItemSelected }) {
18
18
  const [selected, setSelected] = useState(initiallySelected);
19
19
  return (_jsx(ListGroup.Item, { as: "li", style: { cursor: "pointer" }, onClick: () => {
20
20
  setSelected(!selected);
21
21
  onItemSelected({ selected, decay: decay });
22
- }, children: _jsxs(InputGroup, { children: [_jsx(InputGroup.Checkbox, { checked: selected, readOnly: true }), _jsxs("div", { className: `react-select form-control p-2 d-flex flex-column align-items-start gap-1 ${selected ? "list-group-item-primary" : "list-group-item-light"}`, children: [_jsx(DecayLatex, { decay: decay }), _jsxs("div", { className: "d-flex flex-row flex-wrap gap-1", children: [_jsx(NumStrippingLinesBadge, { strippingLines: decay.lines, selected: selected }), decay.tags.map((tag) => (_jsx(DecayTagBadge, { tag: tag }, tag)))] })] })] }) }));
22
+ }, children: _jsxs(InputGroup, { children: [_jsx(InputGroup.Checkbox, { checked: selected, readOnly: true }), _jsxs("div", { className: `react-select form-control p-2 d-flex flex-column align-items-start gap-1 ${selected ? "list-group-item-primary" : "list-group-item-light"}`, children: [_jsx(DecayLatex, { decay: decay }), _jsxs("div", { className: "d-flex flex-row flex-wrap gap-1", children: [_jsx(StrippingLinesCountBadge, { strippingLineVersions: decay.lines, selected: selected }), decay.tags.map((tag) => (_jsx(DecayTagBadge, { tag: tag }, tag)))] })] })] }) }));
23
23
  }
@@ -1,5 +1,5 @@
1
1
  interface Props {
2
2
  tag: string;
3
3
  }
4
- export declare function DecayTagBadge({ tag, ...props }: Props): import("react/jsx-runtime").JSX.Element | null;
4
+ export declare function DecayTagBadge({ tag, ...props }: Props): import("react/jsx-runtime").JSX.Element;
5
5
  export {};
@@ -13,9 +13,6 @@ import { Badge, OverlayTrigger, Tooltip } from "react-bootstrap";
13
13
  import { useMetadata } from "../providers/MetadataProvider.js";
14
14
  export function DecayTagBadge({ tag, ...props }) {
15
15
  const metadata = useMetadata();
16
- if (!metadata) {
17
- return null;
18
- }
19
16
  const tagInfo = metadata.userHints.decayTags[tag];
20
17
  return (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: tagInfo.description }), children: _jsx(Badge, { pill: true, bg: tagInfo.warn ? "danger" : "warning", text: tagInfo.warn ? "light" : "dark", ...props, children: tag }) }));
21
18
  }
@@ -1,7 +1 @@
1
- import { DttConfig } from "../models/dtt.js";
2
- interface Props {
3
- onConfigSaved: (config: DttConfig) => void;
4
- onDirtyUpdated: (dttId: string, dirty: boolean) => void;
5
- }
6
- export declare function DecayTreeCard({ onConfigSaved, onDirtyUpdated }: Props): import("react/jsx-runtime").JSX.Element | null;
7
- export {};
1
+ export declare function DecayTreeCard(): import("react/jsx-runtime").JSX.Element;
@@ -11,30 +11,25 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  *****************************************************************************/
12
12
  import { DecayTagBadge } from "./DecayTagBadge.js";
13
13
  import { DecayLatex } from "./DecayLatex";
14
- import { TupleToolList } from "./TupleToolList";
15
14
  import { useMetadata } from "../providers/MetadataProvider.js";
16
- import { Card, OverlayTrigger, Stack, Tooltip } from "react-bootstrap";
15
+ import { Accordion, Button, Card, OverlayTrigger, Stack, Tooltip } from "react-bootstrap";
17
16
  import { QuestionCircle } from "react-bootstrap-icons";
18
- import { useEffect } from "react";
19
17
  import { DecayTreeGraph } from "./DecayTreeGraph";
20
18
  import { DecayTreeCardHeader } from "./DecayTreeCardHeader";
21
19
  import { ParticleTagFilters } from "./ParticleTagFilters";
22
20
  import { useDtt } from "../providers/DttProvider";
23
- export function DecayTreeCard({ onConfigSaved, onDirtyUpdated }) {
21
+ import { getControlKey } from "../utils/utils";
22
+ import { TupleToolsAccordion } from "./TupleToolsAccordion";
23
+ import { TupleToolGroupsAccordion } from "./TupleToolGroupsAccordion";
24
+ import { useState } from "react";
25
+ import { GuideLinkButton } from "./GuideLinkButton";
26
+ export function DecayTreeCard() {
24
27
  const metadata = useMetadata();
25
- const { dtt, decay, dirty, selectedBranch, setSelectedBranch } = useDtt();
26
- useEffect(() => {
27
- onDirtyUpdated(dtt.getName(), dirty);
28
- }, [dtt, dirty]);
29
- if (!metadata) {
30
- return null;
31
- }
32
- return (_jsxs(Card, { children: [_jsx(DecayTreeCardHeader, { dttName: dtt.getName(), inputs: dtt.config.inputs }), _jsxs(Card.Body, { children: [_jsx(Card.Title, { className: "d-flex justify-content-between align-items-start", children: _jsxs("span", { children: [_jsx(OverlayTrigger, { placement: "bottom", overlay: _jsx(Tooltip, { children: "Click to select particles to configure. Hold ctrl to select multiple. Clear the selection to configure the entire decay." }), children: _jsx(QuestionCircle, {}) }), " ", "Configure ", _jsx(DecayLatex, { decay: decay })] }) }), _jsx(Stack, { direction: "horizontal", style: { flexWrap: "wrap", justifyContent: "center" }, gap: 2, children: decay.tags
28
+ const { dtt, decay, selectedBranch, setSelectedBranch, hoveredBranch } = useDtt();
29
+ const [activeKey, setActiveKey] = useState("0");
30
+ return (_jsxs(Card, { children: [_jsx(DecayTreeCardHeader, { dttName: dtt.getName(), input: dtt.config.input }), _jsxs(Card.Body, { children: [_jsxs(Card.Title, { className: "d-flex justify-content-between align-items-center", children: [_jsx(DecayLatex, { decay: decay, selection: selectedBranch.length === 0 ? hoveredBranch : selectedBranch }), _jsx(OverlayTrigger, { placement: "left", overlay: _jsxs(Tooltip, { children: ["Click on particles to configure. Hold ", _jsx("code", { children: getControlKey() }), " to select multiple. Clear the selection to configure the entire decay. Use", " ", _jsxs("code", { children: [getControlKey(), "+scroll"] }), " to zoom."] }), children: _jsx(QuestionCircle, {}) })] }), _jsx(Stack, { direction: "horizontal", style: { flexWrap: "wrap", justifyContent: "center" }, gap: 2, children: decay.tags
33
31
  .filter((tag) => metadata.userHints.decayTags[tag].warn)
34
- .map((tag) => (_jsx(DecayTagBadge, { tag: tag }, tag))) }), _jsx(DecayTreeGraph, { decay: decay, selectedParticlesIds: selectedBranch, onNodeSelectionChanged: (nodeIds) => {
35
- setSelectedBranch([...nodeIds].sort());
36
- } }), _jsx(ParticleTagFilters, { branch: selectedBranch, onSelectTag: setSelectedBranch }), _jsxs(Card.Title, { className: "mt-4", children: ["Current selection: ", _jsx(DecayLatex, { decay: decay, selection: selectedBranch })] }), _jsx(TupleToolList, { onGroupSelected: (group) => {
37
- const selection = group.split(",");
38
- setSelectedBranch(selection);
39
- }, onSave: () => onConfigSaved(dtt.config) })] })] }));
32
+ .map((tag) => (_jsx(DecayTagBadge, { tag: tag }, tag))) }), _jsxs("div", { className: "d-flex flex-row", children: [_jsxs("div", { style: { width: "100%" }, children: [_jsx(DecayTreeGraph, { onNodeSelectionChanged: (nodeIds) => {
33
+ setSelectedBranch([...nodeIds].sort());
34
+ } }), _jsx("div", { style: { paddingRight: 12 }, children: _jsx(ParticleTagFilters, {}) })] }), _jsxs("div", { className: "d-flex flex-column gap-2 mt-2", style: { minWidth: "350px" }, children: [_jsxs("div", { className: "d-flex justify-content-between align-items-center", children: [_jsxs("div", { className: "d-flex align-items-center gap-2", children: [selectedBranch.length, " particle", selectedBranch.length === 1 ? "" : "s", " selected", _jsx(GuideLinkButton, { page: "nodeTree", topic: "Configuring the node tree" })] }), _jsx(Button, { disabled: selectedBranch.length === 0, variant: "secondary", size: "sm", onClick: () => setSelectedBranch([]), children: "Deselect" })] }), _jsxs(Accordion, { defaultActiveKey: ["0"], activeKey: activeKey, onSelect: (key) => setActiveKey(key), children: [_jsx(TupleToolsAccordion, {}), _jsx(TupleToolGroupsAccordion, { setActiveKey: setActiveKey })] })] })] })] })] }));
40
35
  }
@@ -1,7 +1,6 @@
1
- import { DttConfig } from "../models/dtt.js";
2
1
  interface Props {
3
2
  dttName: string;
4
- inputs: DttConfig["inputs"];
3
+ input?: string;
5
4
  }
6
- export declare function DecayTreeCardHeader({ dttName, inputs }: Props): import("react/jsx-runtime").JSX.Element;
5
+ export declare function DecayTreeCardHeader({ dttName, input }: Props): import("react/jsx-runtime").JSX.Element;
7
6
  export {};
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Accordion, Card, ListGroup, OverlayTrigger, Popover } from "react-bootstrap";
3
- export function DecayTreeCardHeader({ dttName, inputs }) {
4
- return (_jsxs(Card.Header, { className: "d-flex flex-column align-items-start", children: [_jsx(OverlayTrigger, { placement: "bottom", overlay: _jsx(Popover, { children: _jsxs(Popover.Body, { children: ["The ROOT TTree will be found under ", _jsx("code", { children: `DecayTreeTuple/${dttName}` }), "."] }) }), children: _jsxs(Card.Title, { children: ["Tree name: ", _jsx("code", { children: dttName })] }) }), _jsx(Accordion, { style: { width: "100%" }, children: _jsxs(Accordion.Item, { eventKey: "0", children: [_jsxs(Accordion.Header, { children: [(inputs || []).length, " input location", (inputs || []).length === 1 ? "" : "s"] }), _jsx(Accordion.Body, { children: _jsx(ListGroup, { variant: "flush", children: !inputs || inputs.length === 0 ? (_jsx(ListGroup.Item, { children: "Choose at least one Stripping line, otherwise no decay candidates will be selected" })) : (inputs.map((location, i) => (_jsx(ListGroup.Item, { children: _jsx("code", { children: location }) }, i)))) }) })] }) })] }));
3
+ export function DecayTreeCardHeader({ dttName, input }) {
4
+ return (_jsxs(Card.Header, { className: "d-flex flex-column align-items-start", children: [_jsx(OverlayTrigger, { placement: "bottom", overlay: _jsx(Popover, { children: _jsxs(Popover.Body, { children: ["The ROOT TTree will be found under ", _jsx("code", { children: `DecayTreeTuple/${dttName}` }), "."] }) }), children: _jsxs(Card.Title, { children: ["Tree name: ", _jsx("code", { children: dttName })] }) }), _jsx(Accordion, { style: { width: "100%" }, children: _jsxs(Accordion.Item, { eventKey: "0", children: [_jsx(Accordion.Header, { children: input ? "1 input location" : "No input location" }), _jsx(Accordion.Body, { children: _jsx(ListGroup, { variant: "flush", children: _jsx(ListGroup.Item, { children: input ? (_jsx("code", { children: input })) : ("Choose at least one Stripping line, otherwise no decay candidates will be selected") }) }) })] }) })] }));
5
5
  }
@@ -1,8 +1,5 @@
1
- import { DecayData } from "../models/decayData";
2
1
  interface Props {
3
- decay: DecayData;
4
- selectedParticlesIds: string[];
5
2
  onNodeSelectionChanged: (nodeIds: string[]) => void;
6
3
  }
7
- export declare function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionChanged }: Props): import("react/jsx-runtime").JSX.Element;
4
+ export declare function DecayTreeGraph({ onNodeSelectionChanged }: Props): import("react/jsx-runtime").JSX.Element;
8
5
  export {};
@@ -6,9 +6,12 @@ import { config } from "../config";
6
6
  import { tex2svg } from "../utils/mathjaxUtils";
7
7
  import { useMetadata } from "../providers/MetadataProvider";
8
8
  import { useEffect, useRef } from "react";
9
+ import { useDtt } from "../providers/DttProvider";
10
+ import { LATEX_SELECTED_COLOR } from "../utils/latexUtils";
9
11
  cytoscape.use(dagre);
10
- export function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionChanged }) {
12
+ export function DecayTreeGraph({ onNodeSelectionChanged }) {
11
13
  const metadata = useMetadata();
14
+ const { dtt, decay, selectedBranch, highlightedBranch } = useDtt();
12
15
  const cyRef = useRef(null);
13
16
  // Sync external selection state to cytoscape selection
14
17
  useEffect(() => {
@@ -16,42 +19,39 @@ export function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionCha
16
19
  if (!cy)
17
20
  return;
18
21
  cy.nodes().forEach((node) => {
19
- if (selectedParticlesIds.includes(node.id())) {
22
+ if (selectedBranch.includes(node.id())) {
20
23
  node.select();
21
24
  }
22
25
  else {
23
26
  node.unselect();
24
27
  }
25
28
  });
26
- }, [selectedParticlesIds]);
27
- function makeSVG(text, colour = "black") {
29
+ }, [selectedBranch]);
30
+ function makeSVG(text, color = "black") {
28
31
  const pipes = "\\phantom{{}^{|-}_{|-}}";
29
- const texSVG = tex2svg(`${pipes}${text}${pipes}`).innerHTML.replaceAll("currentColor", colour);
32
+ const texSVG = tex2svg(`${pipes}${text}${pipes}`).innerHTML.replaceAll("currentColor", color);
30
33
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(texSVG)}`;
31
34
  }
32
- function getHtmlTitle(text) {
33
- const container = document.createElement("span");
34
- container.innerText = text;
35
- return container;
36
- }
37
35
  function buildElements() {
38
36
  const elements = [];
39
- if (!metadata)
40
- return elements;
41
37
  const addNodeAndEdge = (item, parentId) => {
42
38
  const { branch, particle } = item;
43
39
  const particleProperties = metadata.particleProperties[particle];
40
+ const branchList = branch.split(",");
41
+ const tupleToolCount = dtt.getBranchTools(branchList).length + dtt.getGroupsThatIncludeBranch(branchList).length;
42
+ // Color the highlightedBranch (which is hoveredBranch when set, otherwise selectedBranch)
43
+ const imageSrc = highlightedBranch.includes(branch)
44
+ ? makeSVG(particleProperties.latex, LATEX_SELECTED_COLOR)
45
+ : makeSVG(particleProperties.latex, "black");
44
46
  elements.push({
45
47
  data: {
46
48
  id: branch,
47
49
  label: branch,
48
- // Store both SVG variants so we can swap on select/unselect
49
- imageSrc: makeSVG(particleProperties.latex, "black"),
50
- imageSelected: makeSVG(particleProperties.latex, "#0d6efd"),
51
- // Tooltip text (accessible via tippy or title attr if needed)
52
- title: getHtmlTitle(particleProperties.html).innerText,
50
+ imageSrc,
51
+ tupleToolCountLabel: `${tupleToolCount}`,
52
+ tupleToolStatusColor: tupleToolCount > 0 ? "#198754" : "#6c757d",
53
53
  },
54
- selected: selectedParticlesIds.includes(branch),
54
+ selected: selectedBranch.includes(branch),
55
55
  });
56
56
  if (parentId !== null) {
57
57
  elements.push({
@@ -85,7 +85,7 @@ export function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionCha
85
85
  return elements;
86
86
  }
87
87
  const stylesheet = [
88
- // Disable the grey circle on pan/click
88
+ // Disable the grey circle that shows when clicking
89
89
  {
90
90
  selector: "core",
91
91
  style: {
@@ -102,7 +102,21 @@ export function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionCha
102
102
  "background-opacity": 0,
103
103
  width: config.dttGraphOptions.nodes.width,
104
104
  height: config.dttGraphOptions.nodes.height,
105
- label: "",
105
+ label: "data(tupleToolCountLabel)",
106
+ "font-size": 9,
107
+ "font-weight": 700,
108
+ color: "#fff",
109
+ "text-background-color": "data(tupleToolStatusColor)",
110
+ "text-background-opacity": 1,
111
+ "text-background-shape": "circle",
112
+ "text-background-padding": "4px",
113
+ "text-border-width": 1,
114
+ "text-border-color": "#fff",
115
+ "text-border-opacity": 1,
116
+ "text-valign": "bottom",
117
+ "text-halign": "center",
118
+ "text-margin-x": 10,
119
+ "text-margin-y": -2,
106
120
  },
107
121
  },
108
122
  {
@@ -118,13 +132,6 @@ export function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionCha
118
132
  "background-color": config.dttGraphOptions.nodes.hoverColor,
119
133
  },
120
134
  },
121
- {
122
- selector: "node:selected",
123
- style: {
124
- // Swap to the selected SVG when the node is selected
125
- "background-image": "data(imageSelected)",
126
- },
127
- },
128
135
  {
129
136
  selector: "edge",
130
137
  style: {
@@ -137,24 +144,42 @@ export function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionCha
137
144
  },
138
145
  },
139
146
  ];
147
+ const updateZoomBounds = (cy) => {
148
+ const bb = cy.elements().boundingBox();
149
+ const container = cy.container();
150
+ if (!container || bb.w === 0 || bb.h === 0) {
151
+ return;
152
+ }
153
+ const zoomX = container.clientWidth / bb.w;
154
+ const zoomY = container.clientHeight / bb.h;
155
+ const fitZoom = Math.min(zoomX, zoomY);
156
+ cy.minZoom(fitZoom * 0.5);
157
+ cy.maxZoom(fitZoom * 2);
158
+ };
140
159
  return (_jsx(CytoscapeComponent, { elements: buildElements(), stylesheet: stylesheet, style: { width: "100%", height: config.dttGraphOptions.height }, autoungrabify: !config.dttGraphOptions.nodes.draggable, autounselectify: false, boxSelectionEnabled: true, cy: (cy) => {
160
+ if (cyRef.current === null) {
161
+ // Handle zoom events
162
+ cy.container()?.addEventListener("wheel", (e) => {
163
+ if (!(e.ctrlKey || e.metaKey))
164
+ return;
165
+ e.preventDefault();
166
+ const zoom = cy.zoom();
167
+ const factor = 1 + (e.deltaY < 0 ? config.dttGraphOptions.zoomStep : -config.dttGraphOptions.zoomStep);
168
+ cy.zoom({
169
+ level: zoom * factor,
170
+ renderedPosition: { x: e.offsetX, y: e.offsetY },
171
+ });
172
+ }, { passive: false });
173
+ }
141
174
  cyRef.current = cy;
142
175
  // Set up zoom
143
176
  cy.userZoomingEnabled(false);
144
- cy.minZoom(config.dttGraphOptions.zoomMin);
145
- cy.maxZoom(config.dttGraphOptions.zoomMax);
146
- // Handle zoom event
147
- cy.container()?.addEventListener("wheel", (e) => {
148
- if (!(e.ctrlKey || e.metaKey))
149
- return;
150
- e.preventDefault();
151
- const zoom = cy.zoom();
152
- const factor = 1 + (e.deltaY < 0 ? config.dttGraphOptions.zoomStep : -config.dttGraphOptions.zoomStep);
153
- cy.zoom({
154
- level: zoom * factor,
155
- renderedPosition: { x: e.offsetX, y: e.offsetY },
156
- });
157
- }, { passive: false });
177
+ cy.ready(() => {
178
+ updateZoomBounds(cy);
179
+ });
180
+ cy.on("layoutstop", () => {
181
+ updateZoomBounds(cy);
182
+ });
158
183
  // Handle node selection events
159
184
  cy.off("select unselect", "node");
160
185
  cy.on("select unselect", "node", () => {
@@ -4,7 +4,8 @@ interface Props {
4
4
  outline?: boolean;
5
5
  disabled?: boolean;
6
6
  popupMessage?: string;
7
+ placement?: "auto" | "top" | "bottom" | "left" | "right";
7
8
  children?: ReactNode;
8
9
  }
9
- export declare function DeleteButton({ action, outline, disabled, popupMessage, children }: Props): import("react/jsx-runtime").JSX.Element | null;
10
+ export declare function DeleteButton({ action, outline, disabled, popupMessage, placement, children, }: Props): import("react/jsx-runtime").JSX.Element | null;
10
11
  export {};
@@ -11,9 +11,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  \*****************************************************************************/
12
12
  import { Button, OverlayTrigger, Popover } from "react-bootstrap";
13
13
  import { Trash } from "react-bootstrap-icons";
14
- export function DeleteButton({ action, outline, disabled, popupMessage = "Confirm", children }) {
14
+ export function DeleteButton({ action, outline, disabled, popupMessage = "Confirm", placement = "auto", children, }) {
15
15
  if (disabled) {
16
16
  return null;
17
17
  }
18
- return (_jsx(OverlayTrigger, { trigger: "click", placement: "auto", rootClose: true, overlay: _jsx(Popover, { children: _jsx(Popover.Body, { children: _jsx(Button, { variant: "danger", onClick: action, children: popupMessage }) }) }), children: _jsxs(Button, { variant: outline ? "outline-danger" : "danger", disabled: disabled, children: [_jsx(Trash, {}), " ", children] }) }));
18
+ return (_jsx(OverlayTrigger, { trigger: "click", placement: placement, rootClose: true, overlay: _jsx(Popover, { children: _jsx(Popover.Body, { children: _jsx(Button, { variant: "danger", onClick: action, children: popupMessage }) }) }), children: _jsxs(Button, { variant: outline ? "outline-danger" : "danger", disabled: disabled, children: [_jsx(Trash, {}), " ", children] }) }));
19
19
  }
@@ -0,0 +1,9 @@
1
+ import Dtt from "../models/dtt";
2
+ interface Props {
3
+ dtt: Dtt;
4
+ rowId: number;
5
+ autoFocus?: boolean;
6
+ onNameValidated?: (valid: boolean) => void;
7
+ }
8
+ export declare function DttNameInput({ dtt, rowId, autoFocus, onNameValidated }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};