ui.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. /**
  2. * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  5. */
  6. import { merge } from 'rxjs';
  7. import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
  8. import { Button } from '../../mol-plugin-ui/controls/common';
  9. import { GetAppSvg, CubeScanSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
  10. import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
  11. import { download } from '../../mol-util/download';
  12. import { GeometryParams, GeometryControls } from './controls';
  13. interface State {
  14. busy?: boolean
  15. }
  16. export class GeometryExporterUI extends CollapsableControls<{}, State> {
  17. private _controls: GeometryControls | undefined;
  18. private isARSupported: boolean | undefined;
  19. get controls() {
  20. return this._controls || (this._controls = new GeometryControls(this.plugin));
  21. }
  22. protected defaultState(): State & CollapsableState {
  23. return {
  24. header: 'Export Geometry',
  25. isCollapsed: true,
  26. brand: { accent: 'cyan', svg: CubeSendSvg }
  27. };
  28. }
  29. protected renderControls(): JSX.Element {
  30. if (this.isARSupported === undefined) {
  31. this.isARSupported = !!document.createElement('a').relList?.supports?.('ar');
  32. }
  33. const ctrl = this.controls;
  34. return <>
  35. <ParameterControls
  36. params={GeometryParams}
  37. values={ctrl.behaviors.params.value}
  38. onChangeValues={xs => ctrl.behaviors.params.next(xs)}
  39. isDisabled={this.state.busy}
  40. />
  41. <Button icon={GetAppSvg}
  42. onClick={this.save} style={{ marginTop: 1 }}
  43. disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
  44. Save
  45. </Button>
  46. {this.isARSupported && ctrl.behaviors.params.value.format === 'usdz' &&
  47. <Button icon={CubeScanSvg}
  48. onClick={this.viewInAR} style={{ marginTop: 1 }}
  49. disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
  50. View in AR
  51. </Button>
  52. }
  53. </>;
  54. }
  55. componentDidMount() {
  56. if (!this.plugin.canvas3d) return;
  57. const merged = merge(
  58. this.controls.behaviors.params,
  59. this.plugin.canvas3d!.reprCount
  60. );
  61. this.subscribe(merged, () => {
  62. if (!this.state.isCollapsed) this.forceUpdate();
  63. });
  64. }
  65. componentWillUnmount() {
  66. super.componentWillUnmount();
  67. this._controls?.dispose();
  68. this._controls = void 0;
  69. }
  70. save = async () => {
  71. try {
  72. this.setState({ busy: true });
  73. const data = await this.controls.exportGeometry();
  74. download(data.blob, data.filename);
  75. } catch (e) {
  76. console.error(e);
  77. } finally {
  78. this.setState({ busy: false });
  79. }
  80. };
  81. viewInAR = async () => {
  82. try {
  83. this.setState({ busy: true });
  84. const data = await this.controls.exportGeometry();
  85. const a = document.createElement('a');
  86. a.rel = 'ar';
  87. a.href = URL.createObjectURL(data.blob);
  88. // For in-place viewing of USDZ on iOS, the link must contain a single child that is either an img or picture.
  89. // https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/
  90. a.appendChild(document.createElement('img'));
  91. setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
  92. setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
  93. } catch (e) {
  94. console.error(e);
  95. } finally {
  96. this.setState({ busy: false });
  97. }
  98. };
  99. }