lhcb-ntuple-wizard-test 2.0.7 → 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 (184) hide show
  1. package/dist/App.js +2 -1
  2. package/dist/components/AddTupleToolButton.d.ts +1 -0
  3. package/dist/components/AddTupleToolButton.js +16 -0
  4. package/dist/components/BookkeepingPathDropdown.d.ts +1 -1
  5. package/dist/components/BookkeepingPathDropdown.js +1 -4
  6. package/dist/components/DatasetEventTypeBadge.js +1 -1
  7. package/dist/components/DatasetInfo.js +3 -3
  8. package/dist/components/DecayCard.d.ts +1 -0
  9. package/dist/components/DecayCard.js +47 -0
  10. package/dist/components/DecayLatex.d.ts +1 -1
  11. package/dist/components/DecayLatex.js +4 -87
  12. package/dist/components/DecayListItem.js +2 -2
  13. package/dist/components/DecayTagBadge.d.ts +1 -1
  14. package/dist/components/DecayTagBadge.js +0 -3
  15. package/dist/components/DecayTreeCard.d.ts +1 -7
  16. package/dist/components/DecayTreeCard.js +14 -22
  17. package/dist/components/DecayTreeCardHeader.d.ts +2 -3
  18. package/dist/components/DecayTreeCardHeader.js +2 -2
  19. package/dist/components/DecayTreeGraph.d.ts +1 -5
  20. package/dist/components/DecayTreeGraph.js +160 -49
  21. package/dist/components/DeleteButton.d.ts +3 -1
  22. package/dist/components/DeleteButton.js +2 -2
  23. package/dist/components/DttNameInput.d.ts +9 -1
  24. package/dist/components/DttNameInput.js +29 -61
  25. package/dist/components/GuideLinkButton.d.ts +15 -0
  26. package/dist/components/GuideLinkButton.js +16 -0
  27. package/dist/components/NtupleWizard.js +1 -2
  28. package/dist/components/ParticleDropdown.d.ts +11 -1
  29. package/dist/components/ParticleDropdown.js +3 -6
  30. package/dist/components/ParticleLatex.d.ts +6 -0
  31. package/dist/components/ParticleLatex.js +13 -0
  32. package/dist/components/ParticleTagBadge.d.ts +2 -1
  33. package/dist/components/ParticleTagBadge.js +5 -7
  34. package/dist/components/ParticleTagFilters.d.ts +1 -6
  35. package/dist/components/ParticleTagFilters.js +16 -17
  36. package/dist/components/RequestButtonGroup.d.ts +1 -1
  37. package/dist/components/RequestButtonGroup.js +2 -5
  38. package/dist/components/RequestRow.d.ts +1 -1
  39. package/dist/components/RequestRow.js +8 -12
  40. package/dist/components/StrippingLineDropdown.js +14 -16
  41. package/dist/components/StrippingLineInfo.d.ts +5 -5
  42. package/dist/components/StrippingLineInfo.js +14 -4
  43. package/dist/components/StrippingLineInfoButton.d.ts +6 -0
  44. package/dist/components/StrippingLineInfoButton.js +7 -0
  45. package/dist/components/StrippingLineVersionBadge.d.ts +7 -0
  46. package/dist/components/{StrippingLineBadge.js → StrippingLineVersionBadge.js} +5 -5
  47. package/dist/components/StrippingLinesCountBadge.d.ts +8 -0
  48. package/dist/components/StrippingLinesCountBadge.js +11 -0
  49. package/dist/components/TagDropdown.d.ts +3 -3
  50. package/dist/components/TagDropdown.js +2 -2
  51. package/dist/components/{TupleToolDropdown.d.ts → TupleToolClassDropdown.d.ts} +2 -5
  52. package/dist/components/{TupleToolDropdown.js → TupleToolClassDropdown.js} +16 -13
  53. package/dist/components/TupleToolDocsAccordion.d.ts +1 -1
  54. package/dist/components/TupleToolDocsAccordion.js +4 -8
  55. package/dist/components/TupleToolGroupsAccordion.d.ts +5 -0
  56. package/dist/components/TupleToolGroupsAccordion.js +31 -0
  57. package/dist/components/TupleToolLabel.d.ts +1 -1
  58. package/dist/components/TupleToolLabel.js +2 -5
  59. package/dist/components/TupleToolsAccordion.d.ts +1 -0
  60. package/dist/components/TupleToolsAccordion.js +22 -0
  61. package/dist/components/VerticalLine.d.ts +7 -0
  62. package/dist/components/VerticalLine.js +4 -0
  63. package/dist/components/modals/AddTupleToolModal.d.ts +3 -4
  64. package/dist/components/modals/AddTupleToolModal.js +13 -12
  65. package/dist/components/modals/ConfigureTupleToolModal.d.ts +2 -2
  66. package/dist/components/modals/ConfigureTupleToolModal.js +28 -11
  67. package/dist/components/modals/UploadDttConfigModal.d.ts +1 -1
  68. package/dist/components/modals/UploadDttConfigModal.js +1 -4
  69. package/dist/config.d.ts +19 -47
  70. package/dist/config.js +27 -39
  71. package/dist/models/bkPath.js +1 -1
  72. package/dist/models/dtt.d.ts +5 -2
  73. package/dist/models/dtt.js +37 -18
  74. package/dist/models/rowData.d.ts +1 -1
  75. package/dist/models/yamlFile.js +1 -1
  76. package/dist/pages/DecaySearchPage.js +4 -9
  77. package/dist/pages/DecayTreeConfigPage.js +5 -39
  78. package/dist/pages/RequestPage.js +11 -16
  79. package/dist/providers/DttProvider.d.ts +5 -3
  80. package/dist/providers/DttProvider.js +33 -55
  81. package/dist/providers/MetadataProvider.d.ts +1 -1
  82. package/dist/providers/MetadataProvider.js +11 -5
  83. package/dist/providers/RequestProvider.js +2 -2
  84. package/dist/providers/RowProvider.d.ts +2 -2
  85. package/dist/providers/RowProvider.js +10 -9
  86. package/dist/providers/RowsProvider.js +0 -3
  87. package/dist/tests/components/BookkeepingPathDropdown.test.d.ts +1 -0
  88. package/dist/tests/components/BookkeepingPathDropdown.test.js +118 -0
  89. package/dist/tests/components/DatasetInfo.test.d.ts +1 -0
  90. package/dist/tests/components/DatasetInfo.test.js +38 -0
  91. package/dist/tests/components/DecayCard.test.d.ts +1 -0
  92. package/dist/tests/components/DecayCard.test.js +115 -0
  93. package/dist/tests/components/DecayLatex.test.d.ts +1 -0
  94. package/dist/tests/components/DecayLatex.test.js +31 -0
  95. package/dist/tests/components/DecayList.test.d.ts +1 -0
  96. package/dist/tests/components/DecayList.test.js +76 -0
  97. package/dist/tests/components/DecayListItem.test.d.ts +1 -0
  98. package/dist/tests/components/DecayListItem.test.js +51 -0
  99. package/dist/tests/components/DecayTreeCard.test.d.ts +1 -0
  100. package/dist/tests/components/DecayTreeCard.test.js +119 -0
  101. package/dist/tests/components/DecayTreeGraph.test.d.ts +1 -0
  102. package/dist/tests/components/DecayTreeGraph.test.js +125 -0
  103. package/dist/tests/components/DeleteButton.test.d.ts +1 -0
  104. package/dist/tests/components/DeleteButton.test.js +45 -0
  105. package/dist/tests/components/DttNameInput.test.d.ts +1 -0
  106. package/dist/tests/components/DttNameInput.test.js +75 -0
  107. package/dist/tests/components/NtupleWizard.test.d.ts +1 -0
  108. package/dist/tests/components/NtupleWizard.test.js +57 -0
  109. package/dist/tests/components/ParticleDropdown.test.d.ts +1 -0
  110. package/dist/tests/components/ParticleDropdown.test.js +23 -0
  111. package/dist/tests/components/ParticleTagFilters.test.d.ts +1 -0
  112. package/dist/tests/components/ParticleTagFilters.test.js +87 -0
  113. package/dist/tests/components/RequestButtonGroup.test.d.ts +1 -0
  114. package/dist/tests/components/RequestButtonGroup.test.js +132 -0
  115. package/dist/tests/components/RequestRow.test.d.ts +1 -0
  116. package/dist/tests/components/RequestRow.test.js +58 -0
  117. package/dist/tests/components/StrippingLineDropdown.test.d.ts +1 -0
  118. package/dist/tests/components/StrippingLineDropdown.test.js +88 -0
  119. package/dist/tests/components/badges.test.d.ts +1 -0
  120. package/dist/tests/components/badges.test.js +120 -0
  121. package/dist/tests/components/dropdowns.test.d.ts +1 -0
  122. package/dist/tests/components/dropdowns.test.js +110 -0
  123. package/dist/tests/components/dttComponents.test.d.ts +1 -0
  124. package/dist/tests/components/dttComponents.test.js +287 -0
  125. package/dist/tests/components/formInputs.test.d.ts +1 -0
  126. package/dist/tests/components/formInputs.test.js +96 -0
  127. package/dist/tests/components/metadataComponents.test.d.ts +1 -0
  128. package/dist/tests/components/metadataComponents.test.js +137 -0
  129. package/dist/tests/components/miscComponents.test.d.ts +1 -0
  130. package/dist/tests/components/miscComponents.test.js +134 -0
  131. package/dist/tests/components/modals.test.d.ts +1 -0
  132. package/dist/tests/components/modals.test.js +554 -0
  133. package/dist/tests/components/tupleToolParams.test.d.ts +1 -0
  134. package/dist/tests/components/tupleToolParams.test.js +213 -0
  135. package/dist/tests/config.test.d.ts +1 -0
  136. package/dist/tests/config.test.js +31 -0
  137. package/dist/tests/mockSetup.d.ts +1 -0
  138. package/dist/tests/mockSetup.js +30 -0
  139. package/dist/tests/models/BkPath.test.d.ts +1 -0
  140. package/dist/tests/models/BkPath.test.js +87 -0
  141. package/dist/tests/models/Dtt.test.d.ts +1 -0
  142. package/dist/tests/models/Dtt.test.js +376 -0
  143. package/dist/tests/models/TupleTool.test.d.ts +1 -0
  144. package/dist/tests/models/TupleTool.test.js +80 -0
  145. package/dist/tests/models/YamlFile.test.d.ts +1 -0
  146. package/dist/tests/models/YamlFile.test.js +123 -0
  147. package/dist/tests/pages/DecaySearchPage.test.d.ts +1 -0
  148. package/dist/tests/pages/DecaySearchPage.test.js +228 -0
  149. package/dist/tests/pages/DecayTreeConfigPage.test.d.ts +1 -0
  150. package/dist/tests/pages/DecayTreeConfigPage.test.js +105 -0
  151. package/dist/tests/pages/RequestPage.test.d.ts +1 -0
  152. package/dist/tests/pages/RequestPage.test.js +439 -0
  153. package/dist/tests/providers/DttProvider.test.d.ts +1 -0
  154. package/dist/tests/providers/DttProvider.test.js +105 -0
  155. package/dist/tests/providers/MetadataProvider.test.d.ts +1 -0
  156. package/dist/tests/providers/MetadataProvider.test.js +129 -0
  157. package/dist/tests/providers/RequestProvider.test.d.ts +1 -0
  158. package/dist/tests/providers/RequestProvider.test.js +306 -0
  159. package/dist/tests/providers/RowProvider.test.d.ts +1 -0
  160. package/dist/tests/providers/RowProvider.test.js +110 -0
  161. package/dist/tests/providers/RowsProvider.test.d.ts +1 -0
  162. package/dist/tests/providers/RowsProvider.test.js +84 -0
  163. package/dist/tests/providers/WizardConfigProvider.test.d.ts +1 -0
  164. package/dist/tests/providers/WizardConfigProvider.test.js +36 -0
  165. package/dist/tests/setupTests.d.ts +1 -0
  166. package/dist/tests/setupTests.js +15 -0
  167. package/dist/tests/testUtils.d.ts +33 -0
  168. package/dist/tests/testUtils.js +196 -0
  169. package/dist/tests/utils/latexUtils.test.d.ts +1 -0
  170. package/dist/tests/utils/latexUtils.test.js +62 -0
  171. package/dist/tests/utils/utils.test.d.ts +1 -0
  172. package/dist/tests/utils/utils.test.js +394 -0
  173. package/dist/utils/latexUtils.d.ts +13 -0
  174. package/dist/utils/latexUtils.js +86 -0
  175. package/dist/utils/utils.d.ts +1 -0
  176. package/dist/utils/utils.js +40 -4
  177. package/package.json +24 -10
  178. package/dist/components/NumStrippingLinesBadge.d.ts +0 -8
  179. package/dist/components/NumStrippingLinesBadge.js +0 -10
  180. package/dist/components/StrippingLineBadge.d.ts +0 -7
  181. package/dist/components/TupleToolGroup.d.ts +0 -6
  182. package/dist/components/TupleToolGroup.js +0 -22
  183. package/dist/components/TupleToolList.d.ts +0 -7
  184. package/dist/components/TupleToolList.js +0 -38
