lhcb-ntuple-wizard-test 2.0.0 → 2.0.2
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.
- package/dist/App.d.ts +12 -0
- package/dist/App.js +22 -0
- package/dist/components/ConfigFilesUploadingAlert.d.ts +1 -0
- package/dist/components/ConfigFilesUploadingAlert.js +5 -0
- package/dist/components/DatasetEventTypeBadge.d.ts +5 -0
- package/dist/components/DatasetEventTypeBadge.js +23 -0
- package/dist/components/DatasetInfo.d.ts +5 -0
- package/dist/components/DatasetInfo.js +21 -0
- package/dist/components/DecayLatex.d.ts +7 -0
- package/dist/components/DecayLatex.js +103 -0
- package/dist/components/DecayList.d.ts +8 -0
- package/dist/components/DecayList.js +37 -0
- package/dist/components/DecayListItem.d.ts +11 -0
- package/dist/components/DecayListItem.js +23 -0
- package/dist/components/DecayTagBadge.d.ts +5 -0
- package/dist/components/DecayTagBadge.js +21 -0
- package/dist/components/DecayTreeCard.d.ts +7 -0
- package/dist/components/DecayTreeCard.js +43 -0
- package/dist/components/DecayTreeCardHeader.d.ts +7 -0
- package/dist/components/DecayTreeCardHeader.js +5 -0
- package/dist/components/DecayTreeGraph.d.ts +9 -0
- package/dist/components/DecayTreeGraph.js +89 -0
- package/dist/components/DeleteButton.d.ts +9 -0
- package/dist/components/DeleteButton.js +19 -0
- package/dist/components/DttNameInput.d.ts +9 -0
- package/dist/components/DttNameInput.js +73 -0
- package/dist/components/LineTableRow.d.ts +9 -0
- package/dist/components/LineTableRow.js +123 -0
- package/dist/components/LoadingIndicator.d.ts +6 -0
- package/dist/components/LoadingIndicator.js +5 -0
- package/dist/components/NtupleWizard.d.ts +26 -0
- package/dist/components/NtupleWizard.js +33 -0
- package/dist/components/NumStrippingLinesBadge.d.ts +8 -0
- package/dist/components/NumStrippingLinesBadge.js +10 -0
- package/dist/components/ParticleDropdown.d.ts +8 -0
- package/dist/components/ParticleDropdown.js +33 -0
- package/dist/components/ParticleTagBadge.d.ts +9 -0
- package/dist/components/ParticleTagBadge.js +22 -0
- package/dist/components/ParticleTagFilters.d.ts +6 -0
- package/dist/components/ParticleTagFilters.js +51 -0
- package/dist/components/PolarityBadge.d.ts +5 -0
- package/dist/components/PolarityBadge.js +15 -0
- package/dist/components/StrippingLineBadge.d.ts +7 -0
- package/dist/components/StrippingLineBadge.js +29 -0
- package/dist/components/StrippingLineInfo.d.ts +8 -0
- package/dist/components/StrippingLineInfo.js +17 -0
- package/dist/components/SubmitRequestButton.d.ts +10 -0
- package/dist/components/SubmitRequestButton.js +31 -0
- package/dist/components/TagDropdown.d.ts +12 -0
- package/dist/components/TagDropdown.js +31 -0
- package/dist/components/TupleToolDocsAccordion.d.ts +5 -0
- package/dist/components/TupleToolDocsAccordion.js +14 -0
- package/dist/components/TupleToolDropdown.d.ts +21 -0
- package/dist/components/TupleToolDropdown.js +29 -0
- package/dist/components/TupleToolGroup.d.ts +6 -0
- package/dist/components/TupleToolGroup.js +22 -0
- package/dist/components/TupleToolLabel.d.ts +6 -0
- package/dist/components/TupleToolLabel.js +20 -0
- package/dist/components/TupleToolList.d.ts +7 -0
- package/dist/components/TupleToolList.js +38 -0
- package/dist/components/YearBadge.d.ts +5 -0
- package/dist/components/YearBadge.js +15 -0
- package/dist/components/modals/AddTupleToolModal.d.ts +7 -0
- package/dist/components/modals/AddTupleToolModal.js +32 -0
- package/dist/components/modals/ConfigureTupleToolModal.d.ts +8 -0
- package/dist/components/modals/ConfigureTupleToolModal.js +43 -0
- package/dist/components/modals/ReasonForRequestModal.d.ts +8 -0
- package/dist/components/modals/ReasonForRequestModal.js +49 -0
- package/dist/components/modals/UploadDttConfigModal.d.ts +7 -0
- package/dist/components/modals/UploadDttConfigModal.js +35 -0
- package/dist/components/modals/UploadProdConfigModal.d.ts +5 -0
- package/dist/components/modals/UploadProdConfigModal.js +27 -0
- package/dist/components/tupleToolParams/BoolParamInput.d.ts +16 -0
- package/dist/components/tupleToolParams/BoolParamInput.js +15 -0
- package/dist/components/tupleToolParams/DictParamInput.d.ts +14 -0
- package/dist/components/tupleToolParams/DictParamInput.js +31 -0
- package/dist/components/tupleToolParams/ListParamInput.d.ts +14 -0
- package/dist/components/tupleToolParams/ListParamInput.js +46 -0
- package/dist/components/tupleToolParams/NumParamInput.d.ts +18 -0
- package/dist/components/tupleToolParams/NumParamInput.js +25 -0
- package/dist/components/tupleToolParams/StrParamInput.d.ts +19 -0
- package/dist/components/tupleToolParams/StrParamInput.js +22 -0
- package/dist/config.d.ts +78 -0
- package/dist/config.js +72 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/models/bkPath.d.ts +11 -0
- package/dist/models/bkPath.js +40 -0
- package/dist/models/blobFile.d.ts +12 -0
- package/dist/models/blobFile.js +1 -0
- package/dist/models/decayData.d.ts +15 -0
- package/dist/models/decayData.js +1 -0
- package/dist/models/dropdownOption.d.ts +5 -0
- package/dist/models/dropdownOption.js +1 -0
- package/dist/models/dtt.d.ts +108 -0
- package/dist/models/dtt.js +149 -0
- package/dist/models/jobConfig.d.ts +7 -0
- package/dist/models/jobConfig.js +1 -0
- package/dist/models/particle.d.ts +12 -0
- package/dist/models/particle.js +1 -0
- package/dist/models/particleTag.d.ts +4 -0
- package/dist/models/particleTag.js +1 -0
- package/dist/models/rowData.d.ts +15 -0
- package/dist/models/rowData.js +1 -0
- package/dist/models/strippingLine.d.ts +5 -0
- package/dist/models/strippingLine.js +1 -0
- package/dist/models/strippingLineOption.d.ts +6 -0
- package/dist/models/strippingLineOption.js +1 -0
- package/dist/models/tupleTool.d.ts +8 -0
- package/dist/models/tupleTool.js +18 -0
- package/dist/models/tupleTreeGraphData.d.ts +20 -0
- package/dist/models/tupleTreeGraphData.js +1 -0
- package/dist/models/yamlFile.d.ts +11 -0
- package/dist/models/yamlFile.js +78 -0
- package/dist/pages/DecaySearchPage.d.ts +5 -0
- package/dist/pages/DecaySearchPage.js +197 -0
- package/dist/pages/DecayTreeConfigPage.d.ts +5 -0
- package/dist/pages/DecayTreeConfigPage.js +63 -0
- package/dist/pages/LinesTablePage.d.ts +14 -0
- package/dist/pages/LinesTablePage.js +125 -0
- package/dist/providers/DttProvider.d.ts +24 -0
- package/dist/providers/DttProvider.js +77 -0
- package/dist/providers/MetadataProvider.d.ts +87 -0
- package/dist/providers/MetadataProvider.js +50 -0
- package/dist/providers/ProductionConfigProvider.d.ts +14 -0
- package/dist/providers/ProductionConfigProvider.js +76 -0
- package/dist/providers/RequestProvider.d.ts +15 -0
- package/dist/providers/RequestProvider.js +35 -0
- package/dist/providers/RowsProvider.d.ts +14 -0
- package/dist/providers/RowsProvider.js +31 -0
- package/dist/utils/mathjaxUtils.d.ts +12 -0
- package/dist/utils/mathjaxUtils.js +24 -0
- package/dist/utils/utils.d.ts +31 -0
- package/dist/utils/utils.js +80 -0
- package/package.json +3 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import yaml from "js-yaml";
|
|
2
|
+
import { sanitizeFilename } from "../utils/utils";
|
|
3
|
+
export class YamlFile {
|
|
4
|
+
name;
|
|
5
|
+
content;
|
|
6
|
+
constructor(name, content) {
|
|
7
|
+
this.name = sanitizeFilename(name);
|
|
8
|
+
this.content = content;
|
|
9
|
+
}
|
|
10
|
+
static fromDtt(dtt) {
|
|
11
|
+
const yamlContent = {
|
|
12
|
+
...dtt.config,
|
|
13
|
+
tools: YamlFile.reformatTools(dtt.config.tools),
|
|
14
|
+
branches: Object.fromEntries(Object.entries(dtt.config.branches).map(([branchId, branchConfig]) => [
|
|
15
|
+
branchId,
|
|
16
|
+
{
|
|
17
|
+
...branchConfig,
|
|
18
|
+
tools: YamlFile.reformatTools(branchConfig.tools),
|
|
19
|
+
},
|
|
20
|
+
])),
|
|
21
|
+
groups: Object.fromEntries(Object.entries(dtt.config.groups).map(([groupId, groupConfig]) => [
|
|
22
|
+
groupId,
|
|
23
|
+
{
|
|
24
|
+
...groupConfig,
|
|
25
|
+
tools: YamlFile.reformatTools(groupConfig.tools),
|
|
26
|
+
},
|
|
27
|
+
])),
|
|
28
|
+
};
|
|
29
|
+
return new YamlFile(`${dtt.getName()}.yaml`, yaml.dump(yamlContent));
|
|
30
|
+
}
|
|
31
|
+
static reformatTools(tools) {
|
|
32
|
+
return Object.entries(tools).map(([toolName, toolConfig]) => ({
|
|
33
|
+
[toolName]: Object.fromEntries(Object.entries(toolConfig).map(([paramName, param]) => [paramName, param.value])),
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
static createInfoYaml(rows, metadata) {
|
|
37
|
+
const uniquePathSet = new Set(rows.flatMap((row) => row.paths));
|
|
38
|
+
const uniquePaths = [...uniquePathSet].sort();
|
|
39
|
+
const pathIndex = new Map(uniquePaths.map((p, i) => [p, i]));
|
|
40
|
+
const info = {
|
|
41
|
+
defaults: {
|
|
42
|
+
application: `DaVinci/${metadata.tupleTools.applicationInfo.DaVinci}`,
|
|
43
|
+
wg: "OpenData",
|
|
44
|
+
inform: [],
|
|
45
|
+
automatically_configure: true,
|
|
46
|
+
output: "DVNtuple.root",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
if (!row.dtt)
|
|
51
|
+
continue;
|
|
52
|
+
for (const path of row.paths) {
|
|
53
|
+
const jobID = pathIndex.get(path);
|
|
54
|
+
if (jobID === undefined)
|
|
55
|
+
continue;
|
|
56
|
+
const key = `job${jobID}`;
|
|
57
|
+
const dttFile = row.dtt.getName();
|
|
58
|
+
if (key in info) {
|
|
59
|
+
info[key].options.push(dttFile);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const job = {
|
|
63
|
+
input: { bk_query: path },
|
|
64
|
+
options: [dttFile],
|
|
65
|
+
};
|
|
66
|
+
if (path.includes("MDST")) {
|
|
67
|
+
const stream = row.lines[0]?.stream;
|
|
68
|
+
if (stream) {
|
|
69
|
+
job.root_in_tes = `/Event/${stream}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
info[key] = job;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return new YamlFile("info.yaml", yaml.dump(info));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*****************************************************************************\
|
|
3
|
+
* (c) Copyright 2021 CERN for the benefit of the LHCb Collaboration *
|
|
4
|
+
* *
|
|
5
|
+
* This software is distributed under the terms of the GNU General Public *
|
|
6
|
+
* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". *
|
|
7
|
+
* *
|
|
8
|
+
* In applying this licence, CERN does not waive the privileges and immunities *
|
|
9
|
+
* granted to it by virtue of its status as an Intergovernmental Organization *
|
|
10
|
+
* or submit itself to any jurisdiction. *
|
|
11
|
+
\*****************************************************************************/
|
|
12
|
+
import memoize from "lodash.memoize";
|
|
13
|
+
import { useEffect, useState, useTransition } from "react";
|
|
14
|
+
import { Alert, Badge, Button, Dropdown, DropdownButton, InputGroup, OverlayTrigger, Spinner, Tooltip, } from "react-bootstrap";
|
|
15
|
+
import { Link } from "react-router-dom";
|
|
16
|
+
import Select from "react-select";
|
|
17
|
+
import { useMetadata } from "../providers/MetadataProvider";
|
|
18
|
+
import { DecayList } from "../components/DecayList";
|
|
19
|
+
import { ParticleDropdown } from "../components/ParticleDropdown";
|
|
20
|
+
import { getSelectTagOptions, TagDropdown } from "../components/TagDropdown";
|
|
21
|
+
import { useRows } from "../providers/RowsProvider";
|
|
22
|
+
import { LoadingIndicator } from "../components/LoadingIndicator";
|
|
23
|
+
var MatchType;
|
|
24
|
+
(function (MatchType) {
|
|
25
|
+
MatchType["any"] = "any";
|
|
26
|
+
MatchType["all"] = "all";
|
|
27
|
+
MatchType["none"] = "none";
|
|
28
|
+
})(MatchType || (MatchType = {}));
|
|
29
|
+
var HeadMatchType;
|
|
30
|
+
(function (HeadMatchType) {
|
|
31
|
+
HeadMatchType["exactly"] = "exactly";
|
|
32
|
+
HeadMatchType["category"] = "category";
|
|
33
|
+
})(HeadMatchType || (HeadMatchType = {}));
|
|
34
|
+
export function DecaySearchPage({ basePath }) {
|
|
35
|
+
const metadata = useMetadata();
|
|
36
|
+
const { rows, setRows } = useRows();
|
|
37
|
+
const [selectedContains, setSelectedContains] = useState([]);
|
|
38
|
+
const [selectedHead, setSelectedHead] = useState(null);
|
|
39
|
+
const [selectedHeadTags, setSelectedHeadTags] = useState([]);
|
|
40
|
+
const [selectedDecays, setSelectedDecays] = useState(rows.map((row) => row.decay));
|
|
41
|
+
const [selectedTags, setSelectedTags] = useState(null);
|
|
42
|
+
const [selectedLine, setSelectedLine] = useState(null);
|
|
43
|
+
const [chargeConjugateContains, setChargeConjugateContains] = useState(true);
|
|
44
|
+
const [chargeConjugateHeads, setChargeConjugateHeads] = useState(true);
|
|
45
|
+
const [containsMatchType, setContainsMatchType] = useState(MatchType.all);
|
|
46
|
+
const [headMatchType, setHeadMatchType] = useState(HeadMatchType.exactly);
|
|
47
|
+
const [tagMatchType, setTagMatchType] = useState(MatchType.none);
|
|
48
|
+
const [headTagMatchType, setHeadTagMatchType] = useState(MatchType.all);
|
|
49
|
+
const [onlySelected, setOnlySelected] = useState(false);
|
|
50
|
+
const [showDecaysList, setShowDecaysList] = useState(false);
|
|
51
|
+
const [isLoadingDecaysList, startTransition] = useTransition();
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
startTransition(() => {
|
|
54
|
+
setShowDecaysList(true);
|
|
55
|
+
});
|
|
56
|
+
}, []);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!metadata) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Set default value for selectedTags when metadata is loaded
|
|
62
|
+
setSelectedTags(getSelectTagOptions(metadata.userHints.decayTags, (tag) => tag.hide));
|
|
63
|
+
}, [metadata]);
|
|
64
|
+
if (!metadata) {
|
|
65
|
+
return _jsx(LoadingIndicator, { height: "70vh" });
|
|
66
|
+
}
|
|
67
|
+
const sendData = () => {
|
|
68
|
+
const selectedDecaySet = new Set(selectedDecays.map((d) => d.descriptors.plain));
|
|
69
|
+
// Keep only rows whose decay is still selected
|
|
70
|
+
const selectedRows = rows.filter((row) => selectedDecaySet.has(row.decay.descriptors.plain));
|
|
71
|
+
const existingDecaySet = new Set(selectedRows.map((row) => row.decay.descriptors.plain));
|
|
72
|
+
// Track used row IDs
|
|
73
|
+
const usedIds = new Set(selectedRows.map((row) => row.id));
|
|
74
|
+
let nextId = 0;
|
|
75
|
+
const getNextId = () => {
|
|
76
|
+
while (usedIds.has(nextId))
|
|
77
|
+
nextId++;
|
|
78
|
+
usedIds.add(nextId);
|
|
79
|
+
return nextId;
|
|
80
|
+
};
|
|
81
|
+
// Add missing decays
|
|
82
|
+
for (const decay of selectedDecays) {
|
|
83
|
+
const key = decay.descriptors.plain;
|
|
84
|
+
if (!existingDecaySet.has(key)) {
|
|
85
|
+
selectedRows.push({
|
|
86
|
+
id: getNextId(),
|
|
87
|
+
decay,
|
|
88
|
+
dtt: null,
|
|
89
|
+
lines: [],
|
|
90
|
+
paths: [],
|
|
91
|
+
pathOptions: [],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
setRows(selectedRows);
|
|
96
|
+
};
|
|
97
|
+
const variableMatch = (type, conditions) => {
|
|
98
|
+
switch (type) {
|
|
99
|
+
case MatchType.all:
|
|
100
|
+
return conditions.every((x) => x);
|
|
101
|
+
case MatchType.any:
|
|
102
|
+
return conditions.some((x) => x);
|
|
103
|
+
case MatchType.none:
|
|
104
|
+
return !conditions.some((x) => x);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const allStrippingLines = memoize(() => {
|
|
108
|
+
const allLines = Object.values(metadata.stripping).flatMap((stream) => Object.keys(stream));
|
|
109
|
+
const uniqueLines = new Set(allLines);
|
|
110
|
+
return [...uniqueLines];
|
|
111
|
+
});
|
|
112
|
+
const filterDecays = () => {
|
|
113
|
+
// TODO: can probably do some refactoring in here (cf variableMatch)
|
|
114
|
+
let newDecays = Object.values(metadata.decays);
|
|
115
|
+
if (selectedContains) {
|
|
116
|
+
newDecays = newDecays.filter((decay) => {
|
|
117
|
+
const conditions = selectedContains.map((option) => {
|
|
118
|
+
const particle = option.value;
|
|
119
|
+
const antiparticle = metadata.particleProperties[particle]["antiparticle"];
|
|
120
|
+
return (decay.descriptors.plain.includes(particle) ||
|
|
121
|
+
(chargeConjugateContains && decay.descriptors.plain.includes(antiparticle)));
|
|
122
|
+
});
|
|
123
|
+
return variableMatch(containsMatchType, conditions);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (headMatchType === HeadMatchType.exactly) {
|
|
127
|
+
if (selectedHead) {
|
|
128
|
+
const particle = selectedHead.value;
|
|
129
|
+
const antiparticle = metadata.particleProperties[particle].antiparticle;
|
|
130
|
+
newDecays = newDecays.filter((decay) => {
|
|
131
|
+
const head = decay.descriptors.list[0];
|
|
132
|
+
return head === particle || (chargeConjugateHeads && head === antiparticle);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (headMatchType === HeadMatchType.category) {
|
|
137
|
+
if (selectedHeadTags) {
|
|
138
|
+
newDecays = newDecays.filter((decay) => {
|
|
139
|
+
const conditions = selectedHeadTags.map((option) => {
|
|
140
|
+
const tag = option.value;
|
|
141
|
+
const head = metadata.particleProperties[decay.descriptors.list[0]];
|
|
142
|
+
return head.tags.includes(tag);
|
|
143
|
+
});
|
|
144
|
+
return variableMatch(headTagMatchType, conditions);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (onlySelected) {
|
|
149
|
+
newDecays = newDecays.filter((decay) => selectedDecays
|
|
150
|
+
.map((selectedDecay) => selectedDecay.descriptors.plain)
|
|
151
|
+
.includes(decay.descriptors.plain));
|
|
152
|
+
}
|
|
153
|
+
if (selectedTags) {
|
|
154
|
+
newDecays = newDecays.filter((decay) => {
|
|
155
|
+
const conditions = selectedTags.map((tag) => {
|
|
156
|
+
return decay.tags.includes(tag.value);
|
|
157
|
+
});
|
|
158
|
+
return variableMatch(tagMatchType, conditions);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (selectedLine) {
|
|
162
|
+
const line = selectedLine.value;
|
|
163
|
+
newDecays = newDecays.filter((decay) => {
|
|
164
|
+
const decayStreamLines = Object.keys(decay.lines);
|
|
165
|
+
const decayLines = new Set(decayStreamLines.map((streamLine) => streamLine.split("/")[1]));
|
|
166
|
+
return decayLines.has(line);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return newDecays;
|
|
170
|
+
};
|
|
171
|
+
function DropdownMatchTypes({ options, stateItem, stateSetter }) {
|
|
172
|
+
return (_jsx(_Fragment, { children: options.map((option) => (_jsx(Dropdown.Item, { active: stateItem === option, onClick: () => stateSetter(option), children: option }, option))) }));
|
|
173
|
+
}
|
|
174
|
+
function DropdownChargeConjugate({ stateItem, stateSetter }) {
|
|
175
|
+
return (_jsxs(_Fragment, { children: [_jsx(Dropdown.Item, { active: stateItem, onClick: () => stateSetter(true), children: "Include antiparticles" }), _jsx(Dropdown.Item, { active: !stateItem, onClick: () => stateSetter(false), children: "Exclude antiparticles" })] }));
|
|
176
|
+
}
|
|
177
|
+
const getDecayList = () => {
|
|
178
|
+
if (!selectedTags) {
|
|
179
|
+
return (_jsxs(Alert, { variant: "warning", children: [_jsx(Spinner, { size: "sm", animation: "border" }), " Loading decays..."] }));
|
|
180
|
+
}
|
|
181
|
+
if (!showDecaysList || isLoadingDecaysList) {
|
|
182
|
+
return _jsx(LoadingIndicator, { height: "40vh" });
|
|
183
|
+
}
|
|
184
|
+
const filteredDecays = selectedTags ? filterDecays() : [];
|
|
185
|
+
return (_jsx(DecayList, { decays: filteredDecays, selectedDecays: selectedDecays, onItemSelected: (newSelectedDecays) => {
|
|
186
|
+
setSelectedDecays([...newSelectedDecays]);
|
|
187
|
+
} }, JSON.stringify(filteredDecays)));
|
|
188
|
+
};
|
|
189
|
+
const getExtraInputs = () => {
|
|
190
|
+
return (_jsx(_Fragment, { children: _jsxs(InputGroup, { size: "sm", children: [_jsx(DropdownButton, { title: "Tags (" + tagMatchType + " of):", variant: "outline-secondary", children: _jsx(DropdownMatchTypes, { options: Object.values(MatchType), stateItem: tagMatchType, stateSetter: setTagMatchType }) }), _jsx(TagDropdown, { type: "decayTags", placeholder: "DecayLatex tags", value: selectedTags, onChange: (values) => setSelectedTags([...values]), closeMenuOnSelect: false }), _jsx("div", { className: "react-select form-control p-0", children: _jsx(Select, { placeholder: "Stripping line", isClearable: true, options: metadata
|
|
191
|
+
? allStrippingLines().map((line) => ({ value: line, label: line }))
|
|
192
|
+
: [], onChange: setSelectedLine, isLoading: !metadata }) })] }) }));
|
|
193
|
+
};
|
|
194
|
+
return (_jsxs(_Fragment, { children: [_jsx("h3", { children: "DecayLatex search" }), _jsxs(InputGroup, { size: "sm", children: [_jsxs(DropdownButton, { title: "Head (" +
|
|
195
|
+
(headMatchType === HeadMatchType.exactly ? headMatchType : headTagMatchType + " of") +
|
|
196
|
+
"):", variant: "outline-secondary", children: [_jsx(DropdownMatchTypes, { options: Object.values(HeadMatchType), stateItem: headMatchType, stateSetter: setHeadMatchType }), _jsx(Dropdown.Divider, {}), headMatchType === HeadMatchType.exactly ? (_jsx(DropdownChargeConjugate, { stateItem: chargeConjugateHeads, stateSetter: setChargeConjugateHeads })) : (_jsx(DropdownMatchTypes, { options: Object.values(MatchType), stateItem: headTagMatchType, stateSetter: setHeadTagMatchType }))] }), headMatchType === HeadMatchType.exactly ? (_jsx(ParticleDropdown, { isClearable: true, onlyHeads: true, placeholder: "DecayLatex head", onChange: (value) => setSelectedHead(value) })) : (_jsx(TagDropdown, { type: "particleTags", placeholder: "DecayLatex head", filter: (tag) => !tag.hide, onChange: (values) => setSelectedHeadTags([...values]) })), _jsxs(DropdownButton, { title: "Contains (" + containsMatchType + " of):", variant: "outline-secondary", children: [_jsx(DropdownMatchTypes, { options: Object.values(MatchType), stateItem: containsMatchType, stateSetter: setContainsMatchType }), _jsx(Dropdown.Divider, {}), _jsx(DropdownChargeConjugate, { stateItem: chargeConjugateContains, stateSetter: setChargeConjugateContains })] }), _jsx(ParticleDropdown, { isMulti: true, onlyKnown: true, placeholder: "Particles in decay...", onChange: (values) => setSelectedContains(values), closeMenuOnSelect: false }), _jsx(InputGroup.Text, { children: "Show only selected:" }), _jsx(InputGroup.Checkbox, { onChange: () => setOnlySelected(!onlySelected) })] }), getExtraInputs(), getDecayList(), _jsx(Link, { to: basePath, children: _jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Return to production configuration" }), children: _jsxs(Button, { variant: "success", type: "submit", onClick: sendData, children: ["Select ", _jsx(Badge, { pill: true, bg: "secondary", children: selectedDecays.length })] }) }) })] }));
|
|
197
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*****************************************************************************\
|
|
3
|
+
* (c) Copyright 2021 CERN for the benefit of the LHCb Collaboration *
|
|
4
|
+
* *
|
|
5
|
+
* This software is distributed under the terms of the GNU General Public *
|
|
6
|
+
* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". *
|
|
7
|
+
* *
|
|
8
|
+
* In applying this licence, CERN does not waive the privileges and immunities *
|
|
9
|
+
* granted to it by virtue of its status as an Intergovernmental Organization *
|
|
10
|
+
* or submit itself to any jurisdiction. *
|
|
11
|
+
\*****************************************************************************/
|
|
12
|
+
import { Button, Col, OverlayTrigger, Row, Stack, Tooltip } from "react-bootstrap";
|
|
13
|
+
import { useNavigate } from "react-router-dom";
|
|
14
|
+
import { useMetadata } from "../providers/MetadataProvider";
|
|
15
|
+
import { DecayTreeCard } from "../components/DecayTreeCard";
|
|
16
|
+
import { useRows } from "../providers/RowsProvider";
|
|
17
|
+
import Dtt from "../models/dtt";
|
|
18
|
+
import { DttProvider } from "../providers/DttProvider";
|
|
19
|
+
import { LoadingIndicator } from "../components/LoadingIndicator";
|
|
20
|
+
import { useEffect, useState } from "react";
|
|
21
|
+
export function DecayTreeConfigPage({ basePath }) {
|
|
22
|
+
const navigate = useNavigate();
|
|
23
|
+
const metadata = useMetadata();
|
|
24
|
+
const { rows, setRows } = useRows();
|
|
25
|
+
const [dirtyDtts, setDirtyDtts] = useState(new Set());
|
|
26
|
+
// Listen for when the user is about to leave the page and alert them if there are unsaved changes
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const handleBeforeUnload = (event) => {
|
|
29
|
+
if (dirtyDtts.size > 0) {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
34
|
+
return () => {
|
|
35
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
36
|
+
};
|
|
37
|
+
}, [dirtyDtts]);
|
|
38
|
+
if (!metadata) {
|
|
39
|
+
return _jsx(LoadingIndicator, { height: "70vh" });
|
|
40
|
+
}
|
|
41
|
+
const rowsToEdit = rows.filter((row) => !!row.editTree);
|
|
42
|
+
return (_jsxs(_Fragment, { children: [_jsx("h3", { children: "DecayTreeTuple configuration" }), _jsx(Stack, { gap: 5, children: rowsToEdit.map((row) => (_jsx(Row, { className: "justify-content-lg-center", children: _jsx(Col, { lg: "auto", children: _jsx(DttProvider, { metadata: metadata, decay: row.decay, initialConfig: row.dtt.config, children: _jsx(DecayTreeCard, { onConfigSaved: (newConfig) => {
|
|
43
|
+
setRows((prev) => {
|
|
44
|
+
return prev.map((r) => r.id === row.id ? { ...r, dtt: new Dtt(newConfig, {}) } : r);
|
|
45
|
+
});
|
|
46
|
+
}, onDirtyUpdated: (dttName, dirty) => setDirtyDtts((prev) => {
|
|
47
|
+
if (dirty) {
|
|
48
|
+
return new Set(prev.add(dttName));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const newSet = new Set(prev);
|
|
52
|
+
newSet.delete(dttName);
|
|
53
|
+
return newSet;
|
|
54
|
+
}
|
|
55
|
+
}) }) }) }, row.id) }, row.id))) }), _jsx(OverlayTrigger, { placement: "right", overlay: _jsx(Tooltip, { children: "Return to Stripping line and dataset selection" }), children: _jsx(Button, { className: "mt-2 mb-4", type: "submit", onClick: () => {
|
|
56
|
+
if (dirtyDtts.size > 0) {
|
|
57
|
+
if (!confirm("You have unsaved changes. Are you sure you want to discard them?")) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
void navigate(basePath);
|
|
62
|
+
}, variant: dirtyDtts.size > 0 ? "outline-danger" : "primary", children: dirtyDtts.size > 0 ? "Discard changes" : "Return" }) })] }));
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
interface Props {
|
|
3
|
+
decaysPath: string;
|
|
4
|
+
variablesPath: string;
|
|
5
|
+
submitLocation?: string;
|
|
6
|
+
hideDownloadButtons?: boolean;
|
|
7
|
+
hideUploadButtons?: boolean;
|
|
8
|
+
emailIsKnown?: boolean;
|
|
9
|
+
requestReasonMessage?: string;
|
|
10
|
+
requestSubmittedMessage?: ReactNode;
|
|
11
|
+
csrfToken?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function LinesTablePage({ submitLocation, hideDownloadButtons, hideUploadButtons, emailIsKnown, requestReasonMessage, requestSubmittedMessage, csrfToken, variablesPath, decaysPath, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*****************************************************************************\
|
|
3
|
+
* (c) Copyright 2021-2024 CERN for the benefit of the LHCb Collaboration *
|
|
4
|
+
* *
|
|
5
|
+
* This software is distributed under the terms of the GNU General Public *
|
|
6
|
+
* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". *
|
|
7
|
+
* *
|
|
8
|
+
* In applying this licence, CERN does not waive the privileges and immunities *
|
|
9
|
+
* granted to it by virtue of its status as an Intergovernmental Organization *
|
|
10
|
+
* or submit itself to any jurisdiction. *
|
|
11
|
+
\*****************************************************************************/
|
|
12
|
+
import EmailValidator from "email-validator";
|
|
13
|
+
import yaml from "js-yaml";
|
|
14
|
+
import { useEffect, useMemo, useState } from "react";
|
|
15
|
+
import { Alert, Badge, Button, ButtonGroup, Col, FormControl, InputGroup, OverlayTrigger, Row, Stack, Tooltip, } from "react-bootstrap";
|
|
16
|
+
import { Download, GearWideConnected, PlusLg, Upload } from "react-bootstrap-icons";
|
|
17
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
18
|
+
import { downloadText } from "../utils/utils";
|
|
19
|
+
import { DeleteButton } from "../components/DeleteButton";
|
|
20
|
+
import { useMetadata } from "../providers/MetadataProvider";
|
|
21
|
+
import { ConfigFilesUploadingAlert } from "../components/ConfigFilesUploadingAlert";
|
|
22
|
+
import { useRows } from "../providers/RowsProvider";
|
|
23
|
+
import { useRequest } from "../providers/RequestProvider";
|
|
24
|
+
import { LoadingIndicator } from "../components/LoadingIndicator";
|
|
25
|
+
import { LineTableRow } from "../components/LineTableRow";
|
|
26
|
+
import { useProductionConfig } from "../providers/ProductionConfigProvider";
|
|
27
|
+
import { UploadProdConfigModal } from "../components/modals/UploadProdConfigModal";
|
|
28
|
+
import { SubmitRequestButton } from "../components/SubmitRequestButton";
|
|
29
|
+
import { YamlFile } from "../models/yamlFile";
|
|
30
|
+
export function LinesTablePage({ submitLocation, hideDownloadButtons, hideUploadButtons, emailIsKnown, requestReasonMessage, requestSubmittedMessage, csrfToken, variablesPath, decaysPath, }) {
|
|
31
|
+
const navigate = useNavigate();
|
|
32
|
+
const location = useLocation();
|
|
33
|
+
const metadata = useMetadata();
|
|
34
|
+
const { rows, setRows } = useRows();
|
|
35
|
+
const request = useRequest();
|
|
36
|
+
const { parseProductionFiles } = useProductionConfig();
|
|
37
|
+
const [showProdUploadModal, setShowProdUploadModal] = useState(false);
|
|
38
|
+
const [prodUploadLoading, setProdUploadLoading] = useState(false);
|
|
39
|
+
const [requestSubmitted, setRequestSubmitted] = useState(false);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!metadata) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const urlParams = new URLSearchParams(location.search);
|
|
45
|
+
const cloneParam = urlParams.get("clone");
|
|
46
|
+
if (cloneParam === "1") {
|
|
47
|
+
handleClone().catch(console.error);
|
|
48
|
+
}
|
|
49
|
+
}, [metadata, location.search]);
|
|
50
|
+
async function handleClone() {
|
|
51
|
+
if (!metadata) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const raw = localStorage.getItem("yamlFilesToClone");
|
|
55
|
+
if (!raw) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setProdUploadLoading(true);
|
|
59
|
+
setRows([]);
|
|
60
|
+
try {
|
|
61
|
+
const yamlFiles = JSON.parse(raw);
|
|
62
|
+
for (const file of yamlFiles) {
|
|
63
|
+
if (file.defaults)
|
|
64
|
+
file.name = "info.yaml";
|
|
65
|
+
file.blob = new Blob([yaml.dump(file)]);
|
|
66
|
+
}
|
|
67
|
+
const { rows } = await parseProductionFiles(yamlFiles, metadata);
|
|
68
|
+
setRows(rows);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setProdUploadLoading(false);
|
|
72
|
+
localStorage.removeItem("yamlFilesToClone");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const handleSubmitRows = async () => {
|
|
76
|
+
setRows((prev) => prev.map((r) => ({ ...r, editTree: true })));
|
|
77
|
+
await navigate(variablesPath);
|
|
78
|
+
};
|
|
79
|
+
const updateProductionName = ({ target }) => {
|
|
80
|
+
const safeName = target.value.replaceAll(/[^\w]/g, "");
|
|
81
|
+
request.setProductionName(safeName);
|
|
82
|
+
};
|
|
83
|
+
const updateContactEmails = ({ target }) => {
|
|
84
|
+
const emails = target.value.split(/[\s,]+/).filter((s) => s);
|
|
85
|
+
request.setContactEmails(emails);
|
|
86
|
+
};
|
|
87
|
+
const clearAll = () => {
|
|
88
|
+
request.setProductionName("");
|
|
89
|
+
request.setContactEmails([]);
|
|
90
|
+
request.setReasonForRequest("");
|
|
91
|
+
setRows([]);
|
|
92
|
+
};
|
|
93
|
+
const configuredRows = useMemo(() => rows.filter((r) => !!r.dtt), [rows]);
|
|
94
|
+
const { isEmptySession, isEmailValid, allRowsHaveDtt, allRowsHavePaths, validDttNames } = useMemo(() => {
|
|
95
|
+
const isEmptySession = !request.productionName && request.contactEmails.length === 0 && rows.length === 0;
|
|
96
|
+
const isEmailValid = request.contactEmails.length > 0 && request.contactEmails.every(EmailValidator.validate);
|
|
97
|
+
const allRowsHaveDtt = configuredRows.length > 0 && configuredRows.length === rows.length;
|
|
98
|
+
const allRowsHavePaths = rows.every((r) => r.paths.length > 0);
|
|
99
|
+
const names = configuredRows.map((r) => r.dtt.getName().trim() ?? "");
|
|
100
|
+
const validDttNames = names.every(Boolean) && new Set(names).size === names.length;
|
|
101
|
+
return {
|
|
102
|
+
isEmptySession,
|
|
103
|
+
isEmailValid,
|
|
104
|
+
allRowsHaveDtt,
|
|
105
|
+
allRowsHavePaths,
|
|
106
|
+
validDttNames,
|
|
107
|
+
};
|
|
108
|
+
}, [request, rows, configuredRows]);
|
|
109
|
+
if (!metadata || !request) {
|
|
110
|
+
return _jsx(LoadingIndicator, { height: "70vh" });
|
|
111
|
+
}
|
|
112
|
+
return (_jsxs("div", { className: "d-flex flex-column gap-3", children: [rows.map((row) => (_jsx(LineTableRow, { row: row, hideDownloadButtons: hideDownloadButtons ?? false, hideUploadButtons: hideUploadButtons ?? false, variablesPath: variablesPath }, row.id))), prodUploadLoading && _jsx(ConfigFilesUploadingAlert, {}), requestSubmitted && (_jsx(Row, { className: "mt-3", children: _jsx(Col, { children: _jsxs(Alert, { variant: "success", dismissible: true, onClose: () => setRequestSubmitted(false), children: [_jsx(Alert.Heading, { children: "Request submitted!" }), requestSubmittedMessage] }) }) })), _jsx(Row, { children: _jsx(Col, { xs: "auto", children: _jsxs(ButtonGroup, { children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Select decays" }), children: _jsxs(Button, { type: "submit", variant: "success", onClick: () => void navigate(decaysPath), className: "align-items-center d-flex gap-1", children: [_jsx(PlusLg, {}), " Select decays"] }) }), !hideUploadButtons && (_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, {}) }) })), showProdUploadModal && _jsx(UploadProdConfigModal, { onClose: () => setShowProdUploadModal(false) }), _jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Configure all DecayTreeTuples" }), children: _jsxs(Button, { type: "submit", variant: "secondary", onClick: () => {
|
|
113
|
+
handleSubmitRows().catch(console.error);
|
|
114
|
+
}, disabled: !allRowsHaveDtt, className: "align-items-center d-flex gap-1", children: [_jsx(GearWideConnected, {}), " ", _jsx(Badge, { pill: true, bg: "primary", children: configuredRows.length })] }) }), !hideDownloadButtons && (_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: !allRowsHaveDtt || !allRowsHavePaths || !isEmailValid, className: "align-items-center d-flex", children: _jsx(Download, {}) }) }))] }) }) }), _jsx(Row, { children: _jsxs(Col, { xs: 4, children: [_jsxs(InputGroup, { hasValidation: true, children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Name of this production. This will become the subdirectory in AnalysisProductions" }), children: _jsx(InputGroup.Text, { children: "Production name" }) }), _jsx(FormControl, { value: request.productionName, onChange: updateProductionName, isValid: !!request.productionName, placeholder: "MyAnalysis" })] }), !emailIsKnown && (_jsxs(InputGroup, { hasValidation: true, className: "mt-1", children: [_jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Email addresses to notify (comma-separated)" }), children: _jsx(InputGroup.Text, { children: "Email" }) }), _jsx(FormControl, { value: request.contactEmails.join(", "), onChange: updateContactEmails, isInvalid: !isEmailValid && request.contactEmails.length > 0, placeholder: "name@example.com" }), _jsx(FormControl.Feedback, { type: "invalid", children: "Please enter valid email addresses" })] })), _jsxs(Stack, { direction: "horizontal", gap: 1, className: "mt-3", children: [_jsx(SubmitRequestButton, { disabledMessage: !allRowsHaveDtt
|
|
115
|
+
? "Please configure a DecayTreeTuple for each selected decay before submitting"
|
|
116
|
+
: !allRowsHavePaths
|
|
117
|
+
? "Please select at least one Bookkeeping path for each decay before submitting"
|
|
118
|
+
: !request.productionName
|
|
119
|
+
? "Please enter a production name before submitting"
|
|
120
|
+
: !(emailIsKnown || isEmailValid)
|
|
121
|
+
? "Please enter valid email addresses before submitting"
|
|
122
|
+
: !validDttNames
|
|
123
|
+
? "Please make sure all DecayTreeTuple names are unique before submitting"
|
|
124
|
+
: null, onSubmit: () => setRequestSubmitted(true), submitLocation: submitLocation, hideDownloadButtons: hideDownloadButtons, requestReasonMessage: requestReasonMessage, csrfToken: csrfToken }), _jsx(DeleteButton, { action: clearAll, disabled: isEmptySession, outline: undefined, children: "Clear all" })] })] }) })] }));
|
|
125
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import Dtt, { DttConfig, ToolParamTypeMap } from "../models/dtt";
|
|
3
|
+
import { MetadataContext } from "./MetadataProvider";
|
|
4
|
+
import { DecayData } from "../models/decayData";
|
|
5
|
+
import { TupleTool } from "../models/tupleTool";
|
|
6
|
+
interface DttContextType {
|
|
7
|
+
dtt: Dtt;
|
|
8
|
+
decay: DecayData;
|
|
9
|
+
updateToolParam<K extends keyof ToolParamTypeMap>(this: void, branch: string[], tool: TupleTool, param: string, value: ToolParamTypeMap[K]): void;
|
|
10
|
+
addTool(this: void, branch: string[], tool: TupleTool): void;
|
|
11
|
+
removeTool(this: void, branch: string[], tool: TupleTool): void;
|
|
12
|
+
dirty: boolean;
|
|
13
|
+
save(this: void): void;
|
|
14
|
+
revert(this: void): void;
|
|
15
|
+
}
|
|
16
|
+
interface DttProviderProps {
|
|
17
|
+
initialConfig: DttConfig;
|
|
18
|
+
decay: DecayData;
|
|
19
|
+
metadata: MetadataContext;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
export declare function DttProvider({ initialConfig, decay, metadata, children }: DttProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export declare function useDtt(): DttContextType;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useReducer } from "react";
|
|
3
|
+
import Dtt from "../models/dtt";
|
|
4
|
+
const DttContext = createContext(null);
|
|
5
|
+
function reducer(state, action) {
|
|
6
|
+
switch (action.type) {
|
|
7
|
+
case "INIT":
|
|
8
|
+
return {
|
|
9
|
+
dtt: action.dtt,
|
|
10
|
+
lastSaved: structuredClone(action.config),
|
|
11
|
+
dirty: false,
|
|
12
|
+
};
|
|
13
|
+
case "UPDATE":
|
|
14
|
+
return {
|
|
15
|
+
...state,
|
|
16
|
+
dtt: action.dtt,
|
|
17
|
+
dirty: true,
|
|
18
|
+
};
|
|
19
|
+
case "SAVE":
|
|
20
|
+
return {
|
|
21
|
+
...state,
|
|
22
|
+
lastSaved: structuredClone(state.dtt.config),
|
|
23
|
+
dirty: false,
|
|
24
|
+
};
|
|
25
|
+
case "REVERT":
|
|
26
|
+
return {
|
|
27
|
+
...state,
|
|
28
|
+
dtt: state.dtt.withConfig(state.lastSaved),
|
|
29
|
+
dirty: false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function DttProvider({ initialConfig, decay, metadata, children }) {
|
|
34
|
+
const [state, dispatch] = useReducer(reducer, null, () => ({
|
|
35
|
+
dtt: new Dtt(structuredClone(initialConfig), metadata.tupleTools.tupleTools),
|
|
36
|
+
decay,
|
|
37
|
+
lastSaved: structuredClone(initialConfig),
|
|
38
|
+
dirty: false,
|
|
39
|
+
}));
|
|
40
|
+
const updateToolParam = useCallback((branch, tool, param, value) => {
|
|
41
|
+
const next = state.dtt.withUpdatedToolParam(branch, tool, param, value);
|
|
42
|
+
if (next === state.dtt)
|
|
43
|
+
return;
|
|
44
|
+
dispatch({ type: "UPDATE", dtt: next });
|
|
45
|
+
}, [state.dtt]);
|
|
46
|
+
const addTool = useCallback((branch, tool) => {
|
|
47
|
+
dispatch({
|
|
48
|
+
type: "UPDATE",
|
|
49
|
+
dtt: state.dtt.withAddedTool(tool, branch),
|
|
50
|
+
});
|
|
51
|
+
}, [state.dtt]);
|
|
52
|
+
const removeTool = useCallback((branch, tool) => {
|
|
53
|
+
dispatch({
|
|
54
|
+
type: "UPDATE",
|
|
55
|
+
dtt: state.dtt.withRemovedTool(tool, branch),
|
|
56
|
+
});
|
|
57
|
+
}, [state.dtt]);
|
|
58
|
+
const save = useCallback(() => dispatch({ type: "SAVE" }), []);
|
|
59
|
+
const revert = useCallback(() => dispatch({ type: "REVERT" }), []);
|
|
60
|
+
const value = {
|
|
61
|
+
dtt: state.dtt,
|
|
62
|
+
decay,
|
|
63
|
+
dirty: state.dirty,
|
|
64
|
+
updateToolParam,
|
|
65
|
+
addTool,
|
|
66
|
+
removeTool,
|
|
67
|
+
save,
|
|
68
|
+
revert,
|
|
69
|
+
};
|
|
70
|
+
return _jsx(DttContext.Provider, { value: value, children: children });
|
|
71
|
+
}
|
|
72
|
+
export function useDtt() {
|
|
73
|
+
const ctx = useContext(DttContext);
|
|
74
|
+
if (!ctx)
|
|
75
|
+
throw new Error("useDtt must be used inside DttProvider");
|
|
76
|
+
return ctx;
|
|
77
|
+
}
|