Last active
May 9, 2024 11:48
-
-
Save doeixd/8960a4ef6aab93c84d7e68b359228ffd to your computer and use it in GitHub Desktop.
lib.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function ensureRunAfterDOM (fn) { | |
let handleDOMLoaded = Fn(fn) | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', handleDOMLoaded); | |
} else { | |
handleDOMLoaded(); | |
} | |
} | |
function Fn (fn) { | |
return function (...args) { | |
return fn(...args) | |
} | |
} | |
let isString = value => typeof value === 'string' || value instanceof String; | |
function createObserver(fn, options) { | |
const observer = new MutationObserver(fn) | |
return function (element) { | |
if (isString(element)) element = document.querySelector(element) | |
observer.observe(element, options) | |
} | |
} | |
function addEventListenerTo(selector, event, callback, parent = 'body') { | |
const handler = Fn(callback) | |
const addListener = element => { | |
element.addEventListener(event, handler) | |
} | |
function addListenerToMatches() { | |
const parentElement = document.querySelector(parent); | |
const matchingChildren = parentElement.querySelectorAll(selector) | |
addListenerToElements(matchingChildren) | |
} | |
function addListenerToElements(elements) { | |
for (let element of [...elements]) { | |
addListener(element) | |
} | |
} | |
const getAddedElements = mutations => { | |
let addedElements = [] | |
for (let mutation of mutations) { | |
for (let element of mutation.addedNodes) { | |
addedElements.push(element) | |
if (!(element instanceof HTMLElement)) continue | |
const descendants = element.querySelectorAll('*') | |
addedElements.push(...descendants) | |
} | |
} | |
return addedElements | |
} | |
ensureRunAfterDOM(() => { | |
addListenerToMatches() | |
createObserver((mutations) => { | |
const addedElements = getAddedElements(mutations) | |
const matchingElements = addedElements.filter(addedElement => addedElement?.matches?.(selector)) | |
addListenerToElements(matchingElements) | |
}, {childList: true, subtree: true}) | |
(parent); | |
}) | |
} | |
function JS (obj) { | |
return JSON.stringify(obj, null, 2) | |
} | |
function clone (obj) { | |
return JSON.parse(JSON.stringify(obj)) | |
} | |
function entries (obj, one = false) { | |
return Object.entries(obj).map((o, i) => { | |
return one ? [i + 1, o[1]] : o | |
}) | |
} | |
} | |
function isType (type, value) { | |
const isIt = val => { | |
if (type == null || value == null) return value === type | |
return Object.getPrototypeOf(value ?? {}).constructor === type | |
} | |
if (!value) return function (value) { | |
return isIt(value) | |
} | |
return isIt(value) | |
} | |
function applyInlineStylesToElement(element) { | |
return function(styles) { | |
const oldStyles = JSON.parse(JSON.stringify(element.style)) | |
const applyStyles = obj => { | |
Object.entries(obj).forEach(([key, value]) => { | |
element.style[key] = value | |
}) | |
} | |
return { | |
apply: (additionalStyles) => { | |
applyStyles({...styles, ...additionalStyles}) | |
}, | |
revert: () => { | |
applyStyles(oldStyles) | |
} | |
} | |
} | |
} | |
function randomIndex(arr, opts) { | |
const idx = Math.floor(Math.random() * arr.length) | |
return idx | |
} | |
function randomItem(arr, opts) { | |
const idx = Math.floor(Math.random() * arr.length) | |
return arr[idx] | |
} | |
function dedup(arr) { | |
return [...new Set(arr)] | |
} | |
function dedup_words(str = '') { | |
str = String(str) | |
return dedup(str.split(' ')).join(' ') | |
} | |
function structurally_deduplicate_array_of_objects(array) { | |
let map = new Map() | |
for (let object of array) { | |
let _hash = JSON.stringify(object) | |
if (!map.has(_hash)) map.set(_hash, object) | |
} | |
return Array.from(map.values()) | |
} | |
function html(html) { | |
var template = document.createElement('template'); | |
html = html.trim(); | |
template.innerHTML = html; | |
return template.content.firstChild; | |
} | |
const identity = (strings, ...values) => String.raw({ raw: strings }, ...values); | |
function* range(start, end, step) { | |
let current = start; | |
while (current <= end) { | |
yield current; | |
current += step; | |
} | |
} | |
// -------- | |
const $ = (...args) => document.querySelector(...args) | |
const $$ = (...args) => Array.from(document.querySelectorAll(...args)) | |
(window || {}).$ = $ | |
(window || {}).$$ = $$ | |
function convertColor(color, toSpace) { | |
let div = document.createElement('div') | |
div.style.color = `color-mix(in ${toSpace}, ${color} 100%, transparent)` | |
div.style.display = 'none' | |
document.body.appendChild(div) | |
let result = window.getComputedStyle(div).color | |
div.remove() | |
return result | |
} | |
export function setQueryParam(key, value, type = 'soft') { | |
const url = new URL(window.location) | |
url.searchParams.set(key, value) | |
if (type == 'hard') window.location.search = url.href | |
if (type == 'soft') history.pushState(urlParams, '', url.href) | |
} | |
function getQueryParam(key) { | |
const urlParams = new URLSearchParams(window.location.search) | |
urlParams.get(key) | |
window.location.search = urlParams | |
} | |
function getAllQueryParam(key) { | |
const urlParams = new URLSearchParams(window.location.search) | |
urlParams.getAll(key) | |
window.location.search = urlParams | |
} | |
const Params = { | |
get: getQueryParam, | |
set: setQueryParam, | |
getAll: getAllQueryParam | |
} | |
(window || {}).Params = Params | |
// ---- | |
function pick(fields, obj) { | |
let res = {} | |
if (!fields || fields.length == 0) return res | |
fields = [fields].flat() | |
let map = new Set(fields) | |
for (let field of fields) { | |
if (map.has(field)) res[field] = obj?.[field] | |
} | |
return res | |
} | |
function is_object_empty(obj) { | |
return Object.keys(obj).length == 0 | |
} | |
function delete_fields(obj, ...fields) { | |
let _obj = clone(obj) | |
for (let field of fields) { | |
if (Object.hasOwn(_obj, field)) delete _obj[field] | |
} | |
return _obj | |
} | |
function rename_field (obj, from_field, to_field) { | |
let _obj = clone(obj) | |
if (Object.hasOwn(_obj, from_field)) { | |
let from_val = _obj[from_field] | |
delete _obj[from_field] | |
_obj[to_field] = from_val | |
} | |
return _obj | |
} | |
let bound = (obj, val) => { | |
return new Proxy(obj, { | |
get: async (target, key) => { | |
return await target[key]?.(val) | |
} | |
}) | |
} | |
const destructure = (obj, list, defaults = {}) => { | |
if (typeof list === 'object') { | |
if (Array.isArray(list)) { | |
list = list.map((l) => l.trim()); | |
} else { | |
list = Object.keys(list); | |
} | |
} | |
let acc = {}; | |
for (let key of list) { | |
if (typeof obj[key] === 'object' && obj[key] !== null) { | |
acc[key] = destructure(obj[key], list[key], defaults[key] || {}); | |
} else { | |
acc[key] = obj?.[key] !== undefined ? obj[key] : defaults[key]; | |
} | |
} | |
return acc; | |
} | |
const d = (destructure_obj, fn) => { | |
return (args) => { | |
return fn(destructure(args, destructure_obj)) | |
} | |
} | |
const invariant = (_case, msg) => { | |
if (!_case) throw new Error(msg) | |
} | |
const fallback = (subject, fallbacks = [{}], _case = (arg) => Boolean(arg), base = undefined) => { | |
return new Proxy(subject, { | |
get: (target, key) => { | |
let target_val = Reflect.get(target, key) | |
if (_case(target_val)) return target_val | |
fallbacks.reverse() | |
for (let fallback of fallbacks) { | |
let fallback_val = Reflect.get(fallback, key) | |
if (typeof fallback_val == 'function') fallback_val = fallback_val.bind(subject) | |
if (_case(fallback_val)) return fallback_val | |
} | |
return base | |
}, | |
}) | |
} | |
// ------ | |
function where(field, op, value) { | |
op = op == 'includes' ? (pred) => pred.includes(value) : op | |
op = op == 'is' ? (pred) => pred == value : op | |
op = op == 'startsWith' ? (pred) => pred.startsWith(value) : op | |
op = op == 'endsWith' ? (pred) => pred.endsWith(value) : op | |
op = op == 'matches' ? (pred) => value.test(pred) : op | |
return function (subject) { | |
subject = (field && subject?.[field]) ? subject?.[field] : subject | |
return op(subject) | |
} | |
} | |
function not(fn) { | |
return (...args) => !fn(...args) | |
} | |
function exists(field) { | |
return function (subject) { | |
if (field) return subject?.[field] | |
return subject | |
} | |
} | |
function sort(direction = 'asc', on) { | |
let command; | |
if (direction == 'asc') { | |
command = (a, b) => a - b | |
if (on) command = (a, b) => a?.[on] - b?.[on] | |
} | |
if (direction == 'desc') { | |
command = (a, b) => b - a | |
if (on) command = (a, b) => b?.[on] - a?.[on] | |
} | |
return command | |
} | |
function copy_shape (obj) { | |
return Object.fromEntries(Object.keys(obj).map(key => [key, undefined])) | |
} | |
// ----------- | |
function curry(fn, arity = fn.length, ...args) { | |
return arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args); | |
} | |
function* zip(...arrays) { | |
let item_idx = 0 | |
let item = arrays?.[0]?.[0] | |
// let result = [] | |
while (item !== undefined) { | |
for (let array of arrays) { | |
item = array?.[item_idx] | |
if (item === undefined) return | |
// if (item === undefined) return result | |
// result.push(item) | |
yield item | |
} | |
item_idx += 1 | |
} | |
} | |
function s (fn) { | |
return function (a, b) { | |
return fn(b, a) | |
} | |
} | |
function r (...args) { | |
return args.toReversed() | |
} | |
function g (outside) { return inside => (...args) => outside(inside(...args)) } | |
function is_false (value) { return value == false || value === undefined || isNaN(value) || value } | |
function or (fallback, determiner = is_false) { | |
return (fn) => (...args) => { | |
let value = fn(...args) | |
if (determiner(value)) return fallback | |
return value | |
} | |
} | |
function compose (fns, og) { return (...args) => fns.reduce((acc, fn) => fn(acc), og(...args)) } | |
var trait = curry(compose) | |
function last (fn, ...first_args) { return (...last_args) => { | |
return fn(...[...first_args, ...last_args]) | |
} } | |
function first (fn, ...last_args) { return (...first_args) => { | |
return fn(...[...first_args, ...last_args]) | |
} } | |
// ------------ | |
function eq (val) { return x => x == val } | |
function till (cond_fn, val_fn) { return (...args) => { | |
const value = val_fn(...args) | |
const cond_val = cond_fn(value) | |
return [cond_val, value] | |
} } | |
function count (start = 0, inc = 1) { return count + inc } | |
function range_ (start, stop, inc) { return stream(till(eq(stop), (prev = start) => count(prev, inc)))() } | |
function* stream (fn) { | |
let result = fn() | |
let should_stop = false | |
while (!should_stop) { | |
[should_stop, result] = fn(result) | |
if (should_stop) break; | |
yield result | |
} | |
return result | |
} | |
// --------- | |
function match(regex, value) { | |
return String(value).match(regex)?.[0] | |
} | |
// --------- | |
function wait(ms, fn) { | |
return new Promise((resolve) => { | |
setTimeout(() => resolve(fn().then((l) => l)), ms) | |
}) | |
} |
Author
doeixd
commented
May 8, 2024
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment