addon-sdk/source/lib/sdk/ui/toolbar/model.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2 * License, v. 2.0. If a copy of the MPL was not distributed with this
     3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     4 "use strict";
     6 module.metadata = {
     7   "stability": "experimental",
     8   "engines": {
     9     "Firefox": "> 28"
    10   }
    11 };
    13 const { Class } = require("../../core/heritage");
    14 const { EventTarget } = require("../../event/target");
    15 const { off, setListeners, emit } = require("../../event/core");
    16 const { Reactor, foldp, merges, send } = require("../../event/utils");
    17 const { Disposable } = require("../../core/disposable");
    18 const { InputPort } = require("../../input/system");
    19 const { OutputPort } = require("../../output/system");
    20 const { identify } = require("../id");
    21 const { pairs, object, map, each } = require("../../util/sequence");
    22 const { patch, diff } = require("diffpatcher/index");
    23 const { contract } = require("../../util/contract");
    24 const { id: addonID } = require("../../self");
    26 // Input state is accumulated from the input received form the toolbar
    27 // view code & local output. Merging local output reflects local state
    28 // changes without complete roundloop.
    29 const input = foldp(patch, {}, new InputPort({ id: "toolbar-changed" }));
    30 const output = new OutputPort({ id: "toolbar-change" });
    32 // Takes toolbar title and normalizes is to an
    33 // identifier, also prefixes with add-on id.
    34 const titleToId = title =>
    35   ("toolbar-" + addonID + "-" + title).
    36     toLowerCase().
    37     replace(/\s/g, "-").
    38     replace(/[^A-Za-z0-9_\-]/g, "");
    40 const validate = contract({
    41   title: {
    42     is: ["string"],
    43     ok: x => x.length > 0,
    44     msg: "The `option.title` string must be provided"
    45   },
    46   items: {
    47     is:["undefined", "object", "array"],
    48     msg: "The `options.items` must be iterable sequence of items"
    49   },
    50   hidden: {
    51     is: ["boolean", "undefined"],
    52     msg: "The `options.hidden` must be boolean"
    53   }
    54 });
    56 // Toolbars is a mapping between `toolbar.id` & `toolbar` instances,
    57 // which is used to find intstance for dispatching events.
    58 let toolbars = new Map();
    60 const Toolbar = Class({
    61   extends: EventTarget,
    62   implements: [Disposable],
    63   initialize: function(params={}) {
    64     const options = validate(params);
    65     const id = titleToId(options.title);
    67     if (toolbars.has(id))
    68       throw Error("Toolbar with this id already exists: " + id);
    70     // Set of the items in the toolbar isn't mutable, as a matter of fact
    71     // it just defines desired set of items, actual set is under users
    72     // control. Conver test to an array and freeze to make sure users won't
    73     // try mess with it.
    74     const items = Object.freeze(options.items ? [...options.items] : []);
    76     const initial = {
    77       id: id,
    78       title: options.title,
    79       // By default toolbars are visible when add-on is installed, unless
    80       // add-on authors decides it should be hidden. From that point on
    81       // user is in control.
    82       collapsed: !!options.hidden,
    83       // In terms of state only identifiers of items matter.
    84       items: items.map(identify)
    85     };
    87     this.id = id;
    88     this.items = items;
    90     toolbars.set(id, this);
    91     setListeners(this, params);
    93     // Send initial state to the host so it can reflect it
    94     // into a user interface.
    95     send(output, object([id, initial]));
    96   },
    98   get title() {
    99     const state = reactor.value[this.id];
   100     return state && state.title;
   101   },
   102   get hidden() {
   103     const state = reactor.value[this.id];
   104     return state && state.collapsed;
   105   },
   107   destroy: function() {
   108     send(output, object([this.id, null]));
   109   },
   110   // `JSON.stringify` serializes objects based of the return
   111   // value of this method. For convinienc we provide this method
   112   // to serialize actual state data. Note: items will also be
   113   // serialized so they should probably implement `toJSON`.
   114   toJSON: function() {
   115     return {
   116       id: this.id,
   117       title: this.title,
   118       hidden: this.hidden,
   119       items: this.items
   120     };
   121   }
   122 });
   123 exports.Toolbar = Toolbar;
   124 identify.define(Toolbar, toolbar => toolbar.id);
   126 const dispose = toolbar => {
   127   toolbars.delete(toolbar.id);
   128   emit(toolbar, "detach");
   129   off(toolbar);
   130 };
   132 const reactor = new Reactor({
   133   onStep: (present, past) => {
   134     const delta = diff(past, present);
   136     each(([id, update]) => {
   137       const toolbar = toolbars.get(id);
   139       // Remove
   140       if (!update)
   141         dispose(toolbar);
   142       // Add
   143       else if (!past[id])
   144         emit(toolbar, "attach");
   145       // Update
   146       else
   147         emit(toolbar, update.collapsed ? "hide" : "show", toolbar);
   148     }, pairs(delta));
   149   }
   150 });
   151 reactor.run(input);

mercurial