package/dist/App.js CHANGED
@@ -15,6 +15,7 @@ import { Archive, CodeSlash, House } from "react-bootstrap-icons";
15
15
  import { config } from "./config";
16
16
  import { NtupleWizard } from "./components/NtupleWizard";
17
17
  import { BrowserRouter } from "react-router-dom";
18
+ import { Slide, ToastContainer } from "react-toastify";
18
19
  export function App() {
19
- return (_jsxs(_Fragment, { children: [_jsx(Navbar, { collapseOnSelect: true, expand: "lg", bg: "dark", variant: "dark", children: _jsxs(Container, { children: [_jsxs(Navbar.Brand, { href: "/", children: [_jsx("img", { src: "/logo.svg", height: "30", className: "d-inline-block align-top", alt: "LHCb logo" }), " LHCb NTuple Wizard"] }), _jsx(Navbar.Toggle, { "aria-controls": "responsive-navbar-nav" }), _jsx(Navbar.Collapse, { className: "justify-content-end", children: _jsxs(Nav, { children: [_jsxs(NavDropdown, { title: "About", id: "about-dropdown", menuVariant: "dark", children: [_jsxs(NavDropdown.Item, { href: "https://lhcb-dpa.web.cern.ch/lhcb-dpa/wp6/ntupling-wizard.html", target: "_blank", children: [_jsx(House, {}), " Project home"] }), _jsxs(NavDropdown.Item, { href: "https://gitlab.cern.ch/lhcb-dpa/wp6-analysis-preservation-and-open-data/lhcb-ntuple-wizard-frontend", target: "_blank", children: [_jsx(CodeSlash, {}), " Source code"] }), _jsxs(NavDropdown.Item, { href: config.metadata_baseurl, target: "_blank", children: [_jsx(Archive, {}), " Metadata files"] })] }), _jsx(Nav.Link, { href: "https://opendata.cern.ch/", target: "_blank", children: _jsx("img", { src: "/open_data_portal.png", height: "30", alt: "CERN Open Data Portal" }) })] }) })] }) }), _jsx(Container, { className: "mt-4", children: _jsx(BrowserRouter, { children: _jsx(NtupleWizard, {}) }) })] }));
20
+ return (_jsxs(_Fragment, { children: [_jsx(Navbar, { collapseOnSelect: true, expand: "lg", bg: "dark", variant: "dark", children: _jsxs(Container, { children: [_jsxs(Navbar.Brand, { href: "/", children: [_jsx("img", { src: "/logo.svg", height: "30", className: "d-inline-block align-top", alt: "LHCb logo" }), " LHCb NTuple Wizard"] }), _jsx(Navbar.Toggle, { "aria-controls": "responsive-navbar-nav" }), _jsx(Navbar.Collapse, { className: "justify-content-end", children: _jsxs(Nav, { children: [_jsxs(NavDropdown, { title: "About", id: "about-dropdown", menuVariant: "dark", children: [_jsxs(NavDropdown.Item, { href: "https://lhcb-dpa.web.cern.ch/lhcb-dpa/wp6/ntupling-wizard.html", target: "_blank", children: [_jsx(House, {}), " Project home"] }), _jsxs(NavDropdown.Item, { href: "https://gitlab.cern.ch/lhcb-dpa/wp6-analysis-preservation-and-open-data/lhcb-ntuple-wizard-frontend", target: "_blank", children: [_jsx(CodeSlash, {}), " Source code"] }), _jsxs(NavDropdown.Item, { href: config.metadata_baseurl, target: "_blank", children: [_jsx(Archive, {}), " Metadata files"] })] }), _jsx(Nav.Link, { href: "https://opendata.cern.ch/", target: "_blank", children: _jsx("img", { src: "/open_data_portal.png", height: "30", alt: "CERN Open Data Portal" }) })] }) })] }) }), _jsx(Container, { className: "mt-4", children: _jsxs(BrowserRouter, { children: [_jsx(ToastContainer, { transition: Slide, hideProgressBar: true }), _jsx(NtupleWizard, {})] }) })] }));
20
21
  }
@@ -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
  }
@@ -0,0 +1 @@
1
+ export declare function DecayCard(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,47 @@
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
+ import { useState } from "react";
4
+ import { Download, ExclamationCircle, GearWideConnected, PlusLg, Upload } from "react-bootstrap-icons";
5
+ import { DeleteButton } from "./DeleteButton";
6
+ import { DecayLatex } from "./DecayLatex";
7
+ import Dtt from "../models/dtt";
8
+ import { useRows } from "../providers/RowsProvider";
9
+ import { useNavigate } from "react-router-dom";
10
+ import { useMetadata } from "../providers/MetadataProvider";
11
+ import { downloadText } from "../utils/utils";
12
+ import { YamlFile } from "../models/yamlFile";
13
+ import { UploadDttConfigModal } from "./modals/UploadDttConfigModal";
14
+ import { VARIABLES_PATH } from "../constants";
15
+ import { useRow } from "../providers/RowProvider";
16
+ import { useWizardConfig } from "../providers/WizardConfigProvider";
17
+ import { DttNameInput } from "./DttNameInput";
18
+ import { GuideLinkButton } from "./GuideLinkButton";
19
+ export function DecayCard() {
20
+ const { row } = useRow();
21
+ const { setRows, updateRow } = useRows();
22
+ const navigate = useNavigate();
23
+ const metadata = useMetadata();
24
+ const { basePath, variant } = useWizardConfig();
25
+ const [showUploadModal, setShowUploadModal] = useState(false);
26
+ const [dttNameValid, setDttNameValid] = useState(true);
27
+ const [autoFocusRow, setAutoFocusRow] = useState(null);
28
+ const handleCreateDTT = () => {
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);
32
+ updateRow(row.id, (r) => ({
33
+ ...r,
34
+ dtt: row.line ? dtt.withInputFromLine(row.line) : dtt,
35
+ }));
36
+ };
37
+ const handleConfigureDtt = () => {
38
+ setRows((rows) => rows.map((r) => (r.id === row.id ? { ...r, editTree: true } : { ...r, editTree: false })));
39
+ void navigate(`${basePath}${VARIABLES_PATH}`);
40
+ };
41
+ const handleDeleteDtt = () => {
42
+ updateRow(row.id, (r) => ({ ...r, dtt: null }));
43
+ };
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: () => {
45
+ downloadText(YamlFile.fromDtt(row.dtt));
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 }) })] }));
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("RoyalBlue", 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,33 +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 { Button, Card, OverlayTrigger, Stack, Tooltip } from "react-bootstrap";
17
- import { Lock, QuestionCircle, Search, Unlock } from "react-bootstrap-icons";
18
- import { config } from "../config.js";
19
- import { useEffect, useState } from "react";
15
+ import { Accordion, Button, Card, OverlayTrigger, Stack, Tooltip } from "react-bootstrap";
16
+ import { QuestionCircle } from "react-bootstrap-icons";
20
17
  import { DecayTreeGraph } from "./DecayTreeGraph";
21
18
  import { DecayTreeCardHeader } from "./DecayTreeCardHeader";
22
19
  import { ParticleTagFilters } from "./ParticleTagFilters";
23
20
  import { useDtt } from "../providers/DttProvider";
24
- 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() {
25
27
  const metadata = useMetadata();
26
- const { dtt, decay, dirty } = useDtt();
27
- const [selectedBranch, setSelectedBranch] = useState([]);
28
- const [lockGraphZoom, setLockGraphZoom] = useState(!config.dttGraphOptions.interaction.zoomView);
29
- useEffect(() => {
30
- onDirtyUpdated(dtt.getName(), dirty);
31
- }, [dtt, dirty]);
32
- if (!metadata) {
33
- return null;
34
- }
35
- return (_jsxs(Card, { children: [_jsx(DecayTreeCardHeader, { dttName: dtt.getName(), inputs: dtt.config.inputs }), _jsxs(Card.Body, { children: [_jsxs(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(OverlayTrigger, { placement: "bottom", overlay: _jsxs(Tooltip, { children: ["Click to ", lockGraphZoom ? "unlock" : "lock", " scroll-to-zoom."] }), children: _jsxs(Button, { onClick: () => setLockGraphZoom(!lockGraphZoom), variant: "secondary-outline", children: [lockGraphZoom ? _jsx(Lock, {}) : _jsx(Unlock, {}), _jsx(Search, {})] }) })] }), _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
36
31
  .filter((tag) => metadata.userHints.decayTags[tag].warn)
37
- .map((tag) => (_jsx(DecayTagBadge, { tag: tag }, tag))) }), _jsx(DecayTreeGraph, { decay: decay, lockGraphZoom: lockGraphZoom, selectedParticlesIds: selectedBranch, onNodeSelectionChanged: (nodeIds) => {
38
- setSelectedBranch([...nodeIds].sort());
39
- } }), _jsx(ParticleTagFilters, { branch: selectedBranch, onSelectTag: setSelectedBranch }), _jsxs(Card.Title, { className: "mt-4", children: ["Current selection: ", _jsx(DecayLatex, { decay: decay, selection: selectedBranch })] }), _jsx(TupleToolList, { branch: selectedBranch, onGroupSelected: (group) => {
40
- const selection = group.split(",");
41
- setSelectedBranch(selection);
42
- }, 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 })] })] })] })] })] }));
43
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,9 +1,5 @@
1
- import { DecayData } from "../models/decayData";
2
1
  interface Props {
3
- decay: DecayData;
4
- lockGraphZoom: boolean;
5
- selectedParticlesIds: string[];
6
2
  onNodeSelectionChanged: (nodeIds: string[]) => void;
7
3
  }
