From 7b0679f3d10c4935d92ffd358de9bce6ccb3718a Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Wed, 16 Aug 2023 16:56:10 +0545 Subject: [PATCH 01/27] Add basic page view for journal pdf --- .../documents/journal-pdf-preview/index.js | 420 ++++++++++++++++++ .../components/documents/pdf-preview/index.js | 4 + .../components/slate/safe-read-editor.js | 8 +- app/assets/scripts/main.js | 6 + app/assets/scripts/styles/global.js | 12 +- 5 files changed, 442 insertions(+), 8 deletions(-) create mode 100644 app/assets/scripts/components/documents/journal-pdf-preview/index.js diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js new file mode 100644 index 00000000..7e6017a7 --- /dev/null +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -0,0 +1,420 @@ +import T from 'prop-types'; +import React, { + useEffect, + useRef, + useState, + createContext, + useContext, + useCallback, + useMemo, + Fragment +} from 'react'; +import { useParams } from 'react-router'; +import styled from 'styled-components'; + +import { useSingleAtbd } from '../../../context/atbds-list'; +import { useUser } from '../../../context/user'; +import { resolveTitle } from '../../../utils/common'; +import { SafeReadEditor } from '../../slate'; + +function useNumberingContextProvider() { + const [registeredElements, setRegisteredElements] = useState({}); + + const register = useCallback((key) => { + setRegisteredElements((prevElements) => { + if (prevElements[key]) { + return prevElements; + } + + const numElements = Object.keys(prevElements).length; + return { + ...prevElements, + [key]: numElements + 1 + }; + }); + }, []); + + const getNumbering = useCallback( + (key, numberingFromParent = '') => { + if (!registeredElements[key]) { + return ''; + } + + return `${numberingFromParent}${registeredElements[key]}.`; + }, + [registeredElements] + ); + + return useMemo( + () => ({ + getNumbering, + register + }), + [getNumbering, register] + ); +} + +const NumberingContext = createContext({ + numberingFromParent: '', + level: 1, + register: (key) => { + // eslint-disable-next-line no-console + console.warn( + 'NumberingContext::register called before initialization with key', + key + ); + } +}); + +const PreviewContainer = styled.div` + @media print { + @page { + size: portrait; + margin: 15mm; + } + } + + font-family: serif; + width: 210mm; + margin: 15mm; + display: flex; + flex-direction: column; + gap: 2rem; +`; + +const DocumentHeading = styled.h1` + margin: 0; +`; + +const SectionContainer = styled.section` + display: flex; + flex-direction: column; + gap: 1rem; +`; + +const SectionContent = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; +`; + +function Section({ id, children, title, skipNumbering }) { + const { register, getNumbering, level = 1, numberingFromParent } = useContext( + NumberingContext + ); + + const defaultContextValue = useNumberingContextProvider(); + const numbering = getNumbering(id, numberingFromParent); + + const numberingContextValue = useMemo( + () => ({ + ...defaultContextValue, + numberingFromParent: numbering, + level: level + 1 + }), + [defaultContextValue, level, numbering] + ); + + useEffect(() => { + if (children && !skipNumbering) { + register(id); + } + }, [skipNumbering, children, id, register]); + + if (!children) { + return null; + } + + const H = `h${level + 1}`; + + return ( + + + + {numbering} {title} + + {children} + + + ); +} + +Section.propTypes = { + id: T.string, + title: T.node, + children: T.node, + skipNumbering: T.bool +}; + +function ImplementationDataList({ list }) { + return ( +
+ {list?.map(({ url, description }) => ( + +
{url}
+
{description}
+
+ ))} +
+ ); +} + +ImplementationDataList.propTypes = { + list: T.arrayOf( + T.shape({ + url: T.string, + description: T.string + }) + ) +}; + +function JournalPdfPreview() { + const { id, version } = useParams(); + const { atbd: atbdResponse, fetchSingleAtbd } = useSingleAtbd({ + id, + version + }); + const { isAuthReady } = useUser(); + const [previewReady, setPreviewReady] = useState(false); + const contentRef = useRef(null); + + useEffect(() => { + isAuthReady && fetchSingleAtbd(); + }, [isAuthReady, id, version, fetchSingleAtbd]); + + useEffect(() => { + async function waitForImages() { + const images = contentRef.current.querySelectorAll('img'); + const promises = Array.from(images).map((image) => { + return new Promise((accept) => { + image.addEventListener('load', () => { + accept(); + }); + }); + }); + await Promise.all(promises); + setPreviewReady(true); + } + + if (atbdResponse.status === 'succeeded') { + waitForImages(); + } + }, [atbdResponse.status]); + + const numberingContextValue = useNumberingContextProvider(); + + const atbd = atbdResponse?.data ?? { + document: {} + }; + + console.info(atbd); + const { keywords, document } = atbd; + + const { + key_points, + abstract, + plain_summary, + publication_references, + version_description, + introduction, + historical_perspective, + additional_information, + scientific_theory, + scientific_theory_assumptions, + mathematical_theory, + mathematical_theory_assumptions, + algorithm_input_variables, + algorithm_output_variables, + algorithm_usage_constraints, + performance_assessment_validation_errors, + performance_assessment_validation_methods, + performance_assessment_validation_uncertainties, + algorithm_implementations, + data_access_input_data, + data_access_output_data, + data_access_related_urls, + journal_discussion, + data_availability, + journal_acknowledgements + } = document; + + const safeReadContext = { + subsectionLevel: 'h3', + references: publication_references, + atbd + }; + + return ( + + + {resolveTitle(atbd.title)} +
+ {key_points} +
+
+ +
+
+ +
+ {keywords && keywords.length > 0 && ( +
+
Keywords:
+
{keywords.map(({ label }) => label).join(', ')}
+
+ )} +
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+ {previewReady &&
} + + + ); +} + +export default JournalPdfPreview; diff --git a/app/assets/scripts/components/documents/pdf-preview/index.js b/app/assets/scripts/components/documents/pdf-preview/index.js index e6074833..688368ae 100644 --- a/app/assets/scripts/components/documents/pdf-preview/index.js +++ b/app/assets/scripts/components/documents/pdf-preview/index.js @@ -19,6 +19,7 @@ const PreviewContainer = styled.div` @media print { @page { size: portrait; + margin: 15mm; } } `; @@ -270,6 +271,9 @@ function PdfPreview() { id='table-of-contents' />
+
+ This is the header for print +
diff --git a/app/assets/scripts/components/slate/safe-read-editor.js b/app/assets/scripts/components/slate/safe-read-editor.js index aee6dcd7..92110c6b 100644 --- a/app/assets/scripts/components/slate/safe-read-editor.js +++ b/app/assets/scripts/components/slate/safe-read-editor.js @@ -6,6 +6,7 @@ import { RichContextProvider } from './plugins/common/rich-context'; import { IMAGE, IMAGE_BLOCK } from './plugins/image'; import { removeNodeFromSlateDocument } from './nodes-from-slate'; import serializeToString from './serialize-to-string'; +import { isTruthyString } from '../../utils/common'; export default class SafeReadEditor extends React.Component { static getDerivedStateFromError(error) { @@ -36,7 +37,7 @@ SafeReadEditor.propTypes = { }; const SafeReadEditorComponent = (props) => { - const { value, whenEmpty, context, contextDeps, ...rest } = props; + const { value, whenEmpty = null, context, contextDeps, ...rest } = props; const strValue = value?.children ? value.children.map((n) => serializeToString(n).trim()).join('') @@ -47,7 +48,7 @@ const SafeReadEditorComponent = (props) => { // value, and won't have a objectKey. const cleanValue = useMemo( () => - strValue + isTruthyString(strValue) ? removeNodeFromSlateDocument(value, (node) => { // Only act on image blocks. if (node.type !== IMAGE_BLOCK) return false; @@ -63,9 +64,10 @@ const SafeReadEditorComponent = (props) => { [strValue, value] ); - if (whenEmpty && !strValue) { + if (!isTruthyString(strValue)) { return whenEmpty; } + return ( diff --git a/app/assets/scripts/main.js b/app/assets/scripts/main.js index 9de9a126..791d811c 100644 --- a/app/assets/scripts/main.js +++ b/app/assets/scripts/main.js @@ -37,6 +37,7 @@ import Documents from './components/documents/hub'; import DocumentsView from './components/documents/single-view'; import DocumentsEdit from './components/documents/single-edit'; import PdfPreview from './components/documents/pdf-preview'; +import JournalPdfPreview from './components/documents/journal-pdf-preview'; import Contacts from './components/contacts/hub'; import ContactsView from './components/contacts/view'; import ContactsEdit from './components/contacts/edit'; @@ -158,6 +159,11 @@ function Root() { path='/documents/:id/:version/pdf-preview' component={PdfPreview} /> + Date: Thu, 24 Aug 2023 02:32:00 +0545 Subject: [PATCH 02/27] Add contents for journal pdf preview --- .../components/common/forms/input-editor.js | 8 +- .../documents/journal-pdf-preview/index.js | 405 ++++++++---------- app/assets/scripts/components/slate/editor.js | 10 +- .../slate/plugins/caption/caption.js | 40 +- .../components/slate/plugins/caption/index.js | 4 +- .../slate/plugins/common/with-empty-editor.js | 2 +- .../components/slate/plugins/constants.js | 2 + .../plugins/equation/equation-element.js | 17 +- .../components/slate/plugins/image/helpers.js | 8 +- .../components/slate/plugins/table/helpers.js | 8 +- .../scripts/context/heading-numbering.js | 40 ++ app/assets/scripts/context/numbering.js | 106 +++++ app/assets/scripts/styles/global.js | 4 + 13 files changed, 413 insertions(+), 241 deletions(-) create mode 100644 app/assets/scripts/components/slate/plugins/constants.js create mode 100644 app/assets/scripts/context/heading-numbering.js create mode 100644 app/assets/scripts/context/numbering.js diff --git a/app/assets/scripts/components/common/forms/input-editor.js b/app/assets/scripts/components/common/forms/input-editor.js index 70e2088f..d67aa8c9 100644 --- a/app/assets/scripts/components/common/forms/input-editor.js +++ b/app/assets/scripts/components/common/forms/input-editor.js @@ -5,11 +5,13 @@ import { FormHelperMessage } from '@devseed-ui/form'; import FormGroupStructure from './form-group-structure'; import { RichTextEditor, InlineRichTextEditor } from '../../slate'; +import { IMAGE_BLOCK } from '../../slate/plugins/constants'; function validateImageBlock(value) { - const imageBlocks = value?.children?.filter( - (slateElement) => slateElement.type === 'image-block' - ); + const imageBlocks = + value?.children?.filter( + (slateElement) => slateElement.type === IMAGE_BLOCK + ) ?? []; if (imageBlocks.length > 0) { const captions = imageBlocks.map( diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 7e6017a7..1a4917dc 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -3,9 +3,7 @@ import React, { useEffect, useRef, useState, - createContext, useContext, - useCallback, useMemo, Fragment } from 'react'; @@ -16,55 +14,15 @@ import { useSingleAtbd } from '../../../context/atbds-list'; import { useUser } from '../../../context/user'; import { resolveTitle } from '../../../utils/common'; import { SafeReadEditor } from '../../slate'; - -function useNumberingContextProvider() { - const [registeredElements, setRegisteredElements] = useState({}); - - const register = useCallback((key) => { - setRegisteredElements((prevElements) => { - if (prevElements[key]) { - return prevElements; - } - - const numElements = Object.keys(prevElements).length; - return { - ...prevElements, - [key]: numElements + 1 - }; - }); - }, []); - - const getNumbering = useCallback( - (key, numberingFromParent = '') => { - if (!registeredElements[key]) { - return ''; - } - - return `${numberingFromParent}${registeredElements[key]}.`; - }, - [registeredElements] - ); - - return useMemo( - () => ({ - getNumbering, - register - }), - [getNumbering, register] - ); -} - -const NumberingContext = createContext({ - numberingFromParent: '', - level: 1, - register: (key) => { - // eslint-disable-next-line no-console - console.warn( - 'NumberingContext::register called before initialization with key', - key - ); - } -}); +import { + HeadingNumberingContext, + useHeadingNumberingProviderValue +} from '../../../context/heading-numbering'; +import { + NumberingContext, + useNumberingProviderValue +} from '../../../context/numbering'; +import { IMAGE_BLOCK, TABLE_BLOCK } from '../../slate/plugins/constants'; const PreviewContainer = styled.div` @media print { @@ -90,6 +48,7 @@ const SectionContainer = styled.section` display: flex; flex-direction: column; gap: 1rem; + break-inside: avoid; `; const SectionContent = styled.div` @@ -100,19 +59,19 @@ const SectionContent = styled.div` function Section({ id, children, title, skipNumbering }) { const { register, getNumbering, level = 1, numberingFromParent } = useContext( - NumberingContext + HeadingNumberingContext ); - const defaultContextValue = useNumberingContextProvider(); + const headingNumberContextValue = useHeadingNumberingProviderValue(); const numbering = getNumbering(id, numberingFromParent); const numberingContextValue = useMemo( () => ({ - ...defaultContextValue, + ...headingNumberContextValue, numberingFromParent: numbering, level: level + 1 }), - [defaultContextValue, level, numbering] + [headingNumberContextValue, level, numbering] ); useEffect(() => { @@ -128,14 +87,14 @@ function Section({ id, children, title, skipNumbering }) { const H = `h${level + 1}`; return ( - + {numbering} {title} {children} - + ); } @@ -168,6 +127,8 @@ ImplementationDataList.propTypes = { ) }; +const emptyAtbd = { document: {} }; + function JournalPdfPreview() { const { id, version } = useParams(); const { atbd: atbdResponse, fetchSingleAtbd } = useSingleAtbd({ @@ -201,13 +162,11 @@ function JournalPdfPreview() { } }, [atbdResponse.status]); - const numberingContextValue = useNumberingContextProvider(); + const headingNumberingContextValue = useHeadingNumberingProviderValue(); + const equationNumberingContextValue = useNumberingProviderValue(); - const atbd = atbdResponse?.data ?? { - document: {} - }; + const atbd = atbdResponse?.data ?? emptyAtbd; - console.info(atbd); const { keywords, document } = atbd; const { @@ -238,181 +197,195 @@ function JournalPdfPreview() { journal_acknowledgements } = document; - const safeReadContext = { - subsectionLevel: 'h3', - references: publication_references, - atbd - }; + const ContentView = useMemo(() => { + const safeReadContext = { + subsectionLevel: 'h3', + references: publication_references, + atbd + }; + + function transformValue(value) { + if (!value || !value.children) { + return value; + } + + return { + ...value, + children: value.children.map((element) => { + if (!element.children) { + return element; + } + + if (element.type === IMAGE_BLOCK) { + return { + ...element, + children: [ + element.children[0], + { + ...element.children[1], + parent: IMAGE_BLOCK + } + ] + }; + } + + if (element.type === TABLE_BLOCK) { + return { + ...element, + children: [ + element.children[0], + { + ...element.children[1], + parent: TABLE_BLOCK + } + ] + }; + } + return transformValue(element); + }) + }; + } + + // eslint-disable-next-line react/display-name, react/prop-types + return ({ value }) => ( + + ); + }, [atbd, publication_references]); return ( - - - {resolveTitle(atbd.title)} -
- {key_points} -
-
- -
-
- -
- {keywords && keywords.length > 0 && ( -
-
Keywords:
-
{keywords.map(({ label }) => label).join(', ')}
-
- )} -
- -
-
- -
-
-
- + + + + {resolveTitle(atbd.title)} +
+ {key_points} +
+
+ +
+
+ +
+ {keywords && keywords.length > 0 && ( +
+
Keywords:
+
{keywords.map(({ label }) => label).join(', ')}
+
+ )} +
+
-
- +
+
-
-
-
- +
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
- +
-
-
-
- +
- +
-
- -
-
-
- -
-
-
- -
-
- +
+
+ +
+
+ +
+
+ +
- -
-
-
-
- +
+ +
+
+ +
+
+ +
+
-
- +
+
-
- +
+
- -
- -
-
- -
-
- -
- {previewReady &&
} - + {previewReady &&
} + + ); } diff --git a/app/assets/scripts/components/slate/editor.js b/app/assets/scripts/components/slate/editor.js index d6af13da..b93d6897 100644 --- a/app/assets/scripts/components/slate/editor.js +++ b/app/assets/scripts/components/slate/editor.js @@ -48,15 +48,11 @@ import { ReferencesModal, withReferenceModal } from './plugins/reference'; -import { - TABLE, - TableBlockPlugin, - TABLE_BLOCK, - withTable -} from './plugins/table'; +import { TABLE, TableBlockPlugin, withTable } from './plugins/table'; import { withCaption, withCaptionLayout } from './plugins/caption'; -import { IMAGE, ImageBlockPlugin, IMAGE_BLOCK } from './plugins/image'; +import { IMAGE, ImageBlockPlugin } from './plugins/image'; import { LatexModal } from './plugins/equation/latex-cheatsheet-modal'; +import { IMAGE_BLOCK, TABLE_BLOCK } from './plugins/constants'; const EditableDebug = composeDebugEditor(EditableWithPlugins); diff --git a/app/assets/scripts/components/slate/plugins/caption/caption.js b/app/assets/scripts/components/slate/plugins/caption/caption.js index a339fb9e..f12245d3 100644 --- a/app/assets/scripts/components/slate/plugins/caption/caption.js +++ b/app/assets/scripts/components/slate/plugins/caption/caption.js @@ -4,6 +4,9 @@ import { Node } from 'slate'; import { useReadOnly, useSelected } from 'slate-react'; import styled from 'styled-components'; +import { NumberingContext } from '../../../../context/numbering'; +import { IMAGE_BLOCK, TABLE_BLOCK } from '../constants'; + const CaptionElement = styled.figcaption` font-size: 0.875rem; line-height: 1.25rem; @@ -24,8 +27,39 @@ export function Caption(props) { const { attributes, htmlAttributes, element, children } = props; const isSelected = useSelected(); const readOnly = useReadOnly(); + const id = JSON.stringify(element); + const { parent } = element; const emptyCaption = !Node.string(element); + const numberingContext = React.useContext(NumberingContext); + + const showPlaceholder = !readOnly && !isSelected && emptyCaption; + + React.useEffect(() => { + if (numberingContext && !showPlaceholder && id) { + if (parent === TABLE_BLOCK) { + numberingContext.registerTable(id); + } else if (parent === IMAGE_BLOCK) { + numberingContext.registerImage(id); + } + } + }, [numberingContext, showPlaceholder, id, parent]); + + const numbering = React.useMemo(() => { + if (!numberingContext) { + return null; + } + + if (parent === TABLE_BLOCK) { + return numberingContext.getTableNumbering(id); + } + + if (parent === IMAGE_BLOCK) { + return numberingContext.getImageNumbering(id); + } + + return null; + }, [numberingContext, parent, id]); // if (readOnly && emptyCaption) return null; @@ -34,9 +68,10 @@ export function Caption(props) { // positioned and has no interaction. return ( - {!readOnly && !isSelected && emptyCaption && ( + {showPlaceholder && ( Write a caption )} + {numbering} {children} ); @@ -46,5 +81,6 @@ Caption.propTypes = { attributes: T.object, htmlAttributes: T.object, element: T.object, - children: T.node + children: T.node, + variant: T.string }; diff --git a/app/assets/scripts/components/slate/plugins/caption/index.js b/app/assets/scripts/components/slate/plugins/caption/index.js index 93a61293..b42b2783 100644 --- a/app/assets/scripts/components/slate/plugins/caption/index.js +++ b/app/assets/scripts/components/slate/plugins/caption/index.js @@ -3,13 +3,13 @@ import { Transforms, Node, Element, Editor } from 'slate'; import { Caption } from './caption'; -export * from './caption'; +// export * from './caption'; // Caption Element export const CAPTION = 'caption'; // Caption settings. -export const DEFAULTS_CAPTION = { +export const DEFAULT_CAPTION = { caption: { type: CAPTION, component: Caption diff --git a/app/assets/scripts/components/slate/plugins/common/with-empty-editor.js b/app/assets/scripts/components/slate/plugins/common/with-empty-editor.js index 83af8fb9..3378172e 100644 --- a/app/assets/scripts/components/slate/plugins/common/with-empty-editor.js +++ b/app/assets/scripts/components/slate/plugins/common/with-empty-editor.js @@ -11,7 +11,7 @@ export const withEmptyEditor = (editor) => { // We want to ensure that when the editor only has 1 child, it must be a // node. If it is a leaf, normalize to a paragraph. - if (editor.children[0].children.length === 1 && Text.isText(node)) { + if (editor.children[0]?.children?.length === 1 && Text.isText(node)) { Transforms.wrapNodes( editor, { diff --git a/app/assets/scripts/components/slate/plugins/constants.js b/app/assets/scripts/components/slate/plugins/constants.js new file mode 100644 index 00000000..be1bcd43 --- /dev/null +++ b/app/assets/scripts/components/slate/plugins/constants.js @@ -0,0 +1,2 @@ +export const IMAGE_BLOCK = 'image-block'; +export const TABLE_BLOCK = 'table-block'; diff --git a/app/assets/scripts/components/slate/plugins/equation/equation-element.js b/app/assets/scripts/components/slate/plugins/equation/equation-element.js index c1a1eb99..f3f4d91b 100644 --- a/app/assets/scripts/components/slate/plugins/equation/equation-element.js +++ b/app/assets/scripts/components/slate/plugins/equation/equation-element.js @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useContext, useEffect } from 'react'; import T from 'prop-types'; import styled, { css } from 'styled-components'; import { Node, Transforms } from 'slate'; @@ -8,9 +8,11 @@ import { BlockMath, InlineMath } from 'react-katex'; import { themeVal, rgba } from '@devseed-ui/theme-provider'; import { isInlineEquation } from '.'; +import { NumberingContext } from '../../../../context/numbering'; const EquationReadOnly = styled.span` - display: ${({ isInline }) => (isInline ? 'inline' : 'block')}; + display: ${({ isInline }) => (isInline ? 'inline-flex' : 'flex')}; + align-items: center; `; const Equation = styled.span` @@ -54,6 +56,13 @@ function EquationElement(props) { const isInline = isInlineEquation(element); const MathElement = isInline ? InlineMath : BlockMath; + const numberingContext = useContext(NumberingContext); + + useEffect(() => { + if (numberingContext && !isInline) { + numberingContext.registerEquation(latexEquation); + } + }, [numberingContext, isInline, latexEquation]); const returnElement = React.useMemo(() => { if (readOnly) { @@ -73,6 +82,9 @@ function EquationElement(props) { + {!isInline && numberingContext && ( + {numberingContext.getEquationNumbering(latexEquation)} + )} {!isInline && } ); @@ -98,6 +110,7 @@ function EquationElement(props) { isInline, isSelected, latexEquation, + numberingContext, readOnly ]); diff --git a/app/assets/scripts/components/slate/plugins/image/helpers.js b/app/assets/scripts/components/slate/plugins/image/helpers.js index 4b77dd94..fc90c205 100644 --- a/app/assets/scripts/components/slate/plugins/image/helpers.js +++ b/app/assets/scripts/components/slate/plugins/image/helpers.js @@ -5,16 +5,16 @@ import { ELEMENT_IMAGE } from '@udecode/slate-plugins'; -import { DEFAULTS_CAPTION, getEmptyCaptionNode } from '../caption'; +import { DEFAULT_CAPTION, getEmptyCaptionNode } from '../caption'; import ImageBlock from './image-block'; import Image from './image'; import { isInNodeType } from '../common/is-node-type'; import { getPathForRootBlockInsert } from '../common/utils'; +import { IMAGE_BLOCK } from '../constants'; // Plugin type. export const IMAGE = ELEMENT_IMAGE; -export const IMAGE_BLOCK = 'image-block'; /** * Check if the current selection is inside a IMAGE_BLOCK node @@ -79,14 +79,14 @@ const getEmptyImageNode = () => ({ */ export const getEmptyImageBlockNode = () => ({ type: IMAGE_BLOCK, - children: [getEmptyImageNode(), getEmptyCaptionNode()] + children: [getEmptyImageNode(), getEmptyCaptionNode(IMAGE_BLOCK)] }); /** * Render function for the image block elements. */ export const renderElementImageBlock = () => { - const { caption } = DEFAULTS_CAPTION; + const { caption } = DEFAULT_CAPTION; const image = { type: IMAGE, component: Image diff --git a/app/assets/scripts/components/slate/plugins/table/helpers.js b/app/assets/scripts/components/slate/plugins/table/helpers.js index 5f808ff4..98582e5e 100644 --- a/app/assets/scripts/components/slate/plugins/table/helpers.js +++ b/app/assets/scripts/components/slate/plugins/table/helpers.js @@ -7,14 +7,14 @@ import { getAbove } from '@udecode/slate-plugins'; -import { DEFAULTS_CAPTION, getEmptyCaptionNode } from '../caption'; +import { DEFAULT_CAPTION, getEmptyCaptionNode } from '../caption'; import TableBlock from './table-block'; import { isInNodeType } from '../common/is-node-type'; import { getPathForRootBlockInsert } from '../common/utils'; +import { TABLE_BLOCK } from '../constants'; // Plugin type. export const TABLE = ELEMENT_TABLE; -export const TABLE_BLOCK = 'table-block'; /** * Check if the current selection is inside a TABLE node @@ -74,7 +74,7 @@ export const insertTableBlock = (editor) => { */ export const getEmptyTableBlockNode = () => ({ type: TABLE_BLOCK, - children: [getEmptyTableNode(), getEmptyCaptionNode()] + children: [getEmptyTableNode(), getEmptyCaptionNode(TABLE_BLOCK)] }); /** @@ -82,7 +82,7 @@ export const getEmptyTableBlockNode = () => ({ */ export const renderElementTableBlock = () => { const { table, td, th, tr } = DEFAULTS_TABLE; - const { caption } = DEFAULTS_CAPTION; + const { caption } = DEFAULT_CAPTION; const tableBlock = { type: TABLE_BLOCK, component: TableBlock diff --git a/app/assets/scripts/context/heading-numbering.js b/app/assets/scripts/context/heading-numbering.js new file mode 100644 index 00000000..ef99cff3 --- /dev/null +++ b/app/assets/scripts/context/heading-numbering.js @@ -0,0 +1,40 @@ +import { createContext, useCallback, useMemo, useState } from 'react'; + +export function useHeadingNumberingProviderValue() { + const [registeredElements, setRegisteredElements] = useState({}); + + const register = useCallback((key) => { + setRegisteredElements((prevElements) => { + if (prevElements[key]) { + return prevElements; + } + + const numElements = Object.keys(prevElements).length; + return { + ...prevElements, + [key]: numElements + 1 + }; + }); + }, []); + + const getNumbering = useCallback( + (key, numberingFromParent = '') => { + if (!registeredElements[key]) { + return ''; + } + + return `${numberingFromParent}${registeredElements[key]}.`; + }, + [registeredElements] + ); + + return useMemo( + () => ({ + getNumbering, + register + }), + [getNumbering, register] + ); +} + +export const HeadingNumberingContext = createContext(null); diff --git a/app/assets/scripts/context/numbering.js b/app/assets/scripts/context/numbering.js new file mode 100644 index 00000000..550b31fa --- /dev/null +++ b/app/assets/scripts/context/numbering.js @@ -0,0 +1,106 @@ +import { createContext, useCallback, useMemo, useState } from 'react'; + +export function useNumberingProviderValue() { + const [registeredEquations, setRegisteredEquations] = useState({}); + const [registeredImages, setRegisteredImages] = useState({}); + const [registeredTables, setRegisteredTables] = useState({}); + + const registerEquation = useCallback((key) => { + setRegisteredEquations((prevElements) => { + if (prevElements[key]) { + return prevElements; + } + + const numElements = Object.keys(prevElements).length; + return { + ...prevElements, + [key]: numElements + 1 + }; + }); + }, []); + + const registerImage = useCallback((key) => { + setRegisteredImages((prevElements) => { + if (prevElements[key]) { + return prevElements; + } + + const numElements = Object.keys(prevElements).length; + return { + ...prevElements, + [key]: numElements + 1 + }; + }); + }, []); + + const registerTable = useCallback((key) => { + setRegisteredTables((prevElements) => { + if (prevElements[key]) { + return prevElements; + } + + const numElements = Object.keys(prevElements).length; + return { + ...prevElements, + [key]: numElements + 1 + }; + }); + }, []); + + const getEquationNumbering = useCallback( + (key) => { + const numbering = registeredEquations[key]; + if (!numbering) { + return ''; + } + + return `(${numbering})`; + }, + [registeredEquations] + ); + + const getTableNumbering = useCallback( + (key) => { + const numbering = registeredTables[key]; + if (!numbering) { + return ''; + } + + return `Table ${numbering}: `; + }, + [registeredTables] + ); + + const getImageNumbering = useCallback( + (key) => { + const numbering = registeredImages[key]; + if (!numbering) { + return ''; + } + + return `Image ${numbering}: `; + }, + [registeredImages] + ); + + return useMemo( + () => ({ + getEquationNumbering, + getTableNumbering, + getImageNumbering, + registerEquation, + registerTable, + registerImage + }), + [ + getEquationNumbering, + getTableNumbering, + getImageNumbering, + registerEquation, + registerTable, + registerImage + ] + ); +} + +export const NumberingContext = createContext(null); diff --git a/app/assets/scripts/styles/global.js b/app/assets/scripts/styles/global.js index eee462db..0f237411 100644 --- a/app/assets/scripts/styles/global.js +++ b/app/assets/scripts/styles/global.js @@ -30,6 +30,10 @@ ${reactTippyStyles()} } .slate-equation-element { + .katex-equation-wrapper { + flex-grow: 1; + } + .equation-number { display: none; } From 2f91526c3737a7788949cf184af89e1b5776d36b Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Tue, 29 Aug 2023 14:41:09 +0545 Subject: [PATCH 03/27] Add download button for journal pdf --- .../documents/document-download-menu.js | 118 +++++++---- .../documents/journal-pdf-preview/index.js | 192 ++++++++++++++---- .../components/documents/pdf-preview/index.js | 3 - .../components/slate/plugins/list/index.js | 2 +- app/assets/scripts/context/numbering.js | 2 +- app/assets/scripts/styles/global.js | 7 - 6 files changed, 237 insertions(+), 87 deletions(-) diff --git a/app/assets/scripts/components/documents/document-download-menu.js b/app/assets/scripts/components/documents/document-download-menu.js index 71c1ebcf..b765c195 100644 --- a/app/assets/scripts/components/documents/document-download-menu.js +++ b/app/assets/scripts/components/documents/document-download-menu.js @@ -9,6 +9,7 @@ import { createProcessToast } from '../common/toasts'; import { apiUrl } from '../../config'; import { useAuthToken } from '../../context/user'; +import { axiosAPI } from '../../utils/axios'; function DropMenuItemOutboundLink(props) { const { eventLabel, menuItem, active, ...rest } = props; @@ -40,35 +41,42 @@ export default function DocumentDownloadMenu(props) { // Temporarily disable journal PDF downloading https://github.com/nasa-impact/nasa-apt/issues/744 // const ability = useContextualAbility(); //const canDownloadJournalPdf = ability.can('download-journal-pdf', atbd); - const canDownloadJournalPdf = false; + const [canDownloadJournalPdf, setCanDownloadJournalPdf] = React.useState(false); - const handlePdfDownloadClick = React.useCallback(() => { - const processToast = createProcessToast('Downloading PDF, please wait...'); + React.useEffect(() => { + async function fetchBootstrap() { + try { + const headers = {}; + const response = await axiosAPI({ + url: 'bootstrap', + headers + }); - if (atbd.documentType === 'PDF' && !atbd.pdf) { - processToast.error("This ATBD doesn't have the attachment"); - return; + if (response?.data?.feature_flags?.JOURNAL_PDF_EXPORT_ENABLED) { + setCanDownloadJournalPdf(true); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + setCanDownloadJournalPdf(false); + } } - const { id, version, alias } = atbd; - const pdfUrl = - atbd.documentType === 'PDF' - ? atbd.pdf.file_path - : `${apiUrl}/atbds/${id}/versions/${version}/pdf`; - - const pdfFileName = `${alias}-v${version}.pdf`; - const maxRetries = 50; - const waitBetweenTries = 5000; - const initialWait = 10000; - let retryCount = 0; + if (atbd.document_type === 'HTML') { + fetchBootstrap(); + } + }, [atbd]); - async function fetchPdf(url) { + const fetchPdf = React.useCallback( + async (url, fileName, processToast, retryCount = 0) => { if (retryCount > 0) { processToast.update( 'Generating the PDF. This may take up to 5 minutes.' ); } + const retryUrl = retryCount === 0 ? `${url}?retry=true` : url; + try { let user; let headers = {}; @@ -104,6 +112,11 @@ export default function DocumentDownloadMenu(props) { headers }); + const maxRetries = 50; + const waitBetweenTries = 5000; + const initialWait = 10000; + let retryCount = 0; + // If we get a 404 on retry, it means the PDF is not ready yet. // Keep retrying until we reach the max retry count. if ( @@ -113,9 +126,8 @@ export default function DocumentDownloadMenu(props) { ) { if (retryCount < maxRetries) { setTimeout(() => { - fetchPdf(`${pdfUrl}?retry=true`); + fetchPdf(retryUrl, fileName, processToast, retryCount + 1); }, waitBetweenTries); - ++retryCount; } else { processToast.error( 'Failed to download PDF. Please retry after several minutes. If this error persists, please contact the APT team.' @@ -137,9 +149,8 @@ export default function DocumentDownloadMenu(props) { } setTimeout(() => { - fetchPdf(`${pdfUrl}?retry=true`); + fetchPdf(retryUrl, fileName, processToast, retryCount + 1); }, initialWait); - ++retryCount; return; } @@ -152,7 +163,7 @@ export default function DocumentDownloadMenu(props) { ) { const result = await response.json(); - saveAs(result.pdf_url, pdfFileName); + saveAs(result.pdf_url, fileName); processToast.success( 'PDF downloaded successfully! If the PDF did not open automatically, your browser may have blocked the download. Please make sure that popups are allowed on this site.' ); @@ -163,12 +174,41 @@ export default function DocumentDownloadMenu(props) { } catch (e) { // eslint-disable-next-line no-console console.error(e); - processToast.error('Failed to download PDF!'); + processToast.error('Failed to download the PDF!'); } + }, + [token, atbd] + ); + + const handlePdfDownloadClick = React.useCallback(() => { + const processToast = createProcessToast('Downloading PDF, please wait...'); + + if (atbd.document_type === 'PDF' && !atbd.pdf) { + processToast.error("This ATBD doesn't have the attachment"); + return; } - fetchPdf(pdfUrl); - }, [token, atbd]); + const { id, version, alias } = atbd; + const pdfUrl = + atbd.document_type === 'PDF' + ? atbd.pdf.file_path + : `${apiUrl}/atbds/${id}/versions/${version}/pdf`; + + const pdfFileName = `${alias}-v${version}.pdf`; + fetchPdf(pdfUrl, pdfFileName, processToast); + }, [atbd, fetchPdf]); + + const handleJournalPdfDownloadClick = React.useCallback(() => { + const processToast = createProcessToast( + 'Downloading Journal PDF, please wait...' + ); + + const { id, version, alias } = atbd; + const pdfUrl = `${apiUrl}/atbds/${id}/versions/${version}/journal-pdf`; + + const pdfFileName = `Journal-${alias}-v${version}.pdf`; + fetchPdf(pdfUrl, pdfFileName, processToast); + }, [atbd, fetchPdf]); const dropProps = useMemo(() => { const triggerProps = { @@ -184,21 +224,24 @@ export default function DocumentDownloadMenu(props) { // Therefore if the minor version is a 4, we know that there are also pdf // for minor 3, 2, 1 and 0. The urls are constructed dynamically. let pdfLinks = []; - const { id, version } = atbd; - const pdfUrl = `${apiUrl}/atbds/${id}/versions/${version}/pdf`; + const { version } = atbd; if (canDownloadJournalPdf) { pdfLinks.push({ - id: `${version}-journal`, + id: `${version}-journal-pdf`, label: `${version} Journal PDF`, title: `Download journal for version ${version}`, - href: `${pdfUrl}?journal=true${token ? `&token=${token}` : ''}`, /* eslint-disable-next-line react/display-name */ - render: (props) => ( - + render: (p) => ( + + {p.menuItem.label} + ) }); } @@ -207,7 +250,6 @@ export default function DocumentDownloadMenu(props) { id: `${version}-document`, label: `${version} Document PDF`, title: `Download document for version ${version}`, - href: `${pdfUrl}${token ? `?token=${token}` : ''}`, /* eslint-disable-next-line react/display-name */ render: (p) => { return ( @@ -234,10 +276,10 @@ export default function DocumentDownloadMenu(props) { }, [ hideText, variation, - token, atbd, canDownloadJournalPdf, - handlePdfDownloadClick + handlePdfDownloadClick, + handleJournalPdfDownloadClick ]); return ( diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 1a4917dc..40b5d1e2 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -1,18 +1,11 @@ import T from 'prop-types'; -import React, { - useEffect, - useRef, - useState, - useContext, - useMemo, - Fragment -} from 'react'; +import React, { useEffect, useRef, useState, useContext, useMemo } from 'react'; import { useParams } from 'react-router'; import styled from 'styled-components'; import { useSingleAtbd } from '../../../context/atbds-list'; import { useUser } from '../../../context/user'; -import { resolveTitle } from '../../../utils/common'; +import { isTruthyString, resolveTitle } from '../../../utils/common'; import { SafeReadEditor } from '../../slate'; import { HeadingNumberingContext, @@ -23,6 +16,36 @@ import { useNumberingProviderValue } from '../../../context/numbering'; import { IMAGE_BLOCK, TABLE_BLOCK } from '../../slate/plugins/constants'; +import serializeToString from '../../slate/serialize-to-string'; +import { getContactName } from '../../contacts/contact-utils'; + +const DataList = styled.dl` + display: flex; + gap: 0.5rem; + + dt { + font-weight: bold; + + ::after { + content: ': '; + } + } + + dd { + word-break: break-word; + flex-grow: 1; + } +`; + +const DataListContainer = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + + dt { + font-weight: bold; + } +`; const PreviewContainer = styled.div` @media print { @@ -37,18 +60,25 @@ const PreviewContainer = styled.div` margin: 15mm; display: flex; flex-direction: column; - gap: 2rem; + gap: 3rem; `; const DocumentHeading = styled.h1` margin: 0; `; +const AuthorsSection = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; +`; + const SectionContainer = styled.section` display: flex; flex-direction: column; gap: 1rem; - break-inside: avoid; + break-before: ${(props) => (props.breakBeforePage ? 'page' : undefined)}; `; const SectionContent = styled.div` @@ -57,10 +87,13 @@ const SectionContent = styled.div` gap: 1rem; `; -function Section({ id, children, title, skipNumbering }) { - const { register, getNumbering, level = 1, numberingFromParent } = useContext( - HeadingNumberingContext - ); +function Section({ id, children, title, skipNumbering, breakBeforePage }) { + const { + register, + getNumbering, + level = 1, + numberingFromParent + } = useContext(HeadingNumberingContext); const headingNumberContextValue = useHeadingNumberingProviderValue(); const numbering = getNumbering(id, numberingFromParent); @@ -84,15 +117,16 @@ function Section({ id, children, title, skipNumbering }) { return null; } - const H = `h${level + 1}`; + const H = `h${Math.min((level ?? 0) + 1, 6)}`; return ( - + {numbering} {title} {children} +
); @@ -102,19 +136,26 @@ Section.propTypes = { id: T.string, title: T.node, children: T.node, + breakBeforePage: T.bool, skipNumbering: T.bool }; +const EMPTY_CONTENT_MESSAGE = 'Content not available'; + function ImplementationDataList({ list }) { + if (!list || list.length === 0) { + return EMPTY_CONTENT_MESSAGE; + } + return ( -
+ {list?.map(({ url, description }) => ( - +
{url}
{description}
- +
))} -
+ ); } @@ -127,6 +168,10 @@ ImplementationDataList.propTypes = { ) }; +function hasContent(content) { + return isTruthyString(serializeToString(content)); +} + const emptyAtbd = { document: {} }; function JournalPdfPreview() { @@ -167,7 +212,7 @@ function JournalPdfPreview() { const atbd = atbdResponse?.data ?? emptyAtbd; - const { keywords, document } = atbd; + const { keywords, document, contacts_link } = atbd; const { key_points, @@ -248,18 +293,88 @@ function JournalPdfPreview() { // eslint-disable-next-line react/display-name, react/prop-types return ({ value }) => ( - + ); }, [atbd, publication_references]); + const threeKeyPoints = key_points + ?.split('\n') + .filter(isTruthyString) + .slice(0, 3); + + const contacts = useMemo(() => { + return contacts_link?.reduce( + (acc, contact_link, i) => { + const { contact, affiliations } = contact_link; + + const hasAffiliation = affiliations && affiliations.length > 0; + if (hasAffiliation) { + acc.affiliations_list.push(affiliations); + } + + const item = ( + + {getContactName(contact, { full: true })} + {hasAffiliation && {acc.affiliations_list.length}} + {i < acc.maxIndex && , } + {i === acc.maxIndex - 1 && and } + + ); + + acc.items.push(item); + + return acc; + }, + { + affiliations_list: [], + items: [], + maxIndex: (contacts_link.length ?? 0) - 1 + } + ); + }, [contacts_link]); + return ( {resolveTitle(atbd.title)} + +
{contacts?.items}
+
+ {contacts?.affiliations_list.map((affiliations, i) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {i + 1} {affiliations.join(', ')} +
+ ))} +
+
- {key_points} +
    + {threeKeyPoints?.map((keyPoint) => ( +
  • {keyPoint}
  • + ))} +
+
+ +
Version
+
{version}
+
+ +
Release Date
+
TBD
+
+ +
DOI
+
TBD
+
+
@@ -269,13 +384,13 @@ function JournalPdfPreview() { skipNumbering > + {keywords && keywords.length > 0 && ( + +
Keywords:
+
{keywords.map(({ label }) => label).join(', ')}
+
+ )}
- {keywords && keywords.length > 0 && ( -
-
Keywords:
-
{keywords.map(({ label }) => label).join(', ')}
-
- )}
-
+
@@ -326,12 +441,14 @@ function JournalPdfPreview() {
-
- -
+ {hasContent(algorithm_usage_constraints) && ( +
+ +
+ )}
+ {/* TODO: Contact Details, References */} {previewReady &&
} diff --git a/app/assets/scripts/components/documents/pdf-preview/index.js b/app/assets/scripts/components/documents/pdf-preview/index.js index 688368ae..89661041 100644 --- a/app/assets/scripts/components/documents/pdf-preview/index.js +++ b/app/assets/scripts/components/documents/pdf-preview/index.js @@ -271,9 +271,6 @@ function PdfPreview() { id='table-of-contents' />
-
- This is the header for print -
diff --git a/app/assets/scripts/components/slate/plugins/list/index.js b/app/assets/scripts/components/slate/plugins/list/index.js index f6bdd104..fa654891 100644 --- a/app/assets/scripts/components/slate/plugins/list/index.js +++ b/app/assets/scripts/components/slate/plugins/list/index.js @@ -14,7 +14,7 @@ import { } from '@udecode/slate-plugins'; import { modKey } from '../common/utils'; -import { TABLE_BLOCK } from '../table'; +import { TABLE_BLOCK } from '../constants'; const Ul = styled.ul` list-style: initial; diff --git a/app/assets/scripts/context/numbering.js b/app/assets/scripts/context/numbering.js index 550b31fa..eff391e8 100644 --- a/app/assets/scripts/context/numbering.js +++ b/app/assets/scripts/context/numbering.js @@ -78,7 +78,7 @@ export function useNumberingProviderValue() { return ''; } - return `Image ${numbering}: `; + return `Figure ${numbering}: `; }, [registeredImages] ); diff --git a/app/assets/scripts/styles/global.js b/app/assets/scripts/styles/global.js index 0f237411..4dc89fba 100644 --- a/app/assets/scripts/styles/global.js +++ b/app/assets/scripts/styles/global.js @@ -7,13 +7,6 @@ export default createGlobalStyle` :is(dd, td, li) .slate-p { text-indent: initial!important; } - - .print-page-header { - position: fixed; - top: 10mm; - right: 10mm; - border: 1px solid red; - } } ${reactTippyStyles()} From 80363264d4b6cc5f48fbd8f952200b08abf639eb Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Tue, 29 Aug 2023 15:02:25 +0545 Subject: [PATCH 04/27] Update end-point for journal pdf download --- .../components/documents/document-download-menu.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/scripts/components/documents/document-download-menu.js b/app/assets/scripts/components/documents/document-download-menu.js index b765c195..c3d8fe4f 100644 --- a/app/assets/scripts/components/documents/document-download-menu.js +++ b/app/assets/scripts/components/documents/document-download-menu.js @@ -41,7 +41,8 @@ export default function DocumentDownloadMenu(props) { // Temporarily disable journal PDF downloading https://github.com/nasa-impact/nasa-apt/issues/744 // const ability = useContextualAbility(); //const canDownloadJournalPdf = ability.can('download-journal-pdf', atbd); - const [canDownloadJournalPdf, setCanDownloadJournalPdf] = React.useState(false); + const [canDownloadJournalPdf, setCanDownloadJournalPdf] = + React.useState(false); React.useEffect(() => { async function fetchBootstrap() { @@ -75,7 +76,10 @@ export default function DocumentDownloadMenu(props) { ); } - const retryUrl = retryCount === 0 ? `${url}?retry=true` : url; + const retryUrl = + retryCount === 0 + ? `${url}${url.includes('?') ? '&' : '?'}retry=true` + : url; try { let user; @@ -204,7 +208,7 @@ export default function DocumentDownloadMenu(props) { ); const { id, version, alias } = atbd; - const pdfUrl = `${apiUrl}/atbds/${id}/versions/${version}/journal-pdf`; + const pdfUrl = `${apiUrl}/atbds/${id}/versions/${version}/pdf?journal=true`; const pdfFileName = `Journal-${alias}-v${version}.pdf`; fetchPdf(pdfUrl, pdfFileName, processToast); From 013cfc66b177fa2e507c85586dc623483fb9d471 Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 31 Aug 2023 00:36:13 +0300 Subject: [PATCH 05/27] update text for pdf mode --- .../documents/document-tracker-modal.js | 168 ++++++++++++------ 1 file changed, 115 insertions(+), 53 deletions(-) diff --git a/app/assets/scripts/components/documents/document-tracker-modal.js b/app/assets/scripts/components/documents/document-tracker-modal.js index 910157d2..6b92647b 100644 --- a/app/assets/scripts/components/documents/document-tracker-modal.js +++ b/app/assets/scripts/components/documents/document-tracker-modal.js @@ -210,6 +210,116 @@ export default function DocumentTrackerModal(props) { [atbd] ); + const pdfMode = atbd?.document_type === 'PDF'; + + const infoText = pdfMode ? ( + +

+ The form on this page is divided into several steps and contains all the + fields needed to create a document. You can move freely between steps + and add content in any order. Invite other users to help with the + writing process using the collaborator options. Only you, APT curators, + and invited users will have access to this document. +

+

+ A document goes through different stages, each with different required + actions. The progress tracker below offers an overview of all the + stages, as well as the current progress. +

+
+ ) : ( + +

+ The form on this page is divided into several steps and contains all the + fields needed to create a document. You can freely move between steps + and add content in any order you like. Invite other users to help with + the writing process using the collaborator options. Only you, the APT + Curators, and invited users will have access to this document. +

+

+ A document goes through different stages, each one with different + actions required. The progress tracker below offers an overview of all + the stages as well as the current progress. +

+
+ ); + + const draftText = pdfMode ? ( +

+ The Draft stage is where the majority of document creation work occurs. + The author can download an ATBD template and upload the completed document + as a PDF. Authors can complete different document steps in any order and + add metadata content to the ATBD. Once completed, the Lead Author can + submit the document for review. +

+ ) : ( +

+ The Draft stage is where most of the document creation work happens. + Authors will be able to go through the different document steps in any + order they please and add content to the ATBD. Once done the Lead Author + will submit the document for review. +

+ ); + + const closedReviewText = pdfMode ? ( + +

+ During the closed review stage, the reviewers assigned to the document + review it and leave comments with notes. Authors can read and reply to + the comments. Once the reviewers complete their review, the document + will be transitioned to Open Review. +

+ +

+ If the ATBD is reviewed externally, the author can email the curator to + alert them that the document has been reviewed outside the APT + interface. The curator will contact the reviewers to confirm that the + reviewers have reviewed and approved the ATBD. The curator will then + review the ATBD for completeness. If the ATBD is incomplete, the curator + will contact the lead author for changes. If all required elements are + there, the curator will close the review. The Lead Author can submit the + document for publication. +

+
+ ) : ( +

+ During the closed review stage, the edition of the document is blocked. + The reviewers that the curator assigned to the document are going through + it and leaving comments with their notes. The authors can read and reply + the comments but can make no changes to the document. +

+ ); + + const inReviewText = pdfMode ? ( +

+ In Open Review, reviewers and authors work together iteratively to address + any issues. Authors must make the necessary edits and re-upload their + ATBD. When all the comments are marked as resolved, the Lead Author can + submit the document for publication. +

+ ) : ( +

+ With the Open Review the document is editable again and reviewers and + authors work together iteratively to address any issues. When all the + comments are marked as resolved the Lead Author can submit the document + for publication (curation). +

+ ); + + const publishedText = pdfMode ? ( +

+ At this point, the document is published in APT and publicly available to + all users. An icon will be displayed if the document is also published to + the journal. +

+ ) : ( +

+ At this point, the document is published in APT and publicly available to + all users. If the document is also published to the journal, an icon is + displayed to convey this information. +

+ ); + return ( - {isWelcome && ( - -

- The form on this page is divided into several steps and contains - all the fields needed to create a document. You can freely move - between steps and add content in any order you like. Invite - other users to help with the writing process using the - collaborator options. Only you, the APT Curators, and invited - users will have access to this document. -

-

- A document goes through different stages, each one with - different actions required. The progress tracker below offers an - overview of all the stages as well as the current progress. -

-
- )} + {isWelcome && infoText} {({ checkExpanded, setExpanded }) => ( @@ -250,15 +344,7 @@ export default function DocumentTrackerModal(props) { title={`${getDocumentStatusLabel(DRAFT)}${ isDraft(atbd) ? `: ${percentDraft}%` : '' }`} - content={ -

- The Draft stage is where most of the document creation - work happens. Authors will be able to go through the - different document steps in any order they please and - add content to the ATBD. Once done the Lead Author will - submit the document for review. -

- } + content={draftText} /> - During the closed review stage, the edition of the - document is blocked. The reviewers that the curator - assigned to the document are going through it and - leaving comments with their notes. The authors can read - and reply the comments but can make no changes to the - document. -

- } + content={closedReviewText} />
@@ -302,15 +379,7 @@ export default function DocumentTrackerModal(props) { setExpanded={setExpanded} swatchColor={statusMapping[OPEN_REVIEW]} title={getDocumentStatusLabel(OPEN_REVIEW)} - content={ -

- With the Open Review the document is editable again and - reviewers and authors work together iteratively to - address any issues. When all the comments are marked as - resolved the Lead Author can submit the document for - publication (curation). -

- } + content={inReviewText} /> - At this point, the document is published in APT and - publicly available to all users. If the document is also - published to the journal, an icon is displayed to convey - this information. -

- } + content={publishedText} />
From ea8bc10ba8a9e5739731f75ddd5418a1a66f129d Mon Sep 17 00:00:00 2001 From: Tarashish Mishra Date: Thu, 31 Aug 2023 12:57:02 +0530 Subject: [PATCH 06/27] Match cases where retry=true is not the first query param --- .../scripts/components/documents/document-download-menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/scripts/components/documents/document-download-menu.js b/app/assets/scripts/components/documents/document-download-menu.js index c3d8fe4f..41f02eb7 100644 --- a/app/assets/scripts/components/documents/document-download-menu.js +++ b/app/assets/scripts/components/documents/document-download-menu.js @@ -126,7 +126,7 @@ export default function DocumentDownloadMenu(props) { if ( response.status === 404 && response.headers.get('content-type') === 'application/json' && - url.includes('?retry=true') + url.includes('retry=true') ) { if (retryCount < maxRetries) { setTimeout(() => { From 742f3eb7c69a33bbec83a8e1553adc1dc747e156 Mon Sep 17 00:00:00 2001 From: Ankit Mehta Date: Thu, 31 Aug 2023 14:16:13 +0545 Subject: [PATCH 07/27] Fix failing build due to wrong import (#541) --- app/assets/scripts/components/slate/safe-read-editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/scripts/components/slate/safe-read-editor.js b/app/assets/scripts/components/slate/safe-read-editor.js index 92110c6b..6f6bc159 100644 --- a/app/assets/scripts/components/slate/safe-read-editor.js +++ b/app/assets/scripts/components/slate/safe-read-editor.js @@ -3,10 +3,11 @@ import T from 'prop-types'; import { ReadEditor } from './editor'; import { RichContextProvider } from './plugins/common/rich-context'; -import { IMAGE, IMAGE_BLOCK } from './plugins/image'; +import { IMAGE } from './plugins/image'; import { removeNodeFromSlateDocument } from './nodes-from-slate'; import serializeToString from './serialize-to-string'; import { isTruthyString } from '../../utils/common'; +import { IMAGE_BLOCK } from './plugins/constants'; export default class SafeReadEditor extends React.Component { static getDerivedStateFromError(error) { From 999920b2c3c1e762de31b5ae30a600ac26ade22f Mon Sep 17 00:00:00 2001 From: Ankit Mehta Date: Thu, 7 Sep 2023 20:13:19 +0545 Subject: [PATCH 08/27] Fix MFA prompt when user initially skips it (#542) --- app/assets/scripts/a11n/signin.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/scripts/a11n/signin.js b/app/assets/scripts/a11n/signin.js index d81f6f00..17f144cf 100644 --- a/app/assets/scripts/a11n/signin.js +++ b/app/assets/scripts/a11n/signin.js @@ -114,7 +114,10 @@ function SignIn() { const loggedUser = await Auth.signIn(email, password); setUser(loggedUser); - if (loggedUser.preferredMFA === 'NOMFA') { + if ( + loggedUser.challengeName === 'MFA_SETUP' || + loggedUser.preferredMFA === 'NOMFA' + ) { toastRef.current.update('Please setup OTP using Authenticator app.'); const secretCode = await Auth.setupTOTP(loggedUser); setMfaCode(secretCode); From befb13c4b5eb362574a1c72d96b5a10d786d9e45 Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Tue, 12 Sep 2023 13:09:20 +0200 Subject: [PATCH 09/27] get errors from server --- .../documents/single-edit/use-document-create.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/assets/scripts/components/documents/single-edit/use-document-create.js b/app/assets/scripts/components/documents/single-edit/use-document-create.js index e0ac6a41..75378407 100644 --- a/app/assets/scripts/components/documents/single-edit/use-document-create.js +++ b/app/assets/scripts/components/documents/single-edit/use-document-create.js @@ -18,7 +18,20 @@ export function useDocumentCreate(title, alias, isPdfType) { const result = await createAtbd(title, alias, isPdfType); if (result.error) { - processToast.error(`An error occurred: ${result.error.message}`); + if (result.error.response) { + const { status } = result.error.response; + if (status === 400) { + processToast.error( + `An error occurred: ${result.error.response.data.detail}` + ); + } else { + processToast.error( + `An error occurred: ${result.error.response.statusText}` + ); + } + } else { + processToast.error(`An error occurred: ${result.error.message}`); + } } else { processToast.success('Document successfully created'); // To trigger the modals to open from other pages, we use the history From 187b46b05ca6508c4de1465c5fcd9e01cce1cfd6 Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Sun, 17 Sep 2023 10:09:08 +0545 Subject: [PATCH 10/27] Add reviewer info in the PDF type atbd --- .../documents/single-view/document-body.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/app/assets/scripts/components/documents/single-view/document-body.js b/app/assets/scripts/components/documents/single-view/document-body.js index 8631a62a..ddc1c6df 100644 --- a/app/assets/scripts/components/documents/single-view/document-body.js +++ b/app/assets/scripts/components/documents/single-view/document-body.js @@ -1283,6 +1283,68 @@ const pdfAtbdContentSections = [ })); } }, + { + label: 'Reviewer Information', + id: 'reviewer_info', + shouldRender: ({ atbd }) => { + if (!atbd || !atbd.reviewer_info) { + return false; + } + + const { + reviewer_info: { first_name, last_name } + } = atbd; + + if (!isTruthyString(first_name) && !isTruthyString(last_name)) { + return false; + } + + return true; + }, + render: ({ element, atbd, printMode }) => { + if (!atbd || !atbd.reviewer_info) { + return null; + } + + const { + reviewer_info: { first_name, last_name, email, affiliations } + } = atbd; + + let fullName; + if (isTruthyString(first_name) || isTruthyString(last_name)) { + fullName = [first_name, last_name].filter(isDefined).join(' '); + } + + if (!isTruthyString(fullName) && !isTruthyString(email)) { + return null; + } + + return ( + + +

{fullName}

+ +
Email
+
{email}
+
Affiliations
+ {affiliations.length ? ( +
{renderMultipleStringValues(affiliations)}
+ ) : ( +
No affiliations for the reviewer
+ )} +
+
+
+ ); + } + }, { label: 'References', id: 'references', From 86c9ffea9bb2dfbdc87eb2cdf0572cc4f574eaa1 Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Tue, 19 Sep 2023 01:07:58 +0545 Subject: [PATCH 11/27] Hide sections without content in journal pdf --- .../documents/journal-pdf-preview/index.js | 316 +++++++++++++----- .../components/slate/serialize-to-string.js | 8 +- 2 files changed, 231 insertions(+), 93 deletions(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 40b5d1e2..4ea7c24f 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -50,14 +50,21 @@ const DataListContainer = styled.div` const PreviewContainer = styled.div` @media print { @page { - size: portrait; + size: A4 portrait; margin: 15mm; } } + @media screen { + margin: 0 auto; + background-color: #fff; + margin: 1rem auto; + padding: 15mm; + border: 1px solid rgba(0, 0, 0, 0.1); + width: 210mm; + } + font-family: serif; - width: 210mm; - margin: 15mm; display: flex; flex-direction: column; gap: 3rem; @@ -88,12 +95,9 @@ const SectionContent = styled.div` `; function Section({ id, children, title, skipNumbering, breakBeforePage }) { - const { - register, - getNumbering, - level = 1, - numberingFromParent - } = useContext(HeadingNumberingContext); + const { register, getNumbering, level = 1, numberingFromParent } = useContext( + HeadingNumberingContext + ); const headingNumberContextValue = useHeadingNumberingProviderValue(); const numbering = getNumbering(id, numberingFromParent); @@ -191,6 +195,7 @@ function JournalPdfPreview() { useEffect(() => { async function waitForImages() { const images = contentRef.current.querySelectorAll('img'); + const promises = Array.from(images).map((image) => { return new Promise((accept) => { image.addEventListener('load', () => { @@ -338,6 +343,73 @@ function JournalPdfPreview() { ); }, [contacts_link]); + if (!atbdResponse.data) { + return
Loading...
; + } + + const historicalPerspectiveVisible = hasContent(historical_perspective); + const additionalInformationVisible = hasContent(additional_information); + const contextBackgroundVisible = + historicalPerspectiveVisible || additionalInformationVisible; + + const scientificTheoryAssumptionsVisible = hasContent( + scientific_theory_assumptions + ); + const scientificTheoryVisible = + hasContent(scientific_theory) || scientificTheoryAssumptionsVisible; + const mathematicalTheoryAssumptionsVisible = hasContent( + mathematical_theory_assumptions + ); + const mathematicalTheoryVisible = + hasContent(mathematical_theory) || mathematicalTheoryAssumptionsVisible; + const algorithmInputVariablesVisible = hasContent(algorithm_input_variables); + const algorithmOutputVariablesVisible = hasContent( + algorithm_output_variables + ); + const algorithmDescriptionVisible = + scientificTheoryVisible || + mathematicalTheoryVisible || + algorithmInputVariablesVisible || + algorithmOutputVariablesVisible; + + const algorithmUsageConstraintsVisible = hasContent( + algorithm_usage_constraints + ); + + const validationMethodsVisible = hasContent( + performance_assessment_validation_methods + ); + const uncertainitiesVisible = hasContent( + performance_assessment_validation_uncertainties + ); + const validationErrorsVisible = hasContent( + performance_assessment_validation_errors + ); + const performanceAssessmentVisible = + validationMethodsVisible || + uncertainitiesVisible || + validationErrorsVisible; + + const algorithmAvailabilityVisible = + algorithm_implementations && algorithm_implementations.length > 0; + const inputDataAccessVisible = + data_access_input_data && data_access_input_data.length > 0; + const outputDataAccessVisible = + data_access_output_data && data_access_output_data.length > 0; + const importantRelatedUrlsVisible = + data_access_related_urls && data_access_related_urls.length > 0; + const algorithmImplementationVisible = + algorithmAvailabilityVisible || + inputDataAccessVisible || + outputDataAccessVisible || + importantRelatedUrlsVisible; + + const journalDiscussionVisible = hasContent(journal_discussion); + + const openResearchVisible = hasContent(data_availability); + + const journalAcknowledgementsVisible = hasContent(journal_acknowledgements); + return ( @@ -401,47 +473,73 @@ function JournalPdfPreview() {
-
-
- -
-
- -
-
-
-
- -
- -
-
-
- -
- -
-
-
- + {contextBackgroundVisible && ( +
+ {historicalPerspectiveVisible && ( +
+ +
+ )} + {additionalInformationVisible && ( +
+ +
+ )}
-
- + )} + {algorithmDescriptionVisible && ( +
+ {scientificTheoryVisible && ( +
+ + {scientificTheoryAssumptionsVisible && ( +
+ +
+ )} +
+ )} + {mathematicalTheoryVisible && ( +
+ + {mathematicalTheoryAssumptionsVisible && ( +
+ +
+ )} +
+ )} + {algorithmInputVariablesVisible && ( +
+ +
+ )} + {algorithmOutputVariablesVisible && ( +
+ +
+ )}
-
- {hasContent(algorithm_usage_constraints) && ( + )} + {algorithmUsageConstraintsVisible && (
)} -
-
- -
-
- + {performanceAssessmentVisible && ( +
+ {validationMethodsVisible && ( +
+ +
+ )} + {uncertainitiesVisible && ( +
+ +
+ )} + {validationErrorsVisible && ( +
+ +
+ )}
+ )} + {algorithmImplementationVisible && (
- -
-
-
-
- + {algorithmAvailabilityVisible && ( +
+ +
+ )} + {inputDataAccessVisible && ( +
+ +
+ )} + {outputDataAccessVisible && ( +
+ +
+ )} + {importantRelatedUrlsVisible && ( + + )}
-
- + )} + {journalDiscussionVisible && ( +
+
-
- + )} + {openResearchVisible && ( +
+
- -
- -
-
- -
-
- -
+ )} {/* TODO: Contact Details, References */} {previewReady &&
} diff --git a/app/assets/scripts/components/slate/serialize-to-string.js b/app/assets/scripts/components/slate/serialize-to-string.js index 060aa60c..2be28f16 100644 --- a/app/assets/scripts/components/slate/serialize-to-string.js +++ b/app/assets/scripts/components/slate/serialize-to-string.js @@ -13,12 +13,18 @@ import { TABLE } from './plugins/table'; * @returns string */ const serializeToString = (node) => { - if (!node) return ''; + if (!node) { + return ''; + } if (Text.isText(node)) { return node.text.trim(); } + if (!node.children) { + return ''; + } + const children = node.children.map((n) => serializeToString(n)).join(''); switch (node.type) { From e161d5bf891829e506769d85c02ebc4bbd5fecc7 Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Tue, 19 Sep 2023 01:36:13 +0545 Subject: [PATCH 12/27] Add contacts and references section in journal pdf export --- .../documents/journal-pdf-preview/index.js | 148 +++++++++++++++++- 1 file changed, 144 insertions(+), 4 deletions(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 4ea7c24f..0603940d 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -18,6 +18,40 @@ import { import { IMAGE_BLOCK, TABLE_BLOCK } from '../../slate/plugins/constants'; import serializeToString from '../../slate/serialize-to-string'; import { getContactName } from '../../contacts/contact-utils'; +import { + createDocumentReferenceIndex, + formatReference, + sortReferences +} from '../../../utils/references'; + +const ReferencesList = styled.ol` + && { + list-style: none; + margin: 0; + line-height: 2.5; + } +`; + +const IndentedContainer = styled.div` + padding-left: 1rem; +`; + +const DataListInline = styled.dl` + dt { + display: inline; + text-decoration: underline; + + ::after { + content: ': '; + } + } + + dd { + display: inline; + word-break: break-word; + flex-grow: 1; + } +`; const DataList = styled.dl` display: flex; @@ -95,9 +129,12 @@ const SectionContent = styled.div` `; function Section({ id, children, title, skipNumbering, breakBeforePage }) { - const { register, getNumbering, level = 1, numberingFromParent } = useContext( - HeadingNumberingContext - ); + const { + register, + getNumbering, + level = 1, + numberingFromParent + } = useContext(HeadingNumberingContext); const headingNumberContextValue = useHeadingNumberingProviderValue(); const numbering = getNumbering(id, numberingFromParent); @@ -172,6 +209,60 @@ ImplementationDataList.propTypes = { ) }; +function ContactOutput(props) { + const { data } = props; + const { affiliations, contact, roles } = data; + + const name = [contact.first_name, contact.last_name] + .filter(Boolean) + .join(', '); + + return ( +
+
{name}
+ + {isTruthyString(contact.uuid) && ( + +
UUID
+
{contact.uuid}
+
+ )} + {isTruthyString(contact.url) && ( + +
URL
+
{contact.url}
+
+ )} + +
Contact mechanism
+
+ {contact.mechanisms + ?.map( + ({ mechanism_type, mechanism_value }) => + `${mechanism_type}: ${mechanism_value}` + ) + .join(', ')} +
+
+ +
Role(s) related to this ATBD
+
{roles?.join(', ')}
+
+ {affiliations && ( + +
Affiliation
+
{affiliations.join(', ')}
+
+ )} +
+
+ ); +} + +ContactOutput.propTypes = { + data: T.object +}; + function hasContent(content) { return isTruthyString(serializeToString(content)); } @@ -343,6 +434,37 @@ function JournalPdfPreview() { ); }, [contacts_link]); + const referencesUseIndex = useMemo( + () => createDocumentReferenceIndex(document), + [document] + ); + + const referenceList = useMemo( + () => + Object.values(referencesUseIndex) + .sort(function (a, b) { + const refA = (document.publication_references || []).find( + (r) => r.id === a.refId + ); + const refB = (document.publication_references || []).find( + (r) => r.id === b.refId + ); + + return sortReferences(refA, refB); + }) + .map(({ refId }) => { + const ref = (document.publication_references || []).find( + (r) => r.id === refId + ); + return ( +
  • + {ref ? formatReference(ref, 'jsx') : 'Reference not found'} +
  • + ); + }), + [referencesUseIndex, document.publication_references] + ); + if (!atbdResponse.data) { return
    Loading...
    ; } @@ -410,6 +532,10 @@ function JournalPdfPreview() { const journalAcknowledgementsVisible = hasContent(journal_acknowledgements); + const contactSectionVisible = contacts_link && contacts_link.length > 0; + + const referencesVisible = referenceList && referenceList.length > 0; + return ( @@ -632,7 +758,21 @@ function JournalPdfPreview() {
    )} - {/* TODO: Contact Details, References */} + {contactSectionVisible && ( +
    + {contacts_link.map((contactLink) => ( + + ))} +
    + )} + {referencesVisible && ( +
    + {referenceList} +
    + )} {previewReady &&
    } From e05b30b34c3e88b9b9bdce425e0723a5d397be7d Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Tue, 26 Sep 2023 10:36:25 +0300 Subject: [PATCH 13/27] Add MFA tooltip --- app/assets/scripts/a11n/signin.js | 29 ++++++++++++++++++- .../common/forms/form-info-tooltip.js | 4 +-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/assets/scripts/a11n/signin.js b/app/assets/scripts/a11n/signin.js index 17f144cf..666d0191 100644 --- a/app/assets/scripts/a11n/signin.js +++ b/app/assets/scripts/a11n/signin.js @@ -24,6 +24,7 @@ import { SectionFieldset } from '../components/common/forms/section-fieldset'; import { FormikInputText } from '../components/common/forms/input-text'; import { createProcessToast } from '../components/common/toasts'; import { useUser } from '../context/user'; +import FormInfoTip from '../components/common/forms/form-info-tooltip'; const OTPModal = styled(Modal)` max-width: 24rem; @@ -192,7 +193,33 @@ function SignIn() { {!mfaEnabled && mfaCode && ( -

    Set-up OTP

    +

    + Set-up OTP + + A one-time password (OTP) increases the security of your APT + account. To use OTP with APT, you'll need to download + an application (sometimes referred to as MFA) that generates + a time-sensitive code (OTP) that is entered upon login. APT + doesn't endorse any particular application, but some + include{' '} + + Google Authenticator + {' '} + (Android, iOS),{' '} + + Microsoft Authenticator + {' '} + (Android, iOS), and{' '} + 1Password (Android, + iOS, Mac, PC, Linux, web browser). Please follow the + respective application's instructions for how to + install and setup the OTP generator. +

    + } + /> +

    Please use the following code or QR to generate Time-based OTP from the Authenticator app. diff --git a/app/assets/scripts/components/common/forms/form-info-tooltip.js b/app/assets/scripts/components/common/forms/form-info-tooltip.js index 1a108348..be512889 100644 --- a/app/assets/scripts/components/common/forms/form-info-tooltip.js +++ b/app/assets/scripts/components/common/forms/form-info-tooltip.js @@ -23,9 +23,9 @@ const TooltipTagComponent = React.forwardRef((props, ref) => ( TooltipTagComponent.displayName = 'TooltipTagComponent'; -export default function FormInfoTip({ title }) { +export default function FormInfoTip({ title, ...rest }) { return ( - + More information ); From 75dd33119b5c9917b37da791fe8c8564ae5fe024 Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 28 Sep 2023 15:02:26 +0300 Subject: [PATCH 14/27] center title in journal PDF --- .../scripts/components/documents/journal-pdf-preview/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 0603940d..afa5a388 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -105,6 +105,7 @@ const PreviewContainer = styled.div` `; const DocumentHeading = styled.h1` + text-align: center; margin: 0; `; From 3d58f2a79f4670d54efa03d0cecc911a91b3db8c Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 28 Sep 2023 15:06:44 +0300 Subject: [PATCH 15/27] rm Version, Release Date and DOI from journal PDF --- .../documents/journal-pdf-preview/index.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index afa5a388..401f428f 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -560,20 +560,6 @@ function JournalPdfPreview() { ))}
    -
    - -
    Version
    -
    {version}
    -
    - -
    Release Date
    -
    TBD
    -
    - -
    DOI
    -
    TBD
    -
    -
    From 26efc4d1a7b37f1f3d54287a4d7dd052c53f9e4c Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 28 Sep 2023 15:09:41 +0300 Subject: [PATCH 16/27] rm double colon in keywords --- .../scripts/components/documents/journal-pdf-preview/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 401f428f..e1a3c3fd 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -571,7 +571,7 @@ function JournalPdfPreview() { {keywords && keywords.length > 0 && ( -
    Keywords:
    +
    Keywords
    {keywords.map(({ label }) => label).join(', ')}
    )} From d7567cc4615e9b801f509dec6d227808fb6322cc Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 28 Sep 2023 18:15:49 +0300 Subject: [PATCH 17/27] Fix affiliations section in header - journal --- .../documents/journal-pdf-preview/index.js | 72 ++++++++++++------- app/assets/scripts/utils/references.js | 2 + 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index e1a3c3fd..02cb64f2 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -112,9 +112,11 @@ const DocumentHeading = styled.h1` const AuthorsSection = styled.div` display: flex; flex-direction: column; - align-items: center; gap: 2rem; `; +const AuthorsSectionHeader = styled.div` + text-align: center; +`; const SectionContainer = styled.section` display: flex; @@ -405,34 +407,56 @@ function JournalPdfPreview() { .slice(0, 3); const contacts = useMemo(() => { - return contacts_link?.reduce( - (acc, contact_link, i) => { - const { contact, affiliations } = contact_link; + // Get list of unique affiliations + let affiliations = new Set(); + contacts_link?.forEach(({ affiliations: contactAffiliations }) => { + contactAffiliations?.forEach((affiliation) => { + affiliations.add(affiliation); + }); + }); + + // create contacts list component with superscripts + let contacts = []; + contacts_link?.forEach( + ({ contact, affiliations: contactAffiliations }, i) => { + const hasAffiliation = + contactAffiliations && contactAffiliations.length > 0; - const hasAffiliation = affiliations && affiliations.length > 0; - if (hasAffiliation) { - acc.affiliations_list.push(affiliations); - } + let contactEmail = contact.mechanisms.find( + (mechanism) => mechanism.mechanism_type === 'Email' + )?.mechanism_value; const item = ( - {getContactName(contact, { full: true })} - {hasAffiliation && {acc.affiliations_list.length}} - {i < acc.maxIndex && , } - {i === acc.maxIndex - 1 && and } + + {getContactName(contact, { full: true })} + {contactEmail && ` (${contactEmail})`} + + {hasAffiliation && + contactAffiliations.map((affiliation, j) => { + return ( + <> + + {Array.from(affiliations).indexOf(affiliation) + 1} + + + {j < contactAffiliations.length - 1 && , } + + + ); + })} + {i < contacts_link.length - 1 && , } + {i === contacts_link.length - 2 && and } ); - - acc.items.push(item); - - return acc; - }, - { - affiliations_list: [], - items: [], - maxIndex: (contacts_link.length ?? 0) - 1 + contacts.push(item); } ); + return { + items: contacts, + affiliations_list: Array.from(affiliations), + maxIndex: (contacts_link?.length ?? 0) - 1 + }; }, [contacts_link]); const referencesUseIndex = useMemo( @@ -543,12 +567,12 @@ function JournalPdfPreview() { {resolveTitle(atbd.title)} -
    {contacts?.items}
    + {contacts?.items}
    - {contacts?.affiliations_list.map((affiliations, i) => ( + {contacts?.affiliations_list.map((affiliation, i) => ( // eslint-disable-next-line react/no-array-index-key
    - {i + 1} {affiliations.join(', ')} + {i + 1} {affiliation}
    ))}
    diff --git a/app/assets/scripts/utils/references.js b/app/assets/scripts/utils/references.js index 8d884f80..cfd71c3b 100644 --- a/app/assets/scripts/utils/references.js +++ b/app/assets/scripts/utils/references.js @@ -126,6 +126,7 @@ function formatAuthors(authors, type = 'reference') { } export function sortReferences(refA, refB) { + if (!refA || !refB) return 0; const hasAuthorsA = 'authors' in refA && refA.authors.length > 0; const hasAuthorsB = 'authors' in refB && refB.authors.length > 0; const hasYearA = 'year' in refA; @@ -232,6 +233,7 @@ export const formatReference = (reference, type = 'jsx') => { }; export function formatCitation(reference) { + if (!reference) return ''; const { authors, year, title } = reference; const authorsStr = formatAuthors(authors, 'citation') || title; const yearStr = year || 'n.d.'; From eb6313a3178eb960a83205b85e1fc3f02fa533b4 Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 28 Sep 2023 18:34:19 +0300 Subject: [PATCH 18/27] bullet point after line breaks in journal --- .../components/documents/journal-pdf-preview/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 02cb64f2..479c3a7b 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -118,6 +118,10 @@ const AuthorsSectionHeader = styled.div` text-align: center; `; +const KeyPoint = styled.li` + list-style: disc; +`; + const SectionContainer = styled.section` display: flex; flex-direction: column; @@ -579,8 +583,8 @@ function JournalPdfPreview() {
      - {threeKeyPoints?.map((keyPoint) => ( -
    • {keyPoint}
    • + {threeKeyPoints.map((keyPoint) => ( + {keyPoint} ))}
    From 6acc581620078cfbf4e596eafea6bd5f4bb114f9 Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 28 Sep 2023 18:40:45 +0300 Subject: [PATCH 19/27] add a break before the abstract --- .../scripts/components/documents/journal-pdf-preview/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 479c3a7b..bd593537 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -588,7 +588,7 @@ function JournalPdfPreview() { ))}
    -
    +
    Date: Tue, 3 Oct 2023 12:08:06 +0100 Subject: [PATCH 20/27] Add table numbers --- .../documents/journal-pdf-preview/index.js | 96 ++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index 0603940d..a14ccd92 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -23,6 +23,7 @@ import { formatReference, sortReferences } from '../../../utils/references'; +import get from 'lodash.get'; const ReferencesList = styled.ol` && { @@ -310,6 +311,99 @@ function JournalPdfPreview() { const { keywords, document, contacts_link } = atbd; + // Section id list in the order they should appear in the document + const documentSectionIds = [ + 'key_points', + 'abstract', + 'plain_summary', + 'publication_references', + 'version_description', + 'introduction', + 'historical_perspective', + 'additional_information', + 'scientific_theory', + 'scientific_theory_assumptions', + 'mathematical_theory', + 'mathematical_theory_assumptions', + 'algorithm_input_variables', + 'algorithm_output_variables', + 'algorithm_usage_constraints', + 'performance_assessment_validation_errors', + 'performance_assessment_validation_methods', + 'performance_assessment_validation_uncertainties', + 'algorithm_implementations', + 'data_access_input_data', + 'data_access_output_data', + 'data_access_related_urls', + 'journal_discussion', + 'data_availability', + 'journal_acknowledgements' + ]; + + // Process sections to add table numbers to captions + const sections = documentSectionIds.reduce( + (doc, sectionId) => { + const section = doc[sectionId]; + + // Ensure the section exists + if (!section) return doc; + + // If the section has no children, return the section as is + if (!section.children) { + return { + ...doc, + [sectionId]: section + }; + } + + // Init table count for this section + let tableCount = doc.tableCount; + + const nextDoc = { + ...doc, + [sectionId]: { + ...section, + children: section.children.map((child) => { + // Ignore non-table blocks + if (child.type !== TABLE_BLOCK) { + return child; + } + + // Reverse the table rows to make caption appear first + // and add the table number to the caption + return { + ...child, + children: child.children.reverse().map((c) => { + if (c.type !== 'caption') { + return c; + } + + const currentCaption = get(c, 'children[0].text'); + tableCount++; + + return { + ...c, + children: [ + { + ...c.children[0], + text: `Table ${tableCount}: ${currentCaption}` + } + ] + }; + }) + }; + }) + } + }; + + return { + ...nextDoc, + tableCount: tableCount + }; + }, + { ...document, tableCount: 0 } + ); + const { key_points, abstract, @@ -336,7 +430,7 @@ function JournalPdfPreview() { journal_discussion, data_availability, journal_acknowledgements - } = document; + } = sections; const ContentView = useMemo(() => { const safeReadContext = { From a68f9acac8a8503cde240e3244417eb49b983911 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 3 Oct 2023 14:23:38 +0100 Subject: [PATCH 21/27] Add table numbers to document pdf --- .../documents/journal-pdf-preview/index.js | 97 +---------------- .../documents/single-view/document-body.js | 4 +- .../scripts/utils/format-table-captions.js | 100 ++++++++++++++++++ 3 files changed, 104 insertions(+), 97 deletions(-) create mode 100644 app/assets/scripts/utils/format-table-captions.js diff --git a/app/assets/scripts/components/documents/journal-pdf-preview/index.js b/app/assets/scripts/components/documents/journal-pdf-preview/index.js index a14ccd92..107b1de1 100644 --- a/app/assets/scripts/components/documents/journal-pdf-preview/index.js +++ b/app/assets/scripts/components/documents/journal-pdf-preview/index.js @@ -23,7 +23,7 @@ import { formatReference, sortReferences } from '../../../utils/references'; -import get from 'lodash.get'; +import { formatDocumentTableCaptions } from '../../../utils/format-table-captions'; const ReferencesList = styled.ol` && { @@ -311,99 +311,6 @@ function JournalPdfPreview() { const { keywords, document, contacts_link } = atbd; - // Section id list in the order they should appear in the document - const documentSectionIds = [ - 'key_points', - 'abstract', - 'plain_summary', - 'publication_references', - 'version_description', - 'introduction', - 'historical_perspective', - 'additional_information', - 'scientific_theory', - 'scientific_theory_assumptions', - 'mathematical_theory', - 'mathematical_theory_assumptions', - 'algorithm_input_variables', - 'algorithm_output_variables', - 'algorithm_usage_constraints', - 'performance_assessment_validation_errors', - 'performance_assessment_validation_methods', - 'performance_assessment_validation_uncertainties', - 'algorithm_implementations', - 'data_access_input_data', - 'data_access_output_data', - 'data_access_related_urls', - 'journal_discussion', - 'data_availability', - 'journal_acknowledgements' - ]; - - // Process sections to add table numbers to captions - const sections = documentSectionIds.reduce( - (doc, sectionId) => { - const section = doc[sectionId]; - - // Ensure the section exists - if (!section) return doc; - - // If the section has no children, return the section as is - if (!section.children) { - return { - ...doc, - [sectionId]: section - }; - } - - // Init table count for this section - let tableCount = doc.tableCount; - - const nextDoc = { - ...doc, - [sectionId]: { - ...section, - children: section.children.map((child) => { - // Ignore non-table blocks - if (child.type !== TABLE_BLOCK) { - return child; - } - - // Reverse the table rows to make caption appear first - // and add the table number to the caption - return { - ...child, - children: child.children.reverse().map((c) => { - if (c.type !== 'caption') { - return c; - } - - const currentCaption = get(c, 'children[0].text'); - tableCount++; - - return { - ...c, - children: [ - { - ...c.children[0], - text: `Table ${tableCount}: ${currentCaption}` - } - ] - }; - }) - }; - }) - } - }; - - return { - ...nextDoc, - tableCount: tableCount - }; - }, - { ...document, tableCount: 0 } - ); - const { key_points, abstract, @@ -430,7 +337,7 @@ function JournalPdfPreview() { journal_discussion, data_availability, journal_acknowledgements - } = sections; + } = formatDocumentTableCaptions(document); const ContentView = useMemo(() => { const safeReadContext = { diff --git a/app/assets/scripts/components/documents/single-view/document-body.js b/app/assets/scripts/components/documents/single-view/document-body.js index ddc1c6df..a3a6ca3d 100644 --- a/app/assets/scripts/components/documents/single-view/document-body.js +++ b/app/assets/scripts/components/documents/single-view/document-body.js @@ -28,6 +28,7 @@ import { isJournalPublicationIntended } from '../status'; import serializeSlateToString from '../../slate/serialize-to-string'; import { useContextualAbility } from '../../../a11n'; import { isDefined, isTruthyString } from '../../../utils/common'; +import { formatDocumentTableCaptions } from '../../../utils/format-table-captions'; const PDFPreview = styled.iframe` width: 100%; @@ -1418,7 +1419,6 @@ export function getAtbdContentSections(pdfMode = false) { export default function DocumentBody(props) { const { atbd, disableScrollManagement } = props; const document = atbd.document; - // Scroll to an existing hash when the component mounts. useScrollToHashOnMount(disableScrollManagement); // Setup the listener to change active links. @@ -1456,7 +1456,7 @@ export default function DocumentBody(props) { ); return renderElements(getAtbdContentSections(atbd.document_type === 'PDF'), { - document, + document: formatDocumentTableCaptions(document), referencesUseIndex, referenceList, atbd, diff --git a/app/assets/scripts/utils/format-table-captions.js b/app/assets/scripts/utils/format-table-captions.js new file mode 100644 index 00000000..609c1579 --- /dev/null +++ b/app/assets/scripts/utils/format-table-captions.js @@ -0,0 +1,100 @@ +import get from 'lodash.get'; +import { TABLE_BLOCK } from '../components/slate/plugins/constants'; + +/** + * Include table numbers and move captions before the table in the document. + */ +export function formatDocumentTableCaptions(document) { + // Section id list in the order they should appear in the document + const documentSectionIds = [ + 'key_points', + 'abstract', + 'plain_summary', + 'publication_references', + 'version_description', + 'introduction', + 'historical_perspective', + 'additional_information', + 'scientific_theory', + 'scientific_theory_assumptions', + 'mathematical_theory', + 'mathematical_theory_assumptions', + 'algorithm_input_variables', + 'algorithm_output_variables', + 'algorithm_usage_constraints', + 'performance_assessment_validation_errors', + 'performance_assessment_validation_methods', + 'performance_assessment_validation_uncertainties', + 'algorithm_implementations', + 'data_access_input_data', + 'data_access_output_data', + 'data_access_related_urls', + 'journal_discussion', + 'data_availability', + 'journal_acknowledgements' + ]; + + // Process sections to add table numbers to captions + return documentSectionIds.reduce( + (doc, sectionId) => { + const section = doc[sectionId]; + + // Ensure the section exists + if (!section) return doc; + + // If the section has no children, return the section as is + if (!section.children) { + return { + ...doc, + [sectionId]: section + }; + } + + // Init table count for this section + let tableCount = doc.tableCount; + + const nextDoc = { + ...doc, + [sectionId]: { + ...section, + children: section.children.map((child) => { + // Ignore non-table blocks + if (child.type !== TABLE_BLOCK) { + return child; + } + + // Reverse the table rows to make caption appear first + // and add the table number to the caption + return { + ...child, + children: child.children.reverse().map((c) => { + if (c.type !== 'caption') { + return c; + } + + const currentCaption = get(c, 'children[0].text'); + tableCount++; + + return { + ...c, + children: [ + { + ...c.children[0], + text: `Table ${tableCount}: ${currentCaption}` + } + ] + }; + }) + }; + }) + } + }; + + return { + ...nextDoc, + tableCount: tableCount + }; + }, + { ...document, tableCount: 0 } + ); +} From 82efd4e87bd51ed69e06a39f0844c4bd91225678 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Wed, 4 Oct 2023 10:05:13 +0100 Subject: [PATCH 22/27] Align captions left --- app/assets/scripts/components/slate/plugins/caption/caption.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/scripts/components/slate/plugins/caption/caption.js b/app/assets/scripts/components/slate/plugins/caption/caption.js index f12245d3..1efad477 100644 --- a/app/assets/scripts/components/slate/plugins/caption/caption.js +++ b/app/assets/scripts/components/slate/plugins/caption/caption.js @@ -10,7 +10,7 @@ import { IMAGE_BLOCK, TABLE_BLOCK } from '../constants'; const CaptionElement = styled.figcaption` font-size: 0.875rem; line-height: 1.25rem; - text-align: center; + text-align: left; position: relative; `; From 5654f85e1f012daff5e8e52644fb733c3f7474e7 Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Wed, 4 Oct 2023 17:16:13 +0300 Subject: [PATCH 23/27] Use same style for view mode & edit mode in lists --- .../components/slate/plugins/list/index.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/slate/plugins/list/index.js b/app/assets/scripts/components/slate/plugins/list/index.js index fa654891..2d6f469e 100644 --- a/app/assets/scripts/components/slate/plugins/list/index.js +++ b/app/assets/scripts/components/slate/plugins/list/index.js @@ -153,10 +153,23 @@ const VUl = styled.ul` `; const VOl = styled.ol` - padding-left: ${glsp(1)}!important; + counter-reset: item; + margin: 0; + padding: 0; > li { - padding-left: ${glsp(1 / 2)}; + display: table; + counter-increment: item; + + &::before { + content: counters(item, '.') '. '; + display: table-cell; + padding-right: ${glsp(1 / 2)}; + } + } + + li ol > li:before { + content: counters(item, '.') ' '; } `; From fa8d95741d8fcd70f44cec8942e8da7b19a30c9c Mon Sep 17 00:00:00 2001 From: Marc Farra Date: Thu, 5 Oct 2023 00:45:22 +0300 Subject: [PATCH 24/27] Add secondary button link to video --- .../components/documents/document-tracker-modal.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/assets/scripts/components/documents/document-tracker-modal.js b/app/assets/scripts/components/documents/document-tracker-modal.js index 6b92647b..42a89198 100644 --- a/app/assets/scripts/components/documents/document-tracker-modal.js +++ b/app/assets/scripts/components/documents/document-tracker-modal.js @@ -331,7 +331,6 @@ export default function DocumentTrackerModal(props) { content={ {isWelcome && infoText} - {({ checkExpanded, setExpanded }) => ( @@ -423,6 +422,15 @@ export default function DocumentTrackerModal(props) { > {isWelcome ? 'Understood' : 'Dismiss'} + + + )} /> From 6d3262dcee4925d172bf322dfc22a682446ed2c0 Mon Sep 17 00:00:00 2001 From: Will Rynearson Date: Thu, 5 Oct 2023 09:44:28 +0200 Subject: [PATCH 25/27] update-mathmatical-theory-tooltips --- content/strings/algorithm_description.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/strings/algorithm_description.yml b/content/strings/algorithm_description.yml index 1576820b..f022df3e 100644 --- a/content/strings/algorithm_description.yml +++ b/content/strings/algorithm_description.yml @@ -15,5 +15,5 @@ algorithm_output_variables: scientific_theory: Provide a description of the scientific principles used in product retrieval including the physics and associated geophysical phenomena observed. scientific_theory_assumptions: What are the assumptions or limitations inherent to the algorithm being described? -mathematical_theory: Provide a description of the mathematical theory essential to algorithm development. -mathematical_theory_assumptions: Provide a description of any mathematical assumptions, simplifications and approximations made when deriving the algorithm. +mathematical_theory: Provide a description of the mathematical theory essential to algorithm development. If this is described in the scientific theory section, summarize it in this section in a couple of sentences. +mathematical_theory_assumptions: Provide a description of any mathematical assumptions, simplifications and approximations made when deriving the algorithm. If this is described in the scientific theory section, summarize it in this section in a couple of sentences. From fe40a2fbb2c335fd22bd0d701885d6c66b4c2229 Mon Sep 17 00:00:00 2001 From: Will Rynearson Date: Thu, 5 Oct 2023 09:54:24 +0200 Subject: [PATCH 26/27] update prompt to include retrieval uncertainties --- content/strings/algorithm_description.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/strings/algorithm_description.yml b/content/strings/algorithm_description.yml index f022df3e..0a62f0d0 100644 --- a/content/strings/algorithm_description.yml +++ b/content/strings/algorithm_description.yml @@ -16,4 +16,4 @@ scientific_theory: Provide a description of the scientific principles used in pr scientific_theory_assumptions: What are the assumptions or limitations inherent to the algorithm being described? mathematical_theory: Provide a description of the mathematical theory essential to algorithm development. If this is described in the scientific theory section, summarize it in this section in a couple of sentences. -mathematical_theory_assumptions: Provide a description of any mathematical assumptions, simplifications and approximations made when deriving the algorithm. If this is described in the scientific theory section, summarize it in this section in a couple of sentences. +mathematical_theory_assumptions: Provide a description of any mathematical assumptions, simplifications and approximations made when deriving the algorithm. If this is described in the scientific theory section, summarize it in this section in a couple of sentences. If you have retrieval uncertainties, please add a new header within this section and describe them. From 9c4695e22527d7391d69d650b5e1ac5f9b0b14e0 Mon Sep 17 00:00:00 2001 From: Will Rynearson Date: Thu, 5 Oct 2023 09:56:36 +0200 Subject: [PATCH 27/27] update performance assessment - uncertainties tooltip --- content/strings/algorithm_usage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/strings/algorithm_usage.yml b/content/strings/algorithm_usage.yml index d53fc12d..70a6c561 100644 --- a/content/strings/algorithm_usage.yml +++ b/content/strings/algorithm_usage.yml @@ -1,4 +1,4 @@ constraints: What are the constraints or limitations on the use of the output data based on the algorithm and various assumptions made in producing the data product? validation_methods: Describe the details of the scientific methods utilized for algorithm validation. Details provided should match the current algorithm maturity. -validation_uncertainties: Describe the uncertainties applicable to the validation methods and data used. This may include uncertainty in scientific or mathematical methods and/or errors associated with observation retrievals used for comparison. +validation_uncertainties: Describe the uncertainties applicable to the validation methods. validation_errors: Provide estimated errors of the output data product variables