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

Fix/metadata viewer tweaks #178

Merged
merged 9 commits into from
Dec 14, 2023
73 changes: 35 additions & 38 deletions src/aics-image-viewer/components/MetadataViewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,75 +3,72 @@ import React from "react";
import { MetadataEntry, MetadataRecord } from "../../shared/types";
import "./styles.css";

interface MetadataTableProps {
type MetadataTableProps = {
metadata: MetadataRecord;
// Track whether a category title will abut another category title below when in the collapsed state.
// If so, this category title should not render a bottom border when collapsed, otherwise the border will double
// up with the top border of the lower category and create the appearance of a single thick, uneven border.
// (there is not a way to prevent this with pure CSS to my knowledge)
categoryFollows: boolean;
}
topLevel?: boolean;
};

interface CollapsibleCategoryProps extends MetadataTableProps {
type CollapsibleCategoryProps = {
metadata: MetadataRecord;
title: string;
}
};

const isCategory = (entry: MetadataEntry): entry is MetadataRecord => typeof entry === "object" && entry !== null;

const sortCategoriesFirst = (entry: MetadataEntry): MetadataEntry => {
if (!isCategory(entry) || Array.isArray(entry)) {
return entry;
}

const isCategory = (val: MetadataEntry): val is MetadataRecord => typeof val === "object" && val !== null;
const cats: MetadataRecord = {};
Copy link
Contributor

Choose a reason for hiding this comment

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

🐈

const vals: MetadataRecord = {};
for (const key in entry) {
if (isCategory(entry[key])) {
cats[key] = entry[key];
} else {
vals[key] = entry[key];
}
}

return { ...cats, ...vals };
};

/** Component to hold collapse state */
const MetadataCollapsibleCategory: React.FC<CollapsibleCategoryProps> = ({ metadata, title, categoryFollows }) => {
const MetadataCategory: React.FC<CollapsibleCategoryProps> = ({ metadata, title }) => {
const [collapsed, setCollapsed] = React.useState(true);
const toggleCollapsed = (): void => setCollapsed(!collapsed);
const collapsedClass = collapsed ? " metadata-collapse-collapsed" : "";

return (
<>
<tr
className={
"metadata-collapse-title" + (categoryFollows && collapsed ? " metadata-collapse-no-bottom-border" : "")
}
onClick={toggleCollapsed}
>
<tr className={"metadata-row-collapse-title" + collapsedClass} onClick={() => setCollapsed(!collapsed)}>
<td colSpan={2}>
<span className="metadata-collapse-caret">
<Icon type="right" style={{ transform: `rotate(${collapsed ? 0 : 90}deg)` }} />
</span>
{title}
</td>
</tr>
<tr className={"metadata-collapse-content-row" + (collapsed ? " metadata-collapse-collapsed" : "")}>
<tr className={"metadata-row-collapse-content" + collapsedClass}>
<td className="metadata-collapse-content" colSpan={2}>
<MetadataTable metadata={metadata} categoryFollows={categoryFollows} />
<MetadataTable metadata={metadata} />
</td>
</tr>
</>
);
};

const MetadataTable: React.FC<MetadataTableProps> = ({ metadata, categoryFollows }) => {
const MetadataTable: React.FC<MetadataTableProps> = ({ metadata, topLevel }) => {
const metadataKeys = Object.keys(metadata);
const metadataIsArray = Array.isArray(metadata);

return (
<table className="viewer-metadata-table">
<table className={"viewer-metadata-table" + (topLevel ? " metadata-top-level" : "")}>
<tbody>
{metadataKeys.map((key, idx) => {
const metadataValue = metadataIsArray ? metadata[idx] : metadata[key];
const metadataValue = sortCategoriesFirst(metadataIsArray ? metadata[idx] : metadata[key]);

if (isCategory(metadataValue)) {
// Determine whether this category is followed by another category, ignoring data hierarchy:
// - If this is the last element in the table, this category has another category below if the table does.
// - Otherwise, just check if the next element in this table is a category.
const nextItem = metadataIsArray ? metadata[idx + 1] : metadata[metadataKeys[idx + 1]];
const categoryBelow = idx + 1 >= metadataKeys.length ? isCategory(nextItem) : categoryFollows;

return (
<MetadataCollapsibleCategory
key={key}
metadata={metadataValue}
title={key}
categoryFollows={categoryBelow}
/>
);
return <MetadataCategory key={key} metadata={metadataValue} title={key} />;
} else {
return (
<tr key={key}>
Expand All @@ -87,7 +84,7 @@ const MetadataTable: React.FC<MetadataTableProps> = ({ metadata, categoryFollows
};

const MetadataViewer: React.FC<{ metadata: MetadataRecord }> = ({ metadata }) => (
<MetadataTable metadata={metadata} categoryFollows={false} />
<MetadataTable metadata={metadata} topLevel={true} />
);

export default MetadataViewer;
56 changes: 32 additions & 24 deletions src/aics-image-viewer/components/MetadataViewer/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,47 @@
padding: 10px;
}

/* Table rows */
tr {
&:nth-child(odd) {
background-color: #3a3a3a;
/* On the top nest level, the top border is given by the panel header, and the bottom by the containing table */
&.metadata-top-level {
border-bottom: 1px solid #6e6e6e;

> tbody > tr.metadata-row-collapse-title:first-child {
border-top: none;
}
}

&.metadata-collapse-collapsed {
visibility: collapse;
height: 0px;
display: none;
/* ----- TABLE ROWS ----- */
tr {
&:nth-child(odd):not(.metadata-row-collapse-content) {
background-color: #3a3a3a;
}

&.metadata-collapse-title {
/* CATEGORY TITLE */
&.metadata-row-collapse-title {
color: white;
background-color: #4b4b4b;
&:not(.metadata-collapse-no-bottom-border) {
border-bottom: 1px solid #6e6e6e;
}
&.metadata-collapse-no-bottom-border > td {
background-color: #4b4b4b !important;

/* Keeps borders aligned when this category opens and closes */
&:not(.metadata-collapse-collapsed) > td {
padding-bottom: 5px;
}
&:not(:first-child) {
border-top: 1px solid #6e6e6e;
}
}

&.metadata-collapse-content-row {
background-color: #313131;
/* CATEGORY CONTENT */
&.metadata-row-collapse-content.metadata-collapse-collapsed {
visibility: collapse;
height: 0px;
display: none;
}

/* ROW BORDERS: all categories have top borders; any row which follows a category provides its bottom border */
tr.metadata-row-collapse-title,
&.metadata-row-collapse-content + tr {
border-top: 1px solid #6e6e6e;
}
}

/* Table cells */
/* ----- TABLE CELLS ----- */
td {
padding: 6px 0;
overflow-wrap: break-word;
Expand All @@ -49,7 +58,7 @@
}

&.metadata-key {
padding-left: 24px;
padding-left: 16px;
}

&.metadata-value {
Expand All @@ -59,13 +68,12 @@
}

&:last-child {
padding-right: 16px;
padding-right: 8px;
}

&.metadata-collapse-content {
padding: 0;
padding-left: 16px;
border-bottom: none;
padding-left: 24px;
}

i {
Expand Down
Loading