@naturalcycles/js-lib 14.256.0 → 14.258.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/cfg/frontend/tsconfig.json +67 -0
  2. package/dist/browser/adminService.d.ts +69 -0
  3. package/dist/browser/adminService.js +98 -0
  4. package/dist/browser/analytics.util.d.ts +12 -0
  5. package/dist/browser/analytics.util.js +59 -0
  6. package/dist/browser/i18n/fetchTranslationLoader.d.ts +13 -0
  7. package/dist/browser/i18n/fetchTranslationLoader.js +17 -0
  8. package/dist/browser/i18n/translation.service.d.ts +53 -0
  9. package/dist/browser/i18n/translation.service.js +61 -0
  10. package/dist/browser/imageFitter.d.ts +60 -0
  11. package/dist/browser/imageFitter.js +69 -0
  12. package/dist/browser/script.util.d.ts +14 -0
  13. package/dist/browser/script.util.js +50 -0
  14. package/dist/browser/topbar.d.ts +23 -0
  15. package/dist/browser/topbar.js +137 -0
  16. package/dist/decorators/memo.util.d.ts +2 -1
  17. package/dist/decorators/memo.util.js +8 -6
  18. package/dist/decorators/swarmSafe.decorator.d.ts +9 -0
  19. package/dist/decorators/swarmSafe.decorator.js +42 -0
  20. package/dist/deviceIdService.d.ts +65 -0
  21. package/dist/deviceIdService.js +109 -0
  22. package/dist/error/assert.d.ts +2 -1
  23. package/dist/error/assert.js +15 -13
  24. package/dist/error/error.util.js +9 -6
  25. package/dist/index.d.ts +9 -0
  26. package/dist/index.js +9 -0
  27. package/dist/nanoid.d.ts +7 -0
  28. package/dist/nanoid.js +61 -0
  29. package/dist/number/createDeterministicRandom.d.ts +6 -1
  30. package/dist/number/createDeterministicRandom.js +1 -2
  31. package/dist/string/hash.util.d.ts +1 -1
  32. package/dist/string/hash.util.js +1 -1
  33. package/dist/web.d.ts +6 -0
  34. package/dist/web.js +6 -0
  35. package/dist/zod/zod.util.d.ts +1 -1
  36. package/dist-esm/browser/adminService.js +94 -0
  37. package/dist-esm/browser/analytics.util.js +54 -0
  38. package/dist-esm/browser/i18n/fetchTranslationLoader.js +13 -0
  39. package/dist-esm/browser/i18n/translation.service.js +56 -0
  40. package/dist-esm/browser/imageFitter.js +65 -0
  41. package/dist-esm/browser/script.util.js +46 -0
  42. package/dist-esm/browser/topbar.js +134 -0
  43. package/dist-esm/decorators/memo.util.js +3 -1
  44. package/dist-esm/decorators/swarmSafe.decorator.js +38 -0
  45. package/dist-esm/deviceIdService.js +105 -0
  46. package/dist-esm/error/assert.js +3 -1
  47. package/dist-esm/error/error.util.js +4 -1
  48. package/dist-esm/index.js +9 -0
  49. package/dist-esm/nanoid.js +57 -0
  50. package/dist-esm/number/createDeterministicRandom.js +1 -2
  51. package/dist-esm/string/hash.util.js +1 -1
  52. package/dist-esm/web.js +6 -0
  53. package/package.json +2 -1
  54. package/src/browser/adminService.ts +157 -0
  55. package/src/browser/analytics.util.ts +68 -0
  56. package/src/browser/i18n/fetchTranslationLoader.ts +16 -0
  57. package/src/browser/i18n/translation.service.ts +102 -0
  58. package/src/browser/imageFitter.ts +128 -0
  59. package/src/browser/script.util.ts +52 -0
  60. package/src/browser/topbar.ts +147 -0
  61. package/src/datetime/localDate.ts +16 -0
  62. package/src/datetime/localTime.ts +39 -0
  63. package/src/decorators/debounce.ts +1 -0
  64. package/src/decorators/memo.util.ts +4 -1
  65. package/src/decorators/swarmSafe.decorator.ts +47 -0
  66. package/src/deviceIdService.ts +137 -0
  67. package/src/error/assert.ts +5 -11
  68. package/src/error/error.util.ts +4 -1
  69. package/src/index.ts +9 -0
  70. package/src/json-schema/jsonSchemaBuilder.ts +20 -0
  71. package/src/nanoid.ts +79 -0
  72. package/src/number/createDeterministicRandom.ts +7 -2
  73. package/src/semver.ts +2 -0
  74. package/src/string/hash.util.ts +1 -1
  75. package/src/web.ts +6 -0
  76. package/src/zod/zod.util.ts +1 -1
package/dist/nanoid.js ADDED
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ // Vendored from https://github.com/ai/nanoid/blob/main/index.browser.js
3
+ // All credit to nanoid authors: https://github.com/ai/nanoid
4
+ // Reason for vendoring: (still) cannot import esm, and Nanoid went ESM-only since 4.0
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.nanoidBrowser = nanoidBrowser;
7
+ exports.nanoidBrowserCustomAlphabet = nanoidBrowserCustomAlphabet;
8
+ /// <reference lib="dom" preserve="true" />
9
+ /* eslint-disable no-bitwise */
10
+ // "0-9a-zA-Z-_", same as base64url alphabet
11
+ const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
12
+ function nanoidBrowser(length = 21) {
13
+ let id = '';
14
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length));
15
+ while (length--) {
16
+ // Using the bitwise AND operator to "cap" the value of
17
+ // the random byte from 255 to 63, in that way we can make sure
18
+ // that the value will be a valid index for the "chars" string.
19
+ id += urlAlphabet[bytes[length] & 63];
20
+ }
21
+ return id;
22
+ }
23
+ const defaultRandomFunction = (bytes) => globalThis.crypto.getRandomValues(new Uint8Array(bytes));
24
+ function nanoidBrowserCustomAlphabet(alphabet, length = 21) {
25
+ return customRandom(alphabet, length, defaultRandomFunction);
26
+ }
27
+ function customRandom(alphabet, defaultSize, getRandom) {
28
+ // First, a bitmask is necessary to generate the ID. The bitmask makes bytes
29
+ // values closer to the alphabet size. The bitmask calculates the closest
30
+ // `2^31 - 1` number, which exceeds the alphabet size.
31
+ // For example, the bitmask for the alphabet size 30 is 31 (00011111).
32
+ // `Math.clz32` is not used, because it is not available in browsers.
33
+ const mask = (2 << Math.log2(alphabet.length - 1)) - 1;
34
+ // Though, the bitmask solution is not perfect since the bytes exceeding
35
+ // the alphabet size are refused. Therefore, to reliably generate the ID,
36
+ // the random bytes redundancy has to be satisfied.
37
+ // Note: every hardware random generator call is performance expensive,
38
+ // because the system call for entropy collection takes a lot of time.
39
+ // So, to avoid additional system calls, extra bytes are requested in advance.
40
+ // Next, a step determines how many random bytes to generate.
41
+ // The number of random bytes gets decided upon the ID size, mask,
42
+ // alphabet size, and magic number 1.6 (using 1.6 peaks at performance
43
+ // according to benchmarks).
44
+ // `-~f => Math.ceil(f)` if f is a float
45
+ // `-~i => i + 1` if i is an integer
46
+ const step = -~((1.6 * mask * defaultSize) / alphabet.length);
47
+ return (size = defaultSize) => {
48
+ let id = '';
49
+ while (true) {
50
+ const bytes = getRandom(step);
51
+ // A compact alternative for `for (var i = 0; i < step; i++)`.
52
+ let j = step;
53
+ while (j--) {
54
+ // Adding `|| ''` refuses a random byte that exceeds the alphabet size.
55
+ id += alphabet[bytes[j] & mask] || '';
56
+ if (id.length === size)
57
+ return id;
58
+ }
59
+ }
60
+ };
61
+ }
@@ -1,6 +1,11 @@
1
+ /**
2
+ * Function that returns a random number between 0 and 1.
3
+ * Exactly same signature as Math.random function.
4
+ */
5
+ export type RandomFunction = () => number;
1
6
  /**
2
7
  * Returns a "deterministic Math.random() function"
3
8
  *
4
9
  * Based on: https://gist.github.com/mathiasbynens/5670917
5
10
  */
