Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
SSHOC
sshoc-marketplace-frontend
Commits
1149ac4f
Commit
1149ac4f
authored
Apr 14, 2021
by
Stefan Probst
Browse files
feat: update metadata panel
parent
498d0b3d
Pipeline
#188091
failed with stage
in 49 seconds
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
src/screens/item/ItemLayout.tsx
View file @
1149ac4f
...
...
@@ -5,6 +5,7 @@ import Link from 'next/link'
import
type
{
PropsWithChildren
}
from
'
react
'
import
{
Fragment
,
useMemo
,
useState
}
from
'
react
'
import
type
{
ActorDto
,
PropertyDto
}
from
'
@/api/sshoc
'
import
{
useGetItemCategories
}
from
'
@/api/sshoc
'
import
type
{
Item
as
GenericItem
,
...
...
@@ -139,7 +140,6 @@ export default function ItemLayout({
<
ItemPropertiesList
properties
=
{
item
.
properties
as
ItemProperties
}
contributors
=
{
item
.
contributors
as
ItemContributors
}
licenses
=
{
item
.
licenses
}
source
=
{
item
.
source
}
sourceItemId
=
{
item
.
sourceItemId
}
dateCreated
=
{
item
.
dateCreated
}
...
...
@@ -354,21 +354,14 @@ function ItemMedia({ properties }: { properties: ItemProperties }) {
)
}
/** thumbnail and media are already shown, no need to list them in the metadata panel */
const
HIDDEN_PROPERTIES
=
[
'
thumbnail
'
,
'
media
'
]
/**
* Properties.
* Some properties are marked as `hidden` by the backend.
* `thumbnail` and `media` are already shown, so we treat these as additional hidden properties
* in the metadata sidepanel.
*/
function
ItemPropertiesList
({
properties
,
contributors
,
licenses
,
source
,
sourceItemId
,
dateCreated
,
dateLastUpdated
,
}:
{
const
ADDITIONAL_HIDDEN_PROPERTIES
=
[
'
thumbnail
'
,
'
media
'
]
interface
ItemMetadata
{
properties
:
ItemProperties
contributors
:
ItemContributors
licenses
?:
Item
[
'
licenses
'
]
...
...
@@ -376,171 +369,237 @@ function ItemPropertiesList({
sourceItemId
?:
Item
[
'
sourceItemId
'
]
dateCreated
?:
string
dateLastUpdated
?:
string
})
{
const
groupedProperties
=
useMemo
(()
=>
{
if
(
properties
===
undefined
||
properties
.
length
===
0
)
return
{}
}
const
grouped
:
Record
<
string
,
Array
<
ItemProperty
>>
=
{}
properties
.
forEach
((
property
)
=>
{
if
(
HIDDEN_PROPERTIES
.
includes
(
property
.
type
?.
code
as
string
))
return
/**
* Properties.
*/
function
ItemPropertiesList
(
props
:
ItemMetadata
)
{
const
metadata
=
useItemMetadata
(
props
)
const
label
=
property
.
type
?.
label
as
string
if
(
!
Array
.
isArray
(
grouped
[
label
]))
{
grouped
[
label
]
=
[]
}
grouped
[
label
].
push
(
property
)
})
return
grouped
},
[
properties
])
if
(
metadata
==
null
)
return
null
const
sortedLabels
=
Object
.
keys
(
groupedProperties
).
sort
()
return
(
<
aside
className
=
""
>
<
h2
className
=
"text-xl font-medium"
>
Details
</
h2
>
<
div
className
=
"divide-y"
>
{
Object
.
values
(
metadata
)
}
</
div
>
</
aside
>
)
}
if
(
sortedLabels
.
length
===
0
&&
(
contributors
==
null
||
contributors
.
length
===
0
)
&&
dateCreated
==
null
&&
dateLastUpdated
==
null
&&
(
licenses
==
null
||
licenses
.
length
===
0
)
&&
source
==
null
)
{
return
null
function
ItemPropertyValue
({
property
}:
{
property
:
ItemProperty
})
{
switch
(
property
.
type
?.
type
)
{
case
'
concept
'
:
return
(
<
Anchor
href
=
{
property
.
concept
?.
uri
}
>
{
property
.
concept
?.
label
}
</
Anchor
>
)
case
'
string
'
:
return
<
span
>
{
property
.
value
}
</
span
>
case
'
url
'
:
return
<
Anchor
href
=
{
property
.
value
}
>
{
property
.
value
}
</
Anchor
>
case
'
int
'
:
case
'
float
'
:
return
<
span
>
{
property
.
value
}
</
span
>
case
'
date
'
:
return
property
.
value
===
undefined
?
null
:
(
<
time
dateTime
=
{
property
.
value
}
>
{
formatDate
(
property
.
value
)
}
</
time
>
)
default
:
return
null
}
}
return
(
<
VStack
className
=
"space-y-4"
>
<
SubSectionTitle
as
=
"h2"
>
Details
</
SubSectionTitle
>
<
VStack
as
=
"dl"
className
=
"space-y-2 text-sm leading-7"
>
{
/* contributors are a top-level field, not a property */
}
{
contributors
!=
null
&&
contributors
.
length
>
0
?
(
<
div
>
<
dt
className
=
"inline mr-2 text-gray-500"
>
Contributors:
</
dt
>
<
dd
className
=
"inline"
>
{
contributors
.
map
((
contributor
,
index
)
=>
{
if
(
contributor
==
null
||
contributor
.
actor
==
null
)
return
null
return
(
<
Fragment
key
=
{
`
${
contributor
.
actor
.
id
}${
contributor
.
role
?.
code
}
`
}
>
{
index
!==
0
?
<
span
>
,
</
span
>
:
null
}
{
contributor
.
actor
.
website
!=
null
?
(
<
a
href
=
{
contributor
.
actor
.
website
}
>
{
contributor
.
actor
.
name
}
</
a
>
)
:
(
contributor
.
actor
.
name
)
}
</
Fragment
>
)
})
}
</
dd
>
</
div
>
)
:
null
}
function
useItemMetadata
({
properties
,
contributors
,
source
,
sourceItemId
,
dateCreated
,
dateLastUpdated
,
}:
ItemMetadata
)
{
const
metadata
:
any
=
{}
{
/* dateCreated and dateLastUpdated are top-level fields, not properties */
}
{
dateCreated
!=
null
?
(
<
div
>
<
dt
className
=
"inline mr-2 text-gray-500"
>
Created:
</
dt
>
<
dd
className
=
"inline"
>
<
time
dateTime
=
{
dateCreated
}
>
{
formatDate
(
dateCreated
)
}
</
time
>
</
dd
>
</
div
>
)
:
null
}
{
dateLastUpdated
!=
null
?
(
<
div
>
<
dt
className
=
"inline mr-2 text-gray-500"
>
Last updated:
</
dt
>
<
dd
className
=
"inline"
>
<
time
dateTime
=
{
dateLastUpdated
}
>
{
formatDate
(
dateLastUpdated
)
}
</
time
>
</
dd
>
</
div
>
)
:
null
}
if
(
Array
.
isArray
(
properties
)
&&
properties
.
length
>
0
)
{
const
grouped
:
Record
<
string
,
Record
<
string
,
Array
<
PropertyDto
>>>
=
{}
properties
.
forEach
((
property
)
=>
{
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const
type
=
property
.
type
!
if
(
type
.
hidden
===
true
||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
ADDITIONAL_HIDDEN_PROPERTIES
.
includes
(
type
.
code
!
)
)
{
return
}
{
/* properties */
}
{
sortedLabels
.
map
((
label
)
=>
{
const
properties
=
groupedProperties
[
label
]
const
groupName
=
property
.
type
?.
groupName
??
'
Other
'
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
grouped
[
groupName
]
=
grouped
[
groupName
]
??
{}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const
label
=
type
.
label
!
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
grouped
[
groupName
][
label
]
=
grouped
[
groupName
][
label
]
??
[]
grouped
[
groupName
][
label
].
push
(
property
)
})
const
sorted
=
Object
.
entries
(
grouped
)
.
map
(([
key
,
values
])
=>
{
const
sortedValues
=
Object
.
entries
(
values
).
sort
(([,
[
a
]],
[,
[
b
]])
=>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
a
.
type
!
.
ord
!
>
b
.
type
!
.
ord
!
?
1
:
-
1
,
)
return
[
key
,
sortedValues
]
as
[
string
,
Array
<
[
string
,
Array
<
PropertyDto
>
]
>
,
]
})
.
sort
(([
a
],
[
b
])
=>
(
a
>
b
?
1
:
-
1
))
metadata
.
properties
=
(
<
ul
className
=
"py-8 space-y-6"
>
{
sorted
.
map
(([
groupName
,
entries
])
=>
{
return
(
<
div
key
=
{
label
}
>
<
dt
className
=
"inline mr-2 text-gray-500"
>
{
label
}
:
</
dt
>
<
dd
className
=
"inline"
>
{
properties
.
map
((
property
,
index
)
=>
{
<
li
key
=
{
groupName
}
className
=
"flex flex-col space-y-2"
>
<
span
className
=
"font-bold tracking-wide uppercase text-ui-sm whitespace-nowrap"
>
{
groupName
}
</
span
>
<
ul
className
=
"flex flex-col space-y-2"
>
{
entries
.
map
(([
label
,
properties
])
=>
{
return
(
<
Fragment
key
=
{
property
.
id
}
>
{
index
!==
0
?
<
span
>
,
</
span
>
:
null
}
<
ItemPropertyValue
property
=
{
property
}
/>
</
Fragment
>
<
li
key
=
{
label
}
>
<
span
className
=
"mr-2 font-medium text-gray-500 whitespace-nowrap"
>
{
label
}
:
</
span
>
<
ul
className
=
"inline"
>
{
properties
.
map
((
property
,
index
)
=>
{
return
(
<
li
key
=
{
property
.
id
}
className
=
"inline"
>
{
index
!==
0
?
'
,
'
:
null
}
<
ItemPropertyValue
property
=
{
property
}
/>
</
li
>
)
})
}
</
ul
>
</
li
>
)
})
}
</
dd
>
</
div
>
</
ul
>
</
li
>
)
})
}
</
ul
>
)
}
{
/* licenses are a top-level field, not a property */
}
{
licenses
!=
null
&&
licenses
.
length
>
0
?
(
if
(
Array
.
isArray
(
contributors
)
&&
contributors
.
length
>
0
)
{
const
grouped
:
Record
<
string
,
Array
<
ActorDto
>>
=
{}
contributors
.
forEach
((
contributor
)
=>
{
const
role
=
contributor
.
role
?.
label
if
(
role
!=
null
&&
contributor
.
actor
!=
null
)
{
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
grouped
[
role
]
=
grouped
[
role
]
??
[]
grouped
[
role
].
push
(
contributor
.
actor
)
}
})
const
sorted
=
Object
.
entries
(
grouped
)
.
map
(([
key
,
value
])
=>
{
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return
[
key
,
value
.
sort
((
a
,
b
)
=>
a
.
name
!
.
localeCompare
(
b
.
name
!
))]
as
[
string
,
Array
<
ActorDto
>
,
]
})
.
sort
(([
a
],
[
b
])
=>
(
a
>
b
?
1
:
-
1
))
metadata
.
contributors
=
(
<
ul
className
=
"py-8 space-y-6"
>
{
sorted
.
map
(([
role
,
actors
])
=>
{
return
(
<
li
key
=
{
role
}
className
=
"flex flex-col space-y-3"
>
<
span
className
=
"font-bold tracking-wide uppercase text-ui-sm whitespace-nowrap"
>
{
role
}
</
span
>
<
ul
className
=
"flex flex-col space-y-2"
>
{
actors
.
map
((
actor
)
=>
{
return
(
<
li
key
=
{
actor
.
id
}
className
=
"flex flex-col"
>
<
span
className
=
"mr-2 font-medium text-gray-500 whitespace-nowrap"
>
{
actor
.
name
}
</
span
>
{
actor
.
email
!=
null
?
(
<
Anchor
href
=
{
'
mailto:
'
+
actor
.
email
}
>
{
actor
.
email
}
</
Anchor
>
)
:
null
}
{
actor
.
website
!=
null
?
(
<
Anchor
href
=
{
actor
.
website
}
>
Website
</
Anchor
>
)
:
null
}
</
li
>
)
})
}
</
ul
>
</
li
>
)
})
}
</
ul
>
)
}
if
(
dateCreated
!=
null
||
dateLastUpdated
!=
null
)
{
metadata
.
dates
=
(
<
dl
className
=
"py-8"
>
{
dateCreated
!=
null
?
(
<
div
>
<
dt
className
=
"inline mr-2 text-gray-500"
>
Licenses:
</
dt
>
<
dd
className
=
"inline"
>
{
licenses
.
map
((
license
,
index
)
=>
{
return
(
<
Fragment
key
=
{
license
.
code
}
>
{
index
!==
0
?
<
span
>
,
</
span
>
:
null
}
{
license
.
accessibleAt
!=
null
?
(
<
a
href
=
{
license
.
accessibleAt
}
>
{
license
.
label
}
</
a
>
)
:
(
license
.
label
)
}
</
Fragment
>
)
})
}
<
dt
>
<
span
className
=
"mr-2 font-medium text-gray-500 whitespace-nowrap"
>
Created:
</
span
>
</
dt
>
<
dd
>
<
time
dateTime
=
{
dateCreated
}
>
{
formatDate
(
dateCreated
)
}
</
time
>
</
dd
>
</
div
>
)
:
null
}
{
/* source is a top-level field, not a property */
}
{
source
!=
null
?
(
{
dateLastUpdated
!=
null
?
(
<
div
>
<
dt
className
=
"inline mr-2 text-gray-500"
>
Source:
</
dt
>
<
dd
className
=
"inline"
>
<
Fragment
key
=
{
source
.
id
}
>
{
source
.
urlTemplate
!=
null
&&
sourceItemId
!=
null
?
(
<
a
href
=
{
source
.
urlTemplate
.
replace
(
'
{source-item-id}
'
,
sourceItemId
,
)
}
>
{
source
.
label
}
</
a
>
)
:
(
source
.
label
)
}
</
Fragment
>
<
dt
>
<
span
className
=
"mr-2 font-medium text-gray-500 whitespace-nowrap"
>
Created:
</
span
>
</
dt
>
<
dd
>
<
time
dateTime
=
{
dateLastUpdated
}
>
{
formatDate
(
dateLastUpdated
)
}
</
time
>
</
dd
>
</
div
>
)
:
null
}
</
VStack
>
</
VStack
>
)
}
</
dl
>
)
}
function
ItemPropertyValue
({
property
}:
{
property
:
ItemProperty
})
{
switch
(
property
.
type
?.
type
)
{
case
'
concept
'
:
return
<
a
href
=
{
property
.
concept
?.
uri
}
>
{
property
.
concept
?.
label
}
</
a
>
case
'
string
'
:
return
<
span
>
{
property
.
value
}
</
span
>
case
'
url
'
:
return
<
Anchor
href
=
{
property
.
value
}
>
{
property
.
value
}
</
Anchor
>
case
'
int
'
:
case
'
float
'
:
return
<
span
>
{
property
.
value
}
</
span
>
case
'
date
'
:
return
property
.
value
===
undefined
?
null
:
(
<
time
dateTime
=
{
property
.
value
}
>
{
formatDate
(
property
.
value
)
}
</
time
>
if
(
source
!=
null
)
{
const
label
=
source
.
label
if
(
sourceItemId
!=
null
)
{
metadata
.
sourceItemId
=
(
<
div
className
=
"py-8"
>
<
span
className
=
"mr-2 font-medium text-gray-500 whitespace-nowrap"
>
Source:
</
span
>
{
source
.
urlTemplate
!=
null
?
(
<
Anchor
href
=
{
source
.
urlTemplate
.
replace
(
'
{source-item-id}
'
,
sourceItemId
,
)
}
>
{
label
}
</
Anchor
>
)
:
(
<
span
>
{
label
}
</
span
>
)
}
</
div
>
)
default
:
return
null
}
}
return
metadata
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment