/* A bunch of handy helper functions and classes. */ import { log } from "./logging.js"; /* A simple function that uses the Crypto.getRandomValues to fill an array with 32 bit random numbers. */ export const random_numbers = (count) => { let out = new Uint32Array(count); return window.crypto.getRandomValues(out); } /* The stupid web crypto api doesn't have the randomInt function that the node crypto library has so I have to hack in this complete BS. I really hate browser manufacturers. ___FOOTGUN___: This isn't cryptographically secure. It's mostly just used for generating random CSS ids and ___not___ for anything secure. */ export const rand_int = (min, max) => { return Math.floor((Math.random() * (max - min)) + min); } /* Partially taken from [a blog by Lea Verou](https://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/) on how to externally resolve a promise. You can use this in the very common situation in svelte where you need to await on some condition, but you can't create the promise until the onMount. Instead start it off with defer() then resolve it when you're ready. The main reason I use this is to block some processing until a later event happens, but that later thing happens randomly or in some other part of the code. Another great place for `defer()` is when you have an old callback style system and you need to wait for it in a separate `async/await` function. For example, if you want to wait on a resource to be available in the browser, then you can create a `defer()`, grab the resource, and anything that needs this resource can wait on the `defer()` to know it's ready. As an example, pretend the `setTimeout` in this code is a video downloading: ```javascript const video_promise = defer("blocker"); // pretend this is loading a video setTimeout(() => blocker.resolve(), 3000); const play_video => async() => { await video_promise; // do some stuff after the video is ready } onMount(() => await play_video()); ``` In this code we don't want to play the video until it's totally ready (which we're pretending to do with a `setTimeout`). Rather than some crazy await callback setup we just have a single `video_promise`, wait on it in `play_video`, and whatever does the video loding resolves it. ### Rejection When you call `reject` on the `defer` it will trigger an exception just like with a `Promise.reject`. So the above code can be: ```javascript const play_video => async() => { try { await video_promise; // do some stuff after the video is ready } catch(error) { console.error(error); } } ``` If the video loading code has a problem it can _signal this_ by rejecting the `defer`. That will trigger error handling with simple `try/catch` semantics. + `debug string` -- A debug message to print out whenever this is resolved or rejected. + ___return___ `Promise` -- A normal `Promise`, but now it's `reject` and `resolve` callbacks are attached as methods you can call. */ export const defer = (debug="") => { let res; let rej; let promise = new Promise((resolve, reject) => { if(debug) { res = (result) => { log.debug("resolved defer", debug); resolve(result); } rej = (error) => { log.debug("REJECT defer", debug); reject(error); } } else { res = resolve; rej = reject; } }); promise.resolve = res; promise.reject = rej; return promise; } /* This implements a very simple Mutex style blocker for coordinating multiple async actors waiting on a single resource. The best example is in the `client/components/Source.svelte` where a `Mutex` is used so that mutliple pages are blocked until an external slow JavaScript source is loaded. This hardly never comes up in JavaScript, but when it does you really need something like this. In the `Source.svelte` code we want to load an external `.js` code, but multiple pages might try to load it at the same time. The solution is to: 1. Keep track of what sources have already loaded. 2. Use a single `Mutex` all `Source.svelte` objects use in a `context="module"`. 3. When `Source.svelte` runs it checks to see if the code is already loaded after calling `lock.hold()`. This makes sure that it's waited its turn to try to load the source. 4. If it is ready then it immediately releases the lock. 5. If it hasn't been loaded yet then it injects the `
{#if visible} Shown! {:else} Hidden! {/if}
``` In this example (from `client/components/IsVisible.svelte`) you simply have some callbacks that change a variable and Svelte then displays the correct text. It's also used in `client/components/SnapImage.svelte`. */ export const visibility = (node, opts) => { let options = opts || {}; // NOTE: does JS default values have the ruby problem? const callback = (entries) => { for(let entry of entries) { if(entry.isIntersecting) { node.dispatchEvent(new CustomEvent("visible", { detail: entry })); } else { node.dispatchEvent(new CustomEvent("hidden", { detail: entry })); } } } const vis_observer = new IntersectionObserver(callback, options || {}); vis_observer.observe(node); return create_observer_cleanup(vis_observer, node, options); } /* Similar to `visibility()` it's a Svelte `use:` function that adds resize detection to a node. Not really used in my code, but this comes up whenever you _must_ maintain an aspect ratio and CSS simply can't pull it off. It will be slow though, and use a lot of CPU, but sometimes that's all you can do. */ export const resize = (node, options) => { const callback = (entries) => { entries.forEach(entry => { node.dispatchEvent(new CustomEvent("resize", { detail: entry })); }); } const resize_observer = new ResizeObserver(callback, options || {}); resize_observer.observe(node); return create_observer_cleanup(resize_observer, node, options); }