diff --git a/CHANGELOG.md b/CHANGELOG.md index 905738aed..d0a571ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added better links to and from register and login pages (NS-56) - Added more information to system status page (NS-43) - Added display names for React Contexts for better debugging +- Added new "Grid view" for projects, enabled in page blocks ### Fixed diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewSelector.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewSelector.tsx index 12831044f..135815a1a 100644 --- a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewSelector.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewSelector.tsx @@ -31,41 +31,47 @@ export const ViewSelector: React.FC<{ if (selector.type === 'polygon-selector' && selector.state && selector.state.shape) { const points = selector.state.shape.points as Array<[number, number]>; - if (points && points.length > 2) { - const x1 = Math.min(...points.map(p => p[0])); - const y1 = Math.min(...points.map(p => p[1])); - const x2 = Math.max(...points.map(p => p[0])); - const y2 = Math.max(...points.map(p => p[1])); + const isOpen = selector.state.shape.open; + if (points && ((points.length > 1 && isOpen) || (points.length > 2 && !isOpen))) { + const padding = 30; + const x1 = Math.min(...points.map(p => p[0])) - padding; + const y1 = Math.min(...points.map(p => p[1])) - padding; + const x2 = Math.max(...points.map(p => p[0])) + padding; + const y2 = Math.max(...points.map(p => p[1])) + padding; const cropped = croppedRegion({ x: x1, y: y1, width: x2 - x1, height: y2 - y1 }); if (cropped) { const ratio = 1; setImage(cropped); - const Shape = selector.state.shape.open ? 'polyline' : 'polygon'; + + const Shape = isOpen ? 'polyline' : 'polygon'; + const svgPoints = points.map(p => `${(p[0] - x1) * ratio},${(p[1] - y1) * ratio}`).join(' '); + setMask( {!didError ? ( <> - `${(p[0] - x1) * ratio},${(p[1] - y1) * ratio}`).join(' ')} - style={{ fill: 'white', stroke: 'white', strokeWidth: 2 }} - /> + + + {isOpen ? ( + + ) : null} ) : ( `${(p[0] - x1) * ratio},${(p[1] - y1) * ratio}`).join(' ')} - style={{ fill: 'white', stroke: '#000', strokeWidth: 2 }} + points={svgPoints} + style={isOpen ? { stroke: '#000', strokeWidth: 2 } : { fill: 'white', stroke: 'red', strokeWidth: 2 }} /> )} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/selector-types/PolygonSelector/PolygonSelector.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/selector-types/PolygonSelector/PolygonSelector.tsx index 9d65bac6b..d13f465b4 100644 --- a/services/madoc-ts/src/frontend/shared/capture-models/editor/selector-types/PolygonSelector/PolygonSelector.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/selector-types/PolygonSelector/PolygonSelector.tsx @@ -1,6 +1,10 @@ import { InputShape } from 'polygon-editor'; import { ViewSelector } from '../../../_components/ViewDocument/components/ViewSelector'; -import { BaseSelector } from '../../../types/selector-types'; +import { BaseSelector, SelectorTypeProps } from '../../../types/selector-types'; +import { useTranslation } from 'react-i18next'; +import { satisfies } from 'semver'; +import { Button } from '../../atoms/Button'; +import { ButtonRow } from '../../../../navigation/Button'; export interface PolygonSelectorProps extends BaseSelector { id: string; @@ -13,21 +17,75 @@ export interface PolygonSelectorProps extends BaseSelector { }; } -export function PolygonSelector(props: PolygonSelectorProps) { +export function PolygonSelector({ + chooseSelector, + updateSelector, + readOnly, + state, + currentSelectorId, + clearSelector, + id, + ...props +}: SelectorTypeProps) { + const { t } = useTranslation(); + const isSelecting = currentSelectorId === id; + + const parts: Record = { + preview: null, + confirmButton: null, + editButton: null, + }; + + const existingShape = Boolean(state && state.shape && state.shape.points.length > 1); + const isLine = Boolean(existingShape && state?.shape.open); + + if (existingShape) { + // We can show a preview of the shape here. + parts.preview = ( + + ); + } + + if (chooseSelector && !readOnly) { + if (isSelecting) { + // Show the editing controls. + if (state && clearSelector) { + // Show confirm button + parts.confirmButton = ( + + ); + } + } else { + // Show edit button + parts.editButton = ( + + ); + } + } + return (
- {props.state && props.state.shape && !props.state.shape.open && props.state.shape.points.length > 1 ? ( - + {parts.preview ? ( +
{parts.preview}
) : ( - <>Draw a shape +
{t('Draw a shape')}
)} + + {parts.confirmButton} + {parts.discardButton} + {parts.editButton} +
); } diff --git a/services/madoc-ts/src/frontend/site/blocks/AllProjectsPaginatedItems.tsx b/services/madoc-ts/src/frontend/site/blocks/AllProjectsPaginatedItems.tsx index 59a6ac1b3..e6b0f9ece 100644 --- a/services/madoc-ts/src/frontend/site/blocks/AllProjectsPaginatedItems.tsx +++ b/services/madoc-ts/src/frontend/site/blocks/AllProjectsPaginatedItems.tsx @@ -3,24 +3,29 @@ import React from 'react'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; import { SingleProject } from './SingleProject'; import { useProjectList } from '../hooks/use-project-list'; +import { SingleProjectItem } from '../../tailwind/blocks/SingleProjectItem'; interface AllProjectPaginatedItemsProps { customButtonLabel?: InternationalString; background?: string; radius?: string; + grid?: boolean; } export const AllProjectsPaginatedItems: React.FC = ({ customButtonLabel, radius, background, + grid, }) => { const { resolvedData: data } = useProjectList(); + const Component = grid ? SingleProjectItem : SingleProject; + return ( - <> +
{data?.projects.map(project => ( - background={background} /> ))} - +
); }; @@ -41,6 +46,7 @@ blockEditorFor(AllProjectsPaginatedItems, { customButtonLabel: '', background: null, radius: null, + grid: false, }, source: { name: 'All projects page', @@ -53,5 +59,6 @@ blockEditorFor(AllProjectsPaginatedItems, { customButtonLabel: { type: 'text-field', label: 'Custom button label' }, background: { type: 'color-field', label: 'Background color', defaultValue: '#eeeeee' }, radius: { type: 'text-field', label: 'Border radius', defaultValue: '' }, + grid: { type: 'checkbox-field', label: 'Use new grid', inlineLabel: 'enable grid' }, }, }); diff --git a/services/madoc-ts/src/frontend/site/blocks/SingleProject.tsx b/services/madoc-ts/src/frontend/site/blocks/SingleProject.tsx index 221105b06..1c4177119 100644 --- a/services/madoc-ts/src/frontend/site/blocks/SingleProject.tsx +++ b/services/madoc-ts/src/frontend/site/blocks/SingleProject.tsx @@ -1,9 +1,7 @@ import { InternationalString } from '@iiif/presentation-3'; -import React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; -import { Project } from '../../../types/project-full'; import { useRelativeLinks } from '../hooks/use-relative-links'; import { ObjectContainer } from '../../shared/atoms/ObjectContainer'; import { ProjectStatus } from '../../shared/atoms/ProjectStatus'; @@ -11,12 +9,13 @@ import { useAccessibleColor } from '../../shared/hooks/use-accessible-color'; import { Button } from '../../shared/navigation/Button'; import { Heading3, Subheading3 } from '../../shared/typography/Heading3'; import { LocaleString } from '../../shared/components/LocaleString'; +import { ProjectListItem } from '../../../types/schemas/project-list-item'; interface SingleProjectProps { customButtonLabel?: InternationalString; project?: { id: string }; background?: string; - data?: Project; + data?: ProjectListItem; radius?: string; } diff --git a/services/madoc-ts/src/frontend/tailwind/blocks/SingleProjectItem.tsx b/services/madoc-ts/src/frontend/tailwind/blocks/SingleProjectItem.tsx new file mode 100644 index 000000000..10cccdc57 --- /dev/null +++ b/services/madoc-ts/src/frontend/tailwind/blocks/SingleProjectItem.tsx @@ -0,0 +1,84 @@ +import { useTranslation } from 'react-i18next'; +import { ProjectListItem } from '../../../types/schemas/project-list-item'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { apiHooks } from '../../shared/hooks/use-api-query'; +import { useRelativeLinks } from '../../site/hooks/use-relative-links'; +import { Button } from '../../shared/navigation/Button'; +import { Link } from 'react-router-dom'; +import { InternationalString } from '@iiif/presentation-3'; +import { HrefLink } from '../../shared/utility/href-link'; + +interface SingleProjectItemProps { + project: { id: string }; + data: ProjectListItem; + customButtonLabel?: InternationalString; + radius?: string; + background?: string; +} + +function getThumbnail(image: string) { + if (image.includes('/public/storage/')) { + const parts = image.split('/'); + const filename = parts.pop(); + const file = (filename || '').split('.'); + file.pop(); // extension + const path = parts.join('/'); + return `${path}/256/${file.join('.')}.jpg`; + } + + return image; +} + +export function SingleProjectItem({ data, project, customButtonLabel }: SingleProjectItemProps) { + const { t } = useTranslation(); + const { label, summary, thumbnail, task_id, collection_id } = data || {}; + const { data: taskStats } = apiHooks.getTaskStats(() => (task_id ? [task_id] : undefined), {}); + const { data: collectionStats } = apiHooks.getCollectionStatistics(() => + collection_id ? [collection_id] : undefined + ); + const createLink = useRelativeLinks(); + + if (!data || !project) return null; + + return ( +
+
+ + {label} + + + {summary} + + {/*
{JSON.stringify({ data, taskStats, collectionStats }, null, 2)}
*/} + {collectionStats && taskStats ? ( +
+
+
+ ) : ( +
+ )} +
+ +
+
+
+ {thumbnail ? ( + + + + ) : null} +
+
+ ); +} diff --git a/services/madoc-ts/src/routes/projects/list-projects.ts b/services/madoc-ts/src/routes/projects/list-projects.ts index 7f09554c1..9eaaab324 100644 --- a/services/madoc-ts/src/routes/projects/list-projects.ts +++ b/services/madoc-ts/src/routes/projects/list-projects.ts @@ -60,6 +60,7 @@ export const listProjects: RouteMiddleware = async context => { task_id: project.task_id, status: project.status, template: project.template_name, + thumbnail: project.placeholder_image, }; }); diff --git a/services/madoc-ts/src/types/schemas/project-list-item.ts b/services/madoc-ts/src/types/schemas/project-list-item.ts index 191625c0e..41aff14ca 100644 --- a/services/madoc-ts/src/types/schemas/project-list-item.ts +++ b/services/madoc-ts/src/types/schemas/project-list-item.ts @@ -10,4 +10,5 @@ export type ProjectListItem = { summary: InternationalString; status: number; template?: string; + thumbnail?: string; }; diff --git a/services/madoc-ts/src/types/schemas/project-snippet.ts b/services/madoc-ts/src/types/schemas/project-snippet.ts index 18cf5fe03..5d0e489dd 100644 --- a/services/madoc-ts/src/types/schemas/project-snippet.ts +++ b/services/madoc-ts/src/types/schemas/project-snippet.ts @@ -4,4 +4,5 @@ export type ProjectSnippet = { id: number; label: InternationalString; summary: InternationalString; + thumbnail?: string; }; diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index 86eff6606..cad1abec0 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -313,6 +313,7 @@ "Document root": "Document root", "Download JSON": "Download JSON", "Drafts": "Drafts", + "Draw a shape": "Draw a shape", "Dynamic data sources": "Dynamic data sources", "Edit": "Edit", "Edit annotation style": "Edit annotation style", @@ -983,6 +984,7 @@ "Use Universal Viewer on canvas page": "Use Universal Viewer on canvas page", "Use UniversalViewer in place of the default viewer": "Use UniversalViewer in place of the default viewer", "Use as default value": "Use as default value", + "Use new grid": "Use new grid", "Use your Gravitar profile image": "Use your Gravitar profile image", "User already assigned": "User already assigned", "User dashboard": "User dashboard", @@ -1113,8 +1115,10 @@ "divider": "divider", "done": "done", "edit": "edit", + "edit line": "edit line", "edit region": "edit region", "enable": "enable", + "enable grid": "enable grid", "entity": "entity", "error": "error", "event_id": "event_id",