util.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. export const presence = new Presence({
  2. clientId: "820023496934817804",
  3. });
  4. export const browsingTimestamp = Math.floor(Date.now() / 1000);
  5. export const slideshow = presence.createSlideshow();
  6. let oldSlideshowKey: string;
  7. /**
  8. * Registers a new slideshow key and clears the current slideshow on changes.
  9. */
  10. export function registerSlideshowKey(key: string): boolean {
  11. if (oldSlideshowKey !== key) {
  12. presence.info(`Slideshow key changed from ${oldSlideshowKey} to ${key}`);
  13. slideshow.deleteAllSlides();
  14. oldSlideshowKey = key;
  15. return true;
  16. }
  17. return false;
  18. }
  19. const iconCache: Record<string, Promise<Blob>> = {};
  20. /**
  21. * Converts an icon <i> element to a Blob.
  22. *
  23. * @param icon
  24. * @param backgroundColor The background color of the icon
  25. */
  26. export function getIconImage(
  27. icon: HTMLElement,
  28. backgroundColor = "#fff"
  29. ): Promise<Blob> {
  30. const canvas = document.createElement("canvas");
  31. canvas.width = 512;
  32. canvas.height = 512;
  33. const ctx = canvas.getContext("2d"),
  34. // get the icon's computed style
  35. { fontFamily, fontWeight, color } = getComputedStyle(icon),
  36. { content } = getComputedStyle(icon, ":before"),
  37. text = content.replace(/"/g, ""),
  38. key = `${fontFamily}-${fontWeight}-${backgroundColor}-${color}-${text}`;
  39. if (iconCache[key]) return iconCache[key];
  40. // render the background
  41. ctx.fillStyle = backgroundColor;
  42. ctx.fillRect(0, 0, 512, 512);
  43. // render the text
  44. ctx.font = `${fontWeight} 384px/1 ${fontFamily}`;
  45. ctx.fillStyle = color;
  46. ctx.textAlign = "center";
  47. ctx.textBaseline = "middle";
  48. ctx.fillText(text, 256, 256);
  49. const blobPromise: Promise<Blob> = new Promise(resolve => {
  50. canvas.toBlob(blob => {
  51. // for debugging
  52. const url = URL.createObjectURL(blob);
  53. presence.info(`${key} -> ${url}`);
  54. setTimeout(() => URL.revokeObjectURL(url), 15e3);
  55. resolve(blob);
  56. });
  57. });
  58. iconCache[key] = blobPromise;
  59. return iconCache[key];
  60. }
  61. let batchCacheKey: string,
  62. batchCache: unknown[] = [],
  63. batchInterval: number,
  64. batchIndex = 0,
  65. batchItems: unknown[] = [],
  66. batchAborter = new AbortController();
  67. /**
  68. * Batches a list of items and maps them to a new list of items.
  69. * Useful for expensive operations.
  70. *
  71. * The batch will execute every 5 seconds and will stop if the key changes.
  72. * If new items are added, the batch will restart.
  73. *
  74. * @param key A unique key for the batch
  75. * @param itemList The list of items to batch
  76. * @param mapper The function to map the items
  77. */
  78. export async function batch<I, O>(
  79. key: string,
  80. itemList: I[],
  81. mapper: (input: I) => Awaitable<O>
  82. ): Promise<O[]> {
  83. if (batchCacheKey === key) {
  84. // check if items changed
  85. if (batchItems.length !== itemList.length) {
  86. presence.info(
  87. `Batched items changed from ${batchItems.length} to ${itemList.length}`
  88. );
  89. batchAborter.abort();
  90. batchCache = [];
  91. batchIndex = 0;
  92. batchItems = itemList;
  93. if (batchInterval === null) executeBatch();
  94. }
  95. return batchCache as O[];
  96. }
  97. presence.info(`Batched key changed from ${batchCacheKey} to ${key}`);
  98. clearTimeout(batchInterval);
  99. batchAborter.abort();
  100. batchCacheKey = key;
  101. batchCache = [];
  102. batchItems = itemList;
  103. batchIndex = 0;
  104. async function executeBatch() {
  105. for (let i = batchIndex, j = 0; i < batchItems.length && j < 10; i++, j++) {
  106. const data = await mapper((batchItems as I[])[i]);
  107. if (batchAborter.signal.aborted) {
  108. presence.info("Batch aborted");
  109. batchAborter = new AbortController();
  110. break;
  111. }
  112. batchCache.push(data);
  113. batchIndex++;
  114. }
  115. presence.info(`Batched ${batchIndex} of ${batchItems.length}`);
  116. if (batchIndex === batchItems.length) {
  117. clearTimeout(batchInterval);
  118. batchInterval = null;
  119. } else if (key === batchCacheKey)
  120. batchInterval = setTimeout(executeBatch, 5000);
  121. }
  122. executeBatch();
  123. return batchCache as O[];
  124. }