layout.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { ParamDefinition as PD } from 'mol-util/param-definition';
  7. import { PluginComponent } from './component';
  8. import { PluginContext } from './context';
  9. import { PluginCommands } from './command';
  10. // TODO: support collapsed state control orientation
  11. export const PluginLayoutStateParams = {
  12. isExpanded: PD.Boolean(false),
  13. showControls: PD.Boolean(true)
  14. }
  15. export type PluginLayoutStateProps = PD.Values<typeof PluginLayoutStateParams>
  16. interface RootState {
  17. top: string | null,
  18. bottom: string | null,
  19. left: string | null,
  20. right: string | null,
  21. width: string | null,
  22. height: string | null,
  23. maxWidth: string | null,
  24. maxHeight: string | null,
  25. margin: string | null,
  26. marginLeft: string | null,
  27. marginRight: string | null,
  28. marginTop: string | null,
  29. marginBottom: string | null,
  30. scrollTop: number,
  31. scrollLeft: number,
  32. position: string | null,
  33. overflow: string | null,
  34. viewports: HTMLElement[],
  35. zindex: string | null
  36. }
  37. export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
  38. private updateProps(state: Partial<PluginLayoutStateProps>) {
  39. let prevExpanded = !!this.latestState.isExpanded;
  40. this.updateState(state);
  41. if (this.root && typeof state.isExpanded === 'boolean' && state.isExpanded !== prevExpanded) this.handleExpand();
  42. this.triggerUpdate();
  43. }
  44. private root: HTMLElement;
  45. private rootState: RootState | undefined = void 0;
  46. private expandedViewport: HTMLMetaElement;
  47. setRoot(root: HTMLElement) {
  48. this.root = root;
  49. if (this.latestState.isExpanded) this.handleExpand();
  50. }
  51. private getScrollElement() {
  52. if ((document as any).scrollingElement) return (document as any).scrollingElement;
  53. if (document.documentElement) return document.documentElement;
  54. return document.body;
  55. }
  56. private handleExpand() {
  57. try {
  58. let body = document.getElementsByTagName('body')[0];
  59. let head = document.getElementsByTagName('head')[0];
  60. if (!body || !head) return;
  61. if (this.latestState.isExpanded) {
  62. let children = head.children;
  63. let hasExp = false;
  64. let viewports: HTMLElement[] = [];
  65. for (let i = 0; i < children.length; i++) {
  66. if (children[i] === this.expandedViewport) {
  67. hasExp = true;
  68. } else if (((children[i] as any).name || '').toLowerCase() === 'viewport') {
  69. viewports.push(children[i] as any);
  70. }
  71. }
  72. for (let v of viewports) {
  73. head.removeChild(v);
  74. }
  75. if (!hasExp) head.appendChild(this.expandedViewport);
  76. let s = body.style;
  77. let doc = this.getScrollElement();
  78. let scrollLeft = doc.scrollLeft;
  79. let scrollTop = doc.scrollTop;
  80. this.rootState = {
  81. top: s.top, bottom: s.bottom, right: s.right, left: s.left, scrollTop, scrollLeft, position: s.position, overflow: s.overflow, viewports, zindex: this.root.style.zIndex,
  82. width: s.width, height: s.height,
  83. maxWidth: s.maxWidth, maxHeight: s.maxHeight,
  84. margin: s.margin, marginLeft: s.marginLeft, marginRight: s.marginRight, marginTop: s.marginTop, marginBottom: s.marginBottom
  85. };
  86. s.overflow = 'hidden';
  87. s.position = 'fixed';
  88. s.top = '0';
  89. s.bottom = '0';
  90. s.right = '0';
  91. s.left = '0';
  92. s.width = '100%';
  93. s.height = '100%';
  94. s.maxWidth = '100%';
  95. s.maxHeight = '100%';
  96. s.margin = '0';
  97. s.marginLeft = '0';
  98. s.marginRight = '0';
  99. s.marginTop = '0';
  100. s.marginBottom = '0';
  101. // TODO: setting this breaks viewport controls for some reason. Is there a fix?
  102. // this.root.style.zIndex = '100000';
  103. } else {
  104. let children = head.children;
  105. for (let i = 0; i < children.length; i++) {
  106. if (children[i] === this.expandedViewport) {
  107. head.removeChild(this.expandedViewport);
  108. break;
  109. }
  110. }
  111. if (this.rootState) {
  112. let s = body.style, t = this.rootState;
  113. for (let v of t.viewports) {
  114. head.appendChild(v);
  115. }
  116. s.top = t.top;
  117. s.bottom = t.bottom;
  118. s.left = t.left;
  119. s.right = t.right;
  120. s.width = t.width;
  121. s.height = t.height;
  122. s.maxWidth = t.maxWidth;
  123. s.maxHeight = t.maxHeight;
  124. s.margin = t.margin;
  125. s.marginLeft = t.marginLeft;
  126. s.marginRight = t.marginRight;
  127. s.marginTop = t.marginTop;
  128. s.marginBottom = t.marginBottom;
  129. s.position = t.position;
  130. s.overflow = t.overflow;
  131. let doc = this.getScrollElement();
  132. doc.scrollTop = t.scrollTop;
  133. doc.scrollLeft = t.scrollLeft;
  134. this.rootState = void 0;
  135. this.root.style.zIndex = t.zindex;
  136. }
  137. }
  138. } catch (e) {
  139. this.context.log.error('Layout change error, you might have to reload the page.');
  140. console.log('Layout change error, you might have to reload the page.', e);
  141. }
  142. }
  143. constructor(context: PluginContext) {
  144. super(context, { ...PD.getDefaultValues(PluginLayoutStateParams), ...context.spec.initialLayout });
  145. PluginCommands.Layout.Update.subscribe(context, e => this.updateProps(e.state));
  146. // TODO how best make sure it runs on node.js as well as in the browser?
  147. if (typeof document !== 'undefined') {
  148. // <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' />
  149. this.expandedViewport = document.createElement('meta') as any;
  150. this.expandedViewport.name = 'viewport';
  151. this.expandedViewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0';
  152. }
  153. }
  154. }