lhcb-ntuple-wizard 2.0.3 → 2.1.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 (45) hide show
  1. package/dist/App.js +2 -1
  2. package/dist/components/DecayCard.d.ts +1 -0
  3. package/dist/components/{DttNameInput.js → DecayCard.js} +9 -5
  4. package/dist/components/DecayLatex.js +1 -1
  5. package/dist/components/DecayTreeCard.js +7 -10
  6. package/dist/components/DecayTreeGraph.d.ts +1 -2
  7. package/dist/components/DecayTreeGraph.js +132 -46
  8. package/dist/components/DeleteButton.d.ts +2 -1
  9. package/dist/components/DeleteButton.js +2 -2
  10. package/dist/components/NtupleWizard.d.ts +2 -1
  11. package/dist/components/NtupleWizard.js +8 -2
  12. package/dist/components/RequestButtonGroup.d.ts +1 -7
  13. package/dist/components/RequestButtonGroup.js +5 -3
  14. package/dist/components/RequestRow.d.ts +1 -7
  15. package/dist/components/RequestRow.js +6 -5
  16. package/dist/components/StrippingLineBadge.js +1 -1
  17. package/dist/components/{TupleToolDropdown.d.ts → TupleToolClassDropdown.d.ts} +2 -5
  18. package/dist/components/{TupleToolDropdown.js → TupleToolClassDropdown.js} +10 -4
  19. package/dist/components/TupleToolGroup.d.ts +1 -2
  20. package/dist/components/TupleToolGroup.js +5 -5
  21. package/dist/components/TupleToolList.d.ts +1 -2
  22. package/dist/components/TupleToolList.js +11 -7
  23. package/dist/components/VerticalLine.d.ts +7 -0
  24. package/dist/components/VerticalLine.js +4 -0
  25. package/dist/components/modals/AddTupleToolModal.d.ts +3 -4
  26. package/dist/components/modals/AddTupleToolModal.js +12 -12
  27. package/dist/components/modals/ConfigureTupleToolModal.d.ts +2 -2
  28. package/dist/components/modals/ConfigureTupleToolModal.js +28 -11
  29. package/dist/components/modals/ReasonForRequestModal.d.ts +1 -3
  30. package/dist/components/modals/ReasonForRequestModal.js +6 -4
  31. package/dist/config.d.ts +18 -47
  32. package/dist/config.js +26 -39
  33. package/dist/pages/DecaySearchPage.d.ts +1 -5
  34. package/dist/pages/DecaySearchPage.js +3 -1
  35. package/dist/pages/DecayTreeConfigPage.d.ts +1 -5
  36. package/dist/pages/DecayTreeConfigPage.js +16 -14
  37. package/dist/pages/RequestPage.d.ts +1 -20
  38. package/dist/pages/RequestPage.js +15 -16
  39. package/dist/providers/DttProvider.d.ts +2 -0
  40. package/dist/providers/DttProvider.js +4 -1
  41. package/dist/providers/WizardConfigProvider.d.ts +21 -0
  42. package/dist/providers/WizardConfigProvider.js +21 -0
  43. package/dist/utils/utils.js +36 -3
  44. package/package.json +10 -5
  45. package/dist/components/DttNameInput.d.ts +0 -9
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 DecayCard(): import("react/jsx-runtime").JSX.Element | null;
@@ -12,10 +12,14 @@ import { downloadText } from "../utils/utils";
12
12
  import { YamlFile } from "../models/yamlFile";
13
13
  import { UploadDttConfigModal } from "./modals/UploadDttConfigModal";
14
14
  import { VARIABLES_PATH } from "../constants";
