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)}
+
+
+
+ {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)}
-
-
-
- {keywords && keywords.length > 0 && (
-
- Keywords:
- {keywords.map(({ label }) => label).join(', ')}
-
- )}
-
-
-
-
-
+
+
+
+ {resolveTitle(atbd.title)}
+
+
+
+ {keywords && keywords.length > 0 && (
+
+ Keywords:
+ {keywords.map(({ label }) => label).join(', ')}
+
+ )}
+
-
-
+ {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) && (
+
+ )}
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() {
-
-
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'}
+
+
+ Learn More About Document Stages
+
+
)}
/>
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