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

layout-tree

v0.2.1

Published

An abstract tree data-structure to represent windows as leaf nodes.

Downloads

5

Readme

layout-tree Build Status npm

Data structure for laying out splitted windows.

What?

A data structure and collection of operations around this data structure to represent set of windows in an efficient way.

Example

Imagine you want to show a layout as below:

*---------*---------*---------*---------*
|         |         |         |         |
|         |    1    |         |         |
|         |         |         |         |
*         *---------*         *         *
|         |         |         |         |
|    0    |    2    |    4    |    5    |
|         |         |         |         |
*         *---------*         *         *
|         |         |         |         |
|         |    3    |         |         |
|         |         |         |         |
*---------*---------*---------*---------*

You could then use layout-tree as follows:

import { createTree, createParentNode, createNode } from 'layout-tree'
import { Orientation } from 'layout-tree/lib/constants'

const tree = createTree({
  root: createParentNode({
    orientation: Orientation.VERTICAL,
    children: [
      createNode({ id: 0 }),
      createParentNode({
        orientation: Orientation.HORIZONTAL,
        children: [
          createNode({ id: 1 }),
          createNode({ id: 2 }),
          createNode({ id: 3 })
        ]
      }),
      createNode({ id: 4 }),
      createNode({ id: 5 })
    ]
  })
})

And you can easily read the leaf nodes by using getAt function exported from layout-tree. Following the example above:

import { getAt } from 'layout-tree'

// tree is the tree from example above.
const node = getAt(tree, 1)
// => Node(id: 1) {
//      type: 'node',
//      orientation: null,
//      meta: { id: 1 },
//      parent: ParentNode {
//        type: 'parent',
//        orientation: 'horizontal',
//        children: [ Node(id: 1), Node(id: 2), Node(id: 3) ]
//      }
//    }

Constants

Orientation

Orientation represents the way a ParentNode lays out its children.

declare type Orientation = {
  HORIZONTAL: 'horizontal',
  VERTICAL: 'vertical'
}

Direction

Direction represents the relative position of a sibling Node.

declare type Direction = {
  UP: 'up',
  DOWN: 'down',
  LEFT: 'left',
  RIGHT: 'right'
}

Both Direction and Orientation are exported at top level:

import { Direction, Orientation } from 'layout-tree'

Models

Node

Node is the base class of all other types of nodes (currently only ParentNode with type === 'parent'). It declares the node interface.

declare interface NodeInterface {
  type: string;
  orientation: Orientation | null;
  meta: Object;
  parent: ParentNode | null;

  setParent(parent: ParentNode): Node;
  clone(): Node;
}
declare class Node implements NodeInterface {
  type: 'node';
  orientation: null;
  meta: Object<string, any>;
  parent: ParentNode | null;
}

There are 2 ways of creating a simple node:

1 - You can use the createNode factory function:

import { createNode } from 'layout-tree'

const node = createNode({ name: 'foo' })
// => Node {
//     type: 'node',
//     orientation: null,
//     meta: { name: 'foo' },
//     parent: null
//   }

2 - You can use the Node class:

import { Node } from 'layout-tree'

// You need to pass type as first argument to the constructor.
const node = new Node('node', { name: 'foo' })
// => Node {
//     type: 'node',
//     orientation: null,
//     meta: { name: 'foo' },
//     parent: null
//   }

createNode is the correct way of creating a node, as you can see Node class does not necessarily creates a node with the type of node.

ParentNode

ParentNode is a different type of node to distinguish it from regular node instances.

declare class ParentNode implements NodeInterface {
  type: 'parent';
  orientation: Orientation; // orientation has to be an Orientation type.
  meta: Object<string, any>;
  parent: ParentNode | null;
  children: Array; // adds a children member.

  attachChildren(children: Array<NodeInterface>): ParentNode;
  indexOf(child: NodeInterface): number;
  splitChild(child: NodeInterface, orientation: Orientation): void;
  removeChild(child: NodeInterface): Array<NodeInterface>;
}

There are 2 ways of creating a parent node:

1 - You can use the createParentNode factory function:

import {
  createParentNode,
  createNode,
  Orientation
} from 'layout-tree'

const parent = createParentNode({
  orientation: Orientation.HORIZONTAL,
  children: [
    createNode({ name: 'child_0' }),
    createNode({ name: 'child_1' })
  ]
})
// => ParentNode {
//      type: 'parent',
//      orientation: 'horizontal',
//      meta: {},
//      parent: null,
//      children: [{
//        type: 'node',
//        orientation: null,
//        meta: { name: 'child_0' }
//        parent: [Circular]
//      }, {
//        type: 'node',
//        orientation: null,
//        meta: { name: 'child_1' }
//        parent: [Circular]
//      }]
//    }

2 - You can use the ParentNode class:

import { Node, ParentNode, Orientation } from 'layout-tree'

const parent = new ParentNode(null, Orientation.HORIZONTAL)
parent.attachChildren([
  new Node('node', { name: 'child_0' }),
  new Node('node', { name: 'child_1' })
])
// => ParentNode {
//      type: 'parent',
//      orientation: 'horizontal',
//      meta: {},
//      parent: null,
//      children: [{
//        type: 'node',
//        orientation: null,
//        meta: { name: 'child_0' }
//        parent: [Circular]
//      }, {
//        type: 'node',
//        orientation: null,
//        meta: { name: 'child_1' }
//        parent: [Circular]
//      }]
//    }

