Skip to content

Instantly share code, notes, and snippets.

View jamiebuilds's full-sized avatar

Jamie Kyle jamiebuilds

View GitHub Profile
// Base API
{
let url = new URLPath("./one")
.append(new URLPath("?one=1"), "/two")
.param("two", 2)
.append(new URLPath().append(`./three?three=3`), "./four?four=4")
.append("?five=5")
.params({ six: 6 })
.toURL("https://example.com?v=1")
// URL { "https://example.com/one/two/three/four?v=1&one=1&two=2&three=3&four=4&five=5&six=6" }
import { Dispatch, SetStateAction, useCallback, useState } from "react";
/**
* Returns a stateful value, its previous value, and a function to update it.
*/
export function useStateWithPrev<S>(
initialState: S | (() => S),
initialPrevState: S | (() => S)
): [prevState: S, state: S, setState: Dispatch<SetStateAction<S>>];
// convenience overload when second argument is omitted
/**

Multiple GitHub accounts (Work vs Personal)

This setup uses some tricks to ensure that the right email/name/ssh-key is used for the right repos without having to think about it ever again.

  • First generate two SSH keys, ~/.ssh/id_ed25519 and ~/.ssh/id_ed25519_work
  • Add one key to your personal account and the other to your work account

.ssh/config

/**
* If you have a volume input (0 to 1), such as a slider, use this to convert
* it to a logarithmic volume that is closer to human perception (0 to 1).
*
* A more robust approach would use a Fletcher-Munson curve, however this is a
* close enough approximation for most use cases.
*
* You can customize the curve to your liking, generally 3-4 is a good value.
*
* The inverse of this function is {@link convertVolumeToInput}.
@jamiebuilds
jamiebuilds / main.js
Created June 16, 2023 19:20
dialog.showSaveDialog force normalized extension
let { app, dialog } = require('electron')
let path = require("path")
let pathCompleteExtname = require("path-complete-extname")
app.whenReady().then(async () => {
for (let defaultPath of ['change-me.txt', 'change-me.tar.gz', 'change-me.jpeg']) {
let defaultExtname = pathCompleteExtname(defaultPath)
let defaultBasename = path.basename(defaultPath, defaultExtname)
let { canceled, filePath: selectedPath } = await dialog.showSaveDialog({
@jamiebuilds
jamiebuilds / main.js
Last active June 14, 2023 17:49
showSaveDialog without filters bug
let { app, dialog } = require('electron')
app.whenReady().then(async () => {
// case 1: without filters (BUG)
{
let { canceled, filePath } = await dialog.showSaveDialog({
defaultPath: "change-me.txt",
})
if (canceled) return
await dialog.showMessageBox({
@jamiebuilds
jamiebuilds / index.html
Last active June 14, 2023 17:10
Save Dialog
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
export type QueryTemplateParam = string | number | undefined;
export type QueryFragmentValue = QueryFragment | QueryTemplateParam;
export type QueryFragment = [
{ fragment: string },
ReadonlyArray<QueryTemplateParam>
];
/**
* You can use tagged template literals to build "fragments" of SQL queries

Bump all npm outdated dependencies

npm install $(npm outdated --json | jq -r 'to_entries | map("\(.key)@^\(.value.latest)") | join(" ")')
npm run typecheck