graphql-playground-widget.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. export function initGraphQlPlaygroundWidgets() {
  2. Array.from(document.querySelectorAll('.graphql-playground-widget')).forEach(el => {
  3. const playground = new GraphqlPlaygroundWidget(el as HTMLElement);
  4. playground.init();
  5. });
  6. }
  7. /**
  8. * A widget which lazily creates an iframe containing an instance of the graphql-playground app.
  9. *
  10. * The widget can contain one or more tabs with queries defined as below:
  11. *
  12. * @example
  13. * ```
  14. * <div class="graphql-playground-widget">
  15. * <div class="graphql-playground-tab" data-name="Product List">
  16. * query {
  17. * products(options: { skip: 0 take: 3 }) {
  18. * totalItems
  19. * items {
  20. * name
  21. * variants {
  22. * sku
  23. * name
  24. * price
  25. * }
  26. * }
  27. * }
  28. * }
  29. * </div>
  30. * </div>
  31. * ```
  32. */
  33. export class GraphqlPlaygroundWidget {
  34. private readonly endpoint = 'https://demo.vendure.io/shop-api';
  35. private tabs: Array<{ name: string; query: string; }>;
  36. private triggerYTop: number;
  37. private triggerYBottom: number;
  38. private activateTimer: any;
  39. constructor(private targetElement: HTMLElement) {}
  40. private scrollHandler = () => {
  41. clearTimeout(this.activateTimer);
  42. if (this.triggerYTop < window.scrollY && window.scrollY < this.triggerYBottom) {
  43. this.activateTimer = setTimeout(() => this.activate(), 250);
  44. }
  45. }
  46. init() {
  47. const tabElements: HTMLElement[] = Array.from(this.targetElement.querySelectorAll('.graphql-playground-tab'));
  48. this.tabs = tabElements.map(el => {
  49. return {
  50. name: el.dataset.name || 'Query',
  51. query: this.removeLeadingWhitespace(el.innerHTML),
  52. };
  53. });
  54. this.targetElement.innerHTML = this.loadingHtml;
  55. this.triggerYTop = this.targetElement.offsetTop - window.innerHeight;
  56. this.triggerYBottom = this.targetElement.offsetTop + window.innerHeight;
  57. window.addEventListener('scroll', this.scrollHandler);
  58. this.scrollHandler();
  59. }
  60. activate() {
  61. this.targetElement.innerHTML = '';
  62. window.removeEventListener('scroll', this.scrollHandler);
  63. const iframe = document.createElement('iframe');
  64. const html = this.generateIframeContent();
  65. this.targetElement.appendChild(iframe);
  66. if (iframe.contentWindow) {
  67. iframe.contentWindow.document.open();
  68. iframe.contentWindow.document.write(html);
  69. iframe.contentWindow.document.close();
  70. }
  71. }
  72. private removeLeadingWhitespace(s: string): string {
  73. const matches = s.match(/^\s+/m);
  74. if (!matches) {
  75. return s;
  76. }
  77. const indent = matches[0].replace(/\n/, '');
  78. return s.replace(new RegExp(`^${indent}`, 'gm'), '').trim();
  79. }
  80. private generateIframeContent(): string {
  81. return this.wrapInHtmlDocument(`
  82. <script>
  83. window.addEventListener('load', function (event) {
  84. GraphQLPlayground.init(document.getElementById('root'), {
  85. endpoint: '${this.endpoint}',
  86. settings: {
  87. 'request.credentials': 'include',
  88. },
  89. tabs: [${this.generateTabsArray()}]
  90. });
  91. });
  92. </script>
  93. `);
  94. }
  95. private generateTabsArray() {
  96. return this.tabs.map(tab => `
  97. { endpoint: '${this.endpoint}', name: '${tab.name}', query: \`${tab.query}\` },
  98. `);
  99. }
  100. private wrapInHtmlDocument(toWrap: string): string {
  101. return `
  102. <!DOCTYPE html>
  103. <html>
  104. <head>
  105. <meta charset=utf-8/>
  106. <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
  107. <title>GraphQL Playground</title>
  108. <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/css/index.css" />
  109. <script src="//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/js/middleware.js"></script>
  110. </head>
  111. <body>
  112. <div id="root">
  113. <style>
  114. body {
  115. background-color: rgb(23, 42, 58);
  116. font-family: Open Sans, sans-serif;
  117. height: 90vh;
  118. }
  119. #root {
  120. height: 100%;
  121. width: 100%;
  122. display: flex;
  123. align-items: center;
  124. justify-content: center;
  125. }
  126. </style>
  127. </div>
  128. ${toWrap}
  129. </body>
  130. </html>`;
  131. }
  132. private loadingHtml = `<img src='//cdn.jsdelivr.net/npm/graphql-playground-react/build/logo.png' alt=''>
  133. <div class="loading"> Loading
  134. <span class="title">GraphQL Playground</span>
  135. </div>`;
  136. }