Skip to content
Snippets Groups Projects
Commit 9890e235 authored by Tarje Lavik's avatar Tarje Lavik
Browse files

Add KulturNav import of Concepts

parent d524ebf9
No related branches found
No related tags found
No related merge requests found
/* 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>
......
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),
}
}
}
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
......@@ -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
......
......@@ -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>
......
......@@ -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}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment