Skip to content

Commit

Permalink
Merge pull request #83 from mitaai/blms/dashboard-ui
Browse files Browse the repository at this point in the history
#56 Dashboard UI updates, annotations list view
  • Loading branch information
blms authored Nov 18, 2020
2 parents 50aa6ca + 437b4f2 commit 35754ab
Show file tree
Hide file tree
Showing 14 changed files with 417 additions and 20 deletions.
5 changes: 4 additions & 1 deletion src/components/AnnotationCard/AnnotationCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import DocumentAnnotationsContext from '../../contexts/DocumentAnnotationsContex
import DocumentFiltersContext from '../../contexts/DocumentFiltersContext';

function addHoverEventListenersToAllHighlightedText() {
// console.log('annotation-highlighted-text', $('.annotation-highlighted-text'));

$('.annotation-highlighted-text').on('mouseover', (e) => {
// highlighting all every piece of the annotation a different color by setting it to active
$(`.annotation-highlighted-text[annotation-id='${$(e.target).attr('annotation-id')}']`).addClass('active');
Expand Down Expand Up @@ -184,6 +184,9 @@ function AnnotationCard({
newAnnotationData.modified = new Date();
newAnnotationData.new = false;
newAnnotationData.editing = false;
$($(`#document-content-container span[annotation-id='${newAnnotationData._id}']`).get(-1)).prepend("<span class='annotation-ending-marker'></span>");
const annotationEnding = $(`#document-content-container span[annotation-id='${annotation._id}'] .annotation-ending-marker`);
newAnnotationData.position.height = (annotationEnding.offset().top - newAnnotationData.position.top) + 18;
setSavingAnnotation(false);
// after setting the annotation data we need to reset the "new" data back to null
setNewAnnotationTags(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/* eslint-disable no-underscore-dangle */
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import {
Badge, Card, Col, ListGroup, Row, Tab, Tabs,
} from 'react-bootstrap';
import { format } from 'date-fns';
import LoadingSpinner from '../../LoadingSpinner';
import { getSharedAnnotations, getOwnAnnotations } from '../../../utils/annotationUtil';
import { getGroupNameById } from '../../../utils/groupUtil';
import { FirstNameLastInitial } from '../../../utils/nameUtil';

const DashboardAnnotationList = ({
session,
alerts,
setAlerts,
tab,
mode,
}) => {
const [groupState, setGroupState] = useState({});
const [key, setKey] = useState(tab || 'mine');
const [listLoading, setListLoading] = useState(true);
const [annotations, setAnnotations] = useState([]);
const limit = mode === 'dashboard' ? 10 : undefined;

useEffect(() => {
async function fetchData() {
if (session && (session.user.groups || session.user.id)) {
if (key === 'shared') {
setAnnotations(
await getSharedAnnotations(session.user.groups, limit)
.then(setListLoading(false))
.catch((err) => setAlerts([...alerts, { text: err.message, variant: 'danger' }])),
);
} else if (key === 'mine') {
setAnnotations(
await getOwnAnnotations(session.user.id, limit)
.then(setListLoading(false))
.catch((err) => setAlerts([...alerts, { text: err.message, variant: 'danger' }])),
);
}
}
}
fetchData();
}, [key]);


useEffect(() => {
if (annotations) {
const fetchGroupState = async () => {
annotations.map((annotation) => annotation.permissions.groups.map(async (group) => {
if (!groupState[group]) {
setGroupState({ ...groupState, [group]: await getGroupNameById(group) });
}
}));
};
fetchGroupState();
}
}, [annotations, groupState]);

return (
<Card>
<Card.Header>
<Card.Title>
{mode === 'dashboard' && (
<Link href="/annotations">Annotations</Link>
)}
{mode === 'list' && (
<>Annotations</>
)}
</Card.Title>
<Tabs
transition={false}
style={{ justifyContent: 'flex-end', float: 'right', marginTop: '-2rem' }}
activeKey={key}
onSelect={(k) => setKey(k)}
>
<Tab eventKey="shared" title="Shared" />
<Tab eventKey="mine" title="Mine" />
</Tabs>
</Card.Header>
{listLoading && (
<LoadingSpinner />
)}
{!listLoading && annotations && annotations.length > 0 && (
<>
<ListGroup>
{annotations.sort((a, b) => new Date(b.created) - new Date(a.created)).map(
(annotation) => (
<ListGroup.Item key={annotation._id}>
<Row>
<Col className="ellipsis" xl={8}>
<Link href={`/documents/${annotation.target.document.slug}?mine=${key === 'mine'}#${annotation._id}`}>
{annotation.target.selector.exact}
</Link>
</Col>
<Col className="text-right" xl={4}>
<small style={{ whiteSpace: 'nowrap' }}>{format(new Date(annotation.created), 'Ppp')}</small>
</Col>
</Row>
<Row>
<Col className="paragraph-ellipsis">
{annotation.body.value}
</Col>
</Row>
<Row>
<Col>
<small className="text-muted">
{annotation.target.document.title}
{' ('}
{FirstNameLastInitial(annotation.creator.name)}
)
{annotation.permissions.groups
&& annotation.permissions.groups.length > 0 && (
<Badge
variant="info"
key={annotation.permissions.groups.sort()[0]}
className="mr-2"
>
{groupState[annotation.permissions.groups.sort()[0]]}
</Badge>
)}
</small>
</Col>
</Row>
</ListGroup.Item>
),
)}
</ListGroup>
{mode === 'dashboard' && (
<Card.Footer style={{ fontWeight: 'bold', borderTop: 0 }} key="all-annotations">
<Link href={`/annotations?tab=${key}`} disabled>
{`See all ${key === 'shared' ? key : 'created'} annotations...`}
</Link>
</Card.Footer>
)}
</>
)}
{!listLoading && (!annotations || annotations.length === 0) && (
<Card.Body>
{`You have no ${key === 'shared' ? key : 'created'} annotations.`}
</Card.Body>
)}
</Card>
);
};

export default DashboardAnnotationList;
3 changes: 3 additions & 0 deletions src/components/Dashboard/DashboardAnnotationList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DashboardAnnotationList from './DashboardAnnotationList';

export default DashboardAnnotationList;
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const DashboardGroupList = ({
const [groups, setGroups] = useState([]);

useEffect(() => {
if (session && !deepEqual(session.user.groups, groups)) {
setGroups(session.user.groups);
if (session && !deepEqual(session.user.groups.slice(0, 10), groups)) {
setGroups(session.user.groups.slice(0, 10));
}
}, [session]);

Expand Down
10 changes: 7 additions & 3 deletions src/components/Document/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,17 @@ export default class Document extends React.Component {
// side channel and then add position data to the annotation object.
$($(`#document-content-container span[annotation-id='${annotation._id}']`).get(0))
.prepend("<span class='annotation-beginning-marker'></span>");
$($(`#document-content-container span[annotation-id='${annotation._id}']`).get(-1))
.append("<span class='annotation-ending-marker'></span>");
// so now that we have added the beginning marker we are going to get
// the position of the begginning marker then remove it from the dom
const annotationBeginning = $(
`#document-content-container span[annotation-id='${annotation._id}'] .annotation-beginning-marker`,
);
const annotationBeginning = $(`#document-content-container span[annotation-id='${annotation._id}'] .annotation-beginning-marker`);
const annotationEnding = $(`#document-content-container span[annotation-id='${annotation._id}'] .annotation-ending-marker`);
if (annotationBeginning.get(0) === undefined) {
setAlerts([...alerts, { text: 'unable to annotate a piece of text', variant: 'danger' }]);
} else {
const annotationBeginningPosition = annotationBeginning.offset();
const annotationEndingPosition = annotationEnding.offset();
// this takes into account if the user was scrolling through the document
// as the it was being populated with annotations
annotationBeginningPosition.top += $('#document-container').scrollTop();
Expand All @@ -214,6 +216,7 @@ export default class Document extends React.Component {
{
left: annotationBeginningPosition.left,
top: annotationBeginningPosition.top,
height: (annotationEndingPosition.top - annotationBeginningPosition.top) + 18,
},
...annotation,
},
Expand All @@ -225,6 +228,7 @@ export default class Document extends React.Component {
{
left: annotationBeginningPosition.left,
top: annotationBeginningPosition.top,
height: (annotationEndingPosition.top - annotationBeginningPosition.top) + 18,
},
...annotation,
},
Expand Down
89 changes: 89 additions & 0 deletions src/components/HeatMap/HeatMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* eslint-disable no-underscore-dangle */
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
import React, { useState, useContext, useEffect } from 'react';
import $ from 'jquery';
import {
Nav,
Row,
Col,
Navbar,
Breadcrumb,
Container,
Button,
OverlayTrigger,
Popover,
Form,
Card,
ButtonGroup,
Badge,
Spinner,
} from 'react-bootstrap';

import DocumentFiltersContext from '../../contexts/DocumentFiltersContext';
import DocumentAnnotationsContext from '../../contexts/DocumentAnnotationsContext';


function HeatMap() {
const lineHeight = 18;
const scaleFactor = $('#document-container').height() / $('#document-card-container').height();
const minStrokeHeight = 1;
const offsetTop = $('#document-container').offset() === undefined ? 0 : $('#document-container').offset().top;
const grandularity = lineHeight * scaleFactor >= minStrokeHeight ? lineHeight : Math.ceil(minStrokeHeight / scaleFactor);
const [channelAnnotations] = useContext(DocumentAnnotationsContext);
const [documentFilters] = useContext(DocumentFiltersContext);
const n = (Math.ceil($('#document-card-container').height() / grandularity));
const map = new Array(isNaN(n) ? 0 : n);

for (const side in channelAnnotations) {
if (channelAnnotations[side] !== null) {
for (const anno of channelAnnotations[side]) {
if (documentFilters.annotationIds[side] === null || documentFilters.annotationIds[side].includes(anno._id)) {
if (anno.position.height !== undefined) {
// now we have to convert the annotations position and height into starting and ending indexs for the map
let startIndex = Math.floor((anno.position.top - offsetTop) / grandularity);
startIndex = startIndex < 0 ? 0 : startIndex;
const endIndex = Math.floor((anno.position.top + anno.position.height - offsetTop) / grandularity);
for (let i = startIndex; i <= endIndex; i += 1) {
if (map[i] === undefined) {
map[i] = 0;
}
map[i] += 1;
}
}
}
}
}
}


return (
<>
<div id="heat-map" style={{ height: (map.length * lineHeight * scaleFactor) + 10 }}>
{map.map((v, i) => <div className="stroke" style={{ height: lineHeight * scaleFactor, top: i * lineHeight * scaleFactor, opacity: v * 0.2 }} />)}
</div>

<style jsx global>
{`
#heat-map {
margin-top: -8px;
background: #007bff;
position: absolute;
right: 3px;
width: 8px;
}
#heat-map .stroke {
position: absolute;
background: rgb(255, 255, 10);
width: 100%;
}
`}
</style>
</>
);
}

export default HeatMap;
3 changes: 3 additions & 0 deletions src/components/HeatMap/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import HeatMap from './HeatMap';

export default HeatMap;
13 changes: 9 additions & 4 deletions src/components/SecondNavbar/SecondNavbar.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
Nav, Row, Col, Navbar, Breadcrumb, Container, Button, OverlayTrigger, Popover, Form, Card, ButtonGroup,
Nav, Row, Col, Navbar, Breadcrumb, Container,
} from 'react-bootstrap';
import {
InfoSquare, Filter, ShieldFillCheck, ChatLeftQuoteFill, PeopleFill, BookmarkFill, CalendarEventFill,
InfoSquare,
} from 'react-bootstrap-icons';

import FilterPopover from '../FilterPopover';
Expand All @@ -20,10 +20,10 @@ const SecondNavbar = ({
<Col sm={8}>
<Nav>
<Breadcrumb>
<Breadcrumb.Item active={type === 'dashboard'} href="/">Home</Breadcrumb.Item>
<Breadcrumb.Item active={type === 'dashboard'} href="/">Dashboard</Breadcrumb.Item>
{type === 'document' && (
<Breadcrumb.Item href="/documents" active={!title}>
Library
Documents
</Breadcrumb.Item>
)}
{type === 'group' && (
Expand All @@ -46,6 +46,11 @@ const SecondNavbar = ({
Registration
</Breadcrumb.Item>
)}
{type === 'annotations' && (
<Breadcrumb.Item active>
Annotations
</Breadcrumb.Item>
)}
{title && (
<Breadcrumb.Item active>{title}</Breadcrumb.Item>
)}
Expand Down
Loading

0 comments on commit 35754ab

Please sign in to comment.