From a2b40eaa7cbe88f6329b4d8350f3d4bd68fad058 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Thu, 30 Nov 2023 18:10:26 +0100 Subject: [PATCH 001/104] Add table of references to edit form --- .../app/author/set/[id]/edit/edit-form.tsx | 18 +++++++- .../author/set/[id]/edit/reference-table.tsx | 43 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 app/src/app/author/set/[id]/edit/reference-table.tsx diff --git a/app/src/app/author/set/[id]/edit/edit-form.tsx b/app/src/app/author/set/[id]/edit/edit-form.tsx index 58fd33ebe..67d3fd3df 100644 --- a/app/src/app/author/set/[id]/edit/edit-form.tsx +++ b/app/src/app/author/set/[id]/edit/edit-form.tsx @@ -5,7 +5,11 @@ "use client"; import { KeyedOrganization } from "@lxcat/database/auth"; -import { EditedLTPDocument, type VersionedLTPDocument } from "@lxcat/schema"; +import { + EditedLTPDocument, + Reference, + type VersionedLTPDocument, +} from "@lxcat/schema"; import { stateJSONSchema } from "@lxcat/schema/json-schema"; import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { @@ -29,6 +33,7 @@ import { FieldErrors, FieldPath, FieldValues, get } from "react-hook-form"; import { z } from "zod"; import { Latex } from "../../../../../shared/latex"; import { generateSpeciesForm, SpeciesForm } from "./form-factory"; +import { ReferenceTable } from "./reference-table"; import { SpeciesNode, SpeciesPicker } from "./species-picker"; const EditFormValues = z.object({ @@ -319,6 +324,17 @@ export const EditForm = ( + + ) => + getInputProps("set.references").onChange( + Object.fromEntries( + references.map((reference) => [reference.id, reference]), + ), + )} + /> + diff --git a/app/src/app/author/set/[id]/edit/reference-table.tsx b/app/src/app/author/set/[id]/edit/reference-table.tsx new file mode 100644 index 000000000..970e2d0d2 --- /dev/null +++ b/app/src/app/author/set/[id]/edit/reference-table.tsx @@ -0,0 +1,43 @@ +import { Reference } from "@lxcat/schema"; +import { ActionIcon, Stack } from "@mantine/core"; +import { IconTrash } from "@tabler/icons-react"; +import { DataTable } from "mantine-datatable"; +import Latex from "react-latex-next"; +import { reference2bibliography } from "../../../../../shared/cite"; +// import { Latex } from "../../../../../shared/Latex"; + +export const ReferenceTable = ( + { references, onChange }: { + references: Array; + onChange: (refs: Array) => void; + }, +) => { + return ( + + {reference2bibliography(record)}, + }, { + accessor: "actions", + title: "", + textAlign: "right", + render: (_, index) => ( + + onChange( + references.filter((_, curIndex) => index !== curIndex), + )} + > + + + ), + }]} + records={references} + /> + + ); +}; From 353c3efc63234218d818576885b463c47582c0c3 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Fri, 1 Dec 2023 11:28:18 +0100 Subject: [PATCH 002/104] Make references clickable --- app/src/app/author/set/[id]/edit/reference-table.tsx | 5 ++--- app/src/shared/reference.tsx | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/reference-table.tsx b/app/src/app/author/set/[id]/edit/reference-table.tsx index 970e2d0d2..9204898df 100644 --- a/app/src/app/author/set/[id]/edit/reference-table.tsx +++ b/app/src/app/author/set/[id]/edit/reference-table.tsx @@ -2,8 +2,7 @@ import { Reference } from "@lxcat/schema"; import { ActionIcon, Stack } from "@mantine/core"; import { IconTrash } from "@tabler/icons-react"; import { DataTable } from "mantine-datatable"; -import Latex from "react-latex-next"; -import { reference2bibliography } from "../../../../../shared/cite"; +import { Reference as ReferenceComponent } from "../../../../../shared/Reference"; // import { Latex } from "../../../../../shared/Latex"; export const ReferenceTable = ( @@ -18,7 +17,7 @@ export const ReferenceTable = ( striped columns={[{ accessor: "reference", - render: (record) => {reference2bibliography(record)}, + render: (record) => , }, { accessor: "actions", title: "", diff --git a/app/src/shared/reference.tsx b/app/src/shared/reference.tsx index 4d1d2df0c..1a030b1af 100644 --- a/app/src/shared/reference.tsx +++ b/app/src/shared/reference.tsx @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +"use client"; + import { useMemo } from "react"; import "katex/dist/katex.min.css"; From e6addf55dbcbf94577b9cbdcacc95b91beedc631 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Fri, 1 Dec 2023 13:56:31 +0100 Subject: [PATCH 003/104] Add functionality to import a reference from its DOI --- .../author/set/[id]/edit/reference-table.tsx | 122 ++++++++++++++---- 1 file changed, 94 insertions(+), 28 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/reference-table.tsx b/app/src/app/author/set/[id]/edit/reference-table.tsx index 9204898df..f41cce2ca 100644 --- a/app/src/app/author/set/[id]/edit/reference-table.tsx +++ b/app/src/app/author/set/[id]/edit/reference-table.tsx @@ -1,9 +1,15 @@ +"use client"; + import { Reference } from "@lxcat/schema"; -import { ActionIcon, Stack } from "@mantine/core"; +import { ActionIcon, Button, Modal, Stack, TextInput } from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; import { IconTrash } from "@tabler/icons-react"; import { DataTable } from "mantine-datatable"; +import { useState } from "react"; +import Result, { err, ok } from "true-myth/result"; +import { getReferenceLabel } from "../../../../../shared/cite"; +import { doi2csl } from "../../../../../shared/doi2csl"; import { Reference as ReferenceComponent } from "../../../../../shared/Reference"; -// import { Latex } from "../../../../../shared/Latex"; export const ReferenceTable = ( { references, onChange }: { @@ -11,32 +17,92 @@ export const ReferenceTable = ( onChange: (refs: Array) => void; }, ) => { + const [doiOpened, { open: doiOpen, close: doiClose }] = useDisclosure(false); + const [doiError, setDoiError] = useState(); + const [doiValue, setDoiValue] = useState(""); + + const importFromDoi = async ( + doi: string, + ): Promise> => { + try { + const csl = await doi2csl(doi); + return ok(csl); + } catch (_) { + return err("Invalid DOI"); + } + }; + return ( - - , - }, { - accessor: "actions", - title: "", - textAlign: "right", - render: (_, index) => ( - - onChange( - references.filter((_, curIndex) => index !== curIndex), - )} - > - - - ), - }]} - records={references} - /> - + <> + { + doiClose(); + setDoiError(undefined); + }} + title="Import from DOI" + centered + size="auto" + > + + setDoiValue(event.currentTarget.value)} + /> + + + + + , + }, { + accessor: "actions", + title: "", + textAlign: "right", + render: (_, index) => ( + + onChange( + references.filter((_, curIndex) => index !== curIndex), + )} + > + + + ), + }]} + records={references} + /> + + + + + + ); }; From 224f6394378da064830d73a1bdef5d4cb860d328 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Fri, 1 Dec 2023 14:59:42 +0100 Subject: [PATCH 004/104] Move species tab component to separate module Set correct state meta for states that are picked from the database. --- .../app/author/set/[id]/edit/edit-form.tsx | 113 ++---------- .../app/author/set/[id]/edit/species-tab.tsx | 168 ++++++++++++++++++ 2 files changed, 179 insertions(+), 102 deletions(-) create mode 100644 app/src/app/author/set/[id]/edit/species-tab.tsx diff --git a/app/src/app/author/set/[id]/edit/edit-form.tsx b/app/src/app/author/set/[id]/edit/edit-form.tsx index 67d3fd3df..2b21bd39f 100644 --- a/app/src/app/author/set/[id]/edit/edit-form.tsx +++ b/app/src/app/author/set/[id]/edit/edit-form.tsx @@ -35,6 +35,7 @@ import { Latex } from "../../../../../shared/latex"; import { generateSpeciesForm, SpeciesForm } from "./form-factory"; import { ReferenceTable } from "./reference-table"; import { SpeciesNode, SpeciesPicker } from "./species-picker"; +import { SpeciesTab } from "./species-tab"; const EditFormValues = z.object({ set: EditedLTPDocument, @@ -179,14 +180,6 @@ export const EditForm = ( }, ); const [activeTab, setActiveTab] = useState("general"); - const [ - speciesPickerOpened, - { open: openSpeciesPicker, close: closeSpeciesPicker }, - ] = useDisclosure(false); - const [selectedSpecies, setSelectedSpecies] = useState>( - [], - ); - const { getInputProps } = form; return ( @@ -204,7 +197,7 @@ export const EditForm = ( > General - States + Species References Processes JSON @@ -230,99 +223,15 @@ export const EditForm = ( /> - - - - {getInputProps(`set.states`).value - && Object.entries(form.values.set.states).map( - ([key, state]) => { - const parsed = AnySpeciesSerializable.safeParse(state); - const controlNode = parsed.success - ? parsed.data.serialize().latex - : "..."; - - return ( - - - {controlNode} - - - - - - ); - }, - )} - - - - - - - - - - - - + + { + form.setFieldValue("set.states", species); + form.setFieldValue("set.meta", meta); + }} + /> { + const speciesMeta = { + electronic: { + anyOf: "0", + vibrational: { anyOf: "0", rotational: { anyOf: "0" } }, + }, + }; + + if (species.type !== "simple" && species.type !== "unspecified") { + if (Array.isArray(species.electronic)) { + speciesMeta.electronic.anyOf = "1"; + } else if ( + "vibrational" in species.electronic + && species.electronic.vibrational + ) { + if (Array.isArray(species.electronic.vibrational)) { + speciesMeta.electronic.vibrational.anyOf = "1"; + } else if ( + typeof species.electronic.vibrational === "string" + ) { + speciesMeta.electronic.vibrational.anyOf = "2"; + } else if ("rotational" in species.electronic.vibrational) { + if ( + Array.isArray(species.electronic.vibrational.rotational) + ) { + speciesMeta.electronic.vibrational.rotational.anyOf = "1"; + } else if ( + typeof species.electronic.vibrational.rotational + === "string" + ) { + speciesMeta.electronic.vibrational.rotational.anyOf = "2"; + } + } + } + } + + return speciesMeta; +}; + +export const SpeciesTab = ( + { species, meta, onChange }: { + species: PartialKeyedDocument["states"]; + meta: Record; + onChange: ( + species: PartialKeyedDocument["states"], + meta: Record, + ) => MaybePromise; + }, +) => { + const [ + speciesPickerOpened, + { open: openSpeciesPicker, close: closeSpeciesPicker }, + ] = useDisclosure(false); + const [selectedSpecies, setSelectedSpecies] = useState>( + [], + ); + + return ( + + + {Object.entries(species).map( + ([key, state]) => { + const parsed = AnySpeciesSerializable.safeParse(state); + const controlNode = parsed.success + ? parsed.data.serialize().latex + : "..."; + + return ( + + + {`$${controlNode}$`} + + + + + + ); + }, + )} + + + + + + + + + + + + + ); +}; From c9219772f9af2d1c0575e226e138705eabee65cd Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Fri, 1 Dec 2023 16:05:54 +0100 Subject: [PATCH 005/104] Start developing the process tab Top-level accordion with latex reaction descriptions is implemented. --- .../app/author/set/[id]/edit/edit-form.tsx | 19 ++-- .../app/author/set/[id]/edit/process-tab.tsx | 86 +++++++++++++++++++ .../author/set/[id]/edit/reference-table.tsx | 2 +- 3 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 app/src/app/author/set/[id]/edit/process-tab.tsx diff --git a/app/src/app/author/set/[id]/edit/edit-form.tsx b/app/src/app/author/set/[id]/edit/edit-form.tsx index 2b21bd39f..a4b096e97 100644 --- a/app/src/app/author/set/[id]/edit/edit-form.tsx +++ b/app/src/app/author/set/[id]/edit/edit-form.tsx @@ -10,13 +10,9 @@ import { Reference, type VersionedLTPDocument, } from "@lxcat/schema"; -import { stateJSONSchema } from "@lxcat/schema/json-schema"; -import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { - Accordion, Button, Checkbox, - Modal, NativeSelect, Space, Stack, @@ -25,16 +21,11 @@ import { TextInput, } from "@mantine/core"; import { createFormContext, zodResolver } from "@mantine/form"; -import { useDisclosure } from "@mantine/hooks"; -import { JSONSchema7 } from "json-schema"; -import { nanoid } from "nanoid"; import { useState } from "react"; import { FieldErrors, FieldPath, FieldValues, get } from "react-hook-form"; import { z } from "zod"; -import { Latex } from "../../../../../shared/latex"; -import { generateSpeciesForm, SpeciesForm } from "./form-factory"; +import { ProcessTab } from "./process-tab"; import { ReferenceTable } from "./reference-table"; -import { SpeciesNode, SpeciesPicker } from "./species-picker"; import { SpeciesTab } from "./species-tab"; const EditFormValues = z.object({ @@ -244,6 +235,14 @@ export const EditForm = ( )} /> + + {}} + /> + diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx new file mode 100644 index 000000000..b0a57c908 --- /dev/null +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { type PartialKeyedDocument } from "@lxcat/database/schema"; +import { AnySpeciesSerializable } from "@lxcat/schema/species"; +import { Accordion, MultiSelect } from "@mantine/core"; +import { useMemo } from "react"; +import Latex from "react-latex-next"; +import { reactionAsLatex } from "../../../../../cs/reaction"; +import { reference2bibliography } from "../../../../../shared/cite"; +import { MaybePromise } from "../../../../api/util"; + +const resolveReactionSpecies = ( + reaction: PartialKeyedDocument["processes"][number]["reaction"], + species: PartialKeyedDocument["states"], +) => ({ + ...reaction, + lhs: reaction.lhs.map(({ count, state }) => ({ + count, + state: { + detailed: species[state], + serialized: AnySpeciesSerializable.parse(species[state]).serialize(), + }, + })), + rhs: reaction.rhs.map(({ count, state }) => ({ + count, + state: { + detailed: species[state], + serialized: AnySpeciesSerializable.parse(species[state]).serialize(), + }, + })), +}); + +const ProcessItem = ( + { process, species, references }: { + process: PartialKeyedDocument["processes"][number]; + species: PartialKeyedDocument["states"]; + references: PartialKeyedDocument["references"]; + }, +) => { + const latex = useMemo( + () => reactionAsLatex(resolveReactionSpecies(process.reaction, species)), + [process, species], + ); + + return ( + + + {`$${latex}$`} + + + { + // TODO: References are linked to an info object, not to a reaction. + } + ({ + value: key, + label: reference2bibliography(reference), + }))} + /> + + + ); +}; + +export const ProcessTab = ( + { processes, species, references, onChange }: { + processes: PartialKeyedDocument["processes"]; + species: PartialKeyedDocument["states"]; + references: PartialKeyedDocument["references"]; + onChange: ( + processes: PartialKeyedDocument["processes"], + ) => MaybePromise; + }, +) => { + return ( + + {processes.map((process) => ( + + ))} + + ); +}; diff --git a/app/src/app/author/set/[id]/edit/reference-table.tsx b/app/src/app/author/set/[id]/edit/reference-table.tsx index f41cce2ca..1684ae2dc 100644 --- a/app/src/app/author/set/[id]/edit/reference-table.tsx +++ b/app/src/app/author/set/[id]/edit/reference-table.tsx @@ -9,7 +9,7 @@ import { useState } from "react"; import Result, { err, ok } from "true-myth/result"; import { getReferenceLabel } from "../../../../../shared/cite"; import { doi2csl } from "../../../../../shared/doi2csl"; -import { Reference as ReferenceComponent } from "../../../../../shared/Reference"; +import { Reference as ReferenceComponent } from "../../../../../shared/reference"; export const ReferenceTable = ( { references, onChange }: { From 55e9ded953f1ab8f2220902f97c80504fb1f71e3 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Mon, 5 Feb 2024 11:13:15 +0100 Subject: [PATCH 006/104] Use the new url for the set edit page --- app/src/pages/author/scat-css/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/pages/author/scat-css/index.tsx b/app/src/pages/author/scat-css/index.tsx index b872c1843..d128b2d66 100644 --- a/app/src/pages/author/scat-css/index.tsx +++ b/app/src/pages/author/scat-css/index.tsx @@ -68,7 +68,7 @@ const Admin: NextPage = ({ items: initialItems, user }) => { <> {user.roles?.includes("author") && ( <> - + @@ -102,7 +102,7 @@ const Admin: NextPage = ({ items: initialItems, user }) => { {user.roles?.includes("author") && ( <> - + From 90f470b3810beaf230ee592d87f0ed845a824747 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Mon, 12 Feb 2024 17:59:53 +0100 Subject: [PATCH 007/104] Add `ProcessInfoItem` component `ProcessItem` now correctly splits `info` into several `ProcessInfoItem` components. Add `onChange` functions. Adding and removing process references now works. --- .../app/author/set/[id]/edit/edit-form.tsx | 3 +- .../app/author/set/[id]/edit/process-tab.tsx | 99 +++++++++++++------ 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/edit-form.tsx b/app/src/app/author/set/[id]/edit/edit-form.tsx index a4b096e97..3c254c8cb 100644 --- a/app/src/app/author/set/[id]/edit/edit-form.tsx +++ b/app/src/app/author/set/[id]/edit/edit-form.tsx @@ -240,7 +240,8 @@ export const EditForm = ( processes={form.values.set.processes} species={form.values.set.states} references={form.values.set.references} - onChange={(processes) => {}} + onChange={(processes) => + form.setFieldValue("set.processes", processes)} /> diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index b0a57c908..d3b98370c 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -1,13 +1,16 @@ "use client"; +import { MaybePromise } from "@/app/api/util"; +import { reactionAsLatex } from "@/cs/reaction"; +import { reference2bibliography } from "@/shared/cite"; import { type PartialKeyedDocument } from "@lxcat/database/schema"; import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { Accordion, MultiSelect } from "@mantine/core"; import { useMemo } from "react"; import Latex from "react-latex-next"; -import { reactionAsLatex } from "../../../../../cs/reaction"; -import { reference2bibliography } from "../../../../../shared/cite"; -import { MaybePromise } from "../../../../api/util"; + +type Process = PartialKeyedDocument["processes"][number]; +type ProcessInfo = Process["info"][number]; const resolveReactionSpecies = ( reaction: PartialKeyedDocument["processes"][number]["reaction"], @@ -30,33 +33,61 @@ const resolveReactionSpecies = ( })), }); -const ProcessItem = ( - { process, species, references }: { - process: PartialKeyedDocument["processes"][number]; - species: PartialKeyedDocument["states"]; +const ProcessInfoItem = ( + { info, references, onChange }: { + info: ProcessInfo; references: PartialKeyedDocument["references"]; + onChange: (info: ProcessInfo) => MaybePromise; }, ) => { - const latex = useMemo( - () => reactionAsLatex(resolveReactionSpecies(process.reaction, species)), - [process, species], + const referenceMap = useMemo(() => + Object.fromEntries( + Object.entries(references).map(([ + key, + value, + ]) => [key, reference2bibliography(value)]), + ), [references]); + + return ( + ({ + value: key, + label: referenceMap[key], + }))} + // TODO: Use a component that allows for adding reference comments. + value={info.references.map(ref => typeof ref === "object" ? ref.id : ref)} + onChange={(references) => onChange({ ...info, references })} + /> ); +}; +const ProcessItem = ( + { process, species, references, reactionLatex, onChange }: { + process: Process; + species: PartialKeyedDocument["states"]; + references: PartialKeyedDocument["references"]; + reactionLatex: string; + onChange: (process: Process) => MaybePromise; + }, +) => { return ( - + - {`$${latex}$`} + {`$${reactionLatex}$`} - { - // TODO: References are linked to an info object, not to a reaction. - } - ({ - value: key, - label: reference2bibliography(reference), - }))} - /> + {process.info.map((info, index) => ( + { + process.info[index] = info; + onChange(process); + }} + /> + ))} ); @@ -74,13 +105,25 @@ export const ProcessTab = ( ) => { return ( - {processes.map((process) => ( - - ))} + {processes.map((process, index) => { + const latex = reactionAsLatex( + resolveReactionSpecies(process.reaction, species), + ); + + return ( + { + processes[index] = process; + onChange(processes); + }} + /> + ); + })} ); }; From a9e4b6502bdfd27fad835c50afcae61b65e1d826 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Tue, 13 Feb 2024 12:05:34 +0100 Subject: [PATCH 008/104] Filter reference comments based on available references --- app/src/app/author/set/[id]/edit/process-tab.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index d3b98370c..dcf7237a6 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -48,6 +48,15 @@ const ProcessInfoItem = ( ]) => [key, reference2bibliography(value)]), ), [references]); + // Filters out removed references. + const filteredRefs = info.references.filter((ref) => + typeof ref === "object" ? ref.id : ref in references + ); + + if (filteredRefs.length < info.references.length) { + onChange({ ...info, references: filteredRefs }); + } + return ( Date: Tue, 13 Feb 2024 12:07:03 +0100 Subject: [PATCH 009/104] Add missing license headers --- app/src/app/author/set/[id]/edit/process-tab.tsx | 4 ++++ app/src/app/author/set/[id]/edit/reference-table.tsx | 4 ++++ app/src/app/author/set/[id]/edit/species-tab.tsx | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index dcf7237a6..8338bfd3a 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: LXCat team +// +// SPDX-License-Identifier: AGPL-3.0-or-later + "use client"; import { MaybePromise } from "@/app/api/util"; diff --git a/app/src/app/author/set/[id]/edit/reference-table.tsx b/app/src/app/author/set/[id]/edit/reference-table.tsx index 1684ae2dc..606429445 100644 --- a/app/src/app/author/set/[id]/edit/reference-table.tsx +++ b/app/src/app/author/set/[id]/edit/reference-table.tsx @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: LXCat team +// +// SPDX-License-Identifier: AGPL-3.0-or-later + "use client"; import { Reference } from "@lxcat/schema"; diff --git a/app/src/app/author/set/[id]/edit/species-tab.tsx b/app/src/app/author/set/[id]/edit/species-tab.tsx index 7aa435360..888d8b647 100644 --- a/app/src/app/author/set/[id]/edit/species-tab.tsx +++ b/app/src/app/author/set/[id]/edit/species-tab.tsx @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: LXCat team +// +// SPDX-License-Identifier: AGPL-3.0-or-later + import { PartialKeyedDocument } from "@lxcat/database/schema"; import { stateJSONSchema } from "@lxcat/schema/json-schema"; import { type AnySpecies, AnySpeciesSerializable } from "@lxcat/schema/species"; From c058ec627447c51fb519e8073692070d62606300 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Tue, 13 Feb 2024 12:07:57 +0100 Subject: [PATCH 010/104] Add `@mantine/code-highlight` dependency --- app/package.json | 1 + app/src/app/layout.tsx | 1 + pnpm-lock.yaml | 27 +++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/package.json b/app/package.json index 6aaffb994..50cb3d6a9 100644 --- a/app/package.json +++ b/app/package.json @@ -29,6 +29,7 @@ "@lxcat/converter": "workspace:^", "@lxcat/database": "workspace:^", "@lxcat/schema": "workspace:^", + "@mantine/code-highlight": "^7.10.1", "@mantine/core": "^7.10.1", "@mantine/form": "^7.10.1", "@mantine/hooks": "^7.10.1", diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx index fb5cb6e86..102864d9f 100644 --- a/app/src/app/layout.tsx +++ b/app/src/app/layout.tsx @@ -3,6 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import "@mantine/core/styles.css"; +import "@mantine/code-highlight/styles.css"; import "mantine-datatable/styles.css"; import "../styles/globals.css"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8be5935c9..e0465cc4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -43,6 +47,9 @@ importers: '@lxcat/schema': specifier: workspace:^ version: link:../packages/schema + '@mantine/code-highlight': + specifier: ^7.10.1 + version: 7.10.1(@mantine/core@7.10.1)(@mantine/hooks@7.10.1)(react-dom@18.3.1)(react@18.3.1) '@mantine/core': specifier: ^7.10.1 version: 7.10.1(@mantine/hooks@7.10.1)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) @@ -3060,6 +3067,22 @@ packages: - supports-color dev: true + /@mantine/code-highlight@7.10.1(@mantine/core@7.10.1)(@mantine/hooks@7.10.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-ZeqBnd/i6CNF8avmjgYNqo9hKFnrzYoKV13OrKAHNRZk7vdbBGoSeVYF1vq+ChDBOZQfLOW+2naTi3VdzwLOhg==} + peerDependencies: + '@mantine/core': 7.10.1 + '@mantine/hooks': 7.10.1 + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + '@mantine/core': 7.10.1(@mantine/hooks@7.10.1)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@mantine/hooks': 7.10.1(react@18.3.1) + clsx: 2.1.1 + highlight.js: 11.9.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@mantine/core@7.10.1(@mantine/hooks@7.10.1)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-l9ypojKN3PjwO1CSLIsqxi7mA25+7w+xc71Q+JuCCREI0tuGwkZsKbIOpuTATIJOjPh8ycLiW7QxX1LYsRTq6w==} peerDependencies: @@ -14566,7 +14589,3 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false From 54b449b8f38d8f1bb8dc64aae7fcc9fe4346ddb3 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Tue, 13 Feb 2024 12:08:44 +0100 Subject: [PATCH 011/104] Add rendered view of JSON document in set edit form --- app/src/app/author/set/[id]/edit/edit-form.tsx | 4 ++++ .../app/author/set/[id]/edit/json-tab.module.css | 9 +++++++++ app/src/app/author/set/[id]/edit/json-tab.tsx | 14 ++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 app/src/app/author/set/[id]/edit/json-tab.module.css create mode 100644 app/src/app/author/set/[id]/edit/json-tab.tsx diff --git a/app/src/app/author/set/[id]/edit/edit-form.tsx b/app/src/app/author/set/[id]/edit/edit-form.tsx index 3c254c8cb..bcb813f85 100644 --- a/app/src/app/author/set/[id]/edit/edit-form.tsx +++ b/app/src/app/author/set/[id]/edit/edit-form.tsx @@ -24,6 +24,7 @@ import { createFormContext, zodResolver } from "@mantine/form"; import { useState } from "react"; import { FieldErrors, FieldPath, FieldValues, get } from "react-hook-form"; import { z } from "zod"; +import { JsonTab } from "./json-tab"; import { ProcessTab } from "./process-tab"; import { ReferenceTable } from "./reference-table"; import { SpeciesTab } from "./species-tab"; @@ -244,6 +245,9 @@ export const EditForm = ( form.setFieldValue("set.processes", processes)} /> + + + diff --git a/app/src/app/author/set/[id]/edit/json-tab.module.css b/app/src/app/author/set/[id]/edit/json-tab.module.css new file mode 100644 index 000000000..af3a847a0 --- /dev/null +++ b/app/src/app/author/set/[id]/edit/json-tab.module.css @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: LXCat team + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +.pre { + max-height: calc(100vh - 280px); +} diff --git a/app/src/app/author/set/[id]/edit/json-tab.tsx b/app/src/app/author/set/[id]/edit/json-tab.tsx new file mode 100644 index 000000000..614c4219c --- /dev/null +++ b/app/src/app/author/set/[id]/edit/json-tab.tsx @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: LXCat team +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { CodeHighlight } from "@mantine/code-highlight"; +import classes from "./json-tab.module.css"; + +export const JsonTab = ({ json }: { json: string }) => ( + +); From 700a6832d2cc440ab02140d6d1bee2da8ecd3d3e Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Tue, 13 Feb 2024 12:21:22 +0100 Subject: [PATCH 012/104] Use `mathrm` in `unspecified` species latex electronic descriptor --- packages/database/src/cs/queries/select-cs.spec.ts | 4 ++-- packages/schema/src/species/species.test.ts | 4 ++-- packages/schema/src/species/unspecified/serialize.ts | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/database/src/cs/queries/select-cs.spec.ts b/packages/database/src/cs/queries/select-cs.spec.ts index 69778e252..7b58ed860 100644 --- a/packages/database/src/cs/queries/select-cs.spec.ts +++ b/packages/database/src/cs/queries/select-cs.spec.ts @@ -91,7 +91,7 @@ describe("Selecting individual cross sections", () => { children: [ { children: [], - latex: "*", + latex: "\\mathrm{*}", valid: true, }, ], @@ -229,7 +229,7 @@ describe("Selecting individual cross sections", () => { { children: [], latex: "\\mathrm{N2}", valid: true }, { children: [], latex: "\\mathrm{Ar}^+", valid: true }, { - children: [{ children: [], latex: "*", valid: true }], + children: [{ children: [], latex: "\\mathrm{*}", valid: true }], latex: "\\mathrm{He}", valid: false, }, diff --git a/packages/schema/src/species/species.test.ts b/packages/schema/src/species/species.test.ts index 837becb88..96d8bb50b 100644 --- a/packages/schema/src/species/species.test.ts +++ b/packages/schema/src/species/species.test.ts @@ -32,10 +32,10 @@ describe("State serialization", () => { particle: "Ar", charge: 0, summary: "Ar{*}", - latex: "\\mathrm{Ar}\\left(*\\right)", + latex: "\\mathrm{Ar}\\left(\\mathrm{*}\\right)", electronic: { summary: "*", - latex: "*", + latex: "\\mathrm{*}", }, }, ], diff --git a/packages/schema/src/species/unspecified/serialize.ts b/packages/schema/src/species/unspecified/serialize.ts index 1addc99c4..738d814c6 100644 --- a/packages/schema/src/species/unspecified/serialize.ts +++ b/packages/schema/src/species/unspecified/serialize.ts @@ -8,17 +8,15 @@ import { type Unspecified } from "./unspecified.js"; export const serializeUnspecified = (state: Unspecified): StateSummary => { const serialized = serializeSimpleParticle(state); + const latex = `\\mathrm{${state.electronic}}`; serialized.summary += "{"; serialized.latex += "\\left("; - serialized.electronic = { - summary: state.electronic, - latex: state.electronic, - }; + serialized.electronic = { summary: state.electronic, latex }; serialized.summary += state.electronic; - serialized.latex += state.electronic; + serialized.latex += latex; serialized.summary += "}"; serialized.latex += "\\right)"; From bbde5d99895dd134fc55bc9114655336363fcc26 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Tue, 13 Feb 2024 12:53:49 +0100 Subject: [PATCH 013/104] Make species and process lists scrollable on overflow --- .../set/[id]/edit/process-tab.module.css | 9 +++ .../app/author/set/[id]/edit/process-tab.tsx | 45 ++++++++------- .../set/[id]/edit/species-tab.module.css | 9 +++ .../app/author/set/[id]/edit/species-tab.tsx | 57 ++++++++++--------- 4 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 app/src/app/author/set/[id]/edit/process-tab.module.css create mode 100644 app/src/app/author/set/[id]/edit/species-tab.module.css diff --git a/app/src/app/author/set/[id]/edit/process-tab.module.css b/app/src/app/author/set/[id]/edit/process-tab.module.css new file mode 100644 index 000000000..0540c2d53 --- /dev/null +++ b/app/src/app/author/set/[id]/edit/process-tab.module.css @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: LXCat team + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +.processList { + max-height: calc(100vh - 250px); +} diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index 8338bfd3a..6d66fc3a3 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -9,9 +9,10 @@ import { reactionAsLatex } from "@/cs/reaction"; import { reference2bibliography } from "@/shared/cite"; import { type PartialKeyedDocument } from "@lxcat/database/schema"; import { AnySpeciesSerializable } from "@lxcat/schema/species"; -import { Accordion, MultiSelect } from "@mantine/core"; +import { Accordion, MultiSelect, ScrollArea } from "@mantine/core"; import { useMemo } from "react"; import Latex from "react-latex-next"; +import classes from "./process-tab.module.css"; type Process = PartialKeyedDocument["processes"][number]; type ProcessInfo = Process["info"][number]; @@ -117,26 +118,28 @@ export const ProcessTab = ( }, ) => { return ( - - {processes.map((process, index) => { - const latex = reactionAsLatex( - resolveReactionSpecies(process.reaction, species), - ); + + + {processes.map((process, index) => { + const latex = reactionAsLatex( + resolveReactionSpecies(process.reaction, species), + ); - return ( - { - processes[index] = process; - onChange(processes); - }} - /> - ); - })} - + return ( + { + processes[index] = process; + onChange(processes); + }} + /> + ); + })} + + ); }; diff --git a/app/src/app/author/set/[id]/edit/species-tab.module.css b/app/src/app/author/set/[id]/edit/species-tab.module.css new file mode 100644 index 000000000..7133edcbb --- /dev/null +++ b/app/src/app/author/set/[id]/edit/species-tab.module.css @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: LXCat team + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +.speciesList { + max-height: calc(100vh - 300px); +} diff --git a/app/src/app/author/set/[id]/edit/species-tab.tsx b/app/src/app/author/set/[id]/edit/species-tab.tsx index 888d8b647..1602d604c 100644 --- a/app/src/app/author/set/[id]/edit/species-tab.tsx +++ b/app/src/app/author/set/[id]/edit/species-tab.tsx @@ -5,7 +5,7 @@ import { PartialKeyedDocument } from "@lxcat/database/schema"; import { stateJSONSchema } from "@lxcat/schema/json-schema"; import { type AnySpecies, AnySpeciesSerializable } from "@lxcat/schema/species"; -import { Accordion, Button, Modal, Stack } from "@mantine/core"; +import { Accordion, Button, Modal, ScrollArea, Stack } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { JSONSchema7 } from "json-schema"; import { nanoid } from "nanoid"; @@ -14,6 +14,7 @@ import Latex from "react-latex-next"; import { MaybePromise } from "../../../../api/util"; import { generateSpeciesForm, SpeciesForm } from "./form-factory"; import { SpeciesNode, SpeciesPicker } from "./species-picker"; +import classes from "./species-tab.module.css"; const getSpeciesMeta = (species: AnySpecies) => { const speciesMeta = { @@ -74,33 +75,35 @@ export const SpeciesTab = ( return ( - - {Object.entries(species).map( - ([key, state]) => { - const parsed = AnySpeciesSerializable.safeParse(state); - const controlNode = parsed.success - ? parsed.data.serialize().latex - : "..."; + + + {Object.entries(species).map( + ([key, state]) => { + const parsed = AnySpeciesSerializable.safeParse(state); + const controlNode = parsed.success + ? parsed.data.serialize().latex + : "..."; - return ( - - - {`$${controlNode}$`} - - - - - - ); - }, - )} - + return ( + + + {`$${controlNode}$`} + + + + + + ); + }, + )} + + + +); + const ReactionBuilder = ( { reaction, species, onChange }: { reaction: Process["reaction"]; @@ -91,13 +152,27 @@ const ReactionBuilder = ( onChange: (reaction: Process["reaction"]) => MaybePromise; }, ) => ( - + +
+ onChange({ ...reaction, lhs })} + /> +
onChange({ ...reaction, reversible: value === "true" })} /> +
+ onChange({ ...reaction, rhs })} + /> +
); From e991fc1dc85b511c1c8c78dbafb62244e19088a7 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 14 Feb 2024 12:04:22 +0100 Subject: [PATCH 020/104] Add `MultiSelect to edit type tags --- .../app/author/set/[id]/edit/process-tab.tsx | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index 687a9f3da..14ef62702 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -8,12 +8,13 @@ import { MaybePromise } from "@/app/api/util"; import { reference2bibliography } from "@/shared/cite"; import { LatexSelect } from "@/shared/latex-select"; import { type PartialKeyedDocument } from "@lxcat/database/schema"; -import { ReactionEntry } from "@lxcat/schema/process"; +import { ReactionEntry, ReactionTypeTag } from "@lxcat/schema/process"; import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { Accordion, ActionIcon, Button, + Center, Fieldset, Group, MultiSelect, @@ -152,28 +153,40 @@ const ReactionBuilder = ( onChange: (reaction: Process["reaction"]) => MaybePromise; }, ) => ( - -
- onChange({ ...reaction, lhs })} + + +
+ onChange({ ...reaction, lhs })} + /> +
+ + onChange({ ...reaction, reversible: value === "true" })} /> -
- - onChange({ ...reaction, reversible: value === "true" })} - /> -
- onChange({ ...reaction, rhs })} +
+ onChange({ ...reaction, rhs })} + /> +
+ +
+ + onChange({ ...reaction, typeTags: tags as Array })} + style={{ minWidth: "300px", maxWidth: "500px" }} /> -
-
+ +
); const ProcessItem = ( From 6f12d777a7a7c94f7a4073bc1931069c18614a52 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 14 Feb 2024 12:08:20 +0100 Subject: [PATCH 021/104] Move `key` to parent component --- app/src/app/author/set/[id]/edit/process-tab.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index 14ef62702..2f96092b2 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -99,7 +99,7 @@ const SpeciesBuilder = ( {entries.map((entry, index) => { return ( - + { From e0815a16e5c857d25b05de8ad0f204420f580b7c Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 14 Feb 2024 13:29:25 +0100 Subject: [PATCH 022/104] Only export a single component See . --- app/src/app/author/set/[id]/edit/process-tab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index 2f96092b2..da75472ec 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -40,7 +40,7 @@ function entryAsLatex( return `${entry.count}${speciesMap[entry.state]}`; } -export function reactionAsLatex( +function reactionAsLatex( reaction: Process["reaction"], speciesMap: Record, ) { From 43a02977f91bec16265c479b029de458fbdd1b61 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 14 Feb 2024 13:36:32 +0100 Subject: [PATCH 023/104] Move the `ReactionBuilder` component to a separate module --- .../app/author/set/[id]/edit/process-tab.tsx | 109 +--------------- .../author/set/[id]/edit/reaction-builder.tsx | 116 ++++++++++++++++++ 2 files changed, 118 insertions(+), 107 deletions(-) create mode 100644 app/src/app/author/set/[id]/edit/reaction-builder.tsx diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index da75472ec..63bebf3f5 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -6,26 +6,20 @@ import { MaybePromise } from "@/app/api/util"; import { reference2bibliography } from "@/shared/cite"; -import { LatexSelect } from "@/shared/latex-select"; import { type PartialKeyedDocument } from "@lxcat/database/schema"; -import { ReactionEntry, ReactionTypeTag } from "@lxcat/schema/process"; +import { ReactionEntry } from "@lxcat/schema/process"; import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { Accordion, - ActionIcon, - Button, - Center, Fieldset, - Group, MultiSelect, - NumberInput, ScrollArea, Stack, } from "@mantine/core"; -import { IconTrash } from "@tabler/icons-react"; import { useMemo } from "react"; import Latex from "react-latex-next"; import classes from "./process-tab.module.css"; +import { ReactionBuilder } from "./reaction-builder"; type Process = PartialKeyedDocument["processes"][number]; type ProcessInfo = Process["info"][number]; @@ -89,105 +83,6 @@ const ProcessInfoItem = ( ); }; -const SpeciesBuilder = ( - { entries, species, onChange }: { - entries: Process["reaction"]["lhs"]; - species: Record; - onChange: (entries: Process["reaction"]["lhs"]) => MaybePromise; - }, -) => ( - - {entries.map((entry, index) => { - return ( - - { - const newEntries = [...entries]; - newEntries[index].count = count as number; - return onChange(newEntries); - }} - /> - { - const newEntries = [...entries]; - newEntries[index].state = speciesKey!; - return onChange(newEntries); - }} - grow - /> - - onChange(entries.filter((_, curIndex) => curIndex !== index))} - > - - - - ); - })} - - -); - -const ReactionBuilder = ( - { reaction, species, onChange }: { - reaction: Process["reaction"]; - species: Record; - onChange: (reaction: Process["reaction"]) => MaybePromise; - }, -) => ( - - -
- onChange({ ...reaction, lhs })} - /> -
- - onChange({ ...reaction, reversible: value === "true" })} - /> -
- onChange({ ...reaction, rhs })} - /> -
-
-
- - onChange({ ...reaction, typeTags: tags as Array })} - style={{ minWidth: "300px", maxWidth: "500px" }} - /> -
-
-); - const ProcessItem = ( { process, species, references, onChange, itemValue }: { process: Process; diff --git a/app/src/app/author/set/[id]/edit/reaction-builder.tsx b/app/src/app/author/set/[id]/edit/reaction-builder.tsx new file mode 100644 index 000000000..28c5e2aed --- /dev/null +++ b/app/src/app/author/set/[id]/edit/reaction-builder.tsx @@ -0,0 +1,116 @@ +import { MaybePromise } from "@/app/api/util"; +import { LatexSelect } from "@/shared/latex-select"; +import { PartialKeyedDocument } from "@lxcat/database/schema"; +import { ReactionTypeTag } from "@lxcat/schema/process"; +import { + ActionIcon, + Button, + Center, + Fieldset, + Group, + MultiSelect, + NumberInput, + Stack, +} from "@mantine/core"; +import { IconTrash } from "@tabler/icons-react"; + +type Process = PartialKeyedDocument["processes"][number]; + +const SpeciesBuilder = ( + { entries, species, onChange }: { + entries: Process["reaction"]["lhs"]; + species: Record; + onChange: (entries: Process["reaction"]["lhs"]) => MaybePromise; + }, +) => ( + + {entries.map((entry, index) => { + return ( + + { + const newEntries = [...entries]; + newEntries[index].count = count as number; + return onChange(newEntries); + }} + /> + { + const newEntries = [...entries]; + newEntries[index].state = speciesKey!; + return onChange(newEntries); + }} + grow + /> + + onChange(entries.filter((_, curIndex) => curIndex !== index))} + > + + + + ); + })} + + +); + +export const ReactionBuilder = ( + { reaction, species, onChange }: { + reaction: Process["reaction"]; + species: Record; + onChange: (reaction: Process["reaction"]) => MaybePromise; + }, +) => ( + + +
+ onChange({ ...reaction, lhs })} + /> +
+ + onChange({ ...reaction, reversible: value === "true" })} + /> +
+ onChange({ ...reaction, rhs })} + /> +
+
+
+ + onChange({ ...reaction, typeTags: tags as Array })} + style={{ minWidth: "300px", maxWidth: "500px" }} + /> +
+
+); From f5ace4d5bbc64ec165b46f1a854fbf26aabb0f2b Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 14 Feb 2024 14:45:25 +0100 Subject: [PATCH 024/104] Present info objects in an accordion Treat info object type and comments. --- .../app/author/set/[id]/edit/process-tab.tsx | 130 +++++++++++++++--- 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index 63bebf3f5..27b433eed 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -11,11 +11,19 @@ import { ReactionEntry } from "@lxcat/schema/process"; import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { Accordion, + ActionIcon, + Button, + Center, Fieldset, + Group, MultiSelect, ScrollArea, + Select, + Space, Stack, + TextInput, } from "@mantine/core"; +import { IconTrash } from "@tabler/icons-react"; import { useMemo } from "react"; import Latex from "react-latex-next"; import classes from "./process-tab.module.css"; @@ -53,8 +61,60 @@ function reactionAsLatex( return `${lhs} ${arrow} ${rhs}`; } +const CommentSection = ( + { comments, onChange }: { + comments: Array | undefined; + onChange: (comments: Array | undefined) => MaybePromise; + }, +) => ( + + {comments?.map((comment, index) => { + return ( + + { + const newComments = [...comments]; + newComments[index] = event.currentTarget.value; + return onChange(newComments); + }} + /> + + onChange( + comments + ? comments.filter((_, curIndex) => curIndex !== index) + : undefined, + )} + > + + + + ); + })} +
+ +
+
+); + +const typeLabelMap = { "CrossSection": "Cross section" }; +const typeSelectData = Object + .entries(typeLabelMap) + .map(([value, label]) => ({ value, label })); + const ProcessInfoItem = ( - { info, references, onChange }: { + { id, info, references, onChange }: { + id: string; info: ProcessInfo; references: Record; onChange: (info: ProcessInfo) => MaybePromise; @@ -70,16 +130,39 @@ const ProcessInfoItem = ( } return ( - ({ - value, - label, - }))} - // TODO: Use a component that allows for adding reference comments. - value={info.references.map(ref => typeof ref === "object" ? ref.id : ref)} - onChange={(references) => onChange({ ...info, references })} - /> + + + {typeLabelMap[info.type]} + + + + +
+); + const CommentSection = ( { comments, onChange }: { comments: Array | undefined; @@ -135,32 +160,38 @@ const ProcessInfoItem = ( {typeLabelMap[info.type]} - - - ({ - value, - label, - }))} - // TODO: Use a component that allows for adding reference comments. - value={info.references.map(ref => - typeof ref === "object" ? ref.id : ref - )} - onChange={(references) => onChange({ ...info, references })} - /> -
-
+
+ onChange({ ...info, comments })} + /> +
+ ({ + value, + label, + }))} + // TODO: Use a component that allows for adding reference comments. + value={info.references.map(ref => + typeof ref === "object" ? ref.id : ref + )} + onChange={(references) => onChange({ ...info, references })} + /> +
+ onChange({ ...info, data })} + /> +
+
); From e39efbd0f4bc890a2cfd396371c9de58ed6be17c Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 14 Feb 2024 17:56:42 +0100 Subject: [PATCH 029/104] Add missing license headers --- app/src/app/author/set/[id]/edit/lookup-table.module.css | 6 ++++++ app/src/app/author/set/[id]/edit/lookup-table.tsx | 4 ++++ app/src/app/author/set/[id]/edit/reaction-builder.tsx | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/app/src/app/author/set/[id]/edit/lookup-table.module.css b/app/src/app/author/set/[id]/edit/lookup-table.module.css index 3c2d635d6..c45763843 100644 --- a/app/src/app/author/set/[id]/edit/lookup-table.module.css +++ b/app/src/app/author/set/[id]/edit/lookup-table.module.css @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: LXCat team + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + .viewport { max-height: 500px; } diff --git a/app/src/app/author/set/[id]/edit/lookup-table.tsx b/app/src/app/author/set/[id]/edit/lookup-table.tsx index 4cc67bf07..ba2e17586 100644 --- a/app/src/app/author/set/[id]/edit/lookup-table.tsx +++ b/app/src/app/author/set/[id]/edit/lookup-table.tsx @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: LXCat team +// +// SPDX-License-Identifier: AGPL-3.0-or-later + import { MaybePromise } from "@/app/api/util"; import { ScientificInput } from "@/shared/scientific-input"; import { LUT } from "@lxcat/schema/data-types"; diff --git a/app/src/app/author/set/[id]/edit/reaction-builder.tsx b/app/src/app/author/set/[id]/edit/reaction-builder.tsx index 28c5e2aed..5867d6809 100644 --- a/app/src/app/author/set/[id]/edit/reaction-builder.tsx +++ b/app/src/app/author/set/[id]/edit/reaction-builder.tsx @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: LXCat team +// +// SPDX-License-Identifier: AGPL-3.0-or-later + import { MaybePromise } from "@/app/api/util"; import { LatexSelect } from "@/shared/latex-select"; import { PartialKeyedDocument } from "@lxcat/database/schema"; From 8c89f2fb6bd04b17144a7981ffe8dbd94946e6c5 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 14 Feb 2024 20:03:13 +0100 Subject: [PATCH 030/104] Increase scroll bar thumb z-index Such that it isn't hidden behind the table header. --- app/src/app/author/set/[id]/edit/lookup-table.module.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/app/author/set/[id]/edit/lookup-table.module.css b/app/src/app/author/set/[id]/edit/lookup-table.module.css index c45763843..262206d67 100644 --- a/app/src/app/author/set/[id]/edit/lookup-table.module.css +++ b/app/src/app/author/set/[id]/edit/lookup-table.module.css @@ -7,3 +7,7 @@ .viewport { max-height: 500px; } + +.thumb { + z-index: 10; +} From fee16d2a2e4670d9737e27aa4a043a0b70b7ee51 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Fri, 16 Feb 2024 14:17:41 +0100 Subject: [PATCH 031/104] Make button to add reaction entries smaller --- .../author/set/[id]/edit/reaction-builder.tsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/reaction-builder.tsx b/app/src/app/author/set/[id]/edit/reaction-builder.tsx index 5867d6809..275bc4801 100644 --- a/app/src/app/author/set/[id]/edit/reaction-builder.tsx +++ b/app/src/app/author/set/[id]/edit/reaction-builder.tsx @@ -64,15 +64,18 @@ const SpeciesBuilder = ( ); })} - +
+ +
); From 059897c6f36a86ff46f2f9a1836211904035709d Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Fri, 16 Feb 2024 14:18:11 +0100 Subject: [PATCH 032/104] Implement addition and removal of info objects --- .../app/author/set/[id]/edit/process-tab.tsx | 99 ++++++++++++++----- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index bd1fd7541..7e308bb73 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -7,7 +7,8 @@ import { MaybePromise } from "@/app/api/util"; import { reference2bibliography } from "@/shared/cite"; import { type PartialKeyedDocument } from "@lxcat/database/schema"; -import { ReactionEntry } from "@lxcat/schema/process"; +import { CrossSectionInfo, ReactionEntry } from "@lxcat/schema/process"; +import { ReferenceRef } from "@lxcat/schema/reference"; import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { Accordion, @@ -23,7 +24,8 @@ import { TextInput, } from "@mantine/core"; import { IconTrash } from "@tabler/icons-react"; -import { useMemo } from "react"; +import { nanoid } from "nanoid"; +import { useMemo, useState } from "react"; import Latex from "react-latex-next"; import { LookupTable } from "./lookup-table"; import classes from "./process-tab.module.css"; @@ -138,11 +140,12 @@ const typeSelectData = Object .map(([value, label]) => ({ value, label })); const ProcessInfoItem = ( - { id, info, references, onChange }: { + { id, info, references, onChange, onDelete }: { id: string; info: ProcessInfo; references: Record; onChange: (info: ProcessInfo) => MaybePromise; + onDelete: () => MaybePromise; }, ) => { // Filters out removed references. @@ -156,9 +159,19 @@ const ProcessInfoItem = ( return ( - - {typeLabelMap[info.type]} - +
+ + {typeLabelMap[info.type]} + + + + +
({ value, label }))} + {...getInputProps("set.publishedIn")} + /> name)} From 4539ddaec49c759a628da6f1a42795dc83e5c30c Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Fri, 5 Apr 2024 17:56:40 +0200 Subject: [PATCH 066/104] Correctly treat DOI as optional --- packages/database/src/shared/queries.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/database/src/shared/queries.ts b/packages/database/src/shared/queries.ts index 16f9cefe1..b62f6632e 100644 --- a/packages/database/src/shared/queries.ts +++ b/packages/database/src/shared/queries.ts @@ -236,7 +236,9 @@ export async function insertReferenceDict( const id_dict: Record = {}; for (const [id, reference] of Object.entries(references)) { - const key = await this.getReferenceKeyByDOI(reference.DOI); + // TODO: Either ensure DOI is always there or add more machinery to compare + // references. + const key = reference.DOI && await this.getReferenceKeyByDOI(reference.DOI); if (key) { id_dict[id] = `Reference/${key}`; From 1e0cbf2c1c2de568f6b7967990f73749298a4aac Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Mon, 8 Apr 2024 16:58:07 +0200 Subject: [PATCH 067/104] Fix `updateDraftSet` `cs.info[0]._key` was explicitly deleted before using it in an `insertEdge` call. This is fixed by using `prevCSKey`, which is equivalent. --- packages/database/src/css/queries/author-write.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/database/src/css/queries/author-write.ts b/packages/database/src/css/queries/author-write.ts index 640f4721f..1f1e808c2 100644 --- a/packages/database/src/css/queries/author-write.ts +++ b/packages/database/src/css/queries/author-write.ts @@ -268,7 +268,7 @@ export async function updateDraftSet( // Make cross sections part of set by adding to IsPartOf collection await this.insertEdge( "IsPartOf", - `CrossSection/${cs.info[0]._key}`, + `CrossSection/${prevCSKey}`, `CrossSectionSet/${key}`, ); } else { From db71e3189745de9457b52e7edb72fafa096899af Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Mon, 8 Apr 2024 16:59:55 +0200 Subject: [PATCH 068/104] Fix remaining `@lxcat/database` tests --- .../database/src/cs/queries/public.spec.ts | 2 +- .../src/css/queries/delete-set.spec.ts | 31 ++++++++++++------- .../src/css/queries/update-set.spec.ts | 3 -- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/database/src/cs/queries/public.spec.ts b/packages/database/src/cs/queries/public.spec.ts index 9f13d4cfa..196bdc5b0 100644 --- a/packages/database/src/cs/queries/public.spec.ts +++ b/packages/database/src/cs/queries/public.spec.ts @@ -326,7 +326,7 @@ describe("given 4 published cross sections in 2 sets", () => { }); it("given 1 good and 1 bad ids should return 1 good cross sections", async () => { - const result = await db.getMixtureByIds([csids[0], "bad2"]); + const result = await db.getMixtureByIds([csids[1], "bad2"]); const expected: LTPMixture = { states: { "524": { diff --git a/packages/database/src/css/queries/delete-set.spec.ts b/packages/database/src/css/queries/delete-set.spec.ts index 7e3d664e5..cc5833fa9 100644 --- a/packages/database/src/css/queries/delete-set.spec.ts +++ b/packages/database/src/css/queries/delete-set.spec.ts @@ -4,15 +4,15 @@ import { beforeAll, describe, expect, it } from "vitest"; -import { EditedLTPDocument, Status, VersionInfo } from "@lxcat/schema"; +import { + EditedLTPDocument, + Status, + VersionedLTPDocument, + VersionInfo, +} from "@lxcat/schema"; import { systemDb } from "../../system-db.js"; import { LXCatTestDatabase } from "../../testutils.js"; -import { - CrossSectionSetItem, - FilterOptions, - KeyedSet, - SortOptions, -} from "../public.js"; +import { FilterOptions, KeyedSet, SortOptions } from "../public.js"; import { KeyedVersionInfo } from "./public.js"; import { matches8601, @@ -180,18 +180,25 @@ describe("deleting a published cross section without shared cross sections", () it("should have retrievable by id", async () => { const result = await db.getSetById(keycss1); - const expected: Omit = { - id: keycss1, - complete: false, - description: "Some description", + const expected: VersionedLTPDocument = { + _key: keycss1, name: "Some name", - contributor: "Some organization", // TODO should have organization or contributor not both + description: "Some description", + contributor: { + name: "Some organization", + description: "Description of some organization.", + contact: "info@some-org.com", + howToReference: "", + }, + complete: false, versionInfo: { createdOn: matches8601, status: "retracted", retractMessage: "My retract message", version: 1, }, + references: expect.any(Object), + states: expect.any(Object), processes: expect.any(Array), }; expect(result).toEqual(expected); diff --git a/packages/database/src/css/queries/update-set.spec.ts b/packages/database/src/css/queries/update-set.spec.ts index 367d1eea0..d4d1312ce 100644 --- a/packages/database/src/css/queries/update-set.spec.ts +++ b/packages/database/src/css/queries/update-set.spec.ts @@ -1020,9 +1020,6 @@ describe("given draft cross section set where its non cross section data is alte version: 1, status: "draft", createdOn: matches8601, - commitMessage: expect.stringContaining( - "Indirect draft by editing set", - ), }, type: "CrossSection", isPartOf: [ From ed3ab95551f2d86c0f019dea03c9bb40606874e4 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Mon, 8 Apr 2024 17:21:05 +0200 Subject: [PATCH 069/104] Remove unused definitions --- packages/database/src/cs/collections.ts | 13 ------------- packages/database/src/cs/index.ts | 2 +- packages/database/src/cs/public.ts | 13 +------------ packages/database/src/css/public.ts | 11 ----------- packages/database/src/setup/cs.ts | 1 - packages/database/src/setup/shared.ts | 1 - packages/database/src/setup/users.ts | 1 - 7 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 packages/database/src/cs/collections.ts diff --git a/packages/database/src/cs/collections.ts b/packages/database/src/cs/collections.ts deleted file mode 100644 index 01a8d6faa..000000000 --- a/packages/database/src/cs/collections.ts +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: LXCat team -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -import { VersionInfo } from "@lxcat/schema"; -import { type CrossSectionInfo } from "@lxcat/schema/process"; - -export type CrossSection = { - versionInfo: VersionInfo; - organization: string; // A key in Organization collection - reaction: string; // A key in Reaction collection - info: Omit, "references">; -}; diff --git a/packages/database/src/cs/index.ts b/packages/database/src/cs/index.ts index 33735b05a..f21059d23 100644 --- a/packages/database/src/cs/index.ts +++ b/packages/database/src/cs/index.ts @@ -2,4 +2,4 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -export type { CrossSectionHeading, CrossSectionItem } from "./public.js"; +export type { CrossSectionHeading } from "./public.js"; diff --git a/packages/database/src/cs/public.ts b/packages/database/src/cs/public.ts index 740710bc4..2bb51f692 100644 --- a/packages/database/src/cs/public.ts +++ b/packages/database/src/cs/public.ts @@ -2,10 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -import type { Reference, SelfReference } from "@lxcat/schema"; +import type { Reference } from "@lxcat/schema"; import { type Reaction } from "@lxcat/schema/process"; import { SerializedSpecies } from "@lxcat/schema/species"; -import { CrossSection } from "../cs/collections.js"; import { CrossSectionSet } from "../css/collections.js"; export interface CrossSectionHeading { @@ -18,13 +17,3 @@ export interface CrossSectionHeading { reference: Reference[]; // TODO add CrossSection.threshold? Is it useful when searching for a section? } - -export type CrossSectionItem = - & { - id: string; - isPartOf: Array; - reaction: Reaction; - reference: Reference[]; - } - & Omit - & SelfReference; diff --git a/packages/database/src/css/public.ts b/packages/database/src/css/public.ts index 494d18194..21b976a2c 100644 --- a/packages/database/src/css/public.ts +++ b/packages/database/src/css/public.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import { ReactionTypeTag } from "@lxcat/schema/process"; -import { CrossSectionItem } from "../cs/public.js"; import { StateChoices } from "../shared/queries/state.js"; import { CrossSectionSet } from "./collections.js"; @@ -12,16 +11,6 @@ export interface CrossSectionSetHeading { name: string; } -export type OrphanedCrossSectionItem = Omit; - -export interface CrossSectionSetItem extends CrossSectionSet { - id: string; - processes: OrphanedCrossSectionItem[]; - // contributor string is stored in Organization db collection and CrossSectionSet collection has foreign key to it. - // in current lxcat is called a database - contributor: string; -} - export type KeyedSet = { _key: string } & CrossSectionSet; export interface FilterOptions { diff --git a/packages/database/src/setup/cs.ts b/packages/database/src/setup/cs.ts index e84949997..c8d2aed70 100644 --- a/packages/database/src/setup/cs.ts +++ b/packages/database/src/setup/cs.ts @@ -14,7 +14,6 @@ export const setupCrossSectionCollections = async (db: Database) => { await createCrossSectionHistoryCollection(db); await createCrossSectionSetHistoryCollection(db); }; -export default setupCrossSectionCollections; const createCrossSectionSetCollection = async (db: Database) => { const collection = db.collection("CrossSectionSet"); diff --git a/packages/database/src/setup/shared.ts b/packages/database/src/setup/shared.ts index fe4982c6f..e617d1417 100644 --- a/packages/database/src/setup/shared.ts +++ b/packages/database/src/setup/shared.ts @@ -14,7 +14,6 @@ export const setupSharedCollections = async (db: Database) => { await createReactionCollection(db); await createEdgeCollections(db); }; -export default setupSharedCollections; const createParticleCollection = async (db: Database) => { const collection = db.collection("Particle"); diff --git a/packages/database/src/setup/users.ts b/packages/database/src/setup/users.ts index 51d57b883..57ff2f909 100644 --- a/packages/database/src/setup/users.ts +++ b/packages/database/src/setup/users.ts @@ -31,7 +31,6 @@ export const setupUserCollections = async ( return createMemberOfCollection(db); }; -export default setupUserCollections; const createUserCollection = async ( db: Database, From 71dfde2d51c9990a4bb25b115397bb749d3cbe4f Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Mon, 8 Apr 2024 17:27:04 +0200 Subject: [PATCH 070/104] Fix type check using `@ts-nocheck` on soon-to-be deprecated code --- app/src/cs/picker.tsx | 2 ++ app/src/pages/author/scat-cs/index.tsx | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/app/src/cs/picker.tsx b/app/src/cs/picker.tsx index b5ae9bfc9..65f1d0749 100644 --- a/app/src/cs/picker.tsx +++ b/app/src/cs/picker.tsx @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +// @ts-nocheck + import { CrossSectionItem } from "@lxcat/database/item"; import { Button, Checkbox, Group, Space } from "@mantine/core"; import { useState } from "react"; diff --git a/app/src/pages/author/scat-cs/index.tsx b/app/src/pages/author/scat-cs/index.tsx index e309accfd..f2fc4fae0 100644 --- a/app/src/pages/author/scat-cs/index.tsx +++ b/app/src/pages/author/scat-cs/index.tsx @@ -2,6 +2,11 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later +// FIXME: This code is not yet updated to use the latest info object +// definitions. + +// @ts-nocheck + import { db } from "@lxcat/database"; import { CrossSectionItem } from "@lxcat/database/item"; import { PagingOptions } from "@lxcat/database/shared"; From 7785e1039b3689c06659de674973c645d306459c Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Wed, 17 Apr 2024 10:06:22 +0200 Subject: [PATCH 071/104] Add submit message on success --- app/src/app/author/set/[id]/edit/edit-form.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/edit-form.tsx b/app/src/app/author/set/[id]/edit/edit-form.tsx index 5876f1d3f..ef1e1aee4 100644 --- a/app/src/app/author/set/[id]/edit/edit-form.tsx +++ b/app/src/app/author/set/[id]/edit/edit-form.tsx @@ -11,11 +11,13 @@ import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { Button, Checkbox, + Group, NativeSelect, Select, Space, Stack, Tabs, + Text, Textarea, TextInput, } from "@mantine/core"; @@ -61,6 +63,8 @@ export const EditForm = ( string | null >(null); + const [submitMessage, setSubmitMessage] = useState(); + const speciesMap = useMemo( () => Object.fromEntries( @@ -102,6 +106,7 @@ export const EditForm = ( && typeof data.id === "string" ) { form.setFieldValue("set._key", data.id); + setSubmitMessage(`Saved set with id ${data.id}.`); window.history.pushState(null, "", `/author/set/${data.id}/edit`); } // TODO: Handle user feedback. @@ -241,9 +246,12 @@ export const EditForm = ( placeholder="Describe which changes have been made." {...getInputProps("commitMessage")} /> -
+ -
+ + {submitMessage} + +
From 8262a34ec17dbff48834e8968f367db086ce0768 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Thu, 2 May 2024 15:15:46 +0200 Subject: [PATCH 072/104] Implement copy CSV from file and clipboard --- .../app/author/set/[id]/edit/lookup-table.tsx | 334 ++++++++++-------- .../app/author/set/[id]/edit/process-tab.tsx | 25 +- 2 files changed, 216 insertions(+), 143 deletions(-) diff --git a/app/src/app/author/set/[id]/edit/lookup-table.tsx b/app/src/app/author/set/[id]/edit/lookup-table.tsx index ba2e17586..5083f8556 100644 --- a/app/src/app/author/set/[id]/edit/lookup-table.tsx +++ b/app/src/app/author/set/[id]/edit/lookup-table.tsx @@ -5,8 +5,20 @@ import { MaybePromise } from "@/app/api/util"; import { ScientificInput } from "@/shared/scientific-input"; import { LUT } from "@lxcat/schema/data-types"; -import { ActionIcon, Group, ScrollArea, Table, TextInput } from "@mantine/core"; import { + ActionIcon, + Button, + Center, + FileButton, + Group, + ScrollArea, + Stack, + Table, + TextInput, +} from "@mantine/core"; +import { + IconCsv, + IconFileTypeCsv, IconRowInsertBottom, IconRowInsertTop, IconTrash, @@ -26,147 +38,193 @@ export const LookupTable = ( // removes entries in `data.values`. const [ids, setIds] = useState(data.values.map((_) => nanoid())); + async function loadCSV(file: File | null) { + if (file === null) return; + + return loadCSVString(await file.text()); + } + + async function loadCSVString(csvText: string) { + const values = csvText.split(/\r?\n/).filter((line) => line.length > 0).map( + (line): [number, number] => { + const cols = line.split(/[,\t]/).map(Number); + return [cols[0], cols[1]]; + }, + ); + + setIds(() => values.map((_) => nanoid())); + return onChange({ ...data, values }); + } + + async function loadCSVFromClipboard() { + // NOTE: The `navigator.clipboard` API and is not yet supported in all + // common browsers (e.g. Firefox at the time of writing). + const text = await navigator.clipboard.readText(); + console.log(text); + return loadCSVString(text); + } + return ( - - - - - - - - onChange({ - ...data, - labels: [event.currentTarget.value, data.labels[1]], - })} - /> - - onChange({ - ...data, - units: [event.currentTarget.value, data.units[1]], - })} - /> - - - - - - onChange({ - ...data, - labels: [data.labels[0], event.currentTarget.value], - })} - /> - - onChange({ - ...data, - labels: [data.units[0], event.currentTarget.value], - })} - /> - - - Controls - - - - {data.values.map(([x, y], index) => ( - - - { - const values = [...data.values]; - values[index] = [value as number, y]; - return onChange({ ...data, values }); - }} - /> - - - { - const values = [...data.values]; - values[index] = [x, value as number]; - return onChange({ ...data, values }); - }} - /> - - - - { - setIds((ids) => - ids.flatMap((id, curIndex) => - curIndex === index ? [nanoid(), id] : [id] - ) - ); - return onChange({ + + +
+ + + + + + onChange({ ...data, - values: data.values.flatMap((value, curIndex) => - curIndex === index ? [[0, 0], value] : [value] - ), - }); - }} - > - - - { - setIds((ids) => - ids.flatMap((id, curIndex) => - curIndex === index ? [id, nanoid()] : [id] - ) - ); - return onChange({ + labels: [event.currentTarget.value, data.labels[1]], + })} + /> + + onChange({ ...data, - values: data.values.flatMap((value, curIndex) => - curIndex === index ? [value, [0, 0]] : [value] - ), - }); - }} - > - - - { - setIds((ids) => - ids.filter((_, curIndex) => curIndex !== index) - ); - return onChange({ + units: [event.currentTarget.value, data.units[1]], + })} + /> + + + + + + onChange({ ...data, - values: data.values.filter((_, curIndex) => - curIndex !== index - ), - }); - }} - > - - - - + labels: [data.labels[0], event.currentTarget.value], + })} + /> + + onChange({ + ...data, + labels: [data.units[0], event.currentTarget.value], + })} + /> + + + Controls - ))} - -
-
+ + + {data.values.map(([x, y], index) => ( + + + { + const values = [...data.values]; + values[index] = [value as number, y]; + return onChange({ ...data, values }); + }} + /> + + + { + const values = [...data.values]; + values[index] = [x, value as number]; + return onChange({ ...data, values }); + }} + /> + + + + { + setIds((ids) => + ids.flatMap((id, curIndex) => + curIndex === index ? [nanoid(), id] : [id] + ) + ); + return onChange({ + ...data, + values: data.values.flatMap((value, curIndex) => + curIndex === index ? [[0, 0], value] : [value] + ), + }); + }} + > + + + { + setIds((ids) => + ids.flatMap((id, curIndex) => + curIndex === index ? [id, nanoid()] : [id] + ) + ); + return onChange({ + ...data, + values: data.values.flatMap((value, curIndex) => + curIndex === index ? [value, [0, 0]] : [value] + ), + }); + }} + > + + + { + setIds((ids) => + ids.filter((_, curIndex) => curIndex !== index) + ); + return onChange({ + ...data, + values: data.values.filter((_, curIndex) => + curIndex !== index + ), + }); + }} + > + + + + + + ))} + + + +
+ + + {(props) => ( + + )} + + + +
+ ); }; diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index 90760b691..fee6fb35b 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -15,6 +15,7 @@ import { Button, Center, Fieldset, + FileButton, Group, MultiSelect, ScrollArea, @@ -23,6 +24,9 @@ import { TextInput, } from "@mantine/core"; import { + IconFilePlus, + IconFileTypeCsv, + IconJson, IconPlaylistAdd, IconRowInsertBottom, IconTrash, @@ -195,7 +199,10 @@ const ProcessInfoItem = (
onChange({ ...info, data })} + onChange={(data) => { + console.log(data); + return onChange({ ...info, data }); + }} />
@@ -282,8 +289,12 @@ const ProcessItem = ( info={info} references={references} onChange={(info) => { - process.info[index] = info; - onChange(process); + return onChange({ + ...process, + info: process.info.map((item, curIndex) => + index === curIndex ? info : item + ), + }); }} onDelete={() => { setIds((ids) => @@ -351,8 +362,12 @@ export const ProcessTab = ( species={speciesMap} references={referenceMap} onChange={(process) => { - processes[index] = process; - onChange(processes); + // processes[index] = process; + return onChange( + processes.map((item, curIndex) => + curIndex === index ? process : item + ), + ); }} onDelete={() => { setIds((ids) => From 517c3974c9ad4e6d76e8b432b549c9a70e7771d8 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Sun, 19 May 2024 13:05:44 +0200 Subject: [PATCH 073/104] Move `CommentSection` component to separate module --- .../author/set/[id]/edit/comment-section.tsx | 56 +++++++++++++++++++ .../app/author/set/[id]/edit/process-tab.tsx | 55 +----------------- 2 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 app/src/app/author/set/[id]/edit/comment-section.tsx diff --git a/app/src/app/author/set/[id]/edit/comment-section.tsx b/app/src/app/author/set/[id]/edit/comment-section.tsx new file mode 100644 index 000000000..c3a665fef --- /dev/null +++ b/app/src/app/author/set/[id]/edit/comment-section.tsx @@ -0,0 +1,56 @@ +import { MaybePromise } from "@/app/api/util"; +import { + ActionIcon, + Button, + Center, + Group, + Stack, + TextInput, +} from "@mantine/core"; +import { IconPlaylistAdd, IconTrash } from "@tabler/icons-react"; + +export const CommentSection = ( + { comments, onChange }: { + comments: Array | undefined; + onChange: (comments: Array | undefined) => MaybePromise; + }, +) => ( + + {comments?.map((comment, index) => { + return ( + + { + const newComments = [...comments]; + newComments[index] = event.currentTarget.value; + return onChange(newComments); + }} + /> + + onChange( + comments + ? comments.filter((_, curIndex) => curIndex !== index) + : undefined, + )} + > + + + + ); + })} +
+ +
+
+); diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index fee6fb35b..5362edc92 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -8,25 +8,17 @@ import { MaybePromise } from "@/app/api/util"; import { EditedLTPDocument } from "@lxcat/schema"; import { CrossSectionInfo, ReactionEntry } from "@lxcat/schema/process"; import { ReferenceRef } from "@lxcat/schema/reference"; -import { AnySpeciesSerializable } from "@lxcat/schema/species"; import { Accordion, ActionIcon, Button, Center, Fieldset, - FileButton, - Group, - MultiSelect, ScrollArea, Select, Stack, - TextInput, } from "@mantine/core"; import { - IconFilePlus, - IconFileTypeCsv, - IconJson, IconPlaylistAdd, IconRowInsertBottom, IconTrash, @@ -34,6 +26,7 @@ import { import { nanoid } from "nanoid"; import { useMemo, useState } from "react"; import Latex from "react-latex-next"; +import { CommentSection } from "./comment-section"; import { LookupTable } from "./lookup-table"; import classes from "./process-tab.module.css"; import { ReactionBuilder } from "./reaction-builder"; @@ -95,52 +88,6 @@ const ProcessInfoData = ( ); -const CommentSection = ( - { comments, onChange }: { - comments: Array | undefined; - onChange: (comments: Array | undefined) => MaybePromise; - }, -) => ( - - {comments?.map((comment, index) => { - return ( - - { - const newComments = [...comments]; - newComments[index] = event.currentTarget.value; - return onChange(newComments); - }} - /> - - onChange( - comments - ? comments.filter((_, curIndex) => curIndex !== index) - : undefined, - )} - > - - - - ); - })} -
- -
-
-); - const typeLabelMap = { "CrossSection": "Cross section" }; const typeSelectData = Object .entries(typeLabelMap) From e598da7d8cfd07abdc20aca6cda7f8f8507698c4 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Sun, 19 May 2024 13:07:24 +0200 Subject: [PATCH 074/104] Introduce the `ReferenceSection` component This component allows for linking references to info objects and editing their comments. --- .../app/author/set/[id]/edit/process-tab.tsx | 20 ++- .../set/[id]/edit/reference-section.tsx | 129 ++++++++++++++++++ 2 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 app/src/app/author/set/[id]/edit/reference-section.tsx diff --git a/app/src/app/author/set/[id]/edit/process-tab.tsx b/app/src/app/author/set/[id]/edit/process-tab.tsx index 5362edc92..34418826b 100644 --- a/app/src/app/author/set/[id]/edit/process-tab.tsx +++ b/app/src/app/author/set/[id]/edit/process-tab.tsx @@ -30,6 +30,7 @@ import { CommentSection } from "./comment-section"; import { LookupTable } from "./lookup-table"; import classes from "./process-tab.module.css"; import { ReactionBuilder } from "./reaction-builder"; +import { ReferenceSection } from "./reference-section"; type Process = EditedLTPDocument["processes"][number]; type ProcessInfo = Process["info"][number]; @@ -131,18 +132,13 @@ const ProcessInfoItem = ( onChange={(comments) => onChange({ ...info, comments })} /> - ({ - value, - label, - }))} - // TODO: Use a component that allows for adding reference comments. - value={info.references.map(ref => - typeof ref === "object" ? ref.id : ref - )} - onChange={(references) => onChange({ ...info, references })} - /> +
+ onChange({ ...info, references })} + /> +
; + selected: Array>; + onChange: (selected: Array>) => MaybePromise; +}; + +const ReferenceButton = ( + { selected, references, onChange }: ReferenceButtonProps, +) => { + const combobox = useCombobox({ + onDropdownClose: () => combobox.resetSelectedOption(), + }); + + const options = Object.entries(references).map(([key, ref]) => ( + + + {selected.map((ref) => typeof ref === "string" ? ref : ref.id).includes( + key, + ) && } + {ref} + + + )); + + return ( + { + onChange( + selected.includes(val) + ? selected.filter((item) => item !== val) + : [...selected, val], + ); + }} + > + +
+ +
+
+ + + + + {options} + + + +
+ ); +}; + +export const ReferenceSection = ( + { references, selected, onChange }: { + references: Record; + selected: Array>; + onChange: (references: Array>) => MaybePromise; + }, +) => ( + + + typeof entry === "string" + ? { id: entry, reference: references[entry], comments: [] } + : { ...entry, reference: references[entry.id] } + )} + columns={[{ + accessor: "reference", + title: "Name", + render: (record) => {record.reference}, + }]} + rowExpansion={{ + content: ({ record, index }) => ( +
+ + onChange(selected.map((ref, curIdx) => { + if (curIdx === index) { + if (comments === undefined) { + return record.id; + } + return { id: record.id, comments }; + } + + return ref; + }))} + /> +
+ ), + }} + /> + +
+); From 3b2186af5a92ba79af3e1f84ce3d3219fbc66f18 Mon Sep 17 00:00:00 2001 From: Daan Boer Date: Sun, 19 May 2024 13:14:27 +0200 Subject: [PATCH 075/104] Fix functionality of `Complete` checkbox in edit form --- app/src/app/author/set/[id]/edit/edit-form.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/app/author/set/[id]/edit/edit-form.tsx b/app/src/app/author/set/[id]/edit/edit-form.tsx index ef1e1aee4..c42af79c6 100644 --- a/app/src/app/author/set/[id]/edit/edit-form.tsx +++ b/app/src/app/author/set/[id]/edit/edit-form.tsx @@ -139,7 +139,10 @@ export const EditForm = ( rows={10} {...getInputProps("set.description")} /> - + - -
-
- Excited - - -
- -
-

Term

- -
- -
-
- -
-
- -
-
- ( - onChange(parseInt(v))} - value={value === undefined ? 1 : value.toString()} - error={errorMsg( - errors, - `set.states.${label}.electronic.${eindex}.term.P`, - )} - > - - - - )} - /> -
-
- -
-
-
- - ) - : ( - - )} - - ); -}; - -const initialValue4AtomLS1 = () => ({ - scheme: "LS1", - config: { - core: { - scheme: "LS", - config: [{ n: 0, l: 0, occupance: 0 }], - term: { L: 0, S: 0, P: 1 }, - }, - excited: { - scheme: "LS", - config: [{ n: 0, l: 0, occupance: 0 }], - term: { L: 0, S: 0, P: 1 }, - }, - }, - term: { - L: 0, - K: 0, - S: 0, - P: 1, - J: 0, - }, -}); -const AtomLS1Form = ({ label }: { label: string }) => { - return ( - ( - - )} - /> - ); -}; - -const MolecularParityField = ({ - label, - eindex, -}: { - label: string; - eindex: number; -}) => { - const { - control, - formState: { errors }, - } = useFormContext(); - return ( -
- ( - onChange(value as "u" | "g")} - value={value as string} - error={errorMsg( - errors, - `set.states.${label}.electronic.${eindex}.parity`, - )} - > - - - - )} - /> -
- ); -}; - -// TODO Use SimpleVibrational here, as it is almost same as this component -const LinearTriatomVibrationalFieldItem = ({ - label, - eindex, - vindex, -}: { - label: string; - eindex: number; - vindex: number; -}) => { - const { watch, setValue } = useFormContext(); - const v = watch( - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}.v`, - ); - const initialScheme = typeof v === "string" ? "simple" : "detailed"; - const [scheme, setScheme] = useState(initialScheme); - return ( -
- { - setScheme(v); - if (v === "simple") { - setValue( - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}`, - { v: "" }, - ); - } else { - setValue( - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}`, - { v: [0, 0, 0], rotational: [] }, - ); - } - }} - > - - - - {scheme === "simple" - ? ( - - ) - : ( - - )} -
- ); -}; - -const VibrationalSimpleFieldItem = ({ - label, - eindex, - vindex, -}: { - label: string; - eindex: number; - vindex: number; -}) => { - const { - control, - formState: { errors }, - } = useFormContext(); - - return ( - ( - - )} - /> - ); -}; - -const LinearTriatomRotationalArray = ({ - label, - eindex, - vindex, -}: { - label: string; - eindex: number; - vindex: number; -}) => { - const { - control, - register, - formState: { errors }, - } = useFormContext(); - const array = useFieldArray({ - control, - name: - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}.rotational`, - }); - return ( -
- Rotational -
    - {array.fields.map((_, index) => ( - array.remove(index)} - > - - - ))} - -
- - -
- ); -}; - -const LinearTriatomVibrationalDetailedFieldItem = ({ - label, - eindex, - vindex, -}: { - label: string; - eindex: number; - vindex: number; -}) => { - const { - register, - formState: { errors }, - } = useFormContext(); - - return ( - <> - - -
- -
-
- -
-
- -
-
- - - ); -}; - -const LinearTriatomVibrationalField = ({ - label, - eindex, -}: { - label: string; - eindex: number; -}) => { - return ( - ( - - )} - /> - ); -}; - -const LinearElectronicForm = ({ - label, - eindex, -}: { - label: string; - eindex: number; -}) => { - const { - register, - control, - formState: { errors }, - } = useFormContext(); - return ( - <> -
- -
-
- -
-
- -
-
- ( - onChange(value as "+" | "-")} - value={value as string} - error={errorMsg( - errors, - `set.states.${label}.electronic.${eindex}.reflection`, - )} - > - - - - )} - /> -
- - ); -}; - -const ElectronicArray = ({ - label, - item, - initialValue, -}: { - label: string; - item: (label: string, eindex: number) => ReactNode; - initialValue: any; // TODO add generic type -}) => { - const { - control, - formState: { errors }, - } = useFormContext(); - const array = useFieldArray({ - control, - name: `set.states.${label}.electronic`, - }); - return ( -
-

Electronic

-
    - {array.fields.map((_field, index) => ( - array.remove(index)} - > - {item(label, index)} - - ))} - -
- -
- ); -}; - -const ArrayItem = ({ - removeTitle, - onRemove, - children, -}: { - removeTitle: string; - onRemove: () => void; - children: ReactNode; -}) => { - return ( -
  • -
    - {children} - -
    -
  • - ); -}; - -const LinearTriatomInversionCenterForm = ({ label }: { label: string }) => { - const initialValue = { e: "X", Lambda: 0, S: 0, parity: "g" }; - return ( - ( - - - - - - - - )} - /> - ); -}; - -const VibrationalArray = ({ - label, - eindex, - item, - initialValue, -}: { - label: string; - eindex: number; - item: (label: string, eindex: number, vindex: number) => ReactNode; - initialValue: any; -}) => { - const { - control, - formState: { errors }, - } = useFormContext(); - const array = useFieldArray({ - control, - name: `set.states.${label}.electronic.${eindex}.vibrational`, - }); - return ( -
    - Vibrational -
      - {array.fields.map((_, index) => ( - array.remove(index)} - > - {item(label, eindex, index)} - - ))} - -
    - - -
    - ); -}; - -const RotationalArray = ({ - label, - eindex, - vindex, -}: { - label: string; - eindex: number; - vindex: number; -}) => { - const { - control, - register, - formState: { errors }, - } = useFormContext(); - const array = useFieldArray({ - control, - name: - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}.rotational`, - }); - return ( -
    - Rotational -
      - {array.fields.map((_, index) => ( - array.remove(index)} - > - - - ))} - -
    - - -
    - ); -}; - -type IScheme = "simple" | "detailed"; - -const SimpleVibrational = ({ - label, - eindex, - vindex, - children, -}: { - label: string; - eindex: number; - vindex: number; - children: ReactNode; -}) => { - const { getValues, setValue } = useFormContext(); - const av = getValues( - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}.v`, - ); - const initialScheme = typeof av === "string" || Number.isNaN(av) - ? "simple" - : "detailed"; - const [scheme, setScheme] = useState(initialScheme); - return ( -
    - { - setScheme(v); - if (v === "simple") { - setValue( - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}`, - { v: "" }, - ); - } else { - setValue( - `set.states.${label}.electronic.${eindex}.vibrational.${vindex}`, - { v: 0, rotational: [] }, - ); - } - }} - > - - - - {scheme === "simple" - ? ( - - ) - : children} -
    - ); -}; - -const DiatomicVibrationalForm = ({ - label, - eindex, -}: { - label: string; - eindex: number; -}) => { - const { - register, - formState: { errors }, - } = useFormContext(); - return ( - ( - - <> - - - - - )} - /> - ); -}; - -const SimpleElectronic = ({ - label, - eindex, - initialDetailedValue, - children, -}: { - label: string; - eindex: number; - initialDetailedValue: any; // TODO add generic type - children: ReactNode; -}) => { - const { - register, - setValue, - control, - formState: { errors }, - } = useFormContext(); - const electronicValue = useWatch({ - control, - name: `set.states.${label}.electronic.${eindex}`, - }); - const initialScheme = - "e" in electronicValue && Object.keys(electronicValue).length <= 1 - ? "simple" - : "detailed"; - const [scheme, setScheme] = useState(initialScheme); - return ( - <> - { - setScheme(v); - if (v === "simple") { - setValue( - `set.states.${label}.electronic`, - initialSimpleElectronic(), - ); - } else { - setValue( - `set.states.${label}.electronic.${eindex}`, - initialDetailedValue, - ); - } - }} - > - - - - {scheme === "simple" - ? ( -
    - -
    - ) - : children} - - ); -}; - -const HeteronuclearDiatomForm = ({ label }: { label: string }) => { - const initialValue = { e: "", Lambda: 0, S: 0 }; - return ( - ( - - - - - - - )} - /> - ); -}; - -const HomonuclearDiatomForm = ({ label }: { label: string }) => { - const initialValue = { e: "", Lambda: 0, S: 0, parity: "g" }; - return ( - ( - - - - - - - - )} - /> - ); -}; - -const StateForm = ({ - label, - onRemove, - expanded, -}: { - label: string; - onRemove: () => void; - expanded: boolean; -}) => { - const { - control, - setValue, - getValues, - formState: { errors }, - } = useFormContext(); - const state = useWatch({ name: `set.states.${label}` }); - // TODO label update based on whole state tricky as existing label (a key in states object) needs to be removed - const latex = useMemo(() => { - try { - return getStateLatex(state); - } catch (error) { - // incomplete state, ignore error and dont update id - return ""; - } - }, [state]); - - return ( - - - {latex} - - - {expanded && ( - <> -
    - ( - { - onChange(v); - if (v === "") { - // unset .type and .electronic - const { particle, charge } = getValues( - `set.states.${label}`, - ); - setValue(`set.states.${label}`, { - particle, - charge, - }); - } else { - setValue(`set.states.${label}.electronic`, []); - } - }} - value={value === undefined ? "" : value} - error={errorMsg(errors, `set.states.${label}.type`)} - > - - - - - - - - - )} - /> -
    - - {state.type === "AtomLS" && } - {state.type === "AtomJ1L2" && } - {state.type === "HeteronuclearDiatom" && ( - - )} - {state.type === "HomonuclearDiatom" && ( - - )} - {state.type === "LinearTriatomInversionCenter" && ( - - )} - {state.type === "AtomLS1" && } - -
    - - )} -
    -
    - ); -}; - -const ReferenceForm = ({ - label, - onRemove, -}: { - label: string; - onRemove: () => void; -}) => { - const { watch } = useFormContext(); - const reference = watch(`set.references.${label}`); - return ( -
  • - - -
  • - ); -}; - -const ImportDOIButton = ({ - onAdd, -}: { - onAdd: (newLabel: string, newReference: ReferenceRecord) => void; -}) => { - const [doi, setDoi] = useState(""); - const [open, setOpen] = useState(false); - async function onSubmit() { - // TODO resolving doi can take long time and timeout, should notify user when fetch fails - // TODO use mailto param to improve speed, see https://github.com/CrossRef/rest-api-doc#good-manners--more-reliable-service - const ref = await doi2csl(doi); - // TODO handle fetch/parse errors - const label = getReferenceLabel(ref); - onAdd(label, ref); - setOpen(false); - } - return ( -
    - - setOpen(false)} - title="Import reference based on DOI" - > - setDoi(e.target.value)} - placeholder="Enter DOI like 10.5284/1015681" - // DOI pattern from https://www.crossref.org/blog/dois-and-matching-regular-expressions/ - // Does not work for `10.3390/atoms9010016` - // pattern="^10.\d{4,9}/[-._;()/:A-Z0-9]+$" - /> - - - - -
    - ); -}; - -const ImportBibTeXDOIButton = ({ - onAdd, -}: { - onAdd: (refs: Record) => void; -}) => { - const [bibtex, setBibtex] = useState(""); - const [open, setOpen] = useState(false); - async function onSubmit() { - // TODO resolving doi can take long time and timeout, should notify user when fetch fails - const labelRefs = await bibtex2csl(bibtex); - - onAdd(labelRefs); - setOpen(false); - } - const placeholder = `Enter BibTeX like: -@Article{atoms9010016, - AUTHOR = {Carbone, Emile and Graef, Wouter and Hagelaar, Gerjan and Boer, Daan and Hopkins, Matthew M. and Stephens, Jacob C. and Yee, Benjamin T. and Pancheshnyi, Sergey and van Dijk, Jan and Pitchford, Leanne}, - TITLE = {Data Needs for Modeling Low-Temperature Non-Equilibrium Plasmas: The LXCat Project, History, Perspectives and a Tutorial}, - JOURNAL = {Atoms}, - VOLUME = {9}, - YEAR = {2021}, - NUMBER = {1}, - ARTICLE-NUMBER = {16}, - URL = {https://www.mdpi.com/2218-2004/9/1/16}, - ISSN = {2218-2004}, - DOI = {10.3390/atoms9010016} -}`; - return ( -
    - - setOpen(false)} - title="Import references based on BibTeX" - > -