util.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. export function loadTemplate(
  2. element: ParentNode,
  3. id: string
  4. ): DocumentFragment {
  5. let template = element.querySelector(`#${id}-template`);
  6. if (template !== null && template instanceof HTMLTemplateElement) {
  7. const fragment = document.importNode(template.content, true);
  8. fragment.querySelectorAll('template').forEach((t) => {
  9. let parent = t.parentNode!;
  10. const templateName = t.getAttribute('name');
  11. if (templateName === null) {
  12. return;
  13. }
  14. let template = loadTemplate(fragment, templateName);
  15. let firstElement = template.querySelector('*');
  16. if (firstElement !== null) {
  17. for (let i = 0; i < t.classList.length; ++i) {
  18. firstElement.classList.add(t.classList[i]);
  19. }
  20. }
  21. parent.insertBefore(template, t);
  22. parent.removeChild(t);
  23. });
  24. return fragment;
  25. } else {
  26. throw new Error(`#${id}-template is not a template`);
  27. }
  28. }
  29. export function clearChildren(node: Node): void {
  30. while (node.lastChild !== null) {
  31. node.removeChild(node.lastChild);
  32. }
  33. }
  34. export function getElement<E extends HTMLElement>(
  35. element: ParentNode,
  36. selector: string
  37. ): E {
  38. const e = element.querySelector(selector);
  39. if (e === null) {
  40. throw new Error(`Could not find required element ${selector}`);
  41. }
  42. return e as E;
  43. }
  44. export function createElement(
  45. elementName: string,
  46. value: string | null
  47. ): HTMLElement {
  48. const e = document.createElement(elementName);
  49. e.textContent = value;
  50. return e;
  51. }
  52. export function loadBackground(url: string): Promise<void> {
  53. if (url.includes('.')) {
  54. return new Promise((resolve, reject) => {
  55. let image = new Image();
  56. image.onload = (event) => resolve();
  57. image.src = url;
  58. });
  59. } else {
  60. return Promise.resolve();
  61. }
  62. }
  63. class ListenerManager {
  64. constructor(
  65. private target: EventTarget,
  66. private event: string,
  67. private handler: EventListener
  68. ) {}
  69. attach(): void {
  70. this.target.addEventListener(this.event, this.handler);
  71. }
  72. detach(): void {
  73. this.target.removeEventListener(this.event, this.handler);
  74. }
  75. }
  76. export class ListenersManager {
  77. private listeners: ListenerManager[] = [];
  78. add(
  79. target: EventTarget,
  80. event: string,
  81. handler: EventListener,
  82. attach: boolean = true
  83. ): void {
  84. let listener = new ListenerManager(target, event, handler);
  85. this.listeners.push(listener);
  86. if (attach) {
  87. listener.attach();
  88. }
  89. }
  90. attach(): void {
  91. this.listeners.forEach((l) => l.attach());
  92. }
  93. detach(): void {
  94. this.listeners.forEach((l) => l.detach());
  95. }
  96. }
  97. export class FnContext {
  98. current: Symbol = Symbol();
  99. invalidate() {
  100. this.current = Symbol();
  101. }
  102. wrap<T extends Function>(fn: T): T {
  103. const id = this.current;
  104. const wrappedFn = (...args: any[]) => {
  105. if (this.current === id) {
  106. return fn(...args);
  107. }
  108. };
  109. return (wrappedFn as any) as T;
  110. }
  111. }
  112. export interface Deferred {
  113. promise: Promise<void>;
  114. resolve: () => void;
  115. }
  116. export function makeDeferred(): Deferred {
  117. let resolve: undefined | (() => void);
  118. const promise = new Promise<void>((r) => {
  119. resolve = r;
  120. });
  121. return {
  122. promise,
  123. resolve: resolve!,
  124. };
  125. }
  126. export function deepEqual(
  127. a: unknown,
  128. b: unknown,
  129. context: string = ''
  130. ): [boolean, string] {
  131. if (a === b) {
  132. return [true, context];
  133. }
  134. if (a === null || b === null || a === undefined || b === undefined) {
  135. return [false, context];
  136. }
  137. if (typeof a !== typeof b) {
  138. return [false, context];
  139. }
  140. if (Array.isArray(a) && Array.isArray(b)) {
  141. if (a.length !== b.length) {
  142. return [false, context];
  143. }
  144. for (let i = 0; i < a.length; ++i) {
  145. const result = deepEqual(a[i], b[i], `${context}/${i}`);
  146. if (!result[0]) {
  147. return result;
  148. }
  149. }
  150. return [true, context];
  151. }
  152. if (typeof a === 'object' && typeof b === 'object') {
  153. const keys = Object.keys(a!);
  154. for (const key of keys) {
  155. // @ts-ignore
  156. const result = deepEqual(a[key], b[key], `${context}/${key}`);
  157. if (!result[0]) {
  158. return result;
  159. }
  160. }
  161. for (const key of Object.keys(b!)) {
  162. if (!keys.includes(key)) {
  163. return [false, `${context}/${key}`];
  164. }
  165. }
  166. return [true, context];
  167. }
  168. return [a === b, context];
  169. }