|
1 |
| -import { uniq } from 'lodash'; |
| 1 | +import { uniq, groupBy } from 'lodash'; |
2 | 2 | import React, {
|
3 | 3 | useCallback,
|
4 | 4 | useEffect,
|
@@ -40,7 +40,7 @@ import { Tooltip } from '@veupathdb/coreui';
|
40 | 40 | import { safeHtml } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
|
41 | 41 | // import ShowHideVariableContext
|
42 | 42 | import { VariableDescriptor } from '../../types/variable';
|
43 |
| -import { VariableScope } from '../../types/study'; |
| 43 | +import { FieldWithMetadata, VariableScope } from '../../types/study'; |
44 | 44 | import { ShowHideVariableContext } from '../../utils/show-hide-variable-context';
|
45 | 45 |
|
46 | 46 | import { pruneEmptyFields } from '../../utils/wdk-filter-param-adapter';
|
@@ -192,7 +192,7 @@ interface VariableListProps {
|
192 | 192 | toggleStarredVariable: (targetVariableId: VariableDescriptor) => void;
|
193 | 193 | disabledFieldIds?: string[];
|
194 | 194 | customDisabledVariableMessage?: string;
|
195 |
| - featuredFields: VariableField[]; |
| 195 | + featuredFields: FieldWithMetadata[]; |
196 | 196 | showMultiFilterDescendants: boolean;
|
197 | 197 | // Entities in which single child nodes should be promoted
|
198 | 198 | // (replacing their parent in the tree)
|
@@ -221,7 +221,7 @@ export default function VariableList({
|
221 | 221 | disabledFieldIds,
|
222 | 222 | valuesMap,
|
223 | 223 | fieldTree,
|
224 |
| - featuredFields = [], |
| 224 | + featuredFields, |
225 | 225 | autoFocus,
|
226 | 226 | starredVariables,
|
227 | 227 | toggleStarredVariable,
|
@@ -645,7 +645,14 @@ export default function VariableList({
|
645 | 645 | * Render featured fields panel, if data supports it.
|
646 | 646 | */
|
647 | 647 | const renderFeaturedFields = () => {
|
648 |
| - return featuredFields.length && allowedFeaturedFields.length ? ( |
| 648 | + if (!(featuredFields.length && allowedFeaturedFields.length)) return null; |
| 649 | + |
| 650 | + const groupedFeaturedFields = groupBy( |
| 651 | + allowedFeaturedFields, |
| 652 | + (field) => field.entityName |
| 653 | + ); |
| 654 | + |
| 655 | + return ( |
649 | 656 | <div
|
650 | 657 | style={{
|
651 | 658 | padding: '0.5em 1em',
|
@@ -676,97 +683,120 @@ export default function VariableList({
|
676 | 683 | padding: '0.25em',
|
677 | 684 | margin: 0,
|
678 | 685 | color: '#222',
|
679 |
| - fontWeight: 500, |
| 686 | + fontWeight: 'bold', |
680 | 687 | }}
|
681 | 688 | >
|
682 | 689 | Featured variables
|
683 | 690 | </h3>
|
684 | 691 | </summary>
|
685 |
| - <ul |
686 |
| - style={{ |
687 |
| - listStyle: 'none', |
688 |
| - margin: 0, |
689 |
| - marginTop: '0.25em', |
690 |
| - padding: 0, |
691 |
| - }} |
692 |
| - > |
693 |
| - {allowedFeaturedFields.map((field) => { |
694 |
| - const isActive = field.term === activeField?.term; |
695 |
| - const isDisabled = disabledFields.has(field.term); |
696 |
| - const [entityId, variableId] = field.term.split('/'); |
697 |
| - const CustomCheckbox = |
698 |
| - customCheckboxes && field.term in customCheckboxes |
699 |
| - ? customCheckboxes[field.term] |
700 |
| - : undefined; |
701 |
| - const checked = selectedFields.some((f) => f.term === field.term); |
702 |
| - const onChange = (node: any, checked: boolean) => { |
703 |
| - if (onSelectedFieldsChange == null) return; |
704 |
| - const nextSelectedFields = ( |
705 |
| - checked |
706 |
| - ? selectedFields.concat(field) |
707 |
| - : selectedFields.filter((f) => f.term !== field.term) |
708 |
| - ).map((field) => field.term); |
709 |
| - onSelectedFieldsChange(nextSelectedFields); |
710 |
| - }; |
711 |
| - |
712 |
| - return ( |
713 |
| - <li |
714 |
| - key={field.term} |
715 |
| - style={{ |
716 |
| - lineHeight: '15px', |
717 |
| - }} |
718 |
| - > |
719 |
| - <div |
| 692 | + <div style={{ height: '0.5em' }} /> |
| 693 | + <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}> |
| 694 | + {Object.entries(groupedFeaturedFields).map( |
| 695 | + ([entityName, fields]) => ( |
| 696 | + <li> |
| 697 | + <h4 style={{ fontSize: '1.05em', padding: 0 }}> |
| 698 | + {entityName} |
| 699 | + </h4> |
| 700 | + <ul |
720 | 701 | style={{
|
721 |
| - position: 'relative', |
722 |
| - display: 'flex', |
723 |
| - alignItems: 'center', |
724 |
| - marginLeft: '1em', |
725 |
| - padding: scope === 'download' ? '0.2em 0' : undefined, |
| 702 | + listStyle: 'none', |
| 703 | + margin: 0, |
| 704 | + marginTop: '0.25em', |
| 705 | + padding: 0, |
726 | 706 | }}
|
727 | 707 | >
|
728 |
| - {isMultiPick && |
729 |
| - (CustomCheckbox ? ( |
730 |
| - <CustomCheckbox |
731 |
| - checked={checked} |
732 |
| - onChange={() => onChange(null, checked)} |
733 |
| - /> |
734 |
| - ) : ( |
735 |
| - <input |
736 |
| - type="checkbox" |
737 |
| - checked={checked} |
738 |
| - onChange={(e) => onChange(null, e.target.checked)} |
739 |
| - /> |
740 |
| - ))} |
741 |
| - <FieldNode |
742 |
| - isMultiPick={isMultiPick} |
743 |
| - isMultiFilterDescendant={false} |
744 |
| - showMultiFilterDescendants={showMultiFilterDescendants} |
745 |
| - field={field} |
746 |
| - isActive={isActive} |
747 |
| - isDisabled={isDisabled} |
748 |
| - customDisabledVariableMessage={ |
749 |
| - customDisabledVariableMessage |
750 |
| - } |
751 |
| - searchTerm="" |
752 |
| - variableLinkConfig={variableLinkConfig} |
753 |
| - isStarred={starredVariableTermsSet.has(field.term)} |
754 |
| - starredVariablesLoading={starredVariablesLoading} |
755 |
| - onClickStar={() => |
756 |
| - toggleStarredVariable({ entityId, variableId }) |
757 |
| - } |
758 |
| - scrollIntoView={false} |
759 |
| - asDropdown={asDropdown} |
760 |
| - isFeaturedField={true} |
761 |
| - /> |
762 |
| - </div> |
| 708 | + {fields.map((field) => { |
| 709 | + const isActive = field.term === activeField?.term; |
| 710 | + const isDisabled = disabledFields.has(field.term); |
| 711 | + const [entityId, variableId] = field.term.split('/'); |
| 712 | + const CustomCheckbox = |
| 713 | + customCheckboxes && field.term in customCheckboxes |
| 714 | + ? customCheckboxes[field.term] |
| 715 | + : undefined; |
| 716 | + const checked = selectedFields.some( |
| 717 | + (f) => f.term === field.term |
| 718 | + ); |
| 719 | + const onChange = (_node: any, checked: boolean) => { |
| 720 | + if (onSelectedFieldsChange == null) return; |
| 721 | + const nextSelectedFields = ( |
| 722 | + checked |
| 723 | + ? selectedFields.concat(field) |
| 724 | + : selectedFields.filter( |
| 725 | + (f) => f.term !== field.term |
| 726 | + ) |
| 727 | + ).map((field) => field.term); |
| 728 | + onSelectedFieldsChange(nextSelectedFields); |
| 729 | + }; |
| 730 | + |
| 731 | + return ( |
| 732 | + <li |
| 733 | + key={field.term} |
| 734 | + style={{ |
| 735 | + lineHeight: '15px', |
| 736 | + }} |
| 737 | + > |
| 738 | + <div |
| 739 | + style={{ |
| 740 | + position: 'relative', |
| 741 | + display: 'flex', |
| 742 | + alignItems: 'center', |
| 743 | + marginLeft: '0.25em', |
| 744 | + padding: |
| 745 | + scope === 'download' ? '0.2em 0' : undefined, |
| 746 | + }} |
| 747 | + > |
| 748 | + {isMultiPick && |
| 749 | + (CustomCheckbox ? ( |
| 750 | + <CustomCheckbox |
| 751 | + checked={checked} |
| 752 | + onChange={() => onChange(null, checked)} |
| 753 | + /> |
| 754 | + ) : ( |
| 755 | + <input |
| 756 | + type="checkbox" |
| 757 | + checked={checked} |
| 758 | + onChange={(e) => |
| 759 | + onChange(null, e.target.checked) |
| 760 | + } |
| 761 | + /> |
| 762 | + ))} |
| 763 | + <FieldNode |
| 764 | + isMultiPick={isMultiPick} |
| 765 | + isMultiFilterDescendant={false} |
| 766 | + showMultiFilterDescendants={ |
| 767 | + showMultiFilterDescendants |
| 768 | + } |
| 769 | + field={field} |
| 770 | + isActive={isActive} |
| 771 | + isDisabled={isDisabled} |
| 772 | + customDisabledVariableMessage={ |
| 773 | + customDisabledVariableMessage |
| 774 | + } |
| 775 | + searchTerm="" |
| 776 | + variableLinkConfig={variableLinkConfig} |
| 777 | + isStarred={starredVariableTermsSet.has( |
| 778 | + field.term |
| 779 | + )} |
| 780 | + starredVariablesLoading={starredVariablesLoading} |
| 781 | + onClickStar={() => |
| 782 | + toggleStarredVariable({ entityId, variableId }) |
| 783 | + } |
| 784 | + scrollIntoView={false} |
| 785 | + asDropdown={asDropdown} |
| 786 | + isFeaturedField={true} |
| 787 | + /> |
| 788 | + </div> |
| 789 | + </li> |
| 790 | + ); |
| 791 | + })} |
| 792 | + </ul> |
763 | 793 | </li>
|
764 |
| - ); |
765 |
| - })} |
| 794 | + ) |
| 795 | + )} |
766 | 796 | </ul>
|
767 | 797 | </details>
|
768 | 798 | </div>
|
769 |
| - ) : null; |
| 799 | + ); |
770 | 800 | };
|
771 | 801 |
|
772 | 802 | const sharedProps = {
|
|
0 commit comments