8
- export declare function DecayTreeGraph({ decay, lockGraphZoom, selectedParticlesIds, onNodeSelectionChanged }: Props): import("react/jsx-runtime").JSX.Element;
4
+ export declare function DecayTreeGraph({ onNodeSelectionChanged }: Props): import("react/jsx-runtime").JSX.Element;
9
5
  export {};
@@ -1,52 +1,66 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // @ts-expect-error The react-graph-vis package does not have types
3
- import Graph from "react-graph-vis";
2
+ import CytoscapeComponent from "react-cytoscapejs";
3
+ import cytoscape from "cytoscape";
4
+ import dagre from "cytoscape-dagre";
4
5
  import { config } from "../config";
5
6
  import { tex2svg } from "../utils/mathjaxUtils";
6
7
  import { useMetadata } from "../providers/MetadataProvider";
7
- import { useEffect, useState } from "react";
8
- export function DecayTreeGraph({ decay, lockGraphZoom, selectedParticlesIds, onNodeSelectionChanged }) {
8
+ import { useEffect, useRef } from "react";
9
+ import { useDtt } from "../providers/DttProvider";
10
+ import { LATEX_SELECTED_COLOR } from "../utils/latexUtils";
11
+ cytoscape.use(dagre);
12
+ export function DecayTreeGraph({ onNodeSelectionChanged }) {
9
13
  const metadata = useMetadata();
10
- // eslint-disable-next-line
11
- const [network, setNetwork] = useState(null);
14
+ const { dtt, decay, selectedBranch, highlightedBranch } = useDtt();
15
+ const cyRef = useRef(null);
16
+ // Sync external selection state to cytoscape selection
12
17
  useEffect(() => {
13
- // eslint-disable-next-line
14
- network?.selectNodes(selectedParticlesIds);
15
- }, [selectedParticlesIds]);
16
- useEffect(() => {
17
- // eslint-disable-next-line
18
- network?.setOptions({ interaction: { zoomView: lockGraphZoom } });
19
- }, [lockGraphZoom]);
20
- function htmlTitle(text) {
21
- const container = document.createElement("span");
22
- container.innerText = text;
23
- return container;
24
- }
25
- function makeSVG(text, colour = "black") {
26
- const pipes = "\\phantom{{}^{|-}_{|-}}"; // looks weird but helps ensure uniform text scaling
27
- const texSVG = tex2svg(`${pipes}${text}${pipes}`).innerHTML.replaceAll("currentColor", colour);
18
+ const cy = cyRef.current;
19
+ if (!cy)
20
+ return;
21
+ cy.nodes().forEach((node) => {
22
+ if (selectedBranch.includes(node.id())) {
23
+ node.select();
24
+ }
25
+ else {
26
+ node.unselect();
27
+ }
28
+ });
29
+ }, [selectedBranch]);
30
+ function makeSVG(text, color = "black") {
31
+ const pipes = "\\phantom{{}^{|-}_{|-}}";
32
+ const texSVG = tex2svg(`${pipes}${text}${pipes}`).innerHTML.replaceAll("currentColor", color);
28
33
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(texSVG)}`;
29
34
  }
30
- function buildGraph() {
31
- const graph = { nodes: [], edges: [] };
32
- if (!metadata) {
33
- return graph;
34
- }
35
+ function buildElements() {
36
+ const elements = [];
35
37
  const addNodeAndEdge = (item, parentId) => {
36
38
  const { branch, particle } = item;
37
39
  const particleProperties = metadata.particleProperties[particle];
38
- graph.nodes.push({
39
- id: branch,
40
- label: branch,
41
- title: htmlTitle(particleProperties.html),
42
- shape: "image",
43
- image: {
44
- selected: makeSVG(particleProperties.latex, "#0d6efd"),
45
- unselected: makeSVG(particleProperties.latex, "black"),
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");
46
+ elements.push({
47
+ data: {
48
+ id: branch,
49
+ label: branch,
50
+ imageSrc,
51
+ tupleToolCountLabel: `${tupleToolCount}`,
52
+ tupleToolStatusColor: tupleToolCount > 0 ? "#198754" : "#6c757d",
46
53
  },
54
+ selected: selectedBranch.includes(branch),
47
55
  });
48
56
  if (parentId !== null) {
49
- graph.edges.push({ from: parentId, to: branch });
57
+ elements.push({
58
+ data: {
59
+ id: `${parentId}->${branch}`,
60
+ source: parentId,
61
+ target: branch,
62
+ },
63
+ });
50
64
  }
51
65
  return branch;
52
66
  };
@@ -68,22 +82,119 @@ export function DecayTreeGraph({ decay, lockGraphZoom, selectedParticlesIds, onN
68
82
  }
69
83
  };
70
84
  walk(decay.descriptors.mapped_list, null);
71
- return graph;
85
+ return elements;
72
86
  }
73
- return (_jsx(Graph, { graph: buildGraph(), options: config.dttGraphOptions, events: {
74
- // @ts-expect-error The react-graph-vis package does not have types
75
- selectNode: (event) => {
76
- // eslint-disable-next-line
77
- onNodeSelectionChanged(event.nodes);
87
+ const stylesheet = [
88
+ // Disable the grey circle that shows when clicking
89
+ {
90
+ selector: "core",
91
+ style: {
92
+ "active-bg-size": 0,
93
+ },
94
+ },
95
+ {
96
+ selector: "node",
97
+ style: {
98
+ shape: "ellipse",
99
+ // Use the unselected SVG as the background image
100
+ "background-image": "data(imageSrc)",
101
+ "background-fit": "contain",
102
+ "background-opacity": 0,
103
+ width: config.dttGraphOptions.nodes.width,
104
+ height: config.dttGraphOptions.nodes.height,
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,
120
+ },
121
+ },
122
+ {
123
+ selector: "node:active",
124
+ style: {
125
+ "overlay-opacity": 0,
126
+ },
127
+ },
128
+ {
129
+ selector: "node.hover",
130
+ style: {
131
+ "background-opacity": 0.2,
132
+ "background-color": config.dttGraphOptions.nodes.hoverColor,
78
133
  },
79
- // @ts-expect-error The react-graph-vis package does not have types
80
- deselectNode: (event) => {
81
- // eslint-disable-next-line
82
- onNodeSelectionChanged(event.nodes);
134
+ },
135
+ {
136
+ selector: "edge",
137
+ style: {
138
+ "curve-style": "bezier",
139
+ "target-arrow-shape": "triangle",
140
+ width: config.dttGraphOptions.edges.width,
141
+ "line-color": config.dttGraphOptions.edges.color,
142
+ "target-arrow-color": config.dttGraphOptions.edges.color,
143
+ events: "no",
83
144
  },
84
- },
85
- // @ts-expect-error The react-graph-vis package does not have types
86
- getNetwork: (n) => {
87
- setNetwork(n);
145
+ },
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
+ };
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
+ }
174
+ cyRef.current = cy;
175
+ // Set up zoom
176
+ cy.userZoomingEnabled(false);
177
+ cy.ready(() => {
178
+ updateZoomBounds(cy);
179
+ });
180
+ cy.on("layoutstop", () => {
181
+ updateZoomBounds(cy);
182
+ });
183
+ // Handle node selection events
184
+ cy.off("select unselect", "node");
185
+ cy.on("select unselect", "node", () => {
186
+ const selected = cy.nodes(":selected").map((n) => n.id());
187
+ onNodeSelectionChanged(selected);
188
+ });
189
+ // Handle hover events
190
+ cy.on("mouseover", "node", (e) => {
191
+ e.target.addClass("hover");
192
+ });
193
+ cy.on("mouseout", "node", (e) => {
194
+ e.target.removeClass("hover");
195
+ });
196
+ }, layout: {
197
+ ...config.dttGraphOptions.layout,
198
+ name: "dagre",
88
199
  } }));
89
200
  }