15
- export function DttNameInput({ row, hideDownloadButtons, hideUploadButtons, basePath }) {
16
- const { rows, updateRow } = useRows();
15
+ import { useRow } from "../providers/RowProvider";
16
+ import { useWizardConfig } from "../providers/WizardConfigProvider";
17
+ export function DecayCard() {
18
+ const { row } = useRow();
19
+ const { rows, setRows, updateRow } = useRows();
17
20
  const navigate = useNavigate();
18
21
  const metadata = useMetadata();
22
+ const { basePath, variant } = useWizardConfig();
19
23
  const [showUploadModal, setShowUploadModal] = useState(false);
20
24
  const [dttName, setDttName] = useState(row.dtt?.getName() || "");
21
25
  const [dttNameValid, setDttNameValid] = useState(true);
@@ -46,7 +50,7 @@ export function DttNameInput({ row, hideDownloadButtons, hideUploadButtons, base
46
50
  setAutoFocus(true);
47
51
  };
48
52
  const handleConfigureDtt = () => {
49
- updateRow(row.id, (r) => ({ ...r, editTree: true }));
53
+ setRows((rows) => rows.map((r) => (r.id === row.id ? { ...r, editTree: true } : { ...r, editTree: false })));
50
54
  void navigate(`${basePath}${VARIABLES_PATH}`);
51
55
  };
52
56
  const handleDeleteDtt = () => {
@@ -65,7 +69,7 @@ export function DttNameInput({ row, hideDownloadButtons, hideUploadButtons, base
65
69
  ? "Name cannot be empty"
66
70
  : !allNameCharsValid(dttName.trim())
67
71
  ? "Name contains invalid characters"
68
- : "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, {}) }) }), !hideDownloadButtons && (_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: () => {
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: () => {
69
73
  downloadText(YamlFile.fromDtt(row.dtt));
70
- }, disabled: !dttNameValid, children: _jsx(Download, {}) }) }) })), _jsx(DeleteButton, { action: handleDeleteDtt })] })) : (_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, {}) }) }), !hideUploadButtons && (_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, { children: _jsx(DecayLatex, { decay: row.decay }) })] }));
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 }) })] }));
71
75
  }
@@ -95,7 +95,7 @@ export function DecayLatex({ decay, selection }) {
95
95
  let latex = metadata.particleProperties[cleanName].latex;
96
96
  // Underline marked particles
97
97
  if (isMarked) {
98
- latex = LATEX_COMMANDS.COLOR("RoyalBlue", latex);
98
+ latex = LATEX_COMMANDS.COLOR("#4169E1", latex);
99
99
  }
100
100
  return `${openBrace}${latex}${closeBrace}`;
101
101
  };
@@ -13,30 +13,27 @@ import { DecayTagBadge } from "./DecayTagBadge.js";
13
13
  import { DecayLatex } from "./DecayLatex";
14
14
  import { TupleToolList } from "./TupleToolList";
15
15
  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";
16
+ import { Card, OverlayTrigger, Stack, Tooltip } from "react-bootstrap";
17
+ import { QuestionCircle } from "react-bootstrap-icons";
18
+ import { useEffect } from "react";
20
19
  import { DecayTreeGraph } from "./DecayTreeGraph";
21
20
  import { DecayTreeCardHeader } from "./DecayTreeCardHeader";
22
21
  import { ParticleTagFilters } from "./ParticleTagFilters";
23
22
  import { useDtt } from "../providers/DttProvider";
24
23
  export function DecayTreeCard({ onConfigSaved, onDirtyUpdated }) {
25
24
  const metadata = useMetadata();
26
- const { dtt, decay, dirty } = useDtt();
27
- const [selectedBranch, setSelectedBranch] = useState([]);
28
- const [lockGraphZoom, setLockGraphZoom] = useState(!config.dttGraphOptions.interaction.zoomView);
25
+ const { dtt, decay, dirty, selectedBranch, setSelectedBranch } = useDtt();
29
26
  useEffect(() => {
30
27
  onDirtyUpdated(dtt.getName(), dirty);
31
28
  }, [dtt, dirty]);
32
29
  if (!metadata) {
33
30
  return null;
34
31
  }
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
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
36
33
  .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) => {
34
+ .map((tag) => (_jsx(DecayTagBadge, { tag: tag }, tag))) }), _jsx(DecayTreeGraph, { decay: decay, selectedParticlesIds: selectedBranch, onNodeSelectionChanged: (nodeIds) => {
38
35
  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) => {
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) => {
40
37
  const selection = group.split(",");
41
38
  setSelectedBranch(selection);
42
39
  }, onSave: () => onConfigSaved(dtt.config) })] })] }));
@@ -1,9 +1,8 @@
1
1
  import { DecayData } from "../models/decayData";
2
2
  interface Props {
3
3
  decay: DecayData;
4
- lockGraphZoom: boolean;
5
4
  selectedParticlesIds: string[];
6
5
  onNodeSelectionChanged: (nodeIds: string[]) => void;
7
6
  }
8
- export declare function DecayTreeGraph({ decay, lockGraphZoom, selectedParticlesIds, onNodeSelectionChanged }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export declare function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionChanged }: Props): import("react/jsx-runtime").JSX.Element;
9
8
  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
+ cytoscape.use(dagre);
10
+ export function DecayTreeGraph({ decay, selectedParticlesIds, onNodeSelectionChanged }) {
9
11
  const metadata = useMetadata();
10
- // eslint-disable-next-line
11
- const [network, setNetwork] = useState(null);
12
+ const cyRef = useRef(null);
13
+ // Sync external selection state to cytoscape selection
12
14
  useEffect(() => {
13
- // eslint-disable-next-line
14
- network?.selectNodes(selectedParticlesIds);
15
+ const cy = cyRef.current;
16
+ if (!cy)
17
+ return;
18
+ cy.nodes().forEach((node) => {
19
+ if (selectedParticlesIds.includes(node.id())) {
20
+ node.select();
21
+ }
22
+ else {
23
+ node.unselect();
24
+ }
25
+ });
15
26
  }, [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
27
  function makeSVG(text, colour = "black") {
26
- const pipes = "\\phantom{{}^{|-}_{|-}}"; // looks weird but helps ensure uniform text scaling
28
+ const pipes = "\\phantom{{}^{|-}_{|-}}";
27
29
  const texSVG = tex2svg(`${pipes}${text}${pipes}`).innerHTML.replaceAll("currentColor", colour);
28
30
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(texSVG)}`;
29
31
  }
30
- function buildGraph() {
31
- const graph = { nodes: [], edges: [] };
32
- if (!metadata) {
33
- return graph;
34
- }
32
+ function getHtmlTitle(text) {
33
+ const container = document.createElement("span");
34
+ container.innerText = text;
35
+ return container;
36
+ }
37
+ function buildElements() {
38
+ const elements = [];
39
+ if (!metadata)
40
+ return elements;
35
41
  const addNodeAndEdge = (item, parentId) => {
36
42
  const { branch, particle } = item;
37
43
  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"),
44
+ elements.push({
45
+ data: {
46
+ id: branch,
47
+ 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,
46
53
  },
54
+ selected: selectedParticlesIds.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,94 @@ 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 on pan/click
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: "",
106
+ },
107
+ },
108
+ {
109
+ selector: "node:active",
110
+ style: {
111
+ "overlay-opacity": 0,
112
+ },
113
+ },
114
+ {
115
+ selector: "node.hover",
116
+ style: {
117
+ "background-opacity": 0.2,
118
+ "background-color": config.dttGraphOptions.nodes.hoverColor,
78
119
  },
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);
120
+ },
121
+ {
122
+ selector: "node:selected",
123
+ style: {
124
+ // Swap to the selected SVG when the node is selected
125
+ "background-image": "data(imageSelected)",
83
126
  },
84
- },
85
- // @ts-expect-error The react-graph-vis package does not have types
86
- getNetwork: (n) => {
87
- setNetwork(n);
127
+ },
128
+ {
129
+ selector: "edge",
130
+ style: {
131
+ "curve-style": "bezier",
132
+ "target-arrow-shape": "triangle",
133
+ width: config.dttGraphOptions.edges.width,
134
+ "line-color": config.dttGraphOptions.edges.color,
135
+ "target-arrow-color": config.dttGraphOptions.edges.color,
136
+ events: "no",
137
+ },
138
+ },
139
+ ];
140
+ 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) => {
141
+ cyRef.current = cy;
142
+ // Set up zoom
143
+ 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 });
158
+ // Handle node selection events
159
+ cy.off("select unselect", "node");
160
+ cy.on("select unselect", "node", () => {
161
+ const selected = cy.nodes(":selected").map((n) => n.id());
162
+ onNodeSelectionChanged(selected);
163
+ });
164
+ // Handle hover events
165
+ cy.on("mouseover", "node", (e) => {
166
+ e.target.addClass("hover");
167
+ });
168
+ cy.on("mouseout", "node", (e) => {
169
+ e.target.removeClass("hover");
170
+ });
171
+ }, layout: {
172
+ ...config.dttGraphOptions.layout,
173
+ name: "dagre",
88
174
  } }));
89
175
  }
@@ -3,7 +3,8 @@ interface Props {
3
3
  action: () => void;
4
4
  outline?: boolean;
5
5
  disabled?: boolean;
6
+ popupMessage?: string;
6
7
  children?: ReactNode;
7
8
  }
8
- export declare function DeleteButton({ action, outline, disabled, children }: Props): import("react/jsx-runtime").JSX.Element | null;
9
+ export declare function DeleteButton({ action, outline, disabled, popupMessage, children }: Props): import("react/jsx-runtime").JSX.Element | null;
9
10
  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, children }) {
14
+ export function DeleteButton({ action, outline, disabled, popupMessage = "Confirm", 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: "Confirm" }) }) }), children: _jsxs(Button, { variant: outline ? "outline-danger" : "danger", disabled: disabled, children: [_jsx(Trash, {}), " ", children] }) }));
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] }) }));
19
19
  }
@@ -16,6 +16,7 @@ interface Props {
16
16
  submitLocation?: string;
17
17
  requestReasonMessage?: string;
18
18
  requestSubmittedMessage?: ReactNode;
19
+ onRequestSubmitted?: () => void;
19
20
  }
20
- export declare function NtupleWizard({ basePath, contactEmail, submitLocation, requestReasonMessage, requestSubmittedMessage, }: Props): import("react/jsx-runtime").JSX.Element;
21
+ export declare function NtupleWizard({ basePath, contactEmail, submitLocation, requestReasonMessage, requestSubmittedMessage, onRequestSubmitted, }: Props): import("react/jsx-runtime").JSX.Element;
21
22
  export {};
@@ -19,10 +19,16 @@ import { MathJaxContext } from "better-react-mathjax";
19
19
  import { RowsProvider } from "../providers/RowsProvider.js";
20
20
  import { RequestProvider } from "../providers/RequestProvider";
21
21
  import { SELECT_DECAYS_PATH, VARIABLES_PATH } from "../constants";
22
- export function NtupleWizard({ basePath = "", contactEmail, submitLocation, requestReasonMessage, requestSubmittedMessage, }) {
22
+ import { WizardConfigProvider } from "../providers/WizardConfigProvider";
23
+ export function NtupleWizard({ basePath = "", contactEmail, submitLocation, requestReasonMessage, requestSubmittedMessage, onRequestSubmitted, }) {
23
24
  const { pathname } = useLocation();
24
25
  if (contactEmail) {
25
26
  localStorage.setItem("email", contactEmail);
26
27
  }
27
- return (_jsx(MetadataProvider, { children: _jsx(RowsProvider, { children: _jsx(RequestProvider, { emailIsKnown: !!contactEmail, children: _jsx(MathJaxContext, { children: pathname.endsWith(SELECT_DECAYS_PATH) ? (_jsx(DecaySearchPage, { basePath: basePath })) : pathname.endsWith(VARIABLES_PATH) ? (_jsx(DecayTreeConfigPage, { basePath: basePath })) : (_jsx(RequestPage, { basePath: basePath, submitLocation: submitLocation, requestReasonMessage: requestReasonMessage, requestSubmittedMessage: requestSubmittedMessage })) }) }) }) }));
28
+ return (_jsx(MetadataProvider, { children: _jsx(WizardConfigProvider, { basePath: basePath, submitLocation: submitLocation, requestReasonMessage: requestReasonMessage, requestSubmittedMessage: requestSubmittedMessage, onRequestSubmitted: onRequestSubmitted, children: _jsx(RowsProvider, { children: _jsx(RequestProvider, { emailIsKnown: !!contactEmail, children: _jsx(MathJaxContext, { config: {
29
+ loader: { load: ["[tex]/color"] },
30
+ tex: {
31
+ packages: { "[+]": ["color"] },
32
+ },
33
+ }, children: pathname.endsWith(SELECT_DECAYS_PATH) ? (_jsx(DecaySearchPage, {})) : pathname.endsWith(VARIABLES_PATH) ? (_jsx(DecayTreeConfigPage, {})) : (_jsx(RequestPage, {})) }) }) }) }) }));
28
34
  }
@@ -1,7 +1 @@
1
- import { NtupleWizardVariant } from "../pages/RequestPage";
2
- interface Props {
3
- variant: NtupleWizardVariant;
4
- basePath: string;
5
- }
6
- export declare function RequestButtonGroup({ variant, basePath }: Props): import("react/jsx-runtime").JSX.Element | null;
7
- export {};
1
+ export declare function RequestButtonGroup(): import("react/jsx-runtime").JSX.Element | null;
@@ -10,13 +10,15 @@ import { useRows } from "../providers/RowsProvider";
10
10
  import { useMetadata } from "../providers/MetadataProvider";
11
11
  import { useRequest } from "../providers/RequestProvider";
12
12
  import { useState } from "react";
13
- export function RequestButtonGroup({ variant, basePath }) {
13
+ import { useWizardConfig } from "../providers/WizardConfigProvider";
14
+ export function RequestButtonGroup() {
15
+ const { variant, basePath } = useWizardConfig();
14
16
  const metadata = useMetadata();
15
17
  const { validation } = useRequest();
16
18
  const { rows, configuredRows, setRows } = useRows();
17
19
  const navigate = useNavigate();
18
20
  const [showProdUploadModal, setShowProdUploadModal] = useState(false);
19
- const handleSubmitRows = async () => {
21
+ const configureAllDtts = async () => {
20
22
  setRows((prev) => prev.map((r) => ({ ...r, editTree: true })));
21
23
  await navigate(`${basePath}${VARIABLES_PATH}`);
22
24
  };
@@ -24,6 +26,6 @@ export function RequestButtonGroup({ variant, basePath }) {
24
26
  return null;
25
27
  }
26
28
  return (_jsxs(_Fragment, { children: [showProdUploadModal && _jsx(UploadDttConfigModal, { onClose: () => setShowProdUploadModal(false) }), _jsxs(ButtonGroup, { children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Select decays" }), children: _jsxs(Button, { type: "submit", variant: "success", onClick: () => void navigate(`${basePath}${SELECT_DECAYS_PATH}`), className: "align-items-center d-flex gap-1", children: [_jsx(PlusLg, {}), " Select decays"] }) }), variant === "standalone" && (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Upload Production configuration file" }), children: _jsx(Button, { className: "ms-auto align-items-center d-flex", type: "button", onClick: () => setShowProdUploadModal(true), children: _jsx(Upload, {}) }) })), _jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Configure all DecayTreeTuples" }), children: _jsxs(Button, { type: "submit", variant: "secondary", onClick: () => {
27
- handleSubmitRows().catch(console.error);
29
+ configureAllDtts().catch(console.error);
28
30
  }, disabled: !validation.allRowsHaveDtt, className: "align-items-center d-flex gap-1", children: [_jsx(GearWideConnected, {}), " ", _jsx(Badge, { pill: true, bg: "primary", children: configuredRows.length })] }) }), variant === "standalone" && (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Download Analysis Production configuration file (info.yaml)" }), children: _jsx(Button, { variant: "primary", type: "button", onClick: () => downloadText(YamlFile.createInfoYaml(rows, metadata)), disabled: !validation.allRowsHaveDtt || !validation.allRowsHavePaths || !validation.isEmailValid, className: "align-items-center d-flex", children: _jsx(Download, {}) }) }))] })] }));
29
31
  }
@@ -1,7 +1 @@
1
- interface Props {
2
- hideDownloadButtons: boolean;
3
- hideUploadButtons: boolean;
4
- basePath: string;
5
- }
6
- export declare function RequestRow({ hideDownloadButtons, hideUploadButtons, basePath }: Props): import("react/jsx-runtime").JSX.Element | null;
7
- export {};
1
+ export declare function RequestRow(): import("react/jsx-runtime").JSX.Element | null;
@@ -2,19 +2,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, Col, OverlayTrigger, Popover, Row } from "react-bootstrap";
3
3
  import { InfoCircle } from "react-bootstrap-icons";
4
4
  import { StrippingLineInfo } from "./StrippingLineInfo";
5
- import { DeleteButton } from "./DeleteButton";
6
- import { DttNameInput } from "./DttNameInput";
5
+ import { DecayCard } from "./DecayCard";
7
6
  import { useMetadata } from "../providers/MetadataProvider";
8
- import { useRows } from "../providers/RowsProvider";
9
7
  import { BookkeepingPathDropdown } from "./BookkeepingPathDropdown";
10
8
  import { StrippingLineDropdown } from "./StrippingLineDropdown";
11
9
  import { useRow } from "../providers/RowProvider";
12
- export function RequestRow({ hideDownloadButtons, hideUploadButtons, basePath }) {
10
+ import { VerticalLine } from "./VerticalLine";
11
+ import { DeleteButton } from "./DeleteButton";
12
+ import { useRows } from "../providers/RowsProvider";
13
+ export function RequestRow() {
13
14
  const metadata = useMetadata();
14
15
  const { row } = useRow();
15
16
  const { removeRow } = useRows();
16
17
  if (!metadata) {
17
18
  return null;
18
19
  }
19
- return (_jsxs(Row, { className: "align-items-center", children: [_jsx(Col, { lg: true, children: _jsx(DttNameInput, { row: row, hideDownloadButtons: hideDownloadButtons, hideUploadButtons: hideUploadButtons, basePath: basePath }) }), _jsx(Col, { lg: true, children: _jsx(StrippingLineDropdown, {}) }), _jsx(Col, { xs: "auto", children: _jsx(OverlayTrigger, { trigger: "click", placement: "right", rootClose: true, overlay: _jsx(Popover, { children: _jsx(Popover.Body, { children: row.lines.map((line) => (_jsx(StrippingLineInfo, { line: line.line, stream: line.stream, versions: line.versions, showLink: true }, line.line))) }) }), children: _jsx(Button, { disabled: row.lines.length === 0, children: _jsx(InfoCircle, {}) }) }) }), _jsx(Col, { children: _jsx(BookkeepingPathDropdown, {}) }), _jsx(Col, { xs: "auto", children: _jsx(DeleteButton, { action: () => removeRow(row.id) }) })] }));
20
+ return (_jsxs(Row, { className: "align-items-stretch mt-1", children: [_jsx(Col, { xs: 5, children: _jsx(DecayCard, {}) }), _jsxs(Col, { xs: 6, className: "d-flex flex-column gap-2", children: [_jsx(Row, { children: _jsxs("div", { className: "d-flex flex-row justify-content-between align-items-center gap-3", children: [_jsx("div", { style: { width: "100%" }, children: _jsx(StrippingLineDropdown, {}) }), _jsx(OverlayTrigger, { trigger: "click", rootClose: true, overlay: _jsx(Popover, { children: _jsx(Popover.Body, { children: row.lines.map((line) => (_jsx(StrippingLineInfo, { line: line.line, stream: line.stream, versions: line.versions, showLink: true }, line.line))) }) }), children: _jsx(Button, { disabled: row.lines.length === 0, children: _jsx(InfoCircle, {}) }) })] }) }), _jsx(Row, { children: _jsx(BookkeepingPathDropdown, {}) })] }), _jsxs(Col, { xs: 1, className: "d-flex align-items-center justify-content-center gap-4", children: [_jsx(VerticalLine, {}), _jsx(DeleteButton, { action: () => removeRow(row.id), popupMessage: "Delete row" })] })] }));
20
21
  }
@@ -24,5 +24,5 @@ export function StrippingLineBadge({ version, line, stream, showLink }) {
24
24
  target: "_blank",
25
25
  }
26
26
  : {};
27
- return (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: metadata ? metadata.strippingHints[version].description : "..." }), children: _jsx(Badge, { pill: true, bg: "info", ...badgeProps, children: "S" + version }) }));
27
+ return (_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { style: { zIndex: 9999 }, children: metadata ? metadata.strippingHints[version].description : "..." }), children: _jsx(Badge, { pill: true, bg: "info", ...badgeProps, children: "S" + version }) }));
28
28
  }
@@ -10,12 +10,9 @@
10
10
  \*****************************************************************************/
11
11
  import { GroupBase, Props as SelectProps } from "react-select";
12
12
  import { ReactNode } from "react";
13
- type ToolOption = {
13
+ type ToolDropdownOption = {
14
14
  value: string;
15
15
  label: ReactNode;
16
16
  };
17
- interface Props extends SelectProps<ToolOption, false, GroupBase<ToolOption>> {
18
- filterToolTags: string[];
19
- }
20
- export declare function TupleToolDropdown({ filterToolTags, ...props }: Props): import("react/jsx-runtime").JSX.Element;
17
+ export declare function TupleToolClassDropdown(props: SelectProps<ToolDropdownOption, false, GroupBase<ToolDropdownOption>>): import("react/jsx-runtime").JSX.Element;
21
18
  export {};
@@ -13,17 +13,23 @@ import Select from "react-select";
13
13
  import { TupleToolLabel } from "./TupleToolLabel";
14
14
  import { useMetadata } from "../providers/MetadataProvider.js";
15
15
  import { TupleTool } from "../models/tupleTool";
16
- export function TupleToolDropdown({ filterToolTags, ...props }) {
16
+ import { useDtt } from "../providers/DttProvider";
17
+ export function TupleToolClassDropdown(props) {
17
18
  const metadata = useMetadata();
19
+ const { selectedBranch } = useDtt();
20
+ const allowedTags = ["IParticleTupleTool", ...(selectedBranch.length === 0 ? ["IEventTupleTool"] : [])];
21
+ const disallowedTools = ["LoKi__Hybrid__ArrayTupleTool"];
22
+ // Group tools by tag
18
23
  const options = metadata
19
- ? filterToolTags.map((allowedTag) => ({
24
+ ? allowedTags.map((allowedTag) => ({
20
25
  label: allowedTag,
21
26
  options: Object.keys(metadata.tupleTools.tupleTools)
22
27
  .sort()
23
28
  .map(TupleTool.fromString)
24
29
  .map((tool) => ({ value: tool.toString(), label: _jsx(TupleToolLabel, { tool: tool }) }))
25
- .filter((tool) => metadata.tupleTools.tupleTools[tool.value].tags.includes(allowedTag)),
30
+ .filter((tool) => metadata.tupleTools.tupleTools[tool.value].tags.includes(allowedTag) &&
31
+ !disallowedTools.includes(tool.value)),
26
32
  }))
27
33
  : [];
28
- return (_jsx("div", { className: "react-select form-control p-0", children: _jsx(Select, { options: options, isLoading: !metadata, ...props }) }));
34
+ return (_jsx("div", { className: "react-select form-control p-0", children: _jsx(Select, { placeholder: "TupleTool class", options: options, isLoading: !metadata, ...props }) }));
29
35
  }
@@ -1,6 +1,5 @@
1
1
  interface Props {
2
- branch: string[];
3
2
  onGroupSelected: (group: string) => void;
4
3
  }
5
- export declare function TupleToolGroup({ branch, onGroupSelected }: Props): import("react/jsx-runtime").JSX.Element;
4
+ export declare function TupleToolGroup({ onGroupSelected }: Props): import("react/jsx-runtime").JSX.Element;
6
5
  export {};
@@ -3,14 +3,14 @@ import { Button, Card, ListGroup, OverlayTrigger, Popover } from "react-bootstra
3
3
  import { PencilSquare } from "react-bootstrap-icons";
4
4
  import { DecayLatex } from "./DecayLatex";
5
5
  import { useDtt } from "../providers/DttProvider";
6
- export function TupleToolGroup({ branch, onGroupSelected }) {
7
- const { dtt, decay } = useDtt();
6
+ export function TupleToolGroup({ onGroupSelected }) {
7
+ const { dtt, decay, selectedBranch } = useDtt();
8
8
  let groups = {};
9
- if (branch.length === 0) {
9
+ if (selectedBranch.length === 0) {
10
10
  groups = dtt.config.groups;
11
11
  }
12
- else if (branch.length === 1) {
13
- const filteredGroups = Object.entries(dtt.config.groups).filter(([key]) => key.includes(branch[0]));
12
+ else if (selectedBranch.length === 1) {
13
+ const filteredGroups = Object.entries(dtt.config.groups).filter(([key]) => key.includes(selectedBranch[0]));
14
14
  groups = Object.fromEntries(filteredGroups);
15
15
  }
16
16
  const groupKeys = Object.keys(groups);
@@ -1,7 +1,6 @@
1
1
  interface Props {
2
- branch: string[];
3
2
  onGroupSelected: (group: string) => void;
4
3
  onSave: () => void;
5
4
  }
6
- export declare function TupleToolList({ branch, onGroupSelected, onSave }: Props): import("react/jsx-runtime").JSX.Element | null;
5
+ export declare function TupleToolList({ onGroupSelected, onSave }: Props): import("react/jsx-runtime").JSX.Element | null;
7
6
  export {};
@@ -19,19 +19,23 @@ import { TupleToolGroup } from "./TupleToolGroup";
19
19
  import { AddTupleToolModal } from "./modals/AddTupleToolModal";
20
20
  import { ConfigureTupleToolModal } from "./modals/ConfigureTupleToolModal";
21
21
  import { useDtt } from "../providers/DttProvider";
22
- export function TupleToolList({ branch, onGroupSelected, onSave }) {
22
+ export function TupleToolList({ onGroupSelected, onSave }) {
23
23
  const metadata = useMetadata();
24
- const { dtt, dirty, save, revert, removeTool } = useDtt();
24
+ const { dtt, dirty, save, revert, removeTool, selectedBranch } = useDtt();
25
25
  const [showAddModal, setShowAddModal] = useState(false);
26
- const [showConfigModalWithTool, setShowConfigModalWithTool] = useState(null);
26
+ const [showConfigModal, setShowConfigModal] = useState(null);
27
27
  if (!metadata) {
28
28
  return null;
29
29
  }
30
- const tools = dtt.listTools(branch);
31
- const filterToolTags = branch.length === 0 ? ["IParticleTupleTool", "IEventTupleTool"] : ["IParticleTupleTool"];
30
+ const tools = dtt.listTools(selectedBranch);
32
31
  return (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(Card.Header, { className: "d-flex justify-content-between align-items-start", children: [_jsxs("span", { children: [tools.length, " TupleTool", tools.length === 1 ? "" : "s"] }), _jsx(Button, { variant: "success", size: "sm", onClick: () => setShowAddModal(true), children: _jsx(PlusLg, {}) })] }), _jsx(ListGroup, { variant: "flush", children: tools.map((tool) => {
33
- return (_jsxs(ListGroup.Item, { className: "d-flex justify-content-between align-items-start", variant: showConfigModalWithTool?.equals(tool) ? "secondary" : "", children: [_jsx(TupleToolLabel, { tool: tool }), _jsxs(ButtonGroup, { size: "sm", children: [_jsx(Button, { variant: "outline-secondary", onClick: () => setShowConfigModalWithTool(tool), children: _jsx(PencilSquare, {}) }), _jsx(DeleteButton, { outline: true, action: () => removeTool(branch, tool) })] })] }, tool.toString()));
34
- }) })] }), showAddModal && (_jsx(AddTupleToolModal, { branch: branch, filterToolTags: filterToolTags, onClose: () => setShowAddModal(false) })), showConfigModalWithTool && (_jsx(ConfigureTupleToolModal, { branch: branch, tool: showConfigModalWithTool, onClose: () => setShowConfigModalWithTool(null) })), branch.length === 0 && Object.keys(dtt.config.groups).length > 0 && (_jsx(TupleToolGroup, { branch: branch, onGroupSelected: onGroupSelected })), _jsxs(ButtonGroup, { className: "mt-2", children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Save changes to this DecayTreeTuple" }), children: _jsx(Button, { onClick: () => {
32
+ return (_jsxs(ListGroup.Item, { className: "d-flex justify-content-between align-items-start", variant: showConfigModal?.tool.equals(tool) ? "secondary" : "", children: [_jsx(TupleToolLabel, { tool: tool }), _jsxs(ButtonGroup, { size: "sm", children: [_jsx(Button, { variant: "outline-secondary", onClick: () => setShowConfigModal({ tool, deleteIfCancelled: false }), children: _jsx(PencilSquare, {}) }), _jsx(DeleteButton, { outline: true, action: () => removeTool(selectedBranch, tool) })] })] }, tool.toString()));
33
+ }) })] }), showAddModal && (_jsx(AddTupleToolModal, { onClose: (newTool) => {
34
+ setShowAddModal(false);
35
+ if (newTool && newTool.class === "LoKi__Hybrid__TupleTool") {
36
+ setShowConfigModal({ tool: newTool, deleteIfCancelled: true });
37
+ }
38
+ } })), showConfigModal && (_jsx(ConfigureTupleToolModal, { tool: showConfigModal.tool, deleteIfCancelled: showConfigModal.deleteIfCancelled, onClose: () => setShowConfigModal(null) })), selectedBranch.length === 0 && Object.keys(dtt.config.groups).length > 0 && (_jsx(TupleToolGroup, { onGroupSelected: onGroupSelected })), _jsxs(ButtonGroup, { className: "mt-2", children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Save changes to this DecayTreeTuple" }), children: _jsx(Button, { onClick: () => {
35
39
  save();
36
40
  onSave();
37
41
  }, disabled: !dirty, children: "Save" }) }), _jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Discard changes to this DecayTreeTuple" }), children: _jsx(Button, { variant: "outline-danger", onClick: revert, disabled: !dirty, children: "Revert" }) })] })] }));
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ width?: string;
3
+ height?: string;
4
+ color?: string;
5
+ }
6
+ export declare function VerticalLine({ width, height, color }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};