npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

undoh

v1.2.2

Published

This Typescript module provides an undo class that can be used to implement a simple undo/redo mechanism.

Downloads

47

Readme

Undoh

Since changes to individual system states are often incremental in nature, a function is provided which only records the differences in data structures between two actions (diff). To enable object comparability, complex data structures are normalized via the detour of the JSON storage format and their keys are sorted if necessary.

Data is kept in memory only if it differs from its direct predecessor.

What can be done

Essentially, four operations are provided:

  • The initialization of an undo/redo buffer as a constructor;
  • The storage of the current state (retain);
  • The restoration of the previous state (undo);
  • The repetition of an undone state (redo).

Static utility functions are:

  • Sorting by keys of a possibly nested JSON data structure;
  • Determining differences (replace, insert, delete) between two texts or arrays;
  • The conversion of a text based on the previously identified differences;
  • A range operator similar to Python's.

Information about the state of the stack memory:

  • Can an action be undone (canUndo)?
  • Can an action be redone (canRedo)?
  • How many steps can be undone (countUndo)?
  • How many steps can be redone (countRedo)?

How to use

To save the current state as a snapshot a value is passed to the retain function. The data type of the passed value must be the same as the one specified when the constructor was initialized. If the constructor is initialized with a string, retain cannot be called with an array, for example.

Undo returns a snapshot of the previous state as a result, if available. At the same time the current state is written into the buffer for redo actions. If there are no (more) previous states in memory, the current state is returned.

The redo function can be executed if undo was called before. If there are no future states in memory, the current state is returned. If the data structure is changed after undo and saved again using retain, all values in memory for future changes with redo are lost from this point on.

Since complex data structures are converted using the native JSON functions, a replacer parameter can optionally be passed to the constructor and the retain function, and a reviver parameter can optionally be passed to the undo or redo function. The description of how to use the parameters can be found on the Mozilla pages.

Interface

type tString = Capitalize<string>;
type tIndexable = string | any[];
type tReviver = (key: string, value: any) => any;
type tReplacer = any;

interface iScript {
  pos: number;
  val?: string[] | string;
}
  • constructor(data: T, max?: number, objKeySort?: boolean, replacer?: tReplacer);

    • data: Initializer. The retain function compares the first element to be filed with this value.
    • max (opt): How many operations can be undone?
    • objKeySort (opt): Sort objects in ascending order according to the key.
    • replacer (opt): Applied to the initializer if it is an object. Mozilla doku
  • get countPast(): number;

  • get countFuture(): number;

  • get canUndo(): boolean;

  • get canRedo(): boolean;

  • retain(data: T, replacer?: tReplacer): boolean;

    • data: The data type must correspond to that of the initializer.
    • replacer (opt): Applied to data if it is an object. Mozilla doku
  • undo(reviver?: tReviver): T;

    • reviver (opt): Applied to this first undo-element if it is an object. Mozilla doku
  • redo(reviver?: tReviver): T;

    • reviver (opt): Applied to this first redo-element if it is an object. Mozilla doku
  • static diffScript(older: tIndexable, newer: tIndexable): iScript[];

  • static applyEdit(script: iScript[], older: tIndexable): tIndexable;

  • static jsonSort(data: any): string;

  • static range(start: number, end?: number, step?: number = 1);

Examples

Here is a simple example to illustrate the principle of operation. After execution, the console should display "def" followed by "ghi".

import Undo from "undoh";
let buffer = new Undo("");
buffer.retain("def");
buffer.retain("ghi");
console.log(buffer.undo());
console.log(buffer.redo());

The following example script shows how easy it is to use the Undoh class in a DOM environment. A total of four HTML buttons are created. The upper two are linked to the undo and redo function. The lower two buttons create or remove text input fields. Between one and a maximum of five text input fields can be created. The creation or deletion of a field and the modification of its content can be undone or redone.

Try it here: Stackblitz online example

<!DOCTYPE html>
<html>
<head>
    <script type="module" src="example.js"></script>
    <style>
        button,input { margin: 3px; }
        button { user-select: none; }
    </style>
</head>
<body>
</body>
</html>

The constructor is instantiated with a type in the form Undo<type>. This is an incompatible extension compared to versions earlier than 1.2.

example.ts

import Undo from "./undoh.js";

type idval = { id: string; value: string };

const minInput: number = 1,
  maxInput: number = 5;

let buffer: Undo<idval>;

const add: HTMLButtonElement = document.createElement("button"),
  remove: HTMLButtonElement = document.createElement("button"),
  undo: HTMLButtonElement = document.createElement("button"),
  redo: HTMLButtonElement = document.createElement("button"),
  area: HTMLDivElement = document.createElement("div");

const updateButtonUndoRedo = (): void => {
    undo.textContent = `undo ${buffer.countPast}`;
    redo.textContent = `redo ${buffer.countFuture}`;
    undo.disabled = !buffer.canUndo;
    redo.disabled = !buffer.canRedo;
  },
  updateButtonAddRemove = (): void => {
    add.textContent = `add ${maxInput - area.childElementCount}`;
    remove.textContent = `remove ${area.childElementCount - minInput}`;
    add.disabled = area.childElementCount >= maxInput;
    remove.disabled = area.childElementCount <= minInput;
  },
  retain = (): void => {
    const content: idval[] = [...area.querySelectorAll("input")].map(
      (e: HTMLInputElement): idval => ({ id: e.id, value: e.value })
    );
    if (buffer) {
      buffer.retain(content);
    } else {
      buffer = new Undo(content, 10);
    }
  },
  create = (data: idval | null = null): void => {
    const input: HTMLInputElement = document.createElement("input");
    [input.id, input.value] = data
      ? [data.id, data.value]
      : [`${area.childElementCount + 1}`, ""];
    input.addEventListener("change", (): void => {
      retain();
      updateButtonUndoRedo();
    });
    area.appendChild(input);
  },
  redraw = (r: idval[]): void => {
    area.innerHTML = "";
    r.forEach((e: idval): void => create(e));
    updateButtonUndoRedo();
    updateButtonAddRemove();
  };

add.addEventListener("click", (): void => {
  create();
  retain();
  updateButtonUndoRedo();
  updateButtonAddRemove();
});

remove.addEventListener("click", (): void => {
  const input: HTMLInputElement = area.querySelector("input:last-of-type")!;
  input.remove();
  retain();
  updateButtonUndoRedo();
  updateButtonAddRemove();
});

undo.addEventListener("click", (): void => redraw(buffer.undo()));

redo.addEventListener("click", (): void => redraw(buffer.redo()));

document.body.appendChild(undo);
document.body.appendChild(redo);
document.body.appendChild(area);
document.body.appendChild(add);
document.body.appendChild(remove);

add.dispatchEvent(new MouseEvent("click"));

Alternatively, when working with a replacer, the corresponding retain function can look like this: Stackblitz online example using replacer

retain = (): void => {
  const objects: string[] = ["id", "value"],
    input: HTMLInputElement[] = [...area.querySelectorAll("input")];
  if (buffer) {
    buffer.retain(input, objects);
  } else {
    buffer = new Undo(input, 10, false, objects);
  }
}

"Homer Simpson" - image credits: Denis × DALL·E