Skip to content

Instantly share code, notes, and snippets.

View ccnokes's full-sized avatar

Cameron Nokes ccnokes

View GitHub Profile
@ccnokes
ccnokes / serial.ts
Last active November 17, 2022 23:07
Takes a list of async functions and executes them one at a time. You could just use a `for of` loop for this too 😅
/**
* takes a list of async functions and executes them one at a time
*/
async function serial(
fnList: Array<() => Promise<void>>,
nextIndex?: number = 0,
) {
let current = fnList[nextIndex];
if (!current) return;
@ccnokes
ccnokes / CacheStore.ts
Created April 12, 2021 22:40
A persistent, async store based on Cache API
/**
* A persistent, async store based on Cache API
* NOTE this is experimental
**/
type Options = {
name: string
version: number
userId: string
type: "json" | "text" | "blob"
function getMonths(locale: string, format: 'long' | 'short' = 'long') {
const monthNames = [];
const currentYear = new Date().getFullYear();
for (let i = 0; i < 12; i++) {
monthNames.push(
new Date(currentYear, i, 1).toLocaleDateString(locale, {
month: format
}));
}
return monthNames;
@ccnokes
ccnokes / heic_to_jpeg.sh
Last active November 21, 2022 16:06
Bash script that converts .HEIC to .jpg files
#!/bin/bash
set -eu -o pipefail
count=$(find . -depth 1 -name "*.HEIC" | wc -l | sed 's/[[:space:]]*//')
echo "converting $count files .HEIC files to .jpg"
magick mogrify -monitor -format jpg *.HEIC
echo "Remove .HEIC files? [y/n]"
@ccnokes
ccnokes / useCallIfMounted.tsx
Last active April 6, 2020 16:38
React hook that only runs a function if the component is still mounted. React says this is an anti-pattern, but vanilla Promises and async DOM APIs don't give you a way to cancel async actions 🤷‍♂️.
function useCallIfMounted() {
let isMounted = React.useRef(false);
React.useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, [isMounted]);
return React.useCallback((fn) => isMounted.current && fn(), [isMounted]);
}
@ccnokes
ccnokes / memoize.ts
Last active November 1, 2020 18:31
A memoize function is only as good as its cache
// NOTE can't use lodash.memoize because we need 2 caches and v2 doesn't support replacing the cache implementation
// TS type note: the below type is inspired by Lodash v4 types
function memoize<T extends (...args: any) => any>(fn: T, resolver?: (...args: any[]) => any) {
let objectCache = new WeakMap<any, any>(); // WeakMap can only store objects as keys and allows the objects to get GC'ed out
let primitiveCache = new Map<any, any>(); // Map can store anything as a key but no auto GC
return function(...args: Parameters<T>): ReturnType<T> {
// just in case things got out of hand, dump the cache if too many entries
if (primitiveCache.size > 10000) {
primitiveCache.clear();
}
@ccnokes
ccnokes / check-for-electron-apps.sh
Last active December 8, 2022 12:10
Bash script that checks for apps that use Electron. Detailed a bit more here: https://cameronnokes.com/blog/how-to-know-if-a-desktop-app-uses-electron/
#!/bin/bash
target="${1:-/Applications}"
check() {
stat "$1/Contents/Frameworks/Electron Framework.framework" &> /dev/null
if [[ $? = 0 ]]; then
echo "$1 uses Electron"
fi
}
@ccnokes
ccnokes / ObservableMap.ts
Last active November 5, 2019 04:58
A Map class with built-in event emitter that emits a `update` event whenever something changes
class ObservableMap<K, V> extends Map<K, V> {
readonly events = new EventEmitter();
set(key: K, value: V) {
let previousValue = this.get(key);
super.set(key, value);
this.events.emit("update", {
key,
action: "set",
previousValue,
currentValue: this.get(key)
class EventEmitter {
constructor() {
this.target = new EventTarget();
}
on(eventName, listener) {
return this.target.addEventListener(eventName, listener);
}
once(eventName, listener) {
return this.target.addEventListener(eventName, listener, { once: true });
}
@ccnokes
ccnokes / event-emitter.js
Created October 30, 2019 15:50
Event emitter using the native DOM APIs: EventTarget and CustomEvent
// Who needs eventemitter3, mitt, or some other library when you can use native DOM APIs? 😁
let eventEmitter = new EventTarget();
eventEmitter.addEventListener('test', console.log); // CustomEvent { type: 'test', detail: 123, ... }
eventEmitter.dispatchEvent(new CustomEvent('test', { detail: 123 }));