base-class.ts

export class BaseClass<T extends object> {
  constructor(values: T) {
      Object.assign(this, values);
  }
}

// @ts-expect-error: Typescript doesn't understand that BaseClass will always implement interface T due to Objet.assign(this, values)
export interface BaseClass<T extends object> extends T {

}

// Usage:
class SomeClass extends BaseClass<{ name: string }> {

}

const foo = new SomeClass({ name: 'a' });

foo.name // string

execute-expression.ts

export type Exp = {
  fn: string;
  inputs: { [id: string]: string };
  outputs: { [id: string]: string };
}[];

export type Cmp = {
  name: string;
  inputs: { [id: string]: Var };
  outputs: { [id: string]: Var };
  expression: Exp;
};

export type Var = {
  name: string;
  type: string;
};

export type Data = {
  name: string;
  type: string;
  value: any;
};

export type Fn = {
  name: string;
  inputs: { [id: string]: Var };
  outputs: { [id: string]: Var };
  body: Function;
};

export function execute(
  expression: Exp,
  context: { [id: string]: Cmp | Data | Fn }
) {
  const builtupValues: { [id: string]: Cmp | Data | Fn } = {};

  expression.forEach((step) => {
    const fn = lookup(step.fn, "fn", context) as Fn;
    const inputs = toObject(
      Object.keys(step.inputs).map((key) => {
        const variableName = step.inputs[key];
        let variableValue = lookup(variableName, fn.inputs[key].type, context, builtupValues);
        
        if (typeof variableValue == 'object' && 'type' in variableValue && 'value' in variableValue) {
          variableValue = variableValue.value;
        }
        
        return { [key]: variableValue };
      })
    );

    console.log(inputs);

    const fnOutputs = fn.body(inputs);

    Object.keys(step.outputs).forEach((key) => {
      const fnOutputVariable = step.outputs[key];

      builtupValues[key] = lookup(
        fnOutputVariable,
        fn.outputs[fnOutputVariable].type,
        fnOutputs
      );
    });

    console.log(builtupValues)
  });

  return builtupValues;
}

function toObject(values: { [a: string]: any }[]) {
  const result = {};

  values.forEach((value) => {
    Object.keys(value).forEach((key) => {
      result[key] = value[key];
    });
  });

  return result;
}

export function lookup(
  id: string,
  type: string,
  ...contexts: { [id: string]: Cmp | Data | Fn }[]
) {
  for (let i = 0; i < contexts.length; i++) {
    const context = contexts[i];
    if (id in context) {
      console.log(id, context[id], contexts.map(c => Object.keys(c)).join(','));
      return context[id]
    }
  }

  console.log(id, contexts.map(c => Object.keys(c)).join(','));
  return undefined;
}

console.log(
  execute([{
    fn: 'y',
    inputs: {
      m: 'b'
    },
    outputs: {
      n: 'r'
    },
  }, {
    fn: 'x',
    inputs: {
      a: 'a',
      b: 'n',
    },
    outputs: {
      r: 'r'
    },
  }], {
    a: { name: "a", type: "string", value: "fifty nine + " },
    b: { name: "b", type: "number", value: 5 },
    x: {
      name: "x",
      inputs: { a: { name: "main", type: "any"}, b: { name: "main", type: "any" } },
      outputs: { r: { name: "result", type: "string" } },
      body: ({ a, b }) => ({ r: a + b }),
    },
    y: {
      name: "x",
      inputs: { m: { name: "main", type: "number" } },
      outputs: { r: { name: "result", type: "number" } },
      body: ({ m }) => ({ r: m + 1 }),
    },
  })
);

fixed-table-headers.tsx

export default function FixedTableHeaders() {
  /*
  Key styles
    container: relative overflow-y-auto
    headers: shadow instead of borders, sticky top-0
  */
  return (
    <main className="flex-1 relative h-screen overflow-y-auto">
      <table className="min-w-full">
        <thead className="bg-gray-50">
          <tr>
            <th
              scope="col"
              style={{ boxShadow: "0px 2px 0px 0px rgba(156, 163, 175, 1)" }}
              className="px-6 py-3 text-left text-xs font-medium bg-gray-100 text-gray-500 uppercase tracking-wider sticky top-0"
            >
              Header
            </th>
          </tr>
        </thead>
        <tbody>
          <tr className={1 /* index */ % 2 === 0 ? "bg-white" : "bg-gray-50"}>
            <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
              Foo
            </td>
          </tr>
        </tbody>
      </table>
    </main>
  );
}

generate-array-script-index.ts

/*
  Author: Joshua Ohlman
  Description: Finds all the files in a folder and adds them to an index.ts file as imports & exports
  Name: Export all scripts in dir as an array
*/
import { readdirSync, writeFileSync } from 'fs';
import { resolve } from 'path';

function safeVariableName(path: string) {
  return noExtension(path).replace(/[^a-zA-Z]/g, '')
}
function noExtension(path: string): string {
  return path.replace(/\.[^.]+$/, '');
}

function generateArrayScriptIndex(directoryName: string) {
  const files = readdirSync(directoryName).filter(a => a !== 'index.ts');

  const output = `${
    files.map(file => `import * as ${safeVariableName(file)} from './${noExtension(file)}';`).join('\n')
  }\n\nexport const all = [\n${
    files.map(file => `  ${safeVariableName(file)},`).join('\n')
  }\n]\n\nexport default all;\n`;

  writeFileSync(resolve(directoryName, 'index.ts'), output);
}

generateArrayScriptIndex(process.argv[2])

next-js-get-static-imports-of-files.ts

/*
  Author: Joshua Ohlman
  Description: Render a nextjs route with a list of files as the props
  Name: Get Files as Static Props in NextJs
  Comments: It's pretty crazy how you have to do this, because the next compiler replaces __dirname with '/' you have to explicitly encode the difference
            between the current file & the root directory (see import('../' + ...))

            Also, we get a warning: Critical dependency: the request of a dependency is an expression, which can't be removed
            an alternative is to write a build time step that auto-generates an index which requires each file normally
*/
import { join } from 'path';
import { readdirSync } from 'fs';
import { GetStaticProps } from 'next'


type FilesList = { path: string, component: any }[]

function getFiles(directoryPath: string): Promise<FilesList> {
  const fullPath = join(process.cwd(), directoryPath);
  const paths = readdirSync(fullPath).map(path => join(directoryPath, path));

  return Promise.all(paths.map(async path => {
    const pathWithoutExtension = path.replace(/\.[a-z]+$/, '');
    const component = (await import('../' + pathWithoutExtension)).default;

    return {path, component};
  }))
}

type Props = { files: FilesList }

export const getStaticProps: GetStaticProps<Props> = async (context) => {
  const files = await getFiles('content/services');

  return { props: { files } }
}

next-js-get-static-list-of-files.ts

/*
  Author: Joshua Ohlman
  Description: Render a nextjs route with a list of files as the props
  Name: Import all Files in a directory as Static Props in NextJs
*/
import { join } from 'path';
import { GetStaticProps } from 'next'

type FilesList = { path: string, content: string }[]



type Props = { files: FilesList }

export const getStaticProps: GetStaticProps<Props> = async (context) => {
  /*
    Seems like nextjs wasn't happy with this code being outside of a server-only code block, so I dropped it in here.
    Could probably extract the whole getFiles into a separate module & import it in here.

    The odd thing is that importing join from path still works, but I guess that's because the path module can be imported
    on the client.
  */
  async function getFiles(directoryPath: string): Promise<FilesList> {
    const { readdirSync, readFile } = await import('fs');
    const paths = readdirSync(directoryPath);
  
    return Promise.all(paths.map(async path => {
      const content = await new Promise<string>((resolve, reject) => {
        readFile(join(directoryPath, path), 'utf8', (error, result) => error ? reject(error) : resolve(result))
      });
  
      return {path, content};
    }))
  }

  const files = await getFiles('./content/services');

  return { props: { files } }
}

upload-form.tsx

import React from "react";

export default function UploadForm() {
  const [file, setFile] = React.useState<File | null>(null);
  const [hovering, setHovering] = React.useState<boolean>(false);

  <div
    className={`mt-2 flex justify-center px-6 pt-5 pb-6 border-2 ${
      file || hovering ? "border-blue-300" : "border-gray-300"
    } ${file || hovering ? "border-solid" : "border-dashed"} rounded-md ${
      file || hovering ? "bg-blue-100" : ""
    }`}
    onDrop={(e) => {
      e.preventDefault();

      const file = e.dataTransfer.items[0].getAsFile();

      setFile(file);
    }}
    onDragOver={(e) => {
      e.preventDefault();
      setHovering(true);
    }}
    onDragEnd={(e) => {
      setHovering(false);
    }}
    onDragLeave={(e) => {
      setHovering(false);
    }}
  >
    {file ? (
      <div className="space-y-1 text-center">
        <svg
          className="mx-auto h-12 w-12 text-blue-900"
          stroke="currentColor"
          fill="none"
          viewBox="0 0 48 48"
          aria-hidden="true"
        >
          <path
            d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
            strokeWidth={2}
            strokeLinecap="round"
            strokeLinejoin="round"
          />
        </svg>
        <div className="flex text-sm text-gray-600">{file.name}</div>
        <p className="text-xs text-gray-500">
          Type: {file.type} Size: {(file.size / 1000).toLocaleString()}kb
        </p>
      </div>
    ) : (
      <div className="space-y-1 text-center">
        <svg
          className="mx-auto h-12 w-12 text-gray-400"
          stroke="currentColor"
          fill="none"
          viewBox="0 0 48 48"
          aria-hidden="true"
        >
          <path
            d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
            strokeWidth={2}
            strokeLinecap="round"
            strokeLinejoin="round"
          />
        </svg>
        <div className="flex text-sm text-gray-600">
          {hovering ? (
            <span className="font-bold">Drop to upload</span>
          ) : (
            <>
              <label
                htmlFor="file-upload"
                className="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
              >
                <span>Upload a file</span>
                <input
                  id="file-upload"
                  name="file-upload"
                  type="file"
                  className="sr-only"
                  onChange={(event) => {
                    const file = event.target.files && event.target.files[0];

                    setFile(file);
                  }}
                />
              </label>
              <p className="pl-1">or drag and drop</p>
            </>
          )}
        </div>
        <p className="text-xs text-gray-500">Text files: csv or json</p>
      </div>
    )}
  </div>;
}

zpl-rotate-transform-attempt.ts

type Orientation = "N" | "R" | "I" | "B";

type Rotation = 0 | 90 | 180 | 270;

type Coordinates = [number, number];

export function rotateAndTransformZPL(
  zpl: string,
  rotate: Rotation,
  size: Coordinates, // in pixels/points
  transform: Coordinates
) {
  let result = zpl;

  if (result.match(/\^FW/)) {
    result = result.replace(/\^FW([NRIB])/g, (val) => {
      const currentOrientation = val[3] as Orientation;
      const newOrientation: Orientation = mapOrientation(
        currentOrientation,
        rotate
      );

      return `^FW${newOrientation}`;
    });
  } else if (result.match(/\^XA/)) {
    result.replace(/\^XA/g, `^XA\n^FW${rotationToOrientation[rotate]}`);
  } else {
    result = `^FW${rotationToOrientation[rotate]}\n${result}`;
  }

  result = result.replace(/\^FO\d+,\d+/g, (origin) => {
    const parsedOrigin = origin.match(/\^FO(\d+),(\d+)/);
    const coordinates = [
      parseInt(parsedOrigin[1]),
      parseInt(parsedOrigin[2]),
    ] as Coordinates;

    const rotatedCoordinates = rotateCoordinates(coordinates, rotate, size);
    const transformedCoordinates = transformCoordinates(
      rotatedCoordinates,
      transform
    );

    return `^FO${transformedCoordinates[0]},${transformedCoordinates[1]}`;
  });


  return result;
}

const orientationToRotationMap: { [P in Orientation]: Rotation } = {
  N: 0,
  R: 90,
  I: 180,
  B: 270,
};
const rotationToOrientation: { [P in Rotation]: Orientation } = {
  0: "N",
  90: "R",
  180: "I",
  270: "B",
};

function mapOrientation(orientation: Orientation, rotate: Rotation) {
  const initialRotation = orientationToRotationMap[orientation];
  const newRotation = ((initialRotation + rotate) % 360) as Rotation;

  return rotationToOrientation[newRotation];
}
function rotateCoordinates(
  coordinates: Coordinates,
  rotation: Rotation,
  size: Coordinates
): Coordinates {
  
  if (rotation == 0) return coordinates;

  const top = coordinates[1];
  const left = coordinates[0];
  const width = size[0];
  const height = size[1];

  // 90: left => top, top => right
  if (rotation == 90) return [width - top, left]

  // 180: left => right, top => bottom
  if (rotation == 180) return [width - left, height - top]

  // 270: left => bottom, top => left
  if (rotation == 270) return [top, height - left]
  
  return coordinates;
}
function transformCoordinates(
  coordinates: Coordinates,
  transform: Coordinates
): Coordinates {
  return [coordinates[0] + transform[0], coordinates[1] + transform[1]];
}
console.log(
  rotateAndTransformZPL(
    `
^XA
^FWN
^FX Top section with logo, name and address.
^CF0,60
^FO50,50^GB100,100,100^FS
^FO75,75^FR^GB100,100,100^FS
^FO93,93^GB40,40,40^FS
^FO220,50^FDIntershipping, Inc.^FS
^CF0,30
^FO220,115^FD1000 Shipping Lane^FS
^FO220,155^FDShelbyville TN 38102^FS
^FO220,195^FDUnited States (USA)^FS
^FO50,250^GB700,3,3^FS

^FX Second section with recipient address and permit information.
^CFA,30
^FO50,300^FDJohn Doe^FS
^FO50,340^FD100 Main Street^FS
^FO50,380^FDSpringfield TN 39021^FS
^FO50,420^FDUnited States (USA)^FS
^CFA,15
^FO600,300^GB150,150,3^FS
^FO638,340^FDPermit^FS
^FO638,390^FD123456^FS
^FO50,500^GB700,3,3^FS

^FX Third section with bar code.
^BY5,2,270
^FO100,550^BC^FD12345678^FS

^FX Fourth section (the two boxes on the bottom).
^FO50,900^GB700,250,3^FS
^FO400,900^GB3,250,3^FS
^CF0,40
^FO100,960^FDCtr. X34B-1^FS
^FO100,1010^FDREF1 F00B47^FS
^FO100,1060^FDREF2 BL4H8^FS
^CF0,190
^FO470,955^FDCA^FS

^XZ
`,
    90,
    [203 * 6, 203 * 4],
    [0, 0]
  )
);