Narabe.Rules (Narabe v0.1.0)

Copy Markdown View Source

What may go where: the same rules in the browser, as people drag (a drop that breaks them is not offered), and on the server, as Narabe.Ecto applies a move (one that breaks them is refused).

rules = Narabe.Rules.new(nest: true, max_depth: 4, leaves: ["file"])

Options:

  • :nest - whether an item may hold others (a tree). A list's and a board's items do not nest (false, the default for them).
  • :max_depth - the deepest level an item may be at, the top level being 1. A moved item's own children count: a page with two levels under it cannot go to the deepest level.
  • :leaves - the kinds of items that hold nothing (["file"]).
  • :accepts - which kinds of items a kind of parent takes: %{"section" => ["page"], "root" => ["section"]}. "root" is the top level; a board's columns are parents of their kind ("column"). A kind with no entry takes anything.

An item's kind is its kind (data-kind in the browser, the :kind field of Narabe.Ecto's schema); without one it is "item".

The browser and the server give the same answers, held by test/fixtures/rules.json.

Summary

Types

A move, as the rules see it: the kinds of the items, the kind of their new parent (nil at the top level) and its depth (0 for the top level or a board's column), the height of the tallest item's subtree (1 for an item with no children), and whether the parent is one of the items or under one.

Why a move is refused.

t()

Functions

Checks a move: :ok, or {:error, reason}.

Rules from options (see the moduledoc), the rules themselves, or those of a schema with use Narabe.Ordered (so that a component and Narabe.Ecto have the same: <.tree rules={Page} ...>).

The rules as the browser takes them (data-config), with keys in camel case and nothing that is a default.

Types

move()

@type move() :: %{
  kinds: [String.t()],
  parent_kind: String.t() | nil,
  parent_depth: non_neg_integer(),
  height: pos_integer(),
  into_itself: boolean()
}

A move, as the rules see it: the kinds of the items, the kind of their new parent (nil at the top level) and its depth (0 for the top level or a board's column), the height of the tallest item's subtree (1 for an item with no children), and whether the parent is one of the items or under one.

reason()

@type reason() :: :into_itself | :not_accepted | :too_deep

Why a move is refused.

t()

@type t() :: %Narabe.Rules{
  accepts: %{required(String.t()) => [String.t()]} | nil,
  leaves: [String.t()],
  max_depth: pos_integer() | nil,
  nest: boolean()
}

Functions

check(rules, move)

@spec check(t(), move()) :: :ok | {:error, reason()}

Checks a move: :ok, or {:error, reason}.

iex> rules = Narabe.Rules.new(nest: true, max_depth: 2)
iex> Narabe.Rules.check(rules, %{kinds: ["page"], parent_kind: "page", parent_depth: 1, height: 1, into_itself: false})
:ok
iex> Narabe.Rules.check(rules, %{kinds: ["page"], parent_kind: "page", parent_depth: 1, height: 2, into_itself: false})
{:error, :too_deep}

new(rules)

@spec new(t() | keyword() | module() | nil) :: t()

Rules from options (see the moduledoc), the rules themselves, or those of a schema with use Narabe.Ordered (so that a component and Narabe.Ecto have the same: <.tree rules={Page} ...>).

iex> Narabe.Rules.new(nest: true, max_depth: 3)
%Narabe.Rules{nest: true, max_depth: 3, leaves: [], accepts: nil}

to_client(rules)

@spec to_client(t()) :: map()

The rules as the browser takes them (data-config), with keys in camel case and nothing that is a default.

iex> Narabe.Rules.to_client(Narabe.Rules.new(nest: true, max_depth: 3))
%{"nest" => true, "maxDepth" => 3}