Skip to content

Commit

Permalink
Add modal to table
Browse files Browse the repository at this point in the history
  • Loading branch information
jochenklar committed Oct 5, 2024
1 parent c51ebda commit 7b4abea
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 29 deletions.
16 changes: 13 additions & 3 deletions daiquiri/core/assets/js/api/BaseApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function ValidationError(errors) {
this.errors = errors
}

class BaseApi {
export default class BaseApi {

static get(url) {
return fetch(baseUrl + url).catch(error => {
Expand All @@ -29,6 +29,18 @@ class BaseApi {
})
}

static getText(url) {
return fetch(baseUrl + url).catch(error => {
throw new ApiError(error.message)
}).then(response => {
if (response.ok) {
return response.text()
} else {
throw new ApiError(response.statusText, response.status)
}
})
}

static post(url, data) {
return fetch(baseUrl + url, {
method: 'POST',
Expand Down Expand Up @@ -120,5 +132,3 @@ class BaseApi {
}

}

export default BaseApi
20 changes: 20 additions & 0 deletions daiquiri/core/assets/js/api/CoreApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import BaseApi from './BaseApi'

import { encodeParams } from 'daiquiri/core/assets/js/utils/api'

export default class CoreApi extends BaseApi {

static fetchDataLinks(dataLinkId) {
const params = {
'ID': dataLinkId,
'RESPONSEFORMAT': 'application/json'
}

return this.get(`/datalink/links?${encodeParams(params)}`).then(response => response.links)
}

static fetchNote(url) {
return this.getText(url)
}

}
93 changes: 91 additions & 2 deletions daiquiri/core/assets/js/components/table/Table.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,95 @@
import React from 'react'
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { isEmpty } from 'lodash'

import { useModal } from 'daiquiri/core/assets/js/hooks/modal'

import { getFileUrl, isDataLinkColumn, isImageColumn, isNoteColumn, isModalColumn } from '../../utils/table.js'

import TableFooter from './TableFooter'
import TableHeader from './TableHeader'
import TableModal from './TableModal'
import TablePane from './TablePane'

const Table = ({ columns, rows, pageSizes, params, setParams }) => {
const show = !(isEmpty(columns) || isEmpty(rows))
const pageCount = rows.count / params.page_size
const pageCount = Math.ceil(rows.count / params.page_size)

const [modalRef, showModal, hideModal] = useModal()
const [modalValues, setModalValues] = useState({})
const [active, setActive] = useState({})

useEffect(() => {
if (modalRef.current && modalRef.current.classList.contains('show') && modalValues.page == params.page) {
// update the modal if (a) it is shown and (b) if we are not currently changing pages
updateModal(active)
}
}, [active])

useEffect(() => {
if (modalRef.current && modalRef.current.classList.contains('show')) {
// always update the modal if the rows change
updateModal(active)
}
}, [rows])

const updateModal = ({ rowIndex, columnIndex }) => {
const column = columns[columnIndex]
const value = rows.results[rowIndex][columnIndex]

if (isModalColumn(column)) {
setModalValues({
title: value,
dataLinkId: isDataLinkColumn(column) ? value : null,
noteUrl: isNoteColumn(column) ? getFileUrl(column, value) : null,
imageSrc: isImageColumn(column) ? getFileUrl(column, value) : null,
page: params.page,
up: (rowIndex > 0 || params.page > 1),
down: (rowIndex < params.page_size - 1 || params.page < pageCount),
right: columns.filter((c, i) => i > columnIndex).some(isModalColumn),
left: columns.filter((c, i) => i < columnIndex).some(isModalColumn),
})
}
}

const handleClick = (rowIndex, columnIndex) => {
setActive({ rowIndex, columnIndex })

console.log(isModalColumn(columns[columnIndex]))

if (isModalColumn(columns[columnIndex])) {
updateModal({ rowIndex, columnIndex })
showModal()
}
}

const handleNavigation = (direction) => {
if (direction == 'up') {
if (active.rowIndex > 0) {
setActive({ ...active, rowIndex: active.rowIndex - 1 })
} else if (params.page > 1) {
setActive({ ...active, rowIndex: params.page_size - 1 })
setParams({ ...params, page: params.page - 1 })
}
} else if (direction == 'down') {
if (active.rowIndex < params.page_size - 1) {
setActive({ ...active, rowIndex: active.rowIndex + 1 })
} else if (params.page < pageCount) {
setActive({ ...active, rowIndex: 0 })
setParams({ ...params, page: params.page + 1 })
}
} else if (direction == 'right') {
const columnIndex = columns.findIndex((c, i) => isModalColumn(c) && i > active.columnIndex)
if (columnIndex > 0) {
setActive({ ...active, columnIndex})
}
} else if (direction == 'left') {
const columnIndex = columns.findIndex((c, i) => isModalColumn(c) && i < active.columnIndex)
if (columnIndex > 0) {
setActive({ ...active, columnIndex})
}
}
}

return show && (
<div className="dq-table mb-3">
Expand All @@ -21,7 +102,9 @@ const Table = ({ columns, rows, pageSizes, params, setParams }) => {
columns={columns}
rows={rows}
params={params}
active={active}
setParams={setParams}
onClick={handleClick}
/>
<TableFooter
rowCount={rows.count}
Expand All @@ -30,6 +113,12 @@ const Table = ({ columns, rows, pageSizes, params, setParams }) => {
params={params}
setParams={setParams}
/>
<TableModal
modalRef={modalRef}
modalValues={modalValues}
onNavigation={handleNavigation}
onClose={hideModal}
/>
</div>
)
}
Expand Down
16 changes: 12 additions & 4 deletions daiquiri/core/assets/js/components/table/TableBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isEmpty } from 'lodash'

import TableCell from './TableCell'

const TableBody = ({ columns, rows }) => {
const TableBody = ({ columns, rows, active, onClick }) => {
return (
<tbody>
{
Expand All @@ -15,11 +15,17 @@ const TableBody = ({ columns, rows }) => {
</div>
</td>
) : rows.results.map((row, rowIndex) => (
<tr key={rowIndex}>
<tr key={rowIndex} className={active.rowIndex == rowIndex ? 'table-active' : ''}>
{
columns.map((column, columnIndex) => (
<td key={columnIndex}>
<TableCell column={column} value={row[columnIndex]} />
<TableCell
column={column}
value={row[columnIndex]}
rowIndex={rowIndex}
columnIndex={columnIndex}
onClick={onClick}
/>
</td>
))
}
Expand All @@ -32,7 +38,9 @@ const TableBody = ({ columns, rows }) => {

TableBody.propTypes = {
columns: PropTypes.array.isRequired,
rows: PropTypes.object.isRequired
rows: PropTypes.object.isRequired,
active: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired
}

export default TableBody
52 changes: 47 additions & 5 deletions daiquiri/core/assets/js/components/table/TableCell.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,59 @@
import React from 'react'
import PropTypes from 'prop-types'

const TableCell = ({ column, value }) => {
import { getBasename, getFileUrl, getLinkUrl, getReferenceUrl,
isModalColumn, isFileColumn, isLinkColumn } from '../../utils/table.js'

const TableCell = ({ column, value, rowIndex, columnIndex, onClick }) => {

const handleClick = (event) => {
event.preventDefault()
event.stopPropagation()
onClick(rowIndex, columnIndex)
}

const renderCell = () => {
if (column.ucd && column.ucd.includes('meta.ref')) {
if (isModalColumn(column)) {
// render the modal
return (
<a href={getFileUrl(column, value)} onClick={handleClick}>{value}</a>
)
} else if (isFileColumn(column)) {
// render a file link
return (
<a href={getFileUrl(column, value)} target="_blank" rel="noreferrer">{getBasename(value)}</a>
)
} else if (isLinkColumn(column)) {
// render a regular link
return (
<a href={getLinkUrl(column, value)} target="_blank" rel="noreferrer">{value}</a>
)
} else {
// render a link to the resolver
return (
<a href={getReferenceUrl(column, value)} target="_blank" rel="noreferrer">{value}</a>
)
}
} else {
// this is not a reference, just render the value
return value
}
}

return (
<div className="dq-table-cell">
{value}
<div className="dq-table-cell" onClick={handleClick}>
{renderCell()}
</div>
)
}

TableCell.propTypes = {
column: PropTypes.number.isRequired,
value: PropTypes.array.isRequired
column: PropTypes.object.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
rowIndex: PropTypes.number.isRequired,
columnIndex: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired
}

export default TableCell
62 changes: 62 additions & 0 deletions daiquiri/core/assets/js/components/table/TableModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react'
import PropTypes from 'prop-types'

import { useDataLinksQuery, useNoteQuery } from '../../hooks/queries'

import TableModalNavigation from './TableModalNavigation'

const TableModal = ({ modalRef, modalValues, onNavigation, onClose }) => {

const { data: dataLinks } = useDataLinksQuery(modalValues.dataLinkId)
const { data: note } = useNoteQuery(modalValues.noteUrl)

return (
<div ref={modalRef} className="dq-table-modal modal" tabIndex="-1">
<div className="modal-dialog modal-lg">
{
modalValues && (
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">{modalValues.title}</h5>
<button type="button" className="btn-close" onClick={onClose}></button>
</div>
<div className="modal-body">
<TableModalNavigation values={modalValues} onClick={onNavigation} />
{
dataLinks && (
<ul>
{
dataLinks.map((link, linkIndex) => (
<li key={linkIndex}>
<a href={link.href} target="_blank" rel="noreferrer">{link.text || link.href}</a>
</li>
))
}
</ul>
)
}
{
note && <pre>{note}</pre>
}
{
modalValues.imageSrc && (
<img className="d-block mx-auto" src={modalValues.imageSrc} alt={modalValues.title} />
)
}
</div>
</div>
)
}
</div>
</div>
)
}

TableModal.propTypes = {
modalRef: PropTypes.object,
modalValues: PropTypes.object,
onNavigation: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired
}

export default TableModal
Loading

0 comments on commit 7b4abea

Please sign in to comment.