createParentNode is the correct way of creating a parent node, as using constructor will require you to attach the children manually after creating a parent node.

Tree

Tree is the main data structure layout-tree package exports.

declare type Traverser = (child: NodeInterface, index: number): void;
declare type RootNode = ParentNode | Node;

declare class Tree {
  type: 'tree';
  root: RootNode;
  constructor(root: NodeInterface);
  getAt(index: number): NodeInterface | null;
  traverse(traverserFn: Traverser): void;
  indexOf(node: NodeInterface): number;
  findSibling(node: NodeInterface, direction: Direction): NodeInterface | null;
}

Use createTree function to create a layout-tree:

import { createTree } from 'layout-tree'

const tree = createTree()
// => Tree {
//      type: 'tree',
//      root: Node {
//        type: 'node',
//        orientation: null,
//        meta: null,
//        parent: null
//      }
//    }

Which will represent a tab with a single window:

*---------*
|         |
|    0    |
|         |
*---------*

Operations

Most of the power of this data structures comes from its operations. Each operation modifies the current tree structure, causes a new tree to be returned.

There are 2 operations causes the current tree to be modified:

split

declare function split(tree: Tree, index: number, Orientation): Tree;

split will accept a Tree instance along with an index and an Orientation constant.

split operation creates a new child, therefore increases the children count.

import { createTree, split } from 'layout-tree'

const tree = createTree()

// split returns a new tree.
const newTree = split(tree, 0, Orientation.HORIZONTAL)
// *---------*      *---------*
// |         |      |    0    |
// |    0    |  =>  *---------*
// |         |      |    1    |
// *---------*      *---------*

const anotherTree = split(newTree, 1, Orientation.VERTICAL)
// *---------*      *---------*
// |    0    |      |    0    |
// |---------|  =>  *----*----*
// |    1    |      |  1 | 2  |
// *---------*      *----*----*

remove

declare function remove(tree: Tree, index: number): Tree;

remove will accept a Tree instance along with an index.

remove operation deletes new child, therefore decreases the children count.

import { createTree, split, remove } from 'layout-tree'

const tree = createTree()

// split returns a new tree.
const newTree = split(tree, 0, Orientation.HORIZONTAL)
// *---------*      *---------*
// |         |      |    0    |
// |    0    |  =>  *---------*
// |         |      |    1    |
// *---------*      *---------*

const anotherTree = split(newTree, 1, Orientation.VERTICAL)
// *---------*      *---------*
// |    0    |      |    0    |
// |---------|  =>  *----*----*
// |    1    |      |  1 | 2  |
// *---------*      *----*----*

const removedTree = remove(anotherTree, 1)
// *---------*      *---------*
// |    0    |      |    0    |
// *----*----*  =>  |---------|
// |  1 | 2  |      |    1    |
// *----*----*      *---------*

const beginning = remove(anotherTree, 0)
// *---------*      *---------*
// |    0    |      |         |
// *---------*  =>  |    0    |
// |    1    |      |         |
// *---------*      *---------*

Reading childrens

getAt

If you have a tree and you know index, use getAt to read the child at that index:

declare getAt(tree: Tree, index): Node | null;

findSibling

If you know child and want to find one of its siblings in given Direction use findSibling:

declare findSibling(node: Node, direction: Direction): Node;

Let's see how these functions can be used:

import { createTree, getAt, findSibling, Direction } from 'layout-tree'

const tree = createTree({ ... })
// let's imagine the above call has created the the below layout:
// *---------*---------*---------*---------*
// |         |         |         |         |
// |         |    1    |         |         |
// |         |         |         |         |
// *         *---------*         *         *
// |         |         |         |         |
// |    0    |    2    |    4    |    5    |
// |         |         |         |         |
// *         *---------*         *         *
// |         |         |         |         |
// |         |    3    |         |         |
// |         |         |         |         |
// *---------*---------*---------*---------*

// nothing fancy, just return the node at given index:
getAt(tree, 2) // => Node { meta: { id: 2 } }
getAt(tree, 5) // => Node { meta: { id: 5 } }

// if index is out of range, return null.
getAt(tree, 9) // => null

// let's first get a node, and then find its siblings:
const node = getAt(tree, 2)
// => Node { meta: { id: 2 } }
const siblingUp = findSibling(node, Direction.UP)
// => Node { meta: { id: 1 } }
const siblingLeft = findSibling(node, Direction.LEFT)
// => Node { meta: { id: 0 } }
const siblingRight = findSibling(node, Direction.RIGHT)
// => Node { meta: { id: 4 } }
const siblingDown = findSibling(node, Direction.DOWN)
// => Node { meta: { id: 3 } }

That's it. Check out tests to see a little bit more complicated examples.

Inspiration

This data structure is the result of an attempt to model the beautiful layout structure of vim. It's still not anywhere close to support all the features of vim tabs/windows/buffers but it doesn't try to provide full api parity, instead tries to provide a skeleton to hopefully build upon.

install

npm install layout-tree

licence

MIT