Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Analysis - 514] Personalizable Collections #521

Closed
wants to merge 13 commits into from
4 changes: 4 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
]
},
"dependencies": {
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@faker-js/faker": "7.6.0",
"@iqss/dataverse-client-javascript": "2.0.0-pr192.6406015",
"@iqss/dataverse-design-system": "*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ $tooltip-max-width: 500px;

// Navbar

@import 'bootstrap/scss/carousel';

$navbar-light-brand-color: $dv-brand-color;
$navbar-brand-font-size: $dv-brand-font-size;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ import { Form as FormBS } from 'react-bootstrap'
import * as React from 'react'

export type FormInputElement = HTMLInputElement | HTMLTextAreaElement
export interface FormTextAreaProps extends Omit<React.HTMLAttributes<FormInputElement>, 'rows'> {
export interface FormTextAreaProps extends React.HTMLAttributes<FormInputElement> {
name?: string
disabled?: boolean
isValid?: boolean
isInvalid?: boolean
value?: string
autoFocus?: boolean
rows?: number
}

export const FormTextArea = React.forwardRef(function FormTextArea(
{ name, disabled, isValid, isInvalid, value, autoFocus, ...props }: FormTextAreaProps,
{ name, disabled, isValid, isInvalid, value, autoFocus, rows = 5, ...props }: FormTextAreaProps,
ref
) {
return (
<FormBS.Control
as="textarea"
rows={5}
rows={rows}
disabled={disabled}
name={name}
isValid={isValid}
Expand Down
Binary file added src/assets/cover-example.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/dark-cover-example.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/light-cover-example.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/collection/domain/models/CollectionFeaturedItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface CollectionFeaturedItem {
title: string
content: string
image?: {
url: string
altText: string
}
}
2 changes: 2 additions & 0 deletions src/collection/domain/repositories/CollectionRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Collection } from '../models/Collection'
import { CollectionFacet } from '../models/CollectionFacet'
import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem'
import { CollectionItemsPaginationInfo } from '../models/CollectionItemsPaginationInfo'
import { CollectionItemSubset } from '../models/CollectionItemSubset'
import { CollectionSearchCriteria } from '../models/CollectionSearchCriteria'
Expand All @@ -16,4 +17,5 @@ export interface CollectionRepository {
paginationInfo: CollectionItemsPaginationInfo,
searchCriteria?: CollectionSearchCriteria
): Promise<CollectionItemSubset>
getFeaturedItems(collectionIdOrAlias: number | string): Promise<CollectionFeaturedItem[]>
}
11 changes: 11 additions & 0 deletions src/collection/domain/useCases/getCollectionFeaturedItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem'
import { CollectionRepository } from '../repositories/CollectionRepository'

export async function getCollectionFeaturedItems(
collectionRepository: CollectionRepository,
collectionIdOrAlias: number | string
): Promise<CollectionFeaturedItem[]> {
return collectionRepository.getFeaturedItems(collectionIdOrAlias).catch((error: Error) => {
throw new Error(error.message)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CollectionItemsPaginationInfo } from '../../domain/models/CollectionIte
import { CollectionItemSubset } from '../../domain/models/CollectionItemSubset'
import { CollectionSearchCriteria } from '../../domain/models/CollectionSearchCriteria'
import { JSCollectionItemsMapper } from '../mappers/JSCollectionItemsMapper'
import { CollectionFeaturedItem } from '@/collection/domain/models/CollectionFeaturedItem'

export class CollectionJSDataverseRepository implements CollectionRepository {
getById(id: string): Promise<Collection> {
Expand Down Expand Up @@ -57,4 +58,54 @@ export class CollectionJSDataverseRepository implements CollectionRepository {
}
})
}

getFeaturedItems(_collectionIdOrAlias: number | string): Promise<CollectionFeaturedItem[]> {
// TODO:ME Mocked data for now

return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
title: 'Featured item 1',
content:
'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Aut alias eos expedita quae quisquam ea nemo neque incidunt amet. Odit quos libero aliquam labore dicta eaque dolorum, consequuntur itaque corrupti, reiciendis quas ab. Voluptatem alias, quam, aliquid excepturi repudiandae ab ex pariatur, est id perspiciatis porro impedit adipisci beatae ipsam.'
},
{
title: 'Featured item 2',
content:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Autem assumenda nam repellendus adipisci doloremque fugit maiores. Repudiandae sequi illo eum quod id quisquam vero enim ipsa distinctio, quia, consequatur harum dolor non voluptatibus ipsum, rem vel quo voluptates magni eveniet velit hic! Repellendus, provident? Dolore maxime ullam ut est, delectus itaque beatae alias corporis doloremque architecto magni officiis tenetur reprehenderit.',
image: {
url: 'https://via.placeholder.com/150x80',
altText: 'Placeholder image item 2'
}
},
{
title: 'Featured item 3',
content:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Autem assumenda nam repellendus adipisci doloremque fugit maiores. Repudiandae sequi illo eum quod id quisquam vero enim ipsa distinctio, quia, consequatur harum dolor non voluptatibus ipsum, rem vel quo voluptates magni eveniet velit hic! Repellendus, provident? Dolore maxime ullam ut est, delectus itaque beatae alias corporis doloremque architecto magni officiis tenetur reprehenderit.',
image: {
url: 'https://via.placeholder.com/400x400',
altText: 'Placeholder image item 2'
}
},
{
title: 'Featured item 4',
content:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Autem assumenda nam repellendus adipisci doloremque fugit maiores. Repudiandae sequi illo eum quod id quisquam vero enim ipsa distinctio, quia, consequatur harum dolor non voluptatibus ipsum, rem vel quo voluptates magni eveniet velit hic! Repellendus, provident? Dolore maxime ullam ut est, delectus itaque beatae alias corporis doloremque architecto magni officiis tenetur reprehenderit.',
image: {
url: 'https://via.placeholder.com/800x400',
altText: 'Placeholder image item 2'
}
},
{
title: 'Featured item 5',
content:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere enim alias quibusdam debitis quaerat, consectetur velit aliquid ipsum! Iusto obcaecati quibusdam hic nam voluptate. Consequuntur laborum officia aliquid recusandae ut, numquam sequi explicabo voluptatum reprehenderit, minima nulla repudiandae at magni fugit quis. Numquam architecto voluptas repudiandae vel totam officia ut? Nobis ex natus optio. Laborum veniam inventore suscipit architecto consectetur minima commodi dolore ducimus ipsam sint vitae doloremque, dolorem ullam! Iusto corporis commodi, pariatur vel magni, dolorum vero non, exercitationem cumque in deserunt? Iste quia dolor aut ullam dolorem eum quidem id accusamus officiis dolore. Quia placeat sapiente aperiam vero distinctio quidem, fugiat recusandae quisquam saepe ut, nesciunt laborum. Aspernatur ea, quasi, facilis impedit optio repellendus quo dolorum velit officia nihil mollitia provident commodi, delectus vero sint porro modi dolores? Ipsam doloribus impedit iure nemo architecto minima explicabo, eligendi dolorem voluptatem sequi aperiam, quisquam placeat ullam facere ut, minus at sit inventore enim eveniet cum. Repellendus eius quam architecto vel cum quod, tenetur neque deserunt, hic a perferendis adipisci fuga quibusdam dignissimos accusantium autem. Quisquam, ipsum harum fugit voluptatum aperiam minus corrupti hic qui impedit eveniet facere ut iure omnis error dicta nobis eum. Repellendus quia, laboriosam laudantium voluptates expedita fuga nulla? Quia nobis labore possimus debitis temporibus minus neque molestiae quam! Repellat reiciendis culpa similique odit doloribus quas praesentium quasi. Voluptatem amet iusto dolore, id, ut eligendi soluta assumenda excepturi perferendis, reiciendis odit explicabo ipsam maiores. Aut corporis eveniet quia repudiandae dolorem nam, excepturi ipsam veniam amet perferendis ullam eaque suscipit unde consequuntur asperiores maiores vel. Adipisci, asperiores. Iure quasi natus repudiandae quod, placeat blanditiis earum tenetur at dolores? Laudantium nam aperiam architecto consequatur, quos molestiae, amet sit itaque debitis neque quo? Iste repellendus vero illo deleniti eum impedit perferendis odit earum, iusto in porro id itaque quasi voluptate.'
}
])

// resolve([])
}, 1_000)
})
}
}
8 changes: 8 additions & 0 deletions src/router/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { CreateCollectionFactory } from '../sections/create-collection/CreateCol
import { AccountFactory } from '../sections/account/AccountFactory'
import { ProtectedRoute } from './ProtectedRoute'
import { HomepageFactory } from '../sections/homepage/HomepageFactory'
import { CollectionFeaturedItemsFactory } from '@/sections/collection-featured-items/CollectionFeaturedItemsFactory'

// TODO:ME We are going to need nested layouts routes to achieve the desired layout structure of a collection page with the edition pages
// TODO:ME Or maybe reuse the collection header as a component easier perhaps

export const routes: RouteObject[] = [
{
Expand Down Expand Up @@ -62,6 +66,10 @@ export const routes: RouteObject[] = [
{
path: Route.ACCOUNT,
element: AccountFactory.create()
},
{
path: Route.COLLECTION_FEATURED_ITEMS,
element: CollectionFeaturedItemsFactory.create()
}
]
}
Expand Down
7 changes: 5 additions & 2 deletions src/sections/Route.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@ export enum Route {
COLLECTIONS_BASE = '/collections',
COLLECTIONS = '/collections/:collectionId',
CREATE_COLLECTION = '/collections/:ownerCollectionId/create',
ACCOUNT = '/account'
ACCOUNT = '/account',
COLLECTION_FEATURED_ITEMS = '/collections/:collectionId/featured-items'
}

export const RouteWithParams = {
COLLECTIONS: (collectionId?: string) => `/collections/${collectionId ?? 'root'}`,
CREATE_COLLECTION: (ownerCollectionId?: string) =>
`/collections/${ownerCollectionId ?? ROOT_COLLECTION_ALIAS}/create`,
CREATE_DATASET: (collectionId?: string) =>
`/datasets/${collectionId ?? ROOT_COLLECTION_ALIAS}/create`
`/datasets/${collectionId ?? ROOT_COLLECTION_ALIAS}/create`,
COLLECTION_FEATURED_ITEMS: (collectionId?: string) =>
`/collections/${collectionId ?? ROOT_COLLECTION_ALIAS}/featured-items`
}

export enum QueryParamKey {
Expand Down
54 changes: 54 additions & 0 deletions src/sections/collection-featured-items/CollectionFeaturedItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Alert, Col, Row } from '@iqss/dataverse-design-system'
import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository'
import { useCollection } from '../collection/useCollection'
import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator'
import { CollectionInfo } from '../collection/CollectionInfo'
import { CollectionSkeleton } from '../collection/CollectionSkeleton'
import { PageNotFound } from '../page-not-found/PageNotFound'
import { SeparationLine } from '../shared/layout/SeparationLine/SeparationLine'
import { FeaturedItemsForm } from './FeaturedItemsForm/FeaturedItemsForm'

interface CollectionFeaturedItemsProps {
collectionRepository: CollectionRepository
collectionId: string
}

export const CollectionFeaturedItems = ({
collectionId,
collectionRepository
}: CollectionFeaturedItemsProps) => {
const { collection, isLoading } = useCollection(collectionRepository, collectionId)

if (!isLoading && !collection) {
return <PageNotFound />
}

return (
<Row>
<Col>
{!collection ? (
<CollectionSkeleton />
) : (
<>
<BreadcrumbsGenerator
hierarchy={collection.hierarchy}
withActionItem
actionItemText="Featured Items"
/>

<CollectionInfo collection={collection} showDescription={false} />

<SeparationLine />
<Alert variant="info" customHeading="What is this about?" dismissible={false}>
Add Featured Items to showcase key content in your collection. These items will appear
as cards in a carousel, each including a title and either text or text with an image.
If your collection has a description, it will be the first carousel item by default.
</Alert>

<FeaturedItemsForm />
</>
)}
</Col>
</Row>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ReactElement } from 'react'
import { CollectionFeaturedItems } from './CollectionFeaturedItems'
import { CollectionJSDataverseRepository } from '@/collection/infrastructure/repositories/CollectionJSDataverseRepository'
import { useParams } from 'react-router-dom'

const collectionRepository = new CollectionJSDataverseRepository()

export class CollectionFeaturedItemsFactory {
static create(): ReactElement {
return <CollectionFeaturedItemsWithSearchParams />
}
}

function CollectionFeaturedItemsWithSearchParams() {
const { collectionId = 'root' } = useParams<{ collectionId: string }>()

return (
<CollectionFeaturedItems
collectionRepository={collectionRepository}
collectionId={collectionId}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@use 'sass:color';
@import 'node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module';

.featured-item-fields-wrapper {
position: relative;
padding: 1rem;
border: solid 1px $dv-secondary-color;
border-radius: 6px;

&.sorting {
background-color: color.adjust($dv-secondary-color, $alpha: -0.4);

&.active {
z-index: 999;
background-color: color.adjust($dv-primary-color, $alpha: -0.4);
}
}

.drag-handle {
width: 38px;
height: 38px;
padding: 0;
background-color: transparent;
border: 0;
border: solid 1px $dv-secondary-color;
border-radius: 4px;
cursor: grab;

&:hover:not(.disabled) {
background-color: $dv-secondary-color;
}

&:active:not(.disabled) {
cursor: grabbing;
}

&.disabled {
opacity: 0.5;
pointer-events: none;
}
}

.image-dropzone {
display: grid;
place-items: center;
height: 38px;
border: dashed 1px #ced4da;
border-radius: 6px;
cursor: pointer;
transition: border-color 0.3s;

&:hover {
border-color: #333;
}

&:focus {
border-color: #333;
}

small {
margin: 0;
}
}
}
Loading
Loading