Skip to content

Commit

Permalink
Merge pull request #811 from digirati-co-uk/feature/polygon-bugs
Browse files Browse the repository at this point in the history
Polygons: fixed preview + entity selector
  • Loading branch information
stephenwf committed Feb 2, 2024
2 parents 86bd398 + 1e8297a commit 9566381
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<svg width="100%" height="100%" viewBox={`0 0 ${(x2 - x1) * ratio} ${(y2 - y1) * ratio}`}>
{!didError ? (
<>
<defs>
<mask id={`selector-mask-${selector.id}`}>
<Shape
points={points.map(p => `${(p[0] - x1) * ratio},${(p[1] - y1) * ratio}`).join(' ')}
style={{ fill: 'white', stroke: 'white', strokeWidth: 2 }}
/>
<rect x="0" y="0" width="100%" height="100%" fill="rgba(255, 255, 255, .1)" />
<Shape points={svgPoints} style={{ fill: 'white', stroke: '#000', strokeWidth: 4 }} />
</mask>
</defs>
<image
href={cropped}
mask={`url(#selector-mask-${selector.id})`}
mask={isOpen ? undefined : `url(#selector-mask-${selector.id})`}
width="100%"
height="100%"
{...({ onError: handleSvgError } as any)}
/>
{isOpen ? (
<polyline points={svgPoints} style={{ fill: 'none', stroke: 'red', strokeWidth: 4 }} />
) : null}
</>
) : (
<Shape
points={points.map(p => `${(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 }}
/>
)}
</svg>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<PolygonSelectorProps>) {
const { t } = useTranslation();
const isSelecting = currentSelectorId === id;

const parts: Record<string, React.ReactNode> = {
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 = (
<ViewSelector
fluidImage
selector={{
id: 'test',
type: 'polygon-selector',
state,
}}
/>
);
}

if (chooseSelector && !readOnly) {
if (isSelecting) {
// Show the editing controls.
if (state && clearSelector) {
// Show confirm button
parts.confirmButton = (
<Button primary size="small" onClick={() => clearSelector()}>
{t('confirm')}
</Button>
);
}
} else {
// Show edit button
parts.editButton = (
<Button primary size="small" onClick={() => chooseSelector(id)}>
{existingShape ? (isLine ? t('edit line') : t('edit region')) : t('define region')}
</Button>
);
}
}

return (
<div>
{props.state && props.state.shape && !props.state.shape.open && props.state.shape.points.length > 1 ? (
<ViewSelector
fluidImage
selector={{
id: 'test',
type: 'polygon-selector',
state: props.state,
}}
/>
{parts.preview ? (
<div style={{ marginBottom: 10 }}>{parts.preview}</div>
) : (
<>Draw a shape</>
<div style={{ padding: '10px 4px' }}>{t('Draw a shape')}</div>
)}
<ButtonRow $noMargin>
{parts.confirmButton}
{parts.discardButton}
{parts.editButton}
</ButtonRow>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<AllProjectPaginatedItemsProps> = ({
customButtonLabel,
radius,
background,
grid,
}) => {
const { resolvedData: data } = useProjectList();

const Component = grid ? SingleProjectItem : SingleProject;

return (
<>
<div style={grid ? { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 } : undefined}>
{data?.projects.map(project => (
<SingleProject
<Component
key={project.id}
project={{ id: project.slug }}
data={project as any}
Expand All @@ -29,7 +34,7 @@ export const AllProjectsPaginatedItems: React.FC<AllProjectPaginatedItemsProps>
background={background}
/>
))}
</>
</div>
);
};

Expand All @@ -41,6 +46,7 @@ blockEditorFor(AllProjectsPaginatedItems, {
customButtonLabel: '',
background: null,
radius: null,
grid: false,
},
source: {
name: 'All projects page',
Expand All @@ -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' },
},
});
5 changes: 2 additions & 3 deletions services/madoc-ts/src/frontend/site/blocks/SingleProject.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
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';
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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex bg-white p-5 border gap-10 rounded-md">
<div className="flex-1 flex flex-col justify-between">
<LocaleString as="h2" className="text-xl mb-0 mt-0">
{label}
</LocaleString>
<LocaleString as="p" className="text-md mb-0 mt-0">
{summary}
</LocaleString>
{/* <pre>{JSON.stringify({ data, taskStats, collectionStats }, null, 2)}</pre> */}
{collectionStats && taskStats ? (
<div className="h-3 bg-slate-100 rounded-full overflow-hidden">
<div
className="bg-slate-500 h-3 overflow-hidden rounded-full"
style={{ width: `${(taskStats.total / collectionStats?.canvases) * 100}%` }}
/>
</div>
) : (
<div className="h-3 bg-slate-100" />
)}
<div>
<Button $primary as={Link} to={createLink({ projectId: project.id })}>
{customButtonLabel ? <LocaleString>{customButtonLabel}</LocaleString> : t('Go to project')}
</Button>
</div>
</div>
<div
className={`${thumbnail ? 'aspect-video' : 'aspect-square h-32'} w-64 justify-center flex rounded-xl overflow-hidden`}
>
{thumbnail ? (
<HrefLink className="w-full h-full" href={createLink({ projectId: project.id })}>
<img
className="object-cover w-full h-full hover:scale-110 transition-transform duration-1000"
src={getThumbnail(thumbnail)}
alt=""
/>
</HrefLink>
) : null}
</div>
</div>
);
}
1 change: 1 addition & 0 deletions services/madoc-ts/src/routes/projects/list-projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
});

Expand Down
1 change: 1 addition & 0 deletions services/madoc-ts/src/types/schemas/project-list-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export type ProjectListItem = {
summary: InternationalString;
status: number;
template?: string;
thumbnail?: string;
};
1 change: 1 addition & 0 deletions services/madoc-ts/src/types/schemas/project-snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export type ProjectSnippet = {
id: number;
label: InternationalString;
summary: InternationalString;
thumbnail?: string;
};
4 changes: 4 additions & 0 deletions services/madoc-ts/translations/en/madoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit 9566381

Please sign in to comment.