SnippetsSnippets
Convert to webp

Shell script to convert all files in directory to webp, with default params, or standard cwebp params passed from command.

#!/bin/bash

PARAMS=('-m 6 -q 70 -mt -af -progress')

if [ $# -ne 0 ]; then
	PARAMS=$@;
fi

cd $(pwd)

shopt -s globstar nullglob nocaseglob extglob

To fix problems with globstar(especially on MacOS), use this gist.

A TypeScript type alias called Prettify. It takes a type as its argument and returns a new type that has the same properties as the original type, but the properties are not intersected. This means that the new type is easier to read and understand.

type Prettify<T> = {
  [K in keyof T]: T[K];
} & {};

For deeply nested types:

type Prettify<T> = {
  [K in keyof T]: T[K] extends object ? Prettify<T[K]> : T[K];
} & {};

Use case

Suppose we have a type that has many intersections:

export type SimpleShape = {
  color: string;
} & {
  size: number;
} & {
  shape: 'circle' | 'square';
};

When we hover over the type SimpleShape with many intersections, it can be difficult to see the resolved type. It would be helpful if there was a way to prettify the display of these types.

With Prettify we can exactly do so, using:

type Shape = Prettify<SimpleShape>;
//   ^? type Shape = {
//        color: string;
//        size: number;
//        shape: "circle" | "square";
//      }

Pick keys from an object to construct a picked object, with full type support.

const pick = <T, K extends keyof T>(obj: T, keys: K[]) =>
  keys.reduce(
    (pickedObj, currentKey) => {
      pickedObj[currentKey] = obj[currentKey];
      return pickedObj;
    },
    {} as Pick<T, K>
  );

Example Usage

const x = { a: 1, b: 2, c: 3 };
const y = pick(x, ['b']);
// { b: 2 }
Stream download a file

Download and save a file using streams in Node.js, great for downloading large files without clogging up memory.

import { createWriteStream } from 'node:fs';
import { Readable } from 'node:stream';
import { finished } from 'node:stream/promises';

export const downloadFile = async (
  url: string,
  fileName: string,
  encoding: string
) => {
  const writeStream = createWriteStream(fileName, { encoding });
  // Use body since it is a readable stream
  console.log('Starting File download...');
  const { body } = await fetch(url);
  if (body) {
    //   Pipe body to the writestream
    await finished(Readable.fromWeb(body as any).pipe(writeStream));
    console.log('File download successful!');
  } else {
    throw new Error('File empty!');
  }
};
Add Inter as a variable font

Paste the following code in the global CSS file:

@font-face {
  font-family: 'Inter';
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
  font-named-instance: 'Regular';
  src: url('/fonts/Inter.var.woff2') format('woff2');
}
Keys of Type

Extract all the keys of a certain type T from object O.

type KeysOfType<O, T> = {
  [K in keyof O]: O[K] extends T ? K : never;
}[keyof O & string];

Use case

Suppose we have a type User like so:

type User = {
  id: string;
  name: string;
  isAdmin: boolean;
  isEnabled: boolean;
  hasVerifiedEmail: boolean;
};

And we want to extract all the boolean fields from User and put them in a new type called UserFlags. We can do so by:

type UserFlags = KeysOfType<User, boolean>;
//   ^? type UserFlags = "isAdmin" | "isEnabled" | "hasVerifiedEmail"
Is in Viewport

Check if an element is in the browser viewport or not.

const isInViewport = (element: HTMLElement) => {
  const { top, right, bottom, left } = element.getBoundingClientRect();
  return (
    top >= 0 &&
    left >= 0 &&
    bottom <= window.innerHeight &&
    right <= window.innerWidth
  );
};
Get formatted date with ordinals

Get date formatted with ordinals (st, nd, rd, th, etc) using JavaScript's Intl.

const getFormattedDate = (dateString: string, locale = 'en-IN') => {
  const ordinalMap: Partial<Record<Intl.LDMLPluralRule, string>> = {
    one: 'st',
    two: 'nd',
    few: 'rd',
    other: 'th',
  };
  const ordinalPluralRules = new Intl.PluralRules(locale, { type: 'ordinal' });

  const formatter = new Intl.DateTimeFormat(locale, {
    day: 'numeric',
    month: 'long',
    year: 'numeric',
  });
  const dateParts = formatter.formatToParts(new Date(dateString));

  // Modify day part by adding ordinal
  const dayPart = dateParts.find((p) => p.type === 'day')!;
  const ordinal = ordinalMap[ordinalPluralRules.select(Number(dayPart.value))];
  dayPart.value = dayPart.value + ordinal;

  return dateParts.map((p) => p.value).join('');
};

Example usage and outputs:

getFormattedDate('03-22-2023');
// "22nd March 2023"

getFormattedDate('08-17-2023', 'en-US');
// "August 17th, 2023"

getFormattedDate('12-31-2023', 'en-GB');
// "31st December 2023"

Limitations

This logic only works for English locales and might not work for others like French, German, etc.

Display prices in INR

Display prices with the Rupee symbol, Indian numeric system commas and 2 decimal digits:

const displayInr = (amount: number) =>
  '₹ ' +
  new Intl.NumberFormat('en-IN', {
    maximumFractionDigits: 2,
    minimumFractionDigits: 2,
  }).format(amount);

Example usage and outputs:

displayInr(23);
// "₹ 23.00"

displayInr(1007.5);
// "₹ 1,007.50"

displayInr(1299999.98999);
// "₹ 12,99,999.99"

Wait for a period of time in JavaScript.

export const delay = async (millis: number) =>
  new Promise((resolve) => setTimeout(resolve, millis));

Example Usage

// Wait for 2 seconds
await delay(2000);

Bonus: Node.js

If in node, you could also just do

import { setTimeout as delay } from 'node:timers/promises';

// Wait for 2 seconds
await delay(2000);
Get formatted date for a timezone

Convert a timestamp in millis to a particular timezone using JavaScript's Intl.

const getDateForTimeZone = (
  millis: number,
  tz: string,
  locale = 'en-IN'
): string => {
  const dateFormatter = new Intl.DateTimeFormat(locale, {
    timeZone: tz,
    day: 'numeric',
    month: 'short',
    year: 'numeric',
  });

  const timeFormatter = new Intl.DateTimeFormat(locale, {
    timeZone: tz,
    timeStyle: 'short',
  });

  const timezoneFormatter = new Intl.DateTimeFormat(locale, {
    timeZone: tz,
    timeZoneName: 'long',
  });

  return [
    `${dateFormatter.format(millis)}`,
    `${timeFormatter.format(millis).toUpperCase()}`,
    `${timezoneFormatter.formatToParts(millis).find((p) => p.type === 'timeZoneName')?.value || ''}`,
  ].join(' ');
};

Example usage and outputs:

getDateForTimeZone(1690113193308, 'Asia/Kolkata');
// 23 Jul 2023 5:23 PM India Standard Time

getDateForTimeZone(1690113193308, 'America/Chicago', 'en-US');
// Jul 23, 2023 6:53 AM Central Daylight Time

getDateForTimeZone(1690113193308, 'Europe/Paris', 'fr-FR');
// 23 juil. 2023 13:53 heure d’été d’Europe centrale
Copy text to clipboard
const copyToClipboard = async (textToCopy: string) => {
  if (!navigator.clipboard)
    throw Error('Clipboard API not supported in current browser');
  if (!window.isSecureContext) throw Error('Can only copy in secure contexts');

  await navigator.clipboard.writeText(textToCopy);
};

Fallback Copy (Deprecated)

const fallbackCopyToClipboard = (textToCopy: string) => {
  // Create textarea in Document, focus and select
  let textArea = document.createElement('textarea');
  textArea.value = textToCopy;
  textArea.style.position = 'fixed';
  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();

  try {
    let successful = document.execCommand('copy');
    if (!successful) {
      throw new Error('execCommand failed to copy to clipboard');
    }
  } catch (err) {
    throw new Error(`execCommand failed: ${err}`);
  } finally {
    // Remove the created Textarea
    document.body.removeChild(textArea);
  }
};
Merge classNames helper

Simple version

export const cn = (...classes: (boolean | string)[]) =>
  classes.filter(Boolean).join(' ');

Advanced version with twMerge

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...classes: ClassValue[]) {
  return twMerge(clsx(classes));
}
Convert base64 string to blob
const convertBase64toBlob = (base64String: string, mimeType: string) => {
  const byteCharacters = atob(base64String);
  const byteArrays = [];
  const sliceSize = 512;

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);
    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }
    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }
  return new Blob(byteArrays, { type: mimeType });
};