generic.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import * as React from 'react';
  8. import { StructureHierarchyRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
  9. import { PluginCommands } from '../../mol-plugin/commands';
  10. import { State } from '../../mol-state';
  11. import { PurePluginUIComponent } from '../base';
  12. import { IconButton } from '../controls/common';
  13. import { UpdateTransformControl } from '../state/update-transform';
  14. import { VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, MoreHorizSvg } from '../controls/icons';
  15. export class GenericEntryListControls extends PurePluginUIComponent {
  16. get current() {
  17. return this.plugin.managers.structure.hierarchy.behaviors.selection;
  18. }
  19. componentDidMount() {
  20. this.subscribe(this.current, () => this.forceUpdate());
  21. }
  22. get unitcell() {
  23. const { selection } = this.plugin.managers.structure.hierarchy;
  24. if (selection.structures.length === 0) return null;
  25. const refs = [];
  26. for (const s of selection.structures) {
  27. const model = s.model;
  28. if (model?.unitcell && model.unitcell?.cell.obj) refs.push(model.unitcell);
  29. }
  30. if (refs.length === 0) return null;
  31. return <GenericEntry refs={refs} labelMultiple='Unit Cells' />;
  32. }
  33. get customControls(): JSX.Element[] | null {
  34. const controls: JSX.Element[] = [];
  35. this.plugin.genericRepresentationControls.forEach((provider, key) => {
  36. const [refs, labelMultiple] = provider(this.plugin.managers.structure.hierarchy.selection);
  37. if (refs.length > 0) {
  38. controls.push(<div key={key}>
  39. <GenericEntry refs={refs} labelMultiple={labelMultiple} />
  40. </div>);
  41. }
  42. });
  43. return controls.length > 0 ? controls : null;
  44. }
  45. render() {
  46. return <>
  47. <div style={{ marginTop: '6px' }}>
  48. {this.unitcell}
  49. {this.customControls}
  50. </div>
  51. </>;
  52. }
  53. }
  54. export class GenericEntry<T extends StructureHierarchyRef> extends PurePluginUIComponent<{ refs: T[], labelMultiple?: string }, { showOptions: boolean }> {
  55. state = { showOptions: false }
  56. componentDidMount() {
  57. this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
  58. if (State.ObjectEvent.isCell(e, this.pivot?.cell)) this.forceUpdate();
  59. });
  60. }
  61. get pivot() { return this.props.refs[0]; }
  62. toggleVisibility = (e: React.MouseEvent<HTMLElement>) => {
  63. e.preventDefault();
  64. this.plugin.managers.structure.hierarchy.toggleVisibility(this.props.refs);
  65. e.currentTarget.blur();
  66. }
  67. highlight = (e: React.MouseEvent<HTMLElement>) => {
  68. e.preventDefault();
  69. if (!this.pivot.cell.parent) return;
  70. PluginCommands.Interactivity.Object.Highlight(this.plugin, {
  71. state: this.pivot.cell.parent,
  72. ref: this.props.refs.map(c => c.cell.transform.ref)
  73. });
  74. }
  75. clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
  76. e.preventDefault();
  77. PluginCommands.Interactivity.ClearHighlights(this.plugin);
  78. }
  79. focus = (e: React.MouseEvent<HTMLElement>) => {
  80. e.preventDefault();
  81. let allHidden = true;
  82. for (const uc of this.props.refs) {
  83. if (!uc.cell.state.isHidden) {
  84. allHidden = false;
  85. break;
  86. }
  87. }
  88. if (allHidden) {
  89. this.plugin.managers.structure.hierarchy.toggleVisibility(this.props.refs, 'show');
  90. }
  91. const loci = [];
  92. for (const uc of this.props.refs) {
  93. if (uc.cell.state.isHidden) {
  94. continue;
  95. }
  96. const l = uc.cell.obj?.data.repr.getLoci();
  97. if (l) loci.push(l);
  98. }
  99. this.plugin.managers.camera.focusLoci(loci);
  100. }
  101. toggleOptions = () => this.setState({ showOptions: !this.state.showOptions })
  102. render() {
  103. const { refs, labelMultiple } = this.props;
  104. if (refs.length === 0) return null;
  105. const pivot = refs[0];
  106. let label, description;
  107. if (refs.length === 1) {
  108. const { obj } = pivot.cell;
  109. if (!obj) return null;
  110. label = obj?.label;
  111. description = obj?.description;
  112. } else {
  113. label = `${refs.length} ${labelMultiple || 'Objects'}`;
  114. }
  115. return <>
  116. <div className='msp-flex-row'>
  117. <button className='msp-form-control msp-control-button-label' title={`${label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
  118. {label} <small>{description}</small>
  119. </button>
  120. <IconButton svg={pivot.cell.state.isHidden ? VisibilityOffOutlinedSvg : VisibilityOutlinedSvg} toggleState={false} className='msp-form-control' onClick={this.toggleVisibility} title={`${pivot.cell.state.isHidden ? 'Show' : 'Hide'}`} small flex />
  121. {refs.length === 1 && <IconButton svg={MoreHorizSvg} className='msp-form-control' onClick={this.toggleOptions} title='Options' toggleState={this.state.showOptions} flex />}
  122. </div>
  123. {(refs.length === 1 && this.state.showOptions && pivot.cell.parent) && <>
  124. <div className='msp-control-offset'>
  125. <UpdateTransformControl state={pivot.cell.parent} transform={pivot.cell.transform} customHeader='none' autoHideApply />
  126. </div>
  127. </>}
  128. </>;
  129. }
  130. }