From 9890e2352ec867a108a5988bb66985ed19348227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarje=20S=C3=A6len=20Lavik?= <tarje.lavik@gmail.com> Date: Tue, 27 Apr 2021 14:58:29 +0200 Subject: [PATCH] Add KulturNav import of Concepts --- .../src/kulturnav/SearchKulturnav.js | 46 ++++----- .../import-tool/src/kulturnav/apis/index.js | 87 +++++++---------- .../components/{Preview.js => Card.js} | 93 ++++++++++++------- .../import-tool/src/marcus/apis/index.js | 2 - studio/plugins/import-tool/src/nb/SearchNB.js | 2 +- .../import-tool/src/nb/components/Card.js | 2 +- 6 files changed, 121 insertions(+), 111 deletions(-) rename studio/plugins/import-tool/src/kulturnav/components/{Preview.js => Card.js} (75%) diff --git a/studio/plugins/import-tool/src/kulturnav/SearchKulturnav.js b/studio/plugins/import-tool/src/kulturnav/SearchKulturnav.js index fb96ede..16b6163 100644 --- a/studio/plugins/import-tool/src/kulturnav/SearchKulturnav.js +++ b/studio/plugins/import-tool/src/kulturnav/SearchKulturnav.js @@ -1,33 +1,36 @@ /* eslint-disable no-undef */ import React, {useReducer, useEffect} from 'react' -import ReactPaginate from 'react-paginate' +//import ReactPaginate from 'react-paginate' // import fetch from 'unfetch' -import Preview from './components/Preview' +import Card from './components/Card' import Search from './components/Search' -import styles from '../ImportTool.css' +// import styles from '../ImportTool.css' import {searchReducer} from './reducers/searchReducer' import {chooseItem} from './apis' import {Box, Container, Grid, Flex, Text} from '@sanity/ui' const IMPORT_API_URL = 'https://kulturnav.org/api/search/' +const GET_TYPES = 'Concept' export const initialState = { - sourceAPI: 'nb', - apiURL: 'https://kulturnav.org/api/search/', + sourceAPI: 'kn', + apiURL: IMPORT_API_URL, loading: true, - searchParameter: '', + searchParameter: '*', items: [], page: 0, totalElements: 0, - limit: 30, + max: 64, errorMessage: null, } const SearchNB = () => { const [state, dispatch] = useReducer(searchReducer, initialState) - /* useEffect(() => { - fetch(state.apiURL + new URLSearchParams({})) + useEffect(() => { + fetch( + `${state.apiURL}actualEntityType:${GET_TYPES},compoundName:${state.searchParameter}/${state.page}/${state.max}` + ) .then((response) => response.json()) .then((jsonResponse) => { dispatch({ @@ -36,9 +39,9 @@ const SearchNB = () => { totalElements: jsonResponse.length, }) }) - }, []) */ + }, []) - const handlePageClick = (data) => { + /* const handlePageClick = (data) => { let selected = data.selected let page = selected @@ -48,7 +51,7 @@ const SearchNB = () => { }) fetch( - state.apiURL + 'actualEntityType:Person%20OR%20Concept,compoundName:' + state.searchParameter + state.apiURL + 'actualEntityType:${GET_TYPES},compoundName:' + state.searchParameter ? state.searchParameter : '' + new URLSearchParams({}), ) @@ -68,7 +71,7 @@ const SearchNB = () => { }) } }) - } + } */ const search = (searchValue) => { // setSearchParameter(searchValue) @@ -79,10 +82,7 @@ const SearchNB = () => { }) fetch( - IMPORT_API_URL + - 'actualEntityType:Person%20OR%20Concept,compoundName:' + - searchValue + - new URLSearchParams({}), + `${state.apiURL}actualEntityType:${GET_TYPES},compoundName:${searchValue}/0/${state.max}` ) .then((response) => response.json()) .then((jsonResponse) => { @@ -102,8 +102,8 @@ const SearchNB = () => { }) } - const {searchParameter, items, totalElements, page, limit, errorMessage, loading} = state - console.log(items) + const {searchParameter, items, totalElements, page, max, errorMessage, loading} = state + return ( <Container width={5} paddingY={5}> <form> @@ -115,13 +115,13 @@ const SearchNB = () => { <Text flex={1} size={1}>{totalElements} result found</Text> </Box> - <Box marginBottom={3}> + {/* <Box marginBottom={3}> <ReactPaginate previousLabel={'previous'} nextLabel={'next'} breakLabel={'...'} forcePage={page} - pageCount={totalElements / limit} + pageCount={totalElements / max} marginPagesDisplayed={2} pageRangeDisplayed={3} containerClassName={styles.pagination} @@ -132,7 +132,7 @@ const SearchNB = () => { activeClassName={styles.active} onPageChange={handlePageClick} /> - </Box> + </Box> */} <Grid columns={[3, 4, 4, 4]} gap={[1, 1, 2, 3]}> {loading && !errorMessage ? ( <span>loading... </span> @@ -140,7 +140,7 @@ const SearchNB = () => { <div className="errorMessage">{errorMessage}</div> ) : ( items.map((item) => ( - <Preview key={item.id} item={item} searchValue={searchParameter} onClick={chooseItem} /> + <Card key={item.uuid} item={item} searchValue={searchParameter} onClick={chooseItem} /> )) )} </Grid> diff --git a/studio/plugins/import-tool/src/kulturnav/apis/index.js b/studio/plugins/import-tool/src/kulturnav/apis/index.js index c8551b5..4993389 100644 --- a/studio/plugins/import-tool/src/kulturnav/apis/index.js +++ b/studio/plugins/import-tool/src/kulturnav/apis/index.js @@ -1,58 +1,36 @@ import {nanoid} from 'nanoid' -import {mapMediatypes} from './mapMediatypes' import sanityClient from 'part:@sanity/base/client' const client = sanityClient.withConfig({apiVersion: '2021-03-25'}) export const chooseItem = async (item) => { - // Get a 200x200px thumbnail. Maybe change to a bigger size based on thumbnail_custom. - const imageUrl = item._links.thumbnail_custom.href - - function customImageSize(image, h, w) { - if (!image) { - console.error('No image input') - throw Error - } - const height = '600' || h - const width = '600' || w - const template = image.replace('{height}', height).replace('{width}', width) - return template - } - - const types = mapMediatypes(item.metadata.mediaTypes) - const doc = { - _type: 'HumanMadeObject', - _id: `${item.id}`, + _type: 'Concept', + _id: `${item.uuid}`, accessState: 'open', editorialState: 'published', - license: - item.accessInfo && item.accessInfo.isPublicDomain - ? 'https://creativecommons.org/publicdomain/mark/1.0/' - : 'https://rightsstatements.org/vocab/CNE/1.0/', - label: item.metadata.title, - preferredIdentifier: item.id, + label: { + ...(item.caption.no ? { + nor: item.caption.no + } : null), + ...(item.caption.sv ? { + swe: item.caption.sv + } : null) + }, + /* preferredIdentifier: item.uuid, identifiedBy: [ { _type: 'Identifier', _key: nanoid(), - content: item.id, + content: item.uuid, hasType: { _type: 'reference', _key: nanoid(), _ref: 'de22df48-e3e7-47f2-9d29-cae1b5e4d728', }, }, - ], - hasCurrentOwner: [ - { - _type: 'reference', - _key: nanoid(), - _ref: '37f7376a-c635-420b-8ec6-ec0fd4c4a55c', - }, - ], - subjectOfManifest: item._links.presentation.href, - hasType: types, + ], */ + /* hasType: types, */ wasOutputOf: { _type: 'DataTransferEvent', _key: nanoid(), @@ -68,14 +46,14 @@ export const chooseItem = async (item) => { _type: 'DigitalDevice', _key: nanoid(), /* _ref: nanoid(36), */ - label: 'api.nb.no', + label: 'kulturnav.no/api', }, }, } /* TODO Important to include iiif manifest in asset metadata as the asset could be reused else where in the dataset */ - const assetMeta = { + /* const assetMeta = { source: { // The source this image is from name: 'nb.no', @@ -86,7 +64,7 @@ export const chooseItem = async (item) => { }, description: item.metadata.title, creditLine: 'From nb.no', - } + } */ const getImageBlob = async (url) => { // eslint-disable-next-line no-undef @@ -151,11 +129,20 @@ export const chooseItem = async (item) => { } const createDoc = async (doc) => { - const res = client.createIfNotExists(doc).then((result) => { - console.log(`${result._id} was imported!`) - return result - }) - return res + const transaction = client.transaction() + + transaction.createOrReplace(doc) + + transaction + .commit() + .then((res) => { + console.log(JSON.stringify(res, null, 2)) + return res + }) + .catch((err) => { + console.log('Transaction failed', err) + return err + }) } const setAssetRef = async (docID, assetID) => { @@ -180,23 +167,21 @@ export const chooseItem = async (item) => { } try { - const imageResonse = await getImageBlob(customImageSize(imageUrl)) + /* const imageResonse = await getImageBlob(customImageSize(imageUrl)) const asset = await uploadImageBlob(imageResonse) - await patchAssetMeta(asset._id, assetMeta) + await patchAssetMeta(asset._id, assetMeta) */ - const document = await createDoc(doc) - if (asset && document) { + await createDoc(doc) + /* if (asset && document) { await setAssetRef(document._id, asset._id) - } + } */ return { success: true, - body: JSON.stringify(document, asset), } } catch (err) { return { success: false, - body: JSON.stringify(response.status, response.statusText), } } } diff --git a/studio/plugins/import-tool/src/kulturnav/components/Preview.js b/studio/plugins/import-tool/src/kulturnav/components/Card.js similarity index 75% rename from studio/plugins/import-tool/src/kulturnav/components/Preview.js rename to studio/plugins/import-tool/src/kulturnav/components/Card.js index 1dffe17..df0bb68 100644 --- a/studio/plugins/import-tool/src/kulturnav/components/Preview.js +++ b/studio/plugins/import-tool/src/kulturnav/components/Card.js @@ -1,11 +1,18 @@ import React, {useState} from 'react' -import Button from 'part:@sanity/components/buttons/default' -import Card from 'part:@sanity/components/previews/card' -import DefaultBadge from 'part:@sanity/components/badges/default' -import DateBadge from '../../components/DateBadge' -import styled, {keyframes} from 'styled-components' +import { + Box, + Card as SanityCard, + Heading, + Text, + Badge, + Button, + Inline, + Stack +} from '@sanity/ui' +import {RiDownloadLine} from 'react-icons/ri' +import {chooseItem} from '../apis' -const Preview = ({item, searchValue, onClick}) => { +const Card = ({item}) => { const [isFetching, setIsFetching] = useState(false) const [isImported, setIsImported] = useState(false) const [buttonLabel, setButtonLabel] = useState('Import') @@ -13,12 +20,11 @@ const Preview = ({item, searchValue, onClick}) => { const onChooseItem = async (item) => { setIsFetching(true) setButtonLabel('...importing') - const importStatus = await onClick(item) + const importStatus = await chooseItem(item) if (!importStatus.success) { setIsFetching(false) setButtonLabel('Import failed!') - console.log(importStatus.body) return } @@ -28,10 +34,52 @@ const Preview = ({item, searchValue, onClick}) => { } return ( + <SanityCard style={{display: "flex", flexDirection: "column"}}Â key={item._uuid} padding={[2, 2, 3]} radius={2} shadow={1}> + {/* NOT EASILY AVAILABLE + <Box> + <img style={{width: "100%"}} src={item.hasThumbnail} /> + </Box> */} + <Box style={{flexGrow: "1"}} marginY={3}> + <Heading size="1"> + {item.caption.no || item.caption.sv ||Â 'Manglende caption?'} + </Heading> + <Stack paddingY={2} space={3}> + <Inline space={2}> + <Badge tone="primary">{item.entityType}</Badge> + </Inline> + {item.description && ( + <Text muted size={[1, 1, 2]}> + {item.description} + </Text> + )} + </Stack> + </Box> + <Stack style={{marginTop: "auto"}} style={{borderTop: "1px dotted gray"}} paddingTop={2} space={3}> + <Inline space={2}> + <Button + fontSize={[1, 1, 2]} + padding={[1, 1, 2]} + icon={RiDownloadLine} + text={buttonLabel} + mode={isImported ? 'ghost' : 'default'} + disabled={isFetching} + onClick={() => onChooseItem(item)} + /> + <a href={`https://kulturnav.org/${item.uuid}`} target="_blank" rel="noopener noreferrer">Ã…pne i Kulturnav</a> + </Inline> + </Stack> + </SanityCard> + ) +} + +export default Card + + +/* return ( <Container key={item.uuid}> <Card title={item.caption.no} - /* media={() => ( + media={() => ( <img src={ item && item._links && item._links.thumbnail_large @@ -39,7 +87,7 @@ const Preview = ({item, searchValue, onClick}) => { : 'data:image/png;base64,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' } /> - )} */ + )} > <p> {item.mediaTypes && @@ -54,26 +102,5 @@ const Preview = ({item, searchValue, onClick}) => { View at Kulturnav </a> </Card> - </Container> - ) -} - -const FadeIn = keyframes` - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -` - -const Container = styled.div` - display: flex; - flex-flow: column; - width: 300px; - padding: 6px 0; - margin: 6px; - animation: ${FadeIn} 1.2s cubic-bezier(0.39, 0.575, 0.565, 1) both; -` - -export default Preview + </Container> */ + \ No newline at end of file diff --git a/studio/plugins/import-tool/src/marcus/apis/index.js b/studio/plugins/import-tool/src/marcus/apis/index.js index 304ca04..d246abc 100644 --- a/studio/plugins/import-tool/src/marcus/apis/index.js +++ b/studio/plugins/import-tool/src/marcus/apis/index.js @@ -54,8 +54,6 @@ export const chooseItem = async (uri) => { // Map type to Sanity types const types = mapMediatypes([cleanJSON.type]) - /* TODO - Include iiif manifest in asset metadata as the asset could be reused elsewhere in the dataset */ const assetMeta = { source: { // The source this image is from diff --git a/studio/plugins/import-tool/src/nb/SearchNB.js b/studio/plugins/import-tool/src/nb/SearchNB.js index 0b0066e..ef26f5d 100644 --- a/studio/plugins/import-tool/src/nb/SearchNB.js +++ b/studio/plugins/import-tool/src/nb/SearchNB.js @@ -157,7 +157,7 @@ const SearchNB = () => { <div className="errorMessage">{errorMessage}</div> ) : ( items.map((item) => ( - <Card key={item._id} item={item} searchValue={searchParameter} onClick={chooseItem} /> + <Card key={item.id} item={item} searchValue={searchParameter} onClick={chooseItem} /> )) )} </Grid> diff --git a/studio/plugins/import-tool/src/nb/components/Card.js b/studio/plugins/import-tool/src/nb/components/Card.js index 2ffc5d2..319c4c8 100644 --- a/studio/plugins/import-tool/src/nb/components/Card.js +++ b/studio/plugins/import-tool/src/nb/components/Card.js @@ -29,7 +29,7 @@ const Card = ({item, searchValue, onClick}) => { return ( <SanityCard style={{display: "flex", flexDirection: "column"}}Â - key={item._id} + key={item.id} padding={[2, 2, 3]} radius={2} shadow={1} -- GitLab