diff --git a/browser/CHANGELOG.md b/browser/CHANGELOG.md index cd2b8654f..9b96bb730 100644 --- a/browser/CHANGELOG.md +++ b/browser/CHANGELOG.md @@ -7,6 +7,7 @@ This changelog covers all three packages, as they are (for now) updated as a who ### Atomic Browser - [#747](https://github.com/atomicdata-dev/atomic-server/issues/747) Show ontology classes on new resource page. +- [#770](https://github.com/atomicdata-dev/atomic-server/issues/770) Display more info on search result page. - Fix server not rebuilding client when files changed. ## v0.36.2 diff --git a/browser/data-browser/src/components/Card.tsx b/browser/data-browser/src/components/Card.tsx index d5b12e897..86dab2d3f 100644 --- a/browser/data-browser/src/components/Card.tsx +++ b/browser/data-browser/src/components/Card.tsx @@ -11,17 +11,19 @@ type CardProps = { /** A Card with a border. */ export const Card = styled.div` background-color: ${props => props.theme.colors.bg}; - /** Don't put side margins in this component - use a wrapping component */ - border: solid 1px ${props => props.theme.colors.bg2}; - box-shadow: ${props => props.theme.boxShadow}; + + border: solid 1px + ${props => + props.highlight ? props.theme.colors.main : props.theme.colors.bg2}; + box-shadow: ${props => + props.highlight + ? `0 0 0 1px ${props.theme.colors.main}, ${props.theme.boxShadow}` + : props.theme.boxShadow}; + padding: ${props => props.theme.margin}rem; - /* margin-bottom: ${props => props.theme.margin}rem; */ - padding-bottom: 0; border-radius: ${props => props.theme.radius}; max-height: ${props => (props.small ? '10rem' : 'none')}; overflow: ${props => (props.small ? 'hidden' : 'visible')}; - border-color: ${props => - props.highlight ? props.theme.colors.main : props.theme.colors.bg2}; ${p => transitionName('resource-page', p.about)}; `; diff --git a/browser/data-browser/src/components/Navigation.tsx b/browser/data-browser/src/components/Navigation.tsx index ba44d15f2..c2e1cb503 100644 --- a/browser/data-browser/src/components/Navigation.tsx +++ b/browser/data-browser/src/components/Navigation.tsx @@ -166,7 +166,6 @@ const NavBarBase = styled.div` /** Width of the floating navbar in rem */ const NavBarFloating = styled(NavBarBase)` box-shadow: ${props => props.theme.boxShadow}; - box-sizing: border-box; border-radius: 999px; overflow: hidden; max-width: calc(100% - 2rem); @@ -179,6 +178,11 @@ const NavBarFloating = styled(NavBarBase)` top: ${props => (props.top ? '2rem' : 'auto')}; bottom: ${props => (props.top ? 'auto' : '1rem')}; + &:has(input:focus) { + box-shadow: 0px 0px 0px 1px ${props => props.theme.colors.main}; + border-color: ${props => props.theme.colors.main}; + } + @media (max-width: ${props => props.theme.containerWidth}rem) { max-width: calc(100% - 1rem); left: auto; @@ -198,6 +202,10 @@ const NavBarFixed = styled(NavBarBase)` props.top ? 'solid 1px ' + props.theme.colors.bg2 : 'none'}; border-top: ${props => !props.top ? 'solid 1px ' + props.theme.colors.bg2 : 'none'}; + + &:has(input:focus) { + box-shadow: 0px 0px 0px 2px ${props => props.theme.colors.main}; + } `; const SideBarWrapper = styled('div')` diff --git a/browser/data-browser/src/components/PropVal.tsx b/browser/data-browser/src/components/PropVal.tsx index 6330ac25d..191cb7507 100644 --- a/browser/data-browser/src/components/PropVal.tsx +++ b/browser/data-browser/src/components/PropVal.tsx @@ -61,7 +61,7 @@ function PropVal({ {editable ? ( - + ) : ( props.theme.colors.bg}; - outline: 0; + // Outline is handled by the Navbar. + outline: none; color: ${p => p.theme.colors.textLight}; `; @@ -172,9 +173,7 @@ const Form = styled.form` border-radius: 999px; :hover { - box-shadow: inset 0 0 0 2px - ${props => transparentize(0.6, props.theme.colors.main)}; - + ${props => transparentize(0.6, props.theme.colors.main)}; ${Input} { color: ${p => p.theme.colors.text}; } @@ -183,8 +182,9 @@ const Form = styled.form` ${Input} { color: ${p => p.theme.colors.text}; } + + // Outline is handled by the Navbar. outline: none; - box-shadow: inset 0 0 0 2px ${props => props.theme.colors.main}; } `; diff --git a/browser/data-browser/src/components/ValueComp.tsx b/browser/data-browser/src/components/ValueComp.tsx index b7a726be6..08662dfc9 100644 --- a/browser/data-browser/src/components/ValueComp.tsx +++ b/browser/data-browser/src/components/ValueComp.tsx @@ -16,11 +16,10 @@ import { ErrMessage } from './forms/InputStyles'; type Props = { value: JSONValue; datatype: Datatype; - noMargin?: boolean; }; /** Renders a value in a fitting way, depending on its DataType */ -function ValueComp({ value, datatype, noMargin }: Props): JSX.Element { +function ValueComp({ value, datatype }: Props): JSX.Element { try { switch (datatype) { case Datatype.ATOMIC_URL: { @@ -36,7 +35,7 @@ function ValueComp({ value, datatype, noMargin }: Props): JSX.Element { case (Datatype.DATE, Datatype.TIMESTAMP): return ; case Datatype.MARKDOWN: - return ; + return ; case Datatype.RESOURCEARRAY: return ; default: diff --git a/browser/data-browser/src/components/datatypes/Markdown.tsx b/browser/data-browser/src/components/datatypes/Markdown.tsx index 9eb792665..354353338 100644 --- a/browser/data-browser/src/components/datatypes/Markdown.tsx +++ b/browser/data-browser/src/components/datatypes/Markdown.tsx @@ -3,30 +3,21 @@ import { styled } from 'styled-components'; import remarkGFM from 'remark-gfm'; import { Button } from '../Button'; import { truncateMarkdown } from '../../helpers/markdown'; -import { useState } from 'react'; +import { FC, useState } from 'react'; type Props = { text: string; - /** - * By default, all bottom Markdown elements have some margin (e.g. the last - * paragraph). If you set noMargin, this is corrected. - */ - noMargin?: boolean; renderGFM?: boolean; /** * If this is set, and the markdown is more characters than this number, the * text will be truncated and a button will be shown */ maxLength?: number; + className?: string; }; /** Renders a markdown value */ -function Markdown({ - text, - noMargin, - renderGFM, - maxLength, -}: Props): JSX.Element | null { +const Markdown: FC = ({ text, renderGFM, maxLength, className }) => { const [collapsed, setCollapsed] = useState(true); maxLength = maxLength || 5000; @@ -36,7 +27,7 @@ function Markdown({ } return ( - + {collapsed ? truncateMarkdown(text, maxLength) : text} @@ -47,19 +38,14 @@ function Markdown({ )} ); -} +}; Markdown.defaultProps = { renderGFM: true, }; -interface MarkdownWrapperProps { - noMargin?: boolean; -} - -const MarkdownWrapper = styled.div` +const MarkdownWrapper = styled.div` /* Corrects the margin added by

and other HTML elements */ - margin-bottom: -${p => (p.noMargin ? p.theme.margin : 0)}rem; width: 100%; overflow-x: hidden; diff --git a/browser/data-browser/src/components/forms/ValueForm.tsx b/browser/data-browser/src/components/forms/ValueForm.tsx index 5d3130910..0864136ac 100644 --- a/browser/data-browser/src/components/forms/ValueForm.tsx +++ b/browser/data-browser/src/components/forms/ValueForm.tsx @@ -26,19 +26,13 @@ interface ValueFormProps { * also override it manually */ datatype?: Datatype; - noMargin?: boolean; } /** * A form for a single Value. Presents a normal value, but let's the user click * on a button to turn it into an input. */ -export function ValueForm({ - resource, - noMargin, - propertyURL, - datatype, -}: ValueFormProps) { +export function ValueForm({ resource, propertyURL, datatype }: ValueFormProps) { const [editMode, setEditMode] = useState(false); const property = useProperty(propertyURL); const [value] = useValue(resource, propertyURL); @@ -86,11 +80,7 @@ export function ValueForm({ if (!editMode) { return ( - + setEditMode(!editMode)} /> diff --git a/browser/data-browser/src/routes/SearchRoute.tsx b/browser/data-browser/src/routes/SearchRoute.tsx index b43ff1cc1..d979baa2a 100644 --- a/browser/data-browser/src/routes/SearchRoute.tsx +++ b/browser/data-browser/src/routes/SearchRoute.tsx @@ -142,7 +142,6 @@ export function Search(): JSX.Element { {results.map((subject, index) => ( - - {title} - + + Open site @@ -27,17 +25,10 @@ export function BookmarkCard({ resource }: CardViewProps): JSX.Element { )} - + ); } -const Title = styled.h2` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.2; -`; - const MarkdownWrapper = styled.div` margin-top: ${p => p.theme.margin}rem; margin-inline: -${p => p.theme.margin}rem; diff --git a/browser/data-browser/src/views/Card/CollectionCard.tsx b/browser/data-browser/src/views/Card/CollectionCard.tsx index 45e280e94..754999b5c 100644 --- a/browser/data-browser/src/views/Card/CollectionCard.tsx +++ b/browser/data-browser/src/views/Card/CollectionCard.tsx @@ -1,12 +1,14 @@ -import { useArray, useString, useTitle, properties } from '@tomic/react'; -import { useState } from 'react'; +import { useArray, useString, core, collections } from '@tomic/react'; +import { FC, PropsWithChildren, useState } from 'react'; import Markdown from '../../components/datatypes/Markdown'; -import { AtomicLink } from '../../components/AtomicLink'; import { CardInsideFull, CardRow } from '../../components/Card'; import { ResourceInline } from '../ResourceInline'; import { CardViewProps } from './CardViewProps'; import { Button } from '../../components/Button'; +import { ResourceCardTitle } from './ResourceCardTitle'; +import { Column } from '../../components/Row'; +import { styled } from 'styled-components'; const MAX_COUNT = 5; @@ -15,9 +17,8 @@ const MAX_COUNT = 5; * (shortname) is rendered prominently at the top. */ function CollectionCard({ resource, small }: CardViewProps): JSX.Element { - const [title] = useTitle(resource); - const [description] = useString(resource, properties.description); - const [members] = useArray(resource, properties.collection.members); + const [description] = useString(resource, core.properties.description); + const [members] = useArray(resource, collections.properties.members); const [showAll, setShowMore] = useState(false); const tooMany = members.length > MAX_COUNT; @@ -28,33 +29,43 @@ function CollectionCard({ resource, small }: CardViewProps): JSX.Element { } return ( - <> - -

{title}

- + + {description && } - {!small && ( - - {subjects.map(member => { - return ( - - + + {subjects.length === 0 ? ( + No resources + ) : ( + + {subjects.map(member => { + return ( + + + + ); + })} + {tooMany && ( + + - ); - })} - {tooMany && ( - - - - )} - - )} - + )} + + )} + + ); } +const Show: FC> = ({ show, children }) => { + return show ? children : null; +}; + +const Empty = styled.span` + color: ${({ theme }) => theme.colors.textLight}; +`; + export default CollectionCard; diff --git a/browser/data-browser/src/views/Card/ElementCard.tsx b/browser/data-browser/src/views/Card/ElementCard.tsx index 3b7dad00d..835355fa1 100644 --- a/browser/data-browser/src/views/Card/ElementCard.tsx +++ b/browser/data-browser/src/views/Card/ElementCard.tsx @@ -1,22 +1,20 @@ -import { urls, useResource, useString, useTitle } from '@tomic/react'; +import { urls, useResource, useString } from '@tomic/react'; -import { AtomicLink } from '../../components/AtomicLink'; import Markdown from '../../components/datatypes/Markdown'; import { CardViewProps } from './CardViewProps'; +import { ResourceCardTitle } from './ResourceCardTitle'; +import { Column } from '../../components/Row'; export function ElementCard({ resource }: CardViewProps): JSX.Element { const [documentSubject] = useString(resource, urls.properties.parent); const document = useResource(documentSubject); - const [documentTitle] = useTitle(document); const [text] = useString(resource, urls.properties.description); return ( - <> - -

{documentTitle}

-
+ + - + ); } diff --git a/browser/data-browser/src/views/Card/ResourceCard.tsx b/browser/data-browser/src/views/Card/ResourceCard.tsx index f6627d3bc..a0a57d188 100644 --- a/browser/data-browser/src/views/Card/ResourceCard.tsx +++ b/browser/data-browser/src/views/Card/ResourceCard.tsx @@ -8,6 +8,7 @@ import { server, dataBrowser, collections, + useArray, } from '@tomic/react'; import AllProps from '../../components/AllProps'; import { AtomicLink } from '../../components/AtomicLink'; @@ -23,8 +24,8 @@ import { CardViewProps, CardViewPropsBase } from './CardViewProps'; import { ElementCard } from './ElementCard'; import { ArticleCard } from '../Article'; import { styled } from 'styled-components'; -import { ViewTransitionProps } from '../../helpers/ViewTransitionProps'; -import { transitionName } from '../../helpers/transitionName'; +import { ResourceCardTitle } from './ResourceCardTitle'; +import { Column } from '../../components/Row'; interface ResourceCardProps extends CardViewPropsBase { /** The subject URL - the identifier of the resource. */ @@ -118,17 +119,20 @@ export function ResourceCardDefault({ resource, small, }: CardViewProps): JSX.Element { + const [isA] = useArray(resource, core.properties.isA); + const isAResource = useResource(isA[0]); + return ( - <> - - - {resource.title} - - - + + + {isAResource.title} + + + + {!small && ( )} - + ); } export default ResourceCard; -const DefaultCardTitle = styled.h2` - ${props => transitionName('page-title', props.subject)} +const DescriptionWrapper = styled.div` + max-height: 10rem; + overflow: hidden; +`; + +const ClassName = styled.span` + margin-left: auto; `; diff --git a/browser/data-browser/src/views/Card/ResourceCardTitle.tsx b/browser/data-browser/src/views/Card/ResourceCardTitle.tsx new file mode 100644 index 000000000..e812c3732 --- /dev/null +++ b/browser/data-browser/src/views/Card/ResourceCardTitle.tsx @@ -0,0 +1,47 @@ +import { Resource, core, useArray } from '@tomic/react'; +import { FC, PropsWithChildren } from 'react'; +import { styled } from 'styled-components'; +import { AtomicLink } from '../../components/AtomicLink'; +import { ViewTransitionProps } from '../../helpers/ViewTransitionProps'; +import { transitionName } from '../../helpers/transitionName'; +import { getIconForClass } from '../FolderPage/iconMap'; +import { Row } from '../../components/Row'; + +interface ResourceCardTitleProps { + resource: Resource; +} + +export const ResourceCardTitle: FC> = + ({ resource, children }) => { + const [isA] = useArray(resource, core.properties.isA); + const Icon = getIconForClass(isA[0]); + + return ( + + + + {resource.title} + + {children} + + ); + }; + +const Title = styled.h2` + font-size: 1.4rem; + margin: 0; + ${props => transitionName('page-title', props.subject)}; + white-space: nowrap; + text-overflow: ellipsis; +`; + +const TitleRow = styled(Row)` + max-width: 100%; + height: 2rem; + overflow: hidden; + color: ${({ theme }) => theme.colors.textLight}; + + svg { + min-width: 1em; + } +`; diff --git a/browser/data-browser/src/views/File/FileCard.tsx b/browser/data-browser/src/views/File/FileCard.tsx index d6f1aa6b2..872febb3b 100644 --- a/browser/data-browser/src/views/File/FileCard.tsx +++ b/browser/data-browser/src/views/File/FileCard.tsx @@ -1,27 +1,50 @@ -import { useTitle } from '@tomic/react'; - +import { useMemo } from 'react'; import { AtomicLink } from '../../components/AtomicLink'; import { Row } from '../../components/Row'; import { useFileInfo } from '../../hooks/useFile'; import { CardViewProps } from '../Card/CardViewProps'; +import { ResourceCardTitle } from '../Card/ResourceCardTitle'; +import { ErrorBoundary } from '../ErrorPage'; import { DownloadIconButton } from './DownloadButton'; import { FilePreview } from './FilePreview'; -function FileCard({ resource }: CardViewProps): JSX.Element { - const [title] = useTitle(resource); +function FileCard(props: CardViewProps): JSX.Element { + const FileError = useMemo(() => { + const Temp = () => { + return ( + <> + + {props.resource.title} + +
Can not show file due to invalid data.
+ + ); + }; + + Temp.displayName = 'FileError'; + + return Temp; + }, [props.resource.getSubject(), props.resource.title]); + + return ( + + + + ); +} + +export default FileCard; + +function FileCardInner({ resource }: CardViewProps): JSX.Element { const { downloadFile, bytes } = useFileInfo(resource); return ( <> - -

{title}

-
+
- + ); } - -export default FileCard; diff --git a/browser/data-browser/src/views/File/FilePreview.tsx b/browser/data-browser/src/views/File/FilePreview.tsx index d41a309b8..de2a69575 100644 --- a/browser/data-browser/src/views/File/FilePreview.tsx +++ b/browser/data-browser/src/views/File/FilePreview.tsx @@ -13,12 +13,14 @@ const PDFViewer = lazy(() => import('../../chunks/PDFViewer')); interface FilePreviewProps { resource: Resource; + hideTypes?: string[]; } -export function FilePreview({ resource }: FilePreviewProps) { +export function FilePreview({ resource, hideTypes }: FilePreviewProps) { const { downloadUrl, mimeType, bytes } = useFileInfo(resource); const [ignoreSizeLimit, setIgnoreSizeLimit] = useState(false); const fileSizeLimit = useFilePreviewSizeLimit(); + const shouldShowType = buildShouldShowType(mimeType, hideTypes); if (bytes > fileSizeLimit && !ignoreSizeLimit) { return ( @@ -26,13 +28,13 @@ export function FilePreview({ resource }: FilePreviewProps) { ); } - if (mimeType.startsWith('image/')) { + if (shouldShowType('image/')) { return ( ); } - if (mimeType.startsWith('video/')) { + if (shouldShowType('video/')) { return ( // Don't know how to get captions here // eslint-disable-next-line jsx-a11y/media-has-caption @@ -43,7 +45,7 @@ export function FilePreview({ resource }: FilePreviewProps) { ); } - if (mimeType.startsWith('audio/')) { + if (shouldShowType('audio/')) { return ( // eslint-disable-next-line jsx-a11y/media-has-caption