6
- export declare function _createDeterministicRandom(): () => number;
11
+ export declare function _createDeterministicRandom(seed?: number): RandomFunction;
@@ -7,8 +7,7 @@ exports._createDeterministicRandom = _createDeterministicRandom;
7
7
  *
8
8
  * Based on: https://gist.github.com/mathiasbynens/5670917
9
9
  */
10
- function _createDeterministicRandom() {
11
- let seed = 0x2f6e2b1;
10
+ function _createDeterministicRandom(seed = 0x2f6e2b1) {
12
11
  return () => {
13
12
  // Robert Jenkins’ 32 bit integer hash function
14
13
  seed = (seed + 0x7ed55d16 + (seed << 12)) & 0xffffffff;
@@ -6,7 +6,7 @@ import { Integer } from '../types';
6
6
  *
7
7
  * 1. Performance
8
8
  * 2. For non-cryptographic use (where accidental collision is not the end-of-the-world)
9
- * 3. Compact size (32 bits max, versus 128 in md5; presented in less string json-safe characters)
9
+ * 3. Compact size (32 bits max, versus 128 in md5; presented in smaller number of string json-safe characters)
10
10
  *
11
11
  * Basically, these functions are as simple as they can be, but still "random enough" for
12
12
  * normal non-cryptographic use cases.
@@ -14,7 +14,7 @@ const BASE64URL = BASE62 + '-_';
14
14
  *
15
15
  * 1. Performance
16
16
  * 2. For non-cryptographic use (where accidental collision is not the end-of-the-world)
17
- * 3. Compact size (32 bits max, versus 128 in md5; presented in less string json-safe characters)
17
+ * 3. Compact size (32 bits max, versus 128 in md5; presented in smaller number of string json-safe characters)
18
18
  *
19
19
  * Basically, these functions are as simple as they can be, but still "random enough" for
20
20
  * normal non-cryptographic use cases.
package/dist/web.d.ts CHANGED
@@ -5,6 +5,12 @@ import { StringMap } from './types';
5
5
  * Implements WebStorage API by using in-memory storage.
6
6
  * Can be useful in SSR environment or unit tests.
7
7
  *
8
+ * This is how localStorage can be mocked in Node:
9
+ *
10
+ * Object.assign(globalThis, {
11
+ * localStorage: new InMemoryWebStorage(),
12
+ * })
13
+ *
8
14
  * @experimental
9
15
  */
10
16
  export declare class InMemoryWebStorage implements Storage {
package/dist/web.js CHANGED
@@ -7,6 +7,12 @@ exports.InMemoryWebStorage = void 0;
7
7
  * Implements WebStorage API by using in-memory storage.
8
8
  * Can be useful in SSR environment or unit tests.
9
9
  *
10
+ * This is how localStorage can be mocked in Node:
11
+ *
12
+ * Object.assign(globalThis, {
13
+ * localStorage: new InMemoryWebStorage(),
14
+ * })
15
+ *
10
16
  * @experimental
11
17
  */
12
18
  class InMemoryWebStorage {
@@ -1,4 +1,4 @@
1
- import { ZodError, ZodIssue, ZodSchema } from 'zod';
1
+ import { ZodError, type ZodIssue, type ZodSchema } from 'zod';
2
2
  export interface ZodErrorResult<T> {
3
3
  success: false;
4
4
  data?: T;
@@ -0,0 +1,94 @@
1
+ import { __decorate } from "tslib";
2
+ import { _Memo } from '../decorators/memo.decorator';
3
+ import { isServerSide } from '../env';
4
+ import { _stringify } from '../string/stringify';
5
+ const RED_DOT_ID = '__red-dot__';
6
+ const NOOP = () => { };
7
+ /**
8
+ * @experimental
9
+ *
10
+ * Allows to listen for AdminMode keypress combination (Ctrl+Shift+L by default) to toggle AdminMode,
11
+ * indicated by RedDot DOM element.
12
+ *
13
+ * todo: help with Authentication
14
+ */
15
+ export class AdminService {
16
+ constructor(cfg) {
17
+ this.adminMode = false;
18
+ this.listening = false;
19
+ this.cfg = {
20
+ predicate: e => e.ctrlKey && e.key === 'L',
21
+ persistToLocalStorage: true,
22
+ localStorageKey: '__adminMode__',
23
+ onRedDotClick: NOOP,
24
+ onChange: NOOP,
25
+ beforeEnter: () => true,
26
+ beforeExit: () => true,
27
+ ...cfg,
28
+ };
29
+ }
30
+ /**
31
+ * Start listening to keyboard events to toggle AdminMode when detected.
32
+ */
33
+ startListening() {
34
+ if (this.listening || isServerSide())
35
+ return;
36
+ this.adminMode = !!localStorage.getItem(this.cfg.localStorageKey);
37
+ if (this.adminMode)
38
+ this.toggleRedDotVisibility();
39
+ document.addEventListener('keydown', this.keydownListener.bind(this), { passive: true });
40
+ this.listening = true;
41
+ }
42
+ stopListening() {
43
+ if (isServerSide())
44
+ return;
45
+ document.removeEventListener('keydown', this.keydownListener);
46
+ this.listening = false;
47
+ }
48
+ async keydownListener(e) {
49
+ // console.log(e)
50
+ if (!this.cfg.predicate(e))
51
+ return;
52
+ await this.toggleRedDot();
53
+ }
54
+ async toggleRedDot() {
55
+ try {
56
+ const allow = await this.cfg[this.adminMode ? 'beforeExit' : 'beforeEnter']();
57
+ if (!allow)
58
+ return; // no change
59
+ }
60
+ catch (err) {
61
+ console.error(err);
62
+ // ok to show alert to Admins, it's not user-facing
63
+ alert(_stringify(err));
64
+ return; // treat as "not allowed"
65
+ }
66
+ this.adminMode = !this.adminMode;
67
+ this.toggleRedDotVisibility();
68
+ if (this.cfg.persistToLocalStorage) {
69
+ const { localStorageKey } = this.cfg;
70
+ if (this.adminMode) {
71
+ localStorage.setItem(localStorageKey, '1');
72
+ }
73
+ else {
74
+ localStorage.removeItem(localStorageKey);
75
+ }
76
+ }
77
+ this.cfg.onChange(this.adminMode);
78
+ }
79
+ toggleRedDotVisibility() {
80
+ this.getRedDotElement().style.display = this.adminMode ? 'block' : 'none';
81
+ }
82
+ getRedDotElement() {
83
+ const el = document.createElement('div');
84
+ el.id = RED_DOT_ID;
85
+ el.style.cssText =
86
+ 'position:fixed;width:24px;height:24px;margin-top:-12px;background-color:red;opacity:0.5;top:50%;left:0;z-index:9999999;cursor:pointer;border-radius:0 3px 3px 0';
87
+ el.addEventListener('click', () => this.cfg.onRedDotClick());
88
+ document.body.append(el);
89
+ return el;
90
+ }
91
+ }
92
+ __decorate([
93
+ _Memo()
94
+ ], AdminService.prototype, "getRedDotElement", null);
@@ -0,0 +1,54 @@
1
+ import { isServerSide } from '@naturalcycles/js-lib';
2
+ import { loadScript } from './script.util';
3
+ /* eslint-disable unicorn/prefer-global-this */
4
+ /**
5
+ * Pass enabled = false to only init window.gtag, but not load actual gtag script (e.g in dev mode).
6
+ */
7
+ export async function loadGTag(gtagId, enabled = true) {
8
+ if (isServerSide())
9
+ return;
10
+ window.dataLayer || (window.dataLayer = []);
11
+ window.gtag || (window.gtag = function gtag() {
12
+ // biome-ignore lint/complexity/useArrowFunction: ok
13
+ // biome-ignore lint/style/noArguments: ok
14
+ window.dataLayer.push(arguments);
15
+ });
16
+ window.gtag('js', new Date());
17
+ window.gtag('config', gtagId);
18
+ if (!enabled)
19
+ return;
20
+ await loadScript(`https://www.googletagmanager.com/gtag/js?id=${gtagId}`);
21
+ }
22
+ export async function loadGTM(gtmId, enabled = true) {
23
+ if (isServerSide())
24
+ return;
25
+ window.dataLayer || (window.dataLayer = []);
26
+ window.dataLayer.push({
27
+ 'gtm.start': Date.now(),
28
+ event: 'gtm.js',
29
+ });
30
+ if (!enabled)
31
+ return;
32
+ await loadScript(`https://www.googletagmanager.com/gtm.js?id=${gtmId}`);
33
+ }
34
+ export function loadHotjar(hjid) {
35
+ if (isServerSide())
36
+ return;
37
+ ;
38
+ ((h, o, t, j, a, r) => {
39
+ h.hj =
40
+ h.hj ||
41
+ function hj() {
42
+ // biome-ignore lint/style/noArguments: ok
43
+ ;
44
+ (h.hj.q = h.hj.q || []).push(arguments);
45
+ };
46
+ h._hjSettings = { hjid, hjsv: 6 };
47
+ a = o.querySelectorAll('head')[0];
48
+ r = o.createElement('script');
49
+ r.async = 1;
50
+ r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
51
+ a.append(r);
52
+ })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
53
+ /* eslint-enable */
54
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Use `baseUrl` to prefix your language files.
3
+ * Example URL structure:
4
+ * ${baseUrl}/${locale}.json
5
+ */
6
+ export class FetchTranslationLoader {
7
+ constructor(fetcher) {
8
+ this.fetcher = fetcher;
9
+ }
10
+ async load(locale) {
11
+ return await this.fetcher.get(`${locale}.json`);
12
+ }
13
+ }
@@ -0,0 +1,56 @@
1
+ import { pMap } from '../../promise/pMap';
2
+ export const defaultMissingTranslationHandler = key => {
3
+ console.warn(`[tr] missing: ${key}`);
4
+ return `[${key}]`;
5
+ };
6
+ export class TranslationService {
7
+ constructor(cfg, preloadedLocales = {}) {
8
+ this.cfg = {
9
+ ...cfg,
10
+ missingTranslationHandler: defaultMissingTranslationHandler,
11
+ };
12
+ this.locales = {
13
+ ...preloadedLocales,
14
+ };
15
+ this.currentLocale = cfg.currentLocale || cfg.defaultLocale;
16
+ }
17
+ /**
18
+ * Manually set locale data, bypassing the TranslationLoader.
19
+ */
20
+ setLocale(localeName, locale) {
21
+ this.locales[localeName] = locale;
22
+ }
23
+ getLocale(locale) {
24
+ return this.locales[locale];
25
+ }
26
+ /**
27
+ * Loads locale(s) (if not already cached) via configured TranslationLoader.
28
+ * Resolves promise when done (ready to be used).
29
+ */
30
+ async loadLocale(locale) {
31
+ const locales = Array.isArray(locale) ? locale : [locale];
32
+ await pMap(locales, async (locale) => {
33
+ if (this.locales[locale])
34
+ return; // already loaded
35
+ this.locales[locale] = await this.cfg.translationLoader.load(locale);
36
+ // console.log(`[tr] locale loaded: ${locale}`)
37
+ });
38
+ }
39
+ /**
40
+ * Will invoke `missingTranslationHandler` on missing tranlation.
41
+ *
42
+ * Does NOT do any locale loading. The locale needs to be loaded beforehand:
43
+ * either pre-loaded and passed to the constructor,
44
+ * or `await loadLocale(locale)`.
45
+ */
46
+ translate(key, params) {
47
+ return this.translateIfExists(key, params) || this.cfg.missingTranslationHandler(key, params);
48
+ }
49
+ /**
50
+ * Does NOT invoke `missingTranslationHandler`, returns `undefined` instead.
51
+ */
52
+ translateIfExists(key, _params) {
53
+ // todo: support params
54
+ return this.locales[this.currentLocale]?.[key] || this.locales[this.cfg.defaultLocale]?.[key];
55
+ }
56
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Calculates the width/height of the images to fit in the layout.
3
+ *
4
+ * Currently does not mutate the cfg.images array, but DOES mutate individual images with .fitWidth, .fitHeight properties.
5
+ *
6
+ * @experimental
7
+ */
8
+ export class ImageFitter {
9
+ constructor(cfg) {
10
+ this.containerWidth = -1;
11
+ this.cfg = {
12
+ maxHeight: 300,
13
+ margin: 8,
14
+ ...cfg,
15
+ };
16
+ this.resizeObserver = new ResizeObserver(entries => this.update(entries));
17
+ this.resizeObserver.observe(cfg.containerElement);
18
+ }
19
+ stop() {
20
+ this.resizeObserver.disconnect();
21
+ }
22
+ update(entries) {
23
+ const width = Math.floor(entries[0].contentRect.width);
24
+ if (width === this.containerWidth)
25
+ return; // we're only interested in width changes
26
+ this.containerWidth = width;
27
+ console.log(`resize ${width}`);
28
+ this.doLayout(this.cfg.images);
29
+ this.cfg.onChange(this.cfg.images);
30
+ }
31
+ doLayout(imgs) {
32
+ if (imgs.length === 0)
33
+ return; // nothing to do
34
+ const { maxHeight } = this.cfg;
35
+ let imgNodes = imgs.slice(0);
36
+ w: while (imgNodes.length > 0) {
37
+ let slice;
38
+ let h;
39
+ for (let i = 1; i <= imgNodes.length; i++) {
40
+ slice = imgNodes.slice(0, i);
41
+ h = this.getHeigth(slice);
42
+ if (h < maxHeight) {
43
+ this.setHeight(slice, h);
44
+ imgNodes = imgNodes.slice(i);
45
+ continue w;
46
+ }
47
+ }
48
+ this.setHeight(slice, Math.min(maxHeight, h));
49
+ break;
50
+ }
51
+ }
52
+ getHeigth(images) {
53
+ const width = this.containerWidth - images.length * this.cfg.margin;
54
+ let r = 0;
55
+ images.forEach(img => (r += img.aspectRatio));
56
+ return width / r; // have to round down because Firefox will automatically roundup value with number of decimals > 3
57
+ }
58
+ // mutates/sets images' fitWidth, fitHeight properties
59
+ setHeight(images, height) {
60
+ images.forEach(img => {
61
+ img.fitWidth = Math.floor(height * img.aspectRatio);
62
+ img.fitHeight = Math.floor(height);
63
+ });
64
+ }
65
+ }
@@ -0,0 +1,46 @@
1
+ import { isServerSide } from '../env';
2
+ import { _objectAssign } from '../types';
3
+ /**
4
+ * opt.async defaults to `true`.
5
+ * No other options are set by default.
6
+ */
7
+ export async function loadScript(src, opt) {
8
+ if (isServerSide())
9
+ return;
10
+ return await new Promise((resolve, reject) => {
11
+ const s = _objectAssign(document.createElement('script'), {
12
+ src,
13
+ async: true,
14
+ ...opt,
15
+ onload: resolve,
16
+ onerror: (_event, _source, _lineno, _colno, err) => {
17
+ reject(err || new Error(`loadScript failed: ${src}`));
18
+ },
19
+ });
20
+ document.head.append(s);
21
+ });
22
+ }
23
+ /**
24
+ * Default options:
25
+ * rel: 'stylesheet'
26
+ *
27
+ * No other options are set by default.
28
+ */
29
+ export async function loadCSS(href, opt) {
30
+ if (isServerSide())
31
+ return;
32
+ return await new Promise((resolve, reject) => {
33
+ const link = _objectAssign(document.createElement('link'), {
34
+ href,
35
+ rel: 'stylesheet',
36
+ // type seems to be unnecessary: https://stackoverflow.com/a/5409146/4919972
37
+ // type: 'text/css',
38
+ ...opt,
39
+ onload: resolve,
40
+ onerror: (_event, _source, _lineno, _colno, err) => {
41
+ reject(err || new Error(`loadCSS failed: ${href}`));
42
+ },
43
+ });
44
+ document.head.append(link);
45
+ });
46
+ }
@@ -0,0 +1,134 @@
1
+ // Modified version of topbar:
2
+ // http://buunguyen.github.io/topbar
3
+ /* eslint-disable */
4
+ const browser = typeof window !== 'undefined';
5
+ let canvas;
6
+ let progressTimerId;
7
+ let fadeTimerId;
8
+ let currentProgress;
9
+ let showing;
10
+ const addEvent = (elem, type, handler) => {
11
+ if (elem.addEventListener)
12
+ elem.addEventListener(type, handler, false);
13
+ else if (elem.attachEvent)
14
+ elem.attachEvent('on' + type, handler);
15
+ else
16
+ elem['on' + type] = handler;
17
+ };
18
+ const options = {
19
+ autoRun: true,
20
+ barThickness: 5,
21
+ barColors: {
22
+ '0': 'rgba(26, 188, 156, .9)',
23
+ '.25': 'rgba(52, 152, 219, .9)',
24
+ '.50': 'rgba(241, 196, 15, .9)',
25
+ '.75': 'rgba(230, 126, 34, .9)',
26
+ '1.0': 'rgba(211, 84, 0, .9)',
27
+ },
28
+ shadowBlur: 10,
29
+ shadowColor: 'rgba(0, 0, 0, .6)',
30
+ };
31
+ const repaint = () => {
32
+ canvas.width = window.innerWidth;
33
+ canvas.height = options.barThickness * 5; // need space for shadow
34
+ const ctx = canvas.getContext('2d');
35
+ ctx.shadowBlur = options.shadowBlur;
36
+ ctx.shadowColor = options.shadowColor;
37
+ const lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
38
+ for (const stop in options.barColors) {
39
+ // @ts-ignore
40
+ lineGradient.addColorStop(stop, options.barColors[stop]);
41
+ }
42
+ ctx.lineWidth = options.barThickness;
43
+ ctx.beginPath();
44
+ ctx.moveTo(0, options.barThickness / 2);
45
+ ctx.lineTo(Math.ceil(currentProgress * canvas.width), options.barThickness / 2);
46
+ ctx.strokeStyle = lineGradient;
47
+ ctx.stroke();
48
+ };
49
+ const createCanvas = () => {
50
+ canvas = document.createElement('canvas');
51
+ const style = canvas.style;
52
+ style.position = 'fixed';
53
+ style.top = style.left = style.right = style.margin = style.padding = 0;
54
+ style.zIndex = 100001;
55
+ style.display = 'none';
56
+ document.body.appendChild(canvas);
57
+ addEvent(window, 'resize', repaint);
58
+ };
59
+ export const topbar = {
60
+ config(opts) {
61
+ for (const key in opts) {
62
+ if (options.hasOwnProperty(key)) {
63
+ // @ts-ignore
64
+ options[key] = opts[key];
65
+ }
66
+ }
67
+ },
68
+ set(show, opts) {
69
+ if (show) {
70
+ topbar.show(opts);
71
+ }
72
+ else {
73
+ topbar.hide();
74
+ }
75
+ },
76
+ show(opts) {
77
+ if (!browser)
78
+ return; // ssr protection
79
+ if (opts)
80
+ topbar.config(opts);
81
+ if (showing)
82
+ return;
83
+ showing = true;
84
+ if (fadeTimerId !== null) {
85
+ window.cancelAnimationFrame(fadeTimerId);
86
+ }
87
+ if (!canvas)
88
+ createCanvas();
89
+ canvas.style.opacity = 1;
90
+ canvas.style.display = 'block';
91
+ topbar.progress(0);
92
+ if (options.autoRun) {
93
+ ;
94
+ (function loop() {
95
+ progressTimerId = window.requestAnimationFrame(loop);
96
+ topbar.progress('+' + 0.05 * (1 - Math.sqrt(currentProgress)) ** 2);
97
+ })();
98
+ }
99
+ },
100
+ progress(to) {
101
+ if (!browser)
102
+ return; // ssr protection
103
+ if (typeof to === 'undefined') {
104
+ return currentProgress;
105
+ }
106
+ if (typeof to === 'string') {
107
+ to = (to.indexOf('+') >= 0 || to.indexOf('-') >= 0 ? currentProgress : 0) + parseFloat(to);
108
+ }
109
+ currentProgress = to > 1 ? 1 : to;
110
+ repaint();
111
+ return currentProgress;
112
+ },
113
+ hide() {
114
+ if (!showing || !browser)
115
+ return;
116
+ showing = false;
117
+ if (progressTimerId != null) {
118
+ window.cancelAnimationFrame(progressTimerId);
119
+ progressTimerId = null;
120
+ }
121
+ ;
122
+ (function loop() {
123
+ if (topbar.progress('+.1') >= 1) {
124
+ canvas.style.opacity -= 0.05;
125
+ if (canvas.style.opacity <= 0.05) {
126
+ canvas.style.display = 'none';
127
+ fadeTimerId = null;
128
+ return;
129
+ }
130
+ }
131
+ fadeTimerId = window.requestAnimationFrame(loop);
132
+ })();
133
+ },
134
+ };
@@ -1,4 +1,6 @@
1
- import { _isPrimitive, MISS, pDelay } from '..';
1
+ import { _isPrimitive } from '../is.util';
2
+ import { pDelay } from '../promise/pDelay';
3
+ import { MISS } from '../types';
2
4
  export const jsonMemoSerializer = args => {
3
5
  if (args.length === 0)
4
6
  return undefined;
@@ -0,0 +1,38 @@
1
+ import { _getTargetMethodSignature } from './decorator.util';
2
+ /**
3
+ * Prevents "swarm" of async calls to the same method.
4
+ * Allows max 1 in-flight promise to exist.
5
+ * If more calls appear, while Promise is not resolved yet - same Promise is returned.
6
+ *
7
+ * Does not support `cacheKey`.
8
+ * So, the same Promise is returned, regardless of the arguments.
9
+ */
10
+ // eslint-disable-next-line @typescript-eslint/naming-convention
11
+ export const _SwarmSafe = () => (target, key, descriptor) => {
12
+ if (typeof descriptor.value !== 'function') {
13
+ throw new TypeError('@_SwarmSafe can be applied only to methods');
14
+ }
15
+ const originalFn = descriptor.value;
16
+ const keyStr = String(key);
17
+ const methodSignature = _getTargetMethodSignature(target, keyStr);
18
+ const instanceCache = new Map();
19
+ console.log('SwarmSafe constructor called', { key, methodSignature });
20
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
21
+ descriptor.value = function (...args) {
22
+ console.log('SwarmSafe method called', { key, methodSignature, args });
23
+ const ctx = this;
24
+ let inFlightPromise = instanceCache.get(ctx);
25
+ if (inFlightPromise) {
26
+ console.log(`SwarmSafe: returning in-flight promise`);
27
+ return inFlightPromise;
28
+ }
29
+ console.log(`SwarmSafe: first-time call, creating in-flight promise`);
30
+ inFlightPromise = originalFn.apply(ctx, args);
31
+ instanceCache.set(ctx, inFlightPromise);
32
+ void inFlightPromise.finally(() => {
33
+ console.log(`SwarmSafe: in-flight promise resolved`);
34
+ instanceCache.delete(ctx);
35
+ });
36
+ return inFlightPromise;
37
+ };
38
+ };