From f20e583ff9fef5926c730bf42ea9d1325d1e57c6 Mon Sep 17 00:00:00 2001 From: Heather Date: Thu, 24 Nov 2022 16:38:56 +0000 Subject: [PATCH 001/191] add slot to search --- services/madoc-ts/src/frontend/site/pages/search.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index ceba8067b..90fa91ec6 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Slot } from '../../shared/page-blocks/slot'; import { useTranslation } from 'react-i18next'; import { CloseIcon } from '../../shared/icons/CloseIcon'; import { LoadingBlock } from '../../shared/callouts/LoadingBlock'; @@ -25,6 +26,8 @@ import { useSearch } from '../hooks/use-search'; import { useSearchFacets } from '../hooks/use-search-facets'; import { useSearchQuery } from '../hooks/use-search-query'; import { ButtonRow, TinyButton } from '../../shared/navigation/Button'; +import { StaticPage } from '../features/StaticPage'; +import { Heading1 } from "../../shared/typography/Heading1"; export const Search: React.FC = () => { const { t } = useTranslation(); @@ -46,8 +49,10 @@ export const Search: React.FC = () => { } = useSearchFacets(); return ( - <> + + +
{t('Refine search')} @@ -141,6 +146,6 @@ export const Search: React.FC = () => { />
- +
); }; From 86a099b131411138cbb75f9bd7492a8e3bc35cbc Mon Sep 17 00:00:00 2001 From: Heather Date: Fri, 25 Nov 2022 16:55:57 +0000 Subject: [PATCH 002/191] cusotmisable checkbox --- .../src/frontend/shared/atoms/CheckboxBtn.tsx | 96 ++++++++++ .../shared/components/MetadataFacetEditor.tsx | 3 +- .../shared/components/SearchFilters.tsx | 9 +- .../site/features/SearchPageFilters.tsx | 128 ++++++++++++++ .../frontend/site/hooks/use-search-facets.ts | 1 + .../src/frontend/site/pages/search.tsx | 167 +++++------------- .../src/frontend/site/pages/view-manifest.tsx | 1 + .../madoc-ts/src/frontend/site/routes.tsx | 8 +- services/madoc-ts/translations/en/madoc.json | 3 + 9 files changed, 286 insertions(+), 130 deletions(-) create mode 100644 services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx create mode 100644 services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx diff --git a/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx b/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx new file mode 100644 index 000000000..16adb33de --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import styled from 'styled-components'; +import { InternationalString } from '@iiif/presentation-3'; + +const Box = styled.div` + display: flex; + align-items: center; + cursor: pointer; + padding: 5px 0; + border: none; + background-color: transparent; + text-align: left; + + :disabled { + pointer-events: none; + filter: grayscale(80%); + } +`; + +const CheckText = styled.div` + font-size: 14px; + font-weight: normal; + text-transform: capitalize; + :hover { + color: pink; + } +`; + +const SubText = styled.p` + color: grey; + margin: 0; +`; + +const CountText = styled.small` + color: black; + font-size: 12px; +`; + +const CheckWrapper = styled.div` + position: relative; +`; + +const FakeCheck = styled.div` + position: relative; + margin: 10px; + width: 12px; + height: 12px; + border: none; + border-radius: 1px; + background-color: transparent; + outline: 2px solid pink; + outline-offset: 2px; + transition: 0.1s; + + &[data-is-checked='true'] { + background-color: pink; + outline: 2px solid pink; + } +`; + +const Check = styled.input` + position: absolute; + margin: auto; + top: 0; + bottom: 0; + left: 0; + right: 0; + text-align: center; + opacity: 0; + cursor: pointer; +`; + +export const CheckboxBtn: React.FC<{ + disabled?: boolean; + checked: boolean; + onChange?: any; + inLineLabel?: string | InternationalString; + subLabel?: string | InternationalString; + countText?: string | number; + id?: any; +}> = ({ disabled, checked, onChange, inLineLabel, subLabel, countText, id }) => { + return ( + + + + + + + + {inLineLabel} + {countText && ({countText})} + {subLabel && {subLabel}} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/components/MetadataFacetEditor.tsx b/services/madoc-ts/src/frontend/shared/components/MetadataFacetEditor.tsx index ed8df6799..70b7f46f3 100644 --- a/services/madoc-ts/src/frontend/shared/components/MetadataFacetEditor.tsx +++ b/services/madoc-ts/src/frontend/shared/components/MetadataFacetEditor.tsx @@ -120,11 +120,12 @@ const EditorColumn = styled.div` margin-right: 1em; `; -type FacetConfigValue = { +export type FacetConfigValue = { id: string; label: InternationalString; values: string[]; key: string; + count?: string | number; }; export type FacetConfig = { diff --git a/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx b/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx index 341125a85..6aadc37e6 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx @@ -7,7 +7,14 @@ export const SearchFilterContainer = styled.div` export const SearchFilterCheckbox = styled.div` padding: 0.1em; - background: #eee; + //background: #eee; + border: 1px solid red; +`; + +export const SearchFilterCheckbox2 = styled.input` + padding: 0.1em; + //background: #eee; + border: 1px solid red; `; export const SearchFilterTitle = styled.h3``; diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx new file mode 100644 index 000000000..c0a19c628 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import { + SearchFilterCheckbox, + SearchFilterContainer, + SearchFilterItem, + SearchFilterItemCount, + SearchFilterItemList, + SearchFilterLabel, + SearchFilterSection, + SearchFilterSectionTitle, + SearchFilterTitle, + SearchFilterToggle, +} from '../../shared/components/SearchFilters'; +import { SearchBox } from '../../shared/atoms/SearchBox'; +import { ButtonRow, TinyButton } from '../../shared/navigation/Button'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { CloseIcon } from '../../shared/icons/CloseIcon'; +import { AddIcon } from '../../shared/icons/AddIcon'; +import { useSearchQuery } from '../hooks/use-search-query'; +import { useSiteConfiguration } from './SiteConfigurationContext'; +import { useSearchFacets } from '../hooks/use-search-facets'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { InternationalString } from '@iiif/presentation-3'; +import { FacetConfigValue } from '../../shared/components/MetadataFacetEditor'; +import { CheckboxField } from '../../shared/capture-models/editor/input-types/CheckboxField/CheckboxField'; +import { CheckboxBtn } from '../../shared/atoms/CheckboxBtn'; + +export const Pill = styled.div` + border-radius: 3px; + width: auto; + background-color: #ecf0ff; + color: #437bdd; + margin-right: 1em; + font-size: 12px; + padding: 5px; +`; + +interface SearchPageFilters { + background?: string; + textColor?: string; + displayFacets?: { + id: string; + label: InternationalString; + items: FacetConfigValue[]; + }[]; +} + +export function SearchPageFilters(props: SearchPageFilters) { + const { t } = useTranslation(); + const displayFacets = props.displayFacets; + const { fulltext, appliedFacets } = useSearchQuery(); + const { + project: { showSearchFacetCount }, + } = useSiteConfiguration(); + const { + inQueue, + applyFacet, + clearSingleFacet, + queueSingleFacet, + dequeueSingleFacet, + isFacetSelected, + applyAllFacets, + clearAllFacets, + setFullTextQuery, + } = useSearchFacets(); + + if (!props.displayFacets) { + return null; + } + return ( + + {t('Refine search')} + {/**/} + + applyAllFacets()}> + {t('Apply')} + + clearAllFacets()}> + {t('Clear')} + + + {displayFacets?.map(facet => { + if (facet.items.length === 0) { + return null; + } + return ( + + + {facet.label} + + + {facet.items.map(item => { + const isSelected = isFacetSelected(item.key, item.values); + const itemHash = `item__${facet.id}::${item.key}::${item.values}`; + + return ( + + + e.target.checked + ? queueSingleFacet(item.key, item.values) + : dequeueSingleFacet(item.key, item.values) + } + /> + + {item.label} + + + ); + })} + + + ); + })} + + ); +} + +blockEditorFor(SearchPageFilters, { + label: 'Search Page Filters', + type: 'search-page-filters', + defaultProps: {}, + editor: {}, +}); diff --git a/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts b/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts index 3c1b512c9..2e9810643 100644 --- a/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts +++ b/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts @@ -37,6 +37,7 @@ export function useSearchFacets() { }; const clearSingleFacet = (key: string, values: string[]) => { + console.log(appliedFacets); setQuery( fulltext, appliedFacets.filter(facet => !(facet.k === key && values.indexOf(facet.v) !== -1)), diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index 90fa91ec6..fa6145493 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -1,149 +1,62 @@ import React from 'react'; import { Slot } from '../../shared/page-blocks/slot'; import { useTranslation } from 'react-i18next'; -import { CloseIcon } from '../../shared/icons/CloseIcon'; import { LoadingBlock } from '../../shared/callouts/LoadingBlock'; -import { SearchBox } from '../../shared/atoms/SearchBox'; import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; -import { LocaleString } from '../../shared/components/LocaleString'; import { Pagination } from '../../shared/components/Pagination'; -import { - SearchFilterCheckbox, - SearchFilterContainer, - SearchFilterItem, - SearchFilterItemCount, - SearchFilterItemList, - SearchFilterLabel, - SearchFilterSection, - SearchFilterSectionTitle, - SearchFilterTitle, - SearchFilterToggle, -} from '../../shared/components/SearchFilters'; import { SearchResults, TotalResults } from '../../shared/components/SearchResults'; -import { AddIcon } from '../../shared/icons/AddIcon'; -import { useSiteConfiguration } from '../features/SiteConfigurationContext'; import { useSearch } from '../hooks/use-search'; -import { useSearchFacets } from '../hooks/use-search-facets'; import { useSearchQuery } from '../hooks/use-search-query'; -import { ButtonRow, TinyButton } from '../../shared/navigation/Button'; import { StaticPage } from '../features/StaticPage'; -import { Heading1 } from "../../shared/typography/Heading1"; +import { SearchPageFilters } from '../features/SearchPageFilters'; export const Search: React.FC = () => { const { t } = useTranslation(); const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); - const { rawQuery, page, fulltext, appliedFacets } = useSearchQuery(); - const { - project: { showSearchFacetCount }, - } = useSiteConfiguration(); - const { - inQueue, - applyFacet, - clearSingleFacet, - queueSingleFacet, - dequeueSingleFacet, - isFacetSelected, - applyAllFacets, - clearAllFacets, - setFullTextQuery, - } = useSearchFacets(); + const { rawQuery, page, fulltext } = useSearchQuery(); return ( - - + + +
- - {t('Refine search')} - - - applyAllFacets()}> - {t('Apply')} - - clearAllFacets()}> - {t('Clear')} - - - {displayFacets.map(facet => { - if (facet.items.length === 0) { - return null; - } - return ( - - - {facet.label} - - - {facet.items.map(item => { - const isSelected = isFacetSelected(item.key, item.values); - const itemHash = `item__${facet.id}::${item.key}::${item.values}`; - return ( - - - - e.target.checked - ? queueSingleFacet(item.key, item.values) - : dequeueSingleFacet(item.key, item.values) - } - /> - - - {item.label} - - {showSearchFacetCount ? ( - - {t('{{count}} manifests', { count: item.count })} - - ) : null} - {isSelected !== 0 ? ( - clearSingleFacet(item.key, item.values)}> - - - ) : ( - applyFacet(item.key, item.values)}> - - - )} - - ); - })} - - - ); - })} - -
- {isLoading && !searchResponse ? ( - - ) : ( - - {t('Found {{count}} results', { - count: searchResponse && searchResponse.pagination ? searchResponse.pagination.totalResults : 0, - })} - - )} - - - +
+ + + +
+ +
+ + {isLoading && !searchResponse ? ( + + ) : ( + + {t('Found {{count}} results', { + count: searchResponse && searchResponse.pagination ? searchResponse.pagination.totalResults : 0, + })} + + )} + + + +
diff --git a/services/madoc-ts/src/frontend/site/pages/view-manifest.tsx b/services/madoc-ts/src/frontend/site/pages/view-manifest.tsx index 904218bad..a9eb55e7f 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-manifest.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-manifest.tsx @@ -80,6 +80,7 @@ export function ViewManifest() {
+ diff --git a/services/madoc-ts/src/frontend/site/routes.tsx b/services/madoc-ts/src/frontend/site/routes.tsx index bc60f856f..2e94358b9 100644 --- a/services/madoc-ts/src/frontend/site/routes.tsx +++ b/services/madoc-ts/src/frontend/site/routes.tsx @@ -557,7 +557,13 @@ export function createRoutes(Components: RouteComponents): CreateRouteType { { path: '/search', exact: true, - element: , + element: , + children: [ + { + path: '/search', + element: , + }, + ], }, { path: '/dashboard', diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index a5ce525b7..0ff733873 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -270,8 +270,11 @@ "Enabled translations": "Enabled translations", "Enter IIIF Manifest Collection URL": "Enter IIIF Manifest Collection URL", "Enter URL": "Enter URL", + "Enter a blurb": "Enter a blurb", + "Enter a description": "Enter a description", "Enter a heading": "Enter a heading", "Enter a name": "Enter a name", + "Enter a subheading": "Enter a subheading", "Enter client ID": "Enter client ID", "Enter client secret": "Enter client secret", "Enter keyword": "Enter keyword", From da0720b79335f548098528d0dfedbf88f8c08b76 Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:31:14 +0000 Subject: [PATCH 003/191] IDA-893 Added enrichment to stack - Temporarily use postgis for database (should be removed) - Enable extension - Added new compose with replacement for search --- docker-compose.enrichment.yml | 34 +++++++++++++++++++ .../gateway/conf.d/services/search-api.conf | 12 ++++++- services/shared-postgres/Dockerfile | 2 +- services/shared-postgres/entrypoint.sh | 6 ++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100755 docker-compose.enrichment.yml diff --git a/docker-compose.enrichment.yml b/docker-compose.enrichment.yml new file mode 100755 index 000000000..8e210a128 --- /dev/null +++ b/docker-compose.enrichment.yml @@ -0,0 +1,34 @@ +version: "3.2" +services: + # Internal services + + search: + build: + context: services/enrichment + dockerfile: Dockerfile + environment: + - BROWSABLE=False + - USE_DOCKER=yes + - IPYTHONDIR=/app/.ipython + - MIGRATE=True + - LOAD=False + - DEBUG=True + - DJANGO_DEBUG=True + - WAITRESS=False + - POSTGRES_HOST=shared-postgres + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_USER=${POSTGRES_SEARCH_API_USER} + - REDIS_URL=redis://gateway-redis:6379 + - APPEND_SLASH=False + - ALLOWED_HOSTS=gateway,madoc.local + - POSTGRES_PASSWORD=${POSTGRES_SEARCH_API_PASSWORD} + - POSTGRES_SCHEMA=${POSTGRES_SEARCH_API_SCHEMA} + - POSTGRES_DB=${POSTGRES_DB} + - DATABASE_URL=postgres://${POSTGRES_SEARCH_API_USER}:${POSTGRES_SEARCH_API_PASSWORD}@shared-postgres:${POSTGRES_PORT}/${POSTGRES_DB} + - INLINE_WORKER=True + links: + - shared-postgres + - gateway-redis + volumes: + - ./services/enrichment/app:/app:delegated + diff --git a/services/gateway/conf.d/services/search-api.conf b/services/gateway/conf.d/services/search-api.conf index 12790efbd..19352f040 100644 --- a/services/gateway/conf.d/services/search-api.conf +++ b/services/gateway/conf.d/services/search-api.conf @@ -1,4 +1,14 @@ location /api/search { auth_request /_validate_jwt; - proxy_pass http://search:8000/api/search; + proxy_pass http://search:8000/madoc; +} + +location /api/enrichment { + auth_request /_validate_jwt; + proxy_pass http://search:8000/madoc; +} + +location /api/enrichment/internal { + auth_request /_validate_jwt; + proxy_pass http://search:8000/api; } diff --git a/services/shared-postgres/Dockerfile b/services/shared-postgres/Dockerfile index ebbfa04c3..cf542f48f 100644 --- a/services/shared-postgres/Dockerfile +++ b/services/shared-postgres/Dockerfile @@ -1,4 +1,4 @@ -FROM library/postgres:12 +FROM postgis/postgis:12-3.3 ENV TASKS_API_PASSWORD=tasks_api_password ENV MODEL_API_PASSWORD=model_api_password diff --git a/services/shared-postgres/entrypoint.sh b/services/shared-postgres/entrypoint.sh index cc1cff961..a8f50dee4 100755 --- a/services/shared-postgres/entrypoint.sh +++ b/services/shared-postgres/entrypoint.sh @@ -133,6 +133,12 @@ CREATE EXTENSION IF NOT EXISTS "ltree"; EOSQL +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -d "$POSTGRES_DB" <<-"EOSQL" + +CREATE EXTENSION IF NOT EXISTS "postgis"; + +EOSQL + if [ $NEW_DATABASE_CREATED = 'true' ]; then # Restore a backup if provided. docker_process_init_files /docker-entrypoint-initdb.d/* From 9603838e6345f7bdc94626c3cbd24ddfc4febdad Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:33:02 +0000 Subject: [PATCH 004/191] IDA-893 Added Enrichment API extension --- .../authority/authority-extension.ts | 70 ++++++++++ .../extensions/enrichment/authority/types.ts | 93 ++++++++++++++ .../enrichment/base-django-extension.ts | 121 ++++++++++++++++++ .../src/extensions/enrichment/extension.ts | 115 +++++++++++++++++ .../enrichment/ocr/ocr-extension.ts | 19 +++ .../enrichment/search/search-extension.ts | 31 +++++ .../src/extensions/enrichment/search/types.ts | 17 +++ .../src/extensions/enrichment/types.ts | 18 +++ .../utilities/create-search-ingest.ts | 20 +++ services/madoc-ts/src/gateway/api.ts | 70 ++++++---- services/madoc-ts/src/types/search.ts | 11 +- .../utility/capture-model-to-indexables.ts | 6 + 12 files changed, 562 insertions(+), 29 deletions(-) create mode 100644 services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/authority/types.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/base-django-extension.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/extension.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/ocr/ocr-extension.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/search/search-extension.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/search/types.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/types.ts create mode 100644 services/madoc-ts/src/extensions/enrichment/utilities/create-search-ingest.ts diff --git a/services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts b/services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts new file mode 100644 index 000000000..ad4bbebdf --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts @@ -0,0 +1,70 @@ +import { BaseDjangoExtension } from '../base-django-extension'; +import { + Authority, + AuthoritySnippet, + EnrichmentEntitySnippet, + EnrichmentEntityAuthority, + EnrichmentEntityType, + EnrichmentEntityTypeSnippet, + EntityTypeMadocResponse, + ResourceTag, + ResourceTagSnippet, + EnrichmentEntity, +} from './types'; + +export class AuthorityExtension extends BaseDjangoExtension { + static AUTHORITY = 'authority_service'; + + // /api/authority_service/entity/ + // /api/authority_service/entity// + // /api/authority_service/entity_authority/ + // /api/authority_service/entity_authority// + // /api/authority_service/entity_type/ + // /api/authority_service/entity_type// + // /api/authority_service/resource_tag/ + // /api/authority_service/resource_tag// + // @todo /api/authority_service/search/ + // @todo /api/authority_service/tasks/ + // /api/authority_service/tasks/fetch_authority_data/ + // /api/authority_service/tasks/populate_entity/ + // /api/authority_service/tasks/populate_entity_authority/ + + allTasks = [ + // Authority service. + { + service: 'authority_service', + name: 'populate_entity', + }, + { + service: 'authority_service', + name: 'fetch_authority_data', + }, + { + service: 'authority_service', + name: 'populate_entity_authority', + }, + ]; + + getServiceName() { + return 'authority_service'; + } + + getEntityType(slug: string) { + return this.api.request(`/api/enrichment/entity/${slug}/`); + } + + authority = this.createServiceHelper('authority_service', 'authority'); + entity = this.createPaginatedServiceHelper('authority_service', 'entity'); + entity_authority = this.createPaginatedServiceHelper( + 'authority_service', + 'entity_authority' + ); + entity_type = this.createPaginatedServiceHelper( + 'authority_service', + 'entity_type' + ); + resource_tag = this.createPaginatedServiceHelper( + 'authority_service', + 'resource_tag' + ); +} diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts new file mode 100644 index 000000000..c3d3ac0e7 --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -0,0 +1,93 @@ +import { Pagination } from '../../../types/schemas/_pagination'; + +export interface AuthoritySnippet { + name: string; + url: string; +} + +export interface Authority extends AuthoritySnippet { + base_data_url: string; + base_url: string; +} + +export interface EnrichmentEntitySnippet { + url: string; + id: string; + type: { + url: string; + id: string; + label: string; + }; + label: string; +} + +export interface EnrichmentEntity { + url: string; + id: string; + type: { + url: string; + id: string; + label: string; + }; + label: string; + other_labels: OtherLabels; + authorities: AuthoritySnippet[]; // Can't remember what this should be... +} + +export interface EnrichmentEntityAuthority { + url: string; + id: string; + authority: string; + identifier: string; +} + +export interface EnrichmentEntityTypeSnippet { + url: string; + id: string; + label: string; +} + +// @todo probably will change. +type OtherLabels = Array<{ + value: string; + language: string; +}>; + +export interface EnrichmentEntityType extends EnrichmentEntityTypeSnippet { + created: string; + modified: string; + other_labels: OtherLabels; +} + +export interface ResourceTagSnippet { + url: string; + id: string; + entity: string; + selector: [number, number]; +} + +export interface ResourceTag extends ResourceTagSnippet { + url: string; + id: string; + entity: string; + selector: [number, number]; + resource_id: string; + resource_content_type_id: number; + created: string; + modified: string; +} + +export interface EntitySnippetMadoc { + url: string; + id: string; + created: string; + modified: string; + type: string; + label: string; + other_labels: string[]; +} + +export interface EntityTypeMadocResponse { + pagination: Pagination; + results: EntitySnippetMadoc[]; +} diff --git a/services/madoc-ts/src/extensions/enrichment/base-django-extension.ts b/services/madoc-ts/src/extensions/enrichment/base-django-extension.ts new file mode 100644 index 000000000..dda32dd1f --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/base-django-extension.ts @@ -0,0 +1,121 @@ +import { ApiClient } from '../../gateway/api'; +import { BaseExtension, defaultDispose } from '../extension-manager'; +import { DjangoPagination } from './types'; + +export class BaseDjangoExtension implements BaseExtension { + api: ApiClient; + + allTasks: Array<{ service: string; name: string }> = []; + + constructor(api: ApiClient) { + this.api = api; + } + + getServiceName(): string { + throw new Error('No service name'); + } + + /** + * NOTE: This extension is run in "light" mode, and cannot have side effects. + */ + dispose() { + defaultDispose(this); + } + + djangoCreate(app: string, view: string, data: T) { + return this.api.request(`/api/enrichment/internal/${app}/${view}/`, { + method: 'POST', + body: data, + }); + } + + djangoList(app: string, view: string) { + return this.api.request(`/api/enrichment/internal/${app}/${view}/`); + } + + djangoPaginatedList(app: string, view: string, page = 1) { + return this.api.request>(`/api/enrichment/internal/${app}/${view}/?page=${page}`); + } + + djangoRead(app: string, view: string, id: string) { + return this.api.request(`/api/enrichment/internal/${app}/${view}/${id}/`); + } + + djangoUpdate(app: string, view: string, id: string, data: T) { + return this.api.request(`/api/enrichment/internal/${app}/${view}/${id}/`, { + method: 'PUT', + body: data, + }); + } + + djangoDelete(app: string, view: string, id: string) { + return this.api.request(`/api/enrichment/internal/${app}/${view}/${id}/`, { + method: 'DELETE', + }); + } + + createPaginatedServiceHelper(service: string, view: string) { + return { + list: (page: number) => this.djangoPaginatedList(service, view, page), + get: (name: string) => this.djangoRead(service, view, name), + create: (data: Partial) => this.djangoCreate(service, view, data), + update: (id: string, data: Partial) => this.djangoUpdate(service, view, id, data), + delete: (name: string) => this.djangoDelete(service, view, name), + }; + } + + createServiceHelper(service: string, view: string) { + return { + list: () => this.djangoList(service, view), + get: (name: string) => this.djangoRead(service, view, name), + create: (data: Full) => this.djangoCreate(service, view, data), + update: (id: string, data: Partial) => this.djangoUpdate(service, view, id, data), + delete: (name: string) => this.djangoDelete(service, view, name), + }; + } + + triggerTask( + taskName: string, + subject: string | { id: number; type: string }, + params: P, + isAsync = true + ) { + const service = this.getServiceName(); + const payload = { + task: { + subject: typeof subject === 'string' ? subject : `urn:madoc:${subject.type}:${subject.id}`, + parameters: params, + }, + async: isAsync, + }; + + return this.api.request(`/api/enrichment/internal/${service}/tasks/${taskName}/`, { + method: 'POST', + body: payload, + }); + } + + // + // /api/nlp_service/tasks/ + // /api/nlp_service/tasks/spacy_logged_task/ + // + // /api/task_service/task_log/ + // /api/task_service/task_log// + // /api/task_service/tasks/ + // /api/task_service/tasks/base_logged_task/ + // /api/task_service/tasks/base_parent_task/ + // /api/task_service/tasks/base_pipeline_task/ + // /api/task_service/tasks/base_resource_task/ + // /api/task_service/tasks/base_search_service_indexing_task/ + // /api/task_service/tasks/base_stateless_task/ + // /api/task_service/tasks/base_task/ + // /api/task_service/tasks/raw_task/ + // + // /madoc/entity/ + // /madoc/entity// + // /madoc/resource/ + // /madoc/resource// + // /madoc/search/ + // /madoc/task_log/ + // /madoc/task_log// +} diff --git a/services/madoc-ts/src/extensions/enrichment/extension.ts b/services/madoc-ts/src/extensions/enrichment/extension.ts new file mode 100644 index 000000000..4babf70c7 --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/extension.ts @@ -0,0 +1,115 @@ +import { Topic, TopicType, TopicTypeListResponse } from '../../types/schemas/topics'; +import { BaseDjangoExtension } from './base-django-extension'; +import { EnrichmentIndexPayload } from './types'; + +export class EnrichmentExtension extends BaseDjangoExtension { + // /api/madoc/indexable_data/ + // /api/madoc/indexable_data// + // @todo /api/madoc/jwt/auth_detail/ + // /api/madoc/resource/ + // /api/madoc/resource// + // /api/madoc/resource/add_manifest/ -- FETCH MORE + // @todo /api/madoc/search/ + // @todo /api/madoc/tasks/ + // /api/madoc/tasks/index_all_madoc_resources/ + // /api/madoc/tasks/index_madoc_resource/ + // /api/madoc/tasks/madoc_manifest_enrichment_pipeline/ + // /api/madoc/tasks/madoc_resource_enrichment_pipeline/ + // /api/madoc/tasks/nlp_madoc_resource/ + // /api/madoc/tasks/ocr_madoc_resource/ + + // @todo no idea what this is yet. + indexable_data = this.createPaginatedServiceHelper('madoc', 'indexable_data'); + resource = this.createPaginatedServiceHelper('madoc', 'resource'); + + getServiceName(): string { + return 'madoc'; + } + + // Site APIS. + getSiteTopic(id: string, type: string) { + return this.api.publicRequest(`/madoc/api/topics/${type}/${id}`); + } + getSiteTopicTypes(page = 1) { + return this.api.publicRequest(`/madoc/api/topics?page=${page}`); + } + getSiteTopicType(slug: string, page = 1) { + return this.api.publicRequest(`/madoc/api/topics/${slug}?page=${page}`); + } + + getTopicType(id: string) { + return this.api.request(`/api/enrichment/entity/${id}/`); + } + + getAllEnrichmentTasks(page = 1) { + return this.api.request(`/api/enrichment/task_log?page=${page}`); + } + + getEnrichmentTask(id: string) { + return this.api.request(`/api/enrichment/task_log/${id}`); + } + + allTasks = [ + // Authority service. + { + service: 'madoc', + name: 'index_all_madoc_resources', + }, + { + service: 'madoc', + name: 'index_madoc_resource', + }, + { + service: 'madoc', + name: 'madoc_manifest_enrichment_pipeline', + }, + { + service: 'madoc', + name: 'madoc_resource_enrichment_pipeline', + }, + { + service: 'madoc', + name: 'nlp_madoc_resource', + }, + { + service: 'madoc', + name: 'ocr_madoc_resource', + }, + ]; + + // Dev friendly helpers. + listAllTopics(page = 1) { + return this.api.publicRequest(`/madoc/api/topic-types?page=${page}`); + } + + listAllTopicTypes(page = 1) { + return this.api.publicRequest(`/madoc/api/topic-types?page=${page}`); + } + + getTopicBySlug(type: string, slug: string) { + // @todo + } + + tagMadocResource(entityId: string, type: string, id: number, selector?: any) { + return this.api.request(`/api/enrichment/resource_tag/`, { + method: 'POST', + body: { + entity: entityId, + madoc_id: `urn:madoc:${type.toLowerCase()}:${id}`, + selector, + }, + }); + } + + topicAutoComplete(type: string, query: string) { + // @todo + } + + getManifestTags(id: number) { + // @todo + } + + getCanvasTags(id: number) { + // @todo + } +} diff --git a/services/madoc-ts/src/extensions/enrichment/ocr/ocr-extension.ts b/services/madoc-ts/src/extensions/enrichment/ocr/ocr-extension.ts new file mode 100644 index 000000000..d77107bc0 --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/ocr/ocr-extension.ts @@ -0,0 +1,19 @@ +import { BaseDjangoExtension } from '../base-django-extension'; + +export class OCRExtension extends BaseDjangoExtension { + // /api/ocr_service/convert/ + // /api/ocr_service/convert/convert_aws_textract/ + // /api/ocr_service/convert/convert_google_vision/ + // /api/ocr_service/convert/convert_hocr/ + // /api/ocr_service/convert/convert_mets_alto/ + // /api/ocr_service/ocr/ + // /api/ocr_service/ocr/aws_textract/ + // /api/ocr_service/ocr/aws_textract_iiif_manifest/ + // /api/ocr_service/ocr/base_ocr_iiif_manifest/ + // /api/ocr_service/ocr/base_ocr_task/ + // /api/ocr_service/ocr/google_vision/ + // /api/ocr_service/ocr/google_vision_iiif_manifest/ + // /api/ocr_service/ocr_resource/ + // /api/ocr_service/ocr_resource// + // /api/ocr_service/search/ +} diff --git a/services/madoc-ts/src/extensions/enrichment/search/search-extension.ts b/services/madoc-ts/src/extensions/enrichment/search/search-extension.ts new file mode 100644 index 000000000..6f650fc68 --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/search/search-extension.ts @@ -0,0 +1,31 @@ +import { SearchIndexable } from '../../../utility/capture-model-to-indexables'; +import { BaseDjangoExtension } from '../base-django-extension'; +import { ResourceRelationship } from './types'; +import * as T from './types'; + +export class SearchExtension extends BaseDjangoExtension { + // /api/search_service/content_type/ + // /api/search_service/content_type// + // /api/search_service/context/ + // /api/search_service/context// + // /api/search_service/indexable/ + // /api/search_service/indexable// + // @todo /api/search_service/indexable_search/ + // /api/search_service/json_resource/ + // /api/search_service/json_resource// + // @todo /api/search_service/json_resource/create_nested/ + // @todo /api/search_service/json_resource_search/ + // /api/search_service/resource_relationship/ + // /api/search_service/resource_relationship// + + // This is very internal. + // content_type = this.createServiceHelper('search_service', 'content_type'); + context = this.createPaginatedServiceHelper('search_service', 'context'); + indexable = this.createPaginatedServiceHelper('search_service', 'indexable'); + // @todo unknown what this is. + json_resource = this.createPaginatedServiceHelper('search_service', 'json_resource'); + resource_relationship = this.createPaginatedServiceHelper( + 'search_service', + 'resource_relationship' + ); +} diff --git a/services/madoc-ts/src/extensions/enrichment/search/types.ts b/services/madoc-ts/src/extensions/enrichment/search/types.ts new file mode 100644 index 000000000..3e0d0c079 --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/search/types.ts @@ -0,0 +1,17 @@ +export interface SearchContext { + created: string; + id: string; + modified: string; + urn: string; +} + +export interface ResourceRelationship { + id: string; + created: string; + modified: string; + source_id: string; + source_content_type: number; + type: string; + target_id: string; + target_content_type: number; +} diff --git a/services/madoc-ts/src/extensions/enrichment/types.ts b/services/madoc-ts/src/extensions/enrichment/types.ts new file mode 100644 index 000000000..10e1e1139 --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/types.ts @@ -0,0 +1,18 @@ +import { Canvas, Collection, InternationalString, Manifest } from '@iiif/presentation-3'; + +export type EnrichmentIndexPayload = { + madoc_id: string; + label: InternationalString; + type: 'manifest' | 'canvas' | 'collection'; + madoc_url: string; + thumbnail: string; + iiif_json: Manifest | Canvas | Collection; + contexts: string[]; +}; + +export interface DjangoPagination { + count: number; + next: string; + previous: string; + results: T[]; +} diff --git a/services/madoc-ts/src/extensions/enrichment/utilities/create-search-ingest.ts b/services/madoc-ts/src/extensions/enrichment/utilities/create-search-ingest.ts new file mode 100644 index 000000000..24bc4febb --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/utilities/create-search-ingest.ts @@ -0,0 +1,20 @@ +import { EnrichmentIndexPayload } from '../types'; + +export function createSearchIngest( + resourceId: number, + type: 'manifest' | 'canvas' | 'collection', + manifest: any, + site_id: number, + thumbnail?: string | null, + contexts: string[] = [] +): EnrichmentIndexPayload { + return { + madoc_id: `urn:madoc:${type}:${resourceId}`, + type: type, + label: manifest.label, + madoc_url: `http://madoc.dev/urn:madoc:${type}:${resourceId}`, + iiif_json: manifest, + thumbnail: manifest.thumbnail, + contexts: [`urn:madoc:site:${site_id}`, ...contexts, `urn:madoc:${type}:${resourceId}`], + }; +} diff --git a/services/madoc-ts/src/gateway/api.ts b/services/madoc-ts/src/gateway/api.ts index fddd5a1b5..a55644423 100644 --- a/services/madoc-ts/src/gateway/api.ts +++ b/services/madoc-ts/src/gateway/api.ts @@ -12,6 +12,10 @@ import { DynamicDataSourcesExtension } from '../extensions/capture-models/Dynami import { CaptureModelExtension } from '../extensions/capture-models/extension'; import { Paragraphs } from '../extensions/capture-models/Paragraphs/Paragraphs.extension'; import { plainTextSource } from '../extensions/capture-models/DynamicDataSources/sources/Plaintext.source'; +import { AuthorityExtension } from '../extensions/enrichment/authority/authority-extension'; +import { EnrichmentExtension } from '../extensions/enrichment/extension'; +import { SearchExtension } from '../extensions/enrichment/search/search-extension'; +import { EnrichmentIndexPayload } from '../extensions/enrichment/types'; import { ExtensionManager } from '../extensions/extension-manager'; import { NotificationExtension } from '../extensions/notifications/extension'; import { getDefaultPageBlockDefinitions } from '../extensions/page-blocks/default-definitions'; @@ -116,6 +120,9 @@ export class ApiClient { siteManager!: SiteManagerExtension; projectTemplates!: ProjectTemplateExtension; crowdsourcing!: CrowdsourcingApi; + authority: AuthorityExtension; + enrichment: EnrichmentExtension; + search: SearchExtension; constructor(options: { gateway: string; @@ -138,6 +145,10 @@ export class ApiClient { // This extension will be fine. this.notifications = new NotificationExtension(this); this.tasks = new TaskExtension(this); + // Enrichment + this.authority = new AuthorityExtension(this); + this.enrichment = new EnrichmentExtension(this); + this.search = new SearchExtension(this); if (options.withoutExtensions) { this.crowdsourcing = new CrowdsourcingApi(this, null, captureModelDataSources); @@ -778,25 +789,6 @@ export class ApiClient { }); } - /// Search - - async search(searchTerm: string, pageQuery = 1, facetType?: string, facetValue?: string) { - // Facet Types these are just one at a time for now, may switch to a post query with the json if a list! - if (!searchTerm) - return { - results: [], - pagination: { - page: 1, - totalResults: 0, - totalPages: 1, - }, - facets: [], - }; - return await this.request( - `/api/search/search?${stringify({ fulltext: searchTerm, page: pageQuery, facetType, facetValue })}` - ); - } - async getSearchQuery(query: SearchQuery, page = 1, madoc_id?: string) { return this.searchQuery(query, page, madoc_id); } @@ -1852,11 +1844,35 @@ export class ApiClient { return this.request(`/api/madoc/activity/${primaryStream}/changes`); } + // NEW SEARCH API. + async enrichmentIngestResource(request: EnrichmentIndexPayload) { + return this.request(`/api/enrichment/internal/madoc/resource/`, { + method: 'POST', + body: request, + }); + } + + async triggerSearchIndex(id: number, type: string) { + return this.request(`/api/enrichment/internal/madoc/tasks/index_madoc_resource/`, { + method: 'POST', + body: { + task: { + subject: `urn:madoc:${type}:${id}`, + parameters: [{}], + }, + }, + }); + } + // Search API async searchQuery(query: SearchQuery, page = 1, madoc_id?: string) { - return this.request(`/api/search/search?${stringify({ page, madoc_id })}`, { + return this.request(`/api/enrichment/search/`, { method: 'POST', - body: query, + body: { + ...query, + page, + madoc_id, + }, }); } // can be used for both canvases and manifests @@ -1868,7 +1884,7 @@ export class ApiClient { } async searchReIngest(resource: SearchIngestRequest) { - return this.request(`/api/search/iiif/${resource.id}`, { + return this.request(`/api/enrichment/internal/madoc/resource/${resource.id}`, { method: 'PUT', body: resource, }); @@ -1957,7 +1973,11 @@ export class ApiClient { } async searchListModels(query?: { iiif__madoc_id?: string }) { - return this.request(`/api/search/model?${query ? stringify(query) : ''}`); + try { + return this.request(`/api/search/model?${query ? stringify(query) : ''}`); + } catch (e) { + return []; + } } async searchGetModel(id: number) { @@ -1970,7 +1990,7 @@ export class ApiClient { async searchGetIIIF(id: string) { try { - return this.request(`/api/search/iiif/${id}`); + return this.request(`/api/enrichment/internal/madoc/resource/${id}/`); } catch (err) { return undefined; } @@ -1978,7 +1998,7 @@ export class ApiClient { async searchDeleteIIIF(id: string) { try { - return this.request(`/api/search/iiif/${id}`, { + return this.request(`/api/enrichment/internal/madoc/resource/${id}/`, { method: 'DELETE', }); } catch (err) { diff --git a/services/madoc-ts/src/types/search.ts b/services/madoc-ts/src/types/search.ts index 83c99b747..8f6325e20 100644 --- a/services/madoc-ts/src/types/search.ts +++ b/services/madoc-ts/src/types/search.ts @@ -13,15 +13,18 @@ export type SearchResult = { /** Optional thumbnail of resource */ madoc_thumbnail?: string; + thumbnail?: string; /** Label for the resource from the search result */ label: InternationalString; /** List of contexts for the resource */ - contexts: Array<{ - type: string; - id: string; - }>; + contexts: + | Array + | Array<{ + type: string; + id: string; + }>; /** * List of hits. diff --git a/services/madoc-ts/src/utility/capture-model-to-indexables.ts b/services/madoc-ts/src/utility/capture-model-to-indexables.ts index 107f19ffa..b3d68945c 100644 --- a/services/madoc-ts/src/utility/capture-model-to-indexables.ts +++ b/services/madoc-ts/src/utility/capture-model-to-indexables.ts @@ -20,6 +20,12 @@ export type SearchIndexable = { language_iso639_1: string; language_display: string; language_pg: string; + + // New types. + group_id?: string | null; + contexts?: string[]; + indexable_date_range_start?: string | null; + indexable_date_range_end?: string | null; }; function extractValueFromModelField(field: BaseField): string { From de74ab5e2fc2cf7cd2484fdb3e99b8bc023c1392 Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:33:49 +0000 Subject: [PATCH 005/191] IDA-893 Added new Site APIs for topics --- services/madoc-ts/src/router.ts | 4 + .../src/routes/site/site-topic-type.ts | 54 +++++++++++- .../src/routes/site/site-topic-types.ts | 41 ++++++++- .../madoc-ts/src/routes/site/site-topic.ts | 55 +++++++++++- .../madoc-ts/src/routes/site/site-topics.ts | 56 +++++++++++++ services/madoc-ts/src/routes/topics/index.ts | 15 ++++ .../routes/topics/topic-type-autocomplete.ts | 16 ++++ services/madoc-ts/src/types/schemas/topics.ts | 84 +++++++++++++++++++ 8 files changed, 318 insertions(+), 7 deletions(-) create mode 100644 services/madoc-ts/src/routes/site/site-topics.ts create mode 100644 services/madoc-ts/src/routes/topics/index.ts create mode 100644 services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts create mode 100644 services/madoc-ts/src/types/schemas/topics.ts diff --git a/services/madoc-ts/src/router.ts b/services/madoc-ts/src/router.ts index 62efff55b..159694862 100644 --- a/services/madoc-ts/src/router.ts +++ b/services/madoc-ts/src/router.ts @@ -120,7 +120,9 @@ import { updateProjectStatus } from './routes/projects/update-project-status'; import { siteManifestTasks } from './routes/site/site-manifest-tasks'; import { getStaticPage, sitePages } from './routes/site/site-pages'; import { siteTaskMetadata } from './routes/site/site-task-metadata'; +import { siteTopics } from './routes/site/site-topics'; import { siteUserAutocomplete } from './routes/site/site-user-autocomplete'; +import { topicRoutes } from './routes/topics/index'; import { forgotPassword } from './routes/user/forgot-password'; import { getSiteUser } from './routes/user/get-site-user'; import { loginRefresh } from './routes/user/login-refresh'; @@ -573,6 +575,7 @@ export const router = new TypedRouter({ 'site-project': [TypedRouter.GET, '/s/:slug/madoc/api/projects/:projectSlug', siteProject], 'site-projects': [TypedRouter.GET, '/s/:slug/madoc/api/projects', siteProjects], 'site-search': [TypedRouter.POST, '/s/:slug/madoc/api/search', siteSearch], + 'site-topic-index': [TypedRouter.GET, '/s/:slug/madoc/api/topics/_all', siteTopics], 'site-topic': [TypedRouter.GET, '/s/:slug/madoc/api/topics/:type/:id', siteTopic], 'site-topic-type': [TypedRouter.GET, '/s/:slug/madoc/api/topics/:type', siteTopicType], 'site-topic-types': [TypedRouter.GET, '/s/:slug/madoc/api/topics', siteTopicTypes], @@ -627,6 +630,7 @@ export const router = new TypedRouter({ ...annotationStyles, ...captureModelRoutes, ...getAuthRoutes(), + ...topicRoutes, // Development 'development-plugin': [TypedRouter.POST, '/api/madoc/development/plugin-token', developmentPlugin], diff --git a/services/madoc-ts/src/routes/site/site-topic-type.ts b/services/madoc-ts/src/routes/site/site-topic-type.ts index 71f42ef3c..a98e30155 100644 --- a/services/madoc-ts/src/routes/site/site-topic-type.ts +++ b/services/madoc-ts/src/routes/site/site-topic-type.ts @@ -1,6 +1,54 @@ +import { EntitySnippetMadoc, EntityTypeMadocResponse } from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; +import { TopicSnippet, TopicType } from '../../types/schemas/topics'; -export const siteTopicType: RouteMiddleware<{ slug: string; type: string }> = async context => { - context.response.status = 404; - context.response.body = { error: 'not implemented' }; +export const siteTopicType: RouteMiddleware<{ type: string }> = async context => { + const { siteApi } = context.state; + + const slug = context.params.type; + + // @todo change this to be SLUG later. + // const response = await siteApi.authority.entity_type.get(id); + const response = await siteApi.authority.getEntityType(slug); + + context.response.body = compatTopicType(response, slug); }; + +function compatTopic(topic: EntitySnippetMadoc): TopicSnippet { + return { + id: topic.id, + label: { none: [topic.label] }, + slug: topic.id, + // @todo change to slug when supported. + // slug: encodeURIComponent(topic.label), + }; +} + +// @todo remove once in the backend. +function compatTopicType(topicType: EntityTypeMadocResponse, slug: string): TopicType { + const nuked: any = { results: undefined, url: undefined }; + + return { + // Missing properties + id: 'null', + slug: encodeURIComponent(slug), + + ...topicType, + + // Properties to change. + label: { none: [slug] }, + otherLabels: [], // @todo no other labels given. + topics: topicType.results.map(compatTopic), + + // Mocked editorial + editorial: { + summary: { en: ['Example summary'] }, + related: [], + featured: [], + heroImage: null, + }, + + // Nuke these. + ...nuked, + }; +} diff --git a/services/madoc-ts/src/routes/site/site-topic-types.ts b/services/madoc-ts/src/routes/site/site-topic-types.ts index a13611f28..d9f7b1656 100644 --- a/services/madoc-ts/src/routes/site/site-topic-types.ts +++ b/services/madoc-ts/src/routes/site/site-topic-types.ts @@ -1,6 +1,43 @@ +import { EnrichmentEntityTypeSnippet } from '../../extensions/enrichment/authority/types'; +import { DjangoPagination } from '../../extensions/enrichment/types'; import { RouteMiddleware } from '../../types/route-middleware'; +import { Pagination } from '../../types/schemas/_pagination'; +import { TopicTypeSnippet } from '../../types/schemas/topics'; export const siteTopicTypes: RouteMiddleware<{ slug: string }> = async context => { - context.response.status = 404; - context.response.body = { error: 'not implemented' }; + const { siteApi } = context.state; + + const page = Number(context.query.page || 1) || 1; + const response = await siteApi.authority.entity_type.list(page); + + context.response.body = compatTopicTypes(response, page); }; + +// @todo remove once changed in backend. +function compatTopicTypeSnippet(snippet: EnrichmentEntityTypeSnippet): TopicTypeSnippet { + return { + ...snippet, + slug: snippet.label, + label: { none: [snippet.label] }, + }; +} + +// @todo remove once changed in backend. +function compatTopicTypes(response: DjangoPagination, page: number) { + const { results, next, previous, count } = response; + const totalPages = Math.min(Math.ceil(results.length / count), page); + + return { + topicTypes: results.map(compatTopicTypeSnippet), + original_pagination: { + next, + previous, + count, + }, + pagination: { + page, + totalResults: results.length * totalPages, + totalPages, + } as Pagination, + }; +} diff --git a/services/madoc-ts/src/routes/site/site-topic.ts b/services/madoc-ts/src/routes/site/site-topic.ts index 64a0a1bc5..43b48723a 100644 --- a/services/madoc-ts/src/routes/site/site-topic.ts +++ b/services/madoc-ts/src/routes/site/site-topic.ts @@ -1,6 +1,57 @@ +import { EnrichmentEntity } from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; +import { Topic } from '../../types/schemas/topics'; +import { NotFound } from '../../utility/errors/not-found'; export const siteTopic: RouteMiddleware<{ slug: string; type: string; id: string }> = async context => { - context.response.status = 404; - context.response.body = { error: 'not implemented' }; + const { siteApi } = context.state; + + const slug = context.params.id; + const topicTypeSlug = context.params.type; + + // const response = await siteApi.authority.entity_type.get(id); + const response = await siteApi.authority.entity.get(slug); + + if (response.type) { + if (topicTypeSlug !== response.type.label && topicTypeSlug !== response.type.id) { + throw new NotFound('Topic not found'); + } + } + + // @todo validate topic-type matches. + + context.response.body = compatTopic(response); }; + +function compatTopic(topic: EnrichmentEntity): Topic { + const nuked: any = { other_labels: undefined, url: undefined }; + + return { + ...topic, + // @todo change to slug. + slug: topic.id, + label: { none: [topic.label] }, + topicType: topic.type + ? { + id: topic.type.id, + slug: topic.type.label, + label: { none: [topic.type.label] }, + } + : undefined, + otherLabels: topic.other_labels.map(label => ({ [label.language.slice(0, 2)]: [label.value] })), + authorities: topic.authorities.map(auth => ({ id: auth.name, label: { none: [auth.name] } })), + + // Mocked values. + editorial: { + related: [], + featured: [], + contributors: [], + description: { en: ['Example description'] }, + heroImage: null, + summary: { en: ['Example summary'] }, + }, + + // Nuke these properties + ...nuked, + }; +} diff --git a/services/madoc-ts/src/routes/site/site-topics.ts b/services/madoc-ts/src/routes/site/site-topics.ts new file mode 100644 index 000000000..a9aac811f --- /dev/null +++ b/services/madoc-ts/src/routes/site/site-topics.ts @@ -0,0 +1,56 @@ +import { EnrichmentEntitySnippet } from '../../extensions/enrichment/authority/types'; +import { DjangoPagination } from '../../extensions/enrichment/types'; +import { RouteMiddleware } from '../../types/route-middleware'; +import { Pagination } from '../../types/schemas/_pagination'; +import { TopicSnippet } from '../../types/schemas/topics'; + +export const siteTopics: RouteMiddleware<{ slug: string }> = async context => { + const { siteApi } = context.state; + + const topicType = context.query.topicType || ''; + const page = Number(context.query.page || 1) || 1; + + // @todo be able to filter by `topicType` + const response = await siteApi.authority.entity.list(page); + + context.response.body = compatTopic(response, page); +}; + +// @todo remove once changed in backend. +function compatTopicSnippet(snippet: EnrichmentEntitySnippet): TopicSnippet { + return { + ...snippet, + // Other properties we might want or need. + label: { none: [snippet.label] }, + slug: snippet.id, + // @todo change to slug when supported. + // slug: encodeURIComponent(snippet.label), + topicType: snippet.type + ? { + id: snippet.type.id, + slug: snippet.type.label, + label: { none: [snippet.type.label] }, + } + : undefined, + }; +} + +// @todo remove once changed in backend. +function compatTopic(response: DjangoPagination, page: number) { + const { results, next, previous, count } = response; + const totalPages = Math.min(Math.ceil(results.length / count), page); + + return { + topic: results.map(compatTopicSnippet), + original_pagination: { + next, + previous, + count, + }, + pagination: { + page, + totalResults: results.length * totalPages, + totalPages, + } as Pagination, + }; +} diff --git a/services/madoc-ts/src/routes/topics/index.ts b/services/madoc-ts/src/routes/topics/index.ts new file mode 100644 index 000000000..3e3efb1c2 --- /dev/null +++ b/services/madoc-ts/src/routes/topics/index.ts @@ -0,0 +1,15 @@ +import { RouteWithParams, TypedRouter } from '../../utility/typed-router'; +// import { siteTopicTypes } from '../site/site-topic-types'; +// import { siteTopics } from '../site/site-topics'; +import { topicTypeAutocomplete } from './topic-type-autocomplete'; + +export const topicRoutes: Record> = { + 'topic-type-autocomplete': [TypedRouter.GET, '/api/madoc/topic-types/autocomplete', topicTypeAutocomplete], + + // Site routes. + // 'site-topic-types': [TypedRouter.GET, '/s/:slug/madoc/api/topic-types', siteTopicTypes], + // 'site-topic-type': [TypedRouter.GET, '/s/:slug/madoc/api/topic-types/:slug', siteTopicTypes], + // 'site-topics': [TypedRouter.GET, '/s/:slug/madoc/api/topics', siteTopics], + // topic type + // single topic +}; diff --git a/services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts b/services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts new file mode 100644 index 000000000..015acb5fe --- /dev/null +++ b/services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts @@ -0,0 +1,16 @@ +import { api } from '../../gateway/api.server'; +import { RouteMiddleware } from '../../types/route-middleware'; +import { userWithScope } from '../../utility/user-with-scope'; + +export const topicTypeAutocomplete: RouteMiddleware = async context => { + const { siteId, id } = userWithScope(context, ['site.admin']); + + const items = await api.asUser({ userId: id, siteId }).authority.entity_type.list(1); + + context.response.body = { + completions: items.results.map(item => ({ + uri: item.label, + label: item.label, + })) as { uri: string; label: string; resource_class?: string; score?: number }[], + }; +}; diff --git a/services/madoc-ts/src/types/schemas/topics.ts b/services/madoc-ts/src/types/schemas/topics.ts new file mode 100644 index 000000000..bb9f221fb --- /dev/null +++ b/services/madoc-ts/src/types/schemas/topics.ts @@ -0,0 +1,84 @@ +import { InternationalString } from '@iiif/presentation-3'; +import { SearchResult } from '../search'; +import { Pagination } from './_pagination'; + +export interface TopicTypeSnippet { + id: string; + slug: string; + label: InternationalString; // From string. + + // @todo other presentational properties. All optional + thumbnail?: { url: string; alt?: string }; + totalObjects?: number; +} + +export interface TopicType { + id: string; + slug: string; + label: InternationalString; // From string. + otherLabels: InternationalString[]; + pagination: Pagination; + topics: TopicSnippet[]; + + // @todo other presentational properties. All optional + editorial: { + summary?: InternationalString; + heroImage?: { + url: string; + alt?: string; + overlayColor?: string; + transparent?: boolean; + }; + featured?: Array; + related?: Array; + }; +} + +export interface TopicSnippet { + id: string; + slug: string; + label: InternationalString; // From string. + topicType?: TopicTypeSnippet; + + // @todo other presentation properties. All optional. + thumbnail?: { url: string; alt?: string }; + totalObjects?: number; +} + +export interface Topic { + id: string; + slug: string; + label: InternationalString; // From string. + topicType?: TopicTypeSnippet; + otherLabels: InternationalString[]; + authorities: Array<{ id: string; label: InternationalString }>; + modified: string; + created: string; + + // @todo other presentation properties. These should all be optional. + editorial: { + contributors?: Array<{ + id: string; // Madoc URN. + label: string; + }>; + summary?: InternationalString; + heroImage?: { + url: string; + alt?: string; + overlayColor?: string; + transparent?: boolean; + } | null; + description?: { + label: InternationalString; + value: InternationalString; + }; + // @todo search result format MAY change, hopefully not. + featured?: Array; + related?: Array; + }; +} + +export interface TopicTypeListResponse { + topicTypes: TopicTypeSnippet[]; + pagination: Pagination; +} From a4fc07af614f288c8c4a7540f1de5842a8d65af4 Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:34:40 +0000 Subject: [PATCH 006/191] IDA-893 Temporary changes to canvas/manifest indexing for new service --- .../content/canvases/canvas-search-index.tsx | 18 ++++-- .../manifests/manifest-search-index.tsx | 2 + .../src/routes/search/index-canvas.ts | 55 +++++++------------ .../src/routes/search/index-manifest.ts | 53 ++++++++---------- 4 files changed, 59 insertions(+), 69 deletions(-) diff --git a/services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-search-index.tsx b/services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-search-index.tsx index 3431cd655..311cf7942 100644 --- a/services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-search-index.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-search-index.tsx @@ -49,7 +49,7 @@ export const CanvasSearchIndex = createUniversalComponent
{JSON.stringify(data.canvas, null, 2)}

Indexable

- {data + {data && data.models ? data.models.results.map((result: any, key: number) => { return (
@@ -70,10 +70,18 @@ export const CanvasSearchIndex = createUniversalComponent return ['canvas-search-index', { id: Number(params.id) }]; }, getData: async (key, { id }, api) => { - return { - canvas: await api.searchGetIIIF(`urn:madoc:canvas:${id}`), - models: await api.searchListModels({ iiif__madoc_id: `urn:madoc:canvas:${id}` }), - }; + try { + return { + canvas: await api.searchGetIIIF(`urn:madoc:canvas:${id}`), + models: { results: [] }, + // models: await api.searchListModels({ iiif__madoc_id: `urn:madoc:canvas:${id}` }), + }; + } catch (e) { + return { + canvas: null, + models: { results: [] }, + }; + } }, } ); diff --git a/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx b/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx index e7789b127..a28bbf40c 100644 --- a/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx @@ -7,6 +7,7 @@ import { createUniversalComponent } from '../../../../shared/utility/create-univ import { useParams } from 'react-router-dom'; import { useIndexResource } from '../../../hooks/use-index-resource'; import { EditManifestStructure } from './edit-manifest-structure'; +import { ManageTags } from '../../../molecules/ManageTags'; type ManifestSearchIndexType = { params: { id: string }; @@ -48,6 +49,7 @@ export const ManifestSearchIndex = createUniversalComponent
+
{JSON.stringify(data, null, 2)}
); diff --git a/services/madoc-ts/src/routes/search/index-canvas.ts b/services/madoc-ts/src/routes/search/index-canvas.ts index 33c13c63d..d472ceae5 100644 --- a/services/madoc-ts/src/routes/search/index-canvas.ts +++ b/services/madoc-ts/src/routes/search/index-canvas.ts @@ -4,11 +4,11 @@ import { ParagraphEntity, PARAGRAPHS_PROFILE, } from '../../extensions/capture-models/Paragraphs/Paragraphs.helpers'; +import { createSearchIngest } from '../../extensions/enrichment/utilities/create-search-ingest'; import { traverseDocument } from '../../frontend/shared/capture-models/helpers/traverse-document'; import { BaseField } from '../../frontend/shared/capture-models/types/field-types'; import { api } from '../../gateway/api.server'; import { RouteMiddleware } from '../../types/route-middleware'; -import { SearchIngestRequest } from '../../types/search'; import { captureModelToIndexables, SearchIndexable } from '../../utility/capture-model-to-indexables'; import { optionalUserWithScope } from '../../utility/user-with-scope'; import { getParentResources } from '../../database/queries/resource-queries'; @@ -45,12 +45,10 @@ export const indexCanvas: RouteMiddleware<{ id: string }> = async context => { ) : []; - const searchPayload: SearchIngestRequest = { - id: `urn:madoc:canvas:${canvasId}`, - type: 'Canvas', - cascade: false, - cascade_canvases: false, - resource: { + const searchPayload = createSearchIngest( + canvasId, + 'canvas', + { type: 'Canvas', id: sourceId, label: canvas.label as any, @@ -62,33 +60,16 @@ export const indexCanvas: RouteMiddleware<{ id: string }> = async context => { requiredStatement: canvas.requiredStatement, navDate: (canvas as any).navDate, }, - thumbnail: canvas.thumbnail ? (canvas.thumbnail[0].id as any) : null, - contexts: [ - { id: siteUrn, type: 'Site' }, - ...projectsWithin.map(({ id }) => { - return { id: `urn:madoc:project:${id}`, type: 'Project' }; - }), - ...collectionsWithin.map(({ resource_id }) => { - return { id: `urn:madoc:collection:${resource_id}`, type: 'Collection' }; - }), - // Should this be contexts or manifests here? Do canvases have site contexts too? - ...manifestsWithin.map(({ resource_id }) => { - return { id: `urn:madoc:manifest:${resource_id}`, type: 'Manifest' }; - }), - { - id: `urn:madoc:canvas:${canvasId}`, - type: 'Canvas', - }, - ], - }; - - try { - await api.searchGetIIIF(`urn:madoc:canvas:${canvasId}`); + siteId, + canvas.thumbnail ? (canvas.thumbnail[0].id as any) : null, + [ + ...projectsWithin.map(({ id }) => `urn:madoc:project:${id}`), + ...collectionsWithin.map(({ resource_id }) => `urn:madoc:collection:${resource_id}`), + ...manifestsWithin.map(({ resource_id }) => `urn:madoc:manifest:${resource_id}`), + ] + ); - await userApi.searchReIngest(searchPayload); - } catch (err) { - await userApi.searchIngest(searchPayload); - } + await userApi.enrichmentIngestResource(searchPayload); // 1. Load all capture models for this canvas on this site. const models = await userApi.getAllCaptureModels({ @@ -169,14 +150,16 @@ export const indexCanvas: RouteMiddleware<{ id: string }> = async context => { paragraph: paragraphs as any, }; try { - await userApi.indexCaptureModel(modelId, `urn:madoc:canvas:${canvasId}`, resource); + // @TODO index capture model (specifically paragraphs/OCR) + // await userApi.indexCaptureModel(modelId, `urn:madoc:canvas:${canvasId}`, resource); } catch (err) { // no-op } } + // @todo implement RAW indexables // 5. Index remaining Capture models - if (models.length) { + if ((false as boolean) && models.length) { const indexables: SearchIndexable[] = []; for (const model of fullModels) { try { @@ -203,5 +186,7 @@ export const indexCanvas: RouteMiddleware<{ id: string }> = async context => { // 6. Index any remaining capture model partials // @todo - this is not yet used. + await userApi.triggerSearchIndex(canvasId, 'canvas'); + context.response.body = canvas; }; diff --git a/services/madoc-ts/src/routes/search/index-manifest.ts b/services/madoc-ts/src/routes/search/index-manifest.ts index 21081d8e9..b1649eb50 100644 --- a/services/madoc-ts/src/routes/search/index-manifest.ts +++ b/services/madoc-ts/src/routes/search/index-manifest.ts @@ -4,6 +4,7 @@ import { getSingleManifest, mapManifestSnippets, } from '../../database/queries/get-manifest-snippets'; +import { createSearchIngest } from '../../extensions/enrichment/utilities/create-search-ingest'; import { api } from '../../gateway/api.server'; import { RouteMiddleware } from '../../types/route-middleware'; import { optionalUserWithScope } from '../../utility/user-with-scope'; @@ -12,6 +13,7 @@ export const indexManifest: RouteMiddleware<{ id: string }> = async context => { const { siteId, siteUrn } = optionalUserWithScope(context, []); const userApi = api.asUser({ siteId }); const manifestId = Number(context.params.id); + const sourceId = `http://madoc.dev/urn:madoc:manifest:${manifestId}`; const rows = await context.connection.any( getManifestSnippets( @@ -51,37 +53,30 @@ export const indexManifest: RouteMiddleware<{ id: string }> = async context => { const manifest = table.manifests[`${manifestId}`]; - const searchPayload = { - id: `urn:madoc:manifest:${manifestId}`, - type: 'Manifest', - cascade: false, - cascade_canvases: false, - resource: { + const searchPayload = createSearchIngest( + manifestId, + 'manifest', + { + id: sourceId, ...manifest, - id: `http://madoc.dev/urn:madoc:manifest:${manifestId}`, - type: 'Manifest', }, - thumbnail: manifest.thumbnail, - contexts: [ - { id: siteUrn, type: 'Site' }, - ...projectsWithin.map(({ id }) => { - return { id: `urn:madoc:project:${id}`, type: 'Project' }; - }), - ...collectionsWithin.map(({ resource_id }) => { - return { id: `urn:madoc:collection:${resource_id}`, type: 'Collection' }; - }), - { - id: `urn:madoc:manifest:${manifestId}`, - type: 'Manifest', - }, - ], - }; + siteId, + manifest.thumbnail, + [ + ...projectsWithin.map(({ id }) => `urn:madoc:project:${id}`), + ...collectionsWithin.map(({ resource_id }) => `urn:madoc:collection:${resource_id}`), + ] + ); - try { - await api.searchGetIIIF(`urn:madoc:manifest:${manifestId}`); + await userApi.enrichmentIngestResource(searchPayload); - context.response.body = await userApi.searchReIngest(searchPayload); - } catch (err) { - context.response.body = await userApi.searchIngest(searchPayload); - } + await userApi.triggerSearchIndex(manifestId, 'manifest'); + + // try { + // await api.searchGetIIIF(`urn:madoc:manifest:${manifestId}`); + // + // context.response.body = await userApi.searchReIngest(searchPayload); + // } catch (err) { + // context.response.body = await userApi.searchIngest(searchPayload); + // } }; From 56b45fdfd4201429dac1e751f7adcfb6adf40887 Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:35:52 +0000 Subject: [PATCH 007/191] IDA-893 Page block support for topic types + topics --- .../2022-11-27T20-30.topic-slots.sql | 14 ++ .../down/2022-11-27T20-30.topic-slots.sql | 13 ++ .../src/database/queries/site-editorial.ts | 142 ++++++++++++++++-- .../shared/page-blocks/auto-slot-loader.tsx | 4 + .../shared/page-blocks/explain-slot.tsx | 2 + .../shared/page-blocks/render-blank-slot.tsx | 30 ++++ .../frontend/shared/utility/create-link.ts | 12 +- .../frontend/site/hooks/use-relative-links.ts | 11 +- .../frontend/site/hooks/use-route-context.ts | 10 +- .../src/repository/page-blocks-repository.ts | 10 ++ .../madoc-ts/src/types/schemas/site-page.ts | 30 +++- 11 files changed, 256 insertions(+), 22 deletions(-) create mode 100644 services/madoc-ts/migrations/2022-11-27T20-30.topic-slots.sql create mode 100644 services/madoc-ts/migrations/down/2022-11-27T20-30.topic-slots.sql diff --git a/services/madoc-ts/migrations/2022-11-27T20-30.topic-slots.sql b/services/madoc-ts/migrations/2022-11-27T20-30.topic-slots.sql new file mode 100644 index 000000000..5e9e1decd --- /dev/null +++ b/services/madoc-ts/migrations/2022-11-27T20-30.topic-slots.sql @@ -0,0 +1,14 @@ +--topic-slots (up) +alter table "site_slots" + add filter_topic_type_none boolean default true, + add filter_topic_type_all boolean default false, + add filter_topic_type_exact text, + add filter_topic_type_whitelist text[], + add filter_topic_type_blacklist text[], + + add filter_topic_none boolean default true, + add filter_topic_all boolean default false, + add filter_topic_exact text, + add filter_topic_whitelist text[], + add filter_topic_blacklist text[] +; diff --git a/services/madoc-ts/migrations/down/2022-11-27T20-30.topic-slots.sql b/services/madoc-ts/migrations/down/2022-11-27T20-30.topic-slots.sql new file mode 100644 index 000000000..170fe9f84 --- /dev/null +++ b/services/madoc-ts/migrations/down/2022-11-27T20-30.topic-slots.sql @@ -0,0 +1,13 @@ +--topic-slots (down) + +alter table "site_slots" drop column filter_topic_type_none; +alter table "site_slots" drop column filter_topic_type_all; +alter table "site_slots" drop column filter_topic_type_exact; +alter table "site_slots" drop column filter_topic_type_whitelist; +alter table "site_slots" drop column filter_topic_type_blacklist; + +alter table "site_slots" drop column filter_topic_none; +alter table "site_slots" drop column filter_topic_all; +alter table "site_slots" drop column filter_topic_exact; +alter table "site_slots" drop column filter_topic_whitelist; +alter table "site_slots" drop column filter_topic_blacklist; diff --git a/services/madoc-ts/src/database/queries/site-editorial.ts b/services/madoc-ts/src/database/queries/site-editorial.ts index 74d789344..09aad08e1 100644 --- a/services/madoc-ts/src/database/queries/site-editorial.ts +++ b/services/madoc-ts/src/database/queries/site-editorial.ts @@ -87,6 +87,38 @@ export function getContextualSlots(context: ServerEditorialContext, siteId: numb `); } + if (context.topicType) { + contextQueries.push(sql`( + filter_topic_type_all = true or + filter_topic_type_exact = ${context.topicType} or + ${context.topicType} = any (filter_topic_type_whitelist) or + ( + not ${context.topicType} = any (filter_topic_type_blacklist) and + filter_topic_type_none = false + ) + )`); + } else { + contextQueries.push(sql` + filter_topic_type_none = true + `); + } + + if (context.topic) { + contextQueries.push(sql`( + filter_topic_all = true or + filter_topic_exact = ${context.topic} or + ${context.topic} = any (filter_topic_whitelist) or + ( + not ${context.topic} = any (filter_topic_blacklist) and + filter_topic_none = false + ) + )`); + } else { + contextQueries.push(sql` + filter_topic_none = true + `); + } + if (context.slotIds && context.slotIds.length) { if (context.slotIds.length === 1) { contextQueries.push(sql` @@ -135,6 +167,16 @@ export function getContextualSlots(context: ServerEditorialContext, siteId: numb ss.filter_canvas_exact as slot__filter_canvas_exact, ss.filter_canvas_whitelist as slot__filter_canvas_whitelist, ss.filter_canvas_blacklist as slot__filter_canvas_blacklist, + ss.filter_topic_type_none as slot__filter_topic_type_none, + ss.filter_topic_type_all as slot__filter_topic_type_all, + ss.filter_topic_type_exact as slot__filter_topic_type_exact, + ss.filter_topic_type_whitelist as slot__filter_topic_type_whitelist, + ss.filter_topic_type_blacklist as slot__filter_topic_type_blacklist, + ss.filter_topic_none as slot__filter_topic_none, + ss.filter_topic_all as slot__filter_topic_all, + ss.filter_topic_exact as slot__filter_topic_exact, + ss.filter_topic_whitelist as slot__filter_topic_whitelist, + ss.filter_topic_blacklist as slot__filter_topic_blacklist, -- Block properties sb.id as block__id, @@ -200,7 +242,7 @@ export function addPage(page: CreateNormalPageRequest, siteId: number, user: { i // @todo Page structure // Adding / Removing slots -export function getSpecificityDigitFromConfig(config: SlotFilterConfig) { +export function getSpecificityDigitFromConfig(config: SlotFilterConfig | SlotFilterConfig) { if (config.exact) { return 4; } @@ -239,10 +281,18 @@ export function getSpecificity(config: CreateSlotRequest['filters']) { specificity += getSpecificityDigitFromConfig(config.canvas) * 1000; } + // These are in their own tree, similar to projects/collections. + if (config.topicType) { + specificity += getSpecificityDigitFromConfig(config.topicType) * 1; + } + if (config.topic) { + specificity += getSpecificityDigitFromConfig(config.topic) * 10; + } + return specificity; } -export function parseFilter(config?: SlotFilterConfig) { +export function parseFilter(config?: SlotFilterConfig | SlotFilterConfig, isString = false) { const defaultReturn = { exact: null, whitelist: null, @@ -261,13 +311,13 @@ export function parseFilter(config?: SlotFilterConfig) { if (config.whitelist) { return { ...defaultReturn, - whitelist: sql.array(config.whitelist, 'int' as any), + whitelist: sql.array(config.whitelist, isString ? 'text' : ('int' as any)), }; } if (config.blacklist) { return { ...defaultReturn, - blacklist: sql.array(config.blacklist, 'int' as any), + blacklist: sql.array(config.blacklist, isString ? 'text' : ('int' as any)), }; } if (config.all) { @@ -344,6 +394,20 @@ export type SlotJoinedProperties = { slot__filter_canvas_exact: number; slot__filter_canvas_whitelist?: number[]; slot__filter_canvas_blacklist?: number[]; + + // Filter topic types + slot__filter_topic_type_none: boolean; + slot__filter_topic_type_all: boolean; + slot__filter_topic_type_exact: string; + slot__filter_topic_type_whitelist?: string[]; + slot__filter_topic_type_blacklist?: string[]; + + // Filter topic + slot__filter_topic_none: boolean; + slot__filter_topic_all: boolean; + slot__filter_topic_exact: string; + slot__filter_topic_whitelist?: string[]; + slot__filter_topic_blacklist?: string[]; }; export type BlockJoinedProperties = { @@ -448,6 +512,20 @@ export function mapSlot(slot: any, prefix = ''): SiteSlot { exact: slot[prefix + 'filter_canvas_exact'] || undefined, all: slot[prefix + 'filter_canvas_all'] || undefined, }, + topicType: { + none: slot[prefix + 'filter_topic_type_none'] || undefined, + blacklist: slot[prefix + 'filter_topic_type_blacklist'] || undefined, + whitelist: slot[prefix + 'filter_topic_type_whitelist'] || undefined, + exact: slot[prefix + 'filter_topic_type_exact'] || undefined, + all: slot[prefix + 'filter_topic_type_all'] || undefined, + }, + topic: { + none: slot[prefix + 'filter_topic_none'] || undefined, + blacklist: slot[prefix + 'filter_topic_blacklist'] || undefined, + whitelist: slot[prefix + 'filter_topic_whitelist'] || undefined, + exact: slot[prefix + 'filter_topic_exact'] || undefined, + all: slot[prefix + 'filter_topic_all'] || undefined, + }, }, }; } @@ -542,6 +620,8 @@ export function addSlot(slot: CreateSlotRequest, siteId: number) { const collection = parseFilter(slot.filters?.collection); const manifest = parseFilter(slot.filters?.manifest); const canvas = parseFilter(slot.filters?.canvas); + const topicType = parseFilter(slot.filters?.topicType, true); + const topic = parseFilter(slot.filters?.topic, true); return sql` insert into site_slots ( @@ -568,6 +648,16 @@ export function addSlot(slot: CreateSlotRequest, siteId: number) { filter_canvas_exact, filter_canvas_whitelist, filter_canvas_blacklist, + filter_topic_type_none, + filter_topic_type_all, + filter_topic_type_exact, + filter_topic_type_whitelist, + filter_topic_type_blacklist, + filter_topic_none, + filter_topic_all, + filter_topic_exact, + filter_topic_whitelist, + filter_topic_blacklist, specificity, site_id ) VALUES ( @@ -597,6 +687,19 @@ export function addSlot(slot: CreateSlotRequest, siteId: number) { ${canvas.exact}, ${canvas.whitelist}, ${canvas.blacklist}, + + ${topicType.none}, + ${topicType.all}, + ${topicType.exact}, + ${topicType.whitelist}, + ${topicType.blacklist}, + + ${topic.none}, + ${topic.all}, + ${topic.exact}, + ${topic.whitelist}, + ${topic.blacklist}, + ${specificity}, ${siteId} ) returning * @@ -611,6 +714,8 @@ export function editSlot(id: number, slot: CreateSlotRequest, siteId: number) { const collection = parseFilter(slot.filters?.collection); const manifest = parseFilter(slot.filters?.manifest); const canvas = parseFilter(slot.filters?.canvas); + const topicType = parseFilter(slot.filters?.topicType, true); + const topic = parseFilter(slot.filters?.topic, true); return sql` update site_slots set @@ -620,28 +725,41 @@ export function editSlot(id: number, slot: CreateSlotRequest, siteId: number) { slot_props=${sql.json(slot.props || {})}, filter_project_none=${project.none}, - filter_project_all=${project.all}, - filter_project_exact=${project.exact}, + filter_project_all=${project.all}, + filter_project_exact=${project.exact}, filter_project_whitelist=${project.whitelist}, filter_project_blacklist=${project.blacklist}, filter_collection_none=${collection.none}, - filter_collection_all=${collection.all}, - filter_collection_exact=${collection.exact}, + filter_collection_all=${collection.all}, + filter_collection_exact=${collection.exact}, filter_collection_whitelist=${collection.whitelist}, filter_collection_blacklist=${collection.blacklist}, filter_manifest_none=${manifest.none}, - filter_manifest_all=${manifest.all}, - filter_manifest_exact=${manifest.exact}, + filter_manifest_all=${manifest.all}, + filter_manifest_exact=${manifest.exact}, filter_manifest_whitelist=${manifest.whitelist}, filter_manifest_blacklist=${manifest.blacklist}, filter_canvas_none=${canvas.none}, - filter_canvas_all=${canvas.all}, - filter_canvas_exact=${canvas.exact}, + filter_canvas_all=${canvas.all}, + filter_canvas_exact=${canvas.exact}, filter_canvas_whitelist=${canvas.whitelist}, filter_canvas_blacklist=${canvas.blacklist}, + + filter_topic_type_none=${topicType.none}, + filter_topic_type_all=${topicType.all}, + filter_topic_type_exact=${topicType.exact}, + filter_topic_type_whitelist=${topicType.whitelist}, + filter_topic_type_blacklist=${topicType.blacklist}, + + filter_topic_none=${topic.none}, + filter_topic_all=${topic.all}, + filter_topic_exact=${topic.exact}, + filter_topic_whitelist=${topic.whitelist}, + filter_topic_blacklist=${topic.blacklist}, + specificity=${specificity} where site_id = ${siteId} and id = ${id} returning * `; diff --git a/services/madoc-ts/src/frontend/shared/page-blocks/auto-slot-loader.tsx b/services/madoc-ts/src/frontend/shared/page-blocks/auto-slot-loader.tsx index 316cce558..b29392f82 100644 --- a/services/madoc-ts/src/frontend/shared/page-blocks/auto-slot-loader.tsx +++ b/services/madoc-ts/src/frontend/shared/page-blocks/auto-slot-loader.tsx @@ -27,6 +27,8 @@ export const AutoSlotLoader: React.FC<{ fuzzy?: boolean; slots?: string[]; child routeContext.manifest = routeMatch.params.manifestId ? Number(routeMatch.params.manifestId) : undefined; routeContext.canvas = routeMatch.params.canvasId ? Number(routeMatch.params.canvasId) : undefined; routeContext.project = routeMatch.params.slug ? routeMatch.params.slug : undefined; + routeContext.topicType = routeMatch.params.topicType ? routeMatch.params.topicType : undefined; + routeContext.topic = routeMatch.params.topic ? routeMatch.params.topic : undefined; } routeContext.slotIds = slots && slots.length ? slots : undefined; @@ -37,6 +39,8 @@ export const AutoSlotLoader: React.FC<{ fuzzy?: boolean; slots?: string[]; child routeMatch.params.canvasId, routeMatch.params.collectionId, routeMatch.params.manifestId, + routeMatch.params.topicType, + routeMatch.params.topic, routeMatch.params.slug, ]); diff --git a/services/madoc-ts/src/frontend/shared/page-blocks/explain-slot.tsx b/services/madoc-ts/src/frontend/shared/page-blocks/explain-slot.tsx index 96d2402f2..73e438b7d 100644 --- a/services/madoc-ts/src/frontend/shared/page-blocks/explain-slot.tsx +++ b/services/madoc-ts/src/frontend/shared/page-blocks/explain-slot.tsx @@ -7,6 +7,8 @@ export const ExplainSlot: React.FC<{ slot: SiteSlot; context?: EditorialContext (slot.filters.project?.none && slot.filters.collection?.none && slot.filters.manifest?.none && + slot.filters.topicType?.none && + slot.filters.topic?.none && slot.filters.canvas?.none) ) { return ( diff --git a/services/madoc-ts/src/frontend/shared/page-blocks/render-blank-slot.tsx b/services/madoc-ts/src/frontend/shared/page-blocks/render-blank-slot.tsx index 6824fc54a..0b10f4596 100644 --- a/services/madoc-ts/src/frontend/shared/page-blocks/render-blank-slot.tsx +++ b/services/madoc-ts/src/frontend/shared/page-blocks/render-blank-slot.tsx @@ -46,6 +46,20 @@ function exactFromContext(context: EditorialContext, projectId?: number): Create : { none: true, }, + topicType: context.topicType + ? { + exact: context.topicType, + } + : { + none: true, + }, + topic: context.topic + ? { + exact: context.topic, + } + : { + none: true, + }, project: projectId ? { exact: projectId, @@ -59,6 +73,20 @@ function exactFromContext(context: EditorialContext, projectId?: number): Create function allOfTypeFromContext(ctx: EditorialContext, projectId?: number): CreateSlotRequest['filters'] { const exact = exactFromContext(ctx, projectId); + if (ctx.topic) { + return { + ...exact, + topic: { all: true }, + }; + } + + if (ctx.topicType) { + return { + ...exact, + topicType: { all: true }, + }; + } + if (ctx.canvas) { return { ...exact, @@ -147,6 +175,8 @@ function createSlotRequest( manifest: { all: true, none: true }, collection: { all: true, none: true }, project: { all: true, none: true }, + topic: { all: true, none: true }, + topicType: { all: true, none: true }, }, }; } diff --git a/services/madoc-ts/src/frontend/shared/utility/create-link.ts b/services/madoc-ts/src/frontend/shared/utility/create-link.ts index 584d823e4..8a460d21c 100644 --- a/services/madoc-ts/src/frontend/shared/utility/create-link.ts +++ b/services/madoc-ts/src/frontend/shared/utility/create-link.ts @@ -7,6 +7,8 @@ export function createLink(opt: { canvasId?: string | number; taskId?: string; parentTaskId?: string; + topicType?: string; + topic?: string; query?: any; subRoute?: string; admin?: boolean; @@ -18,6 +20,14 @@ export function createLink(opt: { }`; const canvasSubRoute = opt.admin ? 'canvases' : 'c'; + // Topics. + if (opt.topicType) { + if (opt.topic) { + return `/topics/${opt.topicType}/${opt.topic}${suffix}`; + } + return `/topics/${opt.topicType}${suffix}`; + } + // Tasks. if (opt.taskId) { if (opt.parentTaskId) { @@ -60,7 +70,7 @@ export function createLink(opt: { if (opt.projectId) { path.push(`/projects/${opt.projectId}`); } - if (opt.collectionId) { + if (opt.collectionId && !opt.admin) { path.push(`/collections/${opt.collectionId}`); } diff --git a/services/madoc-ts/src/frontend/site/hooks/use-relative-links.ts b/services/madoc-ts/src/frontend/site/hooks/use-relative-links.ts index ea4450239..1a1c3839d 100644 --- a/services/madoc-ts/src/frontend/site/hooks/use-relative-links.ts +++ b/services/madoc-ts/src/frontend/site/hooks/use-relative-links.ts @@ -2,8 +2,8 @@ import { useCallback } from 'react'; import { createLink } from '../../shared/utility/create-link'; import { useRouteContext } from './use-route-context'; -export function useRelativeLinks() { - const { canvasId, taskId, manifestId, parentTaskId, collectionId, projectId } = useRouteContext(); +export function useRelativeLinks(admin = false) { + const { canvasId, taskId, manifestId, parentTaskId, collectionId, projectId, topic, topicType } = useRouteContext(); return useCallback( ( @@ -14,6 +14,8 @@ export function useRelativeLinks() { canvasId?: string | number; taskId?: string; parentTaskId?: string; + topicType?: string; + topic?: string; query?: any; subRoute?: string; admin?: boolean; @@ -27,9 +29,12 @@ export function useRelativeLinks() { taskId, parentTaskId, canvasId, + topic, + topicType, + admin, ...opts, }); }, - [canvasId, collectionId, manifestId, parentTaskId, projectId, taskId] + [canvasId, collectionId, manifestId, parentTaskId, projectId, taskId, topic, topicType, admin] ); } diff --git a/services/madoc-ts/src/frontend/site/hooks/use-route-context.ts b/services/madoc-ts/src/frontend/site/hooks/use-route-context.ts index 183003bb4..784846eef 100644 --- a/services/madoc-ts/src/frontend/site/hooks/use-route-context.ts +++ b/services/madoc-ts/src/frontend/site/hooks/use-route-context.ts @@ -9,17 +9,21 @@ export type RouteContext = { projectId?: string; taskId?: string; parentTaskId?: string; + topicType?: string; + topic?: string; }; export function useRouteContext(): RouteContext & T { const { context } = useSlots(); - const { canvasId, slug, manifestId, collectionId, parentTaskId, taskId } = useParams<{ + const { canvasId, slug, manifestId, collectionId, parentTaskId, taskId, topicType, topic } = useParams<{ collectionId?: string; manifestId?: string; canvasId?: string; slug?: string; taskId?: string; parentTaskId?: string; + topicType?: string; + topic?: string; }>(); return useMemo(() => { @@ -30,6 +34,8 @@ export function useRouteContext(): RouteC taskId: taskId, parentTaskId, canvasId: canvasId ? Number(canvasId) : context.canvas, + topicType: topicType ? topicType : context.topicType, + topic: topic ? topic : context.topic, }; - }, [context, canvasId, collectionId, manifestId, parentTaskId, slug, taskId]) as any; + }, [context, canvasId, collectionId, manifestId, parentTaskId, topicType, topic, slug, taskId]) as any; } diff --git a/services/madoc-ts/src/repository/page-blocks-repository.ts b/services/madoc-ts/src/repository/page-blocks-repository.ts index ce333b84d..2dfb5cce0 100644 --- a/services/madoc-ts/src/repository/page-blocks-repository.ts +++ b/services/madoc-ts/src/repository/page-blocks-repository.ts @@ -328,6 +328,16 @@ export class PageBlocksRepository extends BaseRepository { ss.filter_canvas_exact as slot__filter_canvas_exact, ss.filter_canvas_whitelist as slot__filter_canvas_whitelist, ss.filter_canvas_blacklist as slot__filter_canvas_blacklist, + ss.filter_topic_type_none as slot__filter_topic_type_none, + ss.filter_topic_type_all as slot__filter_topic_type_all, + ss.filter_topic_type_exact as slot__filter_topic_type_exact, + ss.filter_topic_type_whitelist as slot__filter_topic_type_whitelist, + ss.filter_topic_type_blacklist as slot__filter_topic_type_blacklist, + ss.filter_topic_none as slot__filter_topic_none, + ss.filter_topic_all as slot__filter_topic_all, + ss.filter_topic_exact as slot__filter_topic_exact, + ss.filter_topic_whitelist as slot__filter_topic_whitelist, + ss.filter_topic_blacklist as slot__filter_topic_blacklist, -- Block properties sb.id as block__id, diff --git a/services/madoc-ts/src/types/schemas/site-page.ts b/services/madoc-ts/src/types/schemas/site-page.ts index b8cffe747..4bc887547 100644 --- a/services/madoc-ts/src/types/schemas/site-page.ts +++ b/services/madoc-ts/src/types/schemas/site-page.ts @@ -5,6 +5,8 @@ export type EditorialContext = { collection?: number; manifest?: number; canvas?: number; + topic?: string; + topicType?: string; page?: number; slotIds?: string[]; }; @@ -14,6 +16,8 @@ export type ServerEditorialContext = { collection?: number; manifest?: number; canvas?: number; + topic?: string; + topicType?: string; page?: number; slotIds?: string[]; }; @@ -34,6 +38,8 @@ export type SiteSlot = { collection?: SlotFilterConfig; manifest?: SlotFilterConfig; canvas?: SlotFilterConfig; + topic?: SlotFilterConfig; + topicType?: SlotFilterConfig; }; pageId?: number; blocks: SiteBlock[]; @@ -61,12 +67,12 @@ export type CreateNormalPageRequest = { slug?: string; }; -export type SlotFilterConfig = { +export type SlotFilterConfig = { none?: boolean; all?: boolean; - exact?: number; - whitelist?: number[]; - blacklist?: number[]; + exact?: IDType; + whitelist?: IDType[]; + blacklist?: IDType[]; }; export type CreateSlotRequest = { @@ -79,6 +85,8 @@ export type CreateSlotRequest = { collection?: SlotFilterConfig; manifest?: SlotFilterConfig; canvas?: SlotFilterConfig; + topicType?: SlotFilterConfig; + topic?: SlotFilterConfig; }; pageId?: number; blocks?: SiteBlockRequest[]; @@ -149,6 +157,20 @@ export type SiteSlotRow = { filter_canvas_exact: number; filter_canvas_whitelist?: number[]; filter_canvas_blacklist?: number[]; + + // Filter topic types + filter_topic_type_none: boolean; + filter_topic_type_all: boolean; + filter_topic_type_exact: string; + filter_topic_type_whitelist?: string[]; + filter_topic_type_blacklist?: string[]; + + // Filter topics + filter_topic_none: boolean; + filter_topic_all: boolean; + filter_topic_exact: string; + filter_topic_whitelist?: string[]; + filter_topic_blacklist?: string[]; }; export type SiteBlockRow = { From f5cdf37edf055e1b21fe97f4ab2cb63465082f4e Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:36:51 +0000 Subject: [PATCH 008/191] IDA-893 Skeleton routes for topic types and topics --- .../shared/components/Breadcrumbs.tsx | 44 +++++++++++++- .../shared/components/SearchResults.tsx | 15 +++-- .../frontend/shared/hooks/use-topic-items.ts | 19 +++++++ .../madoc-ts/src/frontend/site/components.tsx | 6 ++ .../site/pages/loaders/topic-loader.tsx | 47 +++++++++++++++ .../pages/loaders/topic-type-list-loader.tsx | 35 ++++++++++++ .../site/pages/loaders/topic-type-loader.tsx | 43 ++++++++++++++ .../frontend/site/pages/view-topic-type.tsx | 34 +++++++++++ .../frontend/site/pages/view-topic-types.tsx | 43 ++++++++++++++ .../src/frontend/site/pages/view-topic.tsx | 51 +++++++++++++++++ .../madoc-ts/src/frontend/site/routes.tsx | 57 +++++++++++++++++++ 11 files changed, 386 insertions(+), 8 deletions(-) create mode 100644 services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts create mode 100644 services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx create mode 100644 services/madoc-ts/src/frontend/site/pages/loaders/topic-type-list-loader.tsx create mode 100644 services/madoc-ts/src/frontend/site/pages/loaders/topic-type-loader.tsx create mode 100644 services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx create mode 100644 services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx create mode 100644 services/madoc-ts/src/frontend/site/pages/view-topic.tsx diff --git a/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx b/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx index f767499e0..dac68dd3f 100644 --- a/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx +++ b/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx @@ -17,6 +17,8 @@ type BreadcrumbContextType = { canvas?: { name: InternationalString; id: number }; task?: { name: string; id: string }; subpage?: { name: InternationalString; path: string }; + topicType?: { name: InternationalString; id: string }; + topic?: { name: InternationalString; id: string }; }; const Helmet: any = _Helmet; @@ -120,6 +122,8 @@ export const BreadcrumbContext: React.FC = ({ canvas, task, subpage, + topicType, + topic, }) => { const parentCtx = useBreadcrumbs(); const ctx = useMemo(() => { @@ -142,8 +146,14 @@ export const BreadcrumbContext: React.FC = ({ if (subpage) { newCtx.subpage = subpage; } + if (topicType) { + newCtx.topicType = topicType; + } + if (topic) { + newCtx.topic = topic; + } return newCtx; - }, [parentCtx, manifest, project, collection, canvas, task, subpage]); + }, [parentCtx, project, collection, manifest, canvas, task, subpage, topicType, topic]); return ( @@ -156,9 +166,15 @@ export type BreadcrumbProps = { currentPage?: string | undefined; textColor?: string | undefined; textColorActive?: string | undefined; + topicRoot?: boolean; }; -export const DisplayBreadcrumbs: React.FC = ({ currentPage, textColor, textColorActive }) => { +export const DisplayBreadcrumbs: React.FC = ({ + currentPage, + topicRoot, + textColor, + textColorActive, +}) => { const site = useSite(); const breads = useBreadcrumbs(); const location = useLocation(); @@ -168,6 +184,28 @@ export const DisplayBreadcrumbs: React.FC = ({ currentPage, tex const stack = useMemo(() => { const flatList = []; + // Topics are only at the top. In theory, you could have collection/manifest/canvases under. + if (breads.topicType || topicRoot) { + flatList.push({ + label: { none: [t('breadcrumbs__Topics', { defaultValue: t('Topics') })] }, + url: `/topics`, + }); + + if (breads.topicType) { + flatList.push({ + label: breads.topicType.name, + url: `/topics/${breads.topicType.id}`, + }); + + if (breads.topic) { + flatList.push({ + label: breads.topic.name, + url: `/topics/${breads.topicType.id}/${breads.topic.id}`, + }); + } + } + } + // Projects can only be in one place. if (breads.project) { flatList.push({ @@ -291,6 +329,8 @@ export const DisplayBreadcrumbs: React.FC = ({ currentPage, tex breads.manifest, breads.project, breads.subpage, + breads.topic, + breads.topicType, currentPage, location.pathname, t, diff --git a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx index 80e9aacdc..12f087e55 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx @@ -70,13 +70,14 @@ function replaceBreaks(str: string) { return str.replace(/[\\n]+/, ''); } -const SearchItem: React.FC<{ result: SearchResult; size?: 'large' | 'small'; search?: string }> = ({ +const SearchItem: React.FC<{ result: SearchResult; size?: 'large' | 'small'; search?: string; admin?: boolean }> = ({ result, size, search, + admin, }) => { const things = ((result && result.contexts) || []).map(value => { - return parseUrn(value.id); + return parseUrn(typeof value === 'string' ? value : value.id); }); const routeContext = useRouteContext(); const projectId = routeContext.projectId; @@ -98,6 +99,7 @@ const SearchItem: React.FC<{ result: SearchResult; size?: 'large' | 'small'; sea canvasId, collectionId, query: { searchText }, + admin, })} style={{ textDecoration: 'none' }} > @@ -106,13 +108,13 @@ const SearchItem: React.FC<{ result: SearchResult; size?: 'large' | 'small'; sea {isManifest ? ( ) : ( - + )} @@ -139,11 +141,12 @@ export const SearchResults: React.FC<{ searchResults: Array; value?: string; isFetching?: boolean; -}> = ({ isFetching, searchResults = [], value }) => ( + admin?: boolean; +}> = ({ isFetching, searchResults = [], value, admin }) => ( {searchResults.map((result: SearchResult, index: number) => { return result ? ( - + ) : null; })} diff --git a/services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts b/services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts new file mode 100644 index 000000000..11e312a95 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts @@ -0,0 +1,19 @@ +import { usePaginatedQuery } from 'react-query'; +import { useTopic } from '../../site/pages/loaders/topic-loader'; +import { useApi } from './use-api'; +import { useLocationQuery } from './use-location-query'; + +export function useTopicItems() { + const { data: topic } = useTopic(); + const api = useApi(); + const query = useLocationQuery<{ fulltext?: string; facets?: string; page?: string }>(); + const page = query.page ? Number(query.page) : 1; + const resp = usePaginatedQuery( + ['topic-items', { id: topic?.id, page }], + async () => { + return api.getSearchQuery({ ...query, facets: [{ type: 'entity', subtype: topic?.id }] } as any, page); + }, + { enabled: !!topic } + ); + return [resp, { page, query }] as const; +} diff --git a/services/madoc-ts/src/frontend/site/components.tsx b/services/madoc-ts/src/frontend/site/components.tsx index 8d0ab674a..4bfce5e84 100644 --- a/services/madoc-ts/src/frontend/site/components.tsx +++ b/services/madoc-ts/src/frontend/site/components.tsx @@ -42,3 +42,9 @@ export { ViewProjectNotes } from './pages/view-project-notes'; export { RedirectPage } from './pages/redirect'; export { ReviewListingPage } from './pages/tasks/review-listing/review-listing-page'; export { SingleReview } from './pages/tasks/review-listing/single-review'; +export { TopicLoader } from './pages/loaders/topic-loader'; +export { TopicTypeListLoader } from './pages/loaders/topic-type-list-loader'; +export { TopicTypeLoader } from './pages/loaders/topic-type-loader'; +export { ViewTopic } from './pages/view-topic'; +export { ViewTopicType } from './pages/view-topic-type'; +export { ViewTopicTypes } from './pages/view-topic-types'; diff --git a/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx new file mode 100644 index 000000000..08a65dbaf --- /dev/null +++ b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx @@ -0,0 +1,47 @@ +import React, { useMemo } from 'react'; +import { Outlet } from 'react-router-dom'; +import { Topic } from '../../../../types/schemas/topics'; +import { UniversalComponent } from '../../../types'; +import { createUniversalComponent } from '../../../shared/utility/create-universal-component'; +import { useStaticData } from '../../../shared/hooks/use-data'; +import { BreadcrumbContext } from '../../../shared/components/Breadcrumbs'; + +export type TopicLoaderType = { + params: { topicType: string; topic: string }; + variables: { topicType: string; topic: string; page?: number }; + query: unknown; + data: Topic; +}; + +export function useTopic() { + return useStaticData( + TopicLoader, + {}, + { + cacheTime: 1000 * 60 * 60, + staleTime: 0, + } + ); +} + +export const TopicLoader: UniversalComponent = createUniversalComponent( + () => { + const { data } = useTopic(); + + const ctx = useMemo(() => (data ? { id: data.slug, name: data.label } : undefined), [data]); + + return ( + + + + ); + }, + { + getKey: params => { + return ['topic-type', { topic: params.topic, topicType: params.topicType }]; + }, + getData: async (key, vars, api) => { + return api.enrichment.getSiteTopic(vars.topic, vars.topicType); + }, + } +); diff --git a/services/madoc-ts/src/frontend/site/pages/loaders/topic-type-list-loader.tsx b/services/madoc-ts/src/frontend/site/pages/loaders/topic-type-list-loader.tsx new file mode 100644 index 000000000..4ac0a0d0b --- /dev/null +++ b/services/madoc-ts/src/frontend/site/pages/loaders/topic-type-list-loader.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Outlet } from 'react-router-dom'; +import { TopicTypeListResponse } from '../../../../types/schemas/topics'; +import { UniversalComponent } from '../../../types'; +import { createUniversalComponent } from '../../../shared/utility/create-universal-component'; +import { usePaginatedData } from '../../../shared/hooks/use-data'; + +export type TopicTypeListLoaderType = { + params: unknown; + variables: { page: number }; + query: { page?: string }; + data: TopicTypeListResponse; +}; + +export function usePaginatedTopicTypes() { + return usePaginatedData(TopicTypeListLoader); +} + +export const TopicTypeListLoader: UniversalComponent = createUniversalComponent< + TopicTypeListLoaderType +>( + () => { + usePaginatedTopicTypes(); + + return ; + }, + { + getKey: (params, query) => { + return ['site-topic-types-list', { page: Number(query.page) || 1 }]; + }, + getData: async (key, vars, api) => { + return api.enrichment.getSiteTopicTypes(vars.page); + }, + } +); diff --git a/services/madoc-ts/src/frontend/site/pages/loaders/topic-type-loader.tsx b/services/madoc-ts/src/frontend/site/pages/loaders/topic-type-loader.tsx new file mode 100644 index 000000000..885173ed3 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/pages/loaders/topic-type-loader.tsx @@ -0,0 +1,43 @@ +import React, { useMemo } from 'react'; +import { Outlet, useParams } from 'react-router-dom'; +import { TopicType } from '../../../../types/schemas/topics'; +import { UniversalComponent } from '../../../types'; +import { createUniversalComponent } from '../../../shared/utility/create-universal-component'; +import { usePaginatedData } from '../../../shared/hooks/use-data'; +import { BreadcrumbContext } from '../../../shared/components/Breadcrumbs'; + +export type TaskLoaderType = { + params: { topicType: string }; + variables: { topicType: string; page: number }; + query: { page?: string }; + data: TopicType; +}; + +export function useTopicType() { + const params = useParams<{ topicType?: string }>(); + return usePaginatedData(TopicTypeLoader, undefined, { enabled: params.topicType && params.topicType !== '_' }); +} + +export const TopicTypeLoader: UniversalComponent = createUniversalComponent( + () => { + const { data } = useTopicType(); + + const ctx = useMemo(() => (data ? { id: data.slug, name: data.label } : { id: '', name: { none: ['...'] } }), [ + data, + ]); + + return ( + + + + ); + }, + { + getKey: (params, query) => { + return ['site-topic-type', { topicType: params.topicType, page: Number(query.page) || 1 }]; + }, + getData: async (key, vars, api) => { + return api.enrichment.getSiteTopicType(vars.topicType, vars.page); + }, + } +); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx new file mode 100644 index 000000000..cc65dd3a9 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { Slot } from '../../shared/page-blocks/slot'; +import { Heading1 } from '../../shared/typography/Heading1'; +import { HrefLink } from '../../shared/utility/href-link'; +import { useRelativeLinks } from '../hooks/use-relative-links'; +import { useTopicType } from './loaders/topic-type-loader'; + +export function ViewTopicType() { + const createLink = useRelativeLinks(); + const { data } = useTopicType(); + + return ( + <> + + + + + {/* Custom slots.. */} + {data?.label || { none: ['...'] }} +
    + {data?.topics.map(topic => ( +
  • + + {topic.label} + +
  • + ))} +
+
{JSON.stringify(data, null, 2)}
+ + ); +} diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx new file mode 100644 index 000000000..fa452af07 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { Pagination } from '../../shared/components/Pagination'; +import { Slot } from '../../shared/page-blocks/slot'; +import { Heading1 } from '../../shared/typography/Heading1'; +import { HrefLink } from '../../shared/utility/href-link'; +import { useRelativeLinks } from '../hooks/use-relative-links'; +import { usePaginatedTopicTypes } from './loaders/topic-type-list-loader'; + +export function ViewTopicTypes() { + const createLink = useRelativeLinks(); + const { data } = usePaginatedTopicTypes(); + + return ( + <> + + + + + {/* Custom slots.. */} + Topic types +
    + {data?.topicTypes.map(topicType => ( +
  • + + {topicType.label} + +
  • + ))} +
+ + + +
{JSON.stringify(data, null, 2)}
+ + ); +} diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx new file mode 100644 index 000000000..91f7c2873 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { Pagination } from '../../shared/components/Pagination'; +import { SearchResults } from '../../shared/components/SearchResults'; +import { useTopicItems } from '../../shared/hooks/use-topic-items'; +import { Slot } from '../../shared/page-blocks/slot'; +import { Heading1, Subheading1 } from '../../shared/typography/Heading1'; +import { useTopic } from './loaders/topic-loader'; +import { useTopicType } from './loaders/topic-type-loader'; + +export function ViewTopic() { + const { data: topicType } = useTopicType(); + const { data } = useTopic(); + const [search, { query, page }] = useTopicItems(); + + return ( + <> + + + + + {/* Custom slots.. */} + {data?.label || { none: ['...'] }} + {topicType ? {topicType?.label || { none: ['...'] }} : null} +
{JSON.stringify(data, null, 2)}
+ +
+

Items in this topic

+ + + +
{JSON.stringify(data, null, 2)}
+
+ + ); +} diff --git a/services/madoc-ts/src/frontend/site/routes.tsx b/services/madoc-ts/src/frontend/site/routes.tsx index bc60f856f..f211011a6 100644 --- a/services/madoc-ts/src/frontend/site/routes.tsx +++ b/services/madoc-ts/src/frontend/site/routes.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { CreateRouteType } from '../types'; +import { TopicTypeLoader } from './pages/loaders/topic-type-loader'; type BaseRouteComponents = typeof import('./components'); // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -524,6 +525,62 @@ export function createRoutes(Components: RouteComponents): CreateRouteType { }, ], }, + // Topics and topic types. + { + path: '/topics', + element: , + children: [ + { + path: '/topics', + exact: true, + element: , + }, + ], + }, + { + path: '/topics/:topicType', + element: , + children: [ + { + path: '/topics/:topicType', + exact: true, + element: , + }, + { + path: '/topics/:topicType/:topic', + exact: true, + element: , + children: [ + { + path: '/topics/:topicType/:topic', + exact: true, + element: , + }, + { + path: '*', + element: , + children: [ + { + path: '*', + element: , + }, + ], + }, + ], + }, + { + path: '*', + element: , + children: [ + { + path: '*', + element: , + }, + ], + }, + ], + }, + { path: '/tasks', element: , From 9d0693e4b8fefecd3b6599e89553458c62dc44be Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:37:20 +0000 Subject: [PATCH 009/191] IDA-893 Topics and Topic types admin pages --- .../frontend/admin/molecules/AdminHeader.tsx | 8 +- .../frontend/admin/molecules/AdminSidebar.tsx | 23 +++++ .../frontend/admin/molecules/ManageTags.tsx | 71 ++++++++++++++++ .../pages/enrichment/authority/authority.tsx | 26 ++++++ .../enrichment/authority/entity-type-model.ts | 8 ++ .../entity-type/entity-type-model.ts | 8 ++ .../authority/entity-type/entity-type.tsx | 75 +++++++++++++++++ .../entity-type/list-entity-types.tsx | 41 +++++++++ .../authority/entity-type/new-entity-type.tsx | 40 +++++++++ .../authority/entity/entity-model.ts | 19 +++++ .../enrichment/authority/entity/entity.tsx | 24 ++++++ .../authority/entity/list-entities.tsx | 31 +++++++ .../authority/entity/new-entity.tsx | 39 +++++++++ .../pages/enrichment/authority/index.tsx | 70 ++++++++++++++++ .../authority/list-resource-tags.tsx | 26 ++++++ .../admin/pages/enrichment/import.tsx | 0 .../src/frontend/admin/pages/homepage.tsx | 11 +++ .../pages/topics/create-new-topic-type.tsx | 45 ++++++++++ .../admin/pages/topics/create-new-topic.tsx | 81 ++++++++++++++++++ .../admin/pages/topics/delete-topic-type.tsx | 37 +++++++++ .../admin/pages/topics/delete-topic.tsx | 39 +++++++++ .../src/frontend/admin/pages/topics/index.tsx | 83 +++++++++++++++++++ .../admin/pages/topics/list-topic-items.tsx | 33 ++++++++ .../admin/pages/topics/list-topic-types.tsx | 22 +++++ .../pages/topics/list-topics-in-type.tsx | 27 ++++++ .../admin/pages/topics/manage-topic-type.tsx | 65 +++++++++++++++ .../admin/pages/topics/manage-topic-types.tsx | 44 ++++++++++ .../admin/pages/topics/manage-topic.tsx | 68 +++++++++++++++ .../admin/pages/topics/topic-details.tsx | 12 +++ .../madoc-ts/src/frontend/admin/routes.tsx | 5 ++ .../AutocompleteField/AutocompleteField.tsx | 32 +++++-- .../helpers/capture-model-shorthand.ts | 7 +- .../frontend/shared/components/AdminMenu.tsx | 8 ++ .../site/hooks/use-current-admin-pages.ts | 16 +++- 34 files changed, 1134 insertions(+), 10 deletions(-) create mode 100644 services/madoc-ts/src/frontend/admin/molecules/ManageTags.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/authority.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type-model.ts create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/list-entities.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/index.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/list-resource-tags.tsx delete mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/import.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/delete-topic-type.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/delete-topic.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/index.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/list-topic-types.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/list-topics-in-type.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-types.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/manage-topic.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/topic-details.tsx diff --git a/services/madoc-ts/src/frontend/admin/molecules/AdminHeader.tsx b/services/madoc-ts/src/frontend/admin/molecules/AdminHeader.tsx index 6dd7cc433..0b67440a4 100644 --- a/services/madoc-ts/src/frontend/admin/molecules/AdminHeader.tsx +++ b/services/madoc-ts/src/frontend/admin/molecules/AdminHeader.tsx @@ -127,7 +127,13 @@ export const AdminHeader: React.FC<{ item ? ( 0)} + $active={ + item.active || + pathname === item.link || + pathname.replace(/ /g, '%20') === item.link || + pathname === item.link.replace(/ /g, '%20') || + (pathname.indexOf(item.link) !== -1 && n > 0) + } as={Link} to={item.link} > diff --git a/services/madoc-ts/src/frontend/admin/molecules/AdminSidebar.tsx b/services/madoc-ts/src/frontend/admin/molecules/AdminSidebar.tsx index 320c946c1..9dac441e4 100644 --- a/services/madoc-ts/src/frontend/admin/molecules/AdminSidebar.tsx +++ b/services/madoc-ts/src/frontend/admin/molecules/AdminSidebar.tsx @@ -21,6 +21,7 @@ import { SiteSwitcherBackButton, SiteSwitcherContainer, SiteSwitcherSiteName, + TopicsIcon, } from '../../shared/components/AdminMenu'; import { useSite, useUser } from '../../shared/hooks/use-site'; import { HrefLink } from '../../shared/utility/href-link'; @@ -42,6 +43,7 @@ export const AdminSidebar: React.FC = () => { isDashboard, isManageManifests, isLocalisation, + isTopics, isMedia, isSiteGlobal, } = useMemo(() => { @@ -61,6 +63,7 @@ export const AdminSidebar: React.FC = () => { isLocalisation: pathname.startsWith('/i18n'), isMedia: pathname.startsWith('/media'), isSiteGlobal: pathname.startsWith('/global'), + isTopics: pathname.startsWith('/topics'), }; }, [pathname]); @@ -146,6 +149,26 @@ export const AdminSidebar: React.FC = () => { + + + + + + {t('Topics')} + + + + {t('All topic types')} + + + {t('Create topic type')} + + + {t('Create topic')} + + + + diff --git a/services/madoc-ts/src/frontend/admin/molecules/ManageTags.tsx b/services/madoc-ts/src/frontend/admin/molecules/ManageTags.tsx new file mode 100644 index 000000000..a96cf9adb --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/molecules/ManageTags.tsx @@ -0,0 +1,71 @@ +import React, { FormEvent } from 'react'; +import { useMutation } from 'react-query'; +import { EnrichmentEntity } from '../../../extensions/enrichment/authority/types'; +import { SuccessMessage } from '../../shared/callouts/SuccessMessage'; +import { Input, InputContainer, InputLabel } from '../../shared/form/Input'; +import { useApi } from '../../shared/hooks/use-api'; +import { Button } from '../../shared/navigation/Button'; +import { HrefLink } from '../../shared/utility/href-link'; +import { useRelativeLinks } from '../../site/hooks/use-relative-links'; + +export function ManageTags({ + data: search, + id, + type, + refresh, +}: { + data: any; + type: string; + id: number; + refresh: () => void; +}) { + const api = useApi(); + const createLink = useRelativeLinks(); + const existingEntities = search.entity_tags as { entity: EnrichmentEntity }[]; + + // @todo no way to delete? + + const [createTag, createTagStatus] = useMutation(async (e: FormEvent) => { + e.preventDefault(); + const form = new FormData(e.target as any); + const data = Object.fromEntries((form as any).entries()); + if (data.tag_id) { + // @todo wrap this into normal api.. + await api.enrichment.tagMadocResource(data.tag_id, type, id); + await api.enrichment.triggerTask('index_madoc_resource', { id, type }, {}, false); + } + refresh(); + (e.target as any).reset(); + }); + + return ( +
+

Topics

+
    + {existingEntities.map((e, k) => ( +
  • + {/* @todo change entity.id to slug! */} + + {e.entity.label} + +
  • + ))} +
+ + {createTagStatus.isLoading ? ( + 'loading...' + ) : ( +
+ + {createTagStatus.isSuccess ? Tag added : null} + Tag ID + + + +
+ )} +
+ ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/authority.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/authority.tsx new file mode 100644 index 000000000..8c8991e04 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/authority.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Outlet } from 'react-router-dom'; +import { WidePage } from '../../../../shared/layout/WidePage'; +import { AdminHeader } from '../../../molecules/AdminHeader'; + +export function Authority() { + return ( + <> + + + + + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type-model.ts b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type-model.ts new file mode 100644 index 000000000..672fb4f08 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type-model.ts @@ -0,0 +1,8 @@ +export const entityTypeModel = { + __nested__: { + other_labels: { allowMultiple: true, label: 'Other label', pluralLabel: 'Other labels', labelledBy: 'value' }, + }, + label: { type: 'text-field', label: 'Entity Label' }, + 'other_labels.value': { type: 'text-field', label: 'Label' }, + 'other_labels.language': { type: 'text-field', label: 'Language' }, +}; diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts new file mode 100644 index 000000000..6a7ed1d93 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts @@ -0,0 +1,8 @@ +export const entityTypeModel = { + __nested__: { + other_labels: { allowMultiple: true, label: 'Other label', pluralLabel: 'Other labels', labelledBy: 'value' }, + }, + label: { type: 'text-field', label: 'Topic type Label' }, + 'other_labels.value': { type: 'text-field', label: 'Label' }, + 'other_labels.language': { type: 'text-field', label: 'Language' }, +}; diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx new file mode 100644 index 000000000..c2e1c9fa3 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; +import ReactTimeago from 'react-timeago'; +import { EnrichmentEntityType } from '../../../../../../extensions/enrichment/authority/types'; +import { EditShorthandCaptureModel } from '../../../../../shared/capture-models/EditorShorthandCaptureModel'; +import { useData } from '../../../../../shared/hooks/use-data'; +import { Button } from '../../../../../shared/navigation/Button'; +import { serverRendererFor } from '../../../../../shared/plugins/external/server-renderer-for'; +import { HrefLink } from '../../../../../shared/utility/href-link'; +import { entityTypeModel } from './entity-type-model'; + +export function EntityType() { + const { data } = useData<{ entity: EnrichmentEntityType; items: any }>(EntityType); + const [isEditing, setIsEditing] = useState(false); + const { entity, items } = data || {}; + const created = entity?.created ? new Date(entity?.created) : null; + + if (isEditing) { + return ( + { + console.log('new data->', newData); + }} + /> + ); + } + + return ( +
+

{entity?.label || '...'}

+ + {created ? ( + + Created + + ) : null} +
+

Other labels

+
    + {entity?.other_labels.map((label, i) => ( +
  • + {label.value} ({label.language}) +
  • + ))} +
+
+
+

Entities in this type

+
    + {items?.results.map((r: any) => { + return ( +
  • + {r.label} +
  • + ); + })} +
+
+
+ ); +} + +serverRendererFor(EntityType, { + getKey(params) { + return ['authority.entity_type.get', { id: params.id }]; + }, + async getData(key: string, vars, api) { + const entity = await api.authority.entity_type.get(vars.id); + return { + entity, + items: await api.enrichment.getTopicType(entity.label), + }; + }, +}); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx new file mode 100644 index 000000000..c638267b7 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { EnrichmentEntitySnippet } from '../../../../../../extensions/enrichment/authority/types'; +import { DjangoPagination } from '../../../../../../extensions/enrichment/types'; +import { usePaginatedData } from '../../../../../shared/hooks/use-data'; +import { Button } from '../../../../../shared/navigation/Button'; +import { serverRendererFor } from '../../../../../shared/plugins/external/server-renderer-for'; +import { Heading2 } from '../../../../../shared/typography/Heading2'; +import { HrefLink } from '../../../../../shared/utility/href-link'; + +export function ListEntityTypes() { + const { data } = usePaginatedData>(ListEntityTypes); + + // @todo pagination. + + console.log(data); + + return ( +
+ List entity type s + +
    + {data?.results.map(result => ( +
  • + {result.label} +
  • + ))} +
+
+ ); +} + +serverRendererFor(ListEntityTypes, { + getKey(params, query) { + return ['authority.entity_type.list', { page: query.page || 1 }]; + }, + getData(key: string, vars, api) { + return api.authority.entity_type.list(vars.page); + }, +}); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx new file mode 100644 index 000000000..8c67d1871 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { useMutation } from 'react-query'; +import { EnrichmentEntityType } from '../../../../../../extensions/enrichment/authority/types'; +import { EditShorthandCaptureModel } from '../../../../../shared/capture-models/EditorShorthandCaptureModel'; +import { useApi } from '../../../../../shared/hooks/use-api'; +import { entityTypeModel } from './entity-type-model'; + +export function NewEntityType() { + const api = useApi(); + const [createNewEntityType, status] = useMutation(async (data: Partial) => { + data.other_labels = (data.other_labels || []).filter(e => e.value !== ''); + return api.authority.entity_type.create(data); + }); + + if (status.isError) { + return
Error...
; + } + + if (status.isSuccess) { + return ( +
+ Added! +
{JSON.stringify(status.data)}
+
+ ); + } + + return ( +
+ { + await createNewEntityType(data); + }} + keepExtraFields + /> +
+ ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts new file mode 100644 index 000000000..0a576fae1 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts @@ -0,0 +1,19 @@ +export const entityModel = { + __nested__: { + other_labels: { allowMultiple: true, label: 'Other label', pluralLabel: 'Other labels', labelledBy: 'value' }, + }, + label: { type: 'text-field', label: 'Topic label' }, + 'other_labels.value': { type: 'text-field', label: 'Label' }, + 'other_labels.language': { type: 'text-field', label: 'Language' }, + type: { + type: 'autocomplete-field', + label: 'Topic type', + dataSource: 'madoc-api://topic-types/autocomplete?q=%', + requestInitial: true, + outputIdAsString: true, + }, + // type: { + // type: 'text-field', + // label: 'Entity type (uuid)', + // }, +}; diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx new file mode 100644 index 000000000..5a6871958 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { EnrichmentEntitySnippet } from '../../../../../../extensions/enrichment/authority/types'; +import { useData } from '../../../../../shared/hooks/use-data'; +import { serverRendererFor } from '../../../../../shared/plugins/external/server-renderer-for'; + +export function Entity() { + const { data } = useData(Entity); + + return ( +
+

{data?.label || '...'}

+
{JSON.stringify(data, null, 2)}
+
+ ); +} + +serverRendererFor(Entity, { + getKey(params) { + return ['authority.entity.get', { id: params.id }]; + }, + getData(key: string, vars, api) { + return api.authority.entity.get(vars.id); + }, +}); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/list-entities.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/list-entities.tsx new file mode 100644 index 000000000..1e8a4cb78 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/list-entities.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { EnrichmentEntitySnippet } from '../../../../../../extensions/enrichment/authority/types'; +import { DjangoPagination } from '../../../../../../extensions/enrichment/types'; +import { usePaginatedData } from '../../../../../shared/hooks/use-data'; +import { Button } from '../../../../../shared/navigation/Button'; +import { serverRendererFor } from '../../../../../shared/plugins/external/server-renderer-for'; +import { Heading2 } from '../../../../../shared/typography/Heading2'; +import { HrefLink } from '../../../../../shared/utility/href-link'; + +export function ListEntities() { + const { data } = usePaginatedData>(ListEntities); + + return ( +
+ List entities + + Create new + +
{JSON.stringify(data, null, 2)}
+
+ ); +} + +serverRendererFor(ListEntities, { + getKey(params, query) { + return ['authority.entity.list', { page: query.page || 1 }]; + }, + getData(key: string, vars, api) { + return api.authority.entity.list(vars.page); + }, +}); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx new file mode 100644 index 000000000..6927c56c9 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { useMutation } from 'react-query'; +import { EditShorthandCaptureModel } from '../../../../../shared/capture-models/EditorShorthandCaptureModel'; +import { useApi } from '../../../../../shared/hooks/use-api'; +import { entityModel } from './entity-model'; + +export function NewEntity() { + const api = useApi(); + const [createNewEntityType, status] = useMutation(async (data: any) => { + data.other_labels = (data.other_labels || []).filter((e: any) => e.value !== ''); + return api.authority.entity.create(data); + }); + + if (status.isError) { + return
Error...
; + } + + if (status.isSuccess) { + return ( +
+ Added! +
{JSON.stringify(status.data)}
+
+ ); + } + + return ( +
+ { + await createNewEntityType(data); + }} + keepExtraFields + /> +
+ ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/index.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/index.tsx new file mode 100644 index 000000000..34759d5ef --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/index.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Authority } from './authority'; +import { EntityType } from './entity-type/entity-type'; +import { ListEntityTypes } from './entity-type/list-entity-types'; +import { NewEntityType } from './entity-type/new-entity-type'; +import { Entity } from './entity/entity'; +import { ListEntities } from './entity/list-entities'; +import { NewEntity } from './entity/new-entity'; +import { ListResourceTags } from './list-resource-tags'; + +export const authorityRoutes = [ + { + path: '/enrichment/authority', + element: , + children: [ + { + path: '/enrichment/authority', + index: true, + element: , + }, + { + path: '/enrichment/authority/entities', + element: , + }, + { + path: '/enrichment/authority/entities/new', + element: , + }, + { + path: '/enrichment/authority/entities/:id', + element: , + }, + { + path: '/enrichment/authority/entity-types', + element: , + }, + { + path: '/enrichment/authority/entity-types/new', + element: , + }, + { + path: '/enrichment/authority/entity-types/:id', + element: , + }, + { + path: '/enrichment/authority/resource-tags', + element: , + }, + ], + }, +]; + +export function AuthorityIndex() { + return ( +
+
    +
  • + Entities +
  • +
  • + Entity types +
  • +
  • + Resource tags +
  • +
+
+ ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/list-resource-tags.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/list-resource-tags.tsx new file mode 100644 index 000000000..6f523f0db --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/list-resource-tags.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { EnrichmentEntitySnippet } from '../../../../../extensions/enrichment/authority/types'; +import { DjangoPagination } from '../../../../../extensions/enrichment/types'; +import { usePaginatedData } from '../../../../shared/hooks/use-data'; +import { serverRendererFor } from '../../../../shared/plugins/external/server-renderer-for'; +import { Heading2 } from '../../../../shared/typography/Heading2'; + +export function ListResourceTags() { + const { data } = usePaginatedData>(ListResourceTags); + + return ( +
+ List resource tags +
{JSON.stringify(data, null, 2)}
+
+ ); +} + +serverRendererFor(ListResourceTags, { + getKey(params, query) { + return ['authority.resource_tag.list', { page: query.page || 1 }]; + }, + getData(key: string, vars, api) { + return api.authority.resource_tag.list(vars.page); + }, +}); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/import.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/import.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/services/madoc-ts/src/frontend/admin/pages/homepage.tsx b/services/madoc-ts/src/frontend/admin/pages/homepage.tsx index 413bee83e..45f407b3c 100644 --- a/services/madoc-ts/src/frontend/admin/pages/homepage.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/homepage.tsx @@ -127,6 +127,17 @@ export const Homepage: UniversalComponent = createUniversalCompone + + {t('Enrichment')} + +
  • + {t('Manage Topics')} +
  • +
  • + {t('Authority')} +
  • +
    +
    {t('Configuration')} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx new file mode 100644 index 000000000..366edb3d7 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useMutation } from 'react-query'; +import { EnrichmentEntityType } from '../../../../extensions/enrichment/authority/types'; +import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; +import { useApi } from '../../../shared/hooks/use-api'; +import { Button } from '../../../shared/navigation/Button'; +import { HrefLink } from '../../../shared/utility/href-link'; +import { entityTypeModel } from '../enrichment/authority/entity-type/entity-type-model'; + +export function CreateNewTopicType() { + const api = useApi(); + const [createNewEntityType, status] = useMutation(async (data: Partial) => { + data.other_labels = (data.other_labels || []).filter(e => e.value !== ''); + return api.authority.entity_type.create(data); + }); + + if (status.isError) { + return
    Error...
    ; + } + + if (status.isSuccess) { + return ( +
    + Added! +
    {JSON.stringify(status.data, null, 2)}
    + +
    + ); + } + + return ( +
    + { + await createNewEntityType(data); + }} + keepExtraFields + /> +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx new file mode 100644 index 000000000..c6e8e308c --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx @@ -0,0 +1,81 @@ +import { getValue } from '@iiif/vault-helpers'; +import React, { useMemo } from 'react'; +import { useMutation } from 'react-query'; +import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; +import { useApi } from '../../../shared/hooks/use-api'; +import { Button } from '../../../shared/navigation/Button'; +import { HrefLink } from '../../../shared/utility/href-link'; +import { useRouteContext } from '../../../site/hooks/use-route-context'; +import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; +import { entityModel } from '../enrichment/authority/entity/entity-model'; + +export function CreateNewTopic() { + const api = useApi(); + const { topicType } = useRouteContext(); + const { data, isLoading } = useTopicType(); + const hasTopic = data || isLoading; + + const [createNewEntityType, status] = useMutation(async (input: any) => { + input.other_labels = (input.other_labels || []).filter((e: any) => e.value !== ''); + + if (hasTopic) { + if (!data) { + return; + } + + // @todo this will hopefully change. + input.type = getValue(data.label); + } + + return { + response: await api.authority.entity.create(input), + topicType: input.type, + }; + }); + const model = useMemo(() => { + const copy: any = { + ...entityModel, + }; + + if (topicType && topicType !== '_') { + delete copy.type; + } + return copy; + }, [topicType]); + + if (status.isError) { + return
    Error...
    ; + } + + if (status.isSuccess) { + return ( +
    + Added! +
    {JSON.stringify(status.data)}
    + {/* @todo hopefully this will change to slug field. */} + {status.data ? ( + + ) : null} +
    + ); + } + + if (isLoading) { + return
    loading...
    ; + } + + return ( +
    + { + await createNewEntityType(input); + }} + keepExtraFields + /> +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/delete-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/delete-topic-type.tsx new file mode 100644 index 000000000..3d8627d16 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/delete-topic-type.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { useMutation } from 'react-query'; +import { useNavigate } from 'react-router-dom'; +import { WarningMessage } from '../../../shared/callouts/WarningMessage'; +import { Button, ButtonRow } from '../../../shared/navigation/Button'; +import { useApi } from '../../../shared/plugins/public-api'; +import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; + +export function DeleteTopicType() { + const api = useApi(); + const { data, isLoading } = useTopicType(); + const navigate = useNavigate(); + + const [deleteTopicType] = useMutation(async () => { + if (data) { + await api.authority.entity_type.delete(data?.id); + navigate(`/topics`); + } + }); + + return ( +
    +
    + {data?.id === 'null' ? ( + + ID field is null. Deletion will not work + + ) : null} +
    + + + +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/delete-topic.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/delete-topic.tsx new file mode 100644 index 000000000..1b146c67b --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/delete-topic.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { useMutation } from 'react-query'; +import { useNavigate } from 'react-router-dom'; +import { WarningMessage } from '../../../shared/callouts/WarningMessage'; +import { useApi } from '../../../shared/hooks/use-api'; +import { Button, ButtonRow } from '../../../shared/navigation/Button'; +import { useRouteContext } from '../../../shared/plugins/public-api'; +import { useTopic } from '../../../site/pages/loaders/topic-loader'; + +export function DeleteTopic() { + const api = useApi(); + const { topicType } = useRouteContext(); + const { data, isLoading } = useTopic(); + const navigate = useNavigate(); + + const [deleteTopic] = useMutation(async () => { + if (data) { + await api.authority.entity.delete(data?.id); + navigate(`/topics/${topicType}?deleted=true`); + } + }); + + return ( +
    +
    + {data?.id === 'null' ? ( + + ID field is null. Deletion will not work + + ) : null} +
    + + + +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/index.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/index.tsx new file mode 100644 index 000000000..422ea4877 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/index.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Outlet } from 'react-router-dom'; +import { CreateNewTopic } from './create-new-topic'; +import { CreateNewTopicType } from './create-new-topic-type'; +import { DeleteTopic } from './delete-topic'; +import { DeleteTopicType } from './delete-topic-type'; +import { ListTopicItems } from './list-topic-items'; +import { ListTopicTypes } from './list-topic-types'; +import { ManageTopicType } from './manage-topic-type'; +import { ManageTopicTypes } from './manage-topic-types'; +import { ManageTopic } from './manage-topic'; +import { ListTopicsInType } from './list-topics-in-type'; +import { TopicDetails } from './topic-details'; + +export const topicRoutes = [ + { + path: '/topics', + element: , + children: [ + { + path: '/topics', + index: true, + element: , + }, + { + path: '/topics/_/create-type', + exact: true, + element: , + }, + { + path: '/topics/_/create-topic', + exact: true, + element: , + }, + ], + }, + { + path: '/topics/:topicType', + element: , + children: [ + { + path: '/topics/:topicType', + index: true, + element: , + }, + { + path: '/topics/:topicType/_/create-topic', + exact: true, + element: , + }, + { + path: '/topics/:topicType/_/delete', + exact: true, + element: , + }, + ], + }, + { + path: '/topics/:topicType/:topic', + element: , + children: [ + { + path: '/topics/:topicType/:topic', + index: true, + element: , + }, + { + path: '/topics/:topicType/:topic/items', + index: true, + element: , + }, + { + path: '/topics/:topicType/:topic/delete', + exact: true, + element: , + }, + ], + }, +]; + +export function TopicsWrapper() { + return ; +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx new file mode 100644 index 000000000..eac2dc563 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import { Pagination } from '../../../shared/components/Pagination'; +import { SearchResults } from '../../../shared/components/SearchResults'; +import { useTopicItems } from '../../../shared/hooks/use-topic-items'; +import { EmptyState } from '../../../shared/layout/EmptyState'; + +export function ListTopicItems() { + const [{ data, isLoading, latestData }, { query, page }] = useTopicItems(); + + if (data?.pagination.totalResults === 0) { + return Nothing tagged yet; + } + + return ( +
    + + + +
    {JSON.stringify(data, null, 2)}
    +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-types.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-types.tsx new file mode 100644 index 000000000..07d09f25b --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-types.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { LocaleString } from '../../../shared/components/LocaleString'; +import { HrefLink } from '../../../shared/utility/href-link'; +import { useRelativeLinks } from '../../../site/hooks/use-relative-links'; +import { usePaginatedTopicTypes } from '../../../site/pages/loaders/topic-type-list-loader'; + +export function ListTopicTypes() { + const createLink = useRelativeLinks(true); + const { data } = usePaginatedTopicTypes(); + + return ( +
      + {data?.topicTypes.map(topicType => ( +
    • + + {topicType.label} + +
    • + ))} +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/list-topics-in-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/list-topics-in-type.tsx new file mode 100644 index 000000000..522024d34 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/list-topics-in-type.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { LocaleString } from '../../../shared/components/LocaleString'; +import { Heading1 } from '../../../shared/typography/Heading1'; +import { HrefLink } from '../../../shared/utility/href-link'; +import { useRelativeLinks } from '../../../site/hooks/use-relative-links'; +import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; + +export function ListTopicsInType() { + const createLink = useRelativeLinks(true); + const { data } = useTopicType(); + + return ( + <> + {data?.label || { none: ['...'] }} +
      + {data?.topics.map(topic => ( +
    • + + {topic.label} + +
    • + ))} +
    +
    {JSON.stringify(data, null, 2)}
    + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx new file mode 100644 index 000000000..d08af5bb1 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Outlet } from 'react-router-dom'; +import { SuccessMessage } from '../../../shared/callouts/SuccessMessage'; +import { LocaleString } from '../../../shared/components/LocaleString'; +import { useLocationQuery } from '../../../shared/hooks/use-location-query'; +import { useSite } from '../../../shared/hooks/use-site'; +import { WidePage } from '../../../shared/layout/WidePage'; +import { useRouteContext } from '../../../site/hooks/use-route-context'; +import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; +import { AdminHeader } from '../../molecules/AdminHeader'; + +export function ManageTopicType() { + const { topicType } = useRouteContext(); + const { t } = useTranslation(); + const { data } = useTopicType(); + const site = useSite(); + const { deleted } = useLocationQuery(); + + const label = {data?.label || { none: ['...'] }}; + + return ( + <> + {deleted ? Topic was deleted : null} + + {t('Manage topic type')} + {' | '} + {t('View on site')} + + } + menu={[ + { + label: t('Topics'), + link: `/topics/${data?.slug || topicType}`, + }, + { + label: 'Create topic', + link: `/topics/${data?.slug || topicType}/_/create-topic`, + }, + { + label: 'Delete', + link: `/topics/${data?.slug || topicType}/_/delete`, + }, + ]} + /> + + + + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-types.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-types.tsx new file mode 100644 index 000000000..fc16bb504 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-types.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Outlet } from 'react-router-dom'; +import { WidePage } from '../../../shared/layout/WidePage'; +import { usePaginatedTopicTypes } from '../../../site/pages/loaders/topic-type-list-loader'; +import { AdminHeader } from '../../molecules/AdminHeader'; + +export function ManageTopicTypes() { + const { t } = useTranslation(); + usePaginatedTopicTypes(); + + return ( + <> + + + + + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic.tsx new file mode 100644 index 000000000..144b35901 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Outlet } from 'react-router-dom'; +import { LocaleString } from '../../../shared/components/LocaleString'; +import { useSite } from '../../../shared/hooks/use-site'; +import { WidePage } from '../../../shared/layout/WidePage'; +import { useRouteContext } from '../../../site/hooks/use-route-context'; +import { useTopic } from '../../../site/pages/loaders/topic-loader'; +import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; +import { AdminHeader } from '../../molecules/AdminHeader'; + +export function ManageTopic() { + const { topic, topicType } = useRouteContext(); + const { t } = useTranslation(); + const { data } = useTopic(); + const { data: topicTypeData } = useTopicType(); + const site = useSite(); + + const label = {data?.label || { none: ['...'] }}; + const topicTypeLabel = {topicTypeData?.label || { none: ['...'] }}; + + return ( + <> + + {t('Manage topic')} + {' | '} + {t('View on site')} + + } + menu={[ + { + label: t('Topic'), + link: `/topics/${topicType}/${data?.slug || topic}`, + }, + { + label: t('Items tagged'), + link: `/topics/${topicType}/${data?.slug || topic}/items`, + }, + { + label: t('Delete'), + link: `/topics/${topicType}/${data?.slug || topic}/delete`, + }, + ]} + /> + + + + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/topic-details.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/topic-details.tsx new file mode 100644 index 000000000..8b9f70b17 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/topic-details.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { useTopic } from '../../../site/pages/loaders/topic-loader'; + +export function TopicDetails() { + const { data } = useTopic(); + + return ( +
    +
    {JSON.stringify(data, null, 2)}
    +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/routes.tsx b/services/madoc-ts/src/frontend/admin/routes.tsx index 35dbd9a6e..6e792a65c 100644 --- a/services/madoc-ts/src/frontend/admin/routes.tsx +++ b/services/madoc-ts/src/frontend/admin/routes.tsx @@ -99,6 +99,9 @@ import { ProjectExportTab } from './pages/crowdsourcing/projects/project-export' import { GenerateApiKey } from './pages/system/generate-api-key'; import { CreateBot } from './pages/global/create-bot'; +import { authorityRoutes } from './pages/enrichment/authority/index'; +import { topicRoutes } from "./pages/topics/index"; + export const routes: RouteObject[] = [ { path: '/', @@ -106,6 +109,8 @@ export const routes: RouteObject[] = [ }, // Aggregations. ...annotationStylesRoutes, + ...authorityRoutes, + ...topicRoutes, // Manual routes. { diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.tsx index 3a7919db7..f333e68b8 100644 --- a/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState, useCallback } from 'react'; import { Select } from 'react-functional-select'; +import { useApi, useOptionalApi } from '../../../../hooks/use-api'; import { BaseField, FieldComponent } from '../../../types/field-types'; import { ErrorMessage } from '../../atoms/Message'; import { Tag } from '../../atoms/Tag'; @@ -13,6 +14,7 @@ export interface AutocompleteFieldProps extends BaseField { requestInitial?: boolean; dataSource: string; disabled?: boolean; + outputIdAsString?: boolean; } export type CompletionItem = { @@ -37,6 +39,7 @@ export const AutocompleteField: FieldComponent = props = const [isLoading, setIsLoading] = useState(false); const [hasFetched, setHasFetched] = useState(false); const [error, setError] = useState(''); + const api = useOptionalApi(); const onOptionChange = (option: CompletionItem | undefined) => { if (!option) { @@ -45,9 +48,13 @@ export const AutocompleteField: FieldComponent = props = } if (!props.value || option.uri !== props.value.uri) { - props.updateValue( - option ? { label: option.label, resource_class: option.resource_class, uri: option.uri } : undefined - ); + if (props.outputIdAsString) { + props.updateValue(option?.uri as any); + } else { + props.updateValue( + option ? { label: option.label, resource_class: option.resource_class, uri: option.uri } : undefined + ); + } } }; @@ -62,16 +69,29 @@ export const AutocompleteField: FieldComponent = props = setIsLoading(false); return; } + const fetcher = (): Promise<{ completions: CompletionItem[] }> => { + if (props.dataSource.startsWith('madoc-api://')) { + const source = props.dataSource.slice('madoc-api://'.length); + if (!api) { + throw new Error('Invalid URL'); + } + return api.request(`/api/madoc/${source.replace(/%/, value || '')}`); + } + return fetch(`${props.dataSource}`.replace(/%/, value || '')).then( + r => r.json() as Promise<{ completions: CompletionItem[] }> + ); + }; + // Make API Request. - fetch(`${props.dataSource}`.replace(/%/, value || '')) - .then(r => r.json() as Promise<{ completions: CompletionItem[] }>) + fetcher() .then(items => { setOptions(items.completions); setIsLoading(false); setHasFetched(true); setError(''); }) - .catch(() => { + .catch(e => { + console.error(e); setError(t('There was a problem fetching results')); }); } diff --git a/services/madoc-ts/src/frontend/shared/capture-models/helpers/capture-model-shorthand.ts b/services/madoc-ts/src/frontend/shared/capture-models/helpers/capture-model-shorthand.ts index 6337f9b5d..da17033eb 100644 --- a/services/madoc-ts/src/frontend/shared/capture-models/helpers/capture-model-shorthand.ts +++ b/services/madoc-ts/src/frontend/shared/capture-models/helpers/capture-model-shorthand.ts @@ -2,15 +2,17 @@ import { CaptureModel } from '../types/capture-model'; import { generateId } from './generate-id'; export function captureModelShorthand(shorthand: { [key: string]: string | any }): CaptureModel['document'] { + const entity = shorthand.__entity__ || {}; + const nested = shorthand.__nested__ || {}; const model: CaptureModel['document'] = { id: generateId(), type: 'entity', label: 'Root', properties: {}, + ...entity, }; - const metadata = shorthand; - const originalKeys = Object.keys(shorthand); + const originalKeys = Object.keys(shorthand).filter(r => r !== '__entity__' && r !== '__nested__'); const processLevel = (doc: CaptureModel['document'], key: string[], originalKey: string) => { if (key.length === 0) { @@ -52,6 +54,7 @@ export function captureModelShorthand(shorthand: { [key: string]: string | any } type: 'entity', label: key[0], // @todo config for mapping fields to labels properties: {}, + ...(nested[key[0]] || {}), } as CaptureModel['document']); // Recursion. diff --git a/services/madoc-ts/src/frontend/shared/components/AdminMenu.tsx b/services/madoc-ts/src/frontend/shared/components/AdminMenu.tsx index c84c4dfdc..48166a9f2 100644 --- a/services/madoc-ts/src/frontend/shared/components/AdminMenu.tsx +++ b/services/madoc-ts/src/frontend/shared/components/AdminMenu.tsx @@ -135,6 +135,14 @@ export function InternationalisationIcon(props: React.SVGProps) { ); } +export function TopicsIcon(props: React.SVGProps) { + return ( + + + + ); +} + // Sidebar export const AdminSidebarContainer = styled.div` background: #4b67e1; diff --git a/services/madoc-ts/src/frontend/site/hooks/use-current-admin-pages.ts b/services/madoc-ts/src/frontend/site/hooks/use-current-admin-pages.ts index 7d72be4d7..2e79ea7dc 100644 --- a/services/madoc-ts/src/frontend/site/hooks/use-current-admin-pages.ts +++ b/services/madoc-ts/src/frontend/site/hooks/use-current-admin-pages.ts @@ -5,7 +5,7 @@ import { useRouteContext } from './use-route-context'; export const useCurrentAdminPages = () => { const { slug } = useSite(); - const { manifestId, collectionId, canvasId } = useRouteContext(); + const { manifestId, collectionId, canvasId, topicType, topic } = useRouteContext(); const user = useUser(); const { t } = useTranslation(); const project = useProject(); @@ -44,5 +44,19 @@ export const useCurrentAdminPages = () => { }); } + if (topicType) { + availablePages.push({ + label: t('Topic type'), + link: `/s/${slug}/admin/topics/${topicType}`, + }); + + if (topic) { + availablePages.push({ + label: t('Topic'), + link: `/s/${slug}/admin/topics/${topicType}/${topic}`, + }); + } + } + return availablePages; }; From 7413d456f37d7a933c3113e9f39165e4a97c9a86 Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Sun, 27 Nov 2022 23:37:39 +0000 Subject: [PATCH 010/191] IDA-893 Added enrichment to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6ed0f9411..6e5d8f5e1 100755 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ infrastructure/ansible/vars/default.yml var_backup e2e/cypress/fixtures/madoc-test-fixtures services/search +services/enrichment services/madoc-remix services/madoc-ts/service-jwts/madoc-remix.json e2e/test-fixtures/postgres/default/default.sql From 88c443b64216475e2fb87de59f8de756d89f3452 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 28 Nov 2022 11:00:30 +0000 Subject: [PATCH 011/191] start resultsblock --- .../src/frontend/shared/atoms/CheckboxBtn.tsx | 17 ++++--- .../shared/components/SearchFilters.tsx | 4 ++ .../site/features/SearchPageFilters.tsx | 44 ++++++++---------- .../site/features/SearchPageResults.tsx | 46 +++++++++++++++++++ .../frontend/site/hooks/use-search-facets.ts | 1 - .../src/frontend/site/pages/search.tsx | 36 +++++---------- services/madoc-ts/translations/en/madoc.json | 2 + 7 files changed, 92 insertions(+), 58 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx diff --git a/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx b/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx index 16adb33de..520fb82e3 100644 --- a/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx +++ b/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx @@ -21,8 +21,9 @@ const CheckText = styled.div` font-size: 14px; font-weight: normal; text-transform: capitalize; + :hover { - color: pink; + cursor: pointer; } `; @@ -40,7 +41,7 @@ const CheckWrapper = styled.div` position: relative; `; -const FakeCheck = styled.div` +const FakeCheck = styled.div<{ $color?: string }>` position: relative; margin: 10px; width: 12px; @@ -48,13 +49,12 @@ const FakeCheck = styled.div` border: none; border-radius: 1px; background-color: transparent; - outline: 2px solid pink; + outline: ${props => (props.$color ? `2px solid ${props.$color}` : '2px solid #4265e9')}; outline-offset: 2px; transition: 0.1s; &[data-is-checked='true'] { - background-color: pink; - outline: 2px solid pink; + background-color: ${props => (props.$color ? props.$color : '#4265e9')}; } `; @@ -78,11 +78,14 @@ export const CheckboxBtn: React.FC<{ subLabel?: string | InternationalString; countText?: string | number; id?: any; -}> = ({ disabled, checked, onChange, inLineLabel, subLabel, countText, id }) => { + color?: string; +}> = ({ disabled, checked, onChange, inLineLabel, subLabel, countText, id, color }) => { + console.log(color); + return ( - + diff --git a/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx b/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx index 6aadc37e6..0d1becdf7 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx @@ -27,6 +27,10 @@ export const SearchFilterLabel = styled.label` display: block; font-size: 0.8em; padding: 0.5em; + + :hover { + cursor: pointer; + } `; export const SearchFilterItem = styled.div<{ $selected?: boolean }>` diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx index c0a19c628..b3619a40c 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx @@ -2,29 +2,20 @@ import React from 'react'; import styled from 'styled-components'; import { useTranslation } from 'react-i18next'; import { - SearchFilterCheckbox, SearchFilterContainer, SearchFilterItem, - SearchFilterItemCount, SearchFilterItemList, SearchFilterLabel, SearchFilterSection, SearchFilterSectionTitle, SearchFilterTitle, - SearchFilterToggle, } from '../../shared/components/SearchFilters'; -import { SearchBox } from '../../shared/atoms/SearchBox'; import { ButtonRow, TinyButton } from '../../shared/navigation/Button'; import { LocaleString } from '../../shared/components/LocaleString'; -import { CloseIcon } from '../../shared/icons/CloseIcon'; -import { AddIcon } from '../../shared/icons/AddIcon'; import { useSearchQuery } from '../hooks/use-search-query'; -import { useSiteConfiguration } from './SiteConfigurationContext'; import { useSearchFacets } from '../hooks/use-search-facets'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; import { InternationalString } from '@iiif/presentation-3'; -import { FacetConfigValue } from '../../shared/components/MetadataFacetEditor'; -import { CheckboxField } from '../../shared/capture-models/editor/input-types/CheckboxField/CheckboxField'; import { CheckboxBtn } from '../../shared/atoms/CheckboxBtn'; export const Pill = styled.div` @@ -37,38 +28,32 @@ export const Pill = styled.div` padding: 5px; `; -interface SearchPageFilters { - background?: string; - textColor?: string; +interface SearchPageFiltersProps { + checkBoxColor?: string; + textTest?: string; displayFacets?: { id: string; label: InternationalString; - items: FacetConfigValue[]; + items: { key: string; label: InternationalString; values: string[]; count: number }[]; }[]; } -export function SearchPageFilters(props: SearchPageFilters) { +export const SearchPageFilters: React.FC = ({ checkBoxColor, displayFacets, textTest }) => { const { t } = useTranslation(); - const displayFacets = props.displayFacets; const { fulltext, appliedFacets } = useSearchQuery(); - const { - project: { showSearchFacetCount }, - } = useSiteConfiguration(); const { inQueue, - applyFacet, - clearSingleFacet, queueSingleFacet, dequeueSingleFacet, isFacetSelected, applyAllFacets, clearAllFacets, - setFullTextQuery, } = useSearchFacets(); - if (!props.displayFacets) { + if (!displayFacets) { return null; } + console.log(textTest); return ( {t('Refine search')} @@ -98,6 +83,7 @@ export function SearchPageFilters(props: SearchPageFilters) { return ( @@ -118,11 +104,19 @@ export function SearchPageFilters(props: SearchPageFilters) { })} ); -} +}; blockEditorFor(SearchPageFilters, { label: 'Search Page Filters', type: 'search-page-filters', - defaultProps: {}, - editor: {}, + anyContext: [], + requiredContext: [], + defaultProps: { + checkBoxColor: '', + textTest: '', + }, + editor: { + checkBoxColor: { label: 'Check box color', type: 'color-field' }, + textTest: { label: 'Test for props', type: 'text-field' }, + }, }); diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx new file mode 100644 index 000000000..a64c0646a --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx @@ -0,0 +1,46 @@ +import {SearchResults, TotalResults} from "../../shared/components/SearchResults"; +import {Pagination} from "../../shared/components/Pagination"; +import React from "react"; +import {useTranslation} from "react-i18next"; +import {InternationalString} from "@iiif/presentation-3"; + +interface SearchPageResultsProps { +results?: {}, +page?: number, +isLoading: boolean, + latestData: {}, + rawQuery: {}, + ft: {} +} + +export const SearchPageResults: React.FC = ({ results, page, isLoading, latestData, rawQuery,ft }) => { + + const { t } = useTranslation(); + + return ( + <> + + {t('Found {{count}} results', { + count: results && results.pagination ? results.pagination.totalResults : 0, + })} + +)} + + + + +)} \ No newline at end of file diff --git a/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts b/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts index 2e9810643..3c1b512c9 100644 --- a/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts +++ b/services/madoc-ts/src/frontend/site/hooks/use-search-facets.ts @@ -37,7 +37,6 @@ export function useSearchFacets() { }; const clearSingleFacet = (key: string, values: string[]) => { - console.log(appliedFacets); setQuery( fulltext, appliedFacets.filter(facet => !(facet.k === key && values.indexOf(facet.v) !== -1)), diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index fa6145493..e2650c1db 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -3,12 +3,12 @@ import { Slot } from '../../shared/page-blocks/slot'; import { useTranslation } from 'react-i18next'; import { LoadingBlock } from '../../shared/callouts/LoadingBlock'; import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; -import { Pagination } from '../../shared/components/Pagination'; -import { SearchResults, TotalResults } from '../../shared/components/SearchResults'; + import { useSearch } from '../hooks/use-search'; import { useSearchQuery } from '../hooks/use-search-query'; import { StaticPage } from '../features/StaticPage'; import { SearchPageFilters } from '../features/SearchPageFilters'; +import { SearchPageResults } from '../features/SearchPageResults'; export const Search: React.FC = () => { const { t } = useTranslation(); @@ -23,7 +23,7 @@ export const Search: React.FC = () => {
    - +
    @@ -33,29 +33,15 @@ export const Search: React.FC = () => { {isLoading && !searchResponse ? ( ) : ( - - {t('Found {{count}} results', { - count: searchResponse && searchResponse.pagination ? searchResponse.pagination.totalResults : 0, - })} - + )} - - -
    diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index 0ff733873..bc83b0437 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -124,6 +124,7 @@ "Change the background of the deep zoom viewer": "Change the background of the deep zoom viewer", "Changes requested on your submission": "Changes requested on your submission", "Changes saved": "Changes saved", + "Check box color": "Check box color", "Choice": "Choice", "Choose": "Choose", "Choose a root for the form": "Choose a root for the form", @@ -637,6 +638,7 @@ "Tag": "Tag", "Task is complete!": "Task is complete!", "Tasks": "Tasks", + "Test for props": "Test for props", "Text align": "Text align", "Text color": "Text color", "Text color active": "Text color active", From 72231ab6fd04a6058615b7defc3fbb02b674b649 Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Mon, 28 Nov 2022 15:11:16 +0000 Subject: [PATCH 012/191] IDA-893 bug fixes --- .../content/manifests/manifest-search-index.tsx | 13 +++++++++++-- .../admin/pages/topics/create-new-topic.tsx | 2 +- .../madoc-ts/src/frontend/shared/hooks/use-data.ts | 2 +- .../madoc-ts/src/frontend/site/pages/view-topic.tsx | 2 +- .../madoc-ts/src/gateway/tasks/search-index-task.ts | 2 ++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx b/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx index a28bbf40c..9862f3875 100644 --- a/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-search-index.tsx @@ -18,7 +18,11 @@ type ManifestSearchIndexType = { export const ManifestSearchIndex = createUniversalComponent( () => { - const { data, isError, refetch } = useData(ManifestSearchIndex, {}, { retry: 0 }); + const { data, isError, refetch } = useData( + ManifestSearchIndex, + {}, + { retry: 0, useErrorBoundary: false, suspense: false } + ); const { data: structure } = useData(EditManifestStructure); const { id } = useParams<{ id: string }>(); const totalCanvases = structure?.items.length || 0; @@ -59,7 +63,12 @@ export const ManifestSearchIndex = createUniversalComponent { - return api.searchGetIIIF(`urn:madoc:manifest:${id}`); + try { + return api.searchGetIIIF(`urn:madoc:manifest:${id}`); + } catch (e) { + console.log('err', e); + return null; + } }, } ); diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx index c6e8e308c..cba93f337 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx @@ -54,7 +54,7 @@ export function CreateNewTopic() {
    {JSON.stringify(status.data)}
    {/* @todo hopefully this will change to slug field. */} {status.data ? ( - ) : null} diff --git a/services/madoc-ts/src/frontend/shared/hooks/use-data.ts b/services/madoc-ts/src/frontend/shared/hooks/use-data.ts index 6fde3a4ab..3b75464f0 100644 --- a/services/madoc-ts/src/frontend/shared/hooks/use-data.ts +++ b/services/madoc-ts/src/frontend/shared/hooks/use-data.ts @@ -119,7 +119,7 @@ export function useData( } return undefined as any; }, - { refetchOnMount: false, cacheTime: 1000 * 60 * 60, ...(config || {}), useErrorBoundary: true } + { refetchOnMount: false, cacheTime: 1000 * 60 * 60, useErrorBoundary: true, ...(config || {}) } ); } diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 91f7c2873..7fc1ed328 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -44,7 +44,7 @@ export function ViewTopic() { stale={search.isLoading} extraQuery={query} /> -
    {JSON.stringify(data, null, 2)}
    +
    {JSON.stringify(search.data, null, 2)}
    ); diff --git a/services/madoc-ts/src/gateway/tasks/search-index-task.ts b/services/madoc-ts/src/gateway/tasks/search-index-task.ts index 991038462..e83d599cf 100644 --- a/services/madoc-ts/src/gateway/tasks/search-index-task.ts +++ b/services/madoc-ts/src/gateway/tasks/search-index-task.ts @@ -110,6 +110,8 @@ export const jobHandler = async (name: string, taskId: string, api: ApiClient) = await api.updateTask(taskId, { status: 3 }); } catch (e) { // ignore error. + console.log(e); + await api.updateTask(taskId, { status: -1 }); } break; From 88a5a9f65922a49730e99fe2f1189830bdd8aaa4 Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Mon, 28 Nov 2022 15:11:43 +0000 Subject: [PATCH 013/191] IDA-893 - Updated schemas --- .../madoc-ts/schemas/CreateSlotRequest.json | 52 ++++ .../madoc-ts/schemas/EditorialContext.json | 6 + .../schemas/ServerEditorialContext.json | 6 + services/madoc-ts/schemas/SiteSlot.json | 52 ++++ services/madoc-ts/schemas/SiteSlotRow.json | 48 +++ .../madoc-ts/schemas/SlotFilterConfig.json | 6 +- .../madoc-ts/schemas/SlotMappingRequest.json | 208 +++++++++++++ services/madoc-ts/schemas/SlotResponse.json | 52 ++++ services/madoc-ts/schemas/Topic.json | 294 ++++++++++++++++++ services/madoc-ts/schemas/TopicSnippet.json | 4 + services/madoc-ts/schemas/TopicType.json | 113 +++++++ .../schemas/TopicTypeListResponse.json | 35 +++ .../madoc-ts/schemas/TopicTypeSnippet.json | 43 +++ 13 files changed, 916 insertions(+), 3 deletions(-) create mode 100644 services/madoc-ts/schemas/Topic.json create mode 100644 services/madoc-ts/schemas/TopicSnippet.json create mode 100644 services/madoc-ts/schemas/TopicType.json create mode 100644 services/madoc-ts/schemas/TopicTypeListResponse.json create mode 100644 services/madoc-ts/schemas/TopicTypeSnippet.json diff --git a/services/madoc-ts/schemas/CreateSlotRequest.json b/services/madoc-ts/schemas/CreateSlotRequest.json index fd59c4102..ad70b465c 100644 --- a/services/madoc-ts/schemas/CreateSlotRequest.json +++ b/services/madoc-ts/schemas/CreateSlotRequest.json @@ -123,6 +123,58 @@ } } } + }, + "topicType": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "topic": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }, diff --git a/services/madoc-ts/schemas/EditorialContext.json b/services/madoc-ts/schemas/EditorialContext.json index d06e96b8f..48fac7d76 100644 --- a/services/madoc-ts/schemas/EditorialContext.json +++ b/services/madoc-ts/schemas/EditorialContext.json @@ -13,6 +13,12 @@ "canvas": { "type": "number" }, + "topic": { + "type": "string" + }, + "topicType": { + "type": "string" + }, "page": { "type": "number" }, diff --git a/services/madoc-ts/schemas/ServerEditorialContext.json b/services/madoc-ts/schemas/ServerEditorialContext.json index 8cfd5e331..04a602ccd 100644 --- a/services/madoc-ts/schemas/ServerEditorialContext.json +++ b/services/madoc-ts/schemas/ServerEditorialContext.json @@ -13,6 +13,12 @@ "canvas": { "type": "number" }, + "topic": { + "type": "string" + }, + "topicType": { + "type": "string" + }, "page": { "type": "number" }, diff --git a/services/madoc-ts/schemas/SiteSlot.json b/services/madoc-ts/schemas/SiteSlot.json index 3990b9409..754349b5b 100644 --- a/services/madoc-ts/schemas/SiteSlot.json +++ b/services/madoc-ts/schemas/SiteSlot.json @@ -129,6 +129,58 @@ } } } + }, + "topic": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "topicType": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }, diff --git a/services/madoc-ts/schemas/SiteSlotRow.json b/services/madoc-ts/schemas/SiteSlotRow.json index b1e5368a2..7d964495d 100644 --- a/services/madoc-ts/schemas/SiteSlotRow.json +++ b/services/madoc-ts/schemas/SiteSlotRow.json @@ -106,6 +106,48 @@ "items": { "type": "number" } + }, + "filter_topic_type_none": { + "type": "boolean" + }, + "filter_topic_type_all": { + "type": "boolean" + }, + "filter_topic_type_exact": { + "type": "string" + }, + "filter_topic_type_whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_topic_type_blacklist": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_topic_none": { + "type": "boolean" + }, + "filter_topic_all": { + "type": "boolean" + }, + "filter_topic_exact": { + "type": "string" + }, + "filter_topic_whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_topic_blacklist": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -121,6 +163,12 @@ "filter_project_all", "filter_project_exact", "filter_project_none", + "filter_topic_all", + "filter_topic_exact", + "filter_topic_none", + "filter_topic_type_all", + "filter_topic_type_exact", + "filter_topic_type_none", "id", "site_id", "slot_layout", diff --git a/services/madoc-ts/schemas/SlotFilterConfig.json b/services/madoc-ts/schemas/SlotFilterConfig.json index c95b48a4b..fc890b89b 100644 --- a/services/madoc-ts/schemas/SlotFilterConfig.json +++ b/services/madoc-ts/schemas/SlotFilterConfig.json @@ -8,18 +8,18 @@ "type": "boolean" }, "exact": { - "type": "number" + "$ref": "#/definitions/IDType" }, "whitelist": { "type": "array", "items": { - "type": "number" + "$ref": "#/definitions/IDType" } }, "blacklist": { "type": "array", "items": { - "type": "number" + "$ref": "#/definitions/IDType" } } }, diff --git a/services/madoc-ts/schemas/SlotMappingRequest.json b/services/madoc-ts/schemas/SlotMappingRequest.json index 0a73e5792..843ff7eb4 100644 --- a/services/madoc-ts/schemas/SlotMappingRequest.json +++ b/services/madoc-ts/schemas/SlotMappingRequest.json @@ -128,6 +128,58 @@ } } } + }, + "topicType": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "topic": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }, @@ -315,6 +367,58 @@ } } } + }, + "topicType": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "topic": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }, @@ -502,6 +606,58 @@ } } } + }, + "topicType": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "topic": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }, @@ -689,6 +845,58 @@ } } } + }, + "topicType": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "topic": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }, diff --git a/services/madoc-ts/schemas/SlotResponse.json b/services/madoc-ts/schemas/SlotResponse.json index addd69fa7..d47dc3d14 100644 --- a/services/madoc-ts/schemas/SlotResponse.json +++ b/services/madoc-ts/schemas/SlotResponse.json @@ -134,6 +134,58 @@ } } } + }, + "topic": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "topicType": { + "type": "object", + "properties": { + "none": { + "type": "boolean" + }, + "all": { + "type": "boolean" + }, + "exact": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "blacklist": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }, diff --git a/services/madoc-ts/schemas/Topic.json b/services/madoc-ts/schemas/Topic.json new file mode 100644 index 000000000..40d273901 --- /dev/null +++ b/services/madoc-ts/schemas/Topic.json @@ -0,0 +1,294 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "topicType": { + "$ref": "#/definitions/TopicTypeSnippet" + }, + "otherLabels": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "authorities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "required": [ + "id", + "label" + ] + } + }, + "modified": { + "type": "string" + }, + "created": { + "type": "string" + }, + "editorial": { + "type": "object", + "properties": { + "contributors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "id", + "label" + ] + } + }, + "summary": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "heroImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "alt": { + "type": "string" + }, + "overlayColor": { + "type": "string" + }, + "transparent": { + "type": "boolean" + } + }, + "required": [ + "url" + ] + }, + "description": { + "type": "object", + "properties": { + "label": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "value": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "required": [ + "label", + "value" + ] + }, + "featured": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "description": "Internal url for Search API - shouldn't be shown to the user, but can be used for unqiueness", + "type": "string" + }, + "resource_id": { + "description": "Madoc identifier, usually in the form `urn:madoc:TYPE:ID`", + "type": "string" + }, + "resource_type": { + "description": "Type of resource (Collection, Manifest or Canvas etc.)", + "type": "string" + }, + "madoc_thumbnail": { + "description": "Optional thumbnail of resource", + "type": "string" + }, + "thumbnail": { + "type": "string" + }, + "label": { + "description": "Label for the resource from the search result", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "contexts": { + "description": "List of contexts for the resource", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ] + } + } + ] + }, + "hits": { + "description": "List of hits.", + "type": "array", + "items": { + "description": "Represents a single search hit, there may be multiple of these per resource.", + "type": "object", + "properties": { + "type": { + "description": "Type of metadata returned", + "type": "string" + }, + "subtype": { + "description": "Subtype of metadata returned", + "type": "string" + }, + "snippet": { + "description": "Preview of search result with highlighted search as HTML", + "type": "string" + }, + "language": { + "description": "ISO language string", + "type": "string" + }, + "rank": { + "type": "number" + }, + "bounding_boxes": { + "description": "Bounding boxes", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + }, + { + "type": "number" + } + ], + "minItems": 4, + "maxItems": 4 + } + } + }, + "required": [ + "language", + "rank", + "snippet", + "subtype", + "type" + ] + } + } + }, + "required": [ + "contexts", + "hits", + "label", + "resource_id", + "resource_type", + "url" + ] + } + }, + "related": { + "type": "array", + "items": { + "$ref": "#/definitions/TopicSnippet" + } + } + } + } + }, + "required": [ + "authorities", + "created", + "editorial", + "id", + "label", + "modified", + "otherLabels", + "slug" + ], + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/services/madoc-ts/schemas/TopicSnippet.json b/services/madoc-ts/schemas/TopicSnippet.json new file mode 100644 index 000000000..521f0b5e6 --- /dev/null +++ b/services/madoc-ts/schemas/TopicSnippet.json @@ -0,0 +1,4 @@ +{ + "$ref": "#/definitions/TopicSnippet", + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/services/madoc-ts/schemas/TopicType.json b/services/madoc-ts/schemas/TopicType.json new file mode 100644 index 000000000..0fdeb0996 --- /dev/null +++ b/services/madoc-ts/schemas/TopicType.json @@ -0,0 +1,113 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "otherLabels": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "pagination": { + "type": "object", + "properties": { + "page": { + "type": "number" + }, + "totalResults": { + "type": "number" + }, + "totalPages": { + "type": "number" + } + }, + "required": [ + "page", + "totalPages", + "totalResults" + ] + }, + "topics": { + "type": "array", + "items": { + "$ref": "#/definitions/TopicSnippet" + } + }, + "editorial": { + "type": "object", + "properties": { + "summary": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "heroImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "alt": { + "type": "string" + }, + "overlayColor": { + "type": "string" + }, + "transparent": { + "type": "boolean" + } + }, + "required": [ + "url" + ] + }, + "featured": { + "type": "array", + "items": { + "$ref": "#/definitions/TopicSnippet" + } + }, + "related": { + "type": "array", + "items": { + "$ref": "#/definitions/TopicTypeSnippet" + } + } + } + } + }, + "required": [ + "editorial", + "id", + "label", + "otherLabels", + "pagination", + "slug", + "topics" + ], + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/services/madoc-ts/schemas/TopicTypeListResponse.json b/services/madoc-ts/schemas/TopicTypeListResponse.json new file mode 100644 index 000000000..27195a895 --- /dev/null +++ b/services/madoc-ts/schemas/TopicTypeListResponse.json @@ -0,0 +1,35 @@ +{ + "type": "object", + "properties": { + "topicTypes": { + "type": "array", + "items": { + "$ref": "#/definitions/TopicTypeSnippet" + } + }, + "pagination": { + "type": "object", + "properties": { + "page": { + "type": "number" + }, + "totalResults": { + "type": "number" + }, + "totalPages": { + "type": "number" + } + }, + "required": [ + "page", + "totalPages", + "totalResults" + ] + } + }, + "required": [ + "pagination", + "topicTypes" + ], + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/services/madoc-ts/schemas/TopicTypeSnippet.json b/services/madoc-ts/schemas/TopicTypeSnippet.json new file mode 100644 index 000000000..bd56bf350 --- /dev/null +++ b/services/madoc-ts/schemas/TopicTypeSnippet.json @@ -0,0 +1,43 @@ +{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "thumbnail": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "alt": { + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "totalObjects": { + "type": "number" + } + }, + "required": [ + "id", + "label", + "slug" + ], + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file From 727f7ce370540a2b35970b62ec14f9c8fbecd6db Mon Sep 17 00:00:00 2001 From: Heather Date: Wed, 30 Nov 2022 13:01:16 +0000 Subject: [PATCH 014/191] appliedFacets block --- .../src/frontend/shared/atoms/CheckboxBtn.tsx | 1 - .../frontend/site/features/AppliedFacets.tsx | 97 +++++++++++++++++++ .../site/features/SearchPageFilters.tsx | 13 +-- .../site/features/SearchPageResults.tsx | 3 +- .../src/frontend/site/pages/search.tsx | 48 ++++++--- 5 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx diff --git a/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx b/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx index 520fb82e3..c88e3a4eb 100644 --- a/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx +++ b/services/madoc-ts/src/frontend/shared/atoms/CheckboxBtn.tsx @@ -80,7 +80,6 @@ export const CheckboxBtn: React.FC<{ id?: any; color?: string; }> = ({ disabled, checked, onChange, inLineLabel, subLabel, countText, id, color }) => { - console.log(color); return ( diff --git a/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx new file mode 100644 index 000000000..2eb5f088f --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import { + SearchFilterContainer, + SearchFilterItem, + SearchFilterItemList, + SearchFilterLabel, + SearchFilterSection, + SearchFilterSectionTitle, + SearchFilterTitle, +} from '../../shared/components/SearchFilters'; +import { ButtonRow, TinyButton } from '../../shared/navigation/Button'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { useSearchQuery } from '../hooks/use-search-query'; +import { useSearchFacets } from '../hooks/use-search-facets'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { InternationalString } from '@iiif/presentation-3'; +import { CheckboxBtn } from '../../shared/atoms/CheckboxBtn'; +import { useSearch } from '../hooks/use-search'; +import { CloseIcon } from "../../shared/icons/CloseIcon"; + +export const AppliedFacetContainer = styled.div` + display: flex; + flex-wrap: wrap; + padding: 1em 0; +`; + +export const Pill = styled.div` + border-radius: 3px; + width: auto; + background-color: #ecf0ff; + color: #437bdd; + margin-right: 1em; + font-size: 12px; + padding: 5px; +`; + +export const RemoveFacet = styled.button` + background-color: transparent; + border: none; + color: #0a2450; + cursor: pointer; + border-radius: 50px; + margin: 2px; + + svg { + height: 16px; + width: 16px; + vertical-align: middle; + } + + :hover { + background-color: rgba(0, 0, 0, 0.1); + transition: background-color ease-in-out 0.3s; + } +`; +interface SearchPageFiltersProps { + facetColor?: string; +} + +export const AppliedFacets: React.FC = ({ facetColor }) => { + const { t } = useTranslation(); + const { fulltext, appliedFacets } = useSearchQuery(); + const { clearSingleFacet } = useSearchFacets(); + + return ( + + {appliedFacets.length > 0 && + appliedFacets.map((facet, i) => ( + + {facet.v} + { + clearSingleFacet(facet.k, [facet.v]); + }} + > + + + + ))} + + ); +}; + +blockEditorFor(AppliedFacets, { + label: 'Applied Facets', + type: 'default.appliedF-facets', + anyContext: [], + requiredContext: [], + defaultProps: { + pillColor: '', + }, + editor: { + pillColor: { label: 'facet color', type: 'color-field' }, + }, +}); diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx index b3619a40c..0bbae9079 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx @@ -17,6 +17,7 @@ import { useSearchFacets } from '../hooks/use-search-facets'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; import { InternationalString } from '@iiif/presentation-3'; import { CheckboxBtn } from '../../shared/atoms/CheckboxBtn'; +import { useSearch } from "../hooks/use-search"; export const Pill = styled.div` border-radius: 3px; @@ -31,14 +32,11 @@ export const Pill = styled.div` interface SearchPageFiltersProps { checkBoxColor?: string; textTest?: string; - displayFacets?: { - id: string; - label: InternationalString; - items: { key: string; label: InternationalString; values: string[]; count: number }[]; - }[]; } -export const SearchPageFilters: React.FC = ({ checkBoxColor, displayFacets, textTest }) => { +export const SearchPageFilters: React.FC = ({ checkBoxColor, textTest }) => { + + const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); const { t } = useTranslation(); const { fulltext, appliedFacets } = useSearchQuery(); const { @@ -53,7 +51,6 @@ export const SearchPageFilters: React.FC = ({ checkBoxCo if (!displayFacets) { return null; } - console.log(textTest); return ( {t('Refine search')} @@ -108,7 +105,7 @@ export const SearchPageFilters: React.FC = ({ checkBoxCo blockEditorFor(SearchPageFilters, { label: 'Search Page Filters', - type: 'search-page-filters', + type: 'default.search-page-filters', anyContext: [], requiredContext: [], defaultProps: { diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx index a64c0646a..200985197 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx @@ -3,9 +3,10 @@ import {Pagination} from "../../shared/components/Pagination"; import React from "react"; import {useTranslation} from "react-i18next"; import {InternationalString} from "@iiif/presentation-3"; +import {SearchResult} from "../../../types/search"; interface SearchPageResultsProps { -results?: {}, +results?: Array, page?: number, isLoading: boolean, latestData: {}, diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index e2650c1db..678a0d558 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -8,7 +8,10 @@ import { useSearch } from '../hooks/use-search'; import { useSearchQuery } from '../hooks/use-search-query'; import { StaticPage } from '../features/StaticPage'; import { SearchPageFilters } from '../features/SearchPageFilters'; -import { SearchPageResults } from '../features/SearchPageResults'; +import { SearchResults, TotalResults } from '../../shared/components/SearchResults'; +import { Pagination } from '../../shared/components/Pagination'; +import { AppliedFacets } from '../features/AppliedFacets'; +import { Heading1 } from '../../shared/typography/Heading1'; export const Search: React.FC = () => { const { t } = useTranslation(); @@ -21,27 +24,48 @@ export const Search: React.FC = () => {
    + + “{fulltext}” search + + + + +
    - - + +
    - + {isLoading && !searchResponse ? ( ) : ( - + + {t('Found {{count}} results', { + count: searchResponse && searchResponse.pagination ? searchResponse.pagination.totalResults : 0, + })} + )} + + +
    From 6233d2188a16cfd57a215be2c1f5feb1c84a5771 Mon Sep 17 00:00:00 2001 From: Heather Date: Wed, 30 Nov 2022 16:57:00 +0000 Subject: [PATCH 015/191] result style --- .../src/frontend/shared/atoms/ImageStrip.tsx | 3 +- .../shared/components/SearchResults.tsx | 77 +++++++----- .../src/frontend/shared/layout/Grid.tsx | 1 + .../frontend/site/features/AppliedFacets.tsx | 50 +++----- .../site/features/SearchPageFilters.tsx | 3 +- .../site/features/SearchPageResults.tsx | 118 ++++++++++++------ .../site/features/SearchPagination.tsx | 57 +++++++++ .../src/frontend/site/pages/search.tsx | 48 +++---- 8 files changed, 223 insertions(+), 134 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/SearchPagination.tsx diff --git a/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx b/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx index 4f2743b7d..b756df29b 100644 --- a/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx +++ b/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx @@ -10,7 +10,8 @@ export const ImageStripBox = styled.div<{ position: relative; flex-shrink: 0; border-radius: 3px; - max-width: ${props => (props.$size === 'small' ? '200px' : '')}; + max-width: ${props => (props.$size === 'small' ? '200px' : '100%')}; + max-height: 400px; border: 1px solid transparent; border-color: ${props => (props.$border ? props.$border : 'transparent')}; background-color: ${props => (props.$bgColor ? props.$bgColor : 'inherit')}; diff --git a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx index 80e9aacdc..815f4a689 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx @@ -11,7 +11,7 @@ import { createLink } from '../utility/create-link'; import { HrefLink } from '../utility/href-link'; import { LocaleString } from './LocaleString'; -const ResultsContainer = styled.div<{ $isFetching?: boolean }>` +export const ResultsContainer = styled.div<{ $isFetching?: boolean }>` flex: 1 1 0px; transition: opacity 0.2s; @@ -50,6 +50,12 @@ const ResultText = styled.span` line-height: 1.3em; `; +const TextContainer = styled.div` + text-overflow: ellipsis; + overflow: hidden; + max-height: 100px; +`; + export const ResultTitle = styled.div` text-decoration: none; color: #2962ff; @@ -70,11 +76,16 @@ function replaceBreaks(str: string) { return str.replace(/[\\n]+/, ''); } -const SearchItem: React.FC<{ result: SearchResult; size?: 'large' | 'small'; search?: string }> = ({ - result, - size, - search, -}) => { +export const SearchItem: React.FC<{ + result: SearchResult; + size?: 'large' | 'small'; + search?: string; + list?: boolean; + border?: string; + textColor?: string; + background?: string; + imageStyle?: string; +}> = ({ result, size, search, list, border, textColor, background, imageStyle }) => { const things = ((result && result.contexts) || []).map(value => { return parseUrn(value.id); }); @@ -90,7 +101,7 @@ const SearchItem: React.FC<{ result: SearchResult; size?: 'large' | 'small'; sea const isManifest = result.resource_type === 'Manifest'; return ( - +
    - + {isManifest ? ( ) : ( - + )} + + {result.label} + {snippet ? ( +
    + +
    + ) : null} +
    -
    - {result.label} - {snippet ? ( -
    - -
    - ) : null} -
    - +
    ); }; @@ -139,12 +150,14 @@ export const SearchResults: React.FC<{ searchResults: Array; value?: string; isFetching?: boolean; -}> = ({ isFetching, searchResults = [], value }) => ( - - {searchResults.map((result: SearchResult, index: number) => { - return result ? ( - - ) : null; - })} - -); +}> = ({ isFetching, searchResults = [], value }) => { + return ( + + {searchResults.map((result: SearchResult, index: number) => { + return result ? ( + + ) : null; + })} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/layout/Grid.tsx b/services/madoc-ts/src/frontend/shared/layout/Grid.tsx index ca6ebcf5a..611bba190 100644 --- a/services/madoc-ts/src/frontend/shared/layout/Grid.tsx +++ b/services/madoc-ts/src/frontend/shared/layout/Grid.tsx @@ -4,6 +4,7 @@ export const GridContainer = styled.div<{ $justify?: string }>` display: flex; justify-content: ${props => props.$justify}; align-items: flex-start; + width: 100%; `; export const CSSThirdGrid = styled.div<{ $justify?: string }>` diff --git a/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx index 2eb5f088f..93a5f539d 100644 --- a/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx +++ b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx @@ -1,24 +1,10 @@ import React from 'react'; import styled from 'styled-components'; import { useTranslation } from 'react-i18next'; -import { - SearchFilterContainer, - SearchFilterItem, - SearchFilterItemList, - SearchFilterLabel, - SearchFilterSection, - SearchFilterSectionTitle, - SearchFilterTitle, -} from '../../shared/components/SearchFilters'; -import { ButtonRow, TinyButton } from '../../shared/navigation/Button'; -import { LocaleString } from '../../shared/components/LocaleString'; import { useSearchQuery } from '../hooks/use-search-query'; import { useSearchFacets } from '../hooks/use-search-facets'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; -import { InternationalString } from '@iiif/presentation-3'; -import { CheckboxBtn } from '../../shared/atoms/CheckboxBtn'; -import { useSearch } from '../hooks/use-search'; -import { CloseIcon } from "../../shared/icons/CloseIcon"; +import { CloseIcon } from '../../shared/icons/CloseIcon'; export const AppliedFacetContainer = styled.div` display: flex; @@ -43,7 +29,7 @@ export const RemoveFacet = styled.button` cursor: pointer; border-radius: 50px; margin: 2px; - + svg { height: 16px; width: 16px; @@ -55,30 +41,32 @@ export const RemoveFacet = styled.button` transition: background-color ease-in-out 0.3s; } `; -interface SearchPageFiltersProps { +interface AppliedFacetsProps { facetColor?: string; } -export const AppliedFacets: React.FC = ({ facetColor }) => { +export const AppliedFacets: React.FC = ({ facetColor }) => { const { t } = useTranslation(); const { fulltext, appliedFacets } = useSearchQuery(); const { clearSingleFacet } = useSearchFacets(); + if (!appliedFacets || appliedFacets.length < 1) { + return null; + } return ( - {appliedFacets.length > 0 && - appliedFacets.map((facet, i) => ( - - {facet.v} - { - clearSingleFacet(facet.k, [facet.v]); - }} - > - - - - ))} + {appliedFacets.map((facet, i) => ( + + {facet.v} + { + clearSingleFacet(facet.k, [facet.v]); + }} + > + + + + ))} ); }; diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx index 0bbae9079..472414259 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx @@ -38,7 +38,8 @@ export const SearchPageFilters: React.FC = ({ checkBoxCo const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); const { t } = useTranslation(); - const { fulltext, appliedFacets } = useSearchQuery(); + const { appliedFacets } = useSearchQuery(); + console.log(displayFacets, latestData) const { inQueue, queueSingleFacet, diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx index 200985197..c14ced489 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx @@ -1,47 +1,83 @@ -import {SearchResults, TotalResults} from "../../shared/components/SearchResults"; -import {Pagination} from "../../shared/components/Pagination"; -import React from "react"; -import {useTranslation} from "react-i18next"; -import {InternationalString} from "@iiif/presentation-3"; -import {SearchResult} from "../../../types/search"; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSearchQuery } from '../hooks/use-search-query'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { SearchResult } from '../../../types/search'; +import { useSearch } from '../hooks/use-search'; +import { ResultsContainer, SearchItem } from '../../shared/components/SearchResults'; +import { ImageGrid } from '../../shared/atoms/ImageGrid'; +import { ImageStripBox } from '../../shared/atoms/ImageStrip'; interface SearchPageResultsProps { -results?: Array, -page?: number, -isLoading: boolean, - latestData: {}, - rawQuery: {}, - ft: {} + background?: string; + list?: boolean; + textColor?: string; + cardBorder?: string; + imageStyle?: string; } -export const SearchPageResults: React.FC = ({ results, page, isLoading, latestData, rawQuery,ft }) => { +export const SearchPageResults: React.FC = ({ + list, + cardBorder, + textColor, + background, + imageStyle +}) => { - const { t } = useTranslation(); + const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); + const { rawQuery, page, fulltext } = useSearchQuery(); - return ( - <> - - {t('Found {{count}} results', { - count: results && results.pagination ? results.pagination.totalResults : 0, - })} - -)} - - - - -)} \ No newline at end of file + const searchResults = searchResponse ? searchResponse.results : []; + + if (!searchResults) { + return null; + } + return ( + + + {searchResults.map((result: SearchResult, index: number) => { + return result ? ( + + ) : null; + })} + + + ); +}; + +blockEditorFor(SearchPageResults, { + label: 'Search Page Results', + type: 'default.SearchPageResults', + anyContext: [], + requiredContext: [], + defaultProps: { + background: '', + list: true, + textColor: '', + cardBorder: '', + imageStyle: 'fit', + }, + editor: { + list: { type: 'checkbox-field', label: 'View', inlineLabel: 'Display as list' }, + background: { label: 'Card background color', type: 'color-field' }, + textColor: { label: 'Card text color', type: 'color-field' }, + cardBorder: { label: 'Card border', type: 'color-field' }, + imageStyle: { + label: 'Image Style', + type: 'dropdown-field', + options: [ + { value: 'covered', text: 'covered' }, + { value: 'fit', text: 'fit' }, + ], + }, + }, +}); diff --git a/services/madoc-ts/src/frontend/site/features/SearchPagination.tsx b/services/madoc-ts/src/frontend/site/features/SearchPagination.tsx new file mode 100644 index 000000000..699fb16cf --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/SearchPagination.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { Pagination, PaginationNumbered } from '../../shared/components/Pagination'; +import { useSearchQuery } from '../hooks/use-search-query'; +import { useSearch } from '../hooks/use-search'; + +export const SearchPagination: React.FC<{ + paginationStyle?: boolean; + position?: 'flex-end' | 'flex-start' | 'center'; +}> = ({ paginationStyle, position }) => { + const { rawQuery, page } = useSearchQuery(); + const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); + const PaginationComponent = paginationStyle ? PaginationNumbered : Pagination; + + const pagination = latestData?.pagination; + + if (!pagination || !latestData) { + return null; + } + + return ( + + ); +}; + +blockEditorFor(SearchPagination, { + type: 'default.SearchPagination', + label: 'Search pagination', + anyContext: [], + requiredContext: [], + defaultProps: { + paginationStyle: false, + position: 'flex-end', + }, + editor: { + paginationStyle: { + type: 'checkbox-field', + inlineLabel: 'Pagination as Numbered?', + label: 'Pagination Numbered', + }, + position: { + label: 'Position', + type: 'dropdown-field', + options: [ + { value: 'flex-start', text: 'Start' }, + { value: 'center', text: 'Center' }, + { value: 'flex-end', text: 'End' }, + ], + }, + }, +}); diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index 678a0d558..074a1fab8 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -2,16 +2,15 @@ import React from 'react'; import { Slot } from '../../shared/page-blocks/slot'; import { useTranslation } from 'react-i18next'; import { LoadingBlock } from '../../shared/callouts/LoadingBlock'; -import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; - import { useSearch } from '../hooks/use-search'; import { useSearchQuery } from '../hooks/use-search-query'; import { StaticPage } from '../features/StaticPage'; import { SearchPageFilters } from '../features/SearchPageFilters'; import { SearchResults, TotalResults } from '../../shared/components/SearchResults'; -import { Pagination } from '../../shared/components/Pagination'; import { AppliedFacets } from '../features/AppliedFacets'; import { Heading1 } from '../../shared/typography/Heading1'; +import { SearchPagination } from '../features/SearchPagination'; +import { SearchPageResults } from "../features/SearchPageResults"; export const Search: React.FC = () => { const { t } = useTranslation(); @@ -20,16 +19,9 @@ export const Search: React.FC = () => { return ( - - - - “{fulltext}” search - - -
    @@ -39,7 +31,12 @@ export const Search: React.FC = () => {
    - + + + + + + {isLoading && !searchResponse ? ( ) : ( @@ -49,23 +46,18 @@ export const Search: React.FC = () => { })} )} - - - + + + + {/**/} + + + +
    From 526b2e77e475d025b9316d1761096769fb771b56 Mon Sep 17 00:00:00 2001 From: Heather Date: Thu, 8 Dec 2022 09:59:21 +0000 Subject: [PATCH 016/191] clean uo --- .../site/features/SearchPageFilters.tsx | 22 +------- .../site/features/SearchPageResults.tsx | 56 +++++++++++-------- .../src/frontend/site/pages/search.tsx | 26 +-------- 3 files changed, 37 insertions(+), 67 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx index 472414259..ead524a52 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import styled from 'styled-components'; import { useTranslation } from 'react-i18next'; import { SearchFilterContainer, @@ -15,31 +14,18 @@ import { LocaleString } from '../../shared/components/LocaleString'; import { useSearchQuery } from '../hooks/use-search-query'; import { useSearchFacets } from '../hooks/use-search-facets'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; -import { InternationalString } from '@iiif/presentation-3'; import { CheckboxBtn } from '../../shared/atoms/CheckboxBtn'; -import { useSearch } from "../hooks/use-search"; - -export const Pill = styled.div` - border-radius: 3px; - width: auto; - background-color: #ecf0ff; - color: #437bdd; - margin-right: 1em; - font-size: 12px; - padding: 5px; -`; +import { useSearch } from '../hooks/use-search'; interface SearchPageFiltersProps { checkBoxColor?: string; - textTest?: string; } -export const SearchPageFilters: React.FC = ({ checkBoxColor, textTest }) => { - +export const SearchPageFilters: React.FC = ({ checkBoxColor }) => { const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); const { t } = useTranslation(); const { appliedFacets } = useSearchQuery(); - console.log(displayFacets, latestData) + const { inQueue, queueSingleFacet, @@ -111,10 +97,8 @@ blockEditorFor(SearchPageFilters, { requiredContext: [], defaultProps: { checkBoxColor: '', - textTest: '', }, editor: { checkBoxColor: { label: 'Check box color', type: 'color-field' }, - textTest: { label: 'Test for props', type: 'text-field' }, }, }); diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx index c14ced489..cb3582196 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; import { useSearchQuery } from '../hooks/use-search-query'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; import { SearchResult } from '../../../types/search'; import { useSearch } from '../hooks/use-search'; -import { ResultsContainer, SearchItem } from '../../shared/components/SearchResults'; +import { ResultsContainer, SearchItem, TotalResults } from '../../shared/components/SearchResults'; import { ImageGrid } from '../../shared/atoms/ImageGrid'; -import { ImageStripBox } from '../../shared/atoms/ImageStrip'; +import { LoadingBlock } from '../../shared/callouts/LoadingBlock'; +import { useTranslation } from 'react-i18next'; interface SearchPageResultsProps { background?: string; @@ -21,36 +21,44 @@ export const SearchPageResults: React.FC = ({ cardBorder, textColor, background, - imageStyle + imageStyle, }) => { - const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); const { rawQuery, page, fulltext } = useSearchQuery(); - + const { t } = useTranslation(); const searchResults = searchResponse ? searchResponse.results : []; if (!searchResults) { return null; } - return ( - - - {searchResults.map((result: SearchResult, index: number) => { - return result ? ( - - ) : null; + return isLoading && !searchResponse ? ( + + ) : ( + <> + + {t('Found {{count}} results', { + count: searchResponse && searchResponse.pagination ? searchResponse.pagination.totalResults : 0, })} - - + + + + {searchResults.map((result: SearchResult, index: number) => { + return result ? ( + + ) : null; + })} + + + ); }; diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index 074a1fab8..f0624ebda 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -1,20 +1,14 @@ import React from 'react'; import { Slot } from '../../shared/page-blocks/slot'; -import { useTranslation } from 'react-i18next'; -import { LoadingBlock } from '../../shared/callouts/LoadingBlock'; -import { useSearch } from '../hooks/use-search'; import { useSearchQuery } from '../hooks/use-search-query'; import { StaticPage } from '../features/StaticPage'; import { SearchPageFilters } from '../features/SearchPageFilters'; -import { SearchResults, TotalResults } from '../../shared/components/SearchResults'; import { AppliedFacets } from '../features/AppliedFacets'; import { Heading1 } from '../../shared/typography/Heading1'; import { SearchPagination } from '../features/SearchPagination'; -import { SearchPageResults } from "../features/SearchPageResults"; +import { SearchPageResults } from '../features/SearchPageResults'; export const Search: React.FC = () => { - const { t } = useTranslation(); - const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); const { rawQuery, page, fulltext } = useSearchQuery(); return ( @@ -35,25 +29,9 @@ export const Search: React.FC = () => { - + - {isLoading && !searchResponse ? ( - - ) : ( - - {t('Found {{count}} results', { - count: searchResponse && searchResponse.pagination ? searchResponse.pagination.totalResults : 0, - })} - - )} - - - {/**/} From b714674b5a167a5319204c45589338366f96f991 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 12 Dec 2022 10:40:52 +0000 Subject: [PATCH 017/191] fix testing styles --- .../shared/components/SearchFilters.tsx | 6 ++---- .../shared/components/SearchResults.tsx | 4 ++-- .../frontend/site/features/AppliedFacets.tsx | 18 +++++------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx b/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx index 0d1becdf7..4608092ad 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchFilters.tsx @@ -7,14 +7,12 @@ export const SearchFilterContainer = styled.div` export const SearchFilterCheckbox = styled.div` padding: 0.1em; - //background: #eee; - border: 1px solid red; + background: #eee; `; export const SearchFilterCheckbox2 = styled.input` padding: 0.1em; - //background: #eee; - border: 1px solid red; + background: #eee; `; export const SearchFilterTitle = styled.h3``; diff --git a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx index 815f4a689..531f45327 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx @@ -101,7 +101,7 @@ export const SearchItem: React.FC<{ const isManifest = result.resource_type === 'Manifest'; return ( -
    + <> -
    + ); }; diff --git a/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx index 93a5f539d..fa7372fae 100644 --- a/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx +++ b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx @@ -1,6 +1,5 @@ import React from 'react'; import styled from 'styled-components'; -import { useTranslation } from 'react-i18next'; import { useSearchQuery } from '../hooks/use-search-query'; import { useSearchFacets } from '../hooks/use-search-facets'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; @@ -41,12 +40,10 @@ export const RemoveFacet = styled.button` transition: background-color ease-in-out 0.3s; } `; -interface AppliedFacetsProps { - facetColor?: string; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface AppliedFacetsProps {} -export const AppliedFacets: React.FC = ({ facetColor }) => { - const { t } = useTranslation(); +export const AppliedFacets: React.FC = () => { const { fulltext, appliedFacets } = useSearchQuery(); const { clearSingleFacet } = useSearchFacets(); @@ -73,13 +70,8 @@ export const AppliedFacets: React.FC = ({ facetColor }) => { blockEditorFor(AppliedFacets, { label: 'Applied Facets', - type: 'default.appliedF-facets', + type: 'default.applied-facets', anyContext: [], requiredContext: [], - defaultProps: { - pillColor: '', - }, - editor: { - pillColor: { label: 'facet color', type: 'color-field' }, - }, + editor: {}, }); From 848a2e9a0f31f2608d8f7ce4618373fb8ab106c9 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 12 Dec 2022 11:06:23 +0000 Subject: [PATCH 018/191] fix defualt list state and add option to hide snippet text --- .../shared/components/SearchResults.tsx | 27 ++++++++++++++----- .../site/features/SearchPageResults.tsx | 17 +++++++----- services/madoc-ts/translations/en/madoc.json | 2 ++ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx index 531f45327..a436912ef 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx @@ -51,9 +51,23 @@ const ResultText = styled.span` `; const TextContainer = styled.div` - text-overflow: ellipsis; - overflow: hidden; - max-height: 100px; + margin-left: 1em; + + &[data-list-item='false'] { + margin-left: 0; + + span { + display: -webkit-box; + max-width: 160px; + height: 100px; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.625; + } + } `; export const ResultTitle = styled.div` @@ -81,11 +95,12 @@ export const SearchItem: React.FC<{ size?: 'large' | 'small'; search?: string; list?: boolean; + hideSnippet?: boolean; border?: string; textColor?: string; background?: string; imageStyle?: string; -}> = ({ result, size, search, list, border, textColor, background, imageStyle }) => { +}> = ({ result, size, search, list, border, textColor, background, imageStyle, hideSnippet }) => { const things = ((result && result.contexts) || []).map(value => { return parseUrn(value.id); }); @@ -126,9 +141,9 @@ export const SearchItem: React.FC<{ )} - + {result.label} - {snippet ? ( + {snippet && !hideSnippet ? (
    = ({ - list, + grid, + snippet, cardBorder, textColor, background, @@ -41,7 +43,7 @@ export const SearchPageResults: React.FC = ({ })} - + {searchResults.map((result: SearchResult, index: number) => { return result ? ( = ({ background={background} border={cardBorder} textColor={textColor} - list={list} + list={!grid} + hideSnippet={snippet} imageStyle={imageStyle} /> ) : null; @@ -69,13 +72,15 @@ blockEditorFor(SearchPageResults, { requiredContext: [], defaultProps: { background: '', - list: true, + grid: '', + snippet: '', textColor: '', cardBorder: '', imageStyle: 'fit', }, editor: { - list: { type: 'checkbox-field', label: 'View', inlineLabel: 'Display as list' }, + grid: { type: 'checkbox-field', label: 'Display as', inlineLabel: 'Display as grid' }, + snippet: { type: 'checkbox-field', label: 'Snippet', inlineLabel: 'Hide snippet?' }, background: { label: 'Card background color', type: 'color-field' }, textColor: { label: 'Card text color', type: 'color-field' }, cardBorder: { label: 'Card border', type: 'color-field' }, diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index bc83b0437..3d8723c9f 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -241,6 +241,7 @@ "Disable plugin": "Disable plugin", "Disable theme": "Disable theme", "Discard merge": "Discard merge", + "Display as": "Display as", "Display name": "Display name", "Display options": "Display options", "Document": "Document", @@ -844,6 +845,7 @@ "remove": "remove", "requiredStatement": "requiredStatement", "search index": "search index", + "show snippet": "show snippet", "size": "size", "summary": "summary", "switch to the main revision": "switch to the main revision", From c0833f3731da586aed292e7ea35b74e4c40eecb0 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 9 Jan 2023 13:00:11 +0000 Subject: [PATCH 019/191] heading based on context --- .../src/frontend/site/pages/search.tsx | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index f0624ebda..34079ead3 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -7,19 +7,44 @@ import { AppliedFacets } from '../features/AppliedFacets'; import { Heading1 } from '../../shared/typography/Heading1'; import { SearchPagination } from '../features/SearchPagination'; import { SearchPageResults } from '../features/SearchPageResults'; +import { DisplayBreadcrumbs, useBreadcrumbs } from '../../shared/components/Breadcrumbs'; +import { useRouteContext } from '../hooks/use-route-context'; +import { LocaleString } from '../../shared/components/LocaleString'; export const Search: React.FC = () => { const { rawQuery, page, fulltext } = useSearchQuery(); + const breads = useBreadcrumbs(); + const isGlobal = !!breads.subpage; + + + const getHeading = () => { + if (breads.manifest) return breads.manifest?.name; + else if (breads.collection) return breads.collection.name; + else { + return breads.project?.name; + } + }; + return ( - + <> + + + + - “{fulltext}” search + {isGlobal ? ( + “{fulltext}” search + ) : ( + + search in {getHeading()} + + )}
    - +
    @@ -39,6 +64,6 @@ export const Search: React.FC = () => {
    -
    + ); }; From 4caff4a6dd6353b579900364d8dfb2f76d9f6ec3 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 9 Jan 2023 15:12:25 +0000 Subject: [PATCH 020/191] tidy up some styles --- .../frontend/shared/atoms/SnippetLarge.tsx | 2 +- .../shared/components/SearchResults.tsx | 52 ++++++++++++++++--- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/services/madoc-ts/src/frontend/shared/atoms/SnippetLarge.tsx b/services/madoc-ts/src/frontend/shared/atoms/SnippetLarge.tsx index 61a08f081..c76479c26 100644 --- a/services/madoc-ts/src/frontend/shared/atoms/SnippetLarge.tsx +++ b/services/madoc-ts/src/frontend/shared/atoms/SnippetLarge.tsx @@ -213,7 +213,7 @@ export const SnippetThumbnailContainer = styled(SnippetUnconstrainedContainer)<{ : props.portrait ? css` margin-bottom: 1rem; - max-height: 11em; + max-height: 14em; max-width: 11em; ` : css` diff --git a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx index a436912ef..333ec81b8 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx @@ -10,6 +10,7 @@ import { SnippetThumbnail, SnippetThumbnailContainer } from '../atoms/SnippetLar import { createLink } from '../utility/create-link'; import { HrefLink } from '../utility/href-link'; import { LocaleString } from './LocaleString'; +import { InternationalString } from '@iiif/presentation-3'; export const ResultsContainer = styled.div<{ $isFetching?: boolean }>` flex: 1 1 0px; @@ -50,12 +51,19 @@ const ResultText = styled.span` line-height: 1.3em; `; +export const ResultTitle = styled.div` + text-decoration: none; + color: #2962ff; + font-size: 1.25rem; + padding-bottom: 0.625rem; +`; + const TextContainer = styled.div` margin-left: 1em; &[data-list-item='false'] { margin-left: 0; - + span { display: -webkit-box; max-width: 160px; @@ -67,14 +75,17 @@ const TextContainer = styled.div` text-overflow: ellipsis; line-height: 1.625; } + + ${ResultTitle} { + font-size: 1rem; + padding-bottom: 0; + } } `; -export const ResultTitle = styled.div` - text-decoration: none; - color: #2962ff; - font-size: 1.25rem; - padding-bottom: 0.625rem; +export const Subtitle = styled.div` + margin: 0.5em 0; + color: #666; `; export const TotalResults = styled.div` @@ -82,6 +93,18 @@ export const TotalResults = styled.div` color: #666; `; +export const MetaDataList = styled.div` + margin-top: 2em; +`; +export const MetaDataItem = styled.span` + margin-right: 1em; + + border: 1px solid #2962ff; + color: #2962ff; + border-radius: 3px; + padding: 0.2em; + font-size: 12px; +`; function sanitizeLabel(str: string) { return str.replace(/^.*': '/, ''); } @@ -130,10 +153,10 @@ export const SearchItem: React.FC<{ {isManifest ? ( - + ) : ( @@ -143,6 +166,7 @@ export const SearchItem: React.FC<{ )} {result.label} + {result.resource_type} {snippet && !hideSnippet ? (
    ) : null} + + {/*{result.metadata && (*/} + {/* */} + {/* {result.metadata.map((item: any, i: number) => {*/} + {/* return (*/} + {/* */} + {/* {item.label} : {item.value}*/} + {/* */} + {/* );*/} + {/* })}*/} + {/* */} + {/*)}*/}
    From a738c0bfa86b89ba4e50f3ed1d72633681d1241f Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 9 Jan 2023 15:23:56 +0000 Subject: [PATCH 021/191] page to context --- .../madoc-ts/src/frontend/site/features/AppliedFacets.tsx | 2 +- .../src/frontend/site/features/SearchPageFilters.tsx | 8 +++++--- .../src/frontend/site/features/SearchPageResults.tsx | 2 +- .../src/frontend/site/features/SearchPagination.tsx | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx index fa7372fae..b1b5ea07d 100644 --- a/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx +++ b/services/madoc-ts/src/frontend/site/features/AppliedFacets.tsx @@ -72,6 +72,6 @@ blockEditorFor(AppliedFacets, { label: 'Applied Facets', type: 'default.applied-facets', anyContext: [], - requiredContext: [], + requiredContext: ['page'], editor: {}, }); diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx index ead524a52..5f2a2e0a6 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageFilters.tsx @@ -16,6 +16,7 @@ import { useSearchFacets } from '../hooks/use-search-facets'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; import { CheckboxBtn } from '../../shared/atoms/CheckboxBtn'; import { useSearch } from '../hooks/use-search'; +import { SearchBox } from '../../shared/atoms/SearchBox'; interface SearchPageFiltersProps { checkBoxColor?: string; @@ -24,7 +25,7 @@ interface SearchPageFiltersProps { export const SearchPageFilters: React.FC = ({ checkBoxColor }) => { const [{ resolvedData: searchResponse, latestData }, displayFacets, isLoading] = useSearch(); const { t } = useTranslation(); - const { appliedFacets } = useSearchQuery(); + const { appliedFacets, fulltext } = useSearchQuery(); const { inQueue, @@ -33,6 +34,7 @@ export const SearchPageFilters: React.FC = ({ checkBoxCo isFacetSelected, applyAllFacets, clearAllFacets, + setFullTextQuery, } = useSearchFacets(); if (!displayFacets) { @@ -41,7 +43,7 @@ export const SearchPageFilters: React.FC = ({ checkBoxCo return ( {t('Refine search')} - {/**/} + applyAllFacets()}> {t('Apply')} @@ -94,7 +96,7 @@ blockEditorFor(SearchPageFilters, { label: 'Search Page Filters', type: 'default.search-page-filters', anyContext: [], - requiredContext: [], + requiredContext: ['page'], defaultProps: { checkBoxColor: '', }, diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx index a7a9c72ee..7601f8757 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPageResults.tsx @@ -69,7 +69,7 @@ blockEditorFor(SearchPageResults, { label: 'Search Page Results', type: 'default.SearchPageResults', anyContext: [], - requiredContext: [], + requiredContext: ['page'], defaultProps: { background: '', grid: '', diff --git a/services/madoc-ts/src/frontend/site/features/SearchPagination.tsx b/services/madoc-ts/src/frontend/site/features/SearchPagination.tsx index 699fb16cf..baa35dfda 100644 --- a/services/madoc-ts/src/frontend/site/features/SearchPagination.tsx +++ b/services/madoc-ts/src/frontend/site/features/SearchPagination.tsx @@ -33,7 +33,7 @@ blockEditorFor(SearchPagination, { type: 'default.SearchPagination', label: 'Search pagination', anyContext: [], - requiredContext: [], + requiredContext: ['page'], defaultProps: { paginationStyle: false, position: 'flex-end', From 32b5607bea9cdf440089681ac46e0f7522d9e785 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 9 Jan 2023 15:27:30 +0000 Subject: [PATCH 022/191] static --- services/madoc-ts/src/frontend/site/pages/search.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index 34079ead3..74fcde182 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -17,7 +17,6 @@ export const Search: React.FC = () => { const breads = useBreadcrumbs(); const isGlobal = !!breads.subpage; - const getHeading = () => { if (breads.manifest) return breads.manifest?.name; else if (breads.collection) return breads.collection.name; @@ -27,9 +26,9 @@ export const Search: React.FC = () => { }; return ( - <> + - + @@ -64,6 +63,6 @@ export const Search: React.FC = () => { - + ); }; From 5a136de360c51cedef883263b14e8f1cfc60b2f3 Mon Sep 17 00:00:00 2001 From: Heather Date: Thu, 12 Jan 2023 13:57:24 +0000 Subject: [PATCH 023/191] Begin matching types to md doc --- .../{install-code.js => install-code.cjs} | 0 .../authority/authority-extension.ts | 23 +++++- .../extensions/enrichment/authority/types.ts | 76 ++++++++++++++----- .../src/routes/site/site-topic-type.ts | 27 +++---- .../src/routes/site/site-topic-types.ts | 28 ++----- .../madoc-ts/src/routes/site/site-topics.ts | 35 ++------- services/madoc-ts/translations/en/madoc.json | 15 ++++ 7 files changed, 114 insertions(+), 90 deletions(-) rename services/madoc-ts/{install-code.js => install-code.cjs} (100%) diff --git a/services/madoc-ts/install-code.js b/services/madoc-ts/install-code.cjs similarity index 100% rename from services/madoc-ts/install-code.js rename to services/madoc-ts/install-code.cjs diff --git a/services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts b/services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts index ad4bbebdf..4c3e2ae07 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/authority-extension.ts @@ -6,10 +6,13 @@ import { EnrichmentEntityAuthority, EnrichmentEntityType, EnrichmentEntityTypeSnippet, - EntityTypeMadocResponse, ResourceTag, ResourceTagSnippet, EnrichmentEntity, + EntityTypesMadocResponse, + EntitiesMadocResponse, + EntityMadocResponse, + EntityTypeMadocResponse, } from './types'; export class AuthorityExtension extends BaseDjangoExtension { @@ -49,8 +52,24 @@ export class AuthorityExtension extends BaseDjangoExtension { return 'authority_service'; } + // list of entity types + getEntityTypes() { + return this.api.request(`/api/enrichment/entity_type/`); + } + + // Entity Type - Retrieve getEntityType(slug: string) { - return this.api.request(`/api/enrichment/entity/${slug}/`); + return this.api.request(`/api/enrichment/entity_type/${slug}/`); + } + + // Entity - List, filtered by chosen Entity Type + getEntities(slug: string) { + return this.api.request(`/api/enrichment/entity/${slug}/`); + } + + // Entity - Retrieve + getEntity(entity_type_slug: string, slug: string) { + return this.api.request(`/api/enrichment/entity/${entity_type_slug}/${slug}/`); } authority = this.createServiceHelper('authority_service', 'authority'); diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts index c3d3ac0e7..6284a22af 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/types.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -13,25 +13,11 @@ export interface Authority extends AuthoritySnippet { export interface EnrichmentEntitySnippet { url: string; id: string; - type: { - url: string; - id: string; - label: string; - }; - label: string; -} - -export interface EnrichmentEntity { - url: string; - id: string; - type: { - url: string; - id: string; - label: string; - }; + created: string; + modified: string; + type: string; label: string; - other_labels: OtherLabels; - authorities: AuthoritySnippet[]; // Can't remember what this should be... + slug: string; } export interface EnrichmentEntityAuthority { @@ -44,7 +30,10 @@ export interface EnrichmentEntityAuthority { export interface EnrichmentEntityTypeSnippet { url: string; id: string; + created: string; + modified: string; label: string; + slug: string; } // @todo probably will change. @@ -77,6 +66,19 @@ export interface ResourceTag extends ResourceTagSnippet { modified: string; } +export interface EnrichmentEntity { + url: string; + id: string; + type: { + url: string; + id: string; + label: string; + }; + label: string; + other_labels: OtherLabels; + authorities: AuthoritySnippet[]; // Can't remember what this should be... +} + export interface EntitySnippetMadoc { url: string; id: string; @@ -84,10 +86,44 @@ export interface EntitySnippetMadoc { modified: string; type: string; label: string; - other_labels: string[]; + slug: string; } -export interface EntityTypeMadocResponse { +// list of entity types +export interface EntityTypesMadocResponse { + pagination: Pagination; + results: EnrichmentEntityTypeSnippet[]; +} + +// Entity - List, filtered by chosen Entity Type +export interface EntitiesMadocResponse { pagination: Pagination; results: EntitySnippetMadoc[]; } + +// Entity Type - Retrieve +export interface EntityTypeMadocResponse { + url: string; + id: string; + created: string; + modified: string; + label: string; + slug: string; +} + +// Entity - Retrieve +export interface EntityMadocResponse { + url: string; + id: string; + created: string; + modified: string; + type: string; + label: string; + slug: string; + title: string[]; + description: string[]; + topic_summary: string[]; + image_url: string; + image_caption: string[]; + secondary_heading: string; +} diff --git a/services/madoc-ts/src/routes/site/site-topic-type.ts b/services/madoc-ts/src/routes/site/site-topic-type.ts index a98e30155..ee14e649c 100644 --- a/services/madoc-ts/src/routes/site/site-topic-type.ts +++ b/services/madoc-ts/src/routes/site/site-topic-type.ts @@ -1,4 +1,8 @@ -import { EntitySnippetMadoc, EntityTypeMadocResponse } from '../../extensions/enrichment/authority/types'; +import { + EntitiesMadocResponse, + EntitySnippetMadoc, + EntityTypeMadocResponse, +} from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; import { TopicSnippet, TopicType } from '../../types/schemas/topics'; @@ -6,39 +10,28 @@ export const siteTopicType: RouteMiddleware<{ type: string }> = async context => const { siteApi } = context.state; const slug = context.params.type; - - // @todo change this to be SLUG later. - // const response = await siteApi.authority.entity_type.get(id); const response = await siteApi.authority.getEntityType(slug); + const topics = await siteApi.authority.getEntities(slug); - context.response.body = compatTopicType(response, slug); + context.response.body = compatTopicType(response, topics, slug); }; function compatTopic(topic: EntitySnippetMadoc): TopicSnippet { return { - id: topic.id, + ...topic, label: { none: [topic.label] }, - slug: topic.id, - // @todo change to slug when supported. - // slug: encodeURIComponent(topic.label), }; } // @todo remove once in the backend. -function compatTopicType(topicType: EntityTypeMadocResponse, slug: string): TopicType { +function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMadocResponse, slug: string): TopicType { const nuked: any = { results: undefined, url: undefined }; return { - // Missing properties - id: 'null', - slug: encodeURIComponent(slug), - ...topicType, - - // Properties to change. label: { none: [slug] }, otherLabels: [], // @todo no other labels given. - topics: topicType.results.map(compatTopic), + topics: topics.results.map(compatTopic), // Mocked editorial editorial: { diff --git a/services/madoc-ts/src/routes/site/site-topic-types.ts b/services/madoc-ts/src/routes/site/site-topic-types.ts index d9f7b1656..7346ed64e 100644 --- a/services/madoc-ts/src/routes/site/site-topic-types.ts +++ b/services/madoc-ts/src/routes/site/site-topic-types.ts @@ -1,43 +1,27 @@ -import { EnrichmentEntityTypeSnippet } from '../../extensions/enrichment/authority/types'; -import { DjangoPagination } from '../../extensions/enrichment/types'; +import { EnrichmentEntityTypeSnippet, EntityTypesMadocResponse } from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; -import { Pagination } from '../../types/schemas/_pagination'; import { TopicTypeSnippet } from '../../types/schemas/topics'; export const siteTopicTypes: RouteMiddleware<{ slug: string }> = async context => { const { siteApi } = context.state; - const page = Number(context.query.page || 1) || 1; - const response = await siteApi.authority.entity_type.list(page); - - context.response.body = compatTopicTypes(response, page); + const response = await siteApi.authority.getEntityTypes(); + context.response.body = compatTopicTypes(response); }; // @todo remove once changed in backend. function compatTopicTypeSnippet(snippet: EnrichmentEntityTypeSnippet): TopicTypeSnippet { return { ...snippet, - slug: snippet.label, label: { none: [snippet.label] }, }; } // @todo remove once changed in backend. -function compatTopicTypes(response: DjangoPagination, page: number) { - const { results, next, previous, count } = response; - const totalPages = Math.min(Math.ceil(results.length / count), page); - +function compatTopicTypes(response: EntityTypesMadocResponse) { + const { results } = response; return { topicTypes: results.map(compatTopicTypeSnippet), - original_pagination: { - next, - previous, - count, - }, - pagination: { - page, - totalResults: results.length * totalPages, - totalPages, - } as Pagination, + pagination: response.pagination, }; } diff --git a/services/madoc-ts/src/routes/site/site-topics.ts b/services/madoc-ts/src/routes/site/site-topics.ts index a9aac811f..fdfb26b4e 100644 --- a/services/madoc-ts/src/routes/site/site-topics.ts +++ b/services/madoc-ts/src/routes/site/site-topics.ts @@ -1,4 +1,4 @@ -import { EnrichmentEntitySnippet } from '../../extensions/enrichment/authority/types'; +import { EnrichmentEntitySnippet, EntitiesMadocResponse } from '../../extensions/enrichment/authority/types'; import { DjangoPagination } from '../../extensions/enrichment/types'; import { RouteMiddleware } from '../../types/route-middleware'; import { Pagination } from '../../types/schemas/_pagination'; @@ -10,47 +10,24 @@ export const siteTopics: RouteMiddleware<{ slug: string }> = async context => { const topicType = context.query.topicType || ''; const page = Number(context.query.page || 1) || 1; - // @todo be able to filter by `topicType` - const response = await siteApi.authority.entity.list(page); - - context.response.body = compatTopic(response, page); + const response = await siteApi.authority.getEntities(topicType); + context.response.body = compatTopic(response); }; // @todo remove once changed in backend. function compatTopicSnippet(snippet: EnrichmentEntitySnippet): TopicSnippet { return { ...snippet, - // Other properties we might want or need. label: { none: [snippet.label] }, - slug: snippet.id, - // @todo change to slug when supported. - // slug: encodeURIComponent(snippet.label), - topicType: snippet.type - ? { - id: snippet.type.id, - slug: snippet.type.label, - label: { none: [snippet.type.label] }, - } - : undefined, }; } // @todo remove once changed in backend. -function compatTopic(response: DjangoPagination, page: number) { - const { results, next, previous, count } = response; - const totalPages = Math.min(Math.ceil(results.length / count), page); +function compatTopic(response: EntitiesMadocResponse) { + const { results } = response; return { topic: results.map(compatTopicSnippet), - original_pagination: { - next, - previous, - count, - }, - pagination: { - page, - totalResults: results.length * totalPages, - totalPages, - } as Pagination, + pagination: response.pagination, }; } diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index a5ce525b7..eebd98e4d 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -46,6 +46,7 @@ "All sub-tasks have been completed": "All sub-tasks have been completed", "All sub-tasks have been completed.": "All sub-tasks have been completed.", "All tasks": "All tasks", + "All topic types": "All topic types", "All users": "All users", "Allow clearing of selection": "Allow clearing of selection", "Allow multiple instances": "Allow multiple instances", @@ -73,6 +74,7 @@ "Assign user": "Assign user", "Assigning a canvas": "Assigning a canvas", "Atlas background": "Atlas background", + "Authority": "Authority", "Auto publish": "Auto publish", "Available": "Available", "Available translations": "Available translations", @@ -200,6 +202,8 @@ "Create new style": "Create new style", "Create project": "Create project", "Create site": "Create site", + "Create topic": "Create topic", + "Create topic type": "Create topic type", "Create translation": "Create translation", "Create translation for {{name}}": "Create translation for {{name}}", "Create user": "Create user", @@ -268,6 +272,7 @@ "Enable search": "Enable search", "Enable theme": "Enable theme", "Enabled translations": "Enabled translations", + "Enrichment": "Enrichment", "Enter IIIF Manifest Collection URL": "Enter IIIF Manifest Collection URL", "Enter URL": "Enter URL", "Enter a heading": "Enter a heading", @@ -411,6 +416,7 @@ "Logout": "Logout", "Long expiry time (minutes)": "Long expiry time (minutes)", "Lost connection to server, retrying...": "Lost connection to server, retrying...", + "Manage Topics": "Manage Topics", "Manage collections": "Manage collections", "Manage collections_plural": "Manage collections", "Manage manifests": "Manage manifests", @@ -419,6 +425,8 @@ "Manage pages, slots and blocks": "Manage pages, slots and blocks", "Manage projects": "Manage projects", "Manage projects_plural": "Manage projects", + "Manage topic type": "Manage topic type", + "Manage topic types displayed": "Manage topic types displayed", "Manifest": "Manifest", "Manifest URL": "Manifest URL", "Manifest already assigned": "Manifest already assigned", @@ -495,6 +503,7 @@ "Open": "Open", "Open in mirador": "Open in mirador", "Options for review listing and pages": "Options for review listing and pages", + "Other label": "Other label", "Override the hero title": "Override the hero title", "Overview": "Overview", "Padding size": "Padding size", @@ -673,6 +682,11 @@ "Title": "Title", "To be removed": "To be removed", "Too difficult": "Too difficult", + "Topic label": "Topic label", + "Topic type": "Topic type", + "Topic type Label": "Topic type Label", + "Topic types": "Topic types", + "Topics": "Topics", "Total contributions": "Total contributions", "Total contributors": "Total contributors", "Transcription": "Transcription", @@ -779,6 +793,7 @@ "breadcrumbs__Collections": "Collections", "breadcrumbs__Manifests": "Manifests", "breadcrumbs__Projects": "Projects", + "breadcrumbs__Topics": "Topics", "button background color": "button background color", "cancel": "cancel", "choose": "choose", From ee42bea8898ee909292e5523dea380efec03b20d Mon Sep 17 00:00:00 2001 From: Heather Date: Fri, 13 Jan 2023 13:43:39 +0000 Subject: [PATCH 024/191] topic type page working --- services/madoc-ts/schemas/TopicType.json | 1 - services/madoc-ts/src/routes/site/site-topic-type.ts | 2 +- services/madoc-ts/src/types/schemas/topics.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/services/madoc-ts/schemas/TopicType.json b/services/madoc-ts/schemas/TopicType.json index 0fdeb0996..182c07faa 100644 --- a/services/madoc-ts/schemas/TopicType.json +++ b/services/madoc-ts/schemas/TopicType.json @@ -104,7 +104,6 @@ "editorial", "id", "label", - "otherLabels", "pagination", "slug", "topics" diff --git a/services/madoc-ts/src/routes/site/site-topic-type.ts b/services/madoc-ts/src/routes/site/site-topic-type.ts index ee14e649c..465a06b33 100644 --- a/services/madoc-ts/src/routes/site/site-topic-type.ts +++ b/services/madoc-ts/src/routes/site/site-topic-type.ts @@ -31,6 +31,7 @@ function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMad ...topicType, label: { none: [slug] }, otherLabels: [], // @todo no other labels given. + pagination: topics.pagination, topics: topics.results.map(compatTopic), // Mocked editorial @@ -40,7 +41,6 @@ function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMad featured: [], heroImage: null, }, - // Nuke these. ...nuked, }; diff --git a/services/madoc-ts/src/types/schemas/topics.ts b/services/madoc-ts/src/types/schemas/topics.ts index bb9f221fb..ebb4e4624 100644 --- a/services/madoc-ts/src/types/schemas/topics.ts +++ b/services/madoc-ts/src/types/schemas/topics.ts @@ -16,7 +16,7 @@ export interface TopicType { id: string; slug: string; label: InternationalString; // From string. - otherLabels: InternationalString[]; + otherLabels?: InternationalString[]; pagination: Pagination; topics: TopicSnippet[]; From dea153fe15baf4a176cb766e90a26d9b29b00de4 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 16 Jan 2023 08:57:02 +0000 Subject: [PATCH 025/191] single topic page :wq --- .../extensions/enrichment/authority/types.ts | 13 +++++- .../src/extensions/enrichment/extension.ts | 4 +- .../authority/entity-type/entity-type.tsx | 2 +- .../enrichment/authority/entity/entity.tsx | 4 +- .../site/pages/loaders/topic-loader.tsx | 4 +- .../src/frontend/site/pages/view-topic.tsx | 42 +++++++++---------- services/madoc-ts/src/router.ts | 6 +-- .../madoc-ts/src/routes/site/site-topic.ts | 40 +++++++++--------- services/madoc-ts/translations/en/madoc.json | 3 ++ 9 files changed, 65 insertions(+), 53 deletions(-) diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts index 6284a22af..100937f2b 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/types.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -1,8 +1,10 @@ import { Pagination } from '../../../types/schemas/_pagination'; export interface AuthoritySnippet { - name: string; + id: string; url: string; + authority: string; + identifier: string; } export interface Authority extends AuthoritySnippet { @@ -74,9 +76,18 @@ export interface EnrichmentEntity { id: string; label: string; }; + description: { value: string; language: string }[]; + image_url: string; label: string; other_labels: OtherLabels; authorities: AuthoritySnippet[]; // Can't remember what this should be... + other_data: { + image_url: string; + image_caption?: string; + }; + + modified: string; + created: string; } export interface EntitySnippetMadoc { diff --git a/services/madoc-ts/src/extensions/enrichment/extension.ts b/services/madoc-ts/src/extensions/enrichment/extension.ts index 4babf70c7..6a8127263 100644 --- a/services/madoc-ts/src/extensions/enrichment/extension.ts +++ b/services/madoc-ts/src/extensions/enrichment/extension.ts @@ -27,8 +27,8 @@ export class EnrichmentExtension extends BaseDjangoExtension { } // Site APIS. - getSiteTopic(id: string, type: string) { - return this.api.publicRequest(`/madoc/api/topics/${type}/${id}`); + getSiteTopic(type: string, slug: string) { + return this.api.publicRequest(`/madoc/api/topics/${type}/${slug}`); } getSiteTopicTypes(page = 1) { return this.api.publicRequest(`/madoc/api/topics?page=${page}`); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx index c2e1c9fa3..74a2b0f1f 100644 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx @@ -69,7 +69,7 @@ serverRendererFor(EntityType, { const entity = await api.authority.entity_type.get(vars.id); return { entity, - items: await api.enrichment.getTopicType(entity.label), + items: await api.enrichment.getTopicType(entity.slug), }; }, }); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx index 5a6871958..0c2b161a5 100644 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { EnrichmentEntitySnippet } from '../../../../../../extensions/enrichment/authority/types'; +import { EnrichmentEntity } from '../../../../../../extensions/enrichment/authority/types'; import { useData } from '../../../../../shared/hooks/use-data'; import { serverRendererFor } from '../../../../../shared/plugins/external/server-renderer-for'; export function Entity() { - const { data } = useData(Entity); + const { data } = useData(Entity); return (
    diff --git a/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx index 08a65dbaf..5e4b6f532 100644 --- a/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx +++ b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx @@ -38,10 +38,10 @@ export const TopicLoader: UniversalComponent = createUniversalC }, { getKey: params => { - return ['topic-type', { topic: params.topic, topicType: params.topicType }]; + return ['site-topic', { topicType: params.topicType, topic: params.topic }]; }, getData: async (key, vars, api) => { - return api.enrichment.getSiteTopic(vars.topic, vars.topicType); + return api.enrichment.getSiteTopic(vars.topicType, vars.topic); }, } ); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 7fc1ed328..a56dbd4aa 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -25,27 +25,27 @@ export function ViewTopic() { {topicType ? {topicType?.label || { none: ['...'] }} : null}
    {JSON.stringify(data, null, 2)}
    -
    -

    Items in this topic

    - - - -
    {JSON.stringify(search.data, null, 2)}
    -
    + {/*
    */} + {/*

    Items in this topic

    */} + {/* */} + {/* */} + {/* */} + {/*
    {JSON.stringify(search.data, null, 2)}
    */} + {/*
    */} ); } diff --git a/services/madoc-ts/src/router.ts b/services/madoc-ts/src/router.ts index 159694862..859aa029b 100644 --- a/services/madoc-ts/src/router.ts +++ b/services/madoc-ts/src/router.ts @@ -122,7 +122,7 @@ import { getStaticPage, sitePages } from './routes/site/site-pages'; import { siteTaskMetadata } from './routes/site/site-task-metadata'; import { siteTopics } from './routes/site/site-topics'; import { siteUserAutocomplete } from './routes/site/site-user-autocomplete'; -import { topicRoutes } from './routes/topics/index'; +import { topicRoutes } from './routes/topics'; import { forgotPassword } from './routes/user/forgot-password'; import { getSiteUser } from './routes/user/get-site-user'; import { loginRefresh } from './routes/user/login-refresh'; @@ -575,8 +575,8 @@ export const router = new TypedRouter({ 'site-project': [TypedRouter.GET, '/s/:slug/madoc/api/projects/:projectSlug', siteProject], 'site-projects': [TypedRouter.GET, '/s/:slug/madoc/api/projects', siteProjects], 'site-search': [TypedRouter.POST, '/s/:slug/madoc/api/search', siteSearch], - 'site-topic-index': [TypedRouter.GET, '/s/:slug/madoc/api/topics/_all', siteTopics], - 'site-topic': [TypedRouter.GET, '/s/:slug/madoc/api/topics/:type/:id', siteTopic], + // 'site-topic-index': [TypedRouter.GET, '/s/:slug/madoc/api/topics/_all', siteTopics], + 'site-topic': [TypedRouter.GET, '/s/:slug/madoc/api/topics/:type/:topic', siteTopic], 'site-topic-type': [TypedRouter.GET, '/s/:slug/madoc/api/topics/:type', siteTopicType], 'site-topic-types': [TypedRouter.GET, '/s/:slug/madoc/api/topics', siteTopicTypes], 'site-published-models': [TypedRouter.GET, '/s/:slug/madoc/api/canvases/:id/models', sitePublishedModels], diff --git a/services/madoc-ts/src/routes/site/site-topic.ts b/services/madoc-ts/src/routes/site/site-topic.ts index 43b48723a..cc75dc99b 100644 --- a/services/madoc-ts/src/routes/site/site-topic.ts +++ b/services/madoc-ts/src/routes/site/site-topic.ts @@ -1,45 +1,43 @@ -import { EnrichmentEntity } from '../../extensions/enrichment/authority/types'; +import { EntityMadocResponse, EntityTypeMadocResponse } from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; import { Topic } from '../../types/schemas/topics'; import { NotFound } from '../../utility/errors/not-found'; -export const siteTopic: RouteMiddleware<{ slug: string; type: string; id: string }> = async context => { +export const siteTopic: RouteMiddleware<{ type: string; topic: string }> = async context => { const { siteApi } = context.state; - const slug = context.params.id; + const slug = context.params.topic; const topicTypeSlug = context.params.type; - // const response = await siteApi.authority.entity_type.get(id); - const response = await siteApi.authority.entity.get(slug); + const response = await siteApi.authority.getEntity(topicTypeSlug, slug); + const topicType = await siteApi.authority.getEntityType(topicTypeSlug); - if (response.type) { - if (topicTypeSlug !== response.type.label && topicTypeSlug !== response.type.id) { - throw new NotFound('Topic not found'); - } - } + context.response.body = compatTopic(response, topicType); - // @todo validate topic-type matches. + // if (response.type) { + // if (topicTypeSlug !== response.type) { + // console.log(response.type, topicTypeSlug); + // throw new NotFound('Topic not found'); + // } + // } - context.response.body = compatTopic(response); + // @todo validate topic-type matches. }; -function compatTopic(topic: EnrichmentEntity): Topic { +function compatTopic(topic: EntityMadocResponse, topicType: EntityTypeMadocResponse): Topic { const nuked: any = { other_labels: undefined, url: undefined }; - return { ...topic, - // @todo change to slug. - slug: topic.id, label: { none: [topic.label] }, topicType: topic.type ? { - id: topic.type.id, - slug: topic.type.label, - label: { none: [topic.type.label] }, + id: topicType.id, + slug: topicType.slug, + label: { none: [topicType.label] }, } : undefined, - otherLabels: topic.other_labels.map(label => ({ [label.language.slice(0, 2)]: [label.value] })), - authorities: topic.authorities.map(auth => ({ id: auth.name, label: { none: [auth.name] } })), + // otherLabels: topic.other_labels.map(label => ({ [label.language.slice(0, 2)]: [label.value] })), + // authorities: topic.authorities.map(auth => ({ id: auth.name, label: { none: [auth.name] } })), // Mocked values. editorial: { diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index eebd98e4d..2234e38fa 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -383,6 +383,7 @@ "Invalid JSON, please try again": "Invalid JSON, please try again", "Invalid collection": "Invalid collection", "Invitations": "Invitations", + "Items tagged": "Items tagged", "JSON": "JSON", "JSON Key / Term": "JSON Key / Term", "Keep working on this image or move on to next image": "Keep working on this image or move on to next image", @@ -425,6 +426,7 @@ "Manage pages, slots and blocks": "Manage pages, slots and blocks", "Manage projects": "Manage projects", "Manage projects_plural": "Manage projects", + "Manage topic": "Manage topic", "Manage topic type": "Manage topic type", "Manage topic types displayed": "Manage topic types displayed", "Manifest": "Manifest", @@ -682,6 +684,7 @@ "Title": "Title", "To be removed": "To be removed", "Too difficult": "Too difficult", + "Topic": "Topic", "Topic label": "Topic label", "Topic type": "Topic type", "Topic type Label": "Topic type Label", From 1e127ea894c3367ef0672057c0c88d29f9c46ed7 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 23 Jan 2023 15:17:28 +0000 Subject: [PATCH 026/191] items in topic --- .../src/frontend/site/pages/view-topic.tsx | 42 +++++++++---------- .../madoc-ts/src/routes/site/site-topic.ts | 13 +++--- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index a56dbd4aa..7fc1ed328 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -25,27 +25,27 @@ export function ViewTopic() { {topicType ? {topicType?.label || { none: ['...'] }} : null}
    {JSON.stringify(data, null, 2)}
    - {/*
    */} - {/*

    Items in this topic

    */} - {/* */} - {/* */} - {/* */} - {/*
    {JSON.stringify(search.data, null, 2)}
    */} - {/*
    */} +
    +

    Items in this topic

    + + + +
    {JSON.stringify(search.data, null, 2)}
    +
    ); } diff --git a/services/madoc-ts/src/routes/site/site-topic.ts b/services/madoc-ts/src/routes/site/site-topic.ts index cc75dc99b..24124c102 100644 --- a/services/madoc-ts/src/routes/site/site-topic.ts +++ b/services/madoc-ts/src/routes/site/site-topic.ts @@ -14,18 +14,17 @@ export const siteTopic: RouteMiddleware<{ type: string; topic: string }> = async context.response.body = compatTopic(response, topicType); - // if (response.type) { - // if (topicTypeSlug !== response.type) { - // console.log(response.type, topicTypeSlug); - // throw new NotFound('Topic not found'); - // } - // } + if (response.type) { + if (topicTypeSlug !== response.type) { + throw new NotFound('Topic not found'); + } + } // @todo validate topic-type matches. }; function compatTopic(topic: EntityMadocResponse, topicType: EntityTypeMadocResponse): Topic { - const nuked: any = { other_labels: undefined, url: undefined }; + const nuked: any = { other_labels: undefined, url: undefined, type: undefined }; return { ...topic, label: { none: [topic.label] }, From f3804387180b5fe5435d29d4ead973e5eed1957d Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 30 Jan 2023 13:34:00 +0000 Subject: [PATCH 027/191] IDA-941 topic page hero --- docker-compose.enrichment.yml | 3 +- docker-compose.yml | 19 ++-- .../extensions/enrichment/authority/types.ts | 5 +- .../src/frontend/site/features/TopicHero.tsx | 96 +++++++++++++++++++ .../src/frontend/site/pages/view-topic.tsx | 53 +++++----- .../madoc-ts/src/routes/site/site-topic.ts | 19 ++-- services/madoc-ts/src/types/schemas/topics.ts | 7 +- 7 files changed, 152 insertions(+), 50 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/TopicHero.tsx diff --git a/docker-compose.enrichment.yml b/docker-compose.enrichment.yml index 8e210a128..93e87e0f1 100755 --- a/docker-compose.enrichment.yml +++ b/docker-compose.enrichment.yml @@ -31,4 +31,5 @@ services: - gateway-redis volumes: - ./services/enrichment/app:/app:delegated - + ports: + - 5020:5000 diff --git a/docker-compose.yml b/docker-compose.yml index f258de958..ce4b3003d 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -203,10 +203,9 @@ services: condition: service_healthy search: - image: ghcr.io/digirati-co-uk/madoc-search-service:main -# build: -# context: services/search -# dockerfile: Dockerfile + build: + context: services/enrichment + dockerfile: Dockerfile environment: - BROWSABLE=False - USE_DOCKER=yes @@ -219,15 +218,21 @@ services: - POSTGRES_HOST=shared-postgres - POSTGRES_PORT=${POSTGRES_PORT} - POSTGRES_USER=${POSTGRES_SEARCH_API_USER} + - REDIS_URL=redis://gateway-redis:6379 + - APPEND_SLASH=False + - ALLOWED_HOSTS=gateway,madoc.local - POSTGRES_PASSWORD=${POSTGRES_SEARCH_API_PASSWORD} - POSTGRES_SCHEMA=${POSTGRES_SEARCH_API_SCHEMA} - POSTGRES_DB=${POSTGRES_DB} - DATABASE_URL=postgres://${POSTGRES_SEARCH_API_USER}:${POSTGRES_SEARCH_API_PASSWORD}@shared-postgres:${POSTGRES_PORT}/${POSTGRES_DB} + - INLINE_WORKER=True links: - shared-postgres -# volumes: -# - ./services/search/search_service:/app - + - gateway-redis + volumes: + - ./services/enrichment/app:/app:delegated + ports: + - 5020:500 okra: image: digirati/okra:latest diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts index 100937f2b..fdc7b2eed 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/types.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -132,9 +132,10 @@ export interface EntityMadocResponse { label: string; slug: string; title: string[]; - description: string[]; - topic_summary: string[]; + description: string; + topic_summary?: string; image_url: string; image_caption: string[]; secondary_heading: string; + authorities: { authority: string; identifier: string; uri: string }[]; } diff --git a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx new file mode 100644 index 000000000..ce3887662 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx @@ -0,0 +1,96 @@ +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import React from 'react'; +import styled from 'styled-components'; +import { useTopic } from '../pages/loaders/topic-loader'; +import { LocaleString } from '../../shared/components/LocaleString'; + +const TopicHeroWrapper = styled.div` + display: flex; +`; + +const HeroText = styled.div` + display: flex; + flex-direction: column; +`; + +const HeroHeading = styled.h1` + font-size: 3em; + line-height: 56px; + font-weight: 700; + margin-bottom: 12px; +`; + +const HeroSummary = styled.p` + font-size: 1em; + line-height: 30px; + font-weight: 500; +`; + +const HeroSubHeading = styled.h2` + font-size: 1.75em; + line-height: 36px; + font-weight: 600; + margin-bottom: 12px; +`; +const HeroDescription = styled.p` + font-size: 1em; + line-height: 28px; + font-weight: 400; +`; + +const ImageMask = styled.div` + height: 500px; + width: 500px; + border-radius: 100%; + background-size: contain; + background-position: center; + margin-left: 4em; +`; +const Right = styled.div` + display: flex; + flex-direction: column; +`; + +export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1Color, h2Color }) => { + const { data } = useTopic(); + + if (!data) { + return null; + } + return ( + + + + {data?.label} + + + {data.editorial.description && ( + + {data.editorial?.description[0]} + + )} + + + {data.editorial.subHeading} + + + {data.editorial.summary} + + + + + + + ); +}; + +blockEditorFor(TopicHero, { + type: 'default.topicHero', + label: 'Topic hero', + anyContext: ['topic'], + requiredContext: ['topic'], + editor: { + h1Color: { label: 'Heading and summary color', type: 'color-field' }, + h2Color: { label: 'Subheading and description color', type: 'color-field' }, + }, +}); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 7fc1ed328..dd5756aff 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -5,13 +5,11 @@ import { Pagination } from '../../shared/components/Pagination'; import { SearchResults } from '../../shared/components/SearchResults'; import { useTopicItems } from '../../shared/hooks/use-topic-items'; import { Slot } from '../../shared/page-blocks/slot'; -import { Heading1, Subheading1 } from '../../shared/typography/Heading1'; import { useTopic } from './loaders/topic-loader'; import { useTopicType } from './loaders/topic-type-loader'; +import { TopicHero } from '../features/TopicHero'; export function ViewTopic() { - const { data: topicType } = useTopicType(); - const { data } = useTopic(); const [search, { query, page }] = useTopicItems(); return ( @@ -20,32 +18,31 @@ export function ViewTopic() { - {/* Custom slots.. */} - {data?.label || { none: ['...'] }} - {topicType ? {topicType?.label || { none: ['...'] }} : null} -
    {JSON.stringify(data, null, 2)}
    + + + -
    -

    Items in this topic

    - - - -
    {JSON.stringify(search.data, null, 2)}
    -
    + {/*
    */} + {/*

    Items in this topic

    */} + {/* */} + {/* */} + {/* */} + {/*
    {JSON.stringify(search.data, null, 2)}
    */} + {/*
    */} ); } diff --git a/services/madoc-ts/src/routes/site/site-topic.ts b/services/madoc-ts/src/routes/site/site-topic.ts index 24124c102..b26c829d8 100644 --- a/services/madoc-ts/src/routes/site/site-topic.ts +++ b/services/madoc-ts/src/routes/site/site-topic.ts @@ -15,7 +15,7 @@ export const siteTopic: RouteMiddleware<{ type: string; topic: string }> = async context.response.body = compatTopic(response, topicType); if (response.type) { - if (topicTypeSlug !== response.type) { + if (topicTypeSlug.toLowerCase() !== response.type.toLowerCase()) { throw new NotFound('Topic not found'); } } @@ -24,10 +24,10 @@ export const siteTopic: RouteMiddleware<{ type: string; topic: string }> = async }; function compatTopic(topic: EntityMadocResponse, topicType: EntityTypeMadocResponse): Topic { - const nuked: any = { other_labels: undefined, url: undefined, type: undefined }; + const nuked: any = { url: undefined, type: undefined, label: undefined }; return { ...topic, - label: { none: [topic.label] }, + label: topic.title, topicType: topic.type ? { id: topicType.id, @@ -36,16 +36,21 @@ function compatTopic(topic: EntityMadocResponse, topicType: EntityTypeMadocRespo } : undefined, // otherLabels: topic.other_labels.map(label => ({ [label.language.slice(0, 2)]: [label.value] })), - // authorities: topic.authorities.map(auth => ({ id: auth.name, label: { none: [auth.name] } })), + authorities: topic.authorities.map(auth => ({ + id: auth.uri, + authority: auth.authority, + label: { none: [auth.identifier] }, + })), // Mocked values. editorial: { related: [], featured: [], contributors: [], - description: { en: ['Example description'] }, - heroImage: null, - summary: { en: ['Example summary'] }, + subHeading: topic.secondary_heading, + description: topic.description, + heroImage: { url: topic.image_url, alt: topic.image_caption, overlayColor: null, transparent: null}, + summary: topic.topic_summary, }, // Nuke these properties diff --git a/services/madoc-ts/src/types/schemas/topics.ts b/services/madoc-ts/src/types/schemas/topics.ts index ebb4e4624..d9e7857de 100644 --- a/services/madoc-ts/src/types/schemas/topics.ts +++ b/services/madoc-ts/src/types/schemas/topics.ts @@ -50,7 +50,6 @@ export interface Topic { slug: string; label: InternationalString; // From string. topicType?: TopicTypeSnippet; - otherLabels: InternationalString[]; authorities: Array<{ id: string; label: InternationalString }>; modified: string; created: string; @@ -62,16 +61,14 @@ export interface Topic { label: string; }>; summary?: InternationalString; + subHeading?: InternationalString; heroImage?: { url: string; alt?: string; overlayColor?: string; transparent?: boolean; } | null; - description?: { - label: InternationalString; - value: InternationalString; - }; + description?: InternationalString; // @todo search result format MAY change, hopefully not. featured?: Array; related?: Array; From 2b564c7594b030a48382c351374a6a1a60534474 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 30 Jan 2023 13:38:20 +0000 Subject: [PATCH 028/191] IDA-942 topic type hero --- .../frontend/site/features/TopicTypeHero.tsx | 87 +++++++++++++++++++ .../frontend/site/pages/view-topic-type.tsx | 10 ++- 2 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx diff --git a/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx new file mode 100644 index 000000000..722f184b2 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx @@ -0,0 +1,87 @@ +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import React from 'react'; +import styled from 'styled-components'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { useTopicType } from '../pages/loaders/topic-type-loader'; + +const TopicHeroWrapper = styled.div` + margin-left: -2.3em; + margin-right: -2.3em; + position: relative; + height: 500px; +`; +const TopWrapper = styled.div` + display: flex; + justify-content: space-between; +`; + +const BackgroundImage = styled.div<{ $overlay?: string }>` + background-repeat: no-repeat; + background-size: cover; + background-position: right; + position: absolute; + height: 100%; + width: 100%; + :after { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: ${props => (props.$overlay ? props.$overlay : '#002d4b')}; + opacity: 70%; + } +`; +const TextBox = styled.div` + background-color: white; + position: absolute; + width: 80vw; + padding: 4em 6em; + left: 10vw; + bottom: 0; +`; + +export const HeroHeading = styled.h1` + font-size: 3em; + line-height: 56px; + font-weight: 700; + grid-column: span 7; + grid-row: row 1; + margin: 0; +`; +export const HeroSubHeading = styled.p` + font-size: 1.37em; + line-height: 30px; + font-weight: 500; +`; + +export const TopicTypeHero: React.FC<{ textColor?: string; overlayColor?: string }> = ({ textColor, overlayColor }) => { + const { data } = useTopicType(); + + if (!data) { + return null; + } + return ( + + + + + {data.label} + + {data.editorial.summary} + + + ); +}; + +blockEditorFor(TopicTypeHero, { + type: 'default.topicTypeHero', + label: 'Topic type hero', + anyContext: ['topic'], + requiredContext: ['topic'], + editor: { + textColor: { label: 'Heading and summary color', type: 'color-field' }, + OverlayColor: { label: 'Overlay color', type: 'color-field' }, + }, +}); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx index cc65dd3a9..c52a9419a 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; import { LocaleString } from '../../shared/components/LocaleString'; import { Slot } from '../../shared/page-blocks/slot'; -import { Heading1 } from '../../shared/typography/Heading1'; import { HrefLink } from '../../shared/utility/href-link'; import { useRelativeLinks } from '../hooks/use-relative-links'; import { useTopicType } from './loaders/topic-type-loader'; +import { TopicTypeHero } from '../features/TopicTypeHero'; export function ViewTopicType() { const createLink = useRelativeLinks(); @@ -17,8 +17,10 @@ export function ViewTopicType() { - {/* Custom slots.. */} - {data?.label || { none: ['...'] }} + + + +
      {data?.topics.map(topic => (
    • @@ -28,7 +30,7 @@ export function ViewTopicType() {
    • ))}
    -
    {JSON.stringify(data, null, 2)}
    + {/*
    {JSON.stringify(data, null, 2)}
    */} ); } From 7ab7abd78753d53364c4bec318bfead3ce644620 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 30 Jan 2023 16:23:16 +0000 Subject: [PATCH 029/191] topic grid --- .../src/frontend/shared/atoms/ImageStrip.tsx | 4 +- .../frontend/shared/typography/Heading3.tsx | 7 ++ .../src/frontend/site/features/TopicGrid.tsx | 108 ++++++++++++++++++ .../frontend/site/pages/view-topic-type.tsx | 20 +--- services/madoc-ts/translations/en/madoc.json | 1 + 5 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/TopicGrid.tsx diff --git a/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx b/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx index 4f2743b7d..860bb71c9 100644 --- a/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx +++ b/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx @@ -10,8 +10,8 @@ export const ImageStripBox = styled.div<{ position: relative; flex-shrink: 0; border-radius: 3px; - max-width: ${props => (props.$size === 'small' ? '200px' : '')}; - border: 1px solid transparent; + max-height: ${props => (props.$size === 'small' ? '200px' : '')}; + border: 1px solid; border-color: ${props => (props.$border ? props.$border : 'transparent')}; background-color: ${props => (props.$bgColor ? props.$bgColor : 'inherit')}; h5, diff --git a/services/madoc-ts/src/frontend/shared/typography/Heading3.tsx b/services/madoc-ts/src/frontend/shared/typography/Heading3.tsx index 48b98c013..d24d4ba1d 100644 --- a/services/madoc-ts/src/frontend/shared/typography/Heading3.tsx +++ b/services/madoc-ts/src/frontend/shared/typography/Heading3.tsx @@ -21,6 +21,13 @@ export const Heading3 = styled.h3<{ $margin?: boolean }>` } `; +export const SingleLineHeading3 = styled(Heading3)` + overflow: hidden; + text-overflow: ellipsis; + text-decoration: none; + white-space: nowrap; +`; + export const Subheading3 = styled.div` margin-bottom: 1em; margin-top: 0.5em; diff --git a/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx new file mode 100644 index 000000000..dc232632b --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx @@ -0,0 +1,108 @@ +import { ImageGrid } from '../../shared/atoms/ImageGrid'; +import { Link } from 'react-router-dom'; +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { useRelativeLinks } from '../hooks/use-relative-links'; +import { useTranslation } from 'react-i18next'; +import { LocaleString, useCreateLocaleString } from '../../shared/components/LocaleString'; +import { useTopicType } from '../pages/loaders/topic-type-loader';; +import { ImageStripBox } from '../../shared/atoms/ImageStrip'; +import { CroppedImage } from '../../shared/atoms/Images'; +import { SingleLineHeading3, Subheading3 } from '../../shared/typography/Heading3'; +import styled from 'styled-components'; +import { Heading5 } from '../../shared/typography/Heading5'; +import { TopicSnippet } from '../../../types/schemas/topics'; + +const Pill = styled.div` + background: #f5d8c0; + color: #002d4b; + border-radius: 4px; + font-size: 12px; + padding: 4px; + margin-top: 0.5em; + display: inline-block; +`; + +export function TopicGrid(props: { + background?: string; + textColor?: string; + cardBorder?: string; + imageStyle?: string; +}) { + const { data } = useTopicType(); + const items = data?.topics; + const createLocaleString = useCreateLocaleString(); + const createLink = useRelativeLinks(); + const { t } = useTranslation(); + if (!data) { + return null; + } + + const renderTopicSnippet = (topic: TopicSnippet) => { + return ( + + + {/* todo await BE */} + {!topic.thumbnail ? ( + {createLocaleString(topic.label, + ) : null} + +
    + {topic.label} + + {/* todo await BE */} + 123 Objects + + PART OF + {topic.slug} +
    +
    + ); + }; + + return ( +
    + + {items?.map(topic => ( + + {renderTopicSnippet(topic)} + + ))} + +
    + ); +} +blockEditorFor(TopicGrid, { + type: 'default.TopicGrid', + label: 'Topic Grid', + defaultProps: { + background: '#ffffff', + textColor: '#002D4B', + cardBorder: '#002D4B', + imageStyle: 'covered', + }, + editor: { + background: { label: 'Card background color', type: 'color-field' }, + textColor: { label: 'Card text color', type: 'color-field' }, + cardBorder: { label: 'Card border', type: 'color-field' }, + imageStyle: { + label: 'Image Style', + type: 'dropdown-field', + options: [ + { value: 'covered', text: 'covered' }, + { value: 'fit', text: 'fit' }, + ], + }, + }, + requiredContext: ['topicType'], + anyContext: ['topicType'], +}); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx index c52a9419a..be892b485 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -1,16 +1,10 @@ import React from 'react'; import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; -import { LocaleString } from '../../shared/components/LocaleString'; import { Slot } from '../../shared/page-blocks/slot'; -import { HrefLink } from '../../shared/utility/href-link'; -import { useRelativeLinks } from '../hooks/use-relative-links'; -import { useTopicType } from './loaders/topic-type-loader'; import { TopicTypeHero } from '../features/TopicTypeHero'; +import { TopicGrid } from '../features/TopicGrid'; export function ViewTopicType() { - const createLink = useRelativeLinks(); - const { data } = useTopicType(); - return ( <> @@ -21,15 +15,9 @@ export function ViewTopicType() { -
      - {data?.topics.map(topic => ( -
    • - - {topic.label} - -
    • - ))} -
    + + + {/*
    {JSON.stringify(data, null, 2)}
    */} ); diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index 2234e38fa..c16180845 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -686,6 +686,7 @@ "Too difficult": "Too difficult", "Topic": "Topic", "Topic label": "Topic label", + "Topic thumbnail": "Topic thumbnail", "Topic type": "Topic type", "Topic type Label": "Topic type Label", "Topic types": "Topic types", From d9c3f5fbd61aa0a455e196c2cd505e3e36fc04a7 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 30 Jan 2023 17:03:25 +0000 Subject: [PATCH 030/191] add a block for all topic types items --- .../site/features/AllTopicTypeItems.tsx | 38 +++++++++++++++ .../frontend/site/pages/view-topic-type.tsx | 13 +++++ .../frontend/site/pages/view-topic-types.tsx | 47 ++++++++----------- services/madoc-ts/translations/en/madoc.json | 2 + 4 files changed, 73 insertions(+), 27 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/AllTopicTypeItems.tsx diff --git a/services/madoc-ts/src/frontend/site/features/AllTopicTypeItems.tsx b/services/madoc-ts/src/frontend/site/features/AllTopicTypeItems.tsx new file mode 100644 index 000000000..e24695512 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/AllTopicTypeItems.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { usePaginatedTopicTypes } from '../pages/loaders/topic-type-list-loader'; +import { ObjectContainer } from '../../shared/atoms/ObjectContainer'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { Heading3 } from '../../shared/typography/Heading3'; +import { Button } from '../../shared/navigation/Button'; +import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { useRelativeLinks } from '../hooks/use-relative-links'; + +export const AllTopicTypeItems: React.FC = () => { + const { data } = usePaginatedTopicTypes(); + const { t } = useTranslation(); + const createLink = useRelativeLinks(); + + return ( + <> + {data?.topicTypes.map(type => ( + + {type.label || { en: ['...'] }} + + + ))} + + ); +}; + +blockEditorFor(AllTopicTypeItems, { + type: 'default.AllTopicTypeItems', + label: 'All Topic Types', + internal: true, + anyContext: [], + requiredContext: [], + editor: {}, +}); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx index be892b485..4a7cf11c0 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -3,8 +3,12 @@ import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; import { Slot } from '../../shared/page-blocks/slot'; import { TopicTypeHero } from '../features/TopicTypeHero'; import { TopicGrid } from '../features/TopicGrid'; +import { Pagination } from '../../shared/components/Pagination'; +import { useTopicType } from './loaders/topic-type-loader'; export function ViewTopicType() { + const { data } = useTopicType(); + return ( <> @@ -18,6 +22,15 @@ export function ViewTopicType() { + + + + + {/*
    {JSON.stringify(data, null, 2)}
    */} ); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx index fa452af07..061afcdeb 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx @@ -1,43 +1,36 @@ import React from 'react'; -import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; -import { LocaleString } from '../../shared/components/LocaleString'; import { Pagination } from '../../shared/components/Pagination'; import { Slot } from '../../shared/page-blocks/slot'; import { Heading1 } from '../../shared/typography/Heading1'; -import { HrefLink } from '../../shared/utility/href-link'; -import { useRelativeLinks } from '../hooks/use-relative-links'; import { usePaginatedTopicTypes } from './loaders/topic-type-list-loader'; +import { StaticPage } from '../features/StaticPage'; +import { useTranslation } from 'react-i18next'; +import { AllTopicTypeItems } from '../features/AllTopicTypeItems'; export function ViewTopicTypes() { - const createLink = useRelativeLinks(); + const { t } = useTranslation(); const { data } = usePaginatedTopicTypes(); return ( - <> - - + + + {t('All Topic Types')} + + - {/* Custom slots.. */} - Topic types -
      - {data?.topicTypes.map(topicType => ( -
    • - - {topicType.label} - -
    • - ))} -
    + + + - + -
    {JSON.stringify(data, null, 2)}
    - + {/*
    {JSON.stringify(data, null, 2)}
    */} +
    ); } diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index c16180845..a5722454b 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -35,6 +35,7 @@ "Align items": "Align items", "Align title (vertical)": "Align title (vertical)", "All": "All", + "All Topic Types": "All Topic Types", "All collections": "All collections", "All locales": "All locales", "All manifests": "All manifests", @@ -321,6 +322,7 @@ "Go back": "Go back", "Go back to project": "Go back to project", "Go back to resource": "Go back to resource", + "Go to Topic Type": "Go to Topic Type", "Go to collection": "Go to collection", "Go to next image": "Go to next image", "Go to previous image": "Go to previous image", From 1d1ad7d1d6a393fb4509f209617a512f3a2d8a5b Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 30 Jan 2023 17:05:08 +0000 Subject: [PATCH 031/191] clean up --- services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx b/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx index 860bb71c9..848723ea6 100644 --- a/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx +++ b/services/madoc-ts/src/frontend/shared/atoms/ImageStrip.tsx @@ -10,7 +10,7 @@ export const ImageStripBox = styled.div<{ position: relative; flex-shrink: 0; border-radius: 3px; - max-height: ${props => (props.$size === 'small' ? '200px' : '')}; + max-width: ${props => (props.$size === 'small' ? '200px' : '')}; border: 1px solid; border-color: ${props => (props.$border ? props.$border : 'transparent')}; background-color: ${props => (props.$bgColor ? props.$bgColor : 'inherit')}; From 9891145e2cdcd500f54f17918e700f11aeb3ea4b Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 31 Jan 2023 11:53:16 +0000 Subject: [PATCH 032/191] fix types --- services/madoc-ts/schemas/Topic.json | 50 +++++------------- .../extensions/enrichment/authority/types.ts | 11 ++-- .../src/frontend/site/features/TopicHero.tsx | 3 +- .../site/pages/loaders/topic-loader.tsx | 2 +- .../frontend/site/pages/view-topic-types.tsx | 5 ++ .../src/frontend/site/pages/view-topic.tsx | 51 +++++++++---------- .../src/routes/site/site-topic-type.ts | 10 ++-- .../madoc-ts/src/routes/site/site-topic.ts | 8 +-- services/madoc-ts/src/types/schemas/topics.ts | 2 +- 9 files changed, 64 insertions(+), 78 deletions(-) diff --git a/services/madoc-ts/schemas/Topic.json b/services/madoc-ts/schemas/Topic.json index 40d273901..1d7652c50 100644 --- a/services/madoc-ts/schemas/Topic.json +++ b/services/madoc-ts/schemas/Topic.json @@ -19,18 +19,6 @@ "topicType": { "$ref": "#/definitions/TopicTypeSnippet" }, - "otherLabels": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "authorities": { "type": "array", "items": { @@ -91,6 +79,15 @@ } } }, + "subHeading": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, "heroImage": { "type": "object", "properties": { @@ -113,30 +110,12 @@ }, "description": { "type": "object", - "properties": { - "label": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "value": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } + "additionalProperties": { + "type": "array", + "items": { + "type": "string" } - }, - "required": [ - "label", - "value" - ] + } }, "featured": { "type": "array", @@ -287,7 +266,6 @@ "id", "label", "modified", - "otherLabels", "slug" ], "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts index fdc7b2eed..3bedfc984 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/types.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -1,4 +1,5 @@ import { Pagination } from '../../../types/schemas/_pagination'; +import { InternationalString } from '@iiif/presentation-3'; export interface AuthoritySnippet { id: string; @@ -120,6 +121,10 @@ export interface EntityTypeMadocResponse { modified: string; label: string; slug: string; + other_labels?: OtherLabels; + other_data?: OtherLabels; + image_url?: string; + description?: InternationalString; } // Entity - Retrieve @@ -131,11 +136,11 @@ export interface EntityMadocResponse { type: string; label: string; slug: string; - title: string[]; - description: string; + title: InternationalString; + description: InternationalString; topic_summary?: string; image_url: string; - image_caption: string[]; + image_caption: string; secondary_heading: string; authorities: { authority: string; identifier: string; uri: string }[]; } diff --git a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx index ce3887662..637d9e17c 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx @@ -54,6 +54,7 @@ const Right = styled.div` export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1Color, h2Color }) => { const { data } = useTopic(); + console.log(data); if (!data) { return null; } @@ -61,7 +62,7 @@ export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1 - {data?.label} + {data?.label[0]} {data.editorial.description && ( diff --git a/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx index 5e4b6f532..1842c4bb0 100644 --- a/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx +++ b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx @@ -28,7 +28,7 @@ export const TopicLoader: UniversalComponent = createUniversalC () => { const { data } = useTopic(); - const ctx = useMemo(() => (data ? { id: data.slug, name: data.label } : undefined), [data]); + const ctx = useMemo(() => (data ? { id: data.id, name: data.label } : undefined), [data]); return ( diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx index 061afcdeb..223a9c3ee 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx @@ -6,6 +6,7 @@ import { usePaginatedTopicTypes } from './loaders/topic-type-list-loader'; import { StaticPage } from '../features/StaticPage'; import { useTranslation } from 'react-i18next'; import { AllTopicTypeItems } from '../features/AllTopicTypeItems'; +import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; export function ViewTopicTypes() { const { t } = useTranslation(); @@ -13,6 +14,10 @@ export function ViewTopicTypes() { return ( + + + + {t('All Topic Types')} diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index dd5756aff..34577b9f8 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -1,12 +1,9 @@ import React from 'react'; import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; -import { LocaleString } from '../../shared/components/LocaleString'; import { Pagination } from '../../shared/components/Pagination'; import { SearchResults } from '../../shared/components/SearchResults'; import { useTopicItems } from '../../shared/hooks/use-topic-items'; import { Slot } from '../../shared/page-blocks/slot'; -import { useTopic } from './loaders/topic-loader'; -import { useTopicType } from './loaders/topic-type-loader'; import { TopicHero } from '../features/TopicHero'; export function ViewTopic() { @@ -14,35 +11,35 @@ export function ViewTopic() { return ( <> - - - + {/**/} + {/* */} + {/**/} - {/*
    */} - {/*

    Items in this topic

    */} - {/* */} - {/* */} - {/* */} - {/*
    {JSON.stringify(search.data, null, 2)}
    */} - {/*
    */} +
    +

    Items in this topic

    + + + + {/*
    {JSON.stringify(search.data, null, 2)}
    */} +
    ); } diff --git a/services/madoc-ts/src/routes/site/site-topic-type.ts b/services/madoc-ts/src/routes/site/site-topic-type.ts index 465a06b33..f64bb1778 100644 --- a/services/madoc-ts/src/routes/site/site-topic-type.ts +++ b/services/madoc-ts/src/routes/site/site-topic-type.ts @@ -13,7 +13,7 @@ export const siteTopicType: RouteMiddleware<{ type: string }> = async context => const response = await siteApi.authority.getEntityType(slug); const topics = await siteApi.authority.getEntities(slug); - context.response.body = compatTopicType(response, topics, slug); + context.response.body = compatTopicType(response, topics); }; function compatTopic(topic: EntitySnippetMadoc): TopicSnippet { @@ -24,13 +24,11 @@ function compatTopic(topic: EntitySnippetMadoc): TopicSnippet { } // @todo remove once in the backend. -function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMadocResponse, slug: string): TopicType { - const nuked: any = { results: undefined, url: undefined }; +function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMadocResponse): TopicType { + const nuked: any = { url: undefined, other_labels: undefined, other_data: undefined }; return { ...topicType, - label: { none: [slug] }, - otherLabels: [], // @todo no other labels given. pagination: topics.pagination, topics: topics.results.map(compatTopic), @@ -39,7 +37,7 @@ function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMad summary: { en: ['Example summary'] }, related: [], featured: [], - heroImage: null, + heroImage: { url: topicType.image_url, alt: null, overlayColor: null, transparent: null }, }, // Nuke these. ...nuked, diff --git a/services/madoc-ts/src/routes/site/site-topic.ts b/services/madoc-ts/src/routes/site/site-topic.ts index b26c829d8..6a6c65fc1 100644 --- a/services/madoc-ts/src/routes/site/site-topic.ts +++ b/services/madoc-ts/src/routes/site/site-topic.ts @@ -24,9 +24,10 @@ export const siteTopic: RouteMiddleware<{ type: string; topic: string }> = async }; function compatTopic(topic: EntityMadocResponse, topicType: EntityTypeMadocResponse): Topic { - const nuked: any = { url: undefined, type: undefined, label: undefined }; + const nuked: any = { url: undefined }; return { - ...topic, + id: topic.id, + slug: topic.slug, label: topic.title, topicType: topic.type ? { @@ -41,7 +42,8 @@ function compatTopic(topic: EntityMadocResponse, topicType: EntityTypeMadocRespo authority: auth.authority, label: { none: [auth.identifier] }, })), - + modified: topic.modified, + created: topic.created, // Mocked values. editorial: { related: [], diff --git a/services/madoc-ts/src/types/schemas/topics.ts b/services/madoc-ts/src/types/schemas/topics.ts index d9e7857de..6547d7c2a 100644 --- a/services/madoc-ts/src/types/schemas/topics.ts +++ b/services/madoc-ts/src/types/schemas/topics.ts @@ -16,7 +16,7 @@ export interface TopicType { id: string; slug: string; label: InternationalString; // From string. - otherLabels?: InternationalString[]; + // otherLabels?: InternationalString[]; pagination: Pagination; topics: TopicSnippet[]; From e80c21c9fe1638a29052a4b182bdd5e2114d895e Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 31 Jan 2023 12:08:27 +0000 Subject: [PATCH 033/191] pagintion components --- .../site/features/TopicTypeListPagination.tsx | 25 +++++++++++++++++++ .../site/features/TopicTypePagination.tsx | 24 ++++++++++++++++++ .../frontend/site/pages/view-topic-type.tsx | 11 ++------ .../frontend/site/pages/view-topic-types.tsx | 13 +++------- .../src/routes/site/site-topic-type.ts | 2 +- 5 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/TopicTypeListPagination.tsx create mode 100644 services/madoc-ts/src/frontend/site/features/TopicTypePagination.tsx diff --git a/services/madoc-ts/src/frontend/site/features/TopicTypeListPagination.tsx b/services/madoc-ts/src/frontend/site/features/TopicTypeListPagination.tsx new file mode 100644 index 000000000..799cfbd16 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/TopicTypeListPagination.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { Pagination } from '../../shared/components/Pagination'; +import { usePaginatedTopicTypes } from '../pages/loaders/topic-type-list-loader'; + +export const TopicTypeListPagination = () => { + const { data } = usePaginatedTopicTypes(); + + return ( + + ); +}; + +blockEditorFor(TopicTypeListPagination, { + type: 'default.TopicTypeListPagination', + label: 'Topic type list pagination', + anyContext: ['topicType'], + requiredContext: ['topicType'], + editor: {}, +}); diff --git a/services/madoc-ts/src/frontend/site/features/TopicTypePagination.tsx b/services/madoc-ts/src/frontend/site/features/TopicTypePagination.tsx new file mode 100644 index 000000000..96ea08e29 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/TopicTypePagination.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { Pagination } from '../../shared/components/Pagination'; +import { useTopicType } from '../pages/loaders/topic-type-loader'; + +export const TopicTypePagination = () => { + const { data } = useTopicType(); + + return ( + + ); +}; + +blockEditorFor(TopicTypePagination, { + type: 'default.TopicTypePagination', + label: 'Topic type pagination', + anyContext: ['topicType'], + requiredContext: ['topicType'], + editor: {}, +}); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx index 4a7cf11c0..da3b122c6 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -3,12 +3,9 @@ import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; import { Slot } from '../../shared/page-blocks/slot'; import { TopicTypeHero } from '../features/TopicTypeHero'; import { TopicGrid } from '../features/TopicGrid'; -import { Pagination } from '../../shared/components/Pagination'; -import { useTopicType } from './loaders/topic-type-loader'; +import { TopicTypePagination } from '../features/TopicTypePagination'; export function ViewTopicType() { - const { data } = useTopicType(); - return ( <> @@ -24,11 +21,7 @@ export function ViewTopicType() { - + {/*
    {JSON.stringify(data, null, 2)}
    */} diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx index 223a9c3ee..9df9f32dd 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-types.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import { Pagination } from '../../shared/components/Pagination'; import { Slot } from '../../shared/page-blocks/slot'; import { Heading1 } from '../../shared/typography/Heading1'; -import { usePaginatedTopicTypes } from './loaders/topic-type-list-loader'; import { StaticPage } from '../features/StaticPage'; import { useTranslation } from 'react-i18next'; import { AllTopicTypeItems } from '../features/AllTopicTypeItems'; import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; +import { TopicTypeListPagination } from '../features/TopicTypeListPagination'; export function ViewTopicTypes() { const { t } = useTranslation(); - const { data } = usePaginatedTopicTypes(); return ( @@ -20,13 +18,10 @@ export function ViewTopicTypes() { {t('All Topic Types')} + - + + diff --git a/services/madoc-ts/src/routes/site/site-topic-type.ts b/services/madoc-ts/src/routes/site/site-topic-type.ts index f64bb1778..6e1872244 100644 --- a/services/madoc-ts/src/routes/site/site-topic-type.ts +++ b/services/madoc-ts/src/routes/site/site-topic-type.ts @@ -25,7 +25,7 @@ function compatTopic(topic: EntitySnippetMadoc): TopicSnippet { // @todo remove once in the backend. function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMadocResponse): TopicType { - const nuked: any = { url: undefined, other_labels: undefined, other_data: undefined }; + const nuked: any = { url: undefined }; return { ...topicType, From 7d82f9dfb4a4b69b586a8e715a462898f74a6056 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 13 Feb 2023 15:15:37 +0000 Subject: [PATCH 034/191] update some BE tyepes --- docker-compose.yml | 4 ++-- services/madoc-ts/schemas/TopicType.json | 9 +++++++++ .../extensions/enrichment/authority/types.ts | 8 ++++++-- .../src/extensions/enrichment/extension.ts | 13 +++++++++++++ .../authority/entity-type/list-entity-types.tsx | 3 --- .../src/frontend/site/features/TopicGrid.tsx | 17 ++++++++--------- .../src/frontend/site/features/TopicHero.tsx | 5 ++--- .../src/frontend/site/pages/view-topic.tsx | 6 +++--- .../madoc-ts/src/routes/site/site-topic-type.ts | 14 ++++++++++++-- .../madoc-ts/src/routes/site/site-topics.ts | 1 - services/madoc-ts/src/types/schemas/topics.ts | 14 +++++++++----- 11 files changed, 64 insertions(+), 30 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ce4b3003d..5a6e7d6f6 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -231,8 +231,8 @@ services: - gateway-redis volumes: - ./services/enrichment/app:/app:delegated - ports: - - 5020:500 +# ports: +# - 5020:5000 okra: image: digirati/okra:latest diff --git a/services/madoc-ts/schemas/TopicType.json b/services/madoc-ts/schemas/TopicType.json index 182c07faa..2b1536047 100644 --- a/services/madoc-ts/schemas/TopicType.json +++ b/services/madoc-ts/schemas/TopicType.json @@ -65,6 +65,15 @@ } } }, + "description": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, "heroImage": { "type": "object", "properties": { diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts index 3bedfc984..ef530ddea 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/types.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -98,7 +98,11 @@ export interface EntitySnippetMadoc { modified: string; type: string; label: string; + type_slug: string; + type_other_labels: InternationalString; slug: string; + title: InternationalString; + image_url: string; } // list of entity types @@ -121,8 +125,8 @@ export interface EntityTypeMadocResponse { modified: string; label: string; slug: string; - other_labels?: OtherLabels; - other_data?: OtherLabels; + other_labels?: InternationalString; + other_data?: any; image_url?: string; description?: InternationalString; } diff --git a/services/madoc-ts/src/extensions/enrichment/extension.ts b/services/madoc-ts/src/extensions/enrichment/extension.ts index 6a8127263..fbd994347 100644 --- a/services/madoc-ts/src/extensions/enrichment/extension.ts +++ b/services/madoc-ts/src/extensions/enrichment/extension.ts @@ -1,6 +1,8 @@ import { Topic, TopicType, TopicTypeListResponse } from '../../types/schemas/topics'; import { BaseDjangoExtension } from './base-django-extension'; import { EnrichmentIndexPayload } from './types'; +import { ApiKey } from '../../types/api-key'; +import {SearchQuery, SearchResponse} from "../../types/search"; export class EnrichmentExtension extends BaseDjangoExtension { // /api/madoc/indexable_data/ @@ -41,6 +43,17 @@ export class EnrichmentExtension extends BaseDjangoExtension { return this.api.request(`/api/enrichment/entity/${id}/`); } + getTopicItems(query: SearchQuery, page = 1, madoc_id?: string) { + return this.api.request(`/madoc/api/search`, { + method: 'POST', + body: { + ...query, + page, + madoc_id, + }, + }); + } + getAllEnrichmentTasks(page = 1) { return this.api.request(`/api/enrichment/task_log?page=${page}`); } diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx index c638267b7..96ef16a51 100644 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/list-entity-types.tsx @@ -11,9 +11,6 @@ export function ListEntityTypes() { const { data } = usePaginatedData>(ListEntityTypes); // @todo pagination. - - console.log(data); - return (
    List entity type s diff --git a/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx index dc232632b..da26b0ac0 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx @@ -5,7 +5,7 @@ import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for import { useRelativeLinks } from '../hooks/use-relative-links'; import { useTranslation } from 'react-i18next'; import { LocaleString, useCreateLocaleString } from '../../shared/components/LocaleString'; -import { useTopicType } from '../pages/loaders/topic-type-loader';; +import { useTopicType } from '../pages/loaders/topic-type-loader'; import { ImageStripBox } from '../../shared/atoms/ImageStrip'; import { CroppedImage } from '../../shared/atoms/Images'; import { SingleLineHeading3, Subheading3 } from '../../shared/typography/Heading3'; @@ -23,12 +23,12 @@ const Pill = styled.div` display: inline-block; `; -export function TopicGrid(props: { +export const TopicGrid: React.FC<{ background?: string; textColor?: string; cardBorder?: string; imageStyle?: string; -}) { +}> = ({ background = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B', imageStyle = 'covered' }) => { const { data } = useTopicType(); const items = data?.topics; const createLocaleString = useCreateLocaleString(); @@ -40,14 +40,13 @@ export function TopicGrid(props: { const renderTopicSnippet = (topic: TopicSnippet) => { return ( - - - {/* todo await BE */} - {!topic.thumbnail ? ( + + + {topic.thumbnail?.url ? ( {createLocaleString(topic.label, ) : null} @@ -58,7 +57,7 @@ export function TopicGrid(props: { 123 Objects PART OF - {topic.slug} + {topic.topicType?.slug}
    ); diff --git a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx index 637d9e17c..ac16e94dd 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx @@ -54,7 +54,6 @@ const Right = styled.div` export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1Color, h2Color }) => { const { data } = useTopic(); - console.log(data); if (!data) { return null; } @@ -62,12 +61,12 @@ export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1 - {data?.label[0]} + {data?.label} {data.editorial.description && ( - {data.editorial?.description[0]} + {data.editorial?.description} )} diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 34577b9f8..6da98dd93 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -11,9 +11,9 @@ export function ViewTopic() { return ( <> - {/**/} - {/* */} - {/**/} + + + diff --git a/services/madoc-ts/src/routes/site/site-topic-type.ts b/services/madoc-ts/src/routes/site/site-topic-type.ts index 6e1872244..73a9be20c 100644 --- a/services/madoc-ts/src/routes/site/site-topic-type.ts +++ b/services/madoc-ts/src/routes/site/site-topic-type.ts @@ -5,6 +5,7 @@ import { } from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; import { TopicSnippet, TopicType } from '../../types/schemas/topics'; +import { InternationalString } from '@iiif/presentation-3'; export const siteTopicType: RouteMiddleware<{ type: string }> = async context => { const { siteApi } = context.state; @@ -17,24 +18,33 @@ export const siteTopicType: RouteMiddleware<{ type: string }> = async context => }; function compatTopic(topic: EntitySnippetMadoc): TopicSnippet { + const nuked: any = { url: undefined }; return { ...topic, - label: { none: [topic.label] }, + label: topic.title, + topicType: { + slug: topic.type_slug, + label: topic.type_other_labels, + }, + thumbnail: { url: topic.image_url, alt: 'todo' }, + totalObjects: 0, + ...nuked, }; } // @todo remove once in the backend. function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMadocResponse): TopicType { const nuked: any = { url: undefined }; - return { ...topicType, + label: { none: [topicType.label] }, pagination: topics.pagination, topics: topics.results.map(compatTopic), // Mocked editorial editorial: { summary: { en: ['Example summary'] }, + description: topicType.description, related: [], featured: [], heroImage: { url: topicType.image_url, alt: null, overlayColor: null, transparent: null }, diff --git a/services/madoc-ts/src/routes/site/site-topics.ts b/services/madoc-ts/src/routes/site/site-topics.ts index fdfb26b4e..fff8ae1a5 100644 --- a/services/madoc-ts/src/routes/site/site-topics.ts +++ b/services/madoc-ts/src/routes/site/site-topics.ts @@ -1,5 +1,4 @@ import { EnrichmentEntitySnippet, EntitiesMadocResponse } from '../../extensions/enrichment/authority/types'; -import { DjangoPagination } from '../../extensions/enrichment/types'; import { RouteMiddleware } from '../../types/route-middleware'; import { Pagination } from '../../types/schemas/_pagination'; import { TopicSnippet } from '../../types/schemas/topics'; diff --git a/services/madoc-ts/src/types/schemas/topics.ts b/services/madoc-ts/src/types/schemas/topics.ts index 6547d7c2a..308bd9150 100644 --- a/services/madoc-ts/src/types/schemas/topics.ts +++ b/services/madoc-ts/src/types/schemas/topics.ts @@ -16,13 +16,14 @@ export interface TopicType { id: string; slug: string; label: InternationalString; // From string. - // otherLabels?: InternationalString[]; + otherLabels?: InternationalString[]; pagination: Pagination; topics: TopicSnippet[]; // @todo other presentational properties. All optional editorial: { summary?: InternationalString; + description?: InternationalString; heroImage?: { url: string; alt?: string; @@ -38,10 +39,13 @@ export interface TopicSnippet { id: string; slug: string; label: InternationalString; // From string. - topicType?: TopicTypeSnippet; - - // @todo other presentation properties. All optional. - thumbnail?: { url: string; alt?: string }; + created: string; + modified: string; + topicType?: { + slug: string; + label: InternationalString; + }; + thumbnail?: { url?: string; alt?: string }; totalObjects?: number; } From fc8e04d2468244bb93053751f514d4cc7d3e9fb3 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 14 Feb 2023 09:25:45 +0000 Subject: [PATCH 035/191] docker-compsoe changes --- docker-compose.enrichment.yml | 8 ++++---- docker-compose.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.enrichment.yml b/docker-compose.enrichment.yml index 93e87e0f1..6e789b9ff 100755 --- a/docker-compose.enrichment.yml +++ b/docker-compose.enrichment.yml @@ -29,7 +29,7 @@ services: links: - shared-postgres - gateway-redis - volumes: - - ./services/enrichment/app:/app:delegated - ports: - - 5020:5000 +# volumes: +# - ./services/enrichment/app:/app:delegated +# ports: +# - 5020:5000 diff --git a/docker-compose.yml b/docker-compose.yml index 5a6e7d6f6..061d8b549 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -231,8 +231,8 @@ services: - gateway-redis volumes: - ./services/enrichment/app:/app:delegated -# ports: -# - 5020:5000 + ports: + - 5020:5000 okra: image: digirati/okra:latest From de341f59a8811159c87c82a0c9290be65e68a88f Mon Sep 17 00:00:00 2001 From: Stephen Fraser Date: Fri, 17 Feb 2023 10:41:37 +0000 Subject: [PATCH 036/191] Updates to topic formats / types / mapping / models --- .env | 2 +- services/madoc-ts/package.json | 2 +- .../MediaExplorer/MediaExplorer.tsx | 67 ++++++- .../extensions/enrichment/authority/types.ts | 176 +++++++++++++++--- .../src/extensions/enrichment/extension.ts | 10 +- .../src/extensions/enrichment/models.ts | 26 +++ .../entity-type/entity-type-model.ts | 8 - .../authority/entity-type/entity-type.tsx | 2 +- .../authority/entity/entity-model.ts | 19 -- .../pages/topics/create-new-topic-type.tsx | 32 ++-- .../admin/pages/topics/edit-topic-type.tsx | 66 +++++++ .../src/frontend/admin/pages/topics/index.tsx | 6 + .../admin/pages/topics/list-topic-items.tsx | 4 +- .../admin/pages/topics/manage-topic-type.tsx | 4 + .../capture-models/plugin-api/global-store.ts | 3 +- .../types/capture-model-shorthand.ts | 29 +++ .../shared/capture-models/types/custom.ts | 3 +- .../capture-models/types/field-types.ts | 4 +- .../frontend/shared/hooks/use-topic-items.ts | 9 +- .../frontend/site/features/TopicTypeHero.tsx | 4 +- .../src/frontend/site/pages/view-topic.tsx | 4 +- .../src/routes/site/site-topic-type.ts | 45 +---- .../src/routes/site/site-topic-types.ts | 20 +- .../madoc-ts/src/routes/site/site-topic.ts | 50 +---- .../src/routes/topics/topic-compat.ts | 38 ++++ .../routes/topics/topic-type-autocomplete.ts | 5 +- services/madoc-ts/src/types/schemas/topics.ts | 84 ++------- setup.sh | 26 +++ 28 files changed, 472 insertions(+), 276 deletions(-) create mode 100644 services/madoc-ts/src/extensions/enrichment/models.ts delete mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts delete mode 100644 services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts create mode 100644 services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/types/capture-model-shorthand.ts create mode 100644 services/madoc-ts/src/routes/topics/topic-compat.ts create mode 100755 setup.sh diff --git a/.env b/.env index eebd4f302..a95f6d83f 100755 --- a/.env +++ b/.env @@ -36,7 +36,7 @@ SMTP_USER=project.1 SMTP_PASSWORD=secret.1 MAIL_FROM_USER="Madoc local " -MADOC_INSTALLATION_CODE='$2b$14$eofdZp3nY.HyK68a9zCfoOs3fuphxHRAR/KhckFm.8Qi8sEmgMcCK' +MADOC_INSTALLATION_CODE='$2b$14$2nHi7.Ei8iywno6osjuSruDOYZAzp20KE4DhHiGqwKBb5J8Io7Zxi' # HTTPS # LOCAL PORT FOR HTTPS, CAN BE CHANGED diff --git a/services/madoc-ts/package.json b/services/madoc-ts/package.json index 9961d0409..327a3b8d2 100644 --- a/services/madoc-ts/package.json +++ b/services/madoc-ts/package.json @@ -30,7 +30,7 @@ "unlink:model-editor": "yarn unlink \"@capture-models/editor\" && yarn install --force", "link:model-helpers": "yarn link \"@capture-models/helpers\"", "unlink:model-helpers": "yarn unlink \"@capture-models/helpers\" && yarn install --force", - "shim-modules": "rm -rf ./dist/node_modules && cp -R ./shim-modules/node_modules ./dist/node_modules", + "shim-modules": "mkdirp -p dist && rm -rf ./dist/node_modules && cp -R ./shim-modules/node_modules ./dist/node_modules", "extract:api": "tsc -p ./tsconfig.types.json" }, "browserslist": "> 0.25%, not dead", diff --git a/services/madoc-ts/src/extensions/capture-models/MediaExplorer/MediaExplorer.tsx b/services/madoc-ts/src/extensions/capture-models/MediaExplorer/MediaExplorer.tsx index c298be95b..5fcc1d720 100644 --- a/services/madoc-ts/src/extensions/capture-models/MediaExplorer/MediaExplorer.tsx +++ b/services/madoc-ts/src/extensions/capture-models/MediaExplorer/MediaExplorer.tsx @@ -12,11 +12,17 @@ export type MediaExplorerProps = { id: string; label: string; type: string; - value: { - id: string; - image: string; - thumbnail: string; - } | null; + value: + | string + | { + id: string; + image: string; + thumbnail: string; + } + | null; + + valueAsString?: boolean; + onlyThumbnail?: boolean; }; export const MediaExplorer: React.FC @@ -68,9 +74,17 @@ export const MediaExplorer: React.FC ( - props.updateValue({ id: media.id, image: media.publicLink, thumbnail: media.thumbnail }) - } + onClick={() => { + if (props.valueAsString) { + if (props.onlyThumbnail) { + props.updateValue(media.thumbnail as any); + } else { + props.updateValue(media.publicLink as any); + } + } else { + props.updateValue({ id: media.id, image: media.publicLink, thumbnail: media.thumbnail }); + } + }} > {media.thumbnail ? thumb : null} {media.displayName} @@ -86,3 +100,36 @@ export const MediaExplorer: React.FC ); }; + +function parseChosenMedia( + media: + | null + | string + | { + id: string; + image: string; + thumbnail: string; + } +): null | { id: string; image: string; thumbnail: string } { + if (!media) { + return null; + } + + if (typeof media === 'string') { + const parsed = /public\/storage\/urn:madoc:site:(\d)+\/media\/public\/([A-Za-z0-9-]+)\/(.*)/.exec(media); + if (!parsed) { + return null; + } + const [, siteId, imageId, fileName] = parsed; + const [, ...parts] = fileName.split('.').reverse(); + const fileNameWithoutExtension = parts.reverse().join('.'); + + return { + id: imageId, + image: `/public/storage/urn:madoc:site:${siteId}/media/public/${imageId}/${fileName}`, + thumbnail: `/public/storage/urn:madoc:site:${siteId}/media/public/${imageId}/256/${fileNameWithoutExtension}.jpg`, + }; + } + + return null; +} diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts index ef530ddea..21160a7ab 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/types.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -14,13 +14,78 @@ export interface Authority extends AuthoritySnippet { } export interface EnrichmentEntitySnippet { + /** + * URL of the Entity (won't resolve due to Gateway) + */ url: string; + + /** + * Unique identifier of the Entity (UUID) + */ id: string; + + /** + * Creation time of the Entity (ISO Timestamp) + */ created: string; + /** + * Last modification time of the Entity (ISO Timestamp) + */ modified: string; - type: string; + /** + * Unique label - not typically for display. For example, it might come from an external vocab with all caps: "TRIBE" + * and the label to display to the user may be simply "Tribe" + */ label: string; + /** + * A unique slug, derived from the label + */ slug: string; + + /** + * [SpaCy NER type](https://github.com/digirati-co-uk/madoc-enrichment/blob/main/endpoint_docs.md#spacy-named-entity-recognition-ner-types) + * + * PERSON - People, including fictional. + * NORP - Nationalities or religious or political groups. + * FAC - Buildings, airports, highways, bridges, etc. + * ORG - Companies, agencies, institutions, etc. + * GPE - Countries, cities, states. + * LOC - Non-GPE locations, mountain ranges, bodies of water. + * PRODUCT - Objects, vehicles, foods, etc. (Not services.) + * EVENT - Named hurricanes, battles, wars, sports events, etc. + * WORK_OF_ART - Titles of books, songs, etc. + * LAW - Named documents made into laws. + * LANGUAGE - Any named language. + * DATE - Absolute or relative dates or periods. + * TIME - Times smaller than a day. + * PERCENT - Percentage, including "%". + * MONEY - Monetary values, including unit. + * QUANTITY - Measurements, as of weight or distance. + * ORDINAL - "first", "second", etc. + * CARDINAL - Numerals that do not fall under another type + */ + type: SpaCyNERType | null; + + /** + * Slug of the Entity's Type. (for navigation) + */ + type_slug: string; + + /** + * Readable labels for the Entity's Type. + */ + type_other_labels: InternationalString; + + /** + * Readable title for the Entity. + */ + title: InternationalString; + + /** + * Unknown image url or list of urls. + * @todo will change. + */ + image_url: string | string[]; } export interface EnrichmentEntityAuthority { @@ -30,15 +95,66 @@ export interface EnrichmentEntityAuthority { identifier: string; } +/** + * Entity - List item + * + * Documentation: https://github.com/digirati-co-uk/madoc-enrichment/blob/main/endpoint_docs.md#entity---list + */ export interface EnrichmentEntityTypeSnippet { + /** + * URL of the Entity (won't resolve due to Gateway) + */ url: string; + + /** + * Unique identifier of the Entity (UUID) + */ id: string; + + /** + * Creation time of the Entity (ISO Timestamp) + */ created: string; + /** + * Last modification time of the Entity (ISO Timestamp) + */ modified: string; + /** + * Unique label - not typically for display. For example, it might come from an external vocab with all caps: "TRIBE" + * and the label to display to the user may be simply "Tribe" + */ label: string; + /** + * A unique slug, derived from the label + */ slug: string; + + /** + * Readable labels for the Entity's Type. + */ + other_labels: InternationalString; } +export type SpaCyNERType = + | 'PERSON' + | 'NORP' + | 'FAC' + | 'ORG' + | 'GPE' + | 'LOC' + | 'PRODUCT' + | 'EVENT' + | 'WORK_OF_ART' + | 'LAW' + | 'LANGUAGE' + | 'DATE' + | 'TIME' + | 'PERCENT' + | 'MONEY' + | 'QUANTITY' + | 'ORDINAL' + | 'CARDINAL'; + // @todo probably will change. type OtherLabels = Array<{ value: string; @@ -48,7 +164,14 @@ type OtherLabels = Array<{ export interface EnrichmentEntityType extends EnrichmentEntityTypeSnippet { created: string; modified: string; - other_labels: OtherLabels; + + description: InternationalString; + + image_url: string; + + other_data: { + example_data: string; + }; } export interface ResourceTagSnippet { @@ -77,10 +200,10 @@ export interface EnrichmentEntity { id: string; label: string; }; - description: { value: string; language: string }[]; + description: InternationalString; image_url: string; label: string; - other_labels: OtherLabels; + other_labels: InternationalString; authorities: AuthoritySnippet[]; // Can't remember what this should be... other_data: { image_url: string; @@ -91,21 +214,10 @@ export interface EnrichmentEntity { created: string; } -export interface EntitySnippetMadoc { - url: string; - id: string; - created: string; - modified: string; - type: string; - label: string; - type_slug: string; - type_other_labels: InternationalString; - slug: string; - title: InternationalString; - image_url: string; -} - -// list of entity types +/** + * Entity - List + * Definition: https://github.com/digirati-co-uk/madoc-enrichment/blob/main/endpoint_docs.md#entity---list + */ export interface EntityTypesMadocResponse { pagination: Pagination; results: EnrichmentEntityTypeSnippet[]; @@ -114,7 +226,7 @@ export interface EntityTypesMadocResponse { // Entity - List, filtered by chosen Entity Type export interface EntitiesMadocResponse { pagination: Pagination; - results: EntitySnippetMadoc[]; + results: EnrichmentEntitySnippet[]; } // Entity Type - Retrieve @@ -137,14 +249,32 @@ export interface EntityMadocResponse { id: string; created: string; modified: string; + type: string; + type_slug: string; + type_other_labels: InternationalString; + label: string; slug: string; title: InternationalString; description: InternationalString; - topic_summary?: string; + topic_summary?: InternationalString; image_url: string; - image_caption: string; + image_caption: InternationalString; secondary_heading: string; - authorities: { authority: string; identifier: string; uri: string }[]; + authorities: { authority: string; id: string; url: string }[]; + + featured_resources: FeatureResource[]; + related_topics: EnrichmentEntitySnippet[]; + other_data: any; +} +interface FeatureResource { + url: string; + created: string; + modified: string; + madoc_id: string; + label: InternationalString; + thumbnail: string; + metadata: any; // ? + count: number; } diff --git a/services/madoc-ts/src/extensions/enrichment/extension.ts b/services/madoc-ts/src/extensions/enrichment/extension.ts index fbd994347..1e943e9ee 100644 --- a/services/madoc-ts/src/extensions/enrichment/extension.ts +++ b/services/madoc-ts/src/extensions/enrichment/extension.ts @@ -2,7 +2,8 @@ import { Topic, TopicType, TopicTypeListResponse } from '../../types/schemas/top import { BaseDjangoExtension } from './base-django-extension'; import { EnrichmentIndexPayload } from './types'; import { ApiKey } from '../../types/api-key'; -import {SearchQuery, SearchResponse} from "../../types/search"; +import { SearchQuery, SearchResponse } from '../../types/search'; +import { EntityTypeMadocResponse } from './authority/types'; export class EnrichmentExtension extends BaseDjangoExtension { // /api/madoc/indexable_data/ @@ -43,6 +44,13 @@ export class EnrichmentExtension extends BaseDjangoExtension { return this.api.request(`/api/enrichment/entity/${id}/`); } + upsertTopicType(topicType: Partial) { + return this.api.request(`/api/enrichment/entity_type/`, { + method: 'POST', + body: topicType, + }); + } + getTopicItems(query: SearchQuery, page = 1, madoc_id?: string) { return this.api.request(`/madoc/api/search`, { method: 'POST', diff --git a/services/madoc-ts/src/extensions/enrichment/models.ts b/services/madoc-ts/src/extensions/enrichment/models.ts new file mode 100644 index 000000000..40e31019e --- /dev/null +++ b/services/madoc-ts/src/extensions/enrichment/models.ts @@ -0,0 +1,26 @@ +import { EnrichmentEntity, EnrichmentEntityType } from './authority/types'; +import { CaptureModelShorthand } from '../../frontend/shared/capture-models/types/capture-model-shorthand'; + +export const entityTypeModel: CaptureModelShorthand = { + other_labels: { type: 'international-field', label: 'Display label' }, + label: { type: 'text-field', label: 'Canonical label' }, + description: { type: 'international-field', label: 'Description' }, + image_url: { type: 'madoc-media-explorer', label: 'Image', valueAsString: true }, + // other_data. + 'other_data.example_data': { type: 'text-field', label: 'Example data' }, +}; + +export const entityModel: CaptureModelShorthand = { + other_labels: { type: 'international-field', label: 'Display label' }, + label: { type: 'text-field', label: 'Canonical label' }, + __nested__: { + other_labels: { allowMultiple: true, label: 'Other label', pluralLabel: 'Other labels', labelledBy: 'value' }, + }, + type: { + type: 'autocomplete-field', + label: 'Topic type', + dataSource: 'madoc-api://topic-types/autocomplete?q=%', + requestInitial: true, + outputIdAsString: true, + }, +}; diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts deleted file mode 100644 index 6a7ed1d93..000000000 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type-model.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const entityTypeModel = { - __nested__: { - other_labels: { allowMultiple: true, label: 'Other label', pluralLabel: 'Other labels', labelledBy: 'value' }, - }, - label: { type: 'text-field', label: 'Topic type Label' }, - 'other_labels.value': { type: 'text-field', label: 'Label' }, - 'other_labels.language': { type: 'text-field', label: 'Language' }, -}; diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx index 74a2b0f1f..437215155 100644 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx @@ -6,7 +6,7 @@ import { useData } from '../../../../../shared/hooks/use-data'; import { Button } from '../../../../../shared/navigation/Button'; import { serverRendererFor } from '../../../../../shared/plugins/external/server-renderer-for'; import { HrefLink } from '../../../../../shared/utility/href-link'; -import { entityTypeModel } from './entity-type-model'; +import { entityTypeModel } from '../entity-type-model'; export function EntityType() { const { data } = useData<{ entity: EnrichmentEntityType; items: any }>(EntityType); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts deleted file mode 100644 index 0a576fae1..000000000 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/entity-model.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const entityModel = { - __nested__: { - other_labels: { allowMultiple: true, label: 'Other label', pluralLabel: 'Other labels', labelledBy: 'value' }, - }, - label: { type: 'text-field', label: 'Topic label' }, - 'other_labels.value': { type: 'text-field', label: 'Label' }, - 'other_labels.language': { type: 'text-field', label: 'Language' }, - type: { - type: 'autocomplete-field', - label: 'Topic type', - dataSource: 'madoc-api://topic-types/autocomplete?q=%', - requestInitial: true, - outputIdAsString: true, - }, - // type: { - // type: 'text-field', - // label: 'Entity type (uuid)', - // }, -}; diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx index 366edb3d7..21c77feb1 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx @@ -1,17 +1,21 @@ import React from 'react'; import { useMutation } from 'react-query'; -import { EnrichmentEntityType } from '../../../../extensions/enrichment/authority/types'; import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; import { useApi } from '../../../shared/hooks/use-api'; import { Button } from '../../../shared/navigation/Button'; import { HrefLink } from '../../../shared/utility/href-link'; -import { entityTypeModel } from '../enrichment/authority/entity-type/entity-type-model'; +import { CustomEditorTypes } from '../../../shared/page-blocks/custom-editor-types'; +import { entityTypeModel } from '../../../../extensions/enrichment/models'; export function CreateNewTopicType() { const api = useApi(); - const [createNewEntityType, status] = useMutation(async (data: Partial) => { - data.other_labels = (data.other_labels || []).filter(e => e.value !== ''); - return api.authority.entity_type.create(data); + const [createNewEntityType, status] = useMutation(async (data: any) => { + + // @todo can change later. + data.image_url = `${window.location.protocol}//${window.location.host}${data.image_url.publicLink || data.image_url}`; + + // data.other_labels = (data.other_labels || []).filter((e: any) => e.value !== ''); + return api.enrichment.upsertTopicType(data); }); if (status.isError) { @@ -32,14 +36,16 @@ export function CreateNewTopicType() { return (
    - { - await createNewEntityType(data); - }} - keepExtraFields - /> + + { + await createNewEntityType(data); + }} + keepExtraFields + /> +
    ); } diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx new file mode 100644 index 000000000..f9cb0974c --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { useApi } from '../../../shared/hooks/use-api'; +import { useMutation } from 'react-query'; +import { Button } from '../../../shared/navigation/Button'; +import { HrefLink } from '../../../shared/utility/href-link'; +import { CustomEditorTypes } from '../../../shared/page-blocks/custom-editor-types'; +import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; +import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; +import { models } from '../../../../extensions/enrichment/models'; + +export function EditTopicType() { + const api = useApi(); + const { data, refetch } = useTopicType(); + const [createNewEntityType, status] = useMutation(async (updatedData: any) => { + if (!data) return; + if (typeof updatedData.image_url !== 'string' || !updatedData.image_url.startsWith('http')) { + // @todo can change later. + updatedData.image_url = `${window.location.protocol}//${window.location.host}${updatedData.image_url}`; + } + + // data.other_labels = (data.other_labels || []).filter((e: any) => e.value !== ''); + const resp = api.enrichment.upsertTopicType({ id: data.id, ...updatedData }); + + + refetch() + + return resp; + }); + + if (!data) { + return
    Loading...
    ; + } + + if (status.isError) { + return
    Error...
    ; + } + + if (status.isSuccess && status.data) { + return ( +
    + Updated +
    {JSON.stringify(status.data, null, 2)}
    + +
    + ); + } + + console.log(data); + + return ( +
    + + { + await createNewEntityType(d); + }} + keepExtraFields + /> + +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/index.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/index.tsx index 422ea4877..f260309ae 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/index.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/index.tsx @@ -11,6 +11,7 @@ import { ManageTopicTypes } from './manage-topic-types'; import { ManageTopic } from './manage-topic'; import { ListTopicsInType } from './list-topics-in-type'; import { TopicDetails } from './topic-details'; +import { EditTopicType } from './edit-topic-type'; export const topicRoutes = [ { @@ -43,6 +44,11 @@ export const topicRoutes = [ index: true, element: , }, + { + path: '/topics/:topicType/_/edit', + index: true, + element: , + }, { path: '/topics/:topicType/_/create-topic', exact: true, diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx index eac2dc563..b2099c0d0 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/list-topic-items.tsx @@ -4,9 +4,11 @@ import { Pagination } from '../../../shared/components/Pagination'; import { SearchResults } from '../../../shared/components/SearchResults'; import { useTopicItems } from '../../../shared/hooks/use-topic-items'; import { EmptyState } from '../../../shared/layout/EmptyState'; +import { useParams } from 'react-router-dom'; export function ListTopicItems() { - const [{ data, isLoading, latestData }, { query, page }] = useTopicItems(); + const { topic } = useParams>(); + const [{ data, isLoading, latestData }, { query, page }] = useTopicItems(topic); if (data?.pagination.totalResults === 0) { return Nothing tagged yet; diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx index d08af5bb1..d7bcfa00f 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/manage-topic-type.tsx @@ -47,6 +47,10 @@ export function ManageTopicType() { label: t('Topics'), link: `/topics/${data?.slug || topicType}`, }, + { + label: 'Edit', + link: `/topics/${data?.slug || topicType}/_/edit`, + }, { label: 'Create topic', link: `/topics/${data?.slug || topicType}/_/create-topic`, diff --git a/services/madoc-ts/src/frontend/shared/capture-models/plugin-api/global-store.ts b/services/madoc-ts/src/frontend/shared/capture-models/plugin-api/global-store.ts index 14ac8906b..e15bb1972 100644 --- a/services/madoc-ts/src/frontend/shared/capture-models/plugin-api/global-store.ts +++ b/services/madoc-ts/src/frontend/shared/capture-models/plugin-api/global-store.ts @@ -1,6 +1,5 @@ import { BaseContent, ContentSpecification } from '../types/content-types'; -import { FieldTypeMap } from '../types/custom'; -import { BaseField, FieldSpecification } from '../types/field-types'; +import { BaseField, FieldSpecification, FieldTypeMap } from '../types/field-types'; import { UnknownRefinement } from '../types/refinements'; import { BaseSelector, SelectorSpecification } from '../types/selector-types'; import { pluginStore } from './globals'; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/types/capture-model-shorthand.ts b/services/madoc-ts/src/frontend/shared/capture-models/types/capture-model-shorthand.ts new file mode 100644 index 000000000..2b4238174 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/types/capture-model-shorthand.ts @@ -0,0 +1,29 @@ +import { FieldTypes } from './field-types'; + +type PathImpl = + Key extends string + ? T[Key] extends Record + ? | `${Key}.${PathImpl> & string}` + | `${Key}.${Exclude & string}` + : never + : never; + +type PathImpl2 = PathImpl | keyof T; + +type Path = PathImpl2 extends string | keyof T ? PathImpl2 : keyof T; + +type PathValue> = + P extends `${infer Key}.${infer Rest}` + ? Key extends keyof T + ? Rest extends Path + ? PathValue + : never + : never + : P extends keyof T + ? T[P] + : never; + +export type CaptureModelShorthand = any> = {} & Partial, FieldTypes['type'] | Partial>> & { + __entity__?: any; + __nested__?: any; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/types/custom.ts b/services/madoc-ts/src/frontend/shared/capture-models/types/custom.ts index ea3f22a0f..8b1378917 100644 --- a/services/madoc-ts/src/frontend/shared/capture-models/types/custom.ts +++ b/services/madoc-ts/src/frontend/shared/capture-models/types/custom.ts @@ -1,2 +1 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface FieldTypeMap {} + diff --git a/services/madoc-ts/src/frontend/shared/capture-models/types/field-types.ts b/services/madoc-ts/src/frontend/shared/capture-models/types/field-types.ts index d55d2738b..7df780b46 100644 --- a/services/madoc-ts/src/frontend/shared/capture-models/types/field-types.ts +++ b/services/madoc-ts/src/frontend/shared/capture-models/types/field-types.ts @@ -1,7 +1,6 @@ import { FC } from 'react'; import { BaseProperty } from './base-property'; import { CaptureModel } from './capture-model'; -import { FieldTypeMap } from './custom'; import { MapValues } from './utility'; export interface BaseField extends BaseProperty { @@ -10,6 +9,9 @@ export interface BaseField extends BaseProperty { value: any; } +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface FieldTypeMap {} + export type FieldTypes = MapValues; export type InjectedFieldProps = { diff --git a/services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts b/services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts index 11e312a95..6044d0c98 100644 --- a/services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts +++ b/services/madoc-ts/src/frontend/shared/hooks/use-topic-items.ts @@ -3,17 +3,16 @@ import { useTopic } from '../../site/pages/loaders/topic-loader'; import { useApi } from './use-api'; import { useLocationQuery } from './use-location-query'; -export function useTopicItems() { - const { data: topic } = useTopic(); +export function useTopicItems(slug: string) { const api = useApi(); const query = useLocationQuery<{ fulltext?: string; facets?: string; page?: string }>(); const page = query.page ? Number(query.page) : 1; const resp = usePaginatedQuery( - ['topic-items', { id: topic?.id, page }], + ['topic-items', { id: slug, page }], async () => { - return api.getSearchQuery({ ...query, facets: [{ type: 'entity', subtype: topic?.id }] } as any, page); + return api.getSearchQuery({ ...query, facets: [{ type: 'entity', indexable_text: slug }] } as any, page); }, - { enabled: !!topic } + { enabled: !!slug } ); return [resp, { page, query }] as const; } diff --git a/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx index 722f184b2..781751852 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx @@ -36,9 +36,9 @@ const BackgroundImage = styled.div<{ $overlay?: string }>` const TextBox = styled.div` background-color: white; position: absolute; - width: 80vw; + width: 80%; padding: 4em 6em; - left: 10vw; + left: 10%; bottom: 0; `; diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 6da98dd93..69caf3367 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -5,9 +5,11 @@ import { SearchResults } from '../../shared/components/SearchResults'; import { useTopicItems } from '../../shared/hooks/use-topic-items'; import { Slot } from '../../shared/page-blocks/slot'; import { TopicHero } from '../features/TopicHero'; +import { useParams } from 'react-router-dom'; export function ViewTopic() { - const [search, { query, page }] = useTopicItems(); + const { topic } = useParams>(); + const [search, { query, page }] = useTopicItems(topic); return ( <> diff --git a/services/madoc-ts/src/routes/site/site-topic-type.ts b/services/madoc-ts/src/routes/site/site-topic-type.ts index 73a9be20c..a639ffe38 100644 --- a/services/madoc-ts/src/routes/site/site-topic-type.ts +++ b/services/madoc-ts/src/routes/site/site-topic-type.ts @@ -1,11 +1,5 @@ -import { - EntitiesMadocResponse, - EntitySnippetMadoc, - EntityTypeMadocResponse, -} from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; -import { TopicSnippet, TopicType } from '../../types/schemas/topics'; -import { InternationalString } from '@iiif/presentation-3'; +import { compatTopicType } from '../topics/topic-compat'; export const siteTopicType: RouteMiddleware<{ type: string }> = async context => { const { siteApi } = context.state; @@ -16,40 +10,3 @@ export const siteTopicType: RouteMiddleware<{ type: string }> = async context => context.response.body = compatTopicType(response, topics); }; - -function compatTopic(topic: EntitySnippetMadoc): TopicSnippet { - const nuked: any = { url: undefined }; - return { - ...topic, - label: topic.title, - topicType: { - slug: topic.type_slug, - label: topic.type_other_labels, - }, - thumbnail: { url: topic.image_url, alt: 'todo' }, - totalObjects: 0, - ...nuked, - }; -} - -// @todo remove once in the backend. -function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMadocResponse): TopicType { - const nuked: any = { url: undefined }; - return { - ...topicType, - label: { none: [topicType.label] }, - pagination: topics.pagination, - topics: topics.results.map(compatTopic), - - // Mocked editorial - editorial: { - summary: { en: ['Example summary'] }, - description: topicType.description, - related: [], - featured: [], - heroImage: { url: topicType.image_url, alt: null, overlayColor: null, transparent: null }, - }, - // Nuke these. - ...nuked, - }; -} diff --git a/services/madoc-ts/src/routes/site/site-topic-types.ts b/services/madoc-ts/src/routes/site/site-topic-types.ts index 7346ed64e..238b07f27 100644 --- a/services/madoc-ts/src/routes/site/site-topic-types.ts +++ b/services/madoc-ts/src/routes/site/site-topic-types.ts @@ -1,6 +1,5 @@ -import { EnrichmentEntityTypeSnippet, EntityTypesMadocResponse } from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; -import { TopicTypeSnippet } from '../../types/schemas/topics'; +import { compatTopicTypes } from '../topics/topic-compat'; export const siteTopicTypes: RouteMiddleware<{ slug: string }> = async context => { const { siteApi } = context.state; @@ -8,20 +7,3 @@ export const siteTopicTypes: RouteMiddleware<{ slug: string }> = async context = const response = await siteApi.authority.getEntityTypes(); context.response.body = compatTopicTypes(response); }; - -// @todo remove once changed in backend. -function compatTopicTypeSnippet(snippet: EnrichmentEntityTypeSnippet): TopicTypeSnippet { - return { - ...snippet, - label: { none: [snippet.label] }, - }; -} - -// @todo remove once changed in backend. -function compatTopicTypes(response: EntityTypesMadocResponse) { - const { results } = response; - return { - topicTypes: results.map(compatTopicTypeSnippet), - pagination: response.pagination, - }; -} diff --git a/services/madoc-ts/src/routes/site/site-topic.ts b/services/madoc-ts/src/routes/site/site-topic.ts index 6a6c65fc1..070f4a65b 100644 --- a/services/madoc-ts/src/routes/site/site-topic.ts +++ b/services/madoc-ts/src/routes/site/site-topic.ts @@ -1,6 +1,4 @@ -import { EntityMadocResponse, EntityTypeMadocResponse } from '../../extensions/enrichment/authority/types'; import { RouteMiddleware } from '../../types/route-middleware'; -import { Topic } from '../../types/schemas/topics'; import { NotFound } from '../../utility/errors/not-found'; export const siteTopic: RouteMiddleware<{ type: string; topic: string }> = async context => { @@ -10,52 +8,10 @@ export const siteTopic: RouteMiddleware<{ type: string; topic: string }> = async const topicTypeSlug = context.params.type; const response = await siteApi.authority.getEntity(topicTypeSlug, slug); - const topicType = await siteApi.authority.getEntityType(topicTypeSlug); - context.response.body = compatTopic(response, topicType); - - if (response.type) { - if (topicTypeSlug.toLowerCase() !== response.type.toLowerCase()) { - throw new NotFound('Topic not found'); - } + if (response.type_slug !== topicTypeSlug) { + throw new NotFound(); } - // @todo validate topic-type matches. + context.response.body = response; }; - -function compatTopic(topic: EntityMadocResponse, topicType: EntityTypeMadocResponse): Topic { - const nuked: any = { url: undefined }; - return { - id: topic.id, - slug: topic.slug, - label: topic.title, - topicType: topic.type - ? { - id: topicType.id, - slug: topicType.slug, - label: { none: [topicType.label] }, - } - : undefined, - // otherLabels: topic.other_labels.map(label => ({ [label.language.slice(0, 2)]: [label.value] })), - authorities: topic.authorities.map(auth => ({ - id: auth.uri, - authority: auth.authority, - label: { none: [auth.identifier] }, - })), - modified: topic.modified, - created: topic.created, - // Mocked values. - editorial: { - related: [], - featured: [], - contributors: [], - subHeading: topic.secondary_heading, - description: topic.description, - heroImage: { url: topic.image_url, alt: topic.image_caption, overlayColor: null, transparent: null}, - summary: topic.topic_summary, - }, - - // Nuke these properties - ...nuked, - }; -} diff --git a/services/madoc-ts/src/routes/topics/topic-compat.ts b/services/madoc-ts/src/routes/topics/topic-compat.ts new file mode 100644 index 000000000..7c07ddce9 --- /dev/null +++ b/services/madoc-ts/src/routes/topics/topic-compat.ts @@ -0,0 +1,38 @@ +import { + EntitiesMadocResponse, + EntityTypeMadocResponse, + EntityTypesMadocResponse, +} from '../../extensions/enrichment/authority/types'; +import { TopicType, TopicTypeListResponse } from '../../types/schemas/topics'; +import { InternationalString } from '@iiif/presentation-3'; + +export function getLabel(snippet: { + other_labels?: InternationalString; + title?: InternationalString; + label: string; +}): InternationalString { + if (snippet.other_labels && Object.keys(snippet.other_labels).length) { + return snippet.other_labels; + } + if (snippet.title && Object.keys(snippet.title).length) { + return snippet.title; + } + return { none: [snippet.label] }; +} + +// @todo remove once in the backend. +export function compatTopicType(topicType: EntityTypeMadocResponse, topics: EntitiesMadocResponse): TopicType { + return { + ...topicType, + pagination: topics.pagination, + topics: topics.results, + }; +} + +export function compatTopicTypes(response: EntityTypesMadocResponse): TopicTypeListResponse { + const { results } = response; + return { + topicTypes: results, + pagination: response.pagination, + }; +} diff --git a/services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts b/services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts index 015acb5fe..44f90ea8e 100644 --- a/services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts +++ b/services/madoc-ts/src/routes/topics/topic-type-autocomplete.ts @@ -1,6 +1,7 @@ import { api } from '../../gateway/api.server'; import { RouteMiddleware } from '../../types/route-middleware'; import { userWithScope } from '../../utility/user-with-scope'; +import { getValue } from '@iiif/vault-helpers'; export const topicTypeAutocomplete: RouteMiddleware = async context => { const { siteId, id } = userWithScope(context, ['site.admin']); @@ -9,8 +10,8 @@ export const topicTypeAutocomplete: RouteMiddleware = async context => { context.response.body = { completions: items.results.map(item => ({ - uri: item.label, - label: item.label, + uri: item.slug, + label: getValue(item.other_labels) || item.label, })) as { uri: string; label: string; resource_class?: string; score?: number }[], }; }; diff --git a/services/madoc-ts/src/types/schemas/topics.ts b/services/madoc-ts/src/types/schemas/topics.ts index 308bd9150..fd47ac833 100644 --- a/services/madoc-ts/src/types/schemas/topics.ts +++ b/services/madoc-ts/src/types/schemas/topics.ts @@ -1,83 +1,21 @@ -import { InternationalString } from '@iiif/presentation-3'; -import { SearchResult } from '../search'; import { Pagination } from './_pagination'; +import { + EnrichmentEntitySnippet, + EnrichmentEntityTypeSnippet, + EntityMadocResponse, + EntityTypeMadocResponse, +} from '../../extensions/enrichment/authority/types'; -export interface TopicTypeSnippet { - id: string; - slug: string; - label: InternationalString; // From string. +export type TopicTypeSnippet = EnrichmentEntityTypeSnippet; - // @todo other presentational properties. All optional - thumbnail?: { url: string; alt?: string }; - totalObjects?: number; -} - -export interface TopicType { - id: string; - slug: string; - label: InternationalString; // From string. - otherLabels?: InternationalString[]; +export type TopicType = EntityTypeMadocResponse & { pagination: Pagination; topics: TopicSnippet[]; +}; - // @todo other presentational properties. All optional - editorial: { - summary?: InternationalString; - description?: InternationalString; - heroImage?: { - url: string; - alt?: string; - overlayColor?: string; - transparent?: boolean; - }; - featured?: Array; - related?: Array; - }; -} +export type TopicSnippet = EnrichmentEntitySnippet; -export interface TopicSnippet { - id: string; - slug: string; - label: InternationalString; // From string. - created: string; - modified: string; - topicType?: { - slug: string; - label: InternationalString; - }; - thumbnail?: { url?: string; alt?: string }; - totalObjects?: number; -} - -export interface Topic { - id: string; - slug: string; - label: InternationalString; // From string. - topicType?: TopicTypeSnippet; - authorities: Array<{ id: string; label: InternationalString }>; - modified: string; - created: string; - - // @todo other presentation properties. These should all be optional. - editorial: { - contributors?: Array<{ - id: string; // Madoc URN. - label: string; - }>; - summary?: InternationalString; - subHeading?: InternationalString; - heroImage?: { - url: string; - alt?: string; - overlayColor?: string; - transparent?: boolean; - } | null; - description?: InternationalString; - // @todo search result format MAY change, hopefully not. - featured?: Array; - related?: Array; - }; -} +export type Topic = EntityMadocResponse; export interface TopicTypeListResponse { topicTypes: TopicTypeSnippet[]; diff --git a/setup.sh b/setup.sh new file mode 100755 index 000000000..f362da6eb --- /dev/null +++ b/setup.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e + +## var +mkdir -p -m 777 var +mkdir -p -m 777 var/certs + +## Installer. +(cd ./services/madoc-ts && yarn install --frozen-lockfile); +(cd ./services/madoc-ts && yarn build); + + +## Certs +mkcert -install -cert-file var/certs/local-cert.pem -key-file var/certs/local-key.pem madoc.local + +## Docker +docker-compose build; + +## Validate certs +echo "First run"; +echo " docker-compose up -d" +echo ""; +echo "Then: " +echo " - Open https://madoc.local/ in browsers you will test with, and accept the certificate"; +echo " - Open https://madoc.local:3088/ in browsers you will test with, and accept the certificate"; From 6d6f7d2ac466d71cab42912b103ca0663001618c Mon Sep 17 00:00:00 2001 From: Heather Date: Fri, 17 Feb 2023 13:44:30 +0000 Subject: [PATCH 037/191] fix build errors --- services/madoc-ts/schemas/Topic.json | 318 +++++------------- services/madoc-ts/schemas/TopicSnippet.json | 2 +- services/madoc-ts/schemas/TopicType.json | 121 ++----- .../schemas/TopicTypeListResponse.json | 2 +- .../madoc-ts/schemas/TopicTypeSnippet.json | 45 +-- .../authority/entity-type/entity-type.tsx | 5 +- .../authority/entity-type/new-entity-type.tsx | 3 +- .../authority/entity/new-entity.tsx | 2 +- .../admin/pages/topics/create-new-topic.tsx | 2 +- .../admin/pages/topics/edit-topic-type.tsx | 4 +- .../shared/components/Breadcrumbs.tsx | 2 +- .../site/pages/loaders/topic-loader.tsx | 2 +- 12 files changed, 151 insertions(+), 357 deletions(-) diff --git a/services/madoc-ts/schemas/Topic.json b/services/madoc-ts/schemas/Topic.json index 1d7652c50..affe65093 100644 --- a/services/madoc-ts/schemas/Topic.json +++ b/services/madoc-ts/schemas/Topic.json @@ -1,13 +1,58 @@ { "type": "object", "properties": { + "url": { + "type": "string" + }, "id": { "type": "string" }, - "slug": { + "created": { + "type": "string" + }, + "modified": { + "type": "string" + }, + "type": { + "type": "string" + }, + "type_slug": { "type": "string" }, + "type_other_labels": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, "label": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "topic_summary": { "type": "object", "additionalProperties": { "type": "array", @@ -16,257 +61,76 @@ } } }, - "topicType": { - "$ref": "#/definitions/TopicTypeSnippet" + "image_url": { + "type": "string" + }, + "image_caption": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "secondary_heading": { + "type": "string" }, "authorities": { "type": "array", "items": { "type": "object", "properties": { + "authority": { + "type": "string" + }, "id": { "type": "string" }, - "label": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } + "url": { + "type": "string" } }, "required": [ + "authority", "id", - "label" + "url" ] } }, - "modified": { - "type": "string" - }, - "created": { - "type": "string" + "featured_resources": { + "type": "array", + "items": { + "$ref": "#/definitions/FeatureResource" + } }, - "editorial": { - "type": "object", - "properties": { - "contributors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "label": { - "type": "string" - } - }, - "required": [ - "id", - "label" - ] - } - }, - "summary": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "subHeading": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "heroImage": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "alt": { - "type": "string" - }, - "overlayColor": { - "type": "string" - }, - "transparent": { - "type": "boolean" - } - }, - "required": [ - "url" - ] - }, - "description": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "featured": { - "type": "array", - "items": { - "type": "object", - "properties": { - "url": { - "description": "Internal url for Search API - shouldn't be shown to the user, but can be used for unqiueness", - "type": "string" - }, - "resource_id": { - "description": "Madoc identifier, usually in the form `urn:madoc:TYPE:ID`", - "type": "string" - }, - "resource_type": { - "description": "Type of resource (Collection, Manifest or Canvas etc.)", - "type": "string" - }, - "madoc_thumbnail": { - "description": "Optional thumbnail of resource", - "type": "string" - }, - "thumbnail": { - "type": "string" - }, - "label": { - "description": "Label for the resource from the search result", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "contexts": { - "description": "List of contexts for the resource", - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ] - } - } - ] - }, - "hits": { - "description": "List of hits.", - "type": "array", - "items": { - "description": "Represents a single search hit, there may be multiple of these per resource.", - "type": "object", - "properties": { - "type": { - "description": "Type of metadata returned", - "type": "string" - }, - "subtype": { - "description": "Subtype of metadata returned", - "type": "string" - }, - "snippet": { - "description": "Preview of search result with highlighted search as HTML", - "type": "string" - }, - "language": { - "description": "ISO language string", - "type": "string" - }, - "rank": { - "type": "number" - }, - "bounding_boxes": { - "description": "Bounding boxes", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "number" - }, - { - "type": "number" - }, - { - "type": "number" - }, - { - "type": "number" - } - ], - "minItems": 4, - "maxItems": 4 - } - } - }, - "required": [ - "language", - "rank", - "snippet", - "subtype", - "type" - ] - } - } - }, - "required": [ - "contexts", - "hits", - "label", - "resource_id", - "resource_type", - "url" - ] - } - }, - "related": { - "type": "array", - "items": { - "$ref": "#/definitions/TopicSnippet" - } - } + "related_topics": { + "type": "array", + "items": { + "$ref": "#/definitions/EnrichmentEntitySnippet" } - } + }, + "other_data": {} }, "required": [ "authorities", "created", - "editorial", + "description", + "featured_resources", "id", + "image_caption", + "image_url", "label", "modified", - "slug" + "other_data", + "related_topics", + "secondary_heading", + "slug", + "title", + "type", + "type_other_labels", + "type_slug", + "url" ], "$schema": "http://json-schema.org/draft-07/schema#" } \ No newline at end of file diff --git a/services/madoc-ts/schemas/TopicSnippet.json b/services/madoc-ts/schemas/TopicSnippet.json index 521f0b5e6..38ad09844 100644 --- a/services/madoc-ts/schemas/TopicSnippet.json +++ b/services/madoc-ts/schemas/TopicSnippet.json @@ -1,4 +1,4 @@ { - "$ref": "#/definitions/TopicSnippet", + "$ref": "#/definitions/EnrichmentEntitySnippet", "$schema": "http://json-schema.org/draft-07/schema#" } \ No newline at end of file diff --git a/services/madoc-ts/schemas/TopicType.json b/services/madoc-ts/schemas/TopicType.json index 2b1536047..c827433dc 100644 --- a/services/madoc-ts/schemas/TopicType.json +++ b/services/madoc-ts/schemas/TopicType.json @@ -1,121 +1,42 @@ { - "type": "object", - "properties": { - "id": { - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/EntityTypeMadocResponse" }, - "slug": { - "type": "string" - }, - "label": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "otherLabels": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "pagination": { + { "type": "object", "properties": { - "page": { - "type": "number" - }, - "totalResults": { - "type": "number" - }, - "totalPages": { - "type": "number" - } - }, - "required": [ - "page", - "totalPages", - "totalResults" - ] - }, - "topics": { - "type": "array", - "items": { - "$ref": "#/definitions/TopicSnippet" - } - }, - "editorial": { - "type": "object", - "properties": { - "summary": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "description": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "heroImage": { + "pagination": { "type": "object", "properties": { - "url": { - "type": "string" + "page": { + "type": "number" }, - "alt": { - "type": "string" + "totalResults": { + "type": "number" }, - "overlayColor": { - "type": "string" - }, - "transparent": { - "type": "boolean" + "totalPages": { + "type": "number" } }, "required": [ - "url" + "page", + "totalPages", + "totalResults" ] }, - "featured": { - "type": "array", - "items": { - "$ref": "#/definitions/TopicSnippet" - } - }, - "related": { + "topics": { "type": "array", "items": { - "$ref": "#/definitions/TopicTypeSnippet" + "$ref": "#/definitions/EnrichmentEntitySnippet" } } - } + }, + "required": [ + "pagination", + "topics" + ] } - }, - "required": [ - "editorial", - "id", - "label", - "pagination", - "slug", - "topics" ], "$schema": "http://json-schema.org/draft-07/schema#" } \ No newline at end of file diff --git a/services/madoc-ts/schemas/TopicTypeListResponse.json b/services/madoc-ts/schemas/TopicTypeListResponse.json index 27195a895..5377d5246 100644 --- a/services/madoc-ts/schemas/TopicTypeListResponse.json +++ b/services/madoc-ts/schemas/TopicTypeListResponse.json @@ -4,7 +4,7 @@ "topicTypes": { "type": "array", "items": { - "$ref": "#/definitions/TopicTypeSnippet" + "$ref": "#/definitions/EnrichmentEntityTypeSnippet" } }, "pagination": { diff --git a/services/madoc-ts/schemas/TopicTypeSnippet.json b/services/madoc-ts/schemas/TopicTypeSnippet.json index bd56bf350..f8c3666f9 100644 --- a/services/madoc-ts/schemas/TopicTypeSnippet.json +++ b/services/madoc-ts/schemas/TopicTypeSnippet.json @@ -1,13 +1,33 @@ { + "description": "Entity - List item\n\nDocumentation: https://github.com/digirati-co-uk/madoc-enrichment/blob/main/endpoint_docs.md#entity---list", "type": "object", "properties": { + "url": { + "description": "URL of the Entity (won't resolve due to Gateway)", + "type": "string" + }, "id": { + "description": "Unique identifier of the Entity (UUID)", "type": "string" }, - "slug": { + "created": { + "description": "Creation time of the Entity (ISO Timestamp)", + "type": "string" + }, + "modified": { + "description": "Last modification time of the Entity (ISO Timestamp)", "type": "string" }, "label": { + "description": "Unique label - not typically for display. For example, it might come from an external vocab with all caps: \"TRIBE\"\nand the label to display to the user may be simply \"Tribe\"", + "type": "string" + }, + "slug": { + "description": "A unique slug, derived from the label", + "type": "string" + }, + "other_labels": { + "description": "Readable labels for the Entity's Type.", "type": "object", "additionalProperties": { "type": "array", @@ -15,29 +35,16 @@ "type": "string" } } - }, - "thumbnail": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "alt": { - "type": "string" - } - }, - "required": [ - "url" - ] - }, - "totalObjects": { - "type": "number" } }, "required": [ + "created", "id", "label", - "slug" + "modified", + "other_labels", + "slug", + "url" ], "$schema": "http://json-schema.org/draft-07/schema#" } \ No newline at end of file diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx index 437215155..faace5e83 100644 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/entity-type.tsx @@ -6,7 +6,7 @@ import { useData } from '../../../../../shared/hooks/use-data'; import { Button } from '../../../../../shared/navigation/Button'; import { serverRendererFor } from '../../../../../shared/plugins/external/server-renderer-for'; import { HrefLink } from '../../../../../shared/utility/href-link'; -import { entityTypeModel } from '../entity-type-model'; +import { entityTypeModel } from '../../../../../../extensions/enrichment/models'; export function EntityType() { const { data } = useData<{ entity: EnrichmentEntityType; items: any }>(EntityType); @@ -26,7 +26,8 @@ export function EntityType() { ); } - return ( + // @ts-ignore + return (

    {entity?.label || '...'}

    diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx index 8c67d1871..cebf3acc0 100644 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity-type/new-entity-type.tsx @@ -3,11 +3,12 @@ import { useMutation } from 'react-query'; import { EnrichmentEntityType } from '../../../../../../extensions/enrichment/authority/types'; import { EditShorthandCaptureModel } from '../../../../../shared/capture-models/EditorShorthandCaptureModel'; import { useApi } from '../../../../../shared/hooks/use-api'; -import { entityTypeModel } from './entity-type-model'; +import { entityTypeModel } from '../../../../../../extensions/enrichment/models'; export function NewEntityType() { const api = useApi(); const [createNewEntityType, status] = useMutation(async (data: Partial) => { + // @ts-ignore data.other_labels = (data.other_labels || []).filter(e => e.value !== ''); return api.authority.entity_type.create(data); }); diff --git a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx index 6927c56c9..ffd425422 100644 --- a/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/enrichment/authority/entity/new-entity.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useMutation } from 'react-query'; import { EditShorthandCaptureModel } from '../../../../../shared/capture-models/EditorShorthandCaptureModel'; import { useApi } from '../../../../../shared/hooks/use-api'; -import { entityModel } from './entity-model'; +import { entityModel } from '../../../../../../extensions/enrichment/models'; export function NewEntity() { const api = useApi(); diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx index cba93f337..d0d65bdf3 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx @@ -7,7 +7,7 @@ import { Button } from '../../../shared/navigation/Button'; import { HrefLink } from '../../../shared/utility/href-link'; import { useRouteContext } from '../../../site/hooks/use-route-context'; import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; -import { entityModel } from '../enrichment/authority/entity/entity-model'; +import { entityModel } from '../../../../extensions/enrichment/models'; export function CreateNewTopic() { const api = useApi(); diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx index f9cb0974c..9042ecaf0 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx @@ -6,7 +6,7 @@ import { HrefLink } from '../../../shared/utility/href-link'; import { CustomEditorTypes } from '../../../shared/page-blocks/custom-editor-types'; import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; import { useTopicType } from '../../../site/pages/loaders/topic-type-loader'; -import { models } from '../../../../extensions/enrichment/models'; +import { entityTypeModel } from '../../../../extensions/enrichment/models'; export function EditTopicType() { const api = useApi(); @@ -53,7 +53,7 @@ export function EditTopicType() {
    { await createNewEntityType(d); diff --git a/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx b/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx index dac68dd3f..4adb116ca 100644 --- a/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx +++ b/services/madoc-ts/src/frontend/shared/components/Breadcrumbs.tsx @@ -17,7 +17,7 @@ type BreadcrumbContextType = { canvas?: { name: InternationalString; id: number }; task?: { name: string; id: string }; subpage?: { name: InternationalString; path: string }; - topicType?: { name: InternationalString; id: string }; + topicType?: { name: InternationalString | string; id: string }; topic?: { name: InternationalString; id: string }; }; diff --git a/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx index 1842c4bb0..1c65890ab 100644 --- a/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx +++ b/services/madoc-ts/src/frontend/site/pages/loaders/topic-loader.tsx @@ -28,7 +28,7 @@ export const TopicLoader: UniversalComponent = createUniversalC () => { const { data } = useTopic(); - const ctx = useMemo(() => (data ? { id: data.id, name: data.label } : undefined), [data]); + const ctx = useMemo(() => (data ? { id: data.id, name: data.title } : undefined), [data]); return ( From 892fe25825a9f877b0cf8cca87e37f0b616ca96d Mon Sep 17 00:00:00 2001 From: Heather Date: Fri, 17 Feb 2023 14:48:13 +0000 Subject: [PATCH 038/191] fix slot errors --- .../src/frontend/site/features/TopicGrid.tsx | 8 +++--- .../src/frontend/site/features/TopicHero.tsx | 25 +++++++++++-------- .../frontend/site/features/TopicTypeHero.tsx | 6 +++-- services/madoc-ts/translations/en/madoc.json | 5 ++++ 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx index da26b0ac0..551389949 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx @@ -42,22 +42,22 @@ export const TopicGrid: React.FC<{ return ( - {topic.thumbnail?.url ? ( + {topic.image_url ? ( {createLocaleString(topic.label, ) : null}
    - {topic.label} + {topic.title} {/* todo await BE */} 123 Objects PART OF - {topic.topicType?.slug} + {topic.type}
    ); diff --git a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx index ac16e94dd..29b148cd3 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx @@ -64,22 +64,25 @@ export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1 {data?.label} - {data.editorial.description && ( + {data.description && ( - {data.editorial?.description} + {data.description} )} - - {data.editorial.subHeading} - - - {data.editorial.summary} - + {data.secondary_heading && ( + + {data.secondary_heading} + + )} + + {data.topic_summary && ( + + {data.topic_summary} + + )} - - - + {data.image_url && } ); }; diff --git a/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx index 781751852..080a016e2 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx @@ -64,12 +64,14 @@ export const TopicTypeHero: React.FC<{ textColor?: string; overlayColor?: string } return ( - + {data.label} - {data.editorial.summary} + {data.description && ( + {data.description} + )} ); diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index a5722454b..8b2361df4 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -106,6 +106,7 @@ "Button label": "Button label", "Button link": "Button link", "Cancel": "Cancel", + "Canonical label": "Canonical label", "Canvas": "Canvas", "Canvas 2": "Canvas 2", "Canvas 3": "Canvas 3", @@ -245,6 +246,7 @@ "Disable plugin": "Disable plugin", "Disable theme": "Disable theme", "Discard merge": "Discard merge", + "Display label": "Display label", "Display name": "Display name", "Display options": "Display options", "Document": "Document", @@ -286,6 +288,7 @@ "Entity labelled by property": "Entity labelled by property", "Errored": "Errored", "Errored / Rejected": "Errored / Rejected", + "Example data": "Example data", "Exit edit mode": "Exit edit mode", "Export": "Export", "Extracting OCR data": "Extracting OCR data", @@ -573,6 +576,7 @@ "Review strategy": "Review strategy", "Reviews": "Reviews", "Revisions being merged": "Revisions being merged", + "Root": "Root", "Save": "Save", "Save changes": "Save changes", "Save for later": "Save for later", @@ -852,6 +856,7 @@ "max contributors": "max contributors", "migrate-capture-model-task": "migrate-capture-model-task", "not started": "not started", + "other_data": "other_data", "paused": "paused", "pending": "pending", "preview": "preview", From e97696a6388badafc816fc7f6cd6d553290c67df Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 21 Feb 2023 16:32:23 +0000 Subject: [PATCH 039/191] Search header component with options to overwrite header --- .../site/features/SearchPageHeading.tsx | 91 +++++++++++++++++++ .../src/frontend/site/pages/search.tsx | 37 ++------ services/madoc-ts/translations/en/madoc.json | 3 + 3 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/SearchPageHeading.tsx diff --git a/services/madoc-ts/src/frontend/site/features/SearchPageHeading.tsx b/services/madoc-ts/src/frontend/site/features/SearchPageHeading.tsx new file mode 100644 index 000000000..d6c65fd21 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/SearchPageHeading.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { useSearchQuery } from '../hooks/use-search-query'; +import { useBreadcrumbs } from '../../shared/components/Breadcrumbs'; +import { Heading1 } from '../../shared/typography/Heading1'; +import { LocaleString } from '../../shared/components/LocaleString'; +import styled from 'styled-components'; + +export const SearchHeadingContainer = styled.div` + h1 { + display: flex; + + span { + &[data-position='first'] { + order: 0; + padding-right: 0.2em; + } + + &[data-position='last'] { + order: 1; + padding-left: 0.2em; + } + + &[data-position='none'] { + display: none; + } + } + } +`; + +export const SearchHeading: React.FC<{ + title?: string; + queryPosition?: string; +}> = ({ title, queryPosition }) => { + const { fulltext } = useSearchQuery(); + + const breads = useBreadcrumbs(); + const isGlobal = !!breads.subpage; + + const getHeading = () => { + if (breads.manifest) return breads.manifest?.name; + else if (breads.collection) return breads.collection.name; + else { + return breads.project?.name; + } + }; + + return ( + + {isGlobal ? ( + <> + + “{fulltext}” + {title ? title : 'search'} + + + ) : ( + + {getHeading()} + {title ? title : 'search'} + + )} + + ); +}; + +blockEditorFor(SearchHeading, { + type: 'default.SearchHeading', + label: 'Search heading', + anyContext: [], + requiredContext: [], + defaultProps: { + queryPosition: 'first', + title: 'search', + }, + editor: { + queryPosition: { + label: 'Search Query position', + type: 'dropdown-field', + options: [ + { value: 'last', text: 'Show after' }, + { value: 'first', text: 'Show before' }, + { value: 'none', text: 'Dont show' }, + ], + }, + title: { + label: 'Additional title', + type: 'text-field', + }, + }, +}); diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index 74fcde182..09aa963eb 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -1,44 +1,22 @@ import React from 'react'; import { Slot } from '../../shared/page-blocks/slot'; -import { useSearchQuery } from '../hooks/use-search-query'; -import { StaticPage } from '../features/StaticPage'; import { SearchPageFilters } from '../features/SearchPageFilters'; import { AppliedFacets } from '../features/AppliedFacets'; -import { Heading1 } from '../../shared/typography/Heading1'; import { SearchPagination } from '../features/SearchPagination'; import { SearchPageResults } from '../features/SearchPageResults'; -import { DisplayBreadcrumbs, useBreadcrumbs } from '../../shared/components/Breadcrumbs'; -import { useRouteContext } from '../hooks/use-route-context'; -import { LocaleString } from '../../shared/components/LocaleString'; - -export const Search: React.FC = () => { - const { rawQuery, page, fulltext } = useSearchQuery(); - - const breads = useBreadcrumbs(); - const isGlobal = !!breads.subpage; - - const getHeading = () => { - if (breads.manifest) return breads.manifest?.name; - else if (breads.collection) return breads.collection.name; - else { - return breads.project?.name; - } - }; +import { DisplayBreadcrumbs } from '../../shared/components/Breadcrumbs'; +import { SearchHeading } from '../features/SearchPageHeading'; +import { StaticPage } from '../features/StaticPage'; +export const Search = () => { return ( - + - - {isGlobal ? ( - “{fulltext}” search - ) : ( - - search in {getHeading()} - - )} + +
    @@ -54,6 +32,7 @@ export const Search: React.FC = () => { + diff --git a/services/madoc-ts/translations/en/madoc.json b/services/madoc-ts/translations/en/madoc.json index 4f7d235d0..77e8dd7c8 100644 --- a/services/madoc-ts/translations/en/madoc.json +++ b/services/madoc-ts/translations/en/madoc.json @@ -27,6 +27,7 @@ "Add new submission": "Add new submission", "Add scope": "Add scope", "Add subpage": "Add subpage", + "Additional title": "Additional title", "Admin": "Admin", "Admin dashboard": "Admin dashboard", "Administer": "Administer", @@ -125,6 +126,7 @@ "Changes requested on your submission": "Changes requested on your submission", "Changes saved": "Changes saved", "Check box color": "Check box color", + "Checkbox options (value,label one per line)": "Checkbox options (value,label one per line)", "Choice": "Choice", "Choose": "Choose", "Choose a root for the form": "Choose a root for the form", @@ -573,6 +575,7 @@ "Saving...": "Saving...", "Scopes": "Scopes", "Search": "Search", + "Search Query position": "Search Query position", "Search collections": "Search collections", "Search configuration": "Search configuration", "Search for existing collection": "Search for existing collection", From 13529ce033465b9cd4f9bbd7d23a22e0dc38e331 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 21 Feb 2023 16:59:43 +0000 Subject: [PATCH 040/191] remove extra pagination component --- services/madoc-ts/src/frontend/site/pages/search.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/madoc-ts/src/frontend/site/pages/search.tsx b/services/madoc-ts/src/frontend/site/pages/search.tsx index 09aa963eb..9143d4977 100644 --- a/services/madoc-ts/src/frontend/site/pages/search.tsx +++ b/services/madoc-ts/src/frontend/site/pages/search.tsx @@ -12,7 +12,7 @@ export const Search = () => { return ( - + @@ -32,7 +32,6 @@ export const Search = () => { - From 3e7ad10db470a2411c45f5d5050f5e72d439dbed Mon Sep 17 00:00:00 2001 From: Matt McGrattan Date: Wed, 22 Feb 2023 10:58:18 +0000 Subject: [PATCH 041/191] Change API endpoint for indexing on enrichment Change Okra image to one with fixed uWSGI limits and set max connections etc in the sysctls --- docker-compose.yml | 8 ++++++-- services/madoc-ts/src/gateway/api.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 061d8b549..418c859c9 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -97,6 +97,7 @@ services: condition: service_healthy tasks-api: + platform: linux/x86_64/v8 tty: true image: ghcr.io/digirati-co-uk/tasks-api:latest environment: @@ -192,6 +193,7 @@ services: - ./configuration/defaults:/app/configurator/default_config storage-api: + platform: linux/x86_64/v8 tty: true image: ghcr.io/digirati-co-uk/storage-api:main environment: @@ -234,5 +236,7 @@ services: ports: - 5020:5000 okra: - image: digirati/okra:latest - + image: digirati/okra:uwsgi + sysctls: + - net.core.somaxconn=1024 + - net.ipv4.tcp_syncookies=0 diff --git a/services/madoc-ts/src/gateway/api.ts b/services/madoc-ts/src/gateway/api.ts index a55644423..abfe67946 100644 --- a/services/madoc-ts/src/gateway/api.ts +++ b/services/madoc-ts/src/gateway/api.ts @@ -1846,7 +1846,7 @@ export class ApiClient { // NEW SEARCH API. async enrichmentIngestResource(request: EnrichmentIndexPayload) { - return this.request(`/api/enrichment/internal/madoc/resource/`, { + return this.request(`/api/enrichment/resource/`, { method: 'POST', body: request, }); From b331f1fcb1359ecd168a5d410b4e88d470d7a710 Mon Sep 17 00:00:00 2001 From: Heather Date: Fri, 24 Feb 2023 16:17:18 +0000 Subject: [PATCH 042/191] fix xreate topic --- .../src/extensions/enrichment/extension.ts | 8 +++++++- .../admin/pages/topics/create-new-topic-type.tsx | 3 ++- .../admin/pages/topics/create-new-topic.tsx | 16 ++++++++-------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/services/madoc-ts/src/extensions/enrichment/extension.ts b/services/madoc-ts/src/extensions/enrichment/extension.ts index 1e943e9ee..148812f32 100644 --- a/services/madoc-ts/src/extensions/enrichment/extension.ts +++ b/services/madoc-ts/src/extensions/enrichment/extension.ts @@ -3,7 +3,7 @@ import { BaseDjangoExtension } from './base-django-extension'; import { EnrichmentIndexPayload } from './types'; import { ApiKey } from '../../types/api-key'; import { SearchQuery, SearchResponse } from '../../types/search'; -import { EntityTypeMadocResponse } from './authority/types'; +import {EntityMadocResponse, EntityTypeMadocResponse} from './authority/types'; export class EnrichmentExtension extends BaseDjangoExtension { // /api/madoc/indexable_data/ @@ -51,6 +51,12 @@ export class EnrichmentExtension extends BaseDjangoExtension { }); } + upsertTopic(topic: Partial) { + return this.api.request(`/api/enrichment/entity/`, { + method: 'POST', + body: topic, + }); + } getTopicItems(query: SearchQuery, page = 1, madoc_id?: string) { return this.api.request(`/madoc/api/search`, { method: 'POST', diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx index 21c77feb1..d70018d37 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx @@ -13,9 +13,10 @@ export function CreateNewTopicType() { // @todo can change later. data.image_url = `${window.location.protocol}//${window.location.host}${data.image_url.publicLink || data.image_url}`; - + console.log(data) // data.other_labels = (data.other_labels || []).filter((e: any) => e.value !== ''); return api.enrichment.upsertTopicType(data); + }); if (status.isError) { diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx index d0d65bdf3..8dc7f31e4 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx @@ -16,20 +16,19 @@ export function CreateNewTopic() { const hasTopic = data || isLoading; const [createNewEntityType, status] = useMutation(async (input: any) => { - input.other_labels = (input.other_labels || []).filter((e: any) => e.value !== ''); + // input.other_labels = (input.other_labels || []).filter((e: any) => e.value !== ''); if (hasTopic) { if (!data) { return; } - // @todo this will hopefully change. input.type = getValue(data.label); + input.type_slug = data.slug; } - return { - response: await api.authority.entity.create(input), - topicType: input.type, + response: await api.enrichment.upsertTopic(input), + topicType: input.type_slug, }; }); const model = useMemo(() => { @@ -48,13 +47,14 @@ export function CreateNewTopic() { } if (status.isSuccess) { + console.log(status.data?.response) return (
    Added! -
    {JSON.stringify(status.data)}
    +
    {JSON.stringify(status.data?.response)}
    {/* @todo hopefully this will change to slug field. */} {status.data ? ( - ) : null} @@ -70,7 +70,7 @@ export function CreateNewTopic() {
    { await createNewEntityType(input); }} From 927eddc1f4860d745f9bcf90fc9e1da91cb5dfb2 Mon Sep 17 00:00:00 2001 From: Heather Date: Mon, 27 Feb 2023 18:03:46 +0000 Subject: [PATCH 043/191] feautured topic items wip --- .../shared/components/CanvasSnippet.tsx | 1 + .../site/features/FeaturedTopicItems.tsx | 169 ++++++++++++++++++ .../src/frontend/site/pages/view-topic.tsx | 6 +- 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx diff --git a/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx b/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx index 4d6069b98..c97ab0b2c 100644 --- a/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx +++ b/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx @@ -23,6 +23,7 @@ export const CanvasSnippet: React.FC<{ collectionId: collectionId, subRoute: model ? 'model' : undefined, }); + console.log(data) if (!data) { return ( diff --git a/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx b/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx new file mode 100644 index 000000000..7c4c0ad66 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx @@ -0,0 +1,169 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import { CanvasFull } from '../../../types/canvas-full'; +import { CanvasSnippet } from '../../shared/components/CanvasSnippet'; +import { useRouteContext } from '../hooks/use-route-context'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; +import { ImageStripBox } from '../../shared/atoms/ImageStrip'; +import { CroppedImage } from '../../shared/atoms/Images'; +import { LocaleString, useCreateLocaleString } from '../../shared/components/LocaleString'; +import { SingleLineHeading5 } from '../../shared/typography/Heading5'; +import { useTranslation } from 'react-i18next'; +import { SnippetContainer } from '../../shared/atoms/SnippetLarge'; +import { useRelativeLinks } from '../../site/hooks/use-relative-links'; +import { Carousel } from '../../shared/atoms/Carousel'; +import { useTopic } from '../pages/loaders/topic-loader'; +import { extractIdFromUrn } from '../../../utility/parse-urn'; + +const FeaturesContainer = styled.div` + display: flex; + justify-content: space-evenly; + + &[data-view-column='true'] { + flex-direction: column; + } + &[data-align='center'] { + align-items: center; + justify-content: center; + } + a { + max-width: 900px; + width: 100%; + } + ${SnippetContainer} { + width: 100%; + } +`; +const FeatureCard = styled.div` + display: flex; + border: 1px solid; + margin: 1em; + + h5 { + color: inherit; + } + :hover { + border-style: dotted; + cursor: pointer; + filter: brightness(90%); + } +`; + +interface FeaturedItemProps { + carousel?: boolean; + header?: string; + snippet?: boolean; + column?: boolean; + imageStyle?: string; + cardBackground?: string; + textColor?: string; + cardBorder?: string; + align?: 'center' | 'start'; +} + +export function FeaturedTopicItems(props: FeaturedItemProps) { + const { data } = useTopic(); + const items = data?.featured_resources ? data?.featured_resources : []; + + const createLocaleString = useCreateLocaleString(); + const { t } = useTranslation(); + + if (!data) { + return null; + } + // @ts-ignore + const Items = items?.map( + item => + item && + (!props.snippet ? ( + + + + + {item.thumbnail ? ( + {createLocaleString(item.label, + ) : null} + + + + {item.label} + + + + ) : ( + + )) + ); + + if (!items || items.length === 0) { + return null; + } + if (!props.carousel || props.column) { + return ( + <> +

    {props.header}

    + + {Items} + + + ); + } + return ( + <> +

    {props.header}

    + + {Items} + + + ); +} + +blockEditorFor(FeaturedTopicItems, { + label: 'Featured Topic Items', + type: 'default.FeaturedTopicItems', + defaultProps: { + header: 'Featured Items', + snippet: false, + column: false, + cardBackground: '#ffffff', + textColor: '', + cardBorder: '', + imageStyle: 'cover', + align: 'start', + carousel: 'false', + }, + editor: { + header: { label: 'label', type: 'text-field' }, + snippet: { type: 'checkbox-field', label: 'Snippet', inlineLabel: 'Show as snippet' }, + column: { type: 'checkbox-field', label: 'Column', inlineLabel: 'Show in column' }, + carousel: { type: 'checkbox-field', label: 'Carousel', inlineLabel: 'Show in carousel' }, + align: { + label: 'Align items', + type: 'dropdown-field', + options: [ + { value: 'center', text: 'centered' }, + { value: 'start', text: 'start' }, + ], + }, + cardBackground: { label: 'Card background color', type: 'color-field' }, + textColor: { label: 'Card text color', type: 'color-field' }, + cardBorder: { label: 'Card border', type: 'color-field' }, + imageStyle: { + label: 'Image Style', + type: 'dropdown-field', + options: [ + { value: 'covered', text: 'covered' }, + { value: 'fit', text: 'fit' }, + ], + }, + }, + anyContext: ['manifest'], + requiredContext: ['manifest'], +}); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 69caf3367..9532f3faa 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -6,6 +6,7 @@ import { useTopicItems } from '../../shared/hooks/use-topic-items'; import { Slot } from '../../shared/page-blocks/slot'; import { TopicHero } from '../features/TopicHero'; import { useParams } from 'react-router-dom'; +import { FeaturedTopicItems } from "../features/FeaturedTopicItems"; export function ViewTopic() { const { topic } = useParams>(); @@ -21,6 +22,10 @@ export function ViewTopic() { + + + +

    Items in this topic

    - {/*
    {JSON.stringify(search.data, null, 2)}
    */}
    ); From d742fec9ec19390d0e34710fd79e37c523e21718 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 28 Feb 2023 14:03:39 +0000 Subject: [PATCH 044/191] featured topcis, create some topic card/snippet components --- .../src/frontend/shared/atoms/Carousel.tsx | 73 +++++++++++------ .../frontend/shared/components/TopicCard.tsx | 64 +++++++++++++++ .../shared/components/TopicSnippet.tsx | 69 ++++++++++++++++ .../src/frontend/shared/icons/ChevronIcon.tsx | 14 ++++ .../site/features/FeaturedTopicItems.tsx | 4 +- .../frontend/site/features/FeaturedTopics.tsx | 80 +++++++++++++++++++ .../src/frontend/site/features/TopicGrid.tsx | 67 ++-------------- .../src/frontend/site/features/TopicHero.tsx | 1 - .../frontend/site/features/TopicTypeHero.tsx | 4 +- .../frontend/site/pages/view-topic-type.tsx | 5 ++ .../src/frontend/site/pages/view-topic.tsx | 11 ++- 11 files changed, 298 insertions(+), 94 deletions(-) create mode 100644 services/madoc-ts/src/frontend/shared/components/TopicCard.tsx create mode 100644 services/madoc-ts/src/frontend/shared/components/TopicSnippet.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/ChevronIcon.tsx create mode 100644 services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx diff --git a/services/madoc-ts/src/frontend/shared/atoms/Carousel.tsx b/services/madoc-ts/src/frontend/shared/atoms/Carousel.tsx index 93ee5e995..8334080d6 100644 --- a/services/madoc-ts/src/frontend/shared/atoms/Carousel.tsx +++ b/services/madoc-ts/src/frontend/shared/atoms/Carousel.tsx @@ -1,20 +1,44 @@ import * as React from 'react'; import styled from 'styled-components'; import { Button } from '../navigation/Button'; -import { SVGProps } from 'react'; +import { ChevronLeft, ChevronRight } from '../icons/ChevronIcon'; const CarouselOuterWrapper = styled.div` width: 90%; + max-width: 1100px; `; const CarouselWrapper = styled.div` display: flex; justify-content: space-between; align-items: center; +`; - svg { - height: 10px; +const CarouselControl = styled.button<{ + $color?: string; +}>` + border: none; + background-color: transparent; + color: ${props => (props.$color ? props.$color : '#3579f6')}; + + display: flex; + font-size: 1em; + padding: 1em; + + &:hover { + color: #333333; + cursor: pointer; + + svg { + fill: #333333; + } } + svg { + fill: ${props => (props.$color ? props.$color : '#3579f6')}; + margin: 0 0.5em; + position: relative; + top: 0.15em; + }, `; const CarouselSlides = styled.div` @@ -22,11 +46,13 @@ const CarouselSlides = styled.div` width: 100%; display: flex; overflow: hidden; + justify-content: center; + `; const CarouselSlide = styled.div` opacity: 0; width: 0; - transition: opacity 0.8s ease, transform 0.8s ease; + transition: opacity 0.5s ease, transform 0.8s ease; visibility: hidden; > div { @@ -48,19 +74,23 @@ const CarouselSlide = styled.div` } `; -const Indicator = styled.button` +const Indicator = styled.button<{ + $color?: string; +}>` height: 12px; width: 64px; background-color: transparent; - border: 1px solid #3579f6; + border: 1px solid; + border-color: ${props => (props.$color ? props.$color : '#3579f6')}; align-self: end; margin: 1em; :hover { + cursor: pointer; background-color: rgba(59, 59, 93, 0.7); } &[data-isActive='true'] { - background-color: #3579f6; + background-color: ${props => (props.$color ? props.$color : '#3579f6')}; :hover { background-color: rgba(59, 59, 93, 0.7); @@ -74,24 +104,16 @@ const IndicatorWrapper = styled.div` interface CarouselProps { children: React.ReactNode; + controlColor?: string; } -export const Carousel = ({ children }: CarouselProps) => { +export const Carousel = ({ children, controlColor }: CarouselProps) => { const [currentSlide, setCurrentSlide] = React.useState(0); const slides = React.Children.toArray(children); const prev = currentSlide === 0 ? slides.length - 1 : currentSlide - 1; const next = currentSlide === slides.length - 1 ? 0 : currentSlide + 1; - const Chevron = (props: SVGProps) => ( - - - - ); - const activeSlide = slides?.map((slide, index) => ( { return ( - + {activeSlide} - + + {slides?.map((slide, index) => ( { diff --git a/services/madoc-ts/src/frontend/shared/components/TopicCard.tsx b/services/madoc-ts/src/frontend/shared/components/TopicCard.tsx new file mode 100644 index 000000000..e358b31db --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/components/TopicCard.tsx @@ -0,0 +1,64 @@ +import { TopicSnippet } from '../../../types/schemas/topics'; +import { ImageStripBox } from '../atoms/ImageStrip'; +import { CroppedImage } from '../atoms/Images'; +import { LocaleString, useCreateLocaleString } from './LocaleString'; +import { SingleLineHeading3, Subheading3 } from '../typography/Heading3'; +import { Heading5 } from '../typography/Heading5'; +import React from 'react'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; + +const TopicStripBox = styled(ImageStripBox)` + height: 300px; + ${CroppedImage} { + height: 100px; + width: 100%; + + img { + object-position: 50% 20% !important; + } + } +`; + +const TypePill = styled.div` + background: #f5d8c0; + color: #002d4b; + border-radius: 4px; + font-size: 12px; + padding: 4px; + margin-top: 0.5em; + display: inline-block; +`; + +export const TopicCard: React.FC<{ + topic: TopicSnippet; + background?: string; + textColor?: string; + cardBorder?: string; +}> = ({ topic, cardBorder, textColor, background }) => { + const createLocaleString = useCreateLocaleString(); + const { t } = useTranslation(); + console.log(topic); + + return ( + + + {topic.image_url ? ( + {createLocaleString(topic.label, + ) : null} + +
    + {topic.title} + + todo awaiting BE + + PART OF + {topic.type} +
    +
    + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/components/TopicSnippet.tsx b/services/madoc-ts/src/frontend/shared/components/TopicSnippet.tsx new file mode 100644 index 000000000..fce91a4fc --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/components/TopicSnippet.tsx @@ -0,0 +1,69 @@ +import { TopicSnippet } from '../../../types/schemas/topics'; +import { CroppedImage } from '../atoms/Images'; +import { LocaleString, useCreateLocaleString } from './LocaleString'; +import { SingleLineHeading3, Subheading3 } from '../typography/Heading3'; +import { Heading5 } from '../typography/Heading5'; +import React from 'react'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import { SnippetContainer } from '../atoms/SnippetLarge'; + +const TopicSnippetContainer = styled(SnippetContainer)` + height: 250px; + max-width: 800px; + padding: 0; + margin-left: auto; + margin-right: auto; +`; +const CardText = styled.div` + margin: 0 1em; + display: flex; + flex-direction: column; + justify-content: space-around; +`; + +const TypePill = styled.div` + background: #f5d8c0; + color: #002d4b; + border-radius: 4px; + font-size: 12px; + padding: 4px; + margin-top: 0.5em; + display: inline-block; +`; + +export const TopicSnippetCard: React.FC<{ + topic: TopicSnippet; + background?: string; + textColor?: string; + cardBorder?: string; +}> = ({ topic, cardBorder, textColor, background }) => { + const createLocaleString = useCreateLocaleString(); + const { t } = useTranslation(); + + return ( + + + {topic.image_url ? ( + {createLocaleString(topic.label, + ) : null} + + + + {topic.title} + + + todo awaiting BE + +
    + PART OF + {topic.type} +
    +
    +
    + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/icons/ChevronIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/ChevronIcon.tsx new file mode 100644 index 000000000..84e21d40a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/ChevronIcon.tsx @@ -0,0 +1,14 @@ +import { SVGProps } from 'react'; +import * as React from 'react'; + +export const ChevronRight = (props: SVGProps) => ( + + + +); + +export const ChevronLeft = (props: SVGProps) => ( + + + +); diff --git a/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx b/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx index 7c4c0ad66..942557772 100644 --- a/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx +++ b/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx @@ -164,6 +164,6 @@ blockEditorFor(FeaturedTopicItems, { ], }, }, - anyContext: ['manifest'], - requiredContext: ['manifest'], + anyContext: ['topic'], + requiredContext: ['topic'], }); diff --git a/services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx b/services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx new file mode 100644 index 000000000..03ed95c6e --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; +import { useCreateLocaleString } from '../../shared/components/LocaleString'; +import { useTranslation } from 'react-i18next'; +import { Carousel } from '../../shared/atoms/Carousel'; +import { useTopicType } from '../pages/loaders/topic-type-loader'; +import { TopicSnippetCard } from '../../shared/components/TopicSnippet'; + +const FeaturesContainer = styled.div` + display: flex; + justify-content: space-evenly; + align-content: center; + a { + text-decoration: none; + width: 100%; + } +`; + +export const FeaturedTopics: React.FC<{ + cardBackground?: string; + textColor?: string; + cardBorder?: string; + controlcolor?: string; +}> = ({ cardBackground = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B', controlcolor = '#002D4B' }) => { + const { data } = useTopicType(); + // todo change when backend has featured items + // const items = data?.featured_items ? data?.featured_items : []; + const items = [data?.topics[0], data?.topics[1], data?.topics[2]]; + + if (!data) { + return null; + } + const Items = items?.map(item => { + return ( + item && ( + + + + ) + ); + }); + + if (!items || items.length === 0) { + return null; + } + return ( + <> +

    Featured in: {data.label}

    + + {Items} + + + ); +} + +blockEditorFor(FeaturedTopics, { + label: 'Featured Topics', + type: 'default.FeaturedTopics', + defaultProps: { + cardBackground: '', + textColor: '', + cardBorder: '', + controlColor: '', + }, + editor: { + cardBackground: { label: 'Card background color', type: 'color-field' }, + textColor: { label: 'Card text color', type: 'color-field' }, + cardBorder: { label: 'Card border', type: 'color-field' }, + controlColor: { label: 'Carousel control colours', type: 'color-field' }, + }, + anyContext: ['topic'], + requiredContext: ['topic'], +}); diff --git a/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx index 551389949..5a771a5ac 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicGrid.tsx @@ -3,66 +3,22 @@ import { Link } from 'react-router-dom'; import React from 'react'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; import { useRelativeLinks } from '../hooks/use-relative-links'; -import { useTranslation } from 'react-i18next'; -import { LocaleString, useCreateLocaleString } from '../../shared/components/LocaleString'; import { useTopicType } from '../pages/loaders/topic-type-loader'; -import { ImageStripBox } from '../../shared/atoms/ImageStrip'; -import { CroppedImage } from '../../shared/atoms/Images'; -import { SingleLineHeading3, Subheading3 } from '../../shared/typography/Heading3'; -import styled from 'styled-components'; -import { Heading5 } from '../../shared/typography/Heading5'; -import { TopicSnippet } from '../../../types/schemas/topics'; - -const Pill = styled.div` - background: #f5d8c0; - color: #002d4b; - border-radius: 4px; - font-size: 12px; - padding: 4px; - margin-top: 0.5em; - display: inline-block; -`; +import { TopicCard } from '../../shared/components/TopicCard'; export const TopicGrid: React.FC<{ background?: string; textColor?: string; cardBorder?: string; imageStyle?: string; -}> = ({ background = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B', imageStyle = 'covered' }) => { +}> = ({ background = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B' }) => { const { data } = useTopicType(); const items = data?.topics; - const createLocaleString = useCreateLocaleString(); const createLink = useRelativeLinks(); - const { t } = useTranslation(); if (!data) { return null; } - const renderTopicSnippet = (topic: TopicSnippet) => { - return ( - - - {topic.image_url ? ( - {createLocaleString(topic.label, - ) : null} - -
    - {topic.title} - - {/* todo await BE */} - 123 Objects - - PART OF - {topic.type} -
    -
    - ); - }; - return (
    @@ -73,34 +29,25 @@ export const TopicGrid: React.FC<{ topic: topic.slug, })} > - {renderTopicSnippet(topic)} + ))}
    ); -} +}; blockEditorFor(TopicGrid, { type: 'default.TopicGrid', label: 'Topic Grid', defaultProps: { - background: '#ffffff', - textColor: '#002D4B', - cardBorder: '#002D4B', - imageStyle: 'covered', + background: '', + textColor: '', + cardBorder: '', }, editor: { background: { label: 'Card background color', type: 'color-field' }, textColor: { label: 'Card text color', type: 'color-field' }, cardBorder: { label: 'Card border', type: 'color-field' }, - imageStyle: { - label: 'Image Style', - type: 'dropdown-field', - options: [ - { value: 'covered', text: 'covered' }, - { value: 'fit', text: 'fit' }, - ], - }, }, requiredContext: ['topicType'], anyContext: ['topicType'], diff --git a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx index 29b148cd3..9cd084f3c 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx @@ -53,7 +53,6 @@ const Right = styled.div` export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1Color, h2Color }) => { const { data } = useTopic(); - if (!data) { return null; } diff --git a/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx index 080a016e2..ba22ef559 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicTypeHero.tsx @@ -69,9 +69,7 @@ export const TopicTypeHero: React.FC<{ textColor?: string; overlayColor?: string {data.label} - {data.description && ( - {data.description} - )} + {data.description && {data.description}} ); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx index da3b122c6..1b90eb0fa 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -4,6 +4,7 @@ import { Slot } from '../../shared/page-blocks/slot'; import { TopicTypeHero } from '../features/TopicTypeHero'; import { TopicGrid } from '../features/TopicGrid'; import { TopicTypePagination } from '../features/TopicTypePagination'; +import { FeaturedTopics } from '../features/FeaturedTopics'; export function ViewTopicType() { return ( @@ -16,6 +17,10 @@ export function ViewTopicType() { + + + + diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 9532f3faa..8ff03791e 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -6,14 +6,15 @@ import { useTopicItems } from '../../shared/hooks/use-topic-items'; import { Slot } from '../../shared/page-blocks/slot'; import { TopicHero } from '../features/TopicHero'; import { useParams } from 'react-router-dom'; -import { FeaturedTopicItems } from "../features/FeaturedTopicItems"; +import { FeaturedTopicItems } from '../features/FeaturedTopicItems'; +import { StaticPage } from '../features/StaticPage'; export function ViewTopic() { const { topic } = useParams>(); const [search, { query, page }] = useTopicItems(topic); return ( - <> + @@ -46,6 +47,10 @@ export function ViewTopic() { extraQuery={query} />
    - + + + {/**/} + + ); } From 09977db5c72f52963858497ad14adeef5f422afa Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 28 Feb 2023 14:30:30 +0000 Subject: [PATCH 045/191] related topics --- .../frontend/shared/components/TopicCard.tsx | 1 - .../frontend/site/features/FeaturedTopics.tsx | 26 +++---- .../frontend/site/features/RelatedTopics.tsx | 77 +++++++++++++++++++ .../frontend/site/pages/view-topic-type.tsx | 7 +- .../src/frontend/site/pages/view-topic.tsx | 3 +- 5 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 services/madoc-ts/src/frontend/site/features/RelatedTopics.tsx diff --git a/services/madoc-ts/src/frontend/shared/components/TopicCard.tsx b/services/madoc-ts/src/frontend/shared/components/TopicCard.tsx index e358b31db..62f987d7c 100644 --- a/services/madoc-ts/src/frontend/shared/components/TopicCard.tsx +++ b/services/madoc-ts/src/frontend/shared/components/TopicCard.tsx @@ -38,7 +38,6 @@ export const TopicCard: React.FC<{ }> = ({ topic, cardBorder, textColor, background }) => { const createLocaleString = useCreateLocaleString(); const { t } = useTranslation(); - console.log(topic); return ( diff --git a/services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx b/services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx index 03ed95c6e..aff1bd3e7 100644 --- a/services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx +++ b/services/madoc-ts/src/frontend/site/features/FeaturedTopics.tsx @@ -2,11 +2,10 @@ import React from 'react'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; import styled from 'styled-components'; import { Link } from 'react-router-dom'; -import { useCreateLocaleString } from '../../shared/components/LocaleString'; -import { useTranslation } from 'react-i18next'; import { Carousel } from '../../shared/atoms/Carousel'; import { useTopicType } from '../pages/loaders/topic-type-loader'; import { TopicSnippetCard } from '../../shared/components/TopicSnippet'; +import { useRelativeLinks } from '../hooks/use-relative-links'; const FeaturesContainer = styled.div` display: flex; @@ -22,9 +21,10 @@ export const FeaturedTopics: React.FC<{ cardBackground?: string; textColor?: string; cardBorder?: string; - controlcolor?: string; -}> = ({ cardBackground = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B', controlcolor = '#002D4B' }) => { + controlColor?: string; +}> = ({ cardBackground = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B', controlColor = '#002D4B' }) => { const { data } = useTopicType(); + const createLink = useRelativeLinks(); // todo change when backend has featured items // const items = data?.featured_items ? data?.featured_items : []; const items = [data?.topics[0], data?.topics[1], data?.topics[2]]; @@ -35,13 +35,13 @@ export const FeaturedTopics: React.FC<{ const Items = items?.map(item => { return ( item && ( - - + + ) ); @@ -54,11 +54,11 @@ export const FeaturedTopics: React.FC<{ <>

    Featured in: {data.label}

    - {Items} + {Items} ); -} +}; blockEditorFor(FeaturedTopics, { label: 'Featured Topics', diff --git a/services/madoc-ts/src/frontend/site/features/RelatedTopics.tsx b/services/madoc-ts/src/frontend/site/features/RelatedTopics.tsx new file mode 100644 index 000000000..3c0d1b4c7 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/RelatedTopics.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; +import { useRelativeLinks } from '../hooks/use-relative-links'; +import { useTopic } from '../pages/loaders/topic-loader'; +import { TopicCard } from '../../shared/components/TopicCard'; + +const RelatedContainer = styled.div` + align-self: center; + display: flex; + margin: 1em; + justify-content: space-around; + align-content: center; + width: calc(325px * 3 + 6em); + + a { + text-decoration: none; + width: 325px; + } +`; + +export const RelatedTopics: React.FC<{ + cardBackground?: string; + textColor?: string; + cardBorder?: string; + controlColor?: string; +}> = ({ cardBackground = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B' }) => { + const { data } = useTopic(); + const createLink = useRelativeLinks(); + + const items = data?.related_topics ? data?.related_topics : []; + + if (!data) { + return null; + } + if (!items || items.length === 0) { + return null; + } + return ( + <> +

    Related topics

    + + {items?.map( + item => + item && ( + + + + ) + )} + + + ); +}; + +blockEditorFor(RelatedTopics, { + label: 'Related Topics', + type: 'default.RelatedTopics', + defaultProps: { + cardBackground: '', + textColor: '', + cardBorder: '', + }, + editor: { + cardBackground: { label: 'Card background color', type: 'color-field' }, + textColor: { label: 'Card text color', type: 'color-field' }, + cardBorder: { label: 'Card border', type: 'color-field' }, + }, + anyContext: ['topic'], + requiredContext: ['topic'], +}); diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx index 1b90eb0fa..1f6d6b270 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -5,10 +5,11 @@ import { TopicTypeHero } from '../features/TopicTypeHero'; import { TopicGrid } from '../features/TopicGrid'; import { TopicTypePagination } from '../features/TopicTypePagination'; import { FeaturedTopics } from '../features/FeaturedTopics'; +import { StaticPage } from '../features/StaticPage'; export function ViewTopicType() { return ( - <> + @@ -28,8 +29,6 @@ export function ViewTopicType() { - - {/*
    {JSON.stringify(data, null, 2)}
    */} - +
    ); } diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx index 8ff03791e..434cc852c 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic.tsx @@ -8,6 +8,7 @@ import { TopicHero } from '../features/TopicHero'; import { useParams } from 'react-router-dom'; import { FeaturedTopicItems } from '../features/FeaturedTopicItems'; import { StaticPage } from '../features/StaticPage'; +import {RelatedTopics} from "../features/RelatedTopics"; export function ViewTopic() { const { topic } = useParams>(); @@ -49,7 +50,7 @@ export function ViewTopic() {
    - {/**/} +
    ); From 5d5a77b2664024fb44816633837682aa171231b7 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 28 Feb 2023 15:01:52 +0000 Subject: [PATCH 046/191] featured topic items --- .../extensions/enrichment/authority/types.ts | 2 +- .../shared/components/CanvasSnippet.tsx | 1 - .../site/features/FeaturedTopicItems.tsx | 119 ++++++------------ 3 files changed, 38 insertions(+), 84 deletions(-) diff --git a/services/madoc-ts/src/extensions/enrichment/authority/types.ts b/services/madoc-ts/src/extensions/enrichment/authority/types.ts index 21160a7ab..dbd6a7b2e 100644 --- a/services/madoc-ts/src/extensions/enrichment/authority/types.ts +++ b/services/madoc-ts/src/extensions/enrichment/authority/types.ts @@ -268,7 +268,7 @@ export interface EntityMadocResponse { related_topics: EnrichmentEntitySnippet[]; other_data: any; } -interface FeatureResource { +export interface FeatureResource { url: string; created: string; modified: string; diff --git a/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx b/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx index c97ab0b2c..4d6069b98 100644 --- a/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx +++ b/services/madoc-ts/src/frontend/shared/components/CanvasSnippet.tsx @@ -23,7 +23,6 @@ export const CanvasSnippet: React.FC<{ collectionId: collectionId, subRoute: model ? 'model' : undefined, }); - console.log(data) if (!data) { return ( diff --git a/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx b/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx index 942557772..b291c6a55 100644 --- a/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx +++ b/services/madoc-ts/src/frontend/site/features/FeaturedTopicItems.tsx @@ -1,8 +1,5 @@ import React from 'react'; import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; -import { CanvasFull } from '../../../types/canvas-full'; -import { CanvasSnippet } from '../../shared/components/CanvasSnippet'; -import { useRouteContext } from '../hooks/use-route-context'; import styled from 'styled-components'; import { Link } from 'react-router-dom'; import { ImageStripBox } from '../../shared/atoms/ImageStrip'; @@ -11,23 +8,19 @@ import { LocaleString, useCreateLocaleString } from '../../shared/components/Loc import { SingleLineHeading5 } from '../../shared/typography/Heading5'; import { useTranslation } from 'react-i18next'; import { SnippetContainer } from '../../shared/atoms/SnippetLarge'; -import { useRelativeLinks } from '../../site/hooks/use-relative-links'; -import { Carousel } from '../../shared/atoms/Carousel'; import { useTopic } from '../pages/loaders/topic-loader'; +import { useApiCanvas } from '../../shared/hooks/use-api-canvas'; import { extractIdFromUrn } from '../../../utility/parse-urn'; +import { FeatureResource } from '../../../extensions/enrichment/authority/types'; -const FeaturesContainer = styled.div` +const FeaturedItemsContainer = styled.div` display: flex; + flex-direction: column; + align-items: center; justify-content: space-evenly; - &[data-view-column='true'] { - flex-direction: column; - } - &[data-align='center'] { - align-items: center; - justify-content: center; - } a { + text-decoration: none; max-width: 900px; width: 100%; } @@ -50,108 +43,70 @@ const FeatureCard = styled.div` } `; -interface FeaturedItemProps { - carousel?: boolean; - header?: string; - snippet?: boolean; - column?: boolean; - imageStyle?: string; +export const FeaturedTopicItems: React.FC<{ cardBackground?: string; textColor?: string; cardBorder?: string; - align?: 'center' | 'start'; -} - -export function FeaturedTopicItems(props: FeaturedItemProps) { + imageStyle?: string; +}> = ({ cardBackground = '#ffffff', textColor = '#002D4B', cardBorder = '#002D4B', imageStyle = 'covered' }) => { const { data } = useTopic(); const items = data?.featured_resources ? data?.featured_resources : []; const createLocaleString = useCreateLocaleString(); const { t } = useTranslation(); - if (!data) { return null; } - // @ts-ignore - const Items = items?.map( - item => - item && - (!props.snippet ? ( - - - - - {item.thumbnail ? ( - {createLocaleString(item.label, - ) : null} - - - - {item.label} - - - - ) : ( - - )) - ); - if (!items || items.length === 0) { return null; } - if (!props.carousel || props.column) { + + const RenderItemSnippet = (item: FeatureResource) => { + const { data: itemData } = useApiCanvas(extractIdFromUrn(item.madoc_id)); + // todo backend needs to give more data return ( - <> -

    {props.header}

    - - {Items} - - + + + + + {item.thumbnail ? ( + {createLocaleString(itemData?.canvas.label, + ) : null} + + + + {itemData?.canvas.label} + + + ); - } + }; return ( <> -

    {props.header}

    - - {Items} - +

    Featured on: {data.label}

    + {items?.map(item => item && )} ); -} +}; blockEditorFor(FeaturedTopicItems, { label: 'Featured Topic Items', type: 'default.FeaturedTopicItems', defaultProps: { header: 'Featured Items', - snippet: false, - column: false, cardBackground: '#ffffff', textColor: '', cardBorder: '', imageStyle: 'cover', - align: 'start', - carousel: 'false', }, editor: { header: { label: 'label', type: 'text-field' }, - snippet: { type: 'checkbox-field', label: 'Snippet', inlineLabel: 'Show as snippet' }, - column: { type: 'checkbox-field', label: 'Column', inlineLabel: 'Show in column' }, - carousel: { type: 'checkbox-field', label: 'Carousel', inlineLabel: 'Show in carousel' }, - align: { - label: 'Align items', - type: 'dropdown-field', - options: [ - { value: 'center', text: 'centered' }, - { value: 'start', text: 'start' }, - ], - }, cardBackground: { label: 'Card background color', type: 'color-field' }, textColor: { label: 'Card text color', type: 'color-field' }, cardBorder: { label: 'Card border', type: 'color-field' }, From 99af422a617aeac33a435d60055f7f8ef08b25d8 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 28 Feb 2023 15:18:28 +0000 Subject: [PATCH 047/191] console logs --- .../frontend/admin/pages/topics/create-new-topic-type.tsx | 1 - .../src/frontend/admin/pages/topics/create-new-topic.tsx | 1 - .../src/frontend/admin/pages/topics/edit-topic-type.tsx | 5 +---- services/madoc-ts/src/frontend/site/features/TopicHero.tsx | 3 ++- .../madoc-ts/src/frontend/site/pages/view-topic-type.tsx | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx index d70018d37..3a2d158ea 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic-type.tsx @@ -13,7 +13,6 @@ export function CreateNewTopicType() { // @todo can change later. data.image_url = `${window.location.protocol}//${window.location.host}${data.image_url.publicLink || data.image_url}`; - console.log(data) // data.other_labels = (data.other_labels || []).filter((e: any) => e.value !== ''); return api.enrichment.upsertTopicType(data); diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx index 8dc7f31e4..78c5e4fbc 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/create-new-topic.tsx @@ -47,7 +47,6 @@ export function CreateNewTopic() { } if (status.isSuccess) { - console.log(status.data?.response) return (
    Added! diff --git a/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx b/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx index 9042ecaf0..07509c91b 100644 --- a/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/topics/edit-topic-type.tsx @@ -21,8 +21,7 @@ export function EditTopicType() { // data.other_labels = (data.other_labels || []).filter((e: any) => e.value !== ''); const resp = api.enrichment.upsertTopicType({ id: data.id, ...updatedData }); - - refetch() + refetch(); return resp; }); @@ -47,8 +46,6 @@ export function EditTopicType() { ); } - console.log(data); - return (
    diff --git a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx index 9cd084f3c..da0158520 100644 --- a/services/madoc-ts/src/frontend/site/features/TopicHero.tsx +++ b/services/madoc-ts/src/frontend/site/features/TopicHero.tsx @@ -47,6 +47,7 @@ const ImageMask = styled.div` margin-left: 4em; `; const Right = styled.div` + margin-left: auto; display: flex; flex-direction: column; `; @@ -60,7 +61,7 @@ export const TopicHero: React.FC<{ h1Color?: string; h2Color?: string }> = ({ h1 - {data?.label} + {data?.title} {data.description && ( diff --git a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx index 1f6d6b270..ffe81ee66 100644 --- a/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx +++ b/services/madoc-ts/src/frontend/site/pages/view-topic-type.tsx @@ -9,7 +9,7 @@ import { StaticPage } from '../features/StaticPage'; export function ViewTopicType() { return ( - + From 275aeced336b9b0ecc48f6a2e2161ea9848fd454 Mon Sep 17 00:00:00 2001 From: Heather <52662841+Heather0K@users.noreply.github.com> Date: Tue, 28 Feb 2023 15:24:51 +0000 Subject: [PATCH 048/191] Update docker-compose.enrichment.yml Co-authored-by: Stephen Fraser --- docker-compose.enrichment.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.enrichment.yml b/docker-compose.enrichment.yml index 6e789b9ff..93e87e0f1 100755 --- a/docker-compose.enrichment.yml +++ b/docker-compose.enrichment.yml @@ -29,7 +29,7 @@ services: links: - shared-postgres - gateway-redis -# volumes: -# - ./services/enrichment/app:/app:delegated -# ports: -# - 5020:5000 + volumes: + - ./services/enrichment/app:/app:delegated + ports: + - 5020:5000 From f717484eebf8230cd784e01c10a6945341c1aea8 Mon Sep 17 00:00:00 2001 From: Heather Date: Tue, 28 Feb 2023 15:48:58 +0000 Subject: [PATCH 049/191] merge 2.1 --- .../madoc-ts/src/frontend/shared/components/SearchResults.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx index 7b4a7eedc..698cf8f82 100644 --- a/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx +++ b/services/madoc-ts/src/frontend/shared/components/SearchResults.tsx @@ -10,8 +10,6 @@ import { SnippetThumbnail, SnippetThumbnailContainer } from '../atoms/SnippetLar import { createLink } from '../utility/create-link'; import { HrefLink } from '../utility/href-link'; import { LocaleString } from './LocaleString'; -import { InternationalString } from '@iiif/presentation-3'; - export const ResultsContainer = styled.div<{ $isFetching?: boolean }>` flex: 1 1 0px; transition: opacity 0.2s; @@ -205,6 +203,7 @@ export const SearchResults: React.FC<{ isFetching?: boolean; admin?: boolean; }> = ({ isFetching, searchResults = [], value, admin }) => { + return ( {searchResults.map((result: SearchResult, index: number) => { return result ? ( @@ -212,4 +211,5 @@ export const SearchResults: React.FC<{ ) : null; })} + ) }; From 39b6162d776d94a32a9bcca3ba0538eebb906d9b Mon Sep 17 00:00:00 2001 From: Heather Date: Wed, 1 Mar 2023 09:27:30 +0000 Subject: [PATCH 050/191] merge enrichment-integration into slots-for-search and fix conflicts --- services/madoc-ts/package.json | 2 +- services/madoc-ts/schemas/Topic.json | 50 ++++++--------------- services/madoc-ts/schemas/TopicSnippet.json | 44 +++++++++++++++++- 3 files changed, 58 insertions(+), 38 deletions(-) diff --git a/services/madoc-ts/package.json b/services/madoc-ts/package.json index 01c66c235..26aba00a1 100644 --- a/services/madoc-ts/package.json +++ b/services/madoc-ts/package.json @@ -320,4 +320,4 @@ "lib/" ] } -} +} \ No newline at end of file diff --git a/services/madoc-ts/schemas/Topic.json b/services/madoc-ts/schemas/Topic.json index 40d273901..1d7652c50 100644 --- a/services/madoc-ts/schemas/Topic.json +++ b/services/madoc-ts/schemas/Topic.json @@ -19,18 +19,6 @@ "topicType": { "$ref": "#/definitions/TopicTypeSnippet" }, - "otherLabels": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "authorities": { "type": "array", "items": { @@ -91,6 +79,15 @@ } } }, + "subHeading": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, "heroImage": { "type": "object", "properties": { @@ -113,30 +110,12 @@ }, "description": { "type": "object", - "properties": { - "label": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "value": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } + "additionalProperties": { + "type": "array", + "items": { + "type": "string" } - }, - "required": [ - "label", - "value" - ] + } }, "featured": { "type": "array", @@ -287,7 +266,6 @@ "id", "label", "modified", - "otherLabels", "slug" ], "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/services/madoc-ts/schemas/TopicSnippet.json b/services/madoc-ts/schemas/TopicSnippet.json index 521f0b5e6..9b305c152 100644 --- a/services/madoc-ts/schemas/TopicSnippet.json +++ b/services/madoc-ts/schemas/TopicSnippet.json @@ -1,4 +1,46 @@ { - "$ref": "#/definitions/TopicSnippet", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "topicType": { + "$ref": "#/definitions/TopicTypeSnippet" + }, + "thumbnail": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "alt": { + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "totalObjects": { + "type": "number" + } + }, + "required": [ + "id", + "label", + "slug" + ], "$schema": "http://json-schema.org/draft-07/schema#" } \ No newline at end of file From b90a4c8c0424261542a4a602bad20ab0c7e34fbc Mon Sep 17 00:00:00 2001 From: Heather Date: Wed, 1 Mar 2023 09:35:42 +0000 Subject: [PATCH 051/191] reset head --- services/madoc-ts/.DS_Store | Bin 0 -> 8196 bytes .../madoc-ts/fixtures/96-jira/MAD-1076.json | 1 + .../fixtures/96-jira/MAD-1199-1-fixed.json | 155 ++++++ .../madoc-ts/fixtures/96-jira/MAD-1199-1.json | 143 ++++++ .../madoc-ts/fixtures/96-jira/MAD-1200-1.json | 162 +++++++ .../madoc-ts/fixtures/96-jira/MAD-1200-2.json | 133 +++++ .../madoc-ts/fixtures/96-jira/MAD-1200-3.json | 167 +++++++ .../madoc-ts/fixtures/96-jira/MAD-1200-4.json | 61 +++ .../madoc-ts/fixtures/97-bugs/05-chain.json | 454 ++++++++++++++++++ .../fixtures/97-bugs/field-overwritten.json | 192 ++++++++ .../2023-02-09T12-20.manifest-thumbnail.sql | 38 ++ .../2023-02-09T12-20.manifest-thumbnail.sql | 22 + .../canvas/canvas-annotation-export.ts | 77 +++ .../canvas/canvas-api-export.ts | 26 + .../canvas/canvas-model-export.ts | 73 +++ .../canvas/canvas-plaintext-export.ts | 25 + .../manifest/manifest-api-export.ts | 18 + .../project/project-api-export.ts | 18 + .../project/project-csv-simple-export.ts | 100 ++++ .../extensions/project-export/extension.ts | 49 ++ .../project-export/server-export.ts | 65 +++ .../src/extensions/project-export/types.ts | 153 ++++++ .../utils/file-patterns-to-list.ts | 81 ++++ .../ProjectExportSnippet.stories.tsx | 122 +++++ .../ProjectExportSnippet.styles.ts | 109 +++++ .../ProjectExportSnippet.tsx | 76 +++ .../admin/features/build-project-export.tsx | 249 ++++++++++ .../features/edit-export-configuration.tsx | 56 +++ .../admin/features/view-project-exports.tsx | 29 ++ .../pages/content/canvases/canvas-export.tsx | 96 ++++ .../content/manifests/manifest-export.tsx | 141 ++++++ .../pages/tasks/export-resource-task.tsx | 14 + .../frontend/admin/stores/export-builder.ts | 60 +++ .../ViewDocument/ViewDocument.styles.ts | 98 ++++ .../_components/ViewDocument/ViewDocument.tsx | 0 .../ViewDocument/components/ViewEntity.tsx | 101 ++++ .../ViewDocument/components/ViewField.tsx | 65 +++ .../ViewDocument/components/ViewProperty.tsx | 50 ++ .../ViewDocument/components/ViewSelector.tsx | 53 ++ .../render/render-entity-list.tsx | 66 +++ .../ViewDocument/render/render-field-list.tsx | 32 ++ .../ViewDocument/render/render-property.tsx | 80 +++ .../capture-models/new/CoreModelEditor.tsx | 372 ++++++++++++++ .../new/DynamicVaultContext.tsx | 90 ++++ .../capture-models/utility/is-entity-empty.ts | 28 ++ .../shared/components/FilePreview.tsx | 170 +++++++ .../shared/components/RichSelectionGrid.tsx | 92 ++++ .../shared/components/RootStatistics.tsx | 73 +++ .../frontend/shared/components/TaskTabs.tsx | 49 ++ .../frontend/shared/hooks/use-decay-state.ts | 18 + .../hooks/use-export-resource-preview.ts | 49 ++ .../shared/hooks/use-export-resources.ts | 33 ++ .../shared/hooks/use-project-exports.ts | 17 + .../src/frontend/shared/icons/BugIcon.tsx | 12 + .../frontend/shared/icons/CheckCircleIcon.tsx | 11 + .../src/frontend/shared/icons/Chevron.tsx | 9 + .../frontend/shared/icons/ListItemIcon.tsx | 15 + .../src/frontend/shared/icons/NoEntryIcon.tsx | 11 + .../shared/icons/RequestChangesIcon.tsx | 10 + .../shared/icons/ResizeHandleIcon.tsx | 11 + .../shared/icons/UnlockSmileyIcon.tsx | 21 + .../site/features/CreateModelTestCase.tsx | 234 +++++++++ .../src/frontend/site/features/EmbedItem.tsx | 37 ++ .../site/features/GenerateManifestPdf.tsx | 47 ++ .../features/ManifestModelCanvasPreview.tsx | 120 +++++ .../hooks/use-keyboard-list-navigation.ts | 47 ++ .../tasks/review-listing/ReviewNagivation.tsx | 165 +++++++ .../src/gateway/helpers/get-site-api.ts | 32 ++ .../src/gateway/tasks/export-resource-task.ts | 396 +++++++++++++++ .../routes/projects/create-project-export.ts | 47 ++ .../routes/projects/get-project-raw-data.ts | 31 ++ .../list-project-model-entity-autocomplete.ts | 29 ++ .../projects/list-projects-autocomplete.ts | 22 + .../madoc-ts/src/utility/create-download.ts | 16 + .../interactions/CaptureModelTestHarness.tsx | 316 ++++++++++++ .../interactions/MAD-1076.stories.tsx | 19 + .../interactions/MAD-1199.stories.tsx | 15 + .../interactions/MAD-1200.stories.tsx | 185 +++++++ .../interactions/MAD-1294.stories.tsx | 21 + .../basic-model-interaction.stories.tsx | 47 ++ .../multiple-interaction.stories.tsx | 58 +++ .../official-project-templates.stories.tsx | 16 + .../remove-selector-interaction.stories.tsx | 39 ++ .../interactions/test-harness-url.stories.tsx | 27 ++ .../the-chain-project.stories.tsx | 84 ++++ 85 files changed, 6851 insertions(+) create mode 100644 services/madoc-ts/.DS_Store create mode 100644 services/madoc-ts/fixtures/96-jira/MAD-1076.json create mode 100644 services/madoc-ts/fixtures/96-jira/MAD-1199-1-fixed.json create mode 100644 services/madoc-ts/fixtures/96-jira/MAD-1199-1.json create mode 100644 services/madoc-ts/fixtures/96-jira/MAD-1200-1.json create mode 100644 services/madoc-ts/fixtures/96-jira/MAD-1200-2.json create mode 100644 services/madoc-ts/fixtures/96-jira/MAD-1200-3.json create mode 100644 services/madoc-ts/fixtures/96-jira/MAD-1200-4.json create mode 100644 services/madoc-ts/fixtures/97-bugs/05-chain.json create mode 100644 services/madoc-ts/fixtures/97-bugs/field-overwritten.json create mode 100644 services/madoc-ts/migrations/2023-02-09T12-20.manifest-thumbnail.sql create mode 100644 services/madoc-ts/migrations/down/2023-02-09T12-20.manifest-thumbnail.sql create mode 100644 services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-annotation-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-api-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-model-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-plaintext-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/export-configs/manifest/manifest-api-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/export-configs/project/project-api-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/export-configs/project/project-csv-simple-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/extension.ts create mode 100644 services/madoc-ts/src/extensions/project-export/server-export.ts create mode 100644 services/madoc-ts/src/extensions/project-export/types.ts create mode 100644 services/madoc-ts/src/extensions/project-export/utils/file-patterns-to-list.ts create mode 100644 services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.stories.tsx create mode 100644 services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.styles.ts create mode 100644 services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.tsx create mode 100644 services/madoc-ts/src/frontend/admin/features/build-project-export.tsx create mode 100644 services/madoc-ts/src/frontend/admin/features/edit-export-configuration.tsx create mode 100644 services/madoc-ts/src/frontend/admin/features/view-project-exports.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-export.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-export.tsx create mode 100644 services/madoc-ts/src/frontend/admin/pages/tasks/export-resource-task.tsx create mode 100644 services/madoc-ts/src/frontend/admin/stores/export-builder.ts create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/ViewDocument.styles.ts create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/ViewDocument.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewEntity.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewField.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewProperty.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewSelector.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-entity-list.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-field-list.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-property.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/new/CoreModelEditor.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/new/DynamicVaultContext.tsx create mode 100644 services/madoc-ts/src/frontend/shared/capture-models/utility/is-entity-empty.ts create mode 100644 services/madoc-ts/src/frontend/shared/components/FilePreview.tsx create mode 100644 services/madoc-ts/src/frontend/shared/components/RichSelectionGrid.tsx create mode 100644 services/madoc-ts/src/frontend/shared/components/RootStatistics.tsx create mode 100644 services/madoc-ts/src/frontend/shared/components/TaskTabs.tsx create mode 100644 services/madoc-ts/src/frontend/shared/hooks/use-decay-state.ts create mode 100644 services/madoc-ts/src/frontend/shared/hooks/use-export-resource-preview.ts create mode 100644 services/madoc-ts/src/frontend/shared/hooks/use-export-resources.ts create mode 100644 services/madoc-ts/src/frontend/shared/hooks/use-project-exports.ts create mode 100644 services/madoc-ts/src/frontend/shared/icons/BugIcon.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/CheckCircleIcon.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/Chevron.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/ListItemIcon.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/NoEntryIcon.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/RequestChangesIcon.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/ResizeHandleIcon.tsx create mode 100644 services/madoc-ts/src/frontend/shared/icons/UnlockSmileyIcon.tsx create mode 100644 services/madoc-ts/src/frontend/site/features/CreateModelTestCase.tsx create mode 100644 services/madoc-ts/src/frontend/site/features/EmbedItem.tsx create mode 100644 services/madoc-ts/src/frontend/site/features/GenerateManifestPdf.tsx create mode 100644 services/madoc-ts/src/frontend/site/features/ManifestModelCanvasPreview.tsx create mode 100644 services/madoc-ts/src/frontend/site/hooks/use-keyboard-list-navigation.ts create mode 100644 services/madoc-ts/src/frontend/site/pages/tasks/review-listing/ReviewNagivation.tsx create mode 100644 services/madoc-ts/src/gateway/helpers/get-site-api.ts create mode 100644 services/madoc-ts/src/gateway/tasks/export-resource-task.ts create mode 100644 services/madoc-ts/src/routes/projects/create-project-export.ts create mode 100644 services/madoc-ts/src/routes/projects/get-project-raw-data.ts create mode 100644 services/madoc-ts/src/routes/projects/list-project-model-entity-autocomplete.ts create mode 100644 services/madoc-ts/src/routes/projects/list-projects-autocomplete.ts create mode 100644 services/madoc-ts/src/utility/create-download.ts create mode 100644 services/madoc-ts/stories/capture-models/interactions/CaptureModelTestHarness.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/MAD-1076.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/MAD-1199.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/MAD-1200.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/MAD-1294.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/basic-model-interaction.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/multiple-interaction.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/official-project-templates.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/remove-selector-interaction.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/test-harness-url.stories.tsx create mode 100644 services/madoc-ts/stories/capture-models/interactions/the-chain-project.stories.tsx diff --git a/services/madoc-ts/.DS_Store b/services/madoc-ts/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9aa89702cce05bc4ea23472af0453753b1583d59 GIT binary patch literal 8196 zcmeHM&2AGh5FRI?Nt*tNwx~ivT9s>4nwCq&C4}_Q1D6KDfuE4Swo5m=QL<^Ms-oP1 zSKt-6^AH?)4DOuZ8}FvFcQ;i}2vocBj@O=VY=1Mh<1G=1%CKD`S|g$yGRvg`9tnxt zImc2etXYQ?z!MF~N3K(S--&6P3d4Y5z%XDKFbo(5{sjhbW^>9-IroEB^E3y22(K6-fTP`Fb;xok z$AJvsBmy{zuxAn8P=xFqI7_ONCpsk^bSb8?>idmYXp^{HuUk0K-UxZB6_n_g|!x)U_^b{heMqR z=3@l2kc7d=+OTmN!TyS17pGuPF@Yj3 zomtp%(O(~Sda!5nn~sr8shFH(=RHtE;NZWAKC`6iFT-2h3-j8L9xr0dEjmU`7d3;x zmk}$>muh8Ej*Y+dG|0hUQO=f`mXV;NnJ=-R@RbI_re;J7Y-m~qjE7@uGEQbR#v${e zp=LFd%`ut8iH4bjjwj=!4m6jB7-5`Cu%O}6!1bsbnFr7IgEHGZ9nc;)sM1r&P4v*# z5poVHBCC5Oq79E~_UE9@-sD(qp;QD*BX}kn2|RkW7sed}(*@#+SxfKzROXWgu1_s} zRK(s>wRjFbxa^~cIFsx(ReMfGA|s06-Uo&w#HTa87r}E^aWl{fA~)U#&v&YBb2R9M zyW6*B&#?#o*47V^N@s4(WV88fA^W;^?2T%E!ygVRjs8o1d**pgb5d?JpWB^LeQD*1 z=lTuX?erzUZg-&a>V@sLy-~#*y6u6))`Et7K9{dA-90}q?v!@6H>`)H(#3{#zPr7J z^6vJ&*F2H{tyM10<2;^Z)<= literal 0 HcmV?d00001 diff --git a/services/madoc-ts/fixtures/96-jira/MAD-1076.json b/services/madoc-ts/fixtures/96-jira/MAD-1076.json new file mode 100644 index 000000000..702111a5d --- /dev/null +++ b/services/madoc-ts/fixtures/96-jira/MAD-1076.json @@ -0,0 +1 @@ +{"id":"5246535f-ee25-4543-ae6c-3f9e75708817","structure":{"id":"e5ec4a70-173c-4f33-86ca-f475ef5b1880","type":"choice","label":"OCR Correction","items":[{"id":"9f5624fc-d05d-4ccc-b674-23f72d0c1e0e","type":"model","label":"Default","fields":[["ocr-correction",[["lines",["text"]]]]]}]},"document":{"id":"69e2a9cb-07e4-4db4-a13d-b051d203d466","type":"entity","label":"OCR Correction","properties":{"ocr-correction":[{"allowMultiple":true,"id":"09e2fa81-b120-46e1-9238-6433cb8fbad2","label":"OCR Correction","labelledBy":"lines","pluralLabel":"OCR Correction","properties":{"lines":[{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"ef7ec2ce-fa45-474f-898b-377f68c6edd5","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b0dc4368-476c-4428-8a93-1eab6872a26a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"55600ac5-2ad9-480b-a0e3-803d43e0b139","state":{"height":16,"width":44,"x":203,"y":240},"type":"box-selector"},"type":"text-field","value":"1����"}]},"selector":{"id":"23532e36-2af8-4864-85c7-15dc97e953cb","state":{"height":16,"width":44,"x":203,"y":240},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"8bf14c1d-f3bd-4f6d-a96a-46dd8336b38e","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"aa576e54-67b8-4bec-bf35-ab81e8daa55c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"829a03a1-0609-4c47-9da8-f6638aaffa9b","state":{"height":93,"width":112,"x":171,"y":261},"type":"box-selector"},"type":"text-field","value":"��)"}]},"selector":{"id":"cebd9a0f-fff1-4dcd-bce4-a83b4526d240","state":{"height":93,"width":112,"x":171,"y":261},"type":"box-selector"},"type":"entity"}]},"selector":{"id":"dc0349fa-21f9-4f91-a23b-f41057cbbbd7","state":{"height":114,"width":132,"x":156,"y":240},"type":"box-selector"},"type":"entity","profile":"http://madoc.io/profiles/capture-model-fields/paragraphs"},{"allowMultiple":true,"id":"0208f332-f190-4965-965b-344c80800341","label":"OCR Correction","labelledBy":"lines","pluralLabel":"OCR Correction","properties":{"lines":[{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"24db06c6-0266-4d23-8ff4-da652ca488fd","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"81682c66-3db0-4811-acc5-a42b20b46612","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"70bb549d-77fd-4454-bd27-ebf6442948dd","state":{"height":49,"width":21,"x":524,"y":234},"type":"box-selector"},"type":"text-field","value":"��"}]},"selector":{"id":"6dda415e-5e22-4323-8f4b-dda426524478","state":{"height":49,"width":21,"x":524,"y":234},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"91f086f0-b1cf-4da0-a19f-373fe0a3a6ae","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"31037cb0-5112-4bba-a35c-82117e30691a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"05f97224-5aca-48ae-9d50-8a6188e13cc2","state":{"height":49,"width":49,"x":511,"y":310},"type":"box-selector"},"type":"text-field","value":"X"}]},"selector":{"id":"104058ca-23cb-48bb-b0ca-2a78f682f174","state":{"height":49,"width":49,"x":511,"y":310},"type":"box-selector"},"type":"entity"}]},"selector":{"id":"daa78362-b18b-4a4c-8ede-ae7df927a8a3","state":{"height":142,"width":420,"x":156,"y":222},"type":"box-selector"},"type":"entity","profile":"http://madoc.io/profiles/capture-model-fields/paragraphs"},{"allowMultiple":true,"id":"2f813195-70e7-4884-8e23-2ccdea67d114","label":"OCR Correction","labelledBy":"lines","pluralLabel":"OCR Correction","properties":{"lines":[{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"953f4411-36b1-4890-97aa-570ffd7502a1","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"29ec48dc-36ab-482e-b834-336bddbf8727","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f2b2108e-a80f-4fe8-8931-0e004ab5f01a","state":{"height":50,"width":46,"x":526,"y":620},"type":"box-selector"},"type":"text-field","value":"Fl"}]},"selector":{"id":"1cc13652-0e5f-41be-beb9-bf63f1c39c03","state":{"height":50,"width":46,"x":526,"y":620},"type":"box-selector"},"type":"entity"}]},"selector":{"id":"56c2a73e-1fae-4228-af6e-b849d95ea31b","state":{"height":452,"width":732,"x":156,"y":222},"type":"box-selector"},"type":"entity","profile":"http://madoc.io/profiles/capture-model-fields/paragraphs"},{"allowMultiple":true,"id":"d96178e2-1ce0-493e-9a16-6f8d9c63a0db","label":"OCR Correction","labelledBy":"lines","pluralLabel":"OCR Correction","properties":{"lines":[]},"selector":{"id":"36583892-069e-4e28-a79f-403bfe7d5dbf","state":{"height":1162,"width":754,"x":156,"y":222},"type":"box-selector"},"type":"entity","profile":"http://madoc.io/profiles/capture-model-fields/paragraphs"},{"allowMultiple":true,"id":"59275449-f335-4109-9e62-3f173d3fa738","label":"OCR Correction","labelledBy":"lines","pluralLabel":"OCR Correction","properties":{"lines":[{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"1398da6c-a857-4202-bbe4-990230f01e89","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ba5adee2-0b8c-4814-9fad-7783ce07c3d1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"c9b29950-2064-44e7-9a96-e0e5cca6da58","state":{"height":58,"width":407,"x":957,"y":195},"type":"box-selector"},"type":"text-field","value":"Nachkommen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"5bf75862-8fbb-4720-9143-2883d1959765","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"cb743e0e-f530-4637-96e0-f9b05fd43e9f","state":{"height":55,"width":316,"x":1460,"y":194},"type":"box-selector"},"type":"text-field","value":"einheitlich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"05e5bc31-a0fd-4c74-a17f-dff86c8f0221","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"62dc551c-9dd2-484e-8205-2b773ac1564b","state":{"height":72,"width":475,"x":1871,"y":194},"type":"box-selector"},"type":"text-field","value":"grau-langfl��glig"}]},"selector":{"id":"966d007d-2d01-4b6b-90c3-f2ae6b968adf","state":{"height":72,"width":1389,"x":957,"y":194},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"050efa24-2623-4aa5-8618-fa84e979d79f","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a53ce028-037e-479b-aa91-5f5337e34ee1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"86e034d4-78b1-4df4-a047-f7d2dcd24490","state":{"height":59,"width":243,"x":954,"y":275},"type":"box-selector"},"type":"text-field","value":"wurden,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"06a99daa-a6ee-41e8-83b3-f2c191bdbf5a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6ef843bf-e5a0-458f-904d-25519a76f93d","state":{"height":30,"width":111,"x":1238,"y":294},"type":"box-selector"},"type":"text-field","value":"war"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4ca530ed-1d90-41a5-bca5-f8ebe023f6ee","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"21b1afe1-00a4-49e5-abc8-168235678422","state":{"height":49,"width":133,"x":1391,"y":274},"type":"box-selector"},"type":"text-field","value":"nach"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9b948a49-3536-403a-8d75-71d317a2d5f3","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"619f78c3-a152-41c9-a890-5c9a7efa8604","state":{"height":49,"width":123,"x":1569,"y":273},"type":"box-selector"},"type":"text-field","value":"dem"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7b30a194-7374-4a00-89f5-7297463b8522","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7334d441-77bb-410a-9d9c-b13955b077ca","state":{"height":37,"width":182,"x":1736,"y":284},"type":"box-selector"},"type":"text-field","value":"ersten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c107f5f4-565c-4d4c-b182-b2d39b24754c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"dfeb62d7-aeee-4b66-82b5-2294ef2e1221","state":{"height":52,"width":381,"x":1961,"y":271},"type":"box-selector"},"type":"text-field","value":"Mendelschen"}]},"selector":{"id":"07606ae1-c595-4a96-a8d2-6707348c970f","state":{"height":63,"width":1388,"x":954,"y":271},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"d433f425-0829-4f38-94fc-8a21b6506e54","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"cca83292-67dc-4682-ada9-147ad4619dfa","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d802d6b8-c21d-46a3-817f-30827bc48858","state":{"height":51,"width":195,"x":957,"y":347},"type":"box-selector"},"type":"text-field","value":"Gesetz"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"57ff168f-25a9-443d-89bb-b3ff4d15fe9d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a4686b79-5529-4a7c-b8df-2f433c17302e","state":{"height":29,"width":64,"x":1192,"y":367},"type":"box-selector"},"type":"text-field","value":"zu"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"312e96a0-5a5b-48e6-b030-9bd747a79855","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1f0b4478-e8b9-4739-a450-af83282c2a2e","state":{"height":39,"width":270,"x":1297,"y":357},"type":"box-selector"},"type":"text-field","value":"erwarten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"da1018f5-4bc6-443f-a103-d27ef9aeac8e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6c7be8e3-4283-432c-a660-dd6195a1ecb9","state":{"height":45,"width":264,"x":1607,"y":363},"type":"box-selector"},"type":"text-field","value":"gewesen."},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ba276f4f-d80b-4ac3-ba53-422b0bfb5205","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"28337071-b413-42ab-b721-a65987325936","state":{"height":50,"width":121,"x":1909,"y":343},"type":"box-selector"},"type":"text-field","value":"Da��"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"cefdf44b-7520-4012-a90c-23d8ee0e8aa7","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"fd8bf970-01d1-4d31-ab76-dc1c5d73a8e8","state":{"height":49,"width":109,"x":2070,"y":346},"type":"box-selector"},"type":"text-field","value":"sich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e8c24fd5-66c2-4765-b924-aa265446ef78","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a1d82095-c3d4-4c03-983b-80deaaa287b1","state":{"height":49,"width":126,"x":2220,"y":345},"type":"box-selector"},"type":"text-field","value":"aber"}]},"selector":{"id":"1c8d2835-9a4a-424f-803d-2c2f7a55eb30","state":{"height":65,"width":1389,"x":957,"y":343},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"72679d5e-a8d3-4daf-97b2-f98562b82138","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"fca8fdc6-7bf2-4c76-8593-b05e988545d9","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"24cf0c7f-c76c-4ba2-906c-3f6c54d5d1b5","state":{"height":46,"width":56,"x":956,"y":423},"type":"box-selector"},"type":"text-field","value":"in"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e84ce791-ed11-476d-8436-387dc19b77cb","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4a17d5b9-ade2-420a-a984-777699e0876d","state":{"height":49,"width":96,"x":1045,"y":420},"type":"box-selector"},"type":"text-field","value":"der"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"af1c4232-b00a-4fd3-b5a7-5c02b4e42bf5","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"2c7635fd-775e-4db6-904c-7142e6ed407b","state":{"height":47,"width":229,"x":1171,"y":421},"type":"box-selector"},"type":"text-field","value":"zweiten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"028fb116-8327-499f-b572-576fa3c4a4b7","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"727eef50-d234-4e4b-90bd-ccea33c8bd51","state":{"height":63,"width":740,"x":1431,"y":416},"type":"box-selector"},"type":"text-field","value":"Nachkommengeneration"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"312afc1c-14c9-4ef4-8ba7-d8a71bec1a05","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"57e3eef3-be0f-4ef9-88fd-c34a30c05603","state":{"height":49,"width":142,"x":2202,"y":417},"type":"box-selector"},"type":"text-field","value":"nicht"}]},"selector":{"id":"3da33b1e-40e9-4a7c-b496-80cb731249e6","state":{"height":63,"width":1388,"x":956,"y":416},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"2eeac56f-202e-4554-886d-6eace4c985b6","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3bc39b2d-3d32-455b-b42d-265b53c305cb","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"14a8bf1e-5cee-45da-9806-0a22279a3436","state":{"height":49,"width":86,"x":958,"y":493},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"437c97ee-5da7-4350-8e0d-89ffce7f04a1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6c50fc52-498f-4635-b387-8c3f66eab215","state":{"height":50,"width":379,"x":1086,"y":490},"type":"box-selector"},"type":"text-field","value":"Verh��ltnisse"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e1bb90fb-3bed-4676-b109-7e10f8b70ce5","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"92566c33-16c2-4e58-998b-20c0a1b6c21e","state":{"height":49,"width":92,"x":1510,"y":490},"type":"box-selector"},"type":"text-field","value":"des"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b5850ad9-4f87-47d3-b8fc-67164a0222d1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"68207676-db3f-44d3-8dea-b819f56c7689","state":{"height":50,"width":466,"x":1643,"y":488},"type":"box-selector"},"type":"text-field","value":"Erbsenversuchs"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7a90a002-d95b-45b1-a823-c7fb728fe9db","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"3d229f53-3a3b-4af8-8187-599aea9cb424","state":{"height":58,"width":190,"x":2154,"y":488},"type":"box-selector"},"type":"text-field","value":"���Gelb��"}]},"selector":{"id":"08d14bc0-3bd3-4314-afaa-f6e501dd4b11","state":{"height":58,"width":1386,"x":958,"y":488},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"bb6c43d9-abc8-464b-91ff-b30be8b65a14","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c3bb1544-016a-48fa-9c8e-c5fd76073fa3","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"aacd93a3-7b6c-4fcc-a5dd-7578d0c5f13e","state":{"height":50,"width":139,"x":957,"y":563},"type":"box-selector"},"type":"text-field","value":"rund"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c7529158-3075-44b7-82ea-d53d4b9017ee","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"3aafa707-ac74-4f97-9be3-3d154e4ec152","state":{"height":52,"width":52,"x":1149,"y":560},"type":"box-selector"},"type":"text-field","value":"X"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f92fd965-4130-41eb-8792-49062dc64a97","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"58216e80-9d6c-4009-b441-be74e7d2c0b7","state":{"height":63,"width":426,"x":1254,"y":561},"type":"box-selector"},"type":"text-field","value":"Gr��n-runzlig\""},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6ab45135-ce01-4781-9c9a-7fea7a705225","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"886a5a96-242b-4032-8841-43f79d0965eb","state":{"height":46,"width":100,"x":1727,"y":563},"type":"box-selector"},"type":"text-field","value":"mit"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"d9564ee4-35c2-47ea-917c-520d0244c46a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"88099f1e-64d1-425c-bdf5-e33b531b4f1c","state":{"height":50,"width":158,"x":1872,"y":558},"type":"box-selector"},"type":"text-field","value":"ihren"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4a89820d-5e85-42b1-8585-ba4ceb12f291","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7cdf19db-d71d-4ae0-91e5-ebc72f466df8","state":{"height":47,"width":115,"x":2074,"y":563},"type":"box-selector"},"type":"text-field","value":"vier"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a16ed039-278a-43db-b1cc-4886b34fa910","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"01a2d1a5-87e3-43aa-b614-91404952905e","state":{"height":30,"width":111,"x":2232,"y":580},"type":"box-selector"},"type":"text-field","value":"ver��"}]},"selector":{"id":"e4547564-b09a-40f6-8d28-adbe40da6d80","state":{"height":66,"width":1386,"x":957,"y":558},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"3ae294be-124d-4e75-928d-6cb15a1b3b53","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"817ef792-9ef4-4256-8b56-93bd9f91883f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"262b47e1-844a-42ab-a99d-2a466d5a152e","state":{"height":51,"width":324,"x":955,"y":635},"type":"box-selector"},"type":"text-field","value":"schiedenen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"da835529-2fa1-4e97-9157-05e15383ccd5","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"fd2ccfd1-971e-4c3b-b54d-70a6d6bd3a29","state":{"height":50,"width":477,"x":1366,"y":633},"type":"box-selector"},"type":"text-field","value":"Kombinationen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ab50b745-12ff-45b4-973d-4b61746edeef","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"dd9cb280-8f8c-4efe-8b5a-52c5fe4018ca","state":{"height":60,"width":415,"x":1930,"y":632},"type":"box-selector"},"type":"text-field","value":"wiederholten,"}]},"selector":{"id":"aaa65339-e34f-43b0-8f4d-fc457a9c4c1a","state":{"height":60,"width":1390,"x":955,"y":632},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"e1f32f53-06b0-4e79-b9c8-ed71789607ec","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2ad5741a-448e-4030-a271-08bac3c30f4d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b9854b87-1068-4e3b-b72c-6bbaccd54108","state":{"height":49,"width":105,"x":958,"y":709},"type":"box-selector"},"type":"text-field","value":"da��"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e1a5caea-fa22-4c19-89c9-587170a84835","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5f017b49-a979-4c21-99d5-3c031d6251ae","state":{"height":50,"width":268,"x":1091,"y":706},"type":"box-selector"},"type":"text-field","value":"vielmehr"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"873dfd05-8ab6-4a5d-b1ad-a061d59728d1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ce7ced7f-a45e-418c-b3e4-f64b04d77fcb","state":{"height":29,"width":105,"x":1387,"y":726},"type":"box-selector"},"type":"text-field","value":"nur"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c97cb424-2d0d-4067-b35d-bdcc57399513","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"c7d758a9-cae2-4733-ad95-7032f4a55a76","state":{"height":50,"width":87,"x":1520,"y":704},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"d87afe57-4ca9-4eb6-8530-ac98a00fa83d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0e8bb64f-d927-4c7a-b9ce-6fa3b44689ed","state":{"height":50,"width":203,"x":1634,"y":704},"type":"box-selector"},"type":"text-field","value":"beiden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e88b4a5a-fc02-416d-9dca-ae184f24db6f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4ce928c2-e220-4e0d-ab58-916af8360f2a","state":{"height":50,"width":318,"x":1867,"y":703},"type":"box-selector"},"type":"text-field","value":"elterlichen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b4e3212f-ee63-4814-a25b-ceaae9b64a56","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"eb4397c8-2905-4b78-87f0-aad2d65729be","state":{"height":49,"width":130,"x":2213,"y":704},"type":"box-selector"},"type":"text-field","value":"Aus��"}]},"selector":{"id":"cb796bd8-b7b3-41a6-8284-e64346f4aee1","state":{"height":55,"width":1385,"x":958,"y":703},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"5fb18668-1b40-4222-bd6e-d9096fd355d6","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9ef3470b-cf47-401b-92ac-b595acb732e7","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"29362cd0-ac11-435a-95fc-6af1bf59ecdc","state":{"height":64,"width":386,"x":957,"y":780},"type":"box-selector"},"type":"text-field","value":"gangsformen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"08c7c6c9-a648-40ec-afad-1f3364d42feb","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5b78110f-0eab-41dd-b091-5972e17a901f","state":{"height":49,"width":205,"x":1386,"y":778},"type":"box-selector"},"type":"text-field","value":"wieder"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2ae1b282-d8c4-4b43-a385-57a71cf7aa34","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6c43a9d0-658a-41b2-a024-8bfbdefb20f2","state":{"height":57,"width":302,"x":1634,"y":777},"type":"box-selector"},"type":"text-field","value":"auftraten,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4cebcd9f-ba02-4739-a78e-7f23803ca91f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"fd6494c3-864b-45b8-bb46-e95f4802c877","state":{"height":50,"width":172,"x":1980,"y":775},"type":"box-selector"},"type":"text-field","value":"damit"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6091fca8-6653-42b8-bf91-86cafe36e547","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"08408c20-8525-481e-a6b6-444a8cf2c087","state":{"height":49,"width":151,"x":2193,"y":776},"type":"box-selector"},"type":"text-field","value":"hatte"}]},"selector":{"id":"17d7e9b1-5383-4565-b2c3-abfeb637655a","state":{"height":69,"width":1387,"x":957,"y":775},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"737321c1-18e6-401c-ac4f-65e6d217d703","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"461513f8-31fe-4a2c-97d5-a4a5458a8a41","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"619ca94d-bd84-48c7-a1da-23b8cc42a101","state":{"height":50,"width":257,"x":955,"y":850},"type":"box-selector"},"type":"text-field","value":"niemand"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ab43a0df-5fbf-42bf-a98d-34a5e4764bab","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ff37770a-a258-4617-905b-aea5ac91afe4","state":{"height":64,"width":304,"x":1285,"y":850},"type":"box-selector"},"type":"text-field","value":"gerechnet."},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"35f8dd9a-bfdb-42b8-91af-f092ffe1fc75","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5d37278b-e994-4a3f-a5f7-683232b06da6","state":{"height":48,"width":102,"x":1658,"y":850},"type":"box-selector"},"type":"text-field","value":"Die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"fd1d8000-9ac3-4093-baa4-e07fc6e144d5","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6d739374-5357-4898-976a-b8c8241f5bd6","state":{"height":49,"width":203,"x":1830,"y":848},"type":"box-selector"},"type":"text-field","value":"beiden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"68003446-e0aa-4245-be8b-a8d53e63440a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b35a73de-cfd2-4ae2-98c7-338d5fc9f02f","state":{"height":48,"width":238,"x":2105,"y":849},"type":"box-selector"},"type":"text-field","value":"anderen"}]},"selector":{"id":"211d050b-c049-4a85-9298-eb88f13e8e38","state":{"height":66,"width":1388,"x":955,"y":848},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"56476073-dd3b-4ede-a83c-63071ed00d35","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"18454abd-41d5-4d5c-bd73-f0c4da1553b1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"57ad2c87-7840-4c02-b3ca-7d81a7983674","state":{"height":49,"width":495,"x":955,"y":923},"type":"box-selector"},"type":"text-field","value":"Kombinationen:"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"48df620d-f2f9-44d2-9cb7-670978ffa54c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"470449e4-12d4-4594-9d41-1ae263b68a07","state":{"height":65,"width":591,"x":1549,"y":918},"type":"box-selector"},"type":"text-field","value":"Schwarz-langfl��glig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"dd0312ff-fa44-4fc5-a472-8dcc085a0417","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a7f3136a-0625-4f8a-a1cc-e8d392e95b62","state":{"height":49,"width":110,"x":2233,"y":920},"type":"box-selector"},"type":"text-field","value":"und"}]},"selector":{"id":"07129e79-52a6-42f3-877f-d0d2865d9816","state":{"height":65,"width":1388,"x":955,"y":918},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"1e7c17b0-fcba-40d6-85d6-459e37ec4228","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"20d264da-d05a-47fa-9eaa-33fefdefaf8e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"eb0623e7-171a-46aa-a2d3-4e3339d588f2","state":{"height":64,"width":624,"x":955,"y":993},"type":"box-selector"},"type":"text-field","value":"Grau-iStummelfl��glig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"71e44631-213b-47cf-8d71-84f6a291f78a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"821c1778-9568-4212-b3fd-baba5424b89a","state":{"height":29,"width":182,"x":1664,"y":1012},"type":"box-selector"},"type":"text-field","value":"waren"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9496c4d8-9ff4-45db-bcc6-07e5030090e6","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"36778a29-6357-4200-ae7f-b908cbfbe39b","state":{"height":64,"width":407,"x":1934,"y":991},"type":"box-selector"},"type":"text-field","value":"ausgeblieben!"}]},"selector":{"id":"11db6bed-2df8-4e55-8ec4-185a9ef7a3a5","state":{"height":66,"width":1386,"x":955,"y":991},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"0d63db55-1358-4b83-bbe8-c60d80945784","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"da8026fa-6a24-4f5b-a307-b4b9ece38e8a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"aa66fd76-8f3e-40d7-88e2-b7ac754e1d65","state":{"height":49,"width":149,"x":955,"y":1067},"type":"box-selector"},"type":"text-field","value":"Aber"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b9311a0a-29ec-4e89-b175-35b52151442a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d09a8134-8b34-462a-a26b-d0d323d0c082","state":{"height":62,"width":229,"x":1141,"y":1067},"type":"box-selector"},"type":"text-field","value":"Morgan"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7f264f4a-d4a9-4b82-867a-f7cd11e80b41","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"65281ebc-48c5-42d7-96fd-6d3cd8d7305e","state":{"height":49,"width":134,"x":1412,"y":1066},"type":"box-selector"},"type":"text-field","value":"fand"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b92ded61-ce36-42f1-836a-c5cc475339d9","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7c3e2b53-6560-4595-9398-8dcad40d07da","state":{"height":49,"width":88,"x":1588,"y":1065},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"affd2e0c-fbbe-4d59-a1f3-d9081c2e9cad","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"afd1f2d6-d92b-44d2-b29f-65832f66ab13","state":{"height":62,"width":220,"x":1713,"y":1064},"type":"box-selector"},"type":"text-field","value":"L��sung"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"0ff7de65-db60-4f65-92b8-cf222411cb94","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e8b0df5c-9250-4567-be2e-89d74c725641","state":{"height":51,"width":93,"x":1974,"y":1063},"type":"box-selector"},"type":"text-field","value":"des"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"814d1d60-d50d-4798-a886-52eb8a81b4d4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e12e7103-d00c-4cac-b868-3f8a26cd1b97","state":{"height":50,"width":233,"x":2108,"y":1063},"type":"box-selector"},"type":"text-field","value":"R��tsels:"}]},"selector":{"id":"2ce1de02-61eb-46e4-8dd2-ab14a19b6edb","state":{"height":66,"width":1386,"x":955,"y":1063},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"77d4027f-da91-4c28-8659-cc71867642dc","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a221fd1f-b28e-4270-9f8f-968d4341d963","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"efb1b156-f836-471f-a0c8-4b3125e0febc","state":{"height":29,"width":57,"x":957,"y":1160},"type":"box-selector"},"type":"text-field","value":"er"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"55089759-43fe-4294-905a-c49688fb2858","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8e856956-d7a2-4df3-b4e6-bae26abe5a84","state":{"height":49,"width":211,"x":1059,"y":1139},"type":"box-selector"},"type":"text-field","value":"konnte"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"755a8162-fc73-47d6-88ae-acd1239c1c0b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e242140b-5e85-4636-b04e-1db33efc5645","state":{"height":60,"width":328,"x":1322,"y":1137},"type":"box-selector"},"type":"text-field","value":"feststellen,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"240841a5-bc7a-4f36-88b8-fb82ffb89757","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"452f1d59-81ec-4a22-b526-fef5eea993f9","state":{"height":49,"width":106,"x":1702,"y":1137},"type":"box-selector"},"type":"text-field","value":"da��"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"43eee5bc-7f80-42f2-ba44-5b132f1b3e79","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5f5894ec-62a5-4a26-bf19-600d199e04cc","state":{"height":49,"width":87,"x":1862,"y":1136},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"71a5c995-de31-4cc8-893e-54c0ac089d31","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5d7c823e-2d7c-4d72-84df-d005085ddae3","state":{"height":63,"width":347,"x":1999,"y":1135},"type":"box-selector"},"type":"text-field","value":"Erbanlagen"}]},"selector":{"id":"738df411-bd7b-4305-acb5-a273b95cb90b","state":{"height":63,"width":1389,"x":957,"y":1135},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"85133bd3-52a2-4303-9be5-809491597069","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"770d793c-d338-4096-997f-ecb63c920c9a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f9cfb2f4-5326-4fc6-b12e-3946dd0c8923","state":{"height":50,"width":146,"x":957,"y":1210},"type":"box-selector"},"type":"text-field","value":"Grau"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1187dc4f-4e1d-49f6-9da4-31cbb73fb297","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a5aaa6c6-8737-4227-bc6b-8ede1afa7548","state":{"height":50,"width":112,"x":1162,"y":1209},"type":"box-selector"},"type":"text-field","value":"und"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4d35079c-d7ce-47ba-913b-1f083b211714","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"301c4b86-ad15-426d-b307-bfa96c60f303","state":{"height":64,"width":344,"x":1333,"y":1209},"type":"box-selector"},"type":"text-field","value":"Langfl��iglig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a10f5a49-56e8-4f08-9789-3e69bcbe9062","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"dcbcc917-41b9-4e68-90e8-e6181e862824","state":{"height":60,"width":138,"x":1735,"y":1208},"type":"box-selector"},"type":"text-field","value":"hier,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"091e65a6-d727-4ae1-83ab-023aa983c988","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"bd8c97f7-2cbe-4c13-b11d-7e006b89b645","state":{"height":51,"width":241,"x":1935,"y":1206},"type":"box-selector"},"type":"text-field","value":"Schwarz"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1c167c91-9995-467f-8466-98b970d32e0e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"055a67eb-6e37-408c-a9bc-06f5308390c1","state":{"height":49,"width":113,"x":2232,"y":1207},"type":"box-selector"},"type":"text-field","value":"und"}]},"selector":{"id":"41301c4a-d0ab-443f-9986-496bc73a3dc7","state":{"height":67,"width":1388,"x":957,"y":1206},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"337fabe4-c769-4513-a90a-7c56c4a6970e","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"14c7852a-5fcd-4f14-a268-8441016fb911","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"62ce0b5f-0cf3-408e-99b1-de5681e2389e","state":{"height":64,"width":458,"x":957,"y":1281},"type":"box-selector"},"type":"text-field","value":"Stummelfl��glig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4fea716e-5dc2-49e4-a28e-2a4686c28dcd","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e29b86b6-b2f7-4d6a-a926-57861552fe57","state":{"height":50,"width":125,"x":1462,"y":1282},"type":"box-selector"},"type":"text-field","value":"dort"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"660e4563-c2ce-48db-a1a5-e44f9c831e82","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"32ce5920-3bf1-41b2-a5fe-06f0d4968863","state":{"height":46,"width":195,"x":1631,"y":1284},"type":"box-selector"},"type":"text-field","value":"immer"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"10ae398d-90ee-4c7b-ba8c-4e192e49145e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7b689fae-4bd7-47e7-92db-e8b422a9e9c9","state":{"height":31,"width":313,"x":1872,"y":1298},"type":"box-selector"},"type":"text-field","value":"zusammen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"8e182da3-73ca-47d2-9a5f-d103fa0d5600","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"fa05378d-4e26-41b4-8efe-fce222867a35","state":{"height":49,"width":112,"x":2232,"y":1279},"type":"box-selector"},"type":"text-field","value":"auf��"}]},"selector":{"id":"e85add90-0ee1-487c-94de-51f9dc91b1c0","state":{"height":66,"width":1387,"x":957,"y":1279},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"65bdee17-4ca0-401b-afd2-22ff3f795c2f","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c912c1e3-86c8-47c7-b5bf-2b1ce0685b4a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"06e464e9-e3b9-42e7-a11c-8c3b506599fe","state":{"height":37,"width":199,"x":957,"y":1367},"type":"box-selector"},"type":"text-field","value":"treten."},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7679aa8d-29ea-446e-ae6c-6d743cfe88e9","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"09ce0207-227d-46b0-87bb-51994666be9a","state":{"height":49,"width":84,"x":1186,"y":1354},"type":"box-selector"},"type":"text-field","value":"Sie"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ff5a7fde-87a3-40de-9208-a4af45f33159","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"949ff6eb-2ffe-4141-bc4c-69bcfc92d273","state":{"height":48,"width":178,"x":1301,"y":1355},"type":"box-selector"},"type":"text-field","value":"lassen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7d0ba186-4bc5-46a7-883a-9ad5ea901e28","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7003e5a1-7b17-4736-9307-cfd240e1212a","state":{"height":50,"width":108,"x":1508,"y":1353},"type":"box-selector"},"type":"text-field","value":"sich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"35e660f4-c615-4f79-a920-a4380f97b77f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7cc47f08-36d5-42a0-9683-a46f7da1c610","state":{"height":59,"width":163,"x":1645,"y":1353},"type":"box-selector"},"type":"text-field","value":"nicht,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7e022cbd-dc4a-4c50-b9f8-841c582bb870","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"3a9dd3e3-7d4d-4a8d-9e82-b7d4d714c667","state":{"height":46,"width":100,"x":1837,"y":1354},"type":"box-selector"},"type":"text-field","value":"wie"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a5e3f522-3ca0-452e-89a6-acf46e611894","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0e340fd5-0e94-4bd9-a0d4-b33ad00ab517","state":{"height":30,"width":52,"x":1969,"y":1371},"type":"box-selector"},"type":"text-field","value":"es"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e6073735-045c-4eee-b290-69b4da0a78c4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"57f21e75-fe14-4b22-8dc0-64a13f1e331c","state":{"height":49,"width":94,"x":2053,"y":1352},"type":"box-selector"},"type":"text-field","value":"das"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"5dd2982f-721e-42e0-b3bb-2effa46a9a85","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6a49c619-f674-4bf0-9ab9-17bb0578b0bc","state":{"height":49,"width":167,"x":2178,"y":1351},"type":"box-selector"},"type":"text-field","value":"dritte"}]},"selector":{"id":"7c66fbea-bf04-4ad8-93c6-79207a82f0d0","state":{"height":61,"width":1388,"x":957,"y":1351},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"ea52db75-672e-4ec1-a555-0a5c1d03f5de","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7edf19ff-11fb-4dee-9d30-0aef0e9c1c71","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"9c5ba606-4bd2-4416-9c8e-43398859d799","state":{"height":63,"width":407,"x":954,"y":1426},"type":"box-selector"},"type":"text-field","value":"Mendelgesetz"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7cea1ee7-2a71-491d-a580-37a854d42813","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"76297647-0749-47c4-89f3-ec36816cc6cf","state":{"height":60,"width":234,"x":1388,"y":1425},"type":"box-selector"},"type":"text-field","value":"fordert,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"35f36362-f210-4732-aeda-0ac6f805d34d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"649f0e6d-0c58-4343-97b1-7996b73178a3","state":{"height":51,"width":563,"x":1648,"y":1423},"type":"box-selector"},"type":"text-field","value":"auseinanderrei��en"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"86b665a3-6a56-4296-b613-c848d5d2a1ef","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"97b4fc64-fd96-480e-8b6e-76abcd9e7635","state":{"height":49,"width":110,"x":2235,"y":1423},"type":"box-selector"},"type":"text-field","value":"und"}]},"selector":{"id":"96a3b034-ab8e-45e1-95ad-ed4de97da399","state":{"height":66,"width":1391,"x":954,"y":1423},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"3ea57cec-9da1-47f6-a55d-4911bb028c28","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a83419db-6bb0-40bc-a377-41abc59f2279","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"52eb7294-a19b-40ac-ae05-5bf02dfa2dc4","state":{"height":48,"width":106,"x":206,"y":1500},"type":"box-selector"},"type":"text-field","value":"frei"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4d26f7f9-9104-4bcb-a991-60ed46c38d3a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5a960060-be71-4fc6-a24c-bc12a40e12e8","state":{"height":60,"width":404,"x":344,"y":1499},"type":"box-selector"},"type":"text-field","value":"kombinieren,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c224e562-b449-49f6-a531-474a713b531d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1f995f14-492c-4fa1-b266-4849db419c51","state":{"height":49,"width":122,"x":780,"y":1499},"type":"box-selector"},"type":"text-field","value":"weil"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"789dd729-6cc3-40cf-961c-042a12d9b5f1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0e055e9f-2449-4ef5-963a-0dfbad95a1d5","state":{"height":47,"width":75,"x":934,"y":1501},"type":"box-selector"},"type":"text-field","value":"sie"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"94b1f0fe-f7a9-44a0-8f84-1ca6f722ac3e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"30494305-f3d6-4167-96cc-0151b3c16aa8","state":{"height":46,"width":56,"x":1042,"y":1501},"type":"box-selector"},"type":"text-field","value":"in"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1faaab27-60be-4171-96ea-b4ae12efb020","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ceb876d6-0211-442a-98af-5bd72d478d1a","state":{"height":59,"width":52,"x":1128,"y":1501},"type":"box-selector"},"type":"text-field","value":"je"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f2d8b801-3641-459b-aba8-5b363d1653a3","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"85801c59-d7f0-433a-b435-08b1b3fa2bff","state":{"height":47,"width":179,"x":1214,"y":1500},"type":"box-selector"},"type":"text-field","value":"einem"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"afe217ae-25bd-461e-a632-79aa753e6778","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ea710188-0015-4257-bc84-96497eb1213e","state":{"height":50,"width":360,"x":1425,"y":1497},"type":"box-selector"},"type":"text-field","value":"Chromosom"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"aaff6729-123a-4a22-b70b-0da24ee7a22e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8a72abca-b094-4bfb-9c7d-e473fd6388a5","state":{"height":63,"width":201,"x":1815,"y":1496},"type":"box-selector"},"type":"text-field","value":"liegen:"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4389f986-eee3-4771-ba47-1728a4c093d7","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ea6f7928-7710-4950-a714-50a9f00b47ed","state":{"height":51,"width":148,"x":2053,"y":1494},"type":"box-selector"},"type":"text-field","value":"Grau"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"d25fc754-121f-47a5-b90e-715bec423892","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ce3b8d07-ea3d-414c-861b-306a7e21fbbb","state":{"height":49,"width":110,"x":2233,"y":1495},"type":"box-selector"},"type":"text-field","value":"und"}]},"selector":{"id":"cab2b703-5a3a-4a28-92b3-b04ee70dbec8","state":{"height":66,"width":2137,"x":206,"y":1494},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"c3ff0c8b-7d78-4786-a669-af39889de531","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"49d511fa-5990-450e-9be6-8beba92b1cc7","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"9dca7d4c-4073-43fc-b3bb-b7fcf35960e0","state":{"height":64,"width":319,"x":205,"y":1570},"type":"box-selector"},"type":"text-field","value":"langfl��glig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e5988144-4b49-4630-b249-3a1dc98dbf80","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"79c2efef-bced-4f5b-8bda-fc344bc89c5c","state":{"height":46,"width":56,"x":557,"y":1573},"type":"box-selector"},"type":"text-field","value":"in"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"30c0f31b-48cf-4e07-aaa7-95cff1bd291d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e50c1b1f-c8d2-481d-94c5-306e961f1736","state":{"height":58,"width":195,"x":648,"y":1573},"type":"box-selector"},"type":"text-field","value":"einem,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f7dead2f-5db0-4da0-bcee-a2e1d2b95e89","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7b3fbf9e-a9ac-4f90-9ade-51ac99f315f2","state":{"height":50,"width":240,"x":878,"y":1570},"type":"box-selector"},"type":"text-field","value":"Schwarz"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"88ad9b23-02aa-478d-8fcd-1d7d5741002a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7c499a94-ec9a-4941-ad1a-04319eedcadb","state":{"height":49,"width":113,"x":1150,"y":1571},"type":"box-selector"},"type":"text-field","value":"und"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2f43c5c1-ad3e-408d-ad41-967e5e654958","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5aed7a25-dd05-43bf-b7a7-17d2c079fe99","state":{"height":64,"width":450,"x":1298,"y":1569},"type":"box-selector"},"type":"text-field","value":"stummelfl��glig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"19514d5f-2efe-4e80-89e6-36004f8f5162","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8d381a0a-e44e-4a1e-9b37-af40108993a3","state":{"height":46,"width":57,"x":1781,"y":1571},"type":"box-selector"},"type":"text-field","value":"in"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"fff3834d-4d81-4deb-b06d-edc265ea61b8","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"22fa3aa1-1019-4c18-9774-7bc2ddc2ff80","state":{"height":47,"width":179,"x":1873,"y":1570},"type":"box-selector"},"type":"text-field","value":"einem"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1d67a3e0-3bb0-4865-970b-6c8d7bfc76f8","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"79c9ef42-dc33-452c-bd57-0ba6a7eef2d0","state":{"height":49,"width":256,"x":2087,"y":1568},"type":"box-selector"},"type":"text-field","value":"anderen."}]},"selector":{"id":"8e7bf5d4-17b7-4906-8674-5c452a8625b6","state":{"height":66,"width":2138,"x":205,"y":1568},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"7ddd1624-f230-45a3-8617-83ccc62de26c","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f9756e33-0d95-44e4-96e4-b216dfe349c4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d8486879-e4fc-425d-be0d-9a9dc567959f","state":{"height":48,"width":161,"x":205,"y":1644},"type":"box-selector"},"type":"text-field","value":"Diese"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c4a29637-13a4-470f-9ce3-ece12d5c1794","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"79aa9f79-b84a-4397-a585-0691d00af5db","state":{"height":49,"width":146,"x":397,"y":1644},"type":"box-selector"},"type":"text-field","value":"nicht"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2220bd86-602f-422e-b510-b6a7bd88f26b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"626e6d7e-8d09-4351-b43d-77642a23f694","state":{"height":50,"width":488,"x":571,"y":1642},"type":"box-selector"},"type":"text-field","value":"kombinierbaren"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"5f39f37f-de5b-4b16-8ed8-bc0906eeee17","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"115dd2a2-239f-47ea-b10b-793795dd3b20","state":{"height":63,"width":349,"x":1087,"y":1642},"type":"box-selector"},"type":"text-field","value":"Erbanlagen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4a8e43f8-3547-44dc-a4d4-11920cae0623","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0a4f4c6f-e917-4945-92cc-01baaec7842d","state":{"height":49,"width":121,"x":1466,"y":1642},"type":"box-selector"},"type":"text-field","value":"sind"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"45175f8a-5459-48c0-9444-13d2ae3af897","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"676648e4-0c9a-4181-a693-f297b8e0d4f5","state":{"height":65,"width":389,"x":1619,"y":1639},"type":"box-selector"},"type":"text-field","value":"���gekoppelt\"."},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7a14231b-0813-4a4d-85d7-1a639b6df401","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4429782d-ae0b-4d05-b06f-6ff3703a81c9","state":{"height":49,"width":107,"x":2038,"y":1640},"type":"box-selector"},"type":"text-field","value":"Das"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e2aeb306-c6fa-4935-a2d9-415fdf2ba522","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"95193613-bf19-4736-8125-19a3c2992689","state":{"height":49,"width":167,"x":2178,"y":1640},"type":"box-selector"},"type":"text-field","value":"dritte"}]},"selector":{"id":"1ba2860c-acb4-42a6-807f-159dfd0f8b2e","state":{"height":66,"width":2140,"x":205,"y":1639},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"e3b4787e-37cb-4ca1-8097-438fc6407493","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"64e8ee0d-8d35-43ef-a9f7-4edad3970526","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1792d4dc-2b4d-424b-a44d-f08a0f04a072","state":{"height":51,"width":344,"x":206,"y":1714},"type":"box-selector"},"type":"text-field","value":"Mendelsche"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f3a91855-b434-436f-9427-de7e8e6c0dc4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"09cfe42e-ee44-4799-9443-9ae38016a936","state":{"height":50,"width":195,"x":592,"y":1714},"type":"box-selector"},"type":"text-field","value":"Gesetz"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"58d44225-a548-46b6-b31f-d839ac9db12f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"748f3650-c65d-43cf-a042-0b42817667c1","state":{"height":50,"width":188,"x":825,"y":1715},"type":"box-selector"},"type":"text-field","value":"mu��te"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"466a2d85-5bcd-4037-af82-63c6481a1b2a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"73d6e981-27a0-4fe7-b567-a31d99fecfab","state":{"height":50,"width":111,"x":1056,"y":1714},"type":"box-selector"},"type":"text-field","value":"also"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c691a007-1727-460b-bc92-63eb5e09643c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"50ee016d-ad36-4dc0-9a1d-513e0b647f89","state":{"height":46,"width":121,"x":1209,"y":1717},"type":"box-selector"},"type":"text-field","value":"eine"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"d65487a1-bf36-4963-b6e6-42b72e540b17","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b279bf61-76af-40a1-a908-36e489350868","state":{"height":30,"width":138,"x":1371,"y":1733},"type":"box-selector"},"type":"text-field","value":"neue"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2b40d452-0d01-4cfc-bb1f-923a63140306","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d5224713-6637-4804-b6f4-b016cd7ff1f8","state":{"height":63,"width":243,"x":1548,"y":1713},"type":"box-selector"},"type":"text-field","value":"Fassung"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e0eb1d3f-aff4-48ff-8105-794ff2f1f7a7","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"950e289c-1d96-44d6-ba90-c79e161bc5ac","state":{"height":49,"width":352,"x":1829,"y":1712},"type":"box-selector"},"type":"text-field","value":"bekommen:"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7b1eb7a7-d839-4d93-b9b9-a190ab79f6d0","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b5fcb909-c1f9-4244-8cce-8434c411d426","state":{"height":49,"width":121,"x":2224,"y":1710},"type":"box-selector"},"type":"text-field","value":"Alle"}]},"selector":{"id":"9f24e76c-83ba-4616-a8cf-7ac711b79008","state":{"height":66,"width":2139,"x":206,"y":1710},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"87f23050-1c89-4158-99d6-a23a48098b4f","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"66bd7be7-bf41-4dc8-844f-d0f44a732486","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1f33a073-f94d-470a-a89b-6a345ae18ad3","state":{"height":60,"width":322,"x":206,"y":1787},"type":"box-selector"},"type":"text-field","value":"Merkmale,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3eb78a37-76fa-4eb3-bb96-b90bb51311ae","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0c3914c2-2009-4dec-857e-bf26cee379f7","state":{"height":49,"width":167,"x":559,"y":1788},"type":"box-selector"},"type":"text-field","value":"deren"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c062b665-d1bc-42c0-8977-36f91dda51f1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8524274b-8457-4671-86ae-b057ee58563a","state":{"height":50,"width":150,"x":757,"y":1786},"type":"box-selector"},"type":"text-field","value":"Gene"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ec9b512a-8947-4b72-82ea-1642839b7eb6","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"de187ea6-2b6c-46d3-ba37-e04789efd914","state":{"height":49,"width":145,"x":938,"y":1788},"type":"box-selector"},"type":"text-field","value":"nicht"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e5426a26-6f59-426c-b90c-d6ec7d75dd36","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e777c420-33e5-4bf8-95cb-1fb0659bbdd0","state":{"height":29,"width":67,"x":1115,"y":1807},"type":"box-selector"},"type":"text-field","value":"an"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e922677f-77d8-45ce-9e93-ab17630ee47f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"21cc88f0-4587-4b54-b33e-a021b16157b3","state":{"height":50,"width":249,"x":1213,"y":1786},"type":"box-selector"},"type":"text-field","value":"dasselbe"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"56209491-b922-495f-87f4-7de9b54fe0e0","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d7f52b70-65c5-466b-86f2-321bad171966","state":{"height":51,"width":359,"x":1494,"y":1785},"type":"box-selector"},"type":"text-field","value":"Chromosom"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7395c006-6f62-43b6-8f68-b49e0104825a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e3ccd5a1-4b0a-469f-b3d9-e650782bd04e","state":{"height":64,"width":294,"x":1882,"y":1784},"type":"box-selector"},"type":"text-field","value":"gebunden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ad67a901-b54d-481e-b081-bcd58bde5f89","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ffca73ab-095d-4754-b5cc-096e51bb0872","state":{"height":59,"width":141,"x":2206,"y":1782},"type":"box-selector"},"type":"text-field","value":"sind,"}]},"selector":{"id":"87b18654-d799-4aeb-a1fe-762278ae7408","state":{"height":66,"width":2141,"x":206,"y":1782},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"2b6a692d-5ab0-4534-ae96-13f5a9ad8c39","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1cb73880-cf33-46c7-8914-98f1509f7ba8","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"cc522349-3086-4410-87bc-e0b42c47e723","state":{"height":50,"width":267,"x":205,"y":1859},"type":"box-selector"},"type":"text-field","value":"vererben"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"69fc1300-4fb1-4f6a-b1de-23e25455d8e9","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"125a79b7-8e8a-4a00-8445-71105d862d48","state":{"height":49,"width":106,"x":519,"y":1861},"type":"box-selector"},"type":"text-field","value":"sich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ec9401a3-811c-4ead-ad34-b3902f90c296","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1fc1e671-4b91-4898-81f6-4376b26e222f","state":{"height":64,"width":354,"x":669,"y":1859},"type":"box-selector"},"type":"text-field","value":"unabh��ngig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1a6e71d1-c69f-479d-b126-9d48c151e5fd","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1914da3a-33d4-4020-8010-c5f5946ceece","state":{"height":59,"width":396,"x":1069,"y":1859},"type":"box-selector"},"type":"text-field","value":"voneinander,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"64a03e6e-53d0-47d8-8a76-842bc88774bb","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b29bf165-4727-46c7-aba1-836a4898fd20","state":{"height":49,"width":224,"x":1511,"y":1858},"type":"box-selector"},"type":"text-field","value":"k��nnen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"0334784d-93e2-48bd-96c1-f0b430917d22","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0edb74d6-e687-478b-952c-6446d5a8ad5f","state":{"height":58,"width":132,"x":1784,"y":1858},"type":"box-selector"},"type":"text-field","value":"also,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6317f370-83ca-4b2a-a1a6-7e3423b039aa","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6bf49918-914e-422b-ac30-7b718ce3d9b3","state":{"height":29,"width":158,"x":1962,"y":1876},"type":"box-selector"},"type":"text-field","value":"wenn"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1fb97304-8c32-48d9-8ce6-502ec4b59b55","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"251cbd69-86c9-4b9d-9ed2-97f17d035e24","state":{"height":47,"width":77,"x":2167,"y":1859},"type":"box-selector"},"type":"text-field","value":"sie"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"29996fab-c515-44d3-9273-2b8e3eec160e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"fa829cca-d933-4ba7-8923-9b4c795ae6da","state":{"height":46,"width":56,"x":2291,"y":1857},"type":"box-selector"},"type":"text-field","value":"in"}]},"selector":{"id":"28be1f32-8c03-4510-824f-e6bf7c5ee55a","state":{"height":66,"width":2142,"x":205,"y":1857},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"5768a620-5be0-46e6-95ed-b8c3d3641e80","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"686cf947-ac5a-4b39-bea2-48dcd10833ff","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f72c8e9b-6851-4c97-8585-a45a322d64eb","state":{"height":47,"width":153,"x":206,"y":1934},"type":"box-selector"},"type":"text-field","value":"einer"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f4ad7942-9a22-4c99-947b-4d7557597356","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"051532af-81e2-4add-8f71-73ff5b9ce3db","state":{"height":50,"width":337,"x":408,"y":1930},"type":"box-selector"},"type":"text-field","value":"Generation"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"84dc9741-0795-4733-bb6e-613398749b28","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"eb068f35-e5dc-4c56-8410-dde709f35384","state":{"height":49,"width":371,"x":795,"y":1931},"type":"box-selector"},"type":"text-field","value":"miteinander"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e56323c8-9c49-454d-bc34-601a861282a9","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6af23908-ceb4-4e18-bdf6-261c4c2689da","state":{"height":50,"width":323,"x":1214,"y":1930},"type":"box-selector"},"type":"text-field","value":"verbunden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a5d3ea50-b709-4125-b4cc-31f12ca36452","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b0316b16-69f8-4ccd-b9fb-a9c58c213d10","state":{"height":40,"width":201,"x":1587,"y":1949},"type":"box-selector"},"type":"text-field","value":"waren,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"deeebe75-a2a2-4be0-9a65-38e7486e8b43","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8f34b601-56a2-4d11-85e9-9a5233fc25a3","state":{"height":46,"width":57,"x":1836,"y":1932},"type":"box-selector"},"type":"text-field","value":"in"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3d1c3a31-805b-4967-8562-b07c836eec33","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d533f19c-3bac-4460-b072-f57834213cd5","state":{"height":49,"width":97,"x":1943,"y":1928},"type":"box-selector"},"type":"text-field","value":"der"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c82fde33-86f8-4fb0-a6e9-4e9265c5d66f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d0280c13-81fa-4b48-8f0c-66a21b428c4d","state":{"height":49,"width":258,"x":2089,"y":1928},"type":"box-selector"},"type":"text-field","value":"n��chsten"}]},"selector":{"id":"a772d8b5-2504-4009-86b0-d3ba4c17240a","state":{"height":61,"width":2141,"x":206,"y":1928},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"116ccb4a-b5b0-4b2f-b2d8-0b22d42a82fb","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"867117d0-6a0e-4d33-90b6-dce27b9caa10","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f4d26960-a4ba-4957-9d05-cf6517bf76f6","state":{"height":50,"width":254,"x":206,"y":2015},"type":"box-selector"},"type":"text-field","value":"getrennt"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b3b5a20b-1ac6-4782-9cbb-d9db4c8ea4b4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"19e1da73-6ccf-43a1-9b1f-67b8535cda99","state":{"height":59,"width":302,"x":506,"y":2004},"type":"box-selector"},"type":"text-field","value":"auftreten;"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"db5374c8-c4fb-44aa-9634-63423eebfee1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7de04875-f66a-45fd-9352-ce669e10bda3","state":{"height":46,"width":78,"x":850,"y":2006},"type":"box-selector"},"type":"text-field","value":"im"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7598329f-c91d-4431-a253-b234f3848091","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e87ad299-b288-4499-9e6a-1b66f641b738","state":{"height":64,"width":242,"x":970,"y":2003},"type":"box-selector"},"type":"text-field","value":"gleichen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9c449c4f-099a-4baa-bde8-45008ea84777","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"2f1ce24a-0711-420b-8cad-5b471ec1fcca","state":{"height":49,"width":360,"x":1255,"y":2003},"type":"box-selector"},"type":"text-field","value":"Chromosom"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"538cebc0-c708-4bd3-a102-1d9df69a9d3d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"99089565-9e74-4513-b0b0-d9c6c308cfb1","state":{"height":65,"width":333,"x":1658,"y":2000},"type":"box-selector"},"type":"text-field","value":"gekoppelte"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"37dba1a6-1587-4077-bd27-8333e9c9448c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"674ba746-ae3e-4ebf-ad0b-ea60868b3942","state":{"height":50,"width":149,"x":2036,"y":1999},"type":"box-selector"},"type":"text-field","value":"Gene"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"fbf057cd-42e0-4ee4-8451-f5ba864ae83c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"57301247-c40b-4222-9936-d29ada3f105e","state":{"height":49,"width":115,"x":2230,"y":1999},"type":"box-selector"},"type":"text-field","value":"hin��"}]},"selector":{"id":"6dbf3b81-bc3e-452d-823e-d0662759d858","state":{"height":68,"width":2139,"x":206,"y":1999},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"c65d658b-a9af-4a09-befc-bbc0f7d207d7","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2306bd81-4c0e-433c-96d5-9ec9c41e865a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"296b2e10-3850-4ada-a724-53d15ace5c39","state":{"height":44,"width":171,"x":205,"y":2095},"type":"box-selector"},"type":"text-field","value":"gegen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a93e6ecc-cf62-4a2f-b1a8-f87640e31488","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"847de8e4-36c6-4e1a-9c4e-7c9e89d30487","state":{"height":49,"width":221,"x":414,"y":2077},"type":"box-selector"},"type":"text-field","value":"werden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2e32c3f4-b53e-49e2-a606-50dbf63e0a43","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7f9921c4-89b5-4771-9fb9-1de730c9cbc3","state":{"height":49,"width":146,"x":673,"y":2076},"type":"box-selector"},"type":"text-field","value":"nicht"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"5bbaabc3-9b15-4123-abae-69967d8ddf3b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1dd4519b-c51f-43e2-8b3f-8b47b6d1e1a8","state":{"height":49,"width":105,"x":859,"y":2075},"type":"box-selector"},"type":"text-field","value":"frei"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"8edba567-ff0b-4d79-832c-fc55f4204dbe","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"85ed0431-c889-457e-87e1-347f5edd2a47","state":{"height":49,"width":355,"x":1004,"y":2075},"type":"box-selector"},"type":"text-field","value":"kombiniert."}]},"selector":{"id":"ebe8bf6f-41d1-43b7-928f-696827e3e98e","state":{"height":64,"width":1154,"x":205,"y":2075},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"1c5383f4-f1c8-40ab-9a7f-bfb6d25d3ab9","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"dd715b93-d1fb-4431-8f89-590850617f7d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"9d189276-1e43-4eeb-8742-ccce0d8b3621","state":{"height":49,"width":150,"x":274,"y":2160},"type":"box-selector"},"type":"text-field","value":"Aber"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"da5a0f1e-5155-4816-9c7d-ff11f8f4179b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6557a0ff-04c3-427b-a7c1-c4970c97831e","state":{"height":50,"width":87,"x":475,"y":2160},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9471bd69-fa10-4298-b9a2-c6196d3bb7d6","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ca5fd6cf-d143-4584-b842-aea904c60c02","state":{"height":65,"width":394,"x":615,"y":2159},"type":"box-selector"},"type":"text-field","value":"Aufregungen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"906fbcdc-34a1-4b68-b66f-b2d88450d9c4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"40c3a93a-ac07-4f7b-98a6-b1dbf10108c4","state":{"height":29,"width":93,"x":1060,"y":2180},"type":"box-selector"},"type":"text-field","value":"um"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"834e077a-b733-4541-b358-40218349f949","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f8a6d222-00db-4b04-8d88-8f137d242815","state":{"height":50,"width":88,"x":1205,"y":2160},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"eff866c4-b1e0-40d2-93ee-585f560131dc","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6ab63f4b-964c-4d27-bf58-aca37d951ecb","state":{"height":65,"width":298,"x":1345,"y":2160},"type":"box-selector"},"type":"text-field","value":"Kreuzung"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"95076629-cfbf-4ee0-b4c9-9b4abbd67043","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8b651f94-b978-498a-8ef5-d4b31033a166","state":{"height":66,"width":532,"x":1696,"y":2160},"type":"box-selector"},"type":"text-field","value":"���Grau-langfl��glig"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"87354cff-b401-4f42-ab9b-c7dd23d09ab6","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8b7eacab-1040-4525-8da3-e2794f5a4dc6","state":{"height":53,"width":52,"x":2288,"y":2158},"type":"box-selector"},"type":"text-field","value":"X"}]},"selector":{"id":"0e53313e-6108-45eb-a085-c7de610637a9","state":{"height":68,"width":2066,"x":274,"y":2158},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"9669cdf7-075b-4c3e-905a-f9f2db092a01","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e8e88bf2-4a03-4910-8f9d-f8025690acd0","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"2eb570bf-1159-49a5-b468-e0d2756725a5","state":{"height":64,"width":750,"x":207,"y":2231},"type":"box-selector"},"type":"text-field","value":"Schwarz-stummelfl��glig\""},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7a03cbd5-7138-44ca-b77d-7377bbca64cd","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8bc6e484-b54c-4b06-8f07-6041b1aa9cd2","state":{"height":50,"width":201,"x":1000,"y":2232},"type":"box-selector"},"type":"text-field","value":"sollten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"5b63db5b-a29f-42f0-bfc0-45291ae09719","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"05a61383-56c8-4ac5-a543-aa7d321e495a","state":{"height":47,"width":101,"x":1239,"y":2235},"type":"box-selector"},"type":"text-field","value":"niit"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6fc35b1f-5791-466a-86ae-fc3923a377ed","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1af0eeae-f738-4dd2-af34-a16a590c67a6","state":{"height":50,"width":179,"x":1380,"y":2233},"type":"box-selector"},"type":"text-field","value":"dieser"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ec269cc2-389a-4592-8b08-60863c352520","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b23cae0b-05e4-41b2-8309-e146f2b6a643","state":{"height":50,"width":126,"x":1597,"y":2233},"type":"box-selector"},"type":"text-field","value":"sehr"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1c82bc13-449e-409c-ab2b-c0330f9ca80f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"cb6d2e7c-d7e2-4617-b5c6-84d927661644","state":{"height":49,"width":454,"x":1761,"y":2234},"type":"box-selector"},"type":"text-field","value":"einleuchtenden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"fc8f1a05-3d29-476b-8af4-cf84e97d3944","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0856bfc9-5df4-4736-b112-8aacfd39a0d8","state":{"height":49,"width":92,"x":2253,"y":2233},"type":"box-selector"},"type":"text-field","value":"Er��"}]},"selector":{"id":"6db46587-cfb0-4212-b432-006ec9fb99f4","state":{"height":64,"width":2138,"x":207,"y":2231},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"bd675f9b-fde5-44df-9f10-47f0d0fbd1c9","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"28bd1dcf-e267-4fed-994f-cf14b21eaf2b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"080b1414-086c-4882-91e0-02373f095de9","state":{"height":63,"width":234,"x":206,"y":2304},"type":"box-selector"},"type":"text-field","value":"kl��rung"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"92c7ed61-6dfe-410e-baed-6458ae0472ab","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"31fb4456-aa58-4eb9-9f9f-b2caaa10eb1d","state":{"height":49,"width":134,"x":467,"y":2305},"type":"box-selector"},"type":"text-field","value":"noch"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"d20fe194-65e2-44a4-aebf-3178be862edb","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"aa1886a6-5b8c-4626-96ee-d7df3c47e5f3","state":{"height":50,"width":145,"x":628,"y":2304},"type":"box-selector"},"type":"text-field","value":"nicht"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7bd90333-f79b-4bf1-8c9d-4330bb4140bc","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"77b202eb-f0f7-41ab-8089-bc9f557e4f5a","state":{"height":48,"width":87,"x":798,"y":2305},"type":"box-selector"},"type":"text-field","value":"ihr"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e4a4abbf-df7c-41c8-82a1-77159b89ff6d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"37e48918-006d-439c-91e3-dff0b0a92780","state":{"height":50,"width":151,"x":912,"y":2304},"type":"box-selector"},"type":"text-field","value":"Ende"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f166eec2-4d37-4d52-b8b6-2aaac3460818","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"27c94c1d-8125-45c6-afd7-8974a379615e","state":{"height":62,"width":277,"x":1091,"y":2305},"type":"box-selector"},"type":"text-field","value":"gefunden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"cb3946ee-52ab-403f-87fd-0471446ed230","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d5d138c0-17c3-40d8-96d5-faf08bbb667c","state":{"height":50,"width":194,"x":1394,"y":2304},"type":"box-selector"},"type":"text-field","value":"haben."},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"05210652-50a5-4172-bbc4-2cdd72066c32","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"73427179-f3cc-4a07-8776-c853d9cf3939","state":{"height":49,"width":263,"x":1616,"y":2305},"type":"box-selector"},"type":"text-field","value":"Pl��tzlich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"18f8a6c4-afca-4d53-adad-643171ef45ed","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"57a365b2-84e7-44fa-82c6-8dd7c643a92a","state":{"height":48,"width":231,"x":1906,"y":2306},"type":"box-selector"},"type":"text-field","value":"n��mlich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"d6efa715-bd40-4c3f-af06-dbc82169f26e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6b7ac5a4-22b1-4d88-9f8e-22ba5637c42f","state":{"height":49,"width":180,"x":2165,"y":2305},"type":"box-selector"},"type":"text-field","value":"tauch��"}]},"selector":{"id":"bb6cc2f8-23da-4dc9-8a1c-3826302ca61e","state":{"height":63,"width":2139,"x":206,"y":2304},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"2e6c607f-4968-48d7-87bd-50ca8dacad77","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"df16c513-8f0e-4496-a40e-b3426aaa5f3b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6d0deed4-7c84-4f9d-b835-bdc51ebe59b0","state":{"height":37,"width":90,"x":206,"y":2387},"type":"box-selector"},"type":"text-field","value":"ten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"50ab0504-fef2-40ac-9e5f-03bad579a62a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"3955f702-3d57-4429-b7a9-5a688103823d","state":{"height":50,"width":89,"x":337,"y":2375},"type":"box-selector"},"type":"text-field","value":"bei"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"dc50059c-67da-48b8-af80-b9c8d2e60127","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"044fb776-f97d-40fb-a4ad-558c3a111265","state":{"height":50,"width":397,"x":465,"y":2375},"type":"box-selector"},"type":"text-field","value":"wiederholten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"60747096-c6c9-4deb-8a35-69355cf05a12","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b227cc40-99aa-4624-bb82-cacb124d1cbf","state":{"height":49,"width":313,"x":903,"y":2376},"type":"box-selector"},"type":"text-field","value":"Versuchen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"5708fa87-fb44-4bef-b68d-391e612ff89e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5797b223-1a50-40df-b1c0-ebc6c5f2eb3e","state":{"height":48,"width":131,"x":1259,"y":2377},"type":"box-selector"},"type":"text-field","value":"doch"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f9337f4b-7df8-4f4b-a90b-e6d1f37e5de1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b91c722f-767b-4f30-bc1f-e2477a3adc0a","state":{"height":47,"width":87,"x":1434,"y":2379},"type":"box-selector"},"type":"text-field","value":"ein"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6fa06ada-a54d-41a3-bd0c-5c30e8636c75","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"7f3fe154-ac12-4652-b04d-9d6af23b74d1","state":{"height":43,"width":134,"x":1562,"y":2396},"type":"box-selector"},"type":"text-field","value":"paar"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"8027ef17-fadf-4a71-8cfe-85a4f9a21447","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d62808fe-d907-4f57-b83c-14e37ba2acc1","state":{"height":63,"width":610,"x":1736,"y":2377},"type":"box-selector"},"type":"text-field","value":"langfl��glig-schwarze"}]},"selector":{"id":"53c4d982-14d2-4c00-81c5-7318f13994f2","state":{"height":65,"width":2140,"x":206,"y":2375},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"74741d0f-9496-4d0c-b78e-51a0864d35e1","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"0d1764c7-7251-402f-87d2-162e237164be","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d24ce661-c92c-48f5-a43f-10db02bf92e0","state":{"height":63,"width":221,"x":204,"y":2448},"type":"box-selector"},"type":"text-field","value":"Fliegen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"be259b2d-7e27-40e2-8cc9-e19588bb728c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6b8f4882-0b82-416f-bdcf-7069bd557018","state":{"height":48,"width":94,"x":458,"y":2449},"type":"box-selector"},"type":"text-field","value":"auf"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2b164fc6-5f7a-48ec-a2c4-3d54e696a554","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"deec10cf-9560-4323-877d-8f6e21f66620","state":{"height":49,"width":113,"x":581,"y":2449},"type":"box-selector"},"type":"text-field","value":"und"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"eb92e748-653b-42f4-9221-2a4286412d02","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"dc88833d-a993-4856-914f-dae7e8852677","state":{"height":50,"width":199,"x":725,"y":2448},"type":"box-selector"},"type":"text-field","value":"ebenso"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2ee93f7c-1ed6-4b85-81ce-c13758975164","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f3463835-f090-4788-be82-7ecfa421cf5c","state":{"height":60,"width":175,"x":959,"y":2451},"type":"box-selector"},"type":"text-field","value":"einige"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4e85f419-2402-4602-82d2-bcc52c6cc390","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8845a7cd-a72b-4243-9cff-8e11179a1328","state":{"height":63,"width":658,"x":1167,"y":2449},"type":"box-selector"},"type":"text-field","value":"grau-stummelfl��glige,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ba726802-c0b5-4441-97e2-c48e90ef464c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"00e14af1-5202-402d-99d4-df29e58fd178","state":{"height":49,"width":85,"x":1858,"y":2449},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f5475b3c-8a0b-4b8a-8100-88dee95303e8","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e7507eee-3810-450f-98c0-1731aa58edb8","state":{"height":50,"width":131,"x":1977,"y":2449},"type":"box-selector"},"type":"text-field","value":"doch"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"12aa1a25-1d90-4d3e-ad81-8574636d1737","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d180cb79-5b59-49d7-9d61-5060e53ca6df","state":{"height":61,"width":203,"x":2142,"y":2451},"type":"box-selector"},"type":"text-field","value":"eigent��"}]},"selector":{"id":"7186205f-5295-45b0-b0dd-519b7e0a8cb9","state":{"height":64,"width":2141,"x":204,"y":2448},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"d82d1868-6ec6-4e05-8b48-a87a1d647a9a","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"06499612-c195-4bc4-95e4-7d7a41c09bae","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"646e397c-daac-4c36-996f-ab5743f351c9","state":{"height":49,"width":99,"x":204,"y":2521},"type":"box-selector"},"type":"text-field","value":"lich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c2f807ee-582a-4f3b-be61-61f04c74cce0","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d95f210a-18f3-4569-b655-cc9c1d18fe87","state":{"height":49,"width":132,"x":353,"y":2521},"type":"box-selector"},"type":"text-field","value":"nach"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"23d91dff-86c1-45bb-8dbb-7b8b4b2d1c4f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"91eaf7f5-0de9-4370-9fdc-8d24183589f1","state":{"height":50,"width":95,"x":536,"y":2520},"type":"box-selector"},"type":"text-field","value":"der"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9a4e9ebc-73e7-468f-8244-1bdd3b00158d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ce6e3a14-3735-48b1-b0b1-ca0f583bdcee","state":{"height":64,"width":350,"x":682,"y":2520},"type":"box-selector"},"type":"text-field","value":"Neufassung"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"33bfa073-dba3-4387-aad8-b4722d538bde","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f554cc44-83b3-4561-afee-01ae02f067cf","state":{"height":51,"width":90,"x":1085,"y":2520},"type":"box-selector"},"type":"text-field","value":"des"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1d8258b6-ee04-49f9-89d8-748869648c80","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ae2ab185-903e-4f9f-8c95-81bed7d4461b","state":{"height":48,"width":205,"x":1229,"y":2521},"type":"box-selector"},"type":"text-field","value":"dritten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ae83cf20-812a-401c-ba00-3fc434852577","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4ea3ad8f-43bb-4d73-9491-57d03e70352a","state":{"height":64,"width":470,"x":1484,"y":2520},"type":"box-selector"},"type":"text-field","value":"Mendelgesetzes"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b6205b0e-cc0a-42ec-a1ad-98906b918a58","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"fb81d8ab-7add-467f-b0ef-9bf24296e375","state":{"height":58,"width":338,"x":2007,"y":2520},"type":"box-selector"},"type":"text-field","value":"���verboten\""}]},"selector":{"id":"e9abf4d2-293e-40d0-9565-a9349bef4ced","state":{"height":64,"width":2141,"x":204,"y":2520},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"d7f87270-3e89-4baf-a6cb-95c3bd8cfab4","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c9110f09-41a3-4345-ab31-14e077900b94","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"8003d909-3a1d-43cd-b935-7b2fbf0486dc","state":{"height":31,"width":197,"x":203,"y":2611},"type":"box-selector"},"type":"text-field","value":"waren."},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"17bea6e6-3699-43a7-83c0-b331c1c58c73","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f8b0a33f-591c-4840-9c84-17769ceefb80","state":{"height":50,"width":116,"x":445,"y":2592},"type":"box-selector"},"type":"text-field","value":"Wie"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"8b596f3c-ade8-4c9c-99f8-da3371639744","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4bffcae5-8b90-44bf-af6f-55f8d02e7931","state":{"height":49,"width":198,"x":604,"y":2592},"type":"box-selector"},"type":"text-field","value":"sollten"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4a3144d8-6d8c-4610-b526-55a54bf46745","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"999d3e62-2077-4971-8076-fa8ef79d0596","state":{"height":49,"width":142,"x":847,"y":2592},"type":"box-selector"},"type":"text-field","value":"denn"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c21e95c8-9864-4a21-ab86-e9c59a446ba6","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5a45c5a1-ccdf-40a9-8a8b-1e1e182e3688","state":{"height":49,"width":84,"x":1034,"y":2592},"type":"box-selector"},"type":"text-field","value":"die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"96b4b834-14d3-4528-b29a-932d1e65e7cd","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d61fdffe-de36-4d87-b7b8-4fdfc28327e6","state":{"height":45,"width":55,"x":1161,"y":2595},"type":"box-selector"},"type":"text-field","value":"in"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9c2f45c2-3147-46be-bcaa-b4a6f398286b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"3ce8aaeb-d2e4-46a0-91a1-4c0ecef939ef","state":{"height":48,"width":102,"x":1261,"y":2593},"type":"box-selector"},"type":"text-field","value":"den"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"69718494-730a-4338-9bbf-2207a7860b5a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a2fe3fce-7b20-41c9-b778-16c63282384e","state":{"height":54,"width":430,"x":1409,"y":2589},"type":"box-selector"},"type":"text-field","value":"Chromosomen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a76ad4f5-6a22-40ed-9fae-94e09a1baf4f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f05a62bd-999e-4bda-b865-df738d7ff950","state":{"height":62,"width":287,"x":1882,"y":2593},"type":"box-selector"},"type":"text-field","value":"liegenden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e891072f-a255-4e00-b11e-512fbce9fe3b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4925e9ed-263a-4227-8ee5-3d372a660713","state":{"height":49,"width":131,"x":2212,"y":2592},"type":"box-selector"},"type":"text-field","value":"Erb��"}]},"selector":{"id":"153f1a0d-68bc-4914-8825-7a9a9f36dc77","state":{"height":66,"width":2140,"x":203,"y":2589},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"996c3132-cfd7-46d2-a27f-4298d0a9d54d","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9cd2b93d-5597-4f29-9be9-9df7f2112435","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6cb6a1d8-8410-4a76-8898-90929a4ed1d9","state":{"height":50,"width":261,"x":203,"y":2664},"type":"box-selector"},"type":"text-field","value":"faktoren"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b2e87713-ca24-4230-9ab5-0af5a5547d16","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"acdcb7d3-6924-4cdf-9f9d-189b5611d552","state":{"height":59,"width":136,"x":496,"y":2667},"type":"box-selector"},"type":"text-field","value":"jetzt"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ead56b90-4b6a-43e9-a6e2-2f332afae33f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"2bdf7433-1f5a-4ecc-b7de-0b80ff8bdbb7","state":{"height":49,"width":94,"x":665,"y":2665},"type":"box-selector"},"type":"text-field","value":"auf"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"821cab3b-3836-45ae-816c-055a87c26c37","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"86033daf-0cf3-43a9-9cec-a2991413762b","state":{"height":49,"width":199,"x":793,"y":2665},"type":"box-selector"},"type":"text-field","value":"einmal"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6cf20762-4be7-403f-adf2-a48988c47765","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"30f4d74f-5462-48be-9c4b-3827f3c69538","state":{"height":47,"width":109,"x":1024,"y":2666},"type":"box-selector"},"type":"text-field","value":"frei"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"853af845-614e-4dc5-9b73-73034cdc2f86","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"c8aec4c8-4432-4127-b3a6-5ec0ce5fdcba","state":{"height":50,"width":339,"x":1165,"y":2664},"type":"box-selector"},"type":"text-field","value":"kombiniert"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3892f2df-2a86-44c9-9830-51d13c55b4d4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"bf95f591-38e0-4ff2-8744-2452d9d7980d","state":{"height":49,"width":224,"x":1535,"y":2665},"type":"box-selector"},"type":"text-field","value":"worden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6419d161-2a9a-4f5a-8d06-0e3cd3ec323f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d639a7ae-0dc0-47d8-9181-c46dd0824a3e","state":{"height":50,"width":149,"x":1793,"y":2664},"type":"box-selector"},"type":"text-field","value":"sein?"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e37eaae0-d161-4144-ab37-af897fc67a67","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"27e8a578-ce5b-4f13-979d-eaebf42a3036","state":{"height":51,"width":86,"x":1977,"y":2664},"type":"box-selector"},"type":"text-field","value":"Sie"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"628232ce-f35c-4d0b-9e64-f2712600c96a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0b3cf4f7-c3d2-4ed6-beb0-dfa449ae1c7a","state":{"height":49,"width":247,"x":2097,"y":2665},"type":"box-selector"},"type":"text-field","value":"konnten"}]},"selector":{"id":"18e9b5e2-c1ed-4da9-b2e7-5a731c5cf055","state":{"height":62,"width":2141,"x":203,"y":2664},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"e133e91a-1d01-4e22-88f0-7112721d89dd","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3668e493-a3e4-4da7-9c76-4f751b910050","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"352836b1-04bd-40a8-a63e-927653a4cc11","state":{"height":49,"width":131,"x":204,"y":2737},"type":"box-selector"},"type":"text-field","value":"doch"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"55e8e60b-c6d6-4971-8559-8819dbfd4b70","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"875645c9-1cf8-4b4f-9b86-596f20646b43","state":{"height":49,"width":144,"x":366,"y":2737},"type":"box-selector"},"type":"text-field","value":"nicht"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"9fe2b32b-9021-4633-accc-eace8cafaa06","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4efc393a-e76a-4481-8917-9bcc3de1efdd","state":{"height":48,"width":109,"x":540,"y":2738},"type":"box-selector"},"type":"text-field","value":"frei"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"0529ec48-3c2a-47cc-b13d-8f70b89d230f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"9da02780-a1b7-4d6b-956e-b7e232a9a3e9","state":{"height":46,"width":74,"x":677,"y":2740},"type":"box-selector"},"type":"text-field","value":"im"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4077ff44-4996-41b7-a0f6-2401ec7872fc","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"abff7656-f249-4069-88db-e6e0953add0e","state":{"height":50,"width":257,"x":781,"y":2736},"type":"box-selector"},"type":"text-field","value":"Zellkern"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ded6f0c6-ce37-484c-926f-69dd7e3786a0","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d19e9b0b-603f-412b-a723-e78185f88c0f","state":{"height":49,"width":395,"x":1070,"y":2736},"type":"box-selector"},"type":"text-field","value":"herumfahren"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e72954ae-131f-42c3-9fd1-b04497e834be","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"14bc1a12-699a-4633-801d-c86b60060921","state":{"height":50,"width":115,"x":1496,"y":2736},"type":"box-selector"},"type":"text-field","value":"und"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"0b0e5a28-5535-44b6-a163-ff74b2539bb9","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1234dc96-70fd-4218-a752-5fc09a0c8d98","state":{"height":50,"width":105,"x":1641,"y":2737},"type":"box-selector"},"type":"text-field","value":"sich"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"7c88245c-84a7-462f-99d8-8fcd8580956d","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0c1b8d34-eaef-4973-b962-53d9c5e074e6","state":{"height":48,"width":435,"x":1778,"y":2749},"type":"box-selector"},"type":"text-field","value":"zusammentun,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"95472985-922d-45a7-8aef-ca645fb899b1","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6bf88a0c-a079-4bd2-9d71-2767c0f170bc","state":{"height":47,"width":100,"x":2244,"y":2739},"type":"box-selector"},"type":"text-field","value":"wie"}]},"selector":{"id":"bcde0adc-9a96-4787-b7fe-5f2ac989784c","state":{"height":61,"width":2140,"x":204,"y":2736},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"e6a9f693-43f6-4e78-b588-4b438ebf5e66","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"15fb0981-4ecb-427d-acbb-2cbcd43674e5","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1207dd6e-a96f-4bb9-ac66-7dd8aebbbfae","state":{"height":30,"width":53,"x":205,"y":2830},"type":"box-selector"},"type":"text-field","value":"es"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"4b1c7fe5-5cdb-42d4-b9be-702d06838bfd","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"83975d88-6878-408f-9f24-042b64afd2ad","state":{"height":48,"width":164,"x":284,"y":2810},"type":"box-selector"},"type":"text-field","value":"ihnen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"2532598c-21ce-4f01-b811-97ea5310ffc7","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4ef3c43c-3d14-4fc8-bc39-57a6d1eb7909","state":{"height":51,"width":275,"x":473,"y":2808},"type":"box-selector"},"type":"text-field","value":"beliebte?"}]},"selector":{"id":"8b3cb2e5-2325-4381-9fa3-3f05181d3e61","state":{"height":52,"width":543,"x":205,"y":2808},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"548a8deb-e62d-40b2-8004-81edc236132a","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"b7fb0eab-94ff-4ca6-9df8-ddbbccfdfeaf","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"81742eb8-9aff-4c92-a9db-fa8e8e539278","state":{"height":65,"width":231,"x":271,"y":2888},"type":"box-selector"},"type":"text-field","value":"Morgan"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"0ebf4529-2b32-4d31-b18d-e279c44e6744","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"ce8e34da-cd9b-4fa2-9660-6d2ad2857054","state":{"height":49,"width":135,"x":543,"y":2891},"type":"box-selector"},"type":"text-field","value":"fand"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"a2fedc25-2227-4027-9016-c09db5721bfd","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b07dd799-8754-4198-a24f-c3fa2d86913b","state":{"height":49,"width":129,"x":720,"y":2891},"type":"box-selector"},"type":"text-field","value":"auch"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"aa856210-0ca0-47a2-88fb-ec4f89a3e82e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a8fe6dac-304c-4da7-a881-d73f7a9876a4","state":{"height":48,"width":93,"x":892,"y":2892},"type":"box-selector"},"type":"text-field","value":"f��r"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e2c5ac3a-14d9-4afb-901e-96cc530f5e3c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"dc9bf15b-ee81-4fda-a607-726bbc2ff317","state":{"height":49,"width":145,"x":1028,"y":2891},"type":"box-selector"},"type":"text-field","value":"diese"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"175f32b0-a951-41fe-9806-68a9c940aad6","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a6c8f4ca-990a-418c-8ed6-bdaa32904f89","state":{"height":64,"width":386,"x":1217,"y":2890},"type":"box-selector"},"type":"text-field","value":"R��tselfragen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"fb03f193-2510-4c46-89ee-e8bdba4f6a90","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e0fbd4fe-760d-480d-92ec-d1400707c17e","state":{"height":47,"width":120,"x":1645,"y":2894},"type":"box-selector"},"type":"text-field","value":"eine"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e3651f3c-2572-4642-b93a-12d767e05431","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"716384f5-d70a-4e9e-bc6b-ec0eea3a4573","state":{"height":64,"width":211,"x":1809,"y":2891},"type":"box-selector"},"type":"text-field","value":"geniale"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ea67c651-2b91-4943-9773-5f21d8d99b41","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"994b85cd-9ef4-4f7a-bc81-4d42b708616a","state":{"height":51,"width":275,"x":2064,"y":2890},"type":"box-selector"},"type":"text-field","value":"Antwort:"}]},"selector":{"id":"e75938c2-f9e6-4011-b996-f106365367ef","state":{"height":67,"width":2068,"x":271,"y":2888},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"a00de50a-ab23-468e-adbc-63f331dcf392","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"63eb7e58-bd2f-45a8-b6bf-e097893f6cd5","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f8529f66-eaa9-488b-b034-022e6ab49ea0","state":{"height":49,"width":102,"x":200,"y":2963},"type":"box-selector"},"type":"text-field","value":"Die"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"497d72ed-b1b1-4766-9f06-da64db716b20","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"50528d62-9894-4676-9c43-c8b8efddf2c8","state":{"height":64,"width":296,"x":337,"y":2962},"type":"box-selector"},"type":"text-field","value":"Trennung"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"fa4235a8-4514-44c5-bd9e-f257a574a759","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"f01b898e-f018-4fde-a7cf-bebb9e660b46","state":{"height":64,"width":362,"x":667,"y":2962},"type":"box-selector"},"type":"text-field","value":"gekoppelter"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3bcc3e60-bc87-4f1b-8141-d83dbc4f6e9f","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"55197984-2a9b-4c29-89b3-dda3be156b03","state":{"height":64,"width":247,"x":1063,"y":2962},"type":"box-selector"},"type":"text-field","value":"Anlagen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1a14fbab-d222-462e-8518-313fe204ca39","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"4e4c5bd1-36fc-47f9-bb40-b07def073a12","state":{"height":48,"width":69,"x":1344,"y":2966},"type":"box-selector"},"type":"text-field","value":"ist"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6df06bf2-bffd-4482-9862-89dec8b36e10","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"dfc19495-4968-4daf-b8bc-7114554ddf1e","state":{"height":29,"width":104,"x":1447,"y":2984},"type":"box-selector"},"type":"text-field","value":"nur"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3ae881d6-bd43-4d30-9bae-cc7e4cedd966","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0c4bd317-372d-460a-8a42-5d0bfba2c9f0","state":{"height":60,"width":266,"x":1588,"y":2963},"type":"box-selector"},"type":"text-field","value":"denkbar,"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"3903f303-2055-404c-a62c-e419713be8c4","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"6b1ee034-5380-4e78-b613-f85cbd8f3578","state":{"height":30,"width":159,"x":1888,"y":2983},"type":"box-selector"},"type":"text-field","value":"wenn"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"ab2853bb-7cbd-42b9-a078-76339d50318a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"04122b70-eb2c-4ecd-9b78-eeaa0adc0bdf","state":{"height":50,"width":260,"x":2082,"y":2964},"type":"box-selector"},"type":"text-field","value":"zwischen"}]},"selector":{"id":"dc3f2c30-9ae6-4176-8401-af92584d0fd5","state":{"height":64,"width":2142,"x":200,"y":2962},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"f4042099-2a83-44f8-80fc-96b8babdcdc5","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"bb5480f9-87e0-4a1c-a891-6d1b80c2bbed","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"c6878de0-91f4-4d5b-9289-ead575d0e820","state":{"height":49,"width":101,"x":201,"y":3035},"type":"box-selector"},"type":"text-field","value":"den"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1b60b9b3-b2ba-42bb-882f-4eb395dddcb0","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"1f05e5a1-c94c-4d6a-867b-bf692a5e1297","state":{"height":49,"width":264,"x":363,"y":3035},"type":"box-selector"},"type":"text-field","value":"einander"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"8f0bb0f2-6129-4976-b23b-f4cb0eac290a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"60494187-83d6-4e5b-92d4-9ea15f5f7b9f","state":{"height":64,"width":465,"x":686,"y":3034},"type":"box-selector"},"type":"text-field","value":"entsprechenden"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"36bb031a-1fb9-4b37-ac63-66f0f4c24a8b","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"dab091a6-ab9b-4430-a5ce-5fca74ead448","state":{"height":51,"width":431,"x":1213,"y":3035},"type":"box-selector"},"type":"text-field","value":"Chromosomen"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"eec86c57-a323-4c36-98ed-1513f903c2ca","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"bd521f46-bac4-4740-9bf3-e3b0d4a10e47","state":{"height":51,"width":194,"x":1705,"y":3035},"type":"box-selector"},"type":"text-field","value":"St��cke"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"c52a0616-3c79-4cfb-ab0e-b3225d3dbaaa","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"a0e4c444-4c1f-4278-ab24-c696bb99ef71","state":{"height":64,"width":380,"x":1962,"y":3035},"type":"box-selector"},"type":"text-field","value":"ausgetauscht"}]},"selector":{"id":"189befdb-fbe1-4fe5-b140-1a4758272eba","state":{"height":65,"width":2141,"x":201,"y":3034},"type":"box-selector"},"type":"entity"},{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"ba7a2717-4429-44e6-9dd2-845c3e03ace8","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"71082874-e993-4bf5-b8bc-72d6b1e2c62a","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"24b5d819-a86c-4bc3-b1e5-87e5f2da6b86","state":{"height":50,"width":235,"x":196,"y":3107},"type":"box-selector"},"type":"text-field","value":"werden."},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"d337f4fa-8f83-4195-8e09-043cc5bbf35e","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"e2e9bfe4-5c07-4c33-a239-b2bb2d8e23ac","state":{"height":51,"width":163,"x":457,"y":3107},"type":"box-selector"},"type":"text-field","value":"Diese"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"f0df108c-a7c0-4c57-9ccf-7f2e838f6d40","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"5beccda8-a0a2-411a-9d86-47d9af897cfb","state":{"height":50,"width":185,"x":645,"y":3107},"type":"box-selector"},"type":"text-field","value":"k��hne"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"20838946-8218-474a-b085-877d40b2a010","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"0ec8c860-c87a-4cf8-b49a-8e7af1434b57","state":{"height":51,"width":235,"x":854,"y":3106},"type":"box-selector"},"type":"text-field","value":"Theorie"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"6f844e89-c6fc-4851-9436-60b2bae46783","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"d8faaf7a-f107-4b92-aab9-4958782d6bfc","state":{"height":50,"width":91,"x":1116,"y":3108},"type":"box-selector"},"type":"text-field","value":"des"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"1820ad7f-1a64-438c-bc80-2ea058b8e550","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"611b0f3f-6f0e-4af7-81c0-12717c50fe23","state":{"height":58,"width":843,"x":1233,"y":3107},"type":"box-selector"},"type":"text-field","value":"���Erbfaktoren-Austausches\""},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"99bc1a72-f044-4b17-b9e9-7e08712d0a83","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"9b5d0ce2-a501-460e-8d87-bd1159cfa110","state":{"height":50,"width":107,"x":2103,"y":3109},"type":"box-selector"},"type":"text-field","value":"lie��"},{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"16653f94-f047-4d78-ab30-49f06b6abc3c","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"b1b8a01f-58c1-4f9a-b9f2-d919087969bc","state":{"height":52,"width":101,"x":2237,"y":3107},"type":"box-selector"},"type":"text-field","value":"sich"}]},"selector":{"id":"aa4b9b50-0526-4e85-877a-e8160c6ed2bf","state":{"height":59,"width":2142,"x":196,"y":3106},"type":"box-selector"},"type":"entity"}]},"selector":{"id":"33575568-3cce-4ca8-9009-5ce4fd873ae6","state":{"height":2982,"width":2198,"x":156,"y":188},"type":"box-selector"},"type":"entity","profile":"http://madoc.io/profiles/capture-model-fields/paragraphs"},{"allowMultiple":true,"id":"9ff61986-5dcc-4f94-a19d-779a20b8a2c9","label":"OCR Correction","labelledBy":"lines","pluralLabel":"OCR Correction","properties":{"lines":[{"allowMultiple":true,"description":"All of the lines inside of a paragraph","id":"0f9d0734-0f92-4b21-b126-e8eeb047edb6","label":"Line","labelledBy":"text","pluralLabel":"Lines","properties":{"text":[{"allowMultiple":true,"description":"Single word, phrase or the whole line","id":"e4480f2d-fb03-409e-b41d-f6f44134c866","label":"Text of line","pluralField":"Text of lines","previewInline":true,"selector":{"id":"72465d49-b9b8-4cea-a03b-4db6fa826168","state":{"height":49,"width":67,"x":1233,"y":3253},"type":"box-selector"},"type":"text-field","value":"26"}]},"selector":{"id":"49ebd097-6859-41b1-bc00-8c9b9da702c8","state":{"height":49,"width":67,"x":1233,"y":3253},"type":"box-selector"},"type":"entity"}]},"selector":{"id":"6e6e8c65-1726-4feb-a769-36465b5e5830","state":{"height":3120,"width":2198,"x":156,"y":188},"type":"box-selector"},"type":"entity","profile":"http://madoc.io/profiles/capture-model-fields/paragraphs"}]}},"target":[{"id":"urn:madoc:manifest:9665","type":"Manifest"},{"id":"urn:madoc:canvas:9693","type":"Canvas"}],"profile":null,"derivedFrom":"2420cacd-d8b0-4234-b706-a378a10847a0","contributors":{"urn:madoc:user:1":{"id":"urn:madoc:user:1","type":"Person","name":"Madoc TS"}}} diff --git a/services/madoc-ts/fixtures/96-jira/MAD-1199-1-fixed.json b/services/madoc-ts/fixtures/96-jira/MAD-1199-1-fixed.json new file mode 100644 index 000000000..d0c3c4c87 --- /dev/null +++ b/services/madoc-ts/fixtures/96-jira/MAD-1199-1-fixed.json @@ -0,0 +1,155 @@ +{ + "id": "c74a586e-35fe-4413-a2f0-38c5e4ce92af", + "structure": { + "id": "5bb490cf-b81d-4a1b-b982-23b89e021a80", + "label": "vertaling-and-transcriptie", + "type": "choice", + "items": [ + { + "id": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "type": "model", + "label": "Default", + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ] + } + ] + }, + "document": { + "id": "23d42da3-5e21-41af-b5df-7d615c972b57", + "type": "entity", + "label": "vertaling-and-transcriptie", + "properties": { + "Vertaling & Transcriptie": [ + { + "id": "a6d45d9b-759b-47bb-aa33-160ec9c59751", + "type": "entity", + "label": "Vertaling & Transcriptie", + "selector": { + "id": "5a6509c1-5389-476c-a731-03d8b78ce19e", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "80e0d157-8ee0-460a-8c69-a4bfac20057e", + "type": "box-selector", + "state": { + "x": 1866, + "y": 855, + "width": 672, + "height": 334 + }, + "revises": "5a6509c1-5389-476c-a731-03d8b78ce19e", + "revisionId": "16bfea07-1a38-4947-b0c6-b6ac284a0450" + } + ] + }, + "immutable": true, + "properties": { + "Vertaling": [ + { + "id": "28aa3946-4e62-4539-ae76-24dd731de724", + "type": "text-field", + "label": "Vertaling", + "value": "" + }, + { + "id": "28aa3946-4e62-4539-ae76-24dd731de727", + "type": "text-field", + "label": "Vertaling", + "value": "test 2", + "revises": "1f519b90-8b5f-4ea3-a868-ca815d72b7ce", + "revision": "16bfea07-1a38-4947-b0c6-b6ac284a0450" + } + ], + "Transcriptie": [ + { + "id": "28aa3946-4e62-4539-ae76-24dd731de729", + "type": "text-field", + "label": "Transcriptie", + "value": "" + }, + { + "id": "cb06c929-8327-46b3-99d3-daf34cefef84", + "type": "text-field", + "label": "Transcriptie", + "value": "test 2", + "revises": "89e6982a-b2ec-4b91-8272-8520139eec4d", + "revision": "16bfea07-1a38-4947-b0c6-b6ac284a0450" + } + ] + }, + "allowMultiple": true + } + ] + } + }, + "revisions": [ + { + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ], + "structureId": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "id": "16bfea07-1a38-4947-b0c6-b6ac284a0450", + "revises": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "approved": false, + "status": "submitted", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "c74a586e-35fe-4413-a2f0-38c5e4ce92af" + }, + { + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ], + "structureId": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "id": "c7397d1d-4bf4-4546-bfb2-4fdf035d3130", + "revises": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "approved": false, + "status": "submitted", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "c74a586e-35fe-4413-a2f0-38c5e4ce92af" + } + ], + "derivedFrom": "9a4d68a8-c32e-4506-ae10-06b26ba2b268", + "target": [ + { + "id": "urn:madoc:manifest:272096", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:272097", + "type": "Canvas" + } + ], + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "Stephen" + } + } +} diff --git a/services/madoc-ts/fixtures/96-jira/MAD-1199-1.json b/services/madoc-ts/fixtures/96-jira/MAD-1199-1.json new file mode 100644 index 000000000..17a260c89 --- /dev/null +++ b/services/madoc-ts/fixtures/96-jira/MAD-1199-1.json @@ -0,0 +1,143 @@ +{ + "id": "c74a586e-35fe-4413-a2f0-38c5e4ce92af", + "structure": { + "id": "5bb490cf-b81d-4a1b-b982-23b89e021a80", + "label": "vertaling-and-transcriptie", + "type": "choice", + "items": [ + { + "id": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "type": "model", + "label": "Default", + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ] + } + ] + }, + "document": { + "id": "23d42da3-5e21-41af-b5df-7d615c972b57", + "type": "entity", + "label": "vertaling-and-transcriptie", + "properties": { + "Vertaling & Transcriptie": [ + { + "id": "a6d45d9b-759b-47bb-aa33-160ec9c59751", + "type": "entity", + "label": "Vertaling & Transcriptie", + "selector": { + "id": "5a6509c1-5389-476c-a731-03d8b78ce19e", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "80e0d157-8ee0-460a-8c69-a4bfac20057e", + "type": "box-selector", + "state": { + "x": 1866, + "y": 855, + "width": 672, + "height": 334 + }, + "revises": "5a6509c1-5389-476c-a731-03d8b78ce19e", + "revisionId": "16bfea07-1a38-4947-b0c6-b6ac284a0450" + } + ] + }, + "immutable": true, + "properties": { + "Vertaling": [ + { + "id": "28aa3946-4e62-4539-ae76-24dd731de727", + "type": "text-field", + "label": "Vertaling", + "value": "test 2", + "revises": "1f519b90-8b5f-4ea3-a868-ca815d72b7ce", + "revision": "16bfea07-1a38-4947-b0c6-b6ac284a0450" + } + ], + "Transcriptie": [ + { + "id": "cb06c929-8327-46b3-99d3-daf34cefef84", + "type": "text-field", + "label": "Transcriptie", + "value": "test 2", + "revises": "89e6982a-b2ec-4b91-8272-8520139eec4d", + "revision": "16bfea07-1a38-4947-b0c6-b6ac284a0450" + } + ] + }, + "allowMultiple": true + } + ] + } + }, + "revisions": [ + { + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ], + "structureId": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "id": "16bfea07-1a38-4947-b0c6-b6ac284a0450", + "revises": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "approved": false, + "status": "submitted", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "c74a586e-35fe-4413-a2f0-38c5e4ce92af" + }, + { + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ], + "structureId": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "id": "c7397d1d-4bf4-4546-bfb2-4fdf035d3130", + "revises": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "approved": false, + "status": "submitted", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "c74a586e-35fe-4413-a2f0-38c5e4ce92af" + } + ], + "derivedFrom": "9a4d68a8-c32e-4506-ae10-06b26ba2b268", + "target": [ + { + "id": "urn:madoc:manifest:272096", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:272097", + "type": "Canvas" + } + ], + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "Stephen" + } + } +} diff --git a/services/madoc-ts/fixtures/96-jira/MAD-1200-1.json b/services/madoc-ts/fixtures/96-jira/MAD-1200-1.json new file mode 100644 index 000000000..5b6e3f901 --- /dev/null +++ b/services/madoc-ts/fixtures/96-jira/MAD-1200-1.json @@ -0,0 +1,162 @@ +{ + "id": "0808202c-dc9f-4d4b-86c7-a4c222f611ab", + "structure": { + "id": "5bb490cf-b81d-4a1b-b982-23b89e021a80", + "label": "vertaling-and-transcriptie", + "type": "choice", + "items": [ + { + "id": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "type": "model", + "label": "Default", + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ] + } + ] + }, + "document": { + "id": "e60b0c30-230e-4bd3-b0aa-2963256c5dcd", + "type": "entity", + "label": "vertaling-and-transcriptie", + "properties": { + "Vertaling & Transcriptie": [ + { + "id": "172592c8-96a0-4efd-a3e0-69e5ffce8bba", + "type": "entity", + "label": "Vertaling & Transcriptie", + "selector": { + "id": "50e00368-90d2-41f6-9c31-2e12553e11ae", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "eb2a9993-a618-42cd-92cb-b074bad91320", + "type": "box-selector", + "state": { + "x": 108, + "y": 576, + "width": 171, + "height": 782 + }, + "revises": "50e00368-90d2-41f6-9c31-2e12553e11ae", + "revisionId": "41697e71-34eb-4f1c-9019-f26fda20bd32" + } + ] + }, + "immutable": true, + "properties": { + "Vertaling": [ + { + "id": "410dbfe8-edbe-4c32-a0e6-d2a6b1b7264f", + "type": "text-field", + "label": "Vertaling", + "value": "test 1", + "revises": "3c8d073a-126a-426e-942c-f328ac7ae988", + "revision": "41697e71-34eb-4f1c-9019-f26fda20bd32", + "immutable": true + } + ], + "Transcriptie": [ + { + "id": "c028819a-d914-4b95-837c-3175a7e18d29", + "type": "text-field", + "label": "Transcriptie", + "value": "test 2", + "revises": "935a73fb-0449-4685-9eff-75935d41a682", + "revision": "41697e71-34eb-4f1c-9019-f26fda20bd32", + "immutable": true + } + ] + }, + "allowMultiple": true + }, + { + "id": "e43077f1-f574-4d9c-8d9f-7fb1922ba6d9", + "type": "entity", + "label": "Vertaling & Transcriptie", + "revision": "41697e71-34eb-4f1c-9019-f26fda20bd32", + "selector": { + "id": "965092e6-7934-4b4c-bc83-126ccb68e816", + "type": "box-selector", + "state": { + "x": 111, + "y": 1364, + "width": 168, + "height": 633 + } + }, + "immutable": false, + "properties": { + "Vertaling": [ + { + "id": "66ea7c0f-f737-457c-9645-1a533821c810", + "type": "text-field", + "label": "Vertaling", + "value": "test 2", + "revision": "41697e71-34eb-4f1c-9019-f26fda20bd32" + } + ], + "Transcriptie": [ + { + "id": "cd3c0963-b30e-4b9d-ae4a-6aa587f6405c", + "type": "text-field", + "label": "Transcriptie", + "value": "test 3", + "revision": "41697e71-34eb-4f1c-9019-f26fda20bd32" + } + ] + }, + "allowMultiple": true + } + ] + } + }, + "revisions": [ + { + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ], + "structureId": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "id": "41697e71-34eb-4f1c-9019-f26fda20bd32", + "revises": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "approved": true, + "status": "accepted", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "0808202c-dc9f-4d4b-86c7-a4c222f611ab" + } + ], + "derivedFrom": "9a4d68a8-c32e-4506-ae10-06b26ba2b268", + "target": [ + { + "id": "urn:madoc:manifest:272096", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:272108", + "type": "Canvas" + } + ], + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "Stephen" + } + } +} diff --git a/services/madoc-ts/fixtures/96-jira/MAD-1200-2.json b/services/madoc-ts/fixtures/96-jira/MAD-1200-2.json new file mode 100644 index 000000000..b6c466bee --- /dev/null +++ b/services/madoc-ts/fixtures/96-jira/MAD-1200-2.json @@ -0,0 +1,133 @@ +{ + "id": "be8a37da-62b0-4430-b605-01d5c19e01d3", + "structure": { + "id": "21b43492-3cab-4424-a20b-0d4eab6bedb8", + "type": "choice", + "label": "Box drawing test", + "items": [ + { + "id": "d05dd72b-7385-4af4-b5a7-3f628346257d", + "type": "model", + "label": "Default", + "fields": [ + [ + "boxes", + [ + "description" + ] + ] + ], + "modelRoot": [ + "boxes" + ] + } + ] + }, + "document": { + "id": "67a1ff7d-86be-4cc1-ab4d-ae65126f5e23", + "type": "entity", + "label": "Box drawing test", + "properties": { + "boxes": [ + { + "id": "1b76fdb8-c5f5-4a29-9544-8485cc77d8f2", + "type": "entity", + "label": "Boxes", + "selector": { + "id": "22753b1d-7862-453b-84fe-8ee6ccc6b44f", + "type": "box-selector", + "state": null, + "required": true + }, + "labelledBy": "description", + "properties": { + "description": [ + { + "id": "4ce6efba-96df-4d41-93e6-731500a5e21d", + "type": "text-field", + "label": "Description", + "value": "", + "minLines": 6, + "multiline": true + } + ] + }, + "allowMultiple": true + }, + { + "id": "a0844c53-ea68-4550-a5c9-f72492bbe517", + "type": "entity", + "label": "Boxes", + "revision": "b5458c19-ab20-4bf7-b89a-6da4a50742ab", + "selector": { + "id": "45857d62-98ce-4c9b-af29-562ba498cc29", + "type": "box-selector", + "state": { + "x": 1095, + "y": 568, + "width": 432, + "height": 261 + }, + "required": true + }, + "immutable": false, + "labelledBy": "description", + "properties": { + "description": [ + { + "id": "67613438-2c60-4292-90e6-bf938a55da87", + "type": "text-field", + "label": "Description", + "value": "Testing a box", + "minLines": 6, + "revision": "b5458c19-ab20-4bf7-b89a-6da4a50742ab", + "multiline": true + } + ] + }, + "allowMultiple": false + } + ] + } + }, + "revisions": [ + { + "fields": [ + [ + "boxes", + [ + "description" + ] + ] + ], + "structureId": "d05dd72b-7385-4af4-b5a7-3f628346257d", + "id": "b5458c19-ab20-4bf7-b89a-6da4a50742ab", + "revises": "d05dd72b-7385-4af4-b5a7-3f628346257d", + "approved": true, + "status": "accepted", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "be8a37da-62b0-4430-b605-01d5c19e01d3" + } + ], + "derivedFrom": "52ca54cb-96ec-4de5-81c7-3416e96af4dc", + "target": [ + { + "id": "urn:madoc:manifest:176", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:339", + "type": "Canvas" + } + ], + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "Stephen" + } + } +} diff --git a/services/madoc-ts/fixtures/96-jira/MAD-1200-3.json b/services/madoc-ts/fixtures/96-jira/MAD-1200-3.json new file mode 100644 index 000000000..6cdc15717 --- /dev/null +++ b/services/madoc-ts/fixtures/96-jira/MAD-1200-3.json @@ -0,0 +1,167 @@ +{ + "id": "bf154613-6bb8-4af5-b3a9-f369ee64af6b", + "structure": { + "id": "5bb490cf-b81d-4a1b-b982-23b89e021a80", + "label": "vertaling-and-transcriptie", + "type": "choice", + "items": [ + { + "id": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "type": "model", + "label": "Default", + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ] + } + ] + }, + "document": { + "id": "d07a7242-294f-425b-ab8d-7ec53ab6a699", + "type": "entity", + "label": "vertaling-and-transcriptie", + "properties": { + "Vertaling & Transcriptie": [ + { + "id": "18920ef4-13e5-45ac-9df9-5c318af4e85c", + "type": "entity", + "label": "Vertaling & Transcriptie", + "selector": { + "id": "fac22505-f7e9-4853-9732-1613f3d1d749", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "a88c6b3e-f680-4a3b-9ea0-c871cb27e342", + "type": "box-selector", + "state": { + "x": 1365, + "y": 662, + "width": 449, + "height": 237 + }, + "revisionId": "54e23c06-f026-4332-8876-1a8c2de82abb", + "revises": "fac22505-f7e9-4853-9732-1613f3d1d749" + } + ] + }, + "properties": { + "Vertaling": [ + { + "id": "b2b92293-698c-4496-8a5d-932dafddeb80", + "type": "text-field", + "label": "Vertaling", + "value": "" + }, + { + "id": "6455b61a-d21d-40e8-9259-47bb883bcb79", + "type": "text-field", + "label": "Vertaling", + "value": "test 1", + "revision": "54e23c06-f026-4332-8876-1a8c2de82abb", + "revises": "b2b92293-698c-4496-8a5d-932dafddeb80", + "immutable": true + } + ], + "Transcriptie": [ + { + "id": "f91cac15-1ddd-4a0b-b255-5e3c651f63d0", + "type": "text-field", + "label": "Transcriptie", + "value": "" + }, + { + "id": "764b2fa5-8dae-465e-bda1-c4349570eb16", + "type": "text-field", + "label": "Transcriptie", + "value": "test 1", + "revision": "54e23c06-f026-4332-8876-1a8c2de82abb", + "revises": "f91cac15-1ddd-4a0b-b255-5e3c651f63d0", + "immutable": true + } + ] + }, + "allowMultiple": true + }, + { + "id": "384b319c-2ade-4a85-9612-d03a542d441e", + "type": "entity", + "label": "Vertaling & Transcriptie", + "selector": { + "id": "f2b28543-618f-4652-94d6-555ec5b0e3f4", + "type": "box-selector", + "state": { + "x": 1985, + "y": 799, + "width": 442, + "height": 245 + } + }, + "allowMultiple": true, + "immutable": false, + "properties": { + "Vertaling": [ + { + "id": "05fd43ca-d29b-4a93-b609-24c94325100b", + "type": "text-field", + "label": "Vertaling", + "value": "test 2", + "revision": "54e23c06-f026-4332-8876-1a8c2de82abb" + } + ], + "Transcriptie": [ + { + "id": "95070d2b-9ab1-43b6-8b47-8e3f18e1b8bc", + "type": "text-field", + "label": "Transcriptie", + "value": "test 2", + "revision": "54e23c06-f026-4332-8876-1a8c2de82abb" + } + ] + }, + "revision": "54e23c06-f026-4332-8876-1a8c2de82abb" + } + ] + } + }, + "revisions": [ + { + "fields": [ + [ + "Vertaling & Transcriptie", + [ + "Vertaling", + "Transcriptie" + ] + ] + ], + "structureId": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "id": "54e23c06-f026-4332-8876-1a8c2de82abb", + "revises": "a5117e9d-8ad4-48a9-b04b-20bcc5773694", + "approved": false, + "status": "submitted", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "c74a586e-35fe-4413-a2f0-38c5e4ce92af" + } + ], + "derivedFrom": "9a4d68a8-c32e-4506-ae10-06b26ba2b268", + "target": [ + { + "id": "urn:madoc:manifest:272096", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:272097", + "type": "Canvas" + } + ], + "contributors": {} +} diff --git a/services/madoc-ts/fixtures/96-jira/MAD-1200-4.json b/services/madoc-ts/fixtures/96-jira/MAD-1200-4.json new file mode 100644 index 000000000..49e520ed6 --- /dev/null +++ b/services/madoc-ts/fixtures/96-jira/MAD-1200-4.json @@ -0,0 +1,61 @@ +{ + "id": "d5872f4a-a7be-4c76-a05a-839c25891ac8", + "document": { + "id": "6638203c-9b91-446a-82f8-17b2aa1ebf52", + "type": "entity", + "label": "Root", + "properties": { + "identifyRegion": [ + { + "id": "ac7eea00-3fad-4d5a-939c-c980067ceda8", + "type": "text-field", + "label": "identifyRegion", + "value": "", + "selector": { + "id": "bd321012-9c87-447e-b5a8-9bb2c4e0845a", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "31a25ba1-034e-4f24-81f1-b12f63d179e4", + "type": "box-selector", + "state": { + "x": 25, + "y": 50, + "width": 100, + "height": 200 + }, + "revisionId": "0162c8b3-abc5-47cb-a118-7186400e2171", + "revises": "bd321012-9c87-447e-b5a8-9bb2c4e0845a", + "revisedBy": [] + } + ] + } + } + ] + } + }, + "structure": { + "label": "Default", + "description": "", + "id": "073c6174-0c1a-484e-966d-4bdf5fe67b45", + "type": "model", + "fields": [ + "identifyRegion" + ] + }, + "revisions": [ + { + "id": "0162c8b3-abc5-47cb-a118-7186400e2171", + "fields": [ + "identifyRegion" + ], + "status": "draft", + "approved": false, + "structureId": "073c6174-0c1a-484e-966d-4bdf5fe67b45", + "label": "Default", + "revises": "073c6174-0c1a-484e-966d-4bdf5fe67b45", + "deletedFields": [] + } + ] +} diff --git a/services/madoc-ts/fixtures/97-bugs/05-chain.json b/services/madoc-ts/fixtures/97-bugs/05-chain.json new file mode 100644 index 000000000..7bb13fdfa --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/05-chain.json @@ -0,0 +1,454 @@ +{ + "id": "16a95e72-ef94-4127-93d7-166c9aaf7efa", + "document": { + "id": "d0fe9196-cb1b-4c84-a7c8-56ba49995573", + "type": "entity", + "label": "Chain", + "properties": { + "Daily Data": [ + { + "id": "468be3d9-7f88-4e68-9275-05b8acd57deb", + "type": "entity", + "label": "Daily Data", + "properties": { + "Max": [ + { + "id": "baa148c3-4795-4937-999a-738186febf4b", + "type": "text-field", + "label": "Thermometer reading 1", + "value": "", + "description": "The reading is taken from the top tip of the vertical line" + } + ], + "Min": [ + { + "id": "63752a9a-b115-44d5-9a21-98fb29e1725c", + "type": "text-field", + "label": "Thermometer reading 2", + "value": "", + "description": "If there are only two thermometer readings leave this box blank (refer to guidelines)" + } + ], + "1:30": [ + { + "id": "60f67207-ae7e-43d1-a06a-14adc57267cc", + "type": "text-field", + "label": "Barometer reading 2", + "value": "", + "description": "Usually marked as 1.30. If there are only two barometer readings, this field should be left blank and the second reading put into box 3 below.", + "previewInline": true + } + ], + "6:30": [ + { + "id": "91944873-d6d9-4fb2-9178-94de133772ae", + "type": "text-field", + "label": "Barometer reading 3", + "value": "", + "description": "Usually shown as 6.30 reading. Or the second reading if only two are present." + } + ], + "8.30": [ + { + "id": "d4f1135f-0ed2-408c-9cc5-2d28c907aa0c", + "type": "text-field", + "label": "Barometer Reading 1", + "value": "", + "description": "This will either be shown as 8.30 or reading 1." + } + ], + "Thermometer reading 3": [ + { + "id": "e27d0ee5-ab5f-42de-9ac3-3e0c9e03c250", + "type": "text-field", + "label": "Thermometer reading 3", + "value": "" + } + ], + "Date of Entry (DD/MM/YYYY)": [ + { + "id": "0a71f433-72be-45c9-bdaa-d92790d145fb", + "type": "text-field", + "label": "Date of Entry (DD/MM/YYYY)", + "value": "", + "description": "", + "placeholder": "Enter date in format DD/MM/YYYY" + } + ] + } + } + ], + "Annual Data": [ + { + "id": "563feffd-544e-44ae-87b2-b668b128e16e", + "type": "entity", + "label": "Annual Data", + "properties": { + "Date": [ + { + "id": "96a703cc-4cbe-4983-8897-4dc9592d44f6", + "type": "text-field", + "label": "Date of entry (MM/YYYY)", + "value": "", + "multiline": false, + "placeholder": "MM/YYYY", + "allowMultiple": false + } + ], + "Day temp": [ + { + "id": "9c94d858-cf77-45ca-85f0-ce2337fc79e5", + "type": "text-field", + "label": "Highest Temp", + "value": "", + "description": "Highest Day Temp (H.D)", + "allowMultiple": false + } + ], + "Means HD": [ + { + "id": "0815b9ce-139a-458c-a89b-960ccd6f85f8", + "type": "text-field", + "label": "Means of H.D. temp", + "value": "", + "allowMultiple": false + } + ], + "rainfall": [ + { + "id": "65267efa-31f6-4b93-b5c6-215f2bf7b7a9", + "type": "text-field", + "label": "Monthly rainfall", + "value": "", + "allowMultiple": false + } + ], + "Any other": [ + { + "id": "acafb1cc-1947-4878-9a6d-45b29be4eac4", + "type": "text-field", + "label": "Any other data", + "value": "", + "description": "Other data such as 25yrl total. Please enter data as \"title - data\" for example \"25yr total - 4inches\"", + "allowMultiple": false + } + ], + "rain days": [ + { + "id": "cdbd3b5d-15f2-498d-931c-42daa3c8d57c", + "type": "text-field", + "label": "Number of days rain fell", + "value": "", + "multiline": false, + "allowMultiple": false + } + ], + "Mean month": [ + { + "id": "9d5c4ed8-4ca4-4c8a-9d16-8868d83a05fd", + "type": "text-field", + "label": "Means of temp for Month", + "value": "", + "allowMultiple": false + } + ], + "Mean night": [ + { + "id": "8c86befc-e335-446a-9081-442b5e452f7f", + "type": "text-field", + "label": "Means of L.D temp", + "value": "", + "allowMultiple": false + } + ], + "Annual rain": [ + { + "id": "a6968045-523c-42e1-94c3-d32cc0624f44", + "type": "text-field", + "label": "Annual rainfall total", + "value": "", + "description": "This will be shown under the December totals" + } + ], + "Lowest temp": [ + { + "id": "65717342-f433-4b70-afe1-4602f1444be8", + "type": "text-field", + "label": "Night Temp", + "value": "", + "description": "Lowest night temperature (L.N.)", + "allowMultiple": false + } + ] + } + } + ], + "Diary Entry": [ + { + "id": "6f6ea26b-4fb7-4906-be04-0deb40ed6d45", + "type": "entity", + "label": "Diary Entry", + "properties": { + "Date": [ + { + "id": "5314e722-0b3b-4d3e-ae6c-a88e67bf1f0a", + "type": "text-field", + "label": "Date of entry ", + "value": "", + "description": "Day of the diary entry, or the date the week starts if entry covers the whole week.", + "placeholder": "Enter date in format DD/MM/YYYY" + } + ], + "weekly diary": [ + { + "id": "261630d9-3a56-4393-843b-9fbce591f83e", + "type": "checkbox-field", + "label": "Weekly diary?", + "value": false, + "inlineLabel": "Tick this box if diary entry covers the whole week" + } + ], + "Transcription": [ + { + "id": "c072a570-3845-4270-97a3-d1da5fb48b73", + "type": "text-field", + "label": "Transcribe the diary entry", + "value": "", + "multiline": true, + "description": "You can use the rotate button on the image to make reading the data easier" + } + ] + } + } + ], + "Weekly Data": [ + { + "id": "ce074f95-4a4d-46ee-aeaa-df441a6b8c19", + "type": "entity", + "label": "Weekly Data", + "properties": { + "Date": [ + { + "id": "eb5c021e-7b02-4c36-8732-7a967f498ef6", + "type": "text-field", + "label": "Week Commencing", + "value": "", + "description": "Sunday date as DD/MM/YYYY", + "placeholder": "DD/MM/YYYY", + "allowMultiple": false + } + ], + "rainfall": [ + { + "id": "d625da46-46e1-4910-9d68-eebe564ce1d7", + "type": "text-field", + "label": "Total rainfall", + "value": "", + "description": "This can be found in the bottom right hand corner of the page" + } + ], + "Weekly Max": [ + { + "id": "4d654355-0a3a-441e-992e-a89eedf0864d", + "type": "text-field", + "label": "Thermometer 1", + "value": "", + "description": "This will either be the first reading or the Min avg depending on the page." + } + ], + "Weekly Min": [ + { + "id": "a5f5734f-e14f-469b-8b2c-92bd13b44ffb", + "type": "text-field", + "label": "Thermometer 2", + "value": "", + "description": "If there are just Min and Max readings, leave this blank." + } + ], + "Eilirs data": [ + { + "id": "ca93079b-07fd-4ca8-a210-831ab07c9d65", + "type": "text-field", + "label": "Thermometer 3", + "value": "", + "description": "This will either be the last avg. reading or the Max avg depending on the page." + } + ] + } + } + ], + "Transcribe Insert": [ + { + "id": "21462887-0996-476d-a199-6f436f8f4397", + "type": "entity", + "label": "Transcribe Insert", + "properties": { + "Date": [ + { + "id": "7f5197dc-55fe-42cf-b8b0-646598f7543f", + "type": "text-field", + "label": "Date of insert", + "value": "", + "description": "Date of the insert, if unsure then just put the day, month or year you can identify. Should be tied to the date of the register its attached too." + } + ], + "type": [ + { + "id": "23ec5f49-c3ef-42ae-980d-56623f4b8eaf", + "type": "dropdown-field", + "label": "Type of Item", + "value": null, + "options": [ + { + "text": "Newspaper Cutting", + "value": "Newspaper Cutting" + }, + { + "text": "Programme", + "value": "Programme" + }, + { + "text": "Leaflet", + "value": "Leaflet" + }, + { + "text": "Invitation", + "value": "Invitation" + }, + { + "text": "Journal", + "value": "Journal" + }, + { + "text": "Other", + "value": "Other" + }, + { + "text": "", + "value": "" + } + ], + "description": "Select the type of item from below", + "placeholder": "Select from list" + } + ], + "transcription": [ + { + "id": "d0d9c201-8fb2-4f4f-859b-cb545c988da0", + "type": "text-field", + "label": "Transcribe item", + "value": "", + "multiline": true, + "description": "You do not need to transcribe the entire insert. A synopsis of the information is enough.", + "previewInline": true + } + ] + } + } + ] + } + }, + "structure": { + "id": "a908c84e-c2fd-45fd-ab3e-38f21af3cd1a", + "type": "choice", + "description": "Here are some choices.", + "profile": [ + "tabs" + ], + "label": "The Chain", + "items": [ + { + "id": "e443bc83-0c1b-40be-b038-b5c267976d96", + "type": "model", + "description": "Transcribe Daily Data", + "label": "Daily Data", + "fields": [ + [ + "Daily Data", + [ + "Date of Entry (DD/MM/YYYY)", + "8.30", + "1:30", + "6:30", + "Max", + "Min", + "Thermometer reading 3" + ] + ] + ], + "instructions": "The barometer readings are shown as horizontal lines on the image, and the thermometer readings are shown as vertical lines. If you are unsure of any fields please consult the guidelines." + }, + { + "id": "582a5bb5-081d-4b80-a34e-504d572c910b", + "type": "model", + "label": "Transcribe diary", + "fields": [ + [ + "Diary Entry", + [ + "Date", + "Transcription", + "weekly diary" + ] + ] + ], + "description": "", + "instructions": "Record the date of the entry, as found along the top line of the image. Transcribe the diary entry (which is normally written vertically in each column). Use the rotate button to help read the diary entry." + }, + { + "id": "d2487729-5f0a-469a-a6ce-326563e27290", + "type": "model", + "label": "Weekly Data", + "fields": [ + [ + "Weekly Data", + [ + "Date", + "Weekly Max", + "Weekly Min", + "Eilirs data", + "rainfall" + ] + ] + ] + }, + { + "id": "4c9675b2-2a60-4644-8cd9-7d8d2ffb2255", + "type": "model", + "label": "Annual Data", + "fields": [ + [ + "Annual Data", + [ + "Date", + "Day temp", + "Lowest temp", + "Means HD", + "Mean night", + "Mean month", + "rainfall", + "rain days", + "Annual rain", + "Any other" + ] + ] + ] + }, + { + "id": "48e73e0c-6ce7-4dc0-897f-afbb28e486fa", + "type": "model", + "label": "Transcribe insert", + "fields": [ + [ + "Transcribe Insert", + [ + "Date", + "type", + "transcription" + ] + ] + ] + } + ] + } +} diff --git a/services/madoc-ts/fixtures/97-bugs/field-overwritten.json b/services/madoc-ts/fixtures/97-bugs/field-overwritten.json new file mode 100644 index 000000000..caf0c585d --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/field-overwritten.json @@ -0,0 +1,192 @@ +{ + "id": "9931ee8b-2e6c-4fee-9ebe-1a12726d0162", + "structure": { + "id": "4a8b054d-a9ff-4bdf-9069-2e3d20b009e1", + "type": "choice", + "label": "IDA-MVP", + "items": [ + { + "id": "6f361094-16b5-41d1-a6ea-cdf94e247238", + "type": "model", + "label": "Default", + "fields": [ + "box", + "test", + "color", + "dfghjk" + ] + } + ], + "description": "", + "profile": [ + "tabs" + ] + }, + "document": { + "id": "1322d4bd-4937-4674-bf62-447d8e2ea807", + "type": "entity", + "label": "IDA-MVP", + "properties": { + "box": [ + { + "id": "6f5a317a-a1ef-478d-a6aa-b994be03e6d1", + "type": "text-field", + "label": "box", + "value": "", + "selector": { + "id": "4c962a31-5fe6-40c9-84b2-4fbf48e0c95e", + "type": "box-selector", + "state": null + }, + "allowMultiple": true + } + ], + "test": [ + { + "id": "e1604795-847b-41b3-8ce5-8775d355f437", + "type": "text-field", + "label": "test", + "value": "added in review", + "selector": { + "id": "71491604-2164-464d-aed8-e207f7e9e0fb", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "27578ee6-1cfd-4ed7-9e7d-75634b66fc15", + "type": "box-selector", + "state": { + "x": 880, + "y": 254, + "width": 806, + "height": 730 + }, + "revises": "71491604-2164-464d-aed8-e207f7e9e0fb", + "revisedBy": [], + "revisionId": "cf355b2d-926f-4ac4-a525-9a812883ddde" + }, + { + "id": "c2f560da-e2c0-4c22-97a7-c5a93f74fcc8", + "type": "box-selector", + "state": { + "x": 1070, + "y": 482, + "width": 1289, + "height": 920 + }, + "revises": "71491604-2164-464d-aed8-e207f7e9e0fb", + "revisedBy": [], + "revisionId": "bd5d3dc6-434c-47b7-ac57-9d1cf94448c3" + } + ] + } + } + ], + "color": [ + { + "id": "a1ba3558-8277-45bc-ab32-b25bc7f3df34", + "type": "color-field", + "label": "color", + "value": "", + "clearable": true + }, + { + "id": "9d34923f-806c-49ec-b6f4-16806aceeb3e", + "type": "color-field", + "label": "color", + "value": "#ffd478", + "revises": "a1ba3558-8277-45bc-ab32-b25bc7f3df34", + "revision": "cf355b2d-926f-4ac4-a525-9a812883ddde", + "clearable": true + }, + { + "id": "c0cc2f26-b4a5-4213-9d28-47cefcb173ff", + "type": "color-field", + "label": "color", + "value": "#fefc78", + "revises": "a1ba3558-8277-45bc-ab32-b25bc7f3df34", + "revision": "bd5d3dc6-434c-47b7-ac57-9d1cf94448c3", + "clearable": true + } + ], + "dfghjk": [ + { + "id": "cf90d41a-f981-4de0-879b-bcbaf0c99269", + "type": "checkbox-field", + "label": "dfghjk", + "value": false, + "selector": { + "id": "553e7083-f811-4fe2-a4ef-661676937970", + "type": "box-selector", + "state": null + } + } + ] + } + }, + "revisions": [ + { + "fields": [ + "box", + "test", + "color", + "dfghjk" + ], + "structureId": "6f361094-16b5-41d1-a6ea-cdf94e247238", + "id": "cf355b2d-926f-4ac4-a525-9a812883ddde", + "revises": "6f361094-16b5-41d1-a6ea-cdf94e247238", + "approved": false, + "status": "draft", + "authors": [ + "urn:madoc:user:1" + ], + "label": "Default", + "captureModelId": "9931ee8b-2e6c-4fee-9ebe-1a12726d0162" + }, + { + "fields": [ + "box", + "test", + "color", + "dfghjk" + ], + "structureId": "6f361094-16b5-41d1-a6ea-cdf94e247238", + "id": "bd5d3dc6-434c-47b7-ac57-9d1cf94448c3", + "revises": "6f361094-16b5-41d1-a6ea-cdf94e247238", + "approved": true, + "status": "accepted", + "authors": [ + "urn:madoc:user:5" + ], + "label": "Default", + "captureModelId": "9931ee8b-2e6c-4fee-9ebe-1a12726d0162" + } + ], + "derivedFrom": "4e85e3cb-c2a3-4369-a14f-2467f65511fb", + "target": [ + { + "id": "urn:madoc:collection:492", + "type": "Collection" + }, + { + "id": "urn:madoc:manifest:556", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:3103", + "type": "Canvas" + } + ], + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "Admin-McLongname" + }, + "urn:madoc:user:5": { + "id": "urn:madoc:user:5", + "type": "Person", + "name": "Ben Admin" + } + } +} diff --git a/services/madoc-ts/migrations/2023-02-09T12-20.manifest-thumbnail.sql b/services/madoc-ts/migrations/2023-02-09T12-20.manifest-thumbnail.sql new file mode 100644 index 000000000..6bae7fad5 --- /dev/null +++ b/services/madoc-ts/migrations/2023-02-09T12-20.manifest-thumbnail.sql @@ -0,0 +1,38 @@ +--manifest-thumbnail (up) +create or replace function manifest_thumbnail( + sid int, manifest_id int +) returns text as +$$ +declare + return_value text; +begin + + -- First check if there is a `default_thumbnail` on the IIIF resource itself + select default_thumbnail + from iiif_resource + left join iiif_derived_resource_items ird on iiif_resource.id = ird.item_id + where id = manifest_id + and ird.site_id = sid + into return_value; + + if return_value is not null then + return return_value; + end if; + + + -- Otherwise get the default thumbnail from the first available canvas. + select default_thumbnail + from iiif_derived_resource_items + left join iiif_resource ir on iiif_derived_resource_items.item_id = ir.id + where item_index < 5 + and resource_id = manifest_id + and default_thumbnail is not null + and site_id = sid + limit 1 + into return_value; + + return return_value; +end; +$$ language plpgsql; + + diff --git a/services/madoc-ts/migrations/down/2023-02-09T12-20.manifest-thumbnail.sql b/services/madoc-ts/migrations/down/2023-02-09T12-20.manifest-thumbnail.sql new file mode 100644 index 000000000..702f538ea --- /dev/null +++ b/services/madoc-ts/migrations/down/2023-02-09T12-20.manifest-thumbnail.sql @@ -0,0 +1,22 @@ +--manifest-thumbnail (down) +create or replace function manifest_thumbnail( + sid int, manifest_id int +) returns text as +$$ +declare + return_value text; +begin + + select default_thumbnail + from iiif_derived_resource_items + left join iiif_resource ir on iiif_derived_resource_items.item_id = ir.id + where item_index < 5 + and resource_id = manifest_id + and default_thumbnail is not null + and site_id = sid + limit 1 + into return_value; + + return return_value; +end; +$$ language plpgsql; diff --git a/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-annotation-export.ts b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-annotation-export.ts new file mode 100644 index 000000000..101ea90bd --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-annotation-export.ts @@ -0,0 +1,77 @@ +import { parseUrn } from '../../../../utility/parse-urn'; +import { ExportFile } from '../../server-export'; +import { ExportConfig } from '../../types'; + +export const canvasAnnotationExport: ExportConfig = { + type: 'canvas-annotation-export', + metadata: { + label: { en: ['Canvas annotation export'] }, + description: { en: ['Export capture models as annotations'] }, + filePatterns: [ + // Make this work... + // - project_id would make multiple file patterns + // - uuid would be a placeholder. + // - format would come from config + `/manifests/{manifest}/annotations/{project_id}/{format}/{canvas}.json`, + ], + }, + supportedTypes: ['canvas'], + supportedContexts: ['project_id'], + configuration: { + filePerProject: false, + defaultValues: { + format: 'w3c-annotation', + }, + editor: { + project_id: { + type: 'hidden', + } as any, + format: { + label: 'Format', + type: 'dropdown-field', + options: [ + { value: 'w3c-annotation', text: 'w3c-annotation' }, + { value: 'open-annotation', text: 'open-annotation' }, + { value: 'w3c-annotation-pages', text: 'w3c-annotation-pages' }, + ], + } as any, + }, + }, + hookConfig(subject, options, config) { + if (config && options.subjectParent && options.subjectParent.type === 'manifest') { + const editor = config.editor as any; + return { + ...config, + editor: { + ...((editor as any) || {}), + project_id: { + type: 'autocomplete-field', + label: 'Project', + dataSource: `madoc-api://iiif/manifests/${options.subjectParent.id}/projects-autocomplete?all=true`, + requestInitial: true, + outputIdAsString: true, + clearable: true, + }, + }, + }; + } + }, + + async exportData(subject, options) { + const project = options.config && options.config.project_id ? parseUrn(options.config.project_id) : undefined; + + const resp = await options.api.getSiteCanvasPublishedModels(subject.id, { + project_id: project?.id, + format: options.config.format || 'w3c-annotation', + }); + + return [ + ExportFile.json( + resp, + `/manifests/${options.subjectParent?.id}/annotations/${project?.id || 'unknown'}/${options.config.format || + 'json'}/${subject.id}.json`, + true + ), + ]; + }, +}; diff --git a/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-api-export.ts b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-api-export.ts new file mode 100644 index 000000000..6dcd837c4 --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-api-export.ts @@ -0,0 +1,26 @@ +import { ExportFile } from '../../server-export'; +import { ExportConfig } from '../../types'; + +export const canvasApiExport: ExportConfig = { + type: 'canvas-api-export', + supportedTypes: ['canvas'], + metadata: { + label: { en: ['Canvas API'] }, + description: { en: ['Export from the internal Madoc API'] }, + filePatterns: ['/manifests/{manifest}/api/{canvas}.json'], + }, + async exportData(subject, options) { + if (!options.subjectParent) { + return []; + } + + return [ + ExportFile.json( + (await options.api.getCanvasById(subject.id)).canvas, + `/manifests/${options.subjectParent.id}/api/${subject.id}.json`, + true, + {} + ), + ]; + }, +}; diff --git a/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-model-export.ts b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-model-export.ts new file mode 100644 index 000000000..be913b7a7 --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-model-export.ts @@ -0,0 +1,73 @@ +import { parseUrn } from '../../../../utility/parse-urn'; +import { ExportFile } from '../../server-export'; +import { ExportConfig } from '../../types'; + +export const canvasModelExport: ExportConfig = { + type: 'canvas-model-export', + metadata: { + label: { en: ['Canvas model export'] }, + description: { en: ['Export capture models from projects'] }, + filePatterns: [`/manifests/{manifest}/models/{project_id}/{format}/{canvas}.json`], + }, + supportedTypes: ['canvas'], + configuration: { + filePerProject: true, + defaultValues: { + format: 'json', + }, + editor: { + project_id: { + type: 'autocomplete-field', + label: 'Project', + dataSource: 'madoc-api://iiif/manifests/:manifest/projects-autocomplete?all=true', + requestInitial: true, + outputIdAsString: false, + clearable: true, + } as any, + format: { + label: 'Format', + type: 'dropdown-field', + options: [ + { value: 'json', text: 'json' }, + { value: 'capture-model', text: 'capture-model' }, + { value: 'capture-model-with-pages', text: 'capture-model-with-pages' }, + { value: 'capture-model-with-pages-resolved', text: 'capture-model-with-pages-resolved' }, + ], + } as any, + }, + }, + hookConfig(subject, options, config) { + if (config && options.subjectParent && options.subjectParent.type === 'manifest') { + const editor = config.editor as any; + return { + ...config, + editor: { + ...((editor as any) || {}), + project_id: { + ...editor.project_id, + dataSource: editor.project_id.dataSource.replace(':manifest', options.subjectParent.id), + }, + }, + }; + } + }, + + async exportData(subject, options) { + const project = options.config && options.config.project_id ? parseUrn(options.config.project_id.uri) : undefined; + + const resp = await options.api.getSiteCanvasPublishedModels(subject.id, { + project_id: project?.id, + format: options.config?.format || 'json', + }); + + // @todo it would be better if getSiteCanvasPublishedModels returned a project-id if known. + return resp.models.map((model: any) => + ExportFile.json( + model, + `/manifests/${options.subjectParent?.id}/models/${project?.id || model.projectId || 'unknown'}/${options.config + ?.format || 'json'}/${subject.id}.json`, + true + ) + ); + }, +}; diff --git a/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-plaintext-export.ts b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-plaintext-export.ts new file mode 100644 index 000000000..a528bea67 --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/export-configs/canvas/canvas-plaintext-export.ts @@ -0,0 +1,25 @@ +import { ExportFile } from '../../server-export'; +import { ExportConfig } from '../../types'; + +export const canvasPlaintextExport: ExportConfig = { + type: 'canvas-plaintext', + supportedTypes: ['canvas'], + metadata: { + label: { en: ['Canvas Plaintext'] }, + description: { en: ['Export transcription as plaintext'] }, + filePatterns: ['/manifests/{manifest}/plaintext/{canvas}.txt'], + }, + async exportData(subject, options) { + if (!options.subjectParent) { + return []; + } + + const { transcription, found } = await options.api.getCanvasPlaintext(subject.id); + if (found && transcription) { + return [ + // Single text file with transcription + ExportFile.text(transcription, `/manifests/${options.subjectParent.id}/plaintext/${subject.id}.txt`, {}), + ]; + } + }, +}; diff --git a/services/madoc-ts/src/extensions/project-export/export-configs/manifest/manifest-api-export.ts b/services/madoc-ts/src/extensions/project-export/export-configs/manifest/manifest-api-export.ts new file mode 100644 index 000000000..06ae112a7 --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/export-configs/manifest/manifest-api-export.ts @@ -0,0 +1,18 @@ +import { ExportFile } from '../../server-export'; +import { ExportConfig } from '../../types'; + +export const manifestApiExport: ExportConfig = { + type: 'manifest-api-export', + metadata: { + label: { en: ['Manifest API'] }, + filePatterns: ['/manifests/{manifest}/api.json'], + description: { en: ['Export Manifest from API (without canvases)'] }, + }, + async exportData(subject, options) { + const { items: _, ...manifest } = (await options.api.getManifestById(subject.id)).manifest; + + return [ExportFile.json(manifest, `/manifests/${subject.id}/api.json`, true, {})]; + }, + + supportedTypes: ['manifest'], +}; diff --git a/services/madoc-ts/src/extensions/project-export/export-configs/project/project-api-export.ts b/services/madoc-ts/src/extensions/project-export/export-configs/project/project-api-export.ts new file mode 100644 index 000000000..0ed407b3a --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/export-configs/project/project-api-export.ts @@ -0,0 +1,18 @@ +import { ExportFile } from '../../server-export'; +import { ExportConfig, ExportDataOptions, ExportFileDefinition, SupportedExportResource } from '../../types'; + +export const projectApiExport: ExportConfig = { + type: 'project-api-export', + metadata: { + label: { en: ['Project API Export'] }, + description: { en: ['Export project metadata from the Madoc API'] }, + }, + supportedContexts: ['project_id'], + supportedTypes: ['project'], + async exportData( + subject: SupportedExportResource, + options: ExportDataOptions + ): Promise { + return [ExportFile.json(await options.api.getProject(subject.id), `/project-metadata.json`, true, {})]; + }, +}; diff --git a/services/madoc-ts/src/extensions/project-export/export-configs/project/project-csv-simple-export.ts b/services/madoc-ts/src/extensions/project-export/export-configs/project/project-csv-simple-export.ts new file mode 100644 index 000000000..86ea755ab --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/export-configs/project/project-csv-simple-export.ts @@ -0,0 +1,100 @@ +import { parseModelTarget } from '../../../../utility/parse-model-target'; +import { ExportFile } from '../../server-export'; +import { ExportConfig, ExportDataOptions, ExportFileDefinition, SupportedExportResource } from '../../types'; + +export const projectCsvSimpleExport: ExportConfig = { + type: 'project-csv-simple-export', + supportedTypes: ['project'], + metadata: { + label: { en: ['Simple CSV Export'] }, + description: { en: ['Exports simple capture models to CSV (no nested models)'] }, + }, + configuration: { + defaultValues: { + entity: '', + }, + editor: { + entity: {}, + }, + }, + hookConfig(subject, options, config) { + if (config && subject.type === 'project') { + const editor = config.editor as any; + return { + ...config, + editor: { + ...((editor as any) || {}), + entity: { + type: 'autocomplete-field', + label: 'Entity from model', + description: 'List will be empty if your capture model does not have any nested entities.', + dataSource: `madoc-api://projects/${subject.id}/model-autocomplete`, + requestInitial: true, + outputIdAsString: true, + clearable: true, + }, + }, + }; + } + }, + async exportData( + subject: SupportedExportResource, + options: ExportDataOptions + ): Promise { + // This will probably be a pretty long-running task. + + const allPublished = await options.api.getProjectFieldsRaw(subject.id, { + status: 'approved', + entity: options.config.entity, + }); + + const rowRecord: Record = {}; + for (const item of allPublished) { + rowRecord[item.doc_id] = rowRecord[item.doc_id] || { + target: item.target, + model_id: item.model_id, + doc_id: item.doc_id, + __fields: [], + }; + + if (!rowRecord[item.doc_id].__fields.includes(item.key)) { + rowRecord[item.doc_id].__fields.push(item.key); + } + + rowRecord[item.doc_id][item.key] = rowRecord[item.doc_id][item.key] || []; + rowRecord[item.doc_id][item.key].push({ value: item.value, id: item.id, revises: item.revises }); + } + + function findBest(fields: any[]) { + const revises = fields.map(r => r.revises); + return fields.filter(r => !revises.includes(r.id)).pop() || fields[0]; + } + + const mappedList = Object.entries(rowRecord) + .map(([key, record]) => { + const newRecord: any = {}; + + newRecord.model_id = record.model_id; + newRecord.doc_id = record.doc_id; + + if (record.__fields) { + for (const field of record.__fields) { + newRecord[field] = findBest(record[field])?.value; + } + + const target = parseModelTarget(record.target); + if (target.manifest) { + newRecord.manifest = target.manifest.id; + } + if (target.canvas) { + newRecord.canvas = target.canvas.id; + } + return newRecord; + } + return false; + }) + .filter(Boolean) as any[]; + + return [await ExportFile.csv(mappedList, 'project-data/data.csv')]; + }, +}; diff --git a/services/madoc-ts/src/extensions/project-export/extension.ts b/services/madoc-ts/src/extensions/project-export/extension.ts new file mode 100644 index 000000000..8c290fd20 --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/extension.ts @@ -0,0 +1,49 @@ +import { ApiClient } from '../../gateway/api'; +import { BaseExtension, defaultDispose } from '../extension-manager'; +import { RegistryExtension } from '../registry-extension'; +import { canvasAnnotationExport } from './export-configs/canvas/canvas-annotation-export'; +import { canvasApiExport } from './export-configs/canvas/canvas-api-export'; +import { canvasModelExport } from './export-configs/canvas/canvas-model-export'; +import { canvasPlaintextExport } from './export-configs/canvas/canvas-plaintext-export'; +import { manifestApiExport } from './export-configs/manifest/manifest-api-export'; +import { projectApiExport } from './export-configs/project/project-api-export'; +import { projectCsvSimpleExport } from './export-configs/project/project-csv-simple-export'; +import { ExportConfig } from './types'; + +export class ProjectExportExtension extends RegistryExtension implements BaseExtension { + api: ApiClient; + + static REGISTRY = 'project-export'; + + constructor(api: ApiClient) { + super({ + registryName: ProjectExportExtension.REGISTRY, + }); + this.api = api; + // List of default export options. + ProjectExportExtension.register(canvasApiExport); + ProjectExportExtension.register(canvasModelExport); + ProjectExportExtension.register(canvasPlaintextExport); + ProjectExportExtension.register(canvasAnnotationExport); + ProjectExportExtension.register(manifestApiExport); + ProjectExportExtension.register(projectApiExport); + ProjectExportExtension.register(projectCsvSimpleExport); + } + + dispose() { + defaultDispose(this); + super.dispose(); + } + + static register(definition: ExportConfig) { + RegistryExtension.emitter.emit(ProjectExportExtension.REGISTRY, definition); + } + + static removePlugin(event: { pluginId: string; siteId?: number; type: string }) { + RegistryExtension.emitter.emit(`remove-plugin-${ProjectExportExtension.REGISTRY}`, event); + } + + static registerPlugin(event: { pluginId: string; siteId?: number; definition: ExportConfig }) { + RegistryExtension.emitter.emit(`plugin-${ProjectExportExtension.REGISTRY}`, event); + } +} diff --git a/services/madoc-ts/src/extensions/project-export/server-export.ts b/services/madoc-ts/src/extensions/project-export/server-export.ts new file mode 100644 index 000000000..3b9ea119d --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/server-export.ts @@ -0,0 +1,65 @@ +// export-resource-task +import { ExportFileDefinition } from './types'; + +export function createServerExportTasks() { + // 1. What do we need to traverse + // 2. Create root task + // - Which contains + // 2. Create sub-task for project + // 3. Create sub-task for each Manifest + // 4. +} + +function text(value: string, path: string, metadata?: any): ExportFileDefinition { + return { + content: { + type: 'text', + value, + }, + text: true, + metadata, + path, + }; +} + +function json(value: any, path: string, pretty = false, metadata?: any): ExportFileDefinition { + return { + content: { + type: 'text', + value: JSON.stringify(value, null, pretty ? 2 : undefined), + contentType: 'application/json', + }, + text: true, + metadata, + path, + }; +} + +export async function csv(input: any, path: string, metadata?: any): Promise { + const { stringify } = await import('csv-stringify'); // Avoid bundling. + const csvData: string = await new Promise((resolve, reject) => { + stringify(input, { header: true }, (err, output) => { + if (err) { + return reject(err); + } + resolve(output); + }); + }); + + return { + content: { + type: 'text', + value: csvData, + contentType: 'text/csv', + }, + text: true, + metadata, + path, + }; +} + +export const ExportFile = { + text, + json, + csv, +}; diff --git a/services/madoc-ts/src/extensions/project-export/types.ts b/services/madoc-ts/src/extensions/project-export/types.ts new file mode 100644 index 000000000..fc2d9a79c --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/types.ts @@ -0,0 +1,153 @@ +import { InternationalString } from '@iiif/presentation-3'; +import React, { JSXElementConstructor } from 'react'; +import { CaptureModel } from '../../frontend/shared/capture-models/types/capture-model'; +import { ApiClient } from '../../gateway/api'; +import { BaseTask } from '../../gateway/tasks/base-task'; +import { CaptureModelShorthand } from '../projects/types'; + +export type SupportedExportResourceTypes = 'project' | 'manifest' | 'canvas'; + +export type SupportedExportResource = { type: SupportedExportResourceTypes; id: number }; + +export interface ExportResourceRequest { + // What is being exported at the level. + subject: SupportedExportResource; + + // Parent. + subjectParent?: SupportedExportResource; + + // Context of the export (usually project) + context: SupportedExportResource; + + // Mapping of configuration for the current level, example: + // [ + // [“project-basic”, {}], + // [“project-reviews-by-user”, {}], + // [“project-task-tree”, {}] + // ] + subjectExports: ExportPlan[keyof ExportPlan]; + + // Details on the file output. + output: + | { + // A do-nothing output - could be used for pushing to an external system. + type: 'none'; + } + | { + // Might not be used yet... but would be a good alternative. + type: 'task-state'; + } + | { + // This will be used for sub-tasks, only the top level zips. + type: 'disk'; + directory: string; + } + | { + // This will be used for top level tasks to zip. + type: 'zip'; + path: string; + fileName: string; + options: { tempDir: string }; // Unknown at this point. + }; + + standalone?: boolean; + + // Mapping of configuration (only top level), example: + // { + // “project”: [ + // [“project-basic”, {}], + // [“project-reviews-by-user”, {}], + // [“project-task-tree”, {}] + // ], + // “manifest”: [ + // [“manifest-iiif”, {“use-original-ids”: true}] + // ], + // “canvas”: [ + // [“canvas-plaintext”, {}], + // [“canvas-mapping”, {}], + // [“canvas-model-json”, {}] + // ], + // } + exportPlan?: ExportPlan; +} + +export type ExportPlan = Record>; + +export type ExportDataOptions = any> = { + api: ApiClient; + config: Config; + // Optional. + subjectParent?: SupportedExportResource; + context?: SupportedExportResource; + task?: BaseTask; + subExport?: (exportType: string, exportSubject: SupportedExportResource, config?: any) => void; +}; + +export interface ExportConfig = any> { + type: string; + source?: { type: string; id?: string; name: string }; + supportedTypes: Array; + supportedContexts?: Array<'project_id' | 'manifest_id' | 'collection_id'>; + // What data should the export get? + exportData( + subject: SupportedExportResource, + options: ExportDataOptions + ): Promise; + + /** + * This will run when + */ + handleSubExports?: ( + subject: SupportedExportResource, + options: ExportDataOptions + ) => Promise; + + hookConfig?: ( + subject: SupportedExportResource, + options: ExportDataOptions, + config: ExportConfig['configuration'] + ) => ExportConfig['configuration'] | undefined; + + // Display information. + metadata: { + label: InternationalString; + description?: InternationalString; + svgIcon?: string | JSXElementConstructor>; + /** + * File patterns + * + * If you set a list of file patterns then the export can be more easily previewed + * with your changes. + * + * Supported replacements: + * - {project}: numeric identifier of project + * - {manifest}: numeric identifier of the manifest + * - {canvas}: numeric identifier of canvas + */ + filePatterns?: string[]; + }; + + // Configuration, if available. + configuration?: { + filePerProject?: boolean; + defaultValues: Config; + editor: string | CaptureModelShorthand | CaptureModel['document']; + }; +} + +export interface ExportFileDefinition { + path: string; + text: boolean; + content: + | { type: 'text'; value: string; contentType?: string } // Text - JSON or whatever + | { type: 'html'; value: string } // HTML which could be previewed. + | { type: 'url'; value: string }; // A URL to a remote resource (e.g. external API integration) + metadata?: any; + encoding?: string | null | undefined; +} + +export interface ExportManifest { + creator: { id: number; name: string }; + created: string; + files: Array<{ path: string; metadata?: any; extension: string }>[]; +} diff --git a/services/madoc-ts/src/extensions/project-export/utils/file-patterns-to-list.ts b/services/madoc-ts/src/extensions/project-export/utils/file-patterns-to-list.ts new file mode 100644 index 000000000..99326f430 --- /dev/null +++ b/services/madoc-ts/src/extensions/project-export/utils/file-patterns-to-list.ts @@ -0,0 +1,81 @@ +import { parseUrn } from '../../../utility/parse-urn'; +import { ExportConfig, SupportedExportResource } from '../types'; + +export function filePatternsToList( + selected: ExportConfig, + options: { + subject: SupportedExportResource; + subjectParent?: SupportedExportResource; + context?: SupportedExportResource; + config?: any; + allProjects?: number[]; + } +): Array<{ subject: SupportedExportResource; path: string }> { + const subject = options.subject; + const files: Array<{ subject: SupportedExportResource; path: string }> = []; + + if (selected.metadata.filePatterns) { + for (const file of selected.metadata.filePatterns) { + const manifest = + subject.type === 'manifest' + ? subject.id + : options.subjectParent?.type === 'manifest' + ? options.subjectParent.id + : options.context?.type === 'manifest' + ? options.context.id + : null; + const canvas = subject.type === 'canvas' ? subject.id : null; + const project = + options.context?.type === 'project' + ? options.context.id + : options.config && options.config.project_id + ? parseUrn(options.config.project_id.uri)?.id + : undefined; + + let fileName = file.replace(/\{manifest}/g, `${manifest}`).replace(/\{canvas}/g, `${canvas}`); + + const config = { + ...(selected.configuration?.defaultValues || {}), + ...(options.config || {}), + }; + + for (const key of Object.keys(config)) { + if (key === 'project_id') { + // keyword! + continue; + } + fileName = fileName.replace(`{${key}}`, config[key]); + } + + if (fileName.indexOf('{project_id}') !== -1) { + if (project) { + fileName = fileName.replace(/\{project_id}/, `${project}`); + } else { + if (options.allProjects && selected.configuration?.filePerProject) { + // Apply all projects. + for (const singleProject of options.allProjects) { + files.push({ + subject, + path: fileName.replace(/\{project_id}/, `${singleProject}`), + }); + } + } else { + files.push({ + subject, + path: fileName.replace(/\{project_id}/, `unknown`), + }); + } + + continue; + } + } + + files.push({ + subject, + path: fileName, + }); + } + } + + return files; +} diff --git a/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.stories.tsx b/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.stories.tsx new file mode 100644 index 000000000..7bfe068b5 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.stories.tsx @@ -0,0 +1,122 @@ +import * as React from 'react'; +import { ProjectExportSnippet } from './ProjectExportSnippet'; + +export default { component: ProjectExportSnippet, title: 'admin/Project Export Snippet' }; + +const exampleTask = { + id: '29518619-dc1b-4b44-93c5-d5a093fd6144', + name: 'Export task', + description: '', + type: 'export-resource-task', + subject: 'urn:madoc:project:51', + status: 3, + status_text: 'completed', + state: { + empty: true, + }, + created_at: 1674658222240, + parameters: [ + { + output: { + path: 'projects/51', + type: 'zip', + options: { + tempDir: 'temp/project-51-1674658219218', + }, + fileName: 'project-51-1674658219218.zip', + }, + context: { + id: 51, + type: 'project', + }, + subject: { + id: 51, + type: 'project', + }, + exportPlan: { + canvas: [ + ['canvas-api-export', {}], + [ + 'canvas-model-export', + { + format: 'json', + }, + ], + ['canvas-plaintext', {}], + [ + 'canvas-annotation-export', + { + format: 'w3c-annotation', + project_id: 'urn:madoc:project:51', + }, + ], + ], + project: [], + manifest: [['manifest-api-export', {}]], + }, + standalone: false, + subjectExports: [], + }, + { + siteId: 1, + userId: 1, + }, + ], + context: ['urn:madoc:site:1'], + modified_at: 1674658247653, + root_task: null, + subject_parent: 'none', + delegated_owners: null, + delegated_task: null, + creator: { + id: 'urn:madoc:user:1', + name: 'Madoc TS', + }, + assignee: null, + parent_task: null, + events: ['madoc-ts.created', 'madoc-ts.subtask_type_status.export-resource-task.3', 'madoc-ts.status.3'], + metadata: {}, + subtasks: [ + { + id: 'b1498e39-75d1-43eb-9d54-9eeb27bbe195', + type: 'export-resource-task', + name: 'Export task', + status: 3, + subject: 'urn:madoc:manifest:272117', + status_text: 'completed', + state: {}, + metadata: {}, + }, + { + id: 'fa767574-cbdf-48f5-8d22-b6cc3b07581f', + type: 'export-resource-task', + name: 'Export task', + status: 3, + subject: 'urn:madoc:manifest:272277', + status_text: 'completed', + state: {}, + metadata: {}, + }, + ], + pagination: { + page: 1, + total_results: 3, + total_pages: 1, + }, + root_statistics: { + error: 0, + not_started: 0, + accepted: 0, + progress: 0, + done: 299, + }, +}; + +export const Default = ProjectExportSnippet.bind({}); +Default.args = { task: exampleTask, flex: true, onDownload: () => void 0 }; + +export const Card = () => ( +
    + void 0} /> +
    +); diff --git a/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.styles.ts b/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.styles.ts new file mode 100644 index 000000000..c8019e3e1 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.styles.ts @@ -0,0 +1,109 @@ +import styled, { css } from 'styled-components'; + +const Container = styled.div<{ $flex?: boolean }>` + background: #ffffff; + border: 1px solid #cdcdcd; + box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.06); + border-radius: 5px; + padding: 1em; + container-type: inline-size; + + ${props => + props.$flex && + css` + display: flex; + justify-content: space-between; + box-shadow: none; + border: none; + border-radius: 0; + + ${Progress} { + font-size: 0.5em; + } + `} +`; + +const TitleSection = styled.div` + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: auto; + grid-column-gap: 1em; + grid-template-areas: + 'title action' + 'subtitle action' + 'progress progress'; +`; + +const Title = styled.div` + grid-area: title; + font-size: 1.1em; +`; + +const Subtitle = styled.div` + font-size: 0.9em; + grid-area: subtitle; + color: #666; +`; + +const TagList = styled.div` + margin: 0.5em 0; + font-size: 0.8em; + display: flex; + flex-wrap: nowrap; + border-radius: 5px; + overflow: hidden; + z-index: 1; + max-width: 400px; + &:hover { + overflow: visible; + } +`; + +const Tag = styled.div` + background: #fff; + border: 1px solid #999; + color: #555; + padding: 0.2em 0.5em; + border-radius: 3px; + margin: 0.2em; + align-self: center; + white-space: nowrap; +`; + +const DownloadBox = styled.div` + background: #ddd; + display: flex; + border-radius: 3px; + place-self: center; +`; + +const DownloadLabel = styled.div` + flex: 1; + align-self: center; + padding: 0 0.5em; + font-size: 0.9em; +`; + +const ViewTask = styled.div` + grid-area: action; + align-self: center; + font-size: 0.9em; +`; + +const Progress = styled.div` + grid-area: progress; + min-width: 100px; +`; + +export const ProjectExportSnippetStyles = { + Container, + TitleSection, + Title, + Subtitle, + TagList, + Tag, + DownloadBox, + DownloadLabel, + ViewTask, + Progress, +}; diff --git a/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.tsx b/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.tsx new file mode 100644 index 000000000..fc78c9991 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/components/ProjectExportSnippet/ProjectExportSnippet.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { ExportResourceTask } from '../../../../gateway/tasks/export-resource-task'; +import { createDownload } from '../../../../utility/create-download'; +import { TimeAgo } from '../../../shared/atoms/TimeAgo'; +import { RootStatistics } from '../../../shared/components/RootStatistics'; +import { useApi, useOptionalApi } from '../../../shared/hooks/use-api'; +import { Button } from '../../../shared/navigation/Button'; +import { HrefLink } from '../../../shared/utility/href-link'; +import { ProjectExportSnippetStyles as S } from './ProjectExportSnippet.styles'; + +interface ProjectExportSnippetProps { + task: ExportResourceTask; + flex?: boolean; + taskLink?: string; + + onDownload?: (output: any) => void; + + apiDownload?: boolean; +} +export function ProjectExportSnippet({ task, flex, taskLink, onDownload, apiDownload }: ProjectExportSnippetProps) { + const output = task.parameters[0].output; + const api = useOptionalApi(); + const tags = Object.keys(task.parameters[0].exportPlan || {}).flatMap(r => + ((task.parameters[0].exportPlan || {}) as any)[r].map((r: any) => r[0]) + ); + + const onDownloadButton = () => { + if (onDownload) { + onDownload(output); + } + if (apiDownload && api && output.type === 'zip') { + api.getStorageRaw(`export`, `${output.path}/${output.fileName}`, false).then(r => { + r.blob().then(blob => createDownload(blob, output.fileName, 'application/zip' as any)); + }); + } + }; + + return ( + + + {task.name} + + {task.creator?.name}{' '} + {task.created_at || task.modified_at ? ( + + ) : null} + + {taskLink ? ( + + view task + + ) : null} + {task.root_statistics && task.status !== 3 ? ( + + + + ) : null} + + {tags.length ? ( + + {tags.map(tag => ( + {tag} + ))} + + ) : null} + {output && output.type === 'zip' && (onDownload || (apiDownload && api)) ? ( + + {output.fileName} + + + ) : null} + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/features/build-project-export.tsx b/services/madoc-ts/src/frontend/admin/features/build-project-export.tsx new file mode 100644 index 000000000..5a03574dc --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/features/build-project-export.tsx @@ -0,0 +1,249 @@ +import { getValue } from '@iiif/vault-helpers'; +import deepmerge from 'deepmerge'; +import React, { useEffect, useState } from 'react'; +import { useMutation } from 'react-query'; +import { useParams } from 'react-router-dom'; +import invariant from 'tiny-invariant'; +import { ExportResourceRequest } from '../../../extensions/project-export/types'; +import { BaseTask } from '../../../gateway/tasks/base-task'; +import { FilePreview } from '../../shared/components/FilePreview'; +import { LocaleString } from '../../shared/components/LocaleString'; +import { RichSelectionGrid } from '../../shared/components/RichSelectionGrid'; +import { RootStatistics } from '../../shared/components/RootStatistics'; +import { Stepper, StepperContainer } from '../../shared/components/Stepper'; +import { useApi } from '../../shared/hooks/use-api'; +import { useProjectExports } from '../../shared/hooks/use-project-exports'; +import { EmptyState } from '../../shared/layout/EmptyState'; +import { ProjectExportSnippet } from '../components/ProjectExportSnippet/ProjectExportSnippet'; +import { useExportBuilder } from '../stores/export-builder'; +import { EditExportConfiguration } from './edit-export-configuration'; +import { shallow } from 'zustand/shallow'; +import { Button, ButtonRow } from '../../shared/navigation/Button'; +import { ErrorMessage } from '../../shared/callouts/ErrorMessage'; + +export function BuildProjectExport() { + const params = useParams<{ id: string }>(); + const configs = useProjectExports(); + const store = useExportBuilder(); + const [finishChoice, setFinishChoices] = useState(false); + const selectedTypes = useExportBuilder((s: any) => Object.keys(s.choices), shallow); + const allComplete = + useExportBuilder( + (s: any) => + Object.entries(s.choices).reduce((acc, [, next]) => { + return acc && (next as any).is_complete; + }, true), + shallow + ) && selectedTypes.length > 0; + + const api = useApi(); + const [rootStatistics, setRootStatistics] = useState>({}); + const [generateExport, generateExportStatus] = useMutation(async () => { + const exportPlan: any = { + canvas: [], + manifest: [], + project: [], + }; + const choices = Object.entries(store.choices).map(([a, b]) => [a, (b as any).config]); + for (const [plan, planConfig] of choices) { + const config = configs.find(s => s.type === plan); + if (config) { + for (const supported of config.supportedTypes) { + exportPlan[supported] = exportPlan[supported] ? exportPlan[supported] : []; + exportPlan[supported].push([plan, planConfig]); + } + } + } + + invariant(params.id); + + const temp = `project-${params.id}-${Date.now()}`; + + const input: Omit = { + subjectExports: exportPlan.project || [], + // Need to sort into these exports. + exportPlan: exportPlan, + standalone: false, + // Need the output. + output: { + type: 'zip', + path: `projects/${params.id}`, + options: { tempDir: `temp/${temp}` }, + fileName: `${temp}.zip`, + }, + }; + + const { task } = await api.createProjectExport(params.id, { + label: `Export of ${temp}`, + request: input, + }); + + if (!task) { + throw new Error('Unknown error'); + } + + return await api.wrapTask( + Promise.resolve(task), + t => { + return t; + }, + { + setRootStatistics, + root: true, + } + ); + }); + + useEffect(() => () => store.reset(), []); + + const isGenerating = !generateExportStatus.isIdle; + + return ( + <> + + setFinishChoices(false)} + > + { + if (selectedTypes.includes(type)) { + store.remove(type); + } else { + const full = configs.find(s => s.type === type); + if (full) { + let config: any = deepmerge({}, full?.configuration?.defaultValues || {}); + if (full.supportedContexts?.includes('project_id')) { + config.project_id = `urn:madoc:project:${params.id}`; + } + if (full?.hookConfig) { + config = full.hookConfig( + { id: Number(params.id), type: 'project' }, + { + config, + api, + }, + full?.configuration + ); + } + + console.log('config -> ', config); + + store.add(type, config, !full.configuration); + } + } + }} + items={configs.map(item => ({ + id: item.type, + label: item.metadata.label, + description: item.metadata.description, + }))} + /> + + + + + + + {configs.map(selected => { + if (!selectedTypes.includes(selected.type)) { + return null; + } + + const status = selected.configuration && !store.choices[selected.type].is_complete ? 'progress' : 'done'; + + console.log('selected', store.choices[selected.type].config); + + return ( + (selected.configuration ? store.uncomplete(selected.type) : void 0)} + open={status !== 'done' && !isGenerating} + key={selected.type} + > + {selected.metadata.label} + {selected.configuration ? ( + { + store.configure(selected.type, newValues); + store.complete(selected.type); + }} + /> + ) : ( + + No configuration options + + + )} + + ); + })} + + +

    Selected configuration

    + + {{ + type: 'text', + value: JSON.stringify( + Object.entries(store.choices).map(([a, b]) => [a, (b as any).config]), + null, + 2 + ), + }} + + + + + +
    + + {isGenerating ? ( + + {generateExportStatus.isLoading && rootStatistics ? : null} + + {generateExportStatus.isError ? ( + <> + Could not generate export. + + {{ type: 'json', value: JSON.stringify(generateExportStatus.error, null, 2) || '' }} + + + ) : null} + + {generateExportStatus.isSuccess ? ( +
    + +
    + ) : null} +
    + ) : null} +
    + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/features/edit-export-configuration.tsx b/services/madoc-ts/src/frontend/admin/features/edit-export-configuration.tsx new file mode 100644 index 000000000..3f0107bdd --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/features/edit-export-configuration.tsx @@ -0,0 +1,56 @@ +import React, { useMemo } from 'react'; +import { useResourceContext } from 'react-iiif-vault'; +import { ExportConfig } from '../../../extensions/project-export/types'; +import { EditShorthandCaptureModel } from '../../shared/capture-models/EditorShorthandCaptureModel'; +import { EditorSlots } from '../../shared/capture-models/new/components/EditorSlots'; +import { useApi } from '../../shared/hooks/use-api'; + +interface EditExportConfigurationProps { + config: ExportConfig; + value?: ExportConfig['configuration']; + onUpdate: (newValues: any) => void; +} +export function EditExportConfiguration(props: EditExportConfigurationProps) { + const ctx = useResourceContext(); + const selected = props.config; + const api = useApi(); + const editorConfig = useMemo(() => { + // if (selected?.hookConfig) { + // const hooked = selected.hookConfig( + // { id: 38, type: 'project' }, + // { + // // config, + // api, + // }, + // selected?.configuration + // ); + // if (hooked) { + // return hooked; + // } + // } + + return selected.configuration; + }, [selected.configuration]); + + console.log(props); + + if (!editorConfig) { + // No config. + return null; + } + + return ( + props.onUpdate(newData)} + saveLabel="Update" + // fullDocument={!!document} + keepExtraFields + immutableFields={['project_id']} + > + + + ); +} diff --git a/services/madoc-ts/src/frontend/admin/features/view-project-exports.tsx b/services/madoc-ts/src/frontend/admin/features/view-project-exports.tsx new file mode 100644 index 000000000..60b195fbf --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/features/view-project-exports.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { useParams } from 'react-router'; +import { apiHooks } from '../../shared/hooks/use-api-query'; +import { Button, ButtonRow } from '../../shared/navigation/Button'; +import { HrefLink } from '../../shared/utility/href-link'; +import { ProjectExportSnippet } from '../components/ProjectExportSnippet/ProjectExportSnippet'; + +export function ViewProjectExports() { + const { id } = useParams<{ id: string }>(); + + const { data } = apiHooks.getTasks(() => [ + 0, + { all: true, type: 'export-resource-task', subject: `urn:madoc:project:${id}`, detail: true }, + ]); + + return ( +
    +

    Project exports

    + {(data?.tasks || []).map(task => ( + + ))} + + + +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-export.tsx b/services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-export.tsx new file mode 100644 index 000000000..910bb9ac4 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/content/canvases/canvas-export.tsx @@ -0,0 +1,96 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { ExportConfig } from '../../../../../extensions/project-export/types'; +import { EditShorthandCaptureModel } from '../../../../shared/capture-models/EditorShorthandCaptureModel'; +import { EditorSlots } from '../../../../shared/capture-models/new/components/EditorSlots'; +import { FilePreview } from '../../../../shared/components/FilePreview'; +import { RichSelectionGrid } from '../../../../shared/components/RichSelectionGrid'; +import { useApi } from '../../../../shared/hooks/use-api'; +import { useExportResourcePreview } from '../../../../shared/hooks/use-export-resource-preview'; +import { useProjectExports } from '../../../../shared/hooks/use-project-exports'; +import { Spinner } from '../../../../shared/icons/Spinner'; +import { EmptyState } from '../../../../shared/layout/EmptyState'; + +export function CanvasExport() { + const params = useParams<'id' | 'manifestId'>(); + const api = useApi(); + const items = useProjectExports('canvas'); + const [selectedType, setSelected] = useState(''); + const [config, setConfig] = useState(); + const [{ data, isLoading }, selected, expectedFiles] = useExportResourcePreview(selectedType, { + subject: { id: Number(params.id), type: 'canvas' }, + subjectParent: { id: Number(params.manifestId), type: 'manifest' }, + config: config, + }); + + const editorConfig = useMemo(() => { + if (selected?.hookConfig) { + const hooked = selected.hookConfig( + { id: Number(params.id), type: 'canvas' }, + { + config, + api, + subjectParent: { id: Number(params.manifestId), type: 'manifest' }, + }, + selected?.configuration + ); + if (hooked) { + return hooked; + } + } + + return selected?.configuration; + }, [api, params.id, params.manifestId, selectedType]); + + useEffect(() => { + setConfig(selected?.configuration?.defaultValues); + }, [selectedType]); + + return ( +
    +

    Canvas export

    + setSelected(s => (s.indexOf(id) === -1 ? [...s, id] : s.filter(i => i !== id)))} + onSelect={id => setSelected(id)} + items={items.map(item => ({ + id: item.type, + label: item.metadata.label, + description: item.metadata.description, + }))} + /> + + {selected && selected.configuration && editorConfig ? ( + setConfig(newData)} + saveLabel="Update" + // fullDocument={!!document} + keepExtraFields + > + + + ) : null} + + {selected && isLoading ? ( + + + + ) : null} + {selected && (!data || data.length === 0) && !isLoading ? No exported data : null} + <> + {data ? ( +
    + {data.map(file => ( + + {file.content} + + ))} +
    + ) : null} + +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-export.tsx b/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-export.tsx new file mode 100644 index 000000000..ab1e7e9e9 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/content/manifests/manifest-export.tsx @@ -0,0 +1,141 @@ +import React, { useMemo, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { ExportConfig, SupportedExportResource } from '../../../../../extensions/project-export/types'; +import { EditShorthandCaptureModel } from '../../../../shared/capture-models/EditorShorthandCaptureModel'; +import { EditorSlots } from '../../../../shared/capture-models/new/components/EditorSlots'; +import { FilePreview } from '../../../../shared/components/FilePreview'; +import { RichSelectionGrid } from '../../../../shared/components/RichSelectionGrid'; +import { useApi } from '../../../../shared/hooks/use-api'; +import { useData, usePaginatedData } from '../../../../shared/hooks/use-data'; +import { useExportResourcePreview } from '../../../../shared/hooks/use-export-resource-preview'; +import { useExportResources } from '../../../../shared/hooks/use-export-resources'; +import { useProjectExports } from '../../../../shared/hooks/use-project-exports'; +import { Pagination } from '../../../molecules/Pagination'; +import { ManifestCanvases } from './manifest-canvases'; +import { ManifestProjects } from './manifest-projects'; + +export function ManifestExport() { + const api = useApi(); + const params = useParams(); + const { resolvedData: data } = usePaginatedData(ManifestCanvases); + const { data: allProjectsData } = useData(ManifestProjects); + const manifestOptions = useProjectExports('manifest'); + const canvasOptions = useProjectExports('canvas'); + const [config, setConfig] = useState(); + const [selectedType, setSelected] = useState(''); + const subjects: SupportedExportResource[] = data?.manifest.items.map(r => ({ id: r.id, type: 'canvas' })) || []; + const [expectedCanvasFiles, selected] = useExportResources(selectedType, { + subjects, + subjectsType: 'canvas', + subjectParent: data ? { id: data.manifest.id, type: 'manifest' } : undefined, + allProjects: allProjectsData?.projects.map(p => p.id), + config, + }); + const [{ data: manifestData }, , expectedManifestFiles] = useExportResourcePreview(selectedType, { + subject: { id: Number(params.id), type: 'manifest' }, + config, + }); + const editorConfig = useMemo(() => { + if (selected?.hookConfig) { + const hooked = selected.hookConfig( + { id: Number(params.id), type: 'canvas' }, + { + config, + api, + subjectParent: { id: Number(params.id), type: 'manifest' }, + }, + selected?.configuration + ); + if (hooked) { + return hooked; + } + } + + return selected?.configuration; + }, [api, params.id, params.manifestId, selectedType]); + + return ( +
    +

    Manifest export

    + setSelected(id)} + items={[...manifestOptions, ...canvasOptions].map(item => ({ + id: item.type, + label: item.metadata.label, + description: item.metadata.description, + }))} + /> + + {selected && selected.configuration && editorConfig ? ( + setConfig(newData)} + saveLabel="Update" + // fullDocument={!!document} + keepExtraFields + > + + + ) : null} + + {expectedManifestFiles && expectedManifestFiles.length ? ( +
    + {manifestData ? ( +
    + {manifestData.map(file => ( + + {file.content} + + ))} +
    + ) : null} +
    + ) : null} + + {expectedCanvasFiles && expectedCanvasFiles.length ? ( +
    + {data?.pagination.totalPages !== 1 ? ( + + ) : null} + {expectedCanvasFiles.map(file => ( + + selected && + selected + .exportData(file.subject, { + subjectParent: { id: data?.manifest.id || 0, type: 'manifest' }, + api, + config, + }) + .catch(err => { + console.log(err); + return []; + }) + .then(r => { + console.log('all', r); + return r?.find(item => item.path === file.path)?.content; + }) + } + /> + ))} + {data?.pagination.totalPages !== 1 ? ( + + ) : null} +
    + ) : null} +
    + ); +} diff --git a/services/madoc-ts/src/frontend/admin/pages/tasks/export-resource-task.tsx b/services/madoc-ts/src/frontend/admin/pages/tasks/export-resource-task.tsx new file mode 100644 index 000000000..62bdf60f4 --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/pages/tasks/export-resource-task.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { ImportManifestTask } from '../../../../gateway/tasks/import-manifest'; +import { ProjectExportSnippet } from '../../components/ProjectExportSnippet/ProjectExportSnippet'; +import { GenericTask } from './generic-task'; + +export function ExportResourceTask({ task, statusBar }: { task: ImportManifestTask; statusBar?: JSX.Element }) { + return ( + } + /> + ); +} diff --git a/services/madoc-ts/src/frontend/admin/stores/export-builder.ts b/services/madoc-ts/src/frontend/admin/stores/export-builder.ts new file mode 100644 index 000000000..936817d0d --- /dev/null +++ b/services/madoc-ts/src/frontend/admin/stores/export-builder.ts @@ -0,0 +1,60 @@ +import { create, createStore } from 'zustand'; + +// 1. Which have been selected +// - add +// - remove +// 2. Configuration for each +// - set default +// - update +// - remove +// 3. Default state (e.g. project_id, collection_id, manifest_id) +// 4. Which states are "complete" in the UI + +export type DefaultContext = { + project_id: undefined | number; + manifest_id: undefined | number; + collection_id: undefined | number; +}; + +export const useExportBuilder = create((set, get) => ({ + choices: {} as Record< + string, + { + is_complete: boolean; + config: any; + + // + } + >, + reset() { + set({ choices: {} }); + }, + add(type: string, config: any, is_complete = false) { + set((s: any) => ({ + choices: { ...s.choices, [type]: { is_complete, config } }, + })); + }, + remove(type: string) { + set((s: any) => { + const { [type]: _, ...choices } = s.choices; + return { + choices, + }; + }); + }, + configure(type: string, config: any) { + set((s: any) => ({ + choices: { ...s.choices, [type]: { ...(s.choices[type] || {}), config } }, + })); + }, + complete(type: string) { + set((s: any) => ({ + choices: { ...s.choices, [type]: { ...(s.choices[type] || {}), is_complete: true } }, + })); + }, + uncomplete(type: string) { + set((s: any) => ({ + choices: { ...s.choices, [type]: { ...(s.choices[type] || {}), is_complete: false } }, + })); + }, +})); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/ViewDocument.styles.ts b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/ViewDocument.styles.ts new file mode 100644 index 000000000..3f0591e24 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/ViewDocument.styles.ts @@ -0,0 +1,98 @@ +import styled, { css } from 'styled-components'; + +export const DocumentLabel = styled.div` + position: relative; + font-size: 11px; + font-weight: 600; + color: #999; + display: flex; + align-items: center; +`; + +export const DocumentLabelIcon = styled.div` + //position: absolute; + //right: 2px; + //top: 2px; + margin-left: auto; +`; + +export const DocumentDescription = styled.div` + font-size: 11px; + color: #999; +`; + +export const DocumentHeading = styled.div<{ $interactive?: boolean }>` + margin: 5px 0; + ${props => + props.$interactive && + css` + cursor: pointer; + `} +`; + +export const DocumentValueWrapper = styled.div` + //background: palegoldenrod; +`; + +export const DocumentSection = styled.div` + border-bottom: 1px solid #eff3fd; + background: #fff; + overflow: hidden; + margin: 0.4em; + border-radius: 3px; + outline: 3px solid transparent; + transition: outline-color 1s; + &[data-highlighted='true'] { + outline-color: orangered; + } +`; + +export const FieldSection = styled.div` + display: flex; + flex: 1; + align-items: center; + outline: 3px solid transparent; + transition: outline-color 1s; + &[data-highlighted='true'] { + outline-color: orangered; + } +`; + +export const DocumentSectionField = styled.div` + //border-bottom: 1px solid #eee; + padding-bottom: 0.4em; + margin-bottom: 0.2em; + background: #fff; + outline: 3px solid transparent; + transition: outline-color 1s; + &[data-highlighted='true'] { + outline-color: orangered; + } +`; + +export const DocumentCollapse = styled.div` + background: #fff; + padding: 10px; + overflow-y: auto; +`; + +export const DocumentEntityList = styled.div` + padding: 2px; + background: #e9effc; + border-radius: 5px; + overflow-y: auto; +`; + +export const DocumentEntityLabel = styled.div` + color: #777; + font-weight: 600; + font-size: 15px; + position: relative; + padding: 5px 10px; +`; + +export const FieldPreviewWrapper = styled.div` + white-space: pre-wrap; + display: flex; + align-items: center; +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/ViewDocument.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/ViewDocument.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewEntity.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewEntity.tsx new file mode 100644 index 000000000..36ca2cc23 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewEntity.tsx @@ -0,0 +1,101 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useDecayState } from '../../../../hooks/use-decay-state'; +import { DownArrowIcon } from '../../../../icons/DownArrowIcon'; +import { useSelectorHelper } from '../../../editor/stores/selectors/selector-helper'; +import { resolveSelector } from '../../../helpers/resolve-selector'; +import { useModelTranslation } from '../../../hooks/use-model-translation'; +import { CaptureModel } from '../../../types/capture-model'; +import { getEntityLabel } from '../../../utility/get-entity-label'; +import { DocumentEntityLabel, DocumentHeading, DocumentSection } from '../ViewDocument.styles'; +import { ViewSelector } from './ViewSelector'; + +export interface ViewEntityProps { + collapsed?: boolean; + entity: CaptureModel['document']; + interactive?: boolean; + fluidImage?: boolean; + highlightRevisionChanges?: string; + children: React.ReactNode; +} + +export function ViewEntity({ + entity, + collapsed, + children, + interactive = true, + fluidImage, + highlightRevisionChanges, +}: ViewEntityProps) { + const ref = useRef(null); + const helper = useSelectorHelper(); + const [isCollapsed, setIsCollapsed] = useState(collapsed || !!entity.selector); + const selector = entity.selector ? resolveSelector(entity.selector, highlightRevisionChanges) : undefined; + const selectorId = selector?.id; + const { t: tModel } = useModelTranslation(); + const label = getEntityLabel(entity, undefined, false, tModel); + const [isOn, trigger] = useDecayState(); + + useEffect(() => { + if (selectorId) { + return helper.withSelector(selectorId).on('click', () => { + trigger(); + setIsCollapsed(false); + if (ref.current) { + ref.current.scrollIntoView({ block: 'nearest', inline: 'center' }); + } + }); + } + }, [helper, selectorId]); + + useEffect(() => { + if (selectorId) { + return helper.withSelector(selectorId).on('hover', () => { + trigger(); + setIsCollapsed(false); + if (ref.current) { + ref.current.scrollIntoView({ block: 'nearest', inline: 'center' }); + } + }); + } + }, [helper, selectorId]); + + + return ( + + (selectorId ? helper.highlight(selectorId) : null)} + onMouseLeave={() => (selectorId ? helper.clearHighlight(selectorId) : null)} + $interactive={interactive} + onClick={() => (interactive ? setIsCollapsed(i => !i) : undefined)} + > + + {selector && !fluidImage ? : null} +
    {label}
    + {interactive ? ( + + ) : null} +
    +
    + {/* This is where the shortened label goes, and where the collapse UI goes. Should be collapsed by default. */} + {/* This is where the entity selector will go, if it exists. */} + {isCollapsed ? null : ( + <> + {selector && fluidImage ? ( + + ) : null} + {children} + + )} +
    + ); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewField.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewField.tsx new file mode 100644 index 000000000..6a00b620c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewField.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useRef } from 'react'; +import { useDecayState } from '../../../../hooks/use-decay-state'; +import { FieldPreview } from '../../../editor/components/FieldPreview/FieldPreview'; +import { useSelectorHelper } from '../../../editor/stores/selectors/selector-helper'; +import { resolveSelector } from '../../../helpers/resolve-selector'; +import { BaseField } from '../../../types/field-types'; +import { FieldSection } from '../ViewDocument.styles'; +import { ViewSelector } from './ViewSelector'; + +export function ViewField({ + field, + fluidImage, + revisionId, +}: { + field: BaseField; + fluidImage?: boolean; + revisionId?: string; +}) { + const ref = useRef(null); + + const helper = useSelectorHelper(); + + const selector = field.selector ? resolveSelector(field.selector, revisionId) : undefined; + const selectorId = selector?.id; + const [isOn, trigger] = useDecayState(); + + useEffect(() => { + if (selectorId) { + return helper.withSelector(selectorId).on('click', () => { + trigger(); + if (ref.current) { + ref.current.scrollIntoView({ block: 'nearest', inline: 'center' }); + } + }); + } + }, [helper, selectorId, trigger]); + + useEffect(() => { + if (selectorId) { + return helper.withSelector(selectorId).on('hover', () => { + trigger(); + if (ref.current) { + ref.current.scrollIntoView({ block: 'nearest', inline: 'center' }); + } + }); + } + }, [helper, selectorId, trigger]); + + if (!selector) { + return ; + } + + return ( + (selectorId ? helper.highlight(selectorId) : null)} + onMouseLeave={() => (selectorId ? helper.clearHighlight(selectorId) : null)} + data-highlighted={isOn} + > + + + + ); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewProperty.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewProperty.tsx new file mode 100644 index 000000000..c3efb52f3 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewProperty.tsx @@ -0,0 +1,50 @@ +import React, { useState } from 'react'; +import { DownArrowIcon } from '../../../../icons/DownArrowIcon'; +import { + DocumentDescription, + DocumentHeading, + DocumentLabel, + DocumentLabelIcon, + DocumentSectionField, + DocumentValueWrapper, +} from '../ViewDocument.styles'; + +export interface ViewPropertyProps { + collapsed?: boolean; + interactive?: boolean; + label: string; + description?: string; + fallback?: any; + children?: React.ReactNode; +} + +export function ViewProperty({ label, description, interactive, collapsed, fallback, children }: ViewPropertyProps) { + const [isCollapsed, setIsCollapsed] = useState(collapsed); + + return ( + + (interactive ? setIsCollapsed(i => !i) : undefined)}> + + {label} + {interactive ? ( + + + + ) : null} + + {isCollapsed && fallback ? ( + {fallback} + ) : description ? ( + {description} + ) : null} + + {!isCollapsed ? {children} : null} + + ); +} 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 new file mode 100644 index 000000000..2e5ac8719 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/components/ViewSelector.tsx @@ -0,0 +1,53 @@ +import { ImageService } from '@iiif/presentation-3'; +import React, { useEffect, useState } from 'react'; +import { useImageService } from 'react-iiif-vault'; +import { CroppedImage } from '../../../../atoms/Images'; +import { useCroppedRegion } from '../../../../hooks/use-cropped-region'; +import { useSelectorHelper } from '../../../editor/stores/selectors/selector-helper'; +import { resolveSelector } from '../../../helpers/resolve-selector'; +import { BaseSelector } from '../../../types/selector-types'; + +export const ViewSelector: React.FC<{ + selector?: BaseSelector; + fluidImage?: boolean; + inline?: boolean; + small?: boolean; + highlightRevisionChanges?: string; +}> = ({ selector: _selector, fluidImage, inline, highlightRevisionChanges }) => { + const selector = _selector ? resolveSelector(_selector, highlightRevisionChanges) : undefined; + const helper = useSelectorHelper(); + const { data: service } = useImageService() as { data?: ImageService }; + const croppedRegion = useCroppedRegion(); + const [image, setImage] = useState(''); + const selectorId = selector?.id; + + useEffect(() => { + if (selector && service && selector.state) { + const cropped = croppedRegion(selector.state); + if (cropped) { + setImage(cropped); + } + } + }, [croppedRegion, selector, service]); + + if (!image) { + return null; + } + + return ( + { + if (selectorId) { + e.preventDefault(); + e.stopPropagation(); + helper.withSelector(selectorId).zoomTo(); + } + }} + > + cropped region of image + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-entity-list.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-entity-list.tsx new file mode 100644 index 000000000..237b44a7b --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-entity-list.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { ViewEntity } from '../components/ViewEntity'; +import { DocumentCollapse, DocumentEntityList } from '../ViewDocument.styles'; +import { renderProperty } from './render-property'; + +export const renderEntityList = ( + entities: CaptureModel['document'][], + { + filterRevisions = [], + highlightRevisionChanges, + collapsedEntity, + fluidImage, + tModel, + }: { + filterRevisions: string[]; + highlightRevisionChanges?: string; + collapsedEntity?: boolean; + fluidImage?: boolean; + tModel: (s: string) => string; + } +) => { + const toRender = entities + .map(entity => { + const flatInnerProperties = Object.entries(entity.properties); + + const renderedProps = flatInnerProperties + .map(([key, field]) => { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + const rendered = renderProperty(field, { + key, + filterRevisions, + highlightRevisionChanges, + collapsed: collapsedEntity, + tModel, + }); + if (!rendered) { + return null; + } + return {rendered}; + }) + .filter(e => e !== null); + + if (renderedProps.length === 0) { + return null; + } + + return ( + + {renderedProps} + + ); + }) + .filter(e => e !== null); + + if (toRender.length === 0) { + return null; + } + + return {toRender}; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-field-list.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-field-list.tsx new file mode 100644 index 000000000..a95575dfe --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-field-list.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { FieldPreview } from '../../../editor/components/FieldPreview/FieldPreview'; +import { filterRevises } from '../../../helpers/filter-revises'; +import { BaseField } from '../../../types/field-types'; +import { isEmptyFieldList } from '../../../utility/is-field-list-empty'; +import { ViewField } from '../components/ViewField'; +import { FieldPreviewWrapper } from '../ViewDocument.styles'; + +export const renderFieldList = ( + fields: BaseField[], + { fluidImage, revisionId }: { fluidImage?: boolean; tModel: (s: string) => string; revisionId?: string } +) => { + if (!fields || isEmptyFieldList(fields)) { + return null; + } + + const filteredFields = (filterRevises(fields) as BaseField[]).filter(r => { + return r.value || r.selector; + }); + + if (filteredFields.length === 0) { + return null; + } + + return ( + + {filteredFields.map(field => ( + + ))} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-property.tsx b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-property.tsx new file mode 100644 index 000000000..b4eebf2d7 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/_components/ViewDocument/render/render-property.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { isRef } from 'react-dnd/lib/utils/isRef'; +import { filterRevises } from '../../../helpers/filter-revises'; +import { isEntityList } from '../../../helpers/is-entity'; +import { CaptureModel } from '../../../types/capture-model'; +import { BaseField } from '../../../types/field-types'; +import { ViewProperty } from '../components/ViewProperty'; +import { renderEntityList } from './render-entity-list'; +import { renderFieldList } from './render-field-list'; + +export const renderProperty = ( + fields: BaseField[] | CaptureModel['document'][], + { + key, + filterRevisions = [], + collapsed, + collapsedEntity, + highlightRevisionChanges, + fluidImage, + tModel, + }: { + key: any; + filterRevisions?: string[]; + highlightRevisionChanges?: string; + collapsed?: boolean; + collapsedEntity?: boolean; + fluidImage?: boolean; + tModel: (s: string) => string; + } +) => { + const label = + fields.length > 1 && fields[0] && fields[0].pluralLabel ? fields[0].pluralLabel : fields[0] ? fields[0].label : ''; + + const arrFixed: Array = fields; + const firstFilter = arrFixed.filter(item => filterRevisions.indexOf(item.revision ? item.revision : '') === -1); + + const filteredFields = filterRevises(firstFilter).filter(f => { + if (highlightRevisionChanges) { + const revised = f.type === 'entity' || (f.revision && f.revision === highlightRevisionChanges); + if (!revised) { + if (f.selector && f.selector.revisedBy) { + for (const selector of f.selector.revisedBy) { + if (!selector.revisionId || filterRevisions.indexOf(selector.revisionId) === -1) { + return true; + } + } + } + } + return revised; + } + + return !f.revision || filterRevisions.indexOf(f.revision) === -1; + }); + const description = fields[0]?.description; + const renderedProperties = isEntityList(filteredFields) + ? renderEntityList(filteredFields, { + filterRevisions, + highlightRevisionChanges, + collapsedEntity, + fluidImage, + tModel, + }) + : renderFieldList(filteredFields as any, { fluidImage, tModel, revisionId: highlightRevisionChanges }); + + if (!renderedProperties) { + return null; + } + + return ( + + {renderedProperties} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/new/CoreModelEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/new/CoreModelEditor.tsx new file mode 100644 index 000000000..eb34a5698 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/new/CoreModelEditor.tsx @@ -0,0 +1,372 @@ +import { Runtime } from '@atlas-viewer/atlas'; +import React, { useCallback, useReducer, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PARAGRAPHS_PROFILE } from '../../../../extensions/capture-models/Paragraphs/Paragraphs.helpers'; +import { slotConfig } from '../../../../extensions/capture-models/Paragraphs/Paragraphs.slots'; +import { AnnotationStyles } from '../../../../types/annotation-styles'; +import { CanvasHighlightedRegions } from '../../../site/features/CanvasHighlightedRegions'; +import { CanvasModelUserStatus } from '../../../site/features/CanvasModelUserStatus'; +import { CanvasViewer, CanvasViewerProps } from '../../../site/features/CanvasViewer'; +import { + CanvasViewerButton, + CanvasViewerContentOverlay, + CanvasViewerControls, + CanvasViewerEditorStyleReset, + CanvasViewerGrid, + CanvasViewerGridContent, + CanvasViewerGridSidebar, +} from '../../../site/features/CanvasViewerGrid'; +import { CreateModelTestCase } from '../../../site/features/CreateModelTestCase'; +import { OpenSeadragonViewer } from '../../../site/features/OpenSeadragonViewer.lazy'; +import { TranscriberModeWorkflowBar } from '../../../site/features/TranscriberModeWorkflowBar'; +import { RouteContext } from '../../../site/hooks/use-route-context'; +import { ViewReadOnlyAnnotation } from '../../atlas/ViewReadOnlyAnnotation'; +import { InfoMessage } from '../../callouts/InfoMessage'; +import { SmallToast } from '../../callouts/SmallToast'; +import { useLocalStorage } from '../../hooks/use-local-storage'; +import { ReadOnlyAnnotation } from '../../hooks/use-read-only-annotations'; +import { HomeIcon } from '../../icons/HomeIcon'; +import { MinusIcon } from '../../icons/MinusIcon'; +import { PlusIcon } from '../../icons/PlusIcon'; +import { RotateIcon } from '../../icons/RotateIcon'; +import { TickIcon } from '../../icons/TickIcon'; +import { EmptyState } from '../../layout/EmptyState'; +import { Button, ButtonIcon } from '../../navigation/Button'; +import { BrowserComponent } from '../../utility/browser-component'; +import { CaptureModel } from '../types/capture-model'; +import { RevisionRequest } from '../types/revision-request'; +import { BackToChoicesButton } from './components/BackToChoicesButton'; +import { DirectEditButton } from './components/DirectEditButton'; +import { EditorRenderingConfig, EditorSlots } from './components/EditorSlots'; +import { RevisionProviderFeatures, RevisionProviderWithFeatures } from './components/RevisionProviderWithFeatures'; +import { SegmentationFieldInstance } from './components/SegmentationFieldInstance'; +import { SegmentationInlineEntity } from './components/SegmentationInlineEntity'; +import { SegmentationInlineProperties } from './components/SegmentationInlineProperties'; +import { SimpleSaveButton } from './components/SimpleSaveButton'; +import { SubmitWithoutPreview } from './components/SubmitWithoutPreview'; +import { DynamicVaultContext } from './DynamicVaultContext'; +import { EditorContentVariations, EditorContentViewer } from './EditorContent'; + +export interface CoreModelEditorProps { + // Data + revision?: string; + captureModel?: CaptureModel; + + // Options + isPreparing?: boolean; + allowMultiple?: boolean; + + forkMode?: boolean; + + isSegmentation?: boolean; + + isEditing?: boolean; + + isVertical?: boolean; + + disablePreview?: boolean; + + preventFurtherSubmission?: boolean; + + disableSaveForLater?: boolean; + + disableNextCanvas?: boolean; + + markedAsUnusable?: boolean; + + enableCanvasUserStatus?: boolean; + + mode?: 'annotation' | 'transcription'; + + annotationTheme?: AnnotationStyles['theme']; + + target: EditorContentVariations; + + readOnlyAnnotations?: ReadOnlyAnnotation[]; + + hideViewerControls?: boolean; + + enableRotation?: boolean; + + canContribute?: boolean; + + // Actions. + updateClaim: (ctx: { revisionRequest: RevisionRequest; context: RouteContext }) => void | Promise; + modelRefetch?: (args?: any) => Promise | Promise; + + enableHighlightedRegions?: boolean; + + components?: Partial; + + canvasViewerPins?: CanvasViewerProps['pins']; + + showBugReport?: boolean; + children?: React.ReactNode; +} +export function CoreModelEditor({ + revision, + captureModel, + annotationTheme, + disablePreview, + isEditing, + mode, + isSegmentation, + forkMode, + isPreparing, + allowMultiple, + disableNextCanvas, + disableSaveForLater, + preventFurtherSubmission, + isVertical, + target: targetProps, + readOnlyAnnotations, + hideViewerControls, + markedAsUnusable, + enableRotation, + canContribute, + modelRefetch, + updateClaim, + components: customComponents, + enableCanvasUserStatus, + enableHighlightedRegions, + canvasViewerPins, + showBugReport, + children, +}: CoreModelEditorProps) { + const { t } = useTranslation(); + const runtime = useRef(); + const osd = useRef(); + const gridRef = useRef(); + const [showPanWarning, setShowPanWarning] = useLocalStorage('pan-warning', false); + const [postSubmission, setPostSubmission] = useState(false); + const [postSubmissionMessage, setPostSubmissionMessage] = useState(false); + const [invalidateKey, invalidate] = useReducer(i => i + 1, 0); + const [isOSD, setIsOSD] = useState(false); + + const onPanInSketchMode = useCallback(() => { + setShowPanWarning(true); + setTimeout(() => { + setShowPanWarning(false); + }, 3000); + }, []); + + const goHome = () => { + if (runtime.current) { + runtime.current.world.goHome(); + } + if (osd.current) { + osd.current.goHome(); + } + }; + + const zoomIn = () => { + if (runtime.current) { + runtime.current.world.zoomIn(); + } + if (osd.current) { + osd.current.zoomIn(); + } + }; + + const zoomOut = () => { + if (runtime.current) { + runtime.current.world.zoomOut(); + } + if (osd.current) { + osd.current.zoomOut(); + } + }; + + const rotate = () => { + setIsOSD(true); + if (osd.current) { + osd.current.rotate(); + } + }; + + const features: RevisionProviderFeatures = isPreparing + ? { + autosave: false, + autoSelectingRevision: true, + revisionEditMode: false, + directEdit: true, + } + : { + preventMultiple: !allowMultiple, + forkMode: forkMode, + }; + + const _components: Partial = isPreparing + ? { + SubmitButton: DirectEditButton, + FieldInstance: isSegmentation ? SegmentationFieldInstance : undefined, + InlineEntity: isSegmentation ? SegmentationInlineEntity : undefined, + InlineProperties: isSegmentation ? SegmentationInlineProperties : undefined, + } + : isEditing || mode === 'transcription' + ? { + SubmitButton: SimpleSaveButton, + } + : disablePreview + ? { + SubmitButton: SubmitWithoutPreview, + } + : {}; + const components = { ..._components, ...(customComponents || {}) }; + + const profileConfig: { [key: string]: Partial } = { + [PARAGRAPHS_PROFILE]: slotConfig, + }; + + async function onAfterSave(ctx: { revisionRequest: RevisionRequest; context: RouteContext }) { + if (!isEditing && !isPreparing) { + await updateClaim(ctx); + } + + if (modelRefetch) { + await modelRefetch(); + } + + // If we have disabled preview, we need to show the post-submission. + if (disablePreview && ctx.revisionRequest.revision.status !== 'draft') { + if (disableNextCanvas) { + setPostSubmissionMessage(true); + } else { + setPostSubmission(true); + } + } + + invalidate(); + } + + return ( + + + {!isPreparing && mode === 'transcription' ? : null} + + + {enableHighlightedRegions ? : null} + + + {isOSD ? ( + <> + + {t('You cannot edit annotations if you are rotating')} + + + + + + + ) : ( + { + return ((runtime as any).current = rt.runtime); + }} + onPanInSketchMode={onPanInSketchMode} + {...targetProps} + > + {(readOnlyAnnotations || []).map(anno => ( + + ))} + + )} + + {hideViewerControls ? null : ( + + {showBugReport ? : null} + {enableRotation ? ( + + + + ) : null} + + + + + + + + + + + )} + + + {t('Hold space to pan and zoom')} + + + + + {postSubmissionMessage ? ( +
    + setPostSubmissionMessage(false)} /> +
    + ) : null} + {enableCanvasUserStatus ? : null} + {preventFurtherSubmission ? ( + <> + + + + + {t('Task is complete!')} + + + {markedAsUnusable ? ( + t('You have marked this as unusable') + ) : ( + <> + {t('Thank you for your submission.')} + {t('You can view your contribution in the left sidebar.')} + {t('You can continue working on another canvas.')} + + )} + + + ) : postSubmission ? ( +
    + setPostSubmission(false)} /> +
    + ) : canContribute && captureModel ? ( + <> + + + + + + + + + ) : ( + {t('Loading your model')} + )} +
    +
    + {children} +
    +
    +
    + ); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/new/DynamicVaultContext.tsx b/services/madoc-ts/src/frontend/shared/capture-models/new/DynamicVaultContext.tsx new file mode 100644 index 000000000..a4bb70046 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/new/DynamicVaultContext.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { CanvasContext, ManifestContext, useExternalManifest } from 'react-iiif-vault'; +import { ViewContentFetch } from '../../../admin/molecules/ViewContentFetch'; +import { CanvasVaultContext } from '../../components/CanvasVaultContext'; +import { ContentExplorer } from '../../components/ContentExplorer'; +import { TinyButton } from '../../navigation/Button'; +import { BrowserComponent } from '../../utility/browser-component'; +import { EditorContentVariations } from './EditorContent'; + +export const DynamicVaultContext = React.memo(function DynamicVaultContext({ + children, + onCreated, + onPanInSketchMode, + height, + canvasId, + canvas, + canvasUri, + manifestUri, + target, + explorerReset, + explorer, +}: EditorContentVariations & { children: React.ReactNode }) { + const { t } = useTranslation(); + + if (explorer) { + return ( + ( + Loading}> + <> + + {explorerReset ? ( + <> +
    + {t('Select different image')} + + ) : null} + +
    + )} + /> + ); + } + + if (canvas && target) { + return <>{children}; + } + + if (canvasUri && manifestUri) { + return ( + + {children} + + ); + } + + if (canvasId) { + return {children}; + } + + if (target) { + return {children}; + } + + return null; +}); + +const ExternalManifestCtx = React.memo(function ExternalManifestCtx({ + manifest, + canvas, + children, +}: { + manifest: string; + canvas: string; + children: React.ReactNode; +}) { + const { isLoaded } = useExternalManifest(manifest); + + if (!isLoaded) { + return null; + } + + return ( + + {children} + + ); +}); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/utility/is-entity-empty.ts b/services/madoc-ts/src/frontend/shared/capture-models/utility/is-entity-empty.ts new file mode 100644 index 000000000..2b291ea60 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/utility/is-entity-empty.ts @@ -0,0 +1,28 @@ +import { isEntityList } from '../helpers/is-entity'; +import { traverseDocument } from '../helpers/traverse-document'; +import { CaptureModel } from '../types/capture-model'; +import { isEmptyFieldList } from './is-field-list-empty'; + +export function isEntityEmpty(found: CaptureModel['document']) { + let isEmpty = true; + traverseDocument(found, { + visitProperty(prop, list) { + if (isEntityList(list)) { + isEmpty = false; + for (const entity of list) { + if (!isEntityEmpty(entity)) { + isEmpty = false; + return; + } + } + return; + } + + if (!isEmptyFieldList(list)) { + isEmpty = false; + } + }, + }); + + return isEmpty; +} diff --git a/services/madoc-ts/src/frontend/shared/components/FilePreview.tsx b/services/madoc-ts/src/frontend/shared/components/FilePreview.tsx new file mode 100644 index 000000000..354b5a219 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/components/FilePreview.tsx @@ -0,0 +1,170 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { useQuery } from 'react-query'; +import styled from 'styled-components'; +import { createDownload } from '../../../utility/create-download'; +import { ArrowDownIcon } from '../icons/ArrowDownIcon'; +import { EmptyState } from '../layout/EmptyState'; + +type SupportedFile = { type: string; value: string }; + +interface FilePreviewProps { + fileName: string; + showLines?: boolean; + download?: boolean; + preFetch?: boolean; + contentType?: string; + children?: SupportedFile; + lazyLoad?: () => Promise | undefined | SupportedFile | null; +} + +const Container = styled.div` + background: #fff; + border: 1px solid #eee; + border-radius: 5px; + overflow: auto; + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + font-size: 0.875em; + white-space: pre; + max-height: 600px; + + & ~ & { + margin-top: 10px; + } +`; + +const Header = styled.div` + background: #f7f8fa; + display: flex; + align-items: center; + position: sticky; + top: 0; + line-height: 1.9em; + z-index: 3; +`; + +const FileName = styled.div` + color: #000; + padding: 0.5em 1em; + margin-right: auto; +`; + +const DownloadButton = styled.button` + margin: 10px; +`; + +const Line = styled.span` + background: #fff; + display: flex; + line-height: 1.3em; + text-wrap: avoid; + max-width: 100%; + position: relative; + white-space: pre-wrap; + &:before { + color: #999; + text-align: right; + margin-right: 0.5em; + counter-increment: lines; + content: counter(lines); + width: 2em; + min-width: 2em; + user-select: none; + display: block; + z-index: 2; + place-self: flex-start; + background: #fff; + } + &:after { + content: ''; + background: #eee; + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 2em; + z-index: 1; + } +`; + +const Body = styled.div` + counter-reset: lines; + min-width: 0; +`; + +const Icon = styled.div` + margin-left: 0.5em; + padding: 0 0.2em; + font-size: 1.2em; + transform: translateY(2px); + + & ~ ${FileName}:hover { + text-decoration: underline; + cursor: pointer; + } +`; + +export function FilePreview(props: FilePreviewProps) { + const container = useRef(null); + const [enabled, setEnabled] = useState(false); + const isLazy = !!props.lazyLoad; + const { data, isFetched } = useQuery( + ['lazy-loaded', { filename: props.fileName }], + async () => { + if (props.lazyLoad) { + return props.lazyLoad(); + } + }, + { enabled: (enabled || props.preFetch) && !!props.lazyLoad } + ); + + const lines = useMemo(() => { + if (isLazy) { + return data ? data.value.split('\n') : []; + } + return props.children ? props.children.value.split('\n') : []; + }, [isLazy, data, props.children]); + + function toggle() { + setEnabled(e => !e); + } + + return ( + +
    + {props.lazyLoad ? ( + enabled ? ( + + + + ) : ( + + + + ) + ) : null} + {props.fileName} + {(!isLazy || (data && (enabled || props.preFetch))) && props.download !== false ? ( + + createDownload( + data || props.children, + props.fileName.split('/').pop() || 'unknown.json', + props.contentType as any + ) + } + > + Download + + ) : null} +
    + {!isLazy || (data && enabled) ? ( + + {lines.map((line, k) => ( + {line} + ))} + + ) : null} + {props.lazyLoad && isFetched && !data && enabled ? No file found : null} +
    + ); +} diff --git a/services/madoc-ts/src/frontend/shared/components/RichSelectionGrid.tsx b/services/madoc-ts/src/frontend/shared/components/RichSelectionGrid.tsx new file mode 100644 index 000000000..bebed4db9 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/components/RichSelectionGrid.tsx @@ -0,0 +1,92 @@ +import { InternationalString } from '@iiif/presentation-3'; +import React from 'react'; +import styled from 'styled-components'; +import { LocaleString } from './LocaleString'; + +export interface RichSelectionGridProps { + items: Array<{ + id: string; + label: InternationalString; + description?: InternationalString; + }>; + selected: string[]; + onSelect: (id: string) => void; +} + +const Container = styled.div` + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + justify-content: space-between; + background-color: inherit; + grid-gap: 0.875em; + width: 100%; + flex-wrap: wrap; + margin-bottom: 1em; +`; + +const Title = styled.div` + font-size: 1.2em; + text-align: center; + margin-bottom: 1em; +`; + +const Description = styled.div` + color: #999; + font-size: 0.875em; + text-align: center; +`; + +const Item = styled.div` + background: #fff; + padding: 1.8em; + border-radius: 5px; + border: 2px solid #eee; + cursor: pointer; + + &:hover { + border-color: #ccc; + } + + &[data-selected='true'] { + background: #f5f5ff; + border-color: #454aff; + + &:hover { + border-color: #6c84fa; + } + + ${Description} { + color: #6f78b9; + } + } +`; + +const EmptyItem = styled.div` + background: transparent; +`; + +export function RichSelectionGrid(props: RichSelectionGridProps) { + return ( + + {props.items.map(item => ( + props.onSelect(item.id)} + > + {item.label} + {item.description || { none: [''] }} + + ))} + {props.items.length < 5 ? ( + <> + + + + + + + ) : null} + + ); +} diff --git a/services/madoc-ts/src/frontend/shared/components/RootStatistics.tsx b/services/madoc-ts/src/frontend/shared/components/RootStatistics.tsx new file mode 100644 index 000000000..19b2c7a10 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/components/RootStatistics.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import styled from 'styled-components'; + +const Container = styled.div` + height: 1.4em; + display: flex; + flex-direction: row; + background: #eee; + margin: 10px 0; + border-radius: 3px; + overflow: hidden; +`; + +const Bar = styled.div` + height: 30px; + transition: width 0.5s; + background: #eee; +`; +export function RootStatistics( + props: Partial<{ + error: number; + not_started: number; + accepted: number; + progress: number; + done: number; + }> +) { + const totals = Object.entries(props || {}).reduce((acc, [, value]) => acc + (value || 0), 0); + return ( + + {props?.error ? ( + + ) : null} + {props?.done ? ( + + ) : null} + {props?.accepted ? ( + + ) : null} + {props?.progress ? ( + + ) : null} + {props?.not_started ? ( + + ) : null} + + ); +} diff --git a/services/madoc-ts/src/frontend/shared/components/TaskTabs.tsx b/services/madoc-ts/src/frontend/shared/components/TaskTabs.tsx new file mode 100644 index 000000000..225a8aaef --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/components/TaskTabs.tsx @@ -0,0 +1,49 @@ +import styled, { css } from 'styled-components'; + +export const TaskTabBackground = styled.div<{ $sticky?: boolean }>` + background: #5677f3; + margin-bottom: 1em; + padding-top: 0.4em; + padding-left: 0.2em; + padding-right: 0.2em; + ${props => + props.$sticky && + css` + position: sticky; + top: -0.25px; + z-index: 9; + `} +`; + +export const TaskTabRow = styled.ul` + list-style: none; + display: flex; + margin: 0; + padding: 0; + overflow-y: auto; +`; + +export const TaskTabItem = styled.li<{ $active?: boolean }>` + padding: 0.75em 2em; + margin: 0 0.2em; + font-size: 0.75em; + color: #fff; + background: #3c5cd2; + text-decoration: none; + white-space: nowrap; + cursor: pointer; + &:hover { + //background: rgba(255, 255, 255, 0.2); + background: #dcebfe; + color: #000; + } + ${props => + props.$active && + css` + background: #fff; + color: #000; + &:hover { + background: #fff; + } + `} +`; diff --git a/services/madoc-ts/src/frontend/shared/hooks/use-decay-state.ts b/services/madoc-ts/src/frontend/shared/hooks/use-decay-state.ts new file mode 100644 index 000000000..2bcd3b6f9 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/hooks/use-decay-state.ts @@ -0,0 +1,18 @@ +import { useCallback, useRef, useState } from 'react'; + +export function useDecayState(duration = 1000) { + const [isOn, setIsOn] = useState(false); + const timeout = useRef(); + const trigger = useCallback(() => { + if (timeout.current) { + clearTimeout(timeout.current); + } + setIsOn(true); + timeout.current = setTimeout(() => { + setIsOn(false); + timeout.current = null; + }, duration); + }, [duration]); + + return [isOn, trigger] as const; +} diff --git a/services/madoc-ts/src/frontend/shared/hooks/use-export-resource-preview.ts b/services/madoc-ts/src/frontend/shared/hooks/use-export-resource-preview.ts new file mode 100644 index 000000000..8f8deb801 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/hooks/use-export-resource-preview.ts @@ -0,0 +1,49 @@ +import { useMemo } from 'react'; +import { useQuery } from 'react-query'; +import { SupportedExportResource } from '../../../extensions/project-export/types'; +import { filePatternsToList } from '../../../extensions/project-export/utils/file-patterns-to-list'; +import { useApi } from './use-api'; +import { useProjectExports } from './use-project-exports'; + +export function useExportResourcePreview( + selectedType: string | null, + options: { + subject: SupportedExportResource; + subjectParent?: SupportedExportResource; + context?: SupportedExportResource; + config?: any; + loadFiles?: boolean; + } +) { + const api = useApi(); + const items = useProjectExports(options.subject.type); + const selected = selectedType ? items.find(r => r.type === selectedType) : null; + const expectedFiles = useMemo(() => { + if (selected) { + return filePatternsToList(selected, { ...options }); + } + + return []; + }, [options, selected]); + + const query = useQuery( + ['export-item', { type: selectedType, ...options }], + async () => { + if (selected) { + try { + return await selected.exportData(options.subject, { + subjectParent: options.subjectParent, + api, + config: options.config, + context: options.subjectParent || options.context, + }); + } catch (e) { + console.log(e); + } + } + }, + { enabled: options.loadFiles !== false } + ); + + return [query, selected, expectedFiles] as const; +} diff --git a/services/madoc-ts/src/frontend/shared/hooks/use-export-resources.ts b/services/madoc-ts/src/frontend/shared/hooks/use-export-resources.ts new file mode 100644 index 000000000..75f1bfeaa --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/hooks/use-export-resources.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react'; +import { SupportedExportResource, SupportedExportResourceTypes } from '../../../extensions/project-export/types'; +import { filePatternsToList } from '../../../extensions/project-export/utils/file-patterns-to-list'; +import { useProjectExports } from './use-project-exports'; + +export function useExportResources( + selectedType: string | null, + options: { + subjectsType: SupportedExportResourceTypes; + subjects: SupportedExportResource[]; + subjectParent?: SupportedExportResource; + context?: SupportedExportResource; + config?: any; + allProjects?: number[]; + } +) { + const items = useProjectExports(options.subjectsType); + const selected = selectedType ? items.find(r => r.type === selectedType) : null; + + const expectedFiles = useMemo(() => { + const files = []; + + if (selected && selected.metadata.filePatterns) { + for (const subject of options.subjects) { + files.push(...filePatternsToList(selected, { subject, ...options })); + } + } + + return files; + }, [options, selected]); + + return [expectedFiles, selected] as const; +} diff --git a/services/madoc-ts/src/frontend/shared/hooks/use-project-exports.ts b/services/madoc-ts/src/frontend/shared/hooks/use-project-exports.ts new file mode 100644 index 000000000..349bbf17f --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/hooks/use-project-exports.ts @@ -0,0 +1,17 @@ +import { useMemo } from 'react'; +import { SupportedExportResourceTypes } from '../../../extensions/project-export/types'; +import { useOptionalApi } from './use-api'; +import { useSite } from './use-site'; + +export function useProjectExports(type?: SupportedExportResourceTypes) { + const api = useOptionalApi(); + const site = useSite(); + const all = useMemo(() => (api ? api.projectExport.getAllDefinitions(site.id) : []), [api, site]); + return useMemo(() => { + if (type) { + return all.filter(item => item.supportedTypes.indexOf(type) !== -1); + } + + return all; + }, [all, type]); +} diff --git a/services/madoc-ts/src/frontend/shared/icons/BugIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/BugIcon.tsx new file mode 100644 index 000000000..65d183565 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/BugIcon.tsx @@ -0,0 +1,12 @@ +import React, { SVGProps } from 'react'; + +export function BugIcon(props: SVGProps) { + return ( + + + + ); +} diff --git a/services/madoc-ts/src/frontend/shared/icons/CheckCircleIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/CheckCircleIcon.tsx new file mode 100644 index 000000000..dcae5ede1 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/CheckCircleIcon.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const CheckCircleIcon = (props: SVGProps) => ( + + + + +); + +export default CheckCircleIcon; diff --git a/services/madoc-ts/src/frontend/shared/icons/Chevron.tsx b/services/madoc-ts/src/frontend/shared/icons/Chevron.tsx new file mode 100644 index 000000000..d2355391d --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/Chevron.tsx @@ -0,0 +1,9 @@ +import { SVGProps } from 'react'; +import * as React from 'react'; + +export const Chevron = (props: SVGProps) => ( + + + + +); diff --git a/services/madoc-ts/src/frontend/shared/icons/ListItemIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/ListItemIcon.tsx new file mode 100644 index 000000000..13ce9a4a1 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/ListItemIcon.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const ListItemIcon = (props: SVGProps) => ( + + + +); + +export default ListItemIcon; diff --git a/services/madoc-ts/src/frontend/shared/icons/NoEntryIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/NoEntryIcon.tsx new file mode 100644 index 000000000..ba2827044 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/NoEntryIcon.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const NoEntryIcon = (props: SVGProps) => ( + + + + +); + +export default NoEntryIcon; diff --git a/services/madoc-ts/src/frontend/shared/icons/RequestChangesIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/RequestChangesIcon.tsx new file mode 100644 index 000000000..1664dd424 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/RequestChangesIcon.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; + +export function RequestChangesIcon(props: React.SVGProps) { + return ( + + + + + ); +} diff --git a/services/madoc-ts/src/frontend/shared/icons/ResizeHandleIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/ResizeHandleIcon.tsx new file mode 100644 index 000000000..c9317b227 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/ResizeHandleIcon.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const ResizeHandleIcon = (props: SVGProps) => ( + + + + +); + +export default ResizeHandleIcon; diff --git a/services/madoc-ts/src/frontend/shared/icons/UnlockSmileyIcon.tsx b/services/madoc-ts/src/frontend/shared/icons/UnlockSmileyIcon.tsx new file mode 100644 index 000000000..46acceb6f --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/icons/UnlockSmileyIcon.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { SVGProps } from 'react'; + +const UnlockSmileyIcon = (props: SVGProps) => ( + + + + +); + +export default UnlockSmileyIcon; diff --git a/services/madoc-ts/src/frontend/site/features/CreateModelTestCase.tsx b/services/madoc-ts/src/frontend/site/features/CreateModelTestCase.tsx new file mode 100644 index 000000000..b58c1d400 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/CreateModelTestCase.tsx @@ -0,0 +1,234 @@ +import { getValue } from '@iiif/vault-helpers'; +import { stringify } from 'query-string'; +import React, { useState } from 'react'; +import useDropdownMenu from 'react-accessible-dropdown-menu-hook'; +import { useCanvas } from 'react-iiif-vault'; +import { useParams } from 'react-router-dom'; +import { Revisions } from '../../shared/capture-models/editor/stores/revisions/index'; +import { CaptureModel } from '../../shared/capture-models/types/capture-model'; +import { FilePreview } from '../../shared/components/FilePreview'; +import { ItemFilterContainer, ItemFilterPopupContainer } from '../../shared/components/ItemFilter'; +import { useDecayState } from '../../shared/hooks/use-decay-state'; +import { useLoadedCaptureModel } from '../../shared/hooks/use-loaded-capture-model'; +import { BugIcon } from '../../shared/icons/BugIcon'; +import { Button, ButtonIcon, ButtonRow } from '../../shared/navigation/Button'; +import { HrefLink } from '../../shared/utility/href-link'; +import { useCanvasModel } from '../hooks/use-canvas-model'; +import { useManifest } from '../hooks/use-manifest'; +import { useRouteContext } from '../hooks/use-route-context'; +import { CanvasViewerButton } from './CanvasViewerGrid'; + +function pascal(str: string) { + // @ts-ignore + return str.replaceAll(/[^a-zA-Z\d\s:]/g, '').replace(/^\w|[A-Z]|\b\w|\s+/g, function(match, index) { + if (+match === 0) return ''; // or if (/\s+/.test(match)) for white spaces + return match.toUpperCase(); + }); +} + +function createCodeTemplate( + name: string, + model: CaptureModel, + { + target, + revision, + }: { + target?: { manifestUri: string; canvasUri: string }; + revision?: string | null; + } = {} +) { + const _name = pascal(name); + return `import * as React from 'react'; +import { CaptureModelTestHarness } from './CaptureModelTestHarness'; +export default { title: 'Capture model interactions / ${name as any}', component: CaptureModelTestHarness }; + +const fixture: any = ${JSON.stringify(model)}; + +export const ${_name} = CaptureModelTestHarness.story({ + captureModel: fixture, + ${ + target + ? `target: { + manifestUri: '${target.manifestUri}', + canvasUri: '${target.canvasUri}', + }, +` + : '' + }${ + revision + ? `revision: '${revision}', +` + : '' + }}); + `; +} + +export function createGithubIssue( + name: string, + model: CaptureModel, + { + target, + revision, + }: { + target?: { manifestUri: string; canvasUri: string }; + revision?: string | null; + } = {} +) { + const opts = { + label: ['bug'], + title: ``, + body: `# Capture model bug - ${name} + +* **environment**: ${location.hostname} +* **URL**: [View on Site](${location.href}) ${ + target + ? ` +* **Manifest**: ${target.manifestUri} +* **Canvas**: ${target.canvasUri} +` + : '' + } + + + `, + }; + return `https://github.com/digirati-co-uk/madoc-platform/issues/new?${stringify(opts, { arrayFormat: 'bracket' })}`; +} + +export function createStorybookUrl( + name: string, + model: CaptureModel, + { + target, + revision, + }: { + target?: { manifestUri: string; canvasUri: string }; + revision?: string | null; + } = {} +) { + const hostname = + location.hostname === 'madoc.local' + ? 'http://localhost:6500' + : 'https://deploy-preview-591--madoc-storybook.netlify.app/'; // @todo change when v2.1 is released. + + return `${hostname}/?${stringify({ + path: '/story/capture-model-interactions-preview--preview-from-url', + captureModel: JSON.stringify(model), + target: JSON.stringify(target), + revision, + })}`; +} + +export function CreateModelTestCase(props: { captureModel?: CaptureModel }) { + const { buttonProps, isOpen } = useDropdownMenu(1, { + disableFocusFirstItemOnClick: true, + }); + const rc = useRouteContext(); + const revision = Revisions.useStoreState(s => s.currentRevisionId); + const { data: manifest } = useManifest(); + const [successCopy, setSuccessfulCopy] = useDecayState(2000); + // const { data: model } = useCanvasModel(); + const canvas = useCanvas(); + + const options = { + revision, + target: undefined as { manifestUri: string; canvasUri: string } | undefined, + }; + const manifestUri = manifest?.manifest.source; + const canvasUri = (canvas as any)?.source_id; + if (manifestUri && canvasUri) { + options.target = { + manifestUri, + canvasUri, + }; + } + + if (!props.captureModel || !canvas || !manifest) { + return null; + } + + const name = [ + (props.captureModel.document.label as any).replaceAll(/[\/']+/g, ''), + (getValue(manifest.manifest.label) as any).replaceAll(/[\/']+/g, '') + + ' - ' + + (getValue(canvas.label) as any).replaceAll(/[\/']+/g, ''), + ].join(' / '); + + return ( + + + + + + + + + + +
    +
      +
    • + name: {name} +
    • +
    • + model: {props.captureModel?.id} +
    • + {revision ? ( +
    • + revision: {revision} +
    • + ) : null} + {options.target ? ( + <> +
    • + manifest:{' '} + + {options.target.manifestUri} + +
    • +
    • + canvas: {options.target.canvasUri} +
    • + + ) : null} +
    +
    +
    +

    Download Fixture

    + ({ + type: 'text', + value: JSON.stringify( + { + url: location.href, + options, + captureModel: props.captureModel, + }, + null, + 2 + ), + })} + /> +
    +
    +
    + ); +} diff --git a/services/madoc-ts/src/frontend/site/features/EmbedItem.tsx b/services/madoc-ts/src/frontend/site/features/EmbedItem.tsx new file mode 100644 index 000000000..851f30266 --- /dev/null +++ b/services/madoc-ts/src/frontend/site/features/EmbedItem.tsx @@ -0,0 +1,37 @@ +import { blockEditorFor } from '../../../extensions/page-blocks/block-editor-for'; +import React from 'react'; +import styled from 'styled-components'; + +const EmbedWrapper = styled.div` + iframe { + border: none; + } +`; +export const EmbedItem: React.FC<{ + link?: string; + height?: string; + width?: string; +}> = ({ link, height, width }) => { + return ( + +