<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import React from 'react';
import DateTimePicker from '@react-native-community/datetimepicker';
import PropTypes from 'prop-types';

import { CircleButton } from './CircleButton';
import { CalendarIcon } from './icons';

import { useDatePicker } from '../hooks/useDatePicker';
import { IconButton } from './IconButton';
import { DARK_GREY } from '../globalStyles/index';

/**
 * Shows an Icon that is pressable and opens a date picker. Handle result with
 * callback "onDateChanged".
 *
 * @prop {Node} initialValue  Initial date value to display in picker
 * @prop {Func} onDateChanged Callback to handle output of selected date.
 *                            Should take one parameter, a timestamp.
 * @prop {Func} maximumDate Maximum date selectable.
 * @prop {Func} minimumDate Minimum date selectable.
 * @prop {Boolean} isCircle Should the Icon have a Circle background rendered.
 */

export const DatePickerButton = ({
  initialValue,
  onDateChanged,
  maximumDate,
  minimumDate,
  isCircle,
  isDisabled,
}) => {
  const [datePickerIsOpen, openDatePicker, datePickerCallback] = useDatePicker(onDateChanged);
  const button = isCircle ? (
    <CircleButton isDisabled={isDisabled} IconComponent={CalendarIcon} onPress={openDatePicker} />
  ) : (
    <IconButton
      isDisabled={isDisabled}
      Icon={<CalendarIcon color={DARK_GREY} />}
      onPress={openDatePicker}
    />
  );

  return (
    <>
      {button}
      {datePickerIsOpen && (
        <DateTimePicker
          maximumDate={maximumDate}
          minimumDate={minimumDate}
          onChange={datePickerCallback}
          mode="date"
          display="spinner"
          value={initialValue}
        />
      )}
    </>
  );
};

DatePickerButton.defaultProps = {
  minimumDate: undefined,
  maximumDate: undefined,
  isCircle: true,
  isDisabled: false,
};

DatePickerButton.propTypes = {
  initialValue: PropTypes.instanceOf(Date).isRequired,
  onDateChanged: PropTypes.func.isRequired,
  maximumDate: PropTypes.instanceOf(Date),
  minimumDate: PropTypes.instanceOf(Date),
  isCircle: PropTypes.bool,
  isDisabled: PropTypes.bool,
};
<commit_msg>Add a "mode" to DatePickerButton<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

import React from 'react';
import DateTimePicker from '@react-native-community/datetimepicker';
import PropTypes from 'prop-types';

import { CircleButton } from './CircleButton';
import { CalendarIcon, ClockIcon } from './icons';

import { useDatePicker } from '../hooks/useDatePicker';
import { IconButton } from './IconButton';
import { DARK_GREY } from '../globalStyles/index';

/**
 * Shows an Icon that is pressable and opens a date picker. Handle result with
 * callback "onDateChanged".
 *
 * @prop {Node} initialValue  Initial date value to display in picker
 * @prop {Func} onDateChanged Callback to handle output of selected date.
 *                            Should take one parameter, a timestamp.
 * @prop {Func} maximumDate Maximum date selectable.
 * @prop {Func} minimumDate Minimum date selectable.
 * @prop {Boolean} isCircle Should the Icon have a Circle background rendered.
 */

export const DatePickerButton = ({
  initialValue,
  onDateChanged,
  maximumDate,
  minimumDate,
  isCircle,
  isDisabled,
  mode,
}) => {
  const [datePickerIsOpen, openDatePicker, datePickerCallback] = useDatePicker(onDateChanged);
  const button = isCircle ? (
    <CircleButton
      isDisabled={isDisabled}
      IconComponent={mode === 'time' ? ClockIcon : CalendarIcon}
      onPress={openDatePicker}
    />
  ) : (
    <IconButton
      isDisabled={isDisabled}
      Icon={<CalendarIcon color={DARK_GREY} />}
      onPress={openDatePicker}
    />
  );

  return (
    <>
      {button}
      {datePickerIsOpen && (
        <DateTimePicker
          maximumDate={maximumDate}
          minimumDate={minimumDate}
          onChange={datePickerCallback}
          mode={mode}
          display="spinner"
          value={initialValue}
        />
      )}
    </>
  );
};

DatePickerButton.defaultProps = {
  minimumDate: undefined,
  maximumDate: undefined,
  isCircle: true,
  isDisabled: false,
  mode: 'date',
};

DatePickerButton.propTypes = {
  initialValue: PropTypes.instanceOf(Date).isRequired,
  onDateChanged: PropTypes.func.isRequired,
  maximumDate: PropTypes.instanceOf(Date),
  minimumDate: PropTypes.instanceOf(Date),
  isCircle: PropTypes.bool,
  isDisabled: PropTypes.bool,
  mode: PropTypes.string,
};
<|endoftext|>


<commit_before>/* globals Sortable */
import Ember from 'ember';

export default Ember.Component.extend({

  group: 'dnt-photo-manager',
  actions: {
    categorize: function (photo, category) {
      this.sendAction('categorize', photo, category);
    },

    removePhoto: function (photo, destroy) {
      this.sendAction('removePhoto', photo, destroy);
    }
  },

  setup: function () {
    this.sortable = Sortable.create(this.$('.grid')[0], {
      draggable: '.dnt-photo-manager-list-item',
      group: this.get('group'),
      onAdd: Ember.run.bind(this, this.onAdd),
      onEnd: Ember.run.bind(this, this.onEnd)
    });

  }.on('didInsertElement'),

  categoryPhotos: Ember.computed('photos.@each.kategori', {
    get: function () {
      let category = this.get('category');
      let photos = this.get('photos');

      return photos.filterBy('kategori', category);
    }
  }),

  onEnd: function (e) {
    const order = this.sortable.toArray();
    const photos = Ember.copy(this.get('photos').toArray()) || [];

    let reordered = photos.sort(function (a, b) {
      let aId = a.get('id');
      let bId = b.get('id');

      let aOrderIndex = order.indexOf(aId);
      let bOrderIndex = order.indexOf(bId);

      if (aOrderIndex === -1 || bOrderIndex === -1) {
        return 0;

      } else {
        return aOrderIndex - bOrderIndex;
      }

    });

    this.set('photos', reordered);
  },

  onAdd: function (e) {
    const item = e.item;
    const itemId = item.dataset.id;
    const bilde = this.get('photos').findBy('id', itemId);
    const category = this.get('category');
    // TODO: This will cause the photos array to be reordered before onEnd
    // and the position of dragged photo will be lost
    this.send('categorize', bilde, category);
    // bilde.set('kategori', category);
  }

});
<commit_msg>Fixed sorting bug by adding original index, to account for Chrome sort function sorting not stable<commit_after>/* globals Sortable */
import Ember from 'ember';

export default Ember.Component.extend({

  group: 'dnt-photo-manager',
  actions: {
    categorize: function (photo, category) {
      this.sendAction('categorize', photo, category);
    },

    removePhoto: function (photo, destroy) {
      this.sendAction('removePhoto', photo, destroy);
    }
  },

  setup: function () {
    this.sortable = Sortable.create(this.$('.grid')[0], {
      draggable: '.dnt-photo-manager-list-item',
      group: this.get('group'),
      onAdd: Ember.run.bind(this, this.onAdd),
      onEnd: Ember.run.bind(this, this.onEnd)
    });

  }.on('didInsertElement'),

  categoryPhotos: Ember.computed('photos.@each.kategori', {
    get: function () {
      let category = this.get('category');
      let photos = this.get('photos');

      return photos.filterBy('kategori', category);
    }
  }),

  onEnd: function (e) {
    const order = this.sortable.toArray();
    const photos = Ember.copy(this.get('photos').toArray()).map(function (item, index) {
      item.index = index; // Add original index to use in custom sort function
      return item;
    }) || [];

    const reordered = photos.sort(function (a, b) {
      let aId = a.get('id');
      let bId = b.get('id');

      let aOrderIndex = order.indexOf(aId);
      let bOrderIndex = order.indexOf(bId);

      if (aOrderIndex === -1 || bOrderIndex === -1) {
        return a.get('index') > b.get('index') ? 1 : -1;

      } else {
        return aOrderIndex - bOrderIndex;
      }
    });

    this.set('photos', reordered);
  },

  onAdd: function (e) {
    const item = e.item;
    const itemId = item.dataset.id;
    const bilde = this.get('photos').findBy('id', itemId);
    const category = this.get('category');
    // TODO: This will cause the photos array to be reordered before onEnd
    // and the position of dragged photo will be lost
    this.send('categorize', bilde, category);
    // bilde.set('kategori', category);
  }

});
<|endoftext|>


<commit_before><commit_msg>Update ogario.v4.master.js<commit_after><|endoftext|>


<commit_before>

var hasOwnProperty = {}.hasOwnProperty;
var renderElements = {};
var renderidOffset = 0;
var addRenderElement = function () {
    var element = this;
    if (!isNode(element)) return;
    renderElements[element.renderid] = element;
    rebuild(element);
};
var removeRenderElement = function () {
    var element = this;
    delete renderElements[element.renderid];
};
function refresh() {
    for (var k in renderElements) {
        var element = renderElements[k];
        rebuild(element);
    }
}
function rebuild(element) {
    element.renders.map(a => a.call(element));
}
var createGetter = function (search) {
    return new Function(`try{with(this.$scope)return ${search}}catch(e){/*console.warn(String(e))*/}`);
};
var directives = {
    click(search) {
        var getter = createGetter(search);
        onclick(this, getter);
    },
    src(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var value = getter() || "";
            if (this.src !== value) this.src = value;
        });
    },
    bind(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var value = getter();
            if (text(this) !== value) text(this, value);
        });
    },
    model(search) {
        var getter = createGetter(search).bind(this);
        if (/^input$/i.test(this.tagName) && /^checkbox$/i.test(this.type) || /^checkbox$/i.test(this.tagName)) {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (this.checked != value) this.checked = value;
            });
            var change = new Function(`with(this.$scope)${search}=this.checked`).bind(this);
        } else if (/^(select|input|textarea)$/i.test(this.tagName)) {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (this.value != value) this.value = value;

            });
            var change = new Function(`with(this.$scope)${search}=this.value`).bind(this);
        } else {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (html(this) != value) html(this, value);
            });
            var change = new Function("html", `with(this.$scope)${search}=html(this)`).bind(this, html);
        }
        var onchange = lazy(change);
        eventsHandlers.map(on => on(this, onchange));
    },
    hide(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            if (getter()) {
                this.style.display = "none";
            } else {
                this.style.display = "";
            }
        });
    },
    show(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            if (getter()) {
                this.style.display = "";
            } else {
                this.style.display = "none";
            }
        });
    },
    "if"(search) {
        // 懒渲染
        var getter = createGetter(search).bind(this);
        var initial = function () {
            var comment = document.createComment("-if:" + search);
            comment.renders = [function () {
                var result = getter();
                if (result) {
                    if (!this.parentNode) appendChild.before(comment, this);
                } else {
                    remove(this);
                }
            }.bind(this)];
            comment.renderid = ++renderidOffset;
            onappend(comment, addRenderElement);
            onremove(comment, removeRenderElement);
            appendChild.after(this, comment);
            rebuild(comment);
        };
        if (this.parentNode) {
            initial.call(this);
        } else {
            once("append")(this, initial);
        }
    },
    repeat(search) {
        // 懒渲染
        throw new Error("repeat is not supported! use list component instead");
    },
    "class"(search) {
        var getter = createGetter(search).bind(this);
        var generatedClassNames = {};
        this.renders.push(function () {
            var originalClassNames = [];
            this.className.split(/\s+/).map(function (k) {
                if (k && !hasOwnProperty.call(generatedClassNames, k) && !hasOwnProperty.call(originalClassNames, k)) {
                    if (!/^\d+$/.test(k)) originalClassNames.push(originalClassNames[k] = k);
                }
            });
            var className = getter();
            var deltaClassNames = [];
            if (isString(className)) {
                className.split(/\s+/).map(function (k) {
                    if (!hasOwnProperty.call(originalClassNames, k)) {
                        if (!/^\d+$/.test(k)) deltaClassNames.push(deltaClassNames[k] = k);
                    }
                });
            } else if (className instanceof Object) {
                for (var k in className) {
                    if (!hasOwnProperty.call(originalClassNames, k) && className[k]) {
                        if (!/^\d+$/.test(k)) deltaClassNames.push(deltaClassNames[k] = k);
                    }
                }
            }
            var destClassName = originalClassNames.concat(deltaClassNames).join(" ");
            generatedClassNames = deltaClassNames;
            if (this.className !== destClassName) {
                this.className = destClassName;
            }
        });
    },
    style(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var stylesheet = getter();
            if (isString(stylesheet)) {
                stylesheet.replace(/[\s\u00a0]+/g, "").split(/;/).map(function (kv) {
                    var [k, v] = kv.split(":");
                    if (this.style[k] !== v) {
                        this.style[k] = v;
                    }
                }, this);
            } else if (stylesheet instanceof Object) {
                for (var k in stylesheet) {
                    if (this.style[k] !== stylesheet[k]) {
                        this.style[k] = stylesheet[k];
                    }
                }
            }
        });
    }
};
function renderElement(element, scope) {
    var children = element.children;
    if (!children) {
        return [].concat.apply([], element).map(function (element) {
            return renderElement(element, scope);
        });
    }
    element.$scope = scope;
    if (children.length) renderElement(children, scope);
    if (element.renderid) return;
    var attrs = [].concat.apply([], element.attributes);
    var { tagName, parentNode, nextSibling } = element;
    if (parentNode) {
        if (!scope[tagName]) tagName = tagName.toLowerCase();
        if (!scope[tagName])
            tagName = tagName.replace(/(?:^|\-+)([a-z])/ig, (_, w) => w.toUpperCase());
        if (!scope[tagName]) tagName = tagName.slice(0, 1).toLowerCase() + tagName.slice(1);
        if (isFunction(scope[tagName])) var replacer = scope[tagName](element);
        if (isElement(replacer) && element !== replacer) {
            if (nextSibling) appendChild.before(nextSibling, replacer);
            else appendChild(parentNode, replacer);
            if (element.parentNode === parentNode) remove(element);
            attrs.map(function (attr) {
                var { name, value } = attr;
                switch (name.toLowerCase()) {
                    case "class":
                        addClass(replacer, value);
                        break;
                    case "style":
                        css(replacer, value);
                        break;
                    default:
                }
            });
            element = replacer;
            element.$scope = scope;
        }
    }
    element.renders = [];
    attrs.map(function (attr) {
        var { name, value } = attr;
        if (/^(?:class|style|src)$/i.test(name)) return;
        name = name.replace(/^(ng|v|.*?)\-/i, "").toLowerCase();
        if (directives.hasOwnProperty(name) && isFunction(directives[name])) {
            directives[name].call(element, value);
        }
    });
    if (element.renders.length) {
        element.renderid = ++renderidOffset;
        onappend(element, addRenderElement);
        onremove(element, removeRenderElement);
        if (element.isMounted) addRenderElement.call(element);
    }
}
function render(element, scope) {
    return renderElement(element, scope);
}

var digest = lazy(refresh);
render.digest = render.apply = render.refresh = digest;

var eventsHandlers = "change,paste,resize,keydown,keypress,keyup,mousedown,mouseup,touchend,touchcancel,touchstart,dragend,drop,click".split(",").map(k => on(k));
eventsHandlers.map(on => on(window, digest));<commit_msg>对于一些自身无法处理的属性传递给下一级结果<commit_after>

var hasOwnProperty = {}.hasOwnProperty;
var renderElements = {};
var renderidOffset = 0;
var addRenderElement = function () {
    var element = this;
    if (!isNode(element)) return;
    renderElements[element.renderid] = element;
    rebuild(element);
};
var removeRenderElement = function () {
    var element = this;
    delete renderElements[element.renderid];
};
function refresh() {
    for (var k in renderElements) {
        var element = renderElements[k];
        rebuild(element);
    }
}
function rebuild(element) {
    element.renders.map(a => a.call(element));
}
var createGetter = function (search) {
    return new Function(`try{with(this.$scope)return ${search}}catch(e){/*console.warn(String(e))*/}`);
};
var directives = {
    click(search) {
        var getter = createGetter(search);
        onclick(this, getter);
    },
    src(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var value = getter() || "";
            if (this.src !== value) this.src = value;
        });
    },
    bind(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var value = getter();
            if (text(this) !== value) text(this, value);
        });
    },
    model(search) {
        var getter = createGetter(search).bind(this);
        if (/^input$/i.test(this.tagName) && /^checkbox$/i.test(this.type) || /^checkbox$/i.test(this.tagName)) {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (this.checked != value) this.checked = value;
            });
            var change = new Function(`with(this.$scope)${search}=this.checked`).bind(this);
        } else if (/^(select|input|textarea)$/i.test(this.tagName)) {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (this.value != value) this.value = value;

            });
            var change = new Function(`with(this.$scope)${search}=this.value`).bind(this);
        } else {
            this.renders.push(function () {
                var value = getter();
                if (value === undefined) value = "";
                if (html(this) != value) html(this, value);
            });
            var change = new Function("html", `with(this.$scope)${search}=html(this)`).bind(this, html);
        }
        var onchange = lazy(change);
        eventsHandlers.map(on => on(this, onchange));
    },
    hide(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            if (getter()) {
                this.style.display = "none";
            } else {
                this.style.display = "";
            }
        });
    },
    show(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            if (getter()) {
                this.style.display = "";
            } else {
                this.style.display = "none";
            }
        });
    },
    "if"(search) {
        // 懒渲染
        var getter = createGetter(search).bind(this);
        var initial = function () {
            var comment = document.createComment("-if:" + search);
            comment.renders = [function () {
                var result = getter();
                if (result) {
                    if (!this.parentNode) appendChild.before(comment, this);
                } else {
                    remove(this);
                }
            }.bind(this)];
            comment.renderid = ++renderidOffset;
            onappend(comment, addRenderElement);
            onremove(comment, removeRenderElement);
            appendChild.after(this, comment);
            rebuild(comment);
        };
        if (this.parentNode) {
            initial.call(this);
        } else {
            once("append")(this, initial);
        }
    },
    repeat(search) {
        // 懒渲染
        throw new Error("repeat is not supported! use list component instead");
    },
    "class"(search) {
        var getter = createGetter(search).bind(this);
        var generatedClassNames = {};
        this.renders.push(function () {
            var originalClassNames = [];
            this.className.split(/\s+/).map(function (k) {
                if (k && !hasOwnProperty.call(generatedClassNames, k) && !hasOwnProperty.call(originalClassNames, k)) {
                    if (!/^\d+$/.test(k)) originalClassNames.push(originalClassNames[k] = k);
                }
            });
            var className = getter();
            var deltaClassNames = [];
            if (isString(className)) {
                className.split(/\s+/).map(function (k) {
                    if (!hasOwnProperty.call(originalClassNames, k)) {
                        if (!/^\d+$/.test(k)) deltaClassNames.push(deltaClassNames[k] = k);
                    }
                });
            } else if (className instanceof Object) {
                for (var k in className) {
                    if (!hasOwnProperty.call(originalClassNames, k) && className[k]) {
                        if (!/^\d+$/.test(k)) deltaClassNames.push(deltaClassNames[k] = k);
                    }
                }
            }
            var destClassName = originalClassNames.concat(deltaClassNames).join(" ");
            generatedClassNames = deltaClassNames;
            if (this.className !== destClassName) {
                this.className = destClassName;
            }
        });
    },
    style(search) {
        var getter = createGetter(search).bind(this);
        this.renders.push(function () {
            var stylesheet = getter();
            if (isString(stylesheet)) {
                stylesheet.replace(/[\s\u00a0]+/g, "").split(/;/).map(function (kv) {
                    var [k, v] = kv.split(":");
                    if (this.style[k] !== v) {
                        this.style[k] = v;
                    }
                }, this);
            } else if (stylesheet instanceof Object) {
                for (var k in stylesheet) {
                    if (this.style[k] !== stylesheet[k]) {
                        this.style[k] = stylesheet[k];
                    }
                }
            }
        });
    }
};
function renderElement(element, scope) {
    var children = element.children;
    if (!children) {
        return [].concat.apply([], element).map(function (element) {
            return renderElement(element, scope);
        });
    }
    element.$scope = scope;
    if (children.length) renderElement(children, scope);
    if (element.renderid) return;
    var attrs = [].concat.apply([], element.attributes);
    var { tagName, parentNode, nextSibling } = element;
    if (parentNode) {
        if (!scope[tagName]) tagName = tagName.toLowerCase();
        if (!scope[tagName])
            tagName = tagName.replace(/(?:^|\-+)([a-z])/ig, (_, w) => w.toUpperCase());
        if (!scope[tagName]) tagName = tagName.slice(0, 1).toLowerCase() + tagName.slice(1);
        if (isFunction(scope[tagName])) var replacer = scope[tagName](element);
        if (isElement(replacer) && element !== replacer) {
            if (nextSibling) appendChild.before(nextSibling, replacer);
            else appendChild(parentNode, replacer);
            if (element.parentNode === parentNode) remove(element);
            attrs.map(function (attr) {
                var { name, value } = attr;
                switch (name.toLowerCase()) {
                    case "class":
                        addClass(replacer, value);
                        break;
                    case "style":
                        css(replacer, value);
                        break;
                    case "src":
                    case "placeholder":
                        replacer[name] = value;
                        break;
                    default:
                        if (!/[\-]/.test(name)) {
                            replacer.setAttribute(name, value);
                        }
                }
            });
            element = replacer;
            element.$scope = scope;
        }
    }
    element.renders = [];
    attrs.map(function (attr) {
        var { name, value } = attr;
        if (/^(?:class|style|src)$/i.test(name)) return;
        var key = name.replace(/^(ng|v|.*?)\-/i, "").toLowerCase();
        if (directives.hasOwnProperty(key) && isFunction(directives[key])) {
            directives[key].call(element, value);
        }
    });
    if (element.renders.length) {
        element.renderid = ++renderidOffset;
        onappend(element, addRenderElement);
        onremove(element, removeRenderElement);
        if (element.isMounted) addRenderElement.call(element);
    }
}
function render(element, scope) {
    return renderElement(element, scope);
}

var digest = lazy(refresh);
render.digest = render.apply = render.refresh = digest;

var eventsHandlers = "change,paste,resize,keydown,keypress,keyup,mousedown,mouseup,touchend,touchcancel,touchstart,dragend,drop,click".split(",").map(k => on(k));
eventsHandlers.map(on => on(window, digest));<|endoftext|>


<commit_before>// Init redis connection and store it in `N.redis`
//

'use strict';

var redis = require('redis');

module.exports = function (N) {

  N.wire.before('init:models', function redis_init(N, callback) {
    var url = (N.config.database || {}).redis;

    if (!url) {
      callback('No redis config found (database.redis)');
      return;
    }

    N.logger.info('Connecting to redis');

    // Callback proxy, since Redis connection is event-driven
    //
    function next(err) {
      if (err) {
        callback('redis error: ' + String(err.message || err));
        return;
      }

      N.logger.info('redis connected');
      callback();
    }

    // disable offline mode to avoid memory leaks if redis is offline,
    // because redis client doesn't appear to have a timeout on a single query
    N.redis = redis.createClient(url, { enable_offline_queue: false });

    N.redis.once('error', next);
    N.redis.once('connect', function () {
      N.redis.removeListener('error', next);

      N.redis.on('error', function (err) {
        N.logger.error('redis error: %s', String(err.message || err));
      });

      N.redis.on('connect', function () {
        N.logger.info('redis reconnected');
      });

      next();
    });
  });
};
<commit_msg>Fix redis connection procedure<commit_after>// Init redis connection and store it in `N.redis`
//

'use strict';

var redis = require('redis');
var redisUrl = require('redis-url');

module.exports = function (N) {

  N.wire.before('init:models', function redis_init(N, callback) {
    var url = (N.config.database || {}).redis;

    if (!url) {
      callback('No redis config found (database.redis)');
      return;
    }

    N.logger.info('Connecting to redis');

    // Callback proxy, since Redis connection is event-driven
    //
    function next(err) {
      if (err) {
        callback('redis error: ' + String(err.message || err));
        return;
      }

      N.logger.info('redis connected');
      callback();
    }

    // disable offline mode to avoid memory leaks if redis is offline,
    // because redis client doesn't appear to have a timeout on a single query
    N.redis = redis.createClient(url, { enable_offline_queue: false });

    N.redis.once('error', next);
    N.redis.once('connect', function () {
      N.redis.removeListener('error', next);

      N.redis.on('error', function (err) {
        N.logger.error('redis error: %s', String(err.message || err));
      });

      N.redis.on('connect', function () {
        N.logger.info('redis reconnected');
      });

      // select used database, we only need to do it once because
      // redis stores and selects it automatically on reconnect
      var parsed = redisUrl.parse(url);

      // flag forces to schedule the command even if redis is not ready yet
      N.redis.send_anyway = true;
      N.redis.select(parsed.database, next);
      N.redis.send_anyway = false;
    });
  });
};
<|endoftext|>


<commit_before>#!/usr/bin/env node
Error.stackTraceLimit = Infinity;

const uglify = require('uglify-js');
const Table  = require('cli-table');
const child  = require('child_process');
const bytes  = require('bytes');
const chalk  = require('chalk');
const babel  = require('babel-core');
const zlib   = require('zlib');
const fs     = require('fs');
const path   = require('path');

require('babel-jest/node_modules/babel-core').register();

const filename = process.argv[2];
if (!filename) {
  console.error('Error: No filename specified');
  process.exit(1);
}

const table = new Table({
  head: ['', 'raw', 'raw win', 'gzip', 'gzip win', 'parse time', 'run'],
  chars: {
    top: '',
    'top-mid': '' ,
    'top-left': '' ,
    'top-right': '',
    bottom: '' ,
    'bottom-mid': '' ,
    'bottom-left': '' ,
    'bottom-right': '',
    left: '',
    'left-mid': '',
    mid: '',
    'mid-mid': '',
    right: '' ,
    'right-mid': '',
    middle: ' ',
  },
  style: {
    'padding-left': 0,
    'padding-right': 0,
    head: ['bold'],
  },
});

const results = [];

const code = fs.readFileSync(filename, 'utf8');
const gzippedCode = zlib.gzipSync(code);

function test(name, callback) {
  const start = Date.now();
  const result = callback(code);
  const end = Date.now();
  const run = end - start;

  fs.writeFileSync('.test_gen_' + name + '.js', result);

  const gzipped = zlib.gzipSync(result);

  const parseStart = Date.now();
  // disable for now we have a failing test in dce
  new Function(result);
  const parseEnd = Date.now();
  const parseNow = parseEnd - parseStart;

  results.push({
    name: name,
    raw: result.length,
    gzip: gzipped.length,
    parse: parseNow,
    run: run,
  });
}

test('babel', function (code, callback) {
  return babel.transform(code, {
    sourceType: 'script',
    plugins: [
      [
        require('../src/replace-plugin'),
        {
          replacements: [{
            identifierName: '__DEV__',
            replacement: {
              type: 'booleanLiteral',
              value: true,
            },
          }],
        },
      ],
      require('../src/constant-folding-plugin'),
      require('../src/dce-plugin'),
      require('../src/simplify-plugin'),

      require('../src/mangle-names-plugin'),
    ],
    minified: true,
    comments: false,
  }).code;
});

if (filename.match(/fb\.js/)) {
  test('jsxmin', function (code, callback) {
    return fs.readFileSync(path.join(__dirname, 'fb-jsmin.js'));
  });
}

test('closure', function (code, callback) {
  return child.execSync(
    'java -jar ' + path.join(__dirname, 'gcc.jar') + ' --jscomp_off=uselessCode --js ' + filename
  );
});

test('uglify', function (code, callback) {
  return uglify.minify(code, {
    fromString: true,
  }).code;
});

results = results.sort(function (a, b) {
  return a.gzip > b.gzip;
});

results.forEach(function (result, i) {
  const row = [
    chalk.bold(result.name),
    bytes(result.raw),
    Math.round(((code.length / result.raw) * 100) - 100) + '%',
    bytes(result.gzip),
    Math.round(((gzippedCode.length / result.gzip) * 100) - 100) + '%',
    Math.round(result.parse) + 'ms',
    Math.round(result.run) + 'ms',
  ];

  const style = chalk.yellow;
  if (i === 0) {
    style = chalk.green;
  }
  if (i === results.length - 1) {
    style = chalk.red;
  }
  row = row.map(function (item) {
    return style(item);
  });

  table.push(row);
});

console.log(table.toString());
<commit_msg>reorder<commit_after>#!/usr/bin/env node
Error.stackTraceLimit = Infinity;

const uglify = require('uglify-js');
const Table  = require('cli-table');
const child  = require('child_process');
const bytes  = require('bytes');
const chalk  = require('chalk');
const babel  = require('babel-core');
const zlib   = require('zlib');
const fs     = require('fs');
const path   = require('path');

require('babel-jest/node_modules/babel-core').register();

const filename = process.argv[2];
if (!filename) {
  console.error('Error: No filename specified');
  process.exit(1);
}

const table = new Table({
  head: ['', 'raw', 'raw win', 'gzip', 'gzip win', 'parse time', 'run'],
  chars: {
    top: '',
    'top-mid': '' ,
    'top-left': '' ,
    'top-right': '',
    bottom: '' ,
    'bottom-mid': '' ,
    'bottom-left': '' ,
    'bottom-right': '',
    left: '',
    'left-mid': '',
    mid: '',
    'mid-mid': '',
    right: '' ,
    'right-mid': '',
    middle: ' ',
  },
  style: {
    'padding-left': 0,
    'padding-right': 0,
    head: ['bold'],
  },
});

const results = [];

const code = fs.readFileSync(filename, 'utf8');
const gzippedCode = zlib.gzipSync(code);

function test(name, callback) {
  const start = Date.now();
  const result = callback(code);
  const end = Date.now();
  const run = end - start;

  fs.writeFileSync('.test_gen_' + name + '.js', result);

  const gzipped = zlib.gzipSync(result);

  const parseStart = Date.now();
  // disable for now we have a failing test in dce
  new Function(result);
  const parseEnd = Date.now();
  const parseNow = parseEnd - parseStart;

  results.push({
    name: name,
    raw: result.length,
    gzip: gzipped.length,
    parse: parseNow,
    run: run,
  });
}

test('babel', function (code, callback) {
  return babel.transform(code, {
    sourceType: 'script',
    plugins: [
      [
        require('../src/replace-plugin'),
        {
          replacements: [{
            identifierName: '__DEV__',
            replacement: {
              type: 'booleanLiteral',
              value: true,
            },
          }],
        },
      ],
      require('../src/constant-folding-plugin'),
      require('../src/mangle-names-plugin'),
      require('../src/dce-plugin'),
      require('../src/simplify-plugin'),
    ],
    minified: true,
    comments: false,
  }).code;
});

if (filename.match(/fb\.js/)) {
  test('jsxmin', function (code, callback) {
    return fs.readFileSync(path.join(__dirname, 'fb-jsmin.js'));
  });
}

test('closure', function (code, callback) {
  return child.execSync(
    'java -jar ' + path.join(__dirname, 'gcc.jar') + ' --jscomp_off=uselessCode --js ' + filename
  );
});

test('uglify', function (code, callback) {
  return uglify.minify(code, {
    fromString: true,
  }).code;
});

results = results.sort(function (a, b) {
  return a.gzip > b.gzip;
});

results.forEach(function (result, i) {
  const row = [
    chalk.bold(result.name),
    bytes(result.raw),
    Math.round(((code.length / result.raw) * 100) - 100) + '%',
    bytes(result.gzip),
    Math.round(((gzippedCode.length / result.gzip) * 100) - 100) + '%',
    Math.round(result.parse) + 'ms',
    Math.round(result.run) + 'ms',
  ];

  const style = chalk.yellow;
  if (i === 0) {
    style = chalk.green;
  }
  if (i === results.length - 1) {
    style = chalk.red;
  }
  row = row.map(function (item) {
    return style(item);
  });

  table.push(row);
});

console.log(table.toString());
<|endoftext|>


<commit_before>/**
 * @file This is the window node module for the automatic tracing functionality
 * that supplies init and binding code for the PancakePainter window API.
 * Exports function returns a window control object that allows triggering on
 * init, show, and hide events.
 *
 * We have full access to globals loaded in the mainWindow as needed, just
 * reference them below.
 **/
 /*globals window, paper, $, path, app, mainWindow, i18n, _ */

module.exports = function(context) {

  // Central window detail object returned for windows[autotrace] object.
  var autotrace = {
    settings: {},
    defaults: {
      tracetype: 'mixed',
      posterize: 2,
      transparent: '#FFFFFF',
      blur: 0,
      outline: false,
      invert: false,
    },
    presets: { // Applied over the top of defaults
      simple: {},
      complex: {
        tracetype: "fills",
        blur: 3,
        posterize: 5,
        transparent: "#00FF00",
      },
    },
    cloneCount: 1, // Number of items to copy import/place.
    paper: {}, // PaperScope for auto trace preview
    preset: 'simple', // Preset window opens with.
    source: '', // Source file to be loaded.
    intermediary: path.join(app.getPath('temp'), 'pp_tempraster.png'),
    tracebmp: path.join(app.getPath('temp'), 'pp_tracesource.bmp'),
  };

  // Switch for detecting if a setting was changed by preset or by hand.
  // Avoids update thrashing cause by mass updates.
  var setByPreset = false;

  var $loadingBar = $('.bar-loader', context);

  // Load the auto trace PaperScript (only when the canvas is ready).
  var autoTraceLoaded = false;
  function autoTraceLoad() {
    if (!autoTraceLoaded) {
      autoTraceLoaded = true;
      autotrace.paper = paper.PaperScript.load($('<script>').attr({
        type:"text/paperscript",
        src: "autotrace.ps.js",
        canvas: "autotrace-preview"
      })[0]);
    }
  }

  /**
   * Apply a flat key:value object to the elements as input values.
   * @param  {Object} settings
   *   An object with keys matching the names and values matching the target
   *   values to set the elements to.
   */
  function applySettings(settings) {
    setByPreset = true; // Block updates.
    _.each(settings, function(value, name){
      var $elem = $('[name=' + name + ']');
      var type = $elem.attr('type') || $elem.prop('tagName').toLowerCase();
      switch (type) {
        case 'color':
        case 'range':
        case 'select':
          $elem.val(value);
          break;
        case 'checkbox':
          $elem.prop('checked', value);
          break;
        case 'radio':
          $elem.filter('[value=' + value + ']').prop('checked', true);
          break;
        default:
      }
      $elem.change();
    });

    // Force update here.
    autotrace.renderUpdate();
    setByPreset = false; // Ready for updates.
  }

  /**
   * Display the current clone setup with live preview image.
   */
  function clonePreview() {
    var $tp = $('div.trace-preview');

    // Clear out any trace images at start.
    $tp.find('img.trace').remove();

    var zoom = 7.5;
    var layout = autotrace.getCloneLayout();
    _.each(layout.positions, function(pos) {
      var $t = $('<img>', {class: 'trace', src: autotrace.previewRasterData});
      $t.css({
        left: (pos[0] / zoom) + 14,
        top: (pos[1] / zoom) + 14,
        transform: "translate(-50%, -50%) scale(" + (layout.scale / zoom) + ")",
      });
      $tp.append($t);
    });
  }

  /**
   * Figure out what the center positions and scales should be for cloneCount
   * and the currently traced image dimensions.
   * @return {Object}
   *   Contains array of positions (in x,y array format) and scale key.
   */
  autotrace.getCloneLayout = function() {
    var out = {scale: 1, positions: []};
    var count = autotrace.cloneCount;
    var traceBounds = autotrace.paper.svgLayer.bounds;

    if (!traceBounds.width) return out;

    var griddleBounds = mainWindow.editorPaperScope.view.bounds;
    var griddleAspect = griddleBounds.height / griddleBounds.width;
    var traceAspect = traceBounds.height / traceBounds.width;
    var landscape = traceAspect < griddleAspect;

    // Every cloned item has the same size regardless of count.
    var fillPercent;
    var q = {width: griddleBounds.width / 4, height: griddleBounds.height / 4};

    // How many?
    switch (count) {
      case 1: // 1 item, center, 80% fill.
        fillPercent = 80;
        out.positions = [[q.width * 2, q.height * 2]];
        break;
      case 2: // 2 items, 90% of 50% width/height. SBS or TB
        fillPercent = 50;

        if (landscape) { // SBS
          out.positions = [
            [q.width * 2, q.height],     // Center Top.
            [q.width * 2, q.height * 3], // Center Bottom.
          ];
        } else { // Top/Bottom
          out.positions = [
            [q.width, q.height * 2],     // Left Middle.
            [q.width * 3, q.height * 2], // Right Middle.
          ];
        }
        break;
      case 4: // 4 items, 92% of 25% width/height. Simple Quadrant
        fillPercent = 35;
        out.positions = [
          [q.width, q.height],         // Top Left.
          [q.width * 3, q.height],     // Top Right.
          [q.width, q.height * 3],     // Bottom Left.
          [q.width * 3, q.height * 3], // Bottom Right.
        ];
        break;
      case 8: // 8 items, 95% of 12.5% width/height. SBS or TB 4x grouping.
        fillPercent = 25;
        // Eighth measurement.
        var e = {width: q.width / 2, height: q.height / 2};

        if (landscape) { // SBS 4 rows of 2.
          out.positions = [
            [q.width, e.height],         // A Left.
            [q.width * 3, e.height],     // A Right.

            [q.width, e.height * 3],     // B Left.
            [q.width * 3, e.height * 3], // B Right.

            [q.width, e.height * 5],     // C Left.
            [q.width * 3, e.height * 5], // C Right.

            [q.width, e.height * 7],     // D Left.
            [q.width * 3, e.height * 7], // D Right.
          ];
        } else { // Top/Bottom 2 rows of 4.
          out.positions = [
            [e.width, q.height],         // Top Left a.
            [e.width * 3, q.height],     // Top Left b.
            [e.width * 5, q.height],     // Top Right a.
            [e.width * 7, q.height],     // Top Right b.

            [e.width, q.height * 3],     // Bottom Left a.
            [e.width * 3, q.height * 3], // Bottom Left b.
            [e.width * 5, q.height * 3], // Bottom Right a.
            [e.width * 7, q.height * 3], // Bottom Right b.
          ];
        }
        break;
    }

    fillPercent /= 100;
    var scale = {
      x: (griddleBounds.width * fillPercent) / traceBounds.width,
      y: (griddleBounds.height * fillPercent) / traceBounds.height
    };

    out.scale = (scale.x < scale.y ? scale.x : scale.y);
    return out;
  };

  // Bind the window's settings inputs into a single object on change.
  function bindSettings() {
    setByPreset = true; // Ignore updates for initial bind.
    $('input, select', context).change(function() {
      // Save each setting based on name attribute.
      if (this.type === 'checkbox') {
        autotrace.settings[this.name] = $(this).prop('checked');
      } else {
        if (this.type === 'radio') {
          if ($(this).prop('checked')) {
            autotrace.settings[this.name] = $(this).val();
          }
        } else {
          autotrace.settings[this.name] = $(this).val();
        }
      }

      if (!setByPreset) {
        autotrace.renderUpdate();
      }
    }).change(); // Trigger initial change to save data.

    // Bind change for changing type description.
    $('select[name=tracetype]', context).change(function() {
      $(this).siblings('aside').text(
        i18n.t('import.auto.settings.types.options.' + $(this).val() + '.desc')
      );
    });

    setByPreset = false; // Ready for updates.
  }

  /**
   * Place/import the traced SVG data from this paperscope to the editor.
   */
  function importTrace() {
    var json = autotrace.paper.svgLayer.exportJSON();
    mainWindow.editorPaperScope.activate();

    // Import the JSON of the SVG trace layer into a temporary layer,
    // then group the contents of that layer, and ditch the layer.
    var tmpLayer = new mainWindow.editorPaperScope.Layer();
    var group = new mainWindow.editorPaperScope.Group(
      tmpLayer.importJSON(json).removeChildren()
    );
    tmpLayer.remove();

    // Position each group into the correct clone layout position, then remove
    // it from its group (just used for positioning).
    var layout = autotrace.getCloneLayout();
    _.each(layout.positions, function(pos) {
      var g = group.clone();
      g.scale(layout.scale);
      g.position = new mainWindow.editorPaperScope.Point(pos);
      var items = g.removeChildren();
      mainWindow.editorPaperScope.mainLayer.addChildren(items);

      // Select added items if only one being cloned.
      if (autotrace.cloneCount === 1) {
        mainWindow.editorPaperScope.selectAll(items);
      }
      g.remove();
    });
    group.remove();

    // Trigger a history change state.
    mainWindow.editorPaperScope.fileChanged();
  }

  // Bind the buttons on the window.
  function bindButtons() {
    $('button', context).click(function() {
      switch(this.name) {
        case 'simple':
        case 'complex':
          applySettings(_.extend({},
            autotrace.defaults,
            autotrace.presets[this.name]
          ));
          break;

        case 'import':
          importTrace();
          /* falls through */

        case 'cancel':
          mainWindow.overlay.toggleWindow('autotrace', false);
          break;

        case 'transparent-pick':
          // TODO: add colorpicker
          break;

        case 'clone-1':
        case 'clone-2':
        case 'clone-4':
        case 'clone-8':
          autotrace.cloneCount = parseInt(this.name.split('-')[1]);
          clonePreview();
          break;
      }
    });

    // Bind ESC key exit.
    // TODO: Build this off data attr global bind thing.
    $(window).keydown(function(e){
      if (e.keyCode === 27) { // Global escape key exit window
        $('button[name=cancel]', context).click();
      }
    });
  }

  // Init after build event.
  autotrace.init = function() {
    bindSettings();
    bindButtons();
  };

  // Window show event.
  autotrace.show = function() {
    autoTraceLoad();

    // Activate the trace preview paperscope.
    autotrace.paper.activate();

    // Apply given preset settings.
    applySettings(_.extend({},
      autotrace.defaults,
      autotrace.presets[autotrace.preset]
    ));

    // Default to 1x.
    $('button[name=clone-1]').click();

    // Init load and build the images
    autotrace.paper.loadTraceImage().then(autotrace.renderUpdate);
  };

  // Trigger the normal trace render update.
  autotrace.renderUpdate = function () {
    // TODO: Rate limit/queue this.
    if (autoTraceLoaded) {
      $loadingBar.css('opacity', 100);
      autotrace.paper.renderTraceImage()
      .then(autotrace.paper.renderTraceVector)
      .then(clonePreview)
      .then(function() {
        $loadingBar.css('opacity', 0);
      });
    }
  };

  // Window hide event.
  autotrace.hide = function() {
    if (autoTraceLoaded) {
      // Cleanup the window.
      autotrace.paper.cleanup();
    }

    // Re-activate the default editor paperscope .
    mainWindow.editorPaperScope.activate();
  };

  return autotrace;
};
<commit_msg>Export the clonePreview function<commit_after>/**
 * @file This is the window node module for the automatic tracing functionality
 * that supplies init and binding code for the PancakePainter window API.
 * Exports function returns a window control object that allows triggering on
 * init, show, and hide events.
 *
 * We have full access to globals loaded in the mainWindow as needed, just
 * reference them below.
 **/
 /*globals window, paper, $, path, app, mainWindow, i18n, _ */

module.exports = function(context) {

  // Central window detail object returned for windows[autotrace] object.
  var autotrace = {
    settings: {},
    defaults: {
      tracetype: 'mixed',
      posterize: 2,
      transparent: '#FFFFFF',
      blur: 0,
      outline: false,
      invert: false,
    },
    presets: { // Applied over the top of defaults
      simple: {},
      complex: {
        tracetype: "fills",
        blur: 3,
        posterize: 5,
        transparent: "#00FF00",
      },
    },
    cloneCount: 1, // Number of items to copy import/place.
    paper: {}, // PaperScope for auto trace preview
    preset: 'simple', // Preset window opens with.
    source: '', // Source file to be loaded.
    intermediary: path.join(app.getPath('temp'), 'pp_tempraster.png'),
    tracebmp: path.join(app.getPath('temp'), 'pp_tracesource.bmp'),
  };

  // Switch for detecting if a setting was changed by preset or by hand.
  // Avoids update thrashing cause by mass updates.
  var setByPreset = false;

  var $loadingBar = $('.bar-loader', context);

  // Load the auto trace PaperScript (only when the canvas is ready).
  var autoTraceLoaded = false;
  function autoTraceLoad() {
    if (!autoTraceLoaded) {
      autoTraceLoaded = true;
      autotrace.paper = paper.PaperScript.load($('<script>').attr({
        type:"text/paperscript",
        src: "autotrace.ps.js",
        canvas: "autotrace-preview"
      })[0]);
    }
  }

  /**
   * Apply a flat key:value object to the elements as input values.
   * @param  {Object} settings
   *   An object with keys matching the names and values matching the target
   *   values to set the elements to.
   */
  function applySettings(settings) {
    setByPreset = true; // Block updates.
    _.each(settings, function(value, name){
      var $elem = $('[name=' + name + ']');
      var type = $elem.attr('type') || $elem.prop('tagName').toLowerCase();
      switch (type) {
        case 'color':
        case 'range':
        case 'select':
          $elem.val(value);
          break;
        case 'checkbox':
          $elem.prop('checked', value);
          break;
        case 'radio':
          $elem.filter('[value=' + value + ']').prop('checked', true);
          break;
        default:
      }
      $elem.change();
    });

    // Force update here.
    autotrace.renderUpdate();
    setByPreset = false; // Ready for updates.
  }

  /**
   * Display the current clone setup with live preview image.
   */
  autotrace.clonePreview = clonePreview;
  function clonePreview() {
    var $tp = $('div.trace-preview');

    // Clear out any trace images at start.
    $tp.find('img.trace').remove();

    var zoom = 7.5;
    var layout = autotrace.getCloneLayout();
    _.each(layout.positions, function(pos) {
      var $t = $('<img>', {class: 'trace', src: autotrace.previewRasterData});
      $t.css({
        left: (pos[0] / zoom) + 14,
        top: (pos[1] / zoom) + 14,
        transform: "translate(-50%, -50%) scale(" + (layout.scale / zoom) + ")",
      });
      $tp.append($t);
    });
  }

  /**
   * Figure out what the center positions and scales should be for cloneCount
   * and the currently traced image dimensions.
   * @return {Object}
   *   Contains array of positions (in x,y array format) and scale key.
   */
  autotrace.getCloneLayout = function() {
    var out = {scale: 1, positions: []};
    var count = autotrace.cloneCount;
    var traceBounds = autotrace.paper.svgLayer.bounds;

    if (!traceBounds.width) return out;

    var griddleBounds = mainWindow.editorPaperScope.view.bounds;
    var griddleAspect = griddleBounds.height / griddleBounds.width;
    var traceAspect = traceBounds.height / traceBounds.width;
    var landscape = traceAspect < griddleAspect;

    // Every cloned item has the same size regardless of count.
    var fillPercent;
    var q = {width: griddleBounds.width / 4, height: griddleBounds.height / 4};

    // How many?
    switch (count) {
      case 1: // 1 item, center, 80% fill.
        fillPercent = 80;
        out.positions = [[q.width * 2, q.height * 2]];
        break;
      case 2: // 2 items, 90% of 50% width/height. SBS or TB
        fillPercent = 50;

        if (landscape) { // SBS
          out.positions = [
            [q.width * 2, q.height],     // Center Top.
            [q.width * 2, q.height * 3], // Center Bottom.
          ];
        } else { // Top/Bottom
          out.positions = [
            [q.width, q.height * 2],     // Left Middle.
            [q.width * 3, q.height * 2], // Right Middle.
          ];
        }
        break;
      case 4: // 4 items, 92% of 25% width/height. Simple Quadrant
        fillPercent = 35;
        out.positions = [
          [q.width, q.height],         // Top Left.
          [q.width * 3, q.height],     // Top Right.
          [q.width, q.height * 3],     // Bottom Left.
          [q.width * 3, q.height * 3], // Bottom Right.
        ];
        break;
      case 8: // 8 items, 95% of 12.5% width/height. SBS or TB 4x grouping.
        fillPercent = 25;
        // Eighth measurement.
        var e = {width: q.width / 2, height: q.height / 2};

        if (landscape) { // SBS 4 rows of 2.
          out.positions = [
            [q.width, e.height],         // A Left.
            [q.width * 3, e.height],     // A Right.

            [q.width, e.height * 3],     // B Left.
            [q.width * 3, e.height * 3], // B Right.

            [q.width, e.height * 5],     // C Left.
            [q.width * 3, e.height * 5], // C Right.

            [q.width, e.height * 7],     // D Left.
            [q.width * 3, e.height * 7], // D Right.
          ];
        } else { // Top/Bottom 2 rows of 4.
          out.positions = [
            [e.width, q.height],         // Top Left a.
            [e.width * 3, q.height],     // Top Left b.
            [e.width * 5, q.height],     // Top Right a.
            [e.width * 7, q.height],     // Top Right b.

            [e.width, q.height * 3],     // Bottom Left a.
            [e.width * 3, q.height * 3], // Bottom Left b.
            [e.width * 5, q.height * 3], // Bottom Right a.
            [e.width * 7, q.height * 3], // Bottom Right b.
          ];
        }
        break;
    }

    fillPercent /= 100;
    var scale = {
      x: (griddleBounds.width * fillPercent) / traceBounds.width,
      y: (griddleBounds.height * fillPercent) / traceBounds.height
    };

    out.scale = (scale.x < scale.y ? scale.x : scale.y);
    return out;
  };

  // Bind the window's settings inputs into a single object on change.
  function bindSettings() {
    setByPreset = true; // Ignore updates for initial bind.
    $('input, select', context).change(function() {
      // Save each setting based on name attribute.
      if (this.type === 'checkbox') {
        autotrace.settings[this.name] = $(this).prop('checked');
      } else {
        if (this.type === 'radio') {
          if ($(this).prop('checked')) {
            autotrace.settings[this.name] = $(this).val();
          }
        } else {
          autotrace.settings[this.name] = $(this).val();
        }
      }

      if (!setByPreset) {
        autotrace.renderUpdate();
      }
    }).change(); // Trigger initial change to save data.

    // Bind change for changing type description.
    $('select[name=tracetype]', context).change(function() {
      $(this).siblings('aside').text(
        i18n.t('import.auto.settings.types.options.' + $(this).val() + '.desc')
      );
    });

    setByPreset = false; // Ready for updates.
  }

  /**
   * Place/import the traced SVG data from this paperscope to the editor.
   */
  function importTrace() {
    var json = autotrace.paper.svgLayer.exportJSON();
    mainWindow.editorPaperScope.activate();

    // Import the JSON of the SVG trace layer into a temporary layer,
    // then group the contents of that layer, and ditch the layer.
    var tmpLayer = new mainWindow.editorPaperScope.Layer();
    var group = new mainWindow.editorPaperScope.Group(
      tmpLayer.importJSON(json).removeChildren()
    );
    tmpLayer.remove();

    // Position each group into the correct clone layout position, then remove
    // it from its group (just used for positioning).
    var layout = autotrace.getCloneLayout();
    _.each(layout.positions, function(pos) {
      var g = group.clone();
      g.scale(layout.scale);
      g.position = new mainWindow.editorPaperScope.Point(pos);
      var items = g.removeChildren();
      mainWindow.editorPaperScope.mainLayer.addChildren(items);

      // Select added items if only one being cloned.
      if (autotrace.cloneCount === 1) {
        mainWindow.editorPaperScope.selectAll(items);
      }
      g.remove();
    });
    group.remove();

    // Trigger a history change state.
    mainWindow.editorPaperScope.fileChanged();
  }

  // Bind the buttons on the window.
  function bindButtons() {
    $('button', context).click(function() {
      switch(this.name) {
        case 'simple':
        case 'complex':
          applySettings(_.extend({},
            autotrace.defaults,
            autotrace.presets[this.name]
          ));
          break;

        case 'import':
          importTrace();
          /* falls through */

        case 'cancel':
          mainWindow.overlay.toggleWindow('autotrace', false);
          break;

        case 'transparent-pick':
          // TODO: add colorpicker
          break;

        case 'clone-1':
        case 'clone-2':
        case 'clone-4':
        case 'clone-8':
          autotrace.cloneCount = parseInt(this.name.split('-')[1]);
          clonePreview();
          break;
      }
    });

    // Bind ESC key exit.
    // TODO: Build this off data attr global bind thing.
    $(window).keydown(function(e){
      if (e.keyCode === 27) { // Global escape key exit window
        $('button[name=cancel]', context).click();
      }
    });
  }

  // Init after build event.
  autotrace.init = function() {
    bindSettings();
    bindButtons();
  };

  // Window show event.
  autotrace.show = function() {
    autoTraceLoad();

    // Activate the trace preview paperscope.
    autotrace.paper.activate();

    // Apply given preset settings.
    applySettings(_.extend({},
      autotrace.defaults,
      autotrace.presets[autotrace.preset]
    ));

    // Default to 1x.
    $('button[name=clone-1]').click();

    // Init load and build the images
    autotrace.paper.loadTraceImage().then(autotrace.renderUpdate);
  };

  // Trigger the normal trace render update.
  autotrace.renderUpdate = function () {
    // TODO: Rate limit/queue this.
    if (autoTraceLoaded) {
      $loadingBar.css('opacity', 100);
      autotrace.paper.renderTraceImage()
      .then(autotrace.paper.renderTraceVector)
      .then(clonePreview)
      .then(function() {
        $loadingBar.css('opacity', 0);
      });
    }
  };

  // Window hide event.
  autotrace.hide = function() {
    if (autoTraceLoaded) {
      // Cleanup the window.
      autotrace.paper.cleanup();
    }

    // Re-activate the default editor paperscope .
    mainWindow.editorPaperScope.activate();
  };

  return autotrace;
};
<|endoftext|>


<commit_before>'use strict';

(function(exports){

  var contentElem = undefined;
  var moveDialog = undefined;
  var delDialog = undefined;
  var gmClientLoggedIn = undefined;

  var rootTagKey = undefined;
  var keyToTag = {};

  function init(_gmClient, _contentElem, srcDir, queryParams, curTabData) {
    contentElem = _contentElem;

    // Setup tag move dialog {{{
    moveDialog = contentElem.find('#move_dialog')
    moveDialog.dialog({
      buttons: [
        {
          id: "move-button-ok",
          text: "Move!",
          click: function() {
            var self = this;

            var node = $(this).data("node");
            var data = $(this).data("data");

            var subj = data.otherNode;

            var leafPolicyVal = moveDialog.find('input[name=new_leaf_policy]:checked').val();
            var leafPolicy = undefined;
            switch (leafPolicyVal) {
              case "del":
                leafPolicy = gmClient.NEW_LEAF_POLICY_DEL;
                break;
              case "keep":
                leafPolicy = gmClient.NEW_LEAF_POLICY_KEEP;
                break;
              default:
                throw new Error("wrong leaf policy: " + leafPolicyVal);
                break;
            }

            // Disable "Move" button
            $("#move-button-ok").button("disable");

            gmClientLoggedIn.updateTag(subj.key, {
              parentTagID: data.node.key,
              newLeafPolicy: leafPolicy,
            }, function(status, resp) {
              // Enable "Move" button back
              $("#move-button-ok").button("enable");
              if (status == 200) {
                // move succeeded, so move the node visually, and close the
                // dialog
                subj.moveTo(node, data.hitMode);
                subj.makeVisible();
                $( self ).dialog( "close" );
              } else {
                // TODO: show error
                alert(JSON.stringify(resp));
              }

              $(data.node.span).removeClass("pending");
            });

          }
        },
        {
          id: "move-button-cancel",
          text: "Cancel",
          click: function() {
            $( this ).dialog( "close" );
          }
        },
      ],
      autoOpen: false,
      modal: true,
      minWidth: 400,
      maxHeight: 300,
      title: "Move tag",
    });
    moveDialog.on("dialogopen", function(event, ui) {
      var node = $(this).data("node");
      var data = $(this).data("data");
      var subj = data.otherNode;
      moveDialog.dialog(
        "option", "title",
        'Move "' + subj.title + '" under "' + data.node.title + '"'
      );
      moveDialog.find('input:radio[name=new_leaf_policy]')
        .filter('[value=del]')
        .prop('checked', true);
    });

    moveDialog.find('#move_dialog_details_link').click(function() {
      moveDialog.find('#move_dialog_details').toggle();
      return false;
    });
    // }}}

    // Setup tag deletion dialog {{{
    delDialog = contentElem.find('#del_dialog')
    delDialog.dialog({
      buttons: [
        {
          id: "del-button-ok",
          text: "Delete!",
          click: function() {
            var self = this;

            var node = $(this).data("node");

            var leafPolicyVal = delDialog.find('input[name=new_leaf_policy]:checked').val();
            var leafPolicy = undefined;
            switch (leafPolicyVal) {
              case "del":
                leafPolicy = gmClient.NEW_LEAF_POLICY_DEL;
                break;
              case "keep":
                leafPolicy = gmClient.NEW_LEAF_POLICY_KEEP;
                break;
              default:
                throw new Error("wrong leaf policy: " + leafPolicyVal);
                break;
            }

            // Disable "Delete" button
            $("#del-button-ok").button("disable");

            gmClientLoggedIn.deleteTag(
              node.key,
              leafPolicy,
              function(status, resp) {
                // Enable "Delete" button back
                $("#del-button-ok").button("enable");
                if (status == 200) {
                  // Deletion succeeded, so delete the node visually, and close
                  // the dialog
                  node.remove();
                  $( self ).dialog( "close" );
                } else {
                  // TODO: show error
                  alert(JSON.stringify(resp));
                }
              }
            );

          }
        },
        {
          id: "del-button-cancel",
          text: "Cancel",
          click: function() {
            $( this ).dialog( "close" );
          }
        },
      ],
      autoOpen: false,
      modal: true,
      minWidth: 400,
      maxHeight: 300,
      title: "Delete tag",
    });
    delDialog.on("dialogopen", function(event, ui) {
      var node = $(this).data("node");
      delDialog.dialog(
        "option", "title",
        'Delete "' + node.title + '"'
      );
      delDialog.find('input:radio[name=new_leaf_policy]')
        .filter('[value=keep]')
        .prop('checked', true);
    });

    delDialog.find('#del_dialog_details_link').click(function() {
      delDialog.find('#del_dialog_details').toggle();
      return false;
    });
    // }}}

    _gmClient.createGMClientLoggedIn().then(function(instance) {
      if (instance) {
        initLoggedIn(instance, contentElem, srcDir);
      } else {
        gmPageWrapper.openPageLogin("openPageTagsTree", []);
        gmPageWrapper.closeCurrentWindow();
      }
    });

  }

  function initLoggedIn(instance, contentElem, srcDir) {
    gmClientLoggedIn = instance;
    var tagsTreeDiv = contentElem.find('#tags_tree_div')

    gmClientLoggedIn.getTagsTree(function(status, resp) {
      if (status == 200) {
        var treeData = convertTreeData(resp, true);

        tagsTreeDiv.fancytree({
          extensions: ["edit", "table", "dnd"],
          edit: {
            adjustWidthOfs: 4,   // null: don't adjust input size to content
            inputCss: { minWidth: "3em" },
            triggerStart: ["f2", "shift+click", "mac+enter"],
            beforeEdit: function(event, data) {
              if (data.node.key === rootTagKey) {
                return false;
              }
              return true;
            },
            edit: $.noop,        // Editor was opened (available as data.input)
            beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
            save: saveTag,       // Save data.input.val() or return false to keep editor open
            close: $.noop,       // Editor was removed
          },
          table: {
          },
          dnd: {
            // Available options with their default:
            autoExpandMS: 1000,   // Expand nodes after n milliseconds of hovering
            draggable: null,      // Additional options passed to jQuery UI draggable
            droppable: null,      // Additional options passed to jQuery UI droppable
            focusOnClick: false,  // Focus, although draggable cancels mousedown event (#270)
            preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
            preventVoidMoves: true,      // Prevent dropping nodes 'before self', etc.
            smartRevert: true,    // set draggable.revert = true if drop was rejected

            // Events that make tree nodes draggable
            dragStart: function(node, data) {
              return true;
            },
            dragStop: null,       // Callback(sourceNode, data)
            initHelper: null,     // Callback(sourceNode, data)
            updateHelper: null,   // Callback(sourceNode, data)

            // Events that make tree nodes accept draggables
            dragEnter: function(node, data) {
              // allow only moving nodes under other nodes; do not allow
              // reordering.
              // (to allow reordering, the returned array should also contain
              // "before", "after".)
              return ["over"];
            },
            dragExpand: null,     // Callback(targetNode, data), return false to prevent autoExpand
            dragOver: null,       // Callback(targetNode, data)
            dragDrop: function(node, data) {
              // This function MUST be defined to enable dropping of items on the tree.
              // data.hitMode is 'before', 'after', or 'over'.
              // We could for example move the source to the new target:
              var oldParent = data.otherNode.parent;
              var subj = data.otherNode;

              // Open the move dialog
              moveDialog
                .data("node", node)
                .data("data", data)
                .dialog("open");
            },
            dragLeave: null       // Callback(targetNode, data)
          },
          source: {
            children: [treeData],
          },
          renderColumns: function(event, data) {
            if (data.node.key !== rootTagKey) {
              var node = data.node;
              var $tdList = $(node.tr).find(">td");
              var $ctrlCol = $tdList.eq(2);

              $ctrlCol.text("");

              // Add controls
              var $ctrlSpan = $("<span/>", {
                class: "hidden",
                id: "ctrl_" + data.node.key,
              });

              // Add "edit" link
              $("<a/>", {
                href: "#",
                text: "[edit]",
                click: function() {
                  gmPageWrapper.openPageEditTag(data.node.key);
                },
              }).appendTo($ctrlSpan);

              // Add "delete" link
              $("<a/>", {
                href: "#",
                text: "[x]",
                class: "delete",
                click: function() {
                  // Open the delete dialog
                  delDialog
                    .data("node", data.node)
                    .dialog("open");
                },
              }).appendTo($ctrlSpan);

              $ctrlSpan.appendTo($ctrlCol);

              $(node.tr).mouseover(function() {
                console.log($(this).addClass("highlighted-row"));
              });

              $(node.tr).mouseout(function() {
                console.log($(this).removeClass("highlighted-row"));
              });

              $tdList.mouseover(function() {
                $tdList.find("#ctrl_" + data.node.key).show();
              })

              $tdList.mouseout(function() {
                $tdList.find("#ctrl_" + data.node.key).hide();
              })
            }
          },
        });

        var tree = tagsTreeDiv.fancytree("getTree");
        var rootNode = tree.getNodeByKey(rootTagKey);
        rootNode.setExpanded(true);
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
      }
    })
  }

  function convertTreeData(tagsTree, isRoot) {
    var key = String(tagsTree.id);

    var ret = {
      title: tagsTree.names.join(", "),
      key: key,
    };

    keyToTag[key] = tagsTree;

    if (isRoot) {
      ret.title = "my tags";
      rootTagKey = key;
    }
    if ("subtags" in tagsTree) {
      ret.children = tagsTree.subtags.map(function(a) {
        return convertTreeData(a, false);
      });
      ret.folder = true;
    }
    return ret;
  }

  // see https://github.com/mar10/fancytree/wiki/ExtEdit for argument details
  function saveTag(event, data) {
    $(data.node.span).addClass("pending");
    var val = data.input.val();
    var prevVal = data.orgTitle;
    //console.log('saveTag event', event)
    //console.log('saveTag data', data)

    gmClientLoggedIn.updateTag(String(data.node.key), {
      names: val.split(",").map(function(a) {
        return a.trim();
      }),
    }, function(status, resp) {
      if (status == 200) {
        // update succeeded, do nothing here
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
        data.node.setTitle(prevVal);
      }

      $(data.node.span).removeClass("pending");
    });

    // Optimistically assume that save will succeed. Accept the user input
    return true;
  }

  exports.init = init;

})(typeof exports === 'undefined' ? this['gmTagsTree']={} : exports);
<commit_msg>Fix tag deletion dialog<commit_after>'use strict';

(function(exports){

  var contentElem = undefined;
  var moveDialog = undefined;
  var delDialog = undefined;
  var gmClientLoggedIn = undefined;

  var rootTagKey = undefined;
  var keyToTag = {};

  function init(_gmClient, _contentElem, srcDir, queryParams, curTabData) {
    contentElem = _contentElem;

    // Setup tag move dialog {{{
    moveDialog = contentElem.find('#move_dialog')
    moveDialog.dialog({
      buttons: [
        {
          id: "move-button-ok",
          text: "Move!",
          click: function() {
            var self = this;

            var node = $(this).data("node");
            var data = $(this).data("data");

            var subj = data.otherNode;

            var leafPolicyVal = moveDialog.find('input[name=new_leaf_policy]:checked').val();
            var leafPolicy = undefined;
            switch (leafPolicyVal) {
              case "del":
                leafPolicy = gmClient.NEW_LEAF_POLICY_DEL;
                break;
              case "keep":
                leafPolicy = gmClient.NEW_LEAF_POLICY_KEEP;
                break;
              default:
                throw new Error("wrong leaf policy: " + leafPolicyVal);
                break;
            }

            // Disable "Move" button
            $("#move-button-ok").button("disable");

            gmClientLoggedIn.updateTag(subj.key, {
              parentTagID: data.node.key,
              newLeafPolicy: leafPolicy,
            }, function(status, resp) {
              // Enable "Move" button back
              $("#move-button-ok").button("enable");
              if (status == 200) {
                // move succeeded, so move the node visually, and close the
                // dialog
                subj.moveTo(node, data.hitMode);
                subj.makeVisible();
                $( self ).dialog( "close" );
              } else {
                // TODO: show error
                alert(JSON.stringify(resp));
              }

              $(data.node.span).removeClass("pending");
            });

          }
        },
        {
          id: "move-button-cancel",
          text: "Cancel",
          click: function() {
            $( this ).dialog( "close" );
          }
        },
      ],
      autoOpen: false,
      modal: true,
      minWidth: 400,
      maxHeight: 300,
      title: "Move tag",
    });
    moveDialog.on("dialogopen", function(event, ui) {
      var node = $(this).data("node");
      var data = $(this).data("data");
      var subj = data.otherNode;
      moveDialog.dialog(
        "option", "title",
        'Move "' + subj.title + '" under "' + data.node.title + '"'
      );
      moveDialog.find('input:radio[name=new_leaf_policy]')
        .filter('[value=del]')
        .prop('checked', true);
    });

    moveDialog.find('#move_dialog_details_link').click(function() {
      moveDialog.find('#move_dialog_details').toggle();
      return false;
    });
    // }}}

    // Setup tag deletion dialog {{{
    delDialog = contentElem.find('#del_dialog')
    delDialog.dialog({
      buttons: [
        {
          id: "del-button-ok",
          text: "Delete!",
          click: function() {
            var self = this;

            var node = $(this).data("node");

            var leafPolicyVal = delDialog.find('input[name=new_leaf_policy]:checked').val();
            var leafPolicy = undefined;
            switch (leafPolicyVal) {
              case "del":
                leafPolicy = gmClient.NEW_LEAF_POLICY_DEL;
                break;
              case "keep":
                leafPolicy = gmClient.NEW_LEAF_POLICY_KEEP;
                break;
              default:
                throw new Error("wrong leaf policy: " + leafPolicyVal);
                break;
            }

            // Disable "Delete" button
            $("#del-button-ok").button("disable");

            gmClientLoggedIn.deleteTag(
              node.key,
              leafPolicy,
              function(status, resp) {
                // Enable "Delete" button back
                $("#del-button-ok").button("enable");
                if (status == 200) {
                  // Deletion succeeded, so delete the node visually, and close
                  // the dialog
                  node.remove();
                  $( self ).dialog( "close" );
                } else {
                  // TODO: show error
                  alert(JSON.stringify(resp));
                }
              }
            );

          }
        },
        {
          id: "del-button-cancel",
          text: "Cancel",
          click: function() {
            $( this ).dialog( "close" );
          }
        },
      ],
      autoOpen: false,
      modal: true,
      minWidth: 400,
      maxHeight: 300,
      title: "Delete tag",
    });
    delDialog.on("dialogopen", function(event, ui) {
      var node = $(this).data("node");
      delDialog.dialog(
        "option", "title",
        'Delete "' + node.title + '"'
      );
      delDialog.find('input:radio[name=new_leaf_policy]')
        .filter('[value=keep]')
        .prop('checked', true);
    });

    delDialog.find('#del_dialog_details_link').click(function() {
      delDialog.find('#del_dialog_details').toggle();
      return false;
    });
    // }}}

    _gmClient.createGMClientLoggedIn().then(function(instance) {
      if (instance) {
        initLoggedIn(instance, contentElem, srcDir);
      } else {
        gmPageWrapper.openPageLogin("openPageTagsTree", []);
        gmPageWrapper.closeCurrentWindow();
      }
    });

  }

  function initLoggedIn(instance, contentElem, srcDir) {
    gmClientLoggedIn = instance;
    var tagsTreeDiv = contentElem.find('#tags_tree_div')

    gmClientLoggedIn.getTagsTree(function(status, resp) {
      if (status == 200) {
        var treeData = convertTreeData(resp, true);

        tagsTreeDiv.fancytree({
          extensions: ["edit", "table", "dnd"],
          edit: {
            adjustWidthOfs: 4,   // null: don't adjust input size to content
            inputCss: { minWidth: "3em" },
            triggerStart: ["f2", "shift+click", "mac+enter"],
            beforeEdit: function(event, data) {
              if (data.node.key === rootTagKey) {
                return false;
              }
              return true;
            },
            edit: $.noop,        // Editor was opened (available as data.input)
            beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
            save: saveTag,       // Save data.input.val() or return false to keep editor open
            close: $.noop,       // Editor was removed
          },
          table: {
          },
          dnd: {
            // Available options with their default:
            autoExpandMS: 1000,   // Expand nodes after n milliseconds of hovering
            draggable: null,      // Additional options passed to jQuery UI draggable
            droppable: null,      // Additional options passed to jQuery UI droppable
            focusOnClick: false,  // Focus, although draggable cancels mousedown event (#270)
            preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
            preventVoidMoves: true,      // Prevent dropping nodes 'before self', etc.
            smartRevert: true,    // set draggable.revert = true if drop was rejected

            // Events that make tree nodes draggable
            dragStart: function(node, data) {
              return true;
            },
            dragStop: null,       // Callback(sourceNode, data)
            initHelper: null,     // Callback(sourceNode, data)
            updateHelper: null,   // Callback(sourceNode, data)

            // Events that make tree nodes accept draggables
            dragEnter: function(node, data) {
              // allow only moving nodes under other nodes; do not allow
              // reordering.
              // (to allow reordering, the returned array should also contain
              // "before", "after".)
              return ["over"];
            },
            dragExpand: null,     // Callback(targetNode, data), return false to prevent autoExpand
            dragOver: null,       // Callback(targetNode, data)
            dragDrop: function(node, data) {
              // This function MUST be defined to enable dropping of items on the tree.
              // data.hitMode is 'before', 'after', or 'over'.
              // We could for example move the source to the new target:
              var oldParent = data.otherNode.parent;
              var subj = data.otherNode;

              // Open the move dialog
              moveDialog
                .data("node", node)
                .data("data", data)
                .dialog("open");
              return false;
            },
            dragLeave: null       // Callback(targetNode, data)
          },
          source: {
            children: [treeData],
          },
          renderColumns: function(event, data) {
            if (data.node.key !== rootTagKey) {
              var node = data.node;
              var $tdList = $(node.tr).find(">td");
              var $ctrlCol = $tdList.eq(2);

              $ctrlCol.text("");

              // Add controls
              var $ctrlSpan = $("<span/>", {
                class: "hidden",
                id: "ctrl_" + data.node.key,
              });

              // Add "edit" link
              $("<a/>", {
                href: "#",
                text: "[edit]",
                click: function() {
                  gmPageWrapper.openPageEditTag(data.node.key);
                },
              }).appendTo($ctrlSpan);

              // Add "delete" link
              $("<a/>", {
                href: "#",
                text: "[x]",
                class: "delete",
                click: function() {
                  // Open the delete dialog
                  delDialog
                    .data("node", data.node)
                    .dialog("open");
                  return false;
                },
              }).appendTo($ctrlSpan);

              $ctrlSpan.appendTo($ctrlCol);

              $(node.tr).mouseover(function() {
                console.log($(this).addClass("highlighted-row"));
              });

              $(node.tr).mouseout(function() {
                console.log($(this).removeClass("highlighted-row"));
              });

              $tdList.mouseover(function() {
                $tdList.find("#ctrl_" + data.node.key).show();
              })

              $tdList.mouseout(function() {
                $tdList.find("#ctrl_" + data.node.key).hide();
              })
            }
          },
        });

        var tree = tagsTreeDiv.fancytree("getTree");
        var rootNode = tree.getNodeByKey(rootTagKey);
        rootNode.setExpanded(true);
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
      }
    })
  }

  function convertTreeData(tagsTree, isRoot) {
    var key = String(tagsTree.id);

    var ret = {
      title: tagsTree.names.join(", "),
      key: key,
    };

    keyToTag[key] = tagsTree;

    if (isRoot) {
      ret.title = "my tags";
      rootTagKey = key;
    }
    if ("subtags" in tagsTree) {
      ret.children = tagsTree.subtags.map(function(a) {
        return convertTreeData(a, false);
      });
      ret.folder = true;
    }
    return ret;
  }

  // see https://github.com/mar10/fancytree/wiki/ExtEdit for argument details
  function saveTag(event, data) {
    $(data.node.span).addClass("pending");
    var val = data.input.val();
    var prevVal = data.orgTitle;
    //console.log('saveTag event', event)
    //console.log('saveTag data', data)

    gmClientLoggedIn.updateTag(String(data.node.key), {
      names: val.split(",").map(function(a) {
        return a.trim();
      }),
    }, function(status, resp) {
      if (status == 200) {
        // update succeeded, do nothing here
      } else {
        // TODO: show error
        alert(JSON.stringify(resp));
        data.node.setTitle(prevVal);
      }

      $(data.node.span).removeClass("pending");
    });

    // Optimistically assume that save will succeed. Accept the user input
    return true;
  }

  exports.init = init;

})(typeof exports === 'undefined' ? this['gmTagsTree']={} : exports);
<|endoftext|>


<commit_before>import React from "react"
import MouseHandler from "./NoteMouseHandler"
import { pointSub, pointAdd } from "../helpers/point"

import { ContextMenu, MenuItem as ContextMenuItem, createContextMenu } from "../components/molecules/ContextMenu"

export default class SelectionMouseHandler extends MouseHandler {
  actionForMouseDown(e) {
    const original = super.actionForMouseDown(e)
    if (original) {
      return original
    }

    const c = this.selectionController
    if (!c) {
      throw new Error("this.selectionController をセットすること")
    }

    if (e.relatedTarget) {
      return null
    }

    const type = c.positionType(e.local)

    if (e.nativeEvent.button === 0) {
      switch (type) {
        case "center": return moveSelectionAction(
          p => c.moveTo(p),
          () => c.getRect())
        case "right": return dragSelectionRightEdgeAction(p => c.resizeRight(p))
        case "left": return dragSelectionLeftEdgeAction(p => c.resizeLeft(p))
        case "outside": break
      }

      return createSelectionAction(
        p => c.startAt(p),
        p => c.resize(p),
        () => c.selectNotes(),
        p => c.setPlayerCursor(p)
      )
    }

    if (e.nativeEvent.button === 2) {
      let selected
      switch (type) {
        case "center":
        case "right":
        case "left":
          selected = true
          break
        case "outside":
          selected = false
          break
      }
      return contextMenuAction(selected,
        () => c.copySelection(),
        () => c.pasteSelection(),
        () => c.deleteSelection()
      )
    }

    return null
  }

  getCursor(e) {
    const c = this.selectionController
    if (!c) {
      throw new Error("this.selectionController をセットすること")
    }

    const type = c.positionType(e.local)
    switch(type) {
      case "center" : return "move"
      case "left": return "w-resize"
      case "right": return "w-resize"
      default: return "crosshair"
    }
  }
}

const contextMenuAction = (selected, copySelection, pasteSelection, deleteSelection) => (onMouseDown, onMouseMove, onMouseUp) => {
  const contextMenu = close =>
    <ContextMenu>
      {selected && <ContextMenuItem onClick={() => {
        copySelection()
        deleteSelection()
        close()
      }}>Cut</ContextMenuItem>}
      {selected && <ContextMenuItem onClick={() => {
        copySelection()
        close()
      }}>Copy</ContextMenuItem>}
      <ContextMenuItem onClick={() => {
        pasteSelection()
        close()
      }}>Paste</ContextMenuItem>
      {selected && <ContextMenuItem onClick={() => {
        deleteSelection()
        close()
      }}>Delete</ContextMenuItem>}
    </ContextMenu>

  const menuCreator = createContextMenu(contextMenu)

  onMouseUp(e => {
    menuCreator(e.nativeEvent)
  })
}

// 選択範囲外でクリックした場合は選択範囲をリセット
const createSelectionAction = (startAt, resize, selectNotes, setPlayerCursor) => (onMouseDown, onMouseMove, onMouseUp) => {
  onMouseDown(e => {
    startAt(e.local)
    setPlayerCursor(e.local)
  })
  onMouseMove(e => resize(e.local))
  onMouseUp(() => selectNotes())
}

const moveSelectionAction = (moveTo, getRect) => (onMouseDown, onMouseMove) => {
  let startPos
  let selectionPos

  onMouseDown(e => {
    startPos = e.local
    selectionPos = getRect()
  })

  onMouseMove(e => {
    const pos = pointAdd(selectionPos, pointSub(e.local, startPos))
    moveTo(pos)
  })
}

const dragSelectionLeftEdgeAction = resizeLeft => (onMouseDown, onMouseMove) => {
  onMouseDown(() => {})

  onMouseMove(e => {
    resizeLeft(e.local)
  })
}

const dragSelectionRightEdgeAction = resizeRight => (onMouseDown, onMouseMove) => {
  onMouseDown(() => {})

  onMouseMove(e => {
    resizeRight(e.local)
  })
}
<commit_msg>improve selection<commit_after>import React from "react"
import MouseHandler from "./NoteMouseHandler"
import { pointSub, pointAdd } from "../helpers/point"

import { ContextMenu, MenuItem as ContextMenuItem, createContextMenu } from "../components/molecules/ContextMenu"

export default class SelectionMouseHandler extends MouseHandler {
  actionForMouseDown(e) {
    const original = super.actionForMouseDown(e)
    if (original) {
      return original
    }

    const c = this.selectionController
    if (!c) {
      throw new Error("this.selectionController をセットすること")
    }

    if (e.relatedTarget) {
      return null
    }

    const type = c.positionType(e.local)

    if (e.nativeEvent.button === 0) {
      switch (type) {
        case "center": return moveSelectionAction(
          p => c.moveTo(p),
          () => c.getRect())
        case "right": return dragSelectionRightEdgeAction(p => c.resizeRight(p))
        case "left": return dragSelectionLeftEdgeAction(p => c.resizeLeft(p))
        case "outside": break
      }

      return createSelectionAction(
        p => c.startAt(p),
        p => c.resize(p),
        () => c.selectNotes(),
        p => c.setPlayerCursor(p)
      )
    }

    if (e.nativeEvent.button === 2) {
      let selected
      switch (type) {
        case "center":
        case "right":
        case "left":
          selected = true
          break
        case "outside":
          selected = false
          break
      }
      return contextMenuAction(selected,
        () => c.copySelection(),
        () => c.pasteSelection(),
        () => c.deleteSelection()
      )
    }

    return null
  }

  getCursor(e) {
    const c = this.selectionController
    if (!c) {
      throw new Error("this.selectionController をセットすること")
    }

    const type = c.positionType(e.local)
    switch(type) {
      case "center" : return "move"
      case "left": return "w-resize"
      case "right": return "w-resize"
      default: return "crosshair"
    }
  }
}

const contextMenuAction = (selected, copySelection, pasteSelection, deleteSelection) => (onMouseDown, onMouseMove, onMouseUp) => {
  const contextMenu = close =>
    <ContextMenu>
      {selected && <ContextMenuItem onClick={() => {
        copySelection()
        deleteSelection()
        close()
      }}>Cut</ContextMenuItem>}
      {selected && <ContextMenuItem onClick={() => {
        copySelection()
        close()
      }}>Copy</ContextMenuItem>}
      <ContextMenuItem onClick={() => {
        pasteSelection()
        close()
      }}>Paste</ContextMenuItem>
      {selected && <ContextMenuItem onClick={() => {
        deleteSelection()
        close()
      }}>Delete</ContextMenuItem>}
    </ContextMenu>

  const menuCreator = createContextMenu(contextMenu)

  onMouseUp(e => {
    menuCreator(e.nativeEvent)
  })
}

// 選択範囲外でクリックした場合は選択範囲をリセット
const createSelectionAction = (startAt, resize, selectNotes, setPlayerCursor) => (onMouseDown) => {
  let rect
  onMouseDown(e => {
    document.addEventListener("mousemove", onMouseMove)
    document.addEventListener("mouseup", onMouseUp)

    rect =  e.currentTarget.getBoundingClientRect()
    startAt(e.local)
    setPlayerCursor(e.local)
  })

  function onMouseMove(e) {
    const local = {
      x: Math.round(e.clientX - rect.left),
      y: Math.round(e.clientY - rect.top)
    }
    resize(local)
  }

  function onMouseUp() {
    document.removeEventListener("mousemove", onMouseMove)
    document.removeEventListener("mouseup", onMouseUp)

    selectNotes()
  }
}

const moveSelectionAction = (moveTo, getRect) => (onMouseDown, onMouseMove) => {
  let startPos
  let selectionPos

  onMouseDown(e => {
    startPos = e.local
    selectionPos = getRect()
  })

  onMouseMove(e => {
    const pos = pointAdd(selectionPos, pointSub(e.local, startPos))
    moveTo(pos)
  })
}

const dragSelectionLeftEdgeAction = resizeLeft => (onMouseDown, onMouseMove) => {
  onMouseDown(() => {})

  onMouseMove(e => {
    resizeLeft(e.local)
  })
}

const dragSelectionRightEdgeAction = resizeRight => (onMouseDown, onMouseMove) => {
  onMouseDown(() => {})

  onMouseMove(e => {
    resizeRight(e.local)
  })
}
<|endoftext|>