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

Simplify timelines UI/reuse UI elements #2629

Merged
merged 49 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
b514517
Strip down the timelines screen to minimum
tillprochaska Oct 19, 2022
3648259
Improve color contrast of secondary/muted text
tillprochaska Oct 19, 2022
5d35dda
Implement basic timelines list view
tillprochaska Oct 19, 2022
26e6918
Render descriptive caption for edge entities
tillprochaska Oct 19, 2022
e297833
Use temporal extent to render start/end dates
tillprochaska Oct 20, 2022
a6ca505
Allow selecting timeline items
tillprochaska Oct 24, 2022
376300f
Display schema and property data for selected timeline items
tillprochaska Jan 9, 2023
e50ab1b
Temporarily rename entity viewer to resolve naming clash
tillprochaska Oct 28, 2022
3f69a56
Make timeline list and entity scrollable independently
tillprochaska Oct 28, 2022
8fd8217
Handle updates to timeline state
tillprochaska Jan 9, 2023
d636196
Allow users to add properties
tillprochaska Oct 31, 2022
36c2642
Allow users to create new items in timelines
tillprochaska Jan 9, 2023
d548c96
Emphasize timeline item color
tillprochaska Oct 31, 2022
c5d0770
Allow toggling editing of timeline items
tillprochaska Oct 31, 2022
4cbdd68
Show placeholder/date format hint
tillprochaska Nov 4, 2022
3ac5a93
Handle timelines without layout
tillprochaska Nov 7, 2022
f370285
Fix jerky scrollbar in entity select autocomplete
tillprochaska Nov 7, 2022
e33f4c2
Allow removing entities from timelines
tillprochaska Nov 8, 2022
e4b0fbd
Hook up timeline component to Redux store
tillprochaska Nov 11, 2022
d06822c
Improve keyboard navigability
tillprochaska Jan 9, 2023
f9d876a
Show loading indicator and disable submit while new entity is being s…
tillprochaska Nov 11, 2022
fd6e3cb
Display timeline items as table
tillprochaska Jan 15, 2023
cb74955
Extract shared types to remove duplication
tillprochaska Dec 21, 2022
9187c33
Simplify timeline item creation state
tillprochaska Dec 21, 2022
1916710
Unselect timeline item when pressing escape
tillprochaska Dec 21, 2022
b24b850
Do not reset properties when changing schema
tillprochaska Dec 21, 2022
29822f5
Add timeline empty state
tillprochaska Jan 13, 2023
10ffaec
Only show end date column if there's at least one item with an end date
tillprochaska Dec 22, 2022
2331d1e
Add utility class to handle FtM’s imprecise dates, e.g. `2022` or `20…
tillprochaska Dec 29, 2022
26fe059
Refactor: Introduce `TimelineItem`, a wrapper class for entities
tillprochaska Dec 29, 2022
59e5cd2
Refactor: Extract keyboard navigation logic into custom hook
tillprochaska Dec 28, 2022
caf3d0b
Fix missing hook dependency
tillprochaska Dec 28, 2022
79d3967
Add basic chart renderer for timelines
tillprochaska Dec 29, 2022
533733c
Move timeline renderers into subdirectories
tillprochaska Dec 29, 2022
1aed501
Make timeline item captions sticky
tillprochaska Dec 29, 2022
412e6bc
Unselect timeline items when clicking outside
tillprochaska Jan 9, 2023
4023889
Add popover when hovering timeline chart items
tillprochaska Jan 3, 2023
cd882d6
Ensure clicking on an item in timeline charts always focuses the item
tillprochaska Jan 6, 2023
4328b32
Extract logic to fetch entity suggestions into custom hook
tillprochaska Jan 9, 2023
9aa0304
Allow editing entity-type properties
tillprochaska Jan 9, 2023
7361f7b
Wire up update status indicator with actual timeline state
tillprochaska Jan 9, 2023
79b5c44
Refactor: Reduce use of optional props
tillprochaska Jan 9, 2023
bbedac2
Use full entity ID when creating new timeline items
tillprochaska Jan 9, 2023
5e6bb5d
Add read-only mode
tillprochaska Jan 11, 2023
87e9156
Translate UI copy
tillprochaska Jan 13, 2023
5409e53
Always display temporal properties in entity viewer, even if they are…
tillprochaska Jan 13, 2023
64f4b11
Add tooltip to remove button
tillprochaska Jan 13, 2023
f59e1df
Replace old Timeline component
tillprochaska Jan 29, 2023
3a45add
Hide `incorporationDate`/`dissolutionDate` properties for `Person` sc…
tillprochaska Jan 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions ui/package-lock.json

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

2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"d3-force": "^3.0.0",
"d3-scale": "^4.0.2",
"dagre": "^0.8.5",
"date-fns": "^2.29.3",
"google-libphonenumber": "^3.2.31",
"http-proxy-middleware": "^2.0.0",
"js-file-download": "^0.4.9",
Expand Down Expand Up @@ -108,6 +109,7 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^14.4.3",
"@types/d3-force": "^3.0.4",
"@types/d3-scale": "^4.0.3",
"@types/dagre": "^0.7.47",
Expand Down
1 change: 1 addition & 0 deletions ui/src/app/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@import './layouts.scss';
@import './blueprint-overrides.scss';
@import 'app/mixins.scss';
@import 'app/utils.scss';

html {
height: 100%;
Expand Down
11 changes: 11 additions & 0 deletions ui/src/app/utils.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is px is right here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a utility class to hide elements visually while still keeping it accessible to screen readers: https://css-tricks.com/inclusively-hidden/

white-space: nowrap;
}
2 changes: 1 addition & 1 deletion ui/src/app/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ $aleph-table-border-color: $light-gray2;
$aleph-content-background: white;
$aleph-border-color: $light-gray2;

$aleph-greyed-text: $gray3;
$aleph-greyed-text: $pt-text-color-muted;
$aleph-theme-intent: $aleph-link-color;
$aleph-profile-dark: $gold2;
$aleph-profile-light: $gold5;
Expand Down
3 changes: 3 additions & 0 deletions ui/src/components/Document/DocumentDropzone.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
.DocumentDropzone {
flex-grow: 1;
&__content {
display: flex;
flex-direction: column;
flex-grow: 1;
min-height: 100%;
&.active {
position: relative;
Expand Down
25 changes: 25 additions & 0 deletions ui/src/components/Timeline/EntityViewer2.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@import 'app/variables.scss';

.EntityViewer2 {
display: grid;
gap: 2 * $aleph-grid-size;
padding-top: 5 * $aleph-grid-size;
}

.EntityViewer2__header {
text-align: center;
}

.EntityViewer2__caption {
margin-top: 0.5 * $aleph-grid-size;
margin-bottom: 0;
}

.EntityViewer2__schema {
color: $aleph-greyed-text;
}

.EntityViewer2__color {
width: 75%;
justify-self: center;
}
46 changes: 46 additions & 0 deletions ui/src/components/Timeline/EntityViewer2.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { render, screen } from 'testUtils';
import { Entity, Model, defaultModel } from '@alephdata/followthemoney';
import EntityViewer2 from './EntityViewer2';

const defaultProps = {
fetchEntitySuggestions: async () => [],
writeable: true,
onVertexChange: () => {},
onEntityChange: () => {},
};

const model = new Model(defaultModel);
let entity: Entity;

beforeEach(() => {
entity = model.getEntity({
id: '1',
schema: 'Company',
properties: {
name: ['ACME, Inc.'],
},
});
});

it('renders schema name and entity caption', () => {
const vertex = { entityId: '1' };
render(<EntityViewer2 {...defaultProps} entity={entity} vertex={vertex} />);
expect(screen.getByText('Company')).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'ACME, Inc.' }));
});

it('renders a color picker', () => {
const vertex = { entityId: '1', color: 'green' };
render(<EntityViewer2 {...defaultProps} entity={entity} vertex={vertex} />);
const picker = document.querySelector(
'.ColorPicker [style*="background-color: green"]'
);
expect(picker).toBeInTheDocument();
});

it('renders properties', () => {
const vertex = { entityId: '1' };
render(<EntityViewer2 {...defaultProps} entity={entity} vertex={vertex} />);
expect(screen.getByText('Name')).toBeInTheDocument();
expect(screen.getByText('Incorporation date')).toBeInTheDocument();
});
56 changes: 56 additions & 0 deletions ui/src/components/Timeline/EntityViewer2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { FC } from 'react';
import { Entity } from '@alephdata/followthemoney';
import { Schema } from 'src/react-ftm/types';
import { DEFAULT_COLOR } from './Timeline';
import type { Vertex, FetchEntitySuggestions } from './types';
import EntityViewerProperties from './EntityViewerProperties';
import { ColorPicker } from 'src/react-ftm';

import './EntityViewer2.scss';

type EntityViewer2Props = {
entity: Entity;
vertex: Vertex;
fetchEntitySuggestions: FetchEntitySuggestions;
writeable?: boolean;
onVertexChange: (vertex: Vertex) => void;
onEntityChange: (entity: Entity) => void;
};

const EntityViewer2: FC<EntityViewer2Props> = ({
entity,
vertex,
fetchEntitySuggestions,
writeable,
onVertexChange,
onEntityChange,
}) => {
const currentColor = vertex?.color || DEFAULT_COLOR;

return (
<article className="EntityViewer2">
<header className="EntityViewer2__header">
<div className="EntityViewer2__schema">
<Schema.Label schema={entity.schema} icon />
</div>
<h2 className="EntityViewer2__caption">{entity.getCaption()}</h2>
</header>
<div className="EntityViewer2__color">
{writeable && (
<ColorPicker
currSelected={currentColor}
onSelect={(color) => onVertexChange({ ...vertex, color })}
/>
)}
</div>
<EntityViewerProperties
entity={entity}
fetchEntitySuggestions={fetchEntitySuggestions}
writeable={writeable}
onChange={(entity) => onEntityChange(entity)}
/>
</article>
);
};

export default EntityViewer2;
71 changes: 71 additions & 0 deletions ui/src/components/Timeline/EntityViewerProperties.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { render, screen } from 'testUtils';
import userEvent from '@testing-library/user-event';
import { Entity, Model, defaultModel } from '@alephdata/followthemoney';
import EntityViewerProperties from './EntityViewerProperties';

const defaultProps = {
fetchEntitySuggestions: async () => [],
writeable: true,
onChange: () => {},
};

const model = new Model(defaultModel);
let entity: Entity;

beforeEach(() => {
entity = model.getEntity({
id: '1',
schema: 'Company',
});
});

it('renders featured, temporal, and non-empty properties', () => {
const { rerender } = render(
<EntityViewerProperties {...defaultProps} entity={entity} />
);

// Featured
expect(screen.getByText('Name')).toBeInTheDocument();
expect(screen.getByText('Jurisdiction')).toBeInTheDocument();
expect(screen.getByText('Registration number')).toBeInTheDocument();

// Temporal
expect(screen.getByText('Incorporation date')).toBeInTheDocument();
expect(screen.getByText('Dissolution date')).toBeInTheDocument();

// Non empty
entity.setProperty('wikidataId', 'Q7102061');
rerender(<EntityViewerProperties {...defaultProps} entity={entity} />);
expect(screen.getByText('Wikidata ID')).toBeInTheDocument();
});

it('can add any other editable property', async () => {
render(<EntityViewerProperties {...defaultProps} entity={entity} />);

// There is no editor for the Wikidata ID
expect(screen.queryByText('Wikidata ID')).toBeNull();

await userEvent.click(screen.getByRole('button', { name: 'Add a property' }));
await userEvent.click(screen.getByRole('menuitem', { name: 'Wikidata ID' }));

// Now there is an editor for the dissolution date
expect(screen.getByText('Wikidata ID').matches('.EditableProperty *')).toBe(
true
);

// Can't add a property twice
await userEvent.click(screen.getByRole('button', { name: 'Add a property' }));
expect(screen.queryByRole('menuitem', { name: 'Wikidata ID' })).toBeNull();
});

it('cannot add properties if not writeable', () => {
render(
<EntityViewerProperties
{...defaultProps}
writeable={false}
entity={entity}
/>
);

expect(screen.queryByRole('button', { name: 'Add a property' })).toBeNull();
});
Loading