<commit_before>// Copyright 2015 Endless Mobile, Inc.

/* exported Card, MinSize, MaxSize */

const EosKnowledgePrivate = imports.gi.EosKnowledgePrivate;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Mainloop = imports.mainloop;

const ArticleObjectModel = imports.search.articleObjectModel;
const ContentObjectModel = imports.search.contentObjectModel;
const Engine = imports.search.engine;
const ImageCoverFrame = imports.app.widgets.imageCoverFrame;
const Module = imports.app.interfaces.module;
const SetObjectModel = imports.search.setObjectModel;
const SpaceContainer = imports.app.widgets.spaceContainer;
const StyleClasses = imports.app.styleClasses;
const Utils = imports.app.utils;

/**
 * Constants: MinSize
 * Minimum size of cards for each size class
 *
 * Members:
 *  A - Smallest possible size for cards
 *  B - Size B
 *  C - Size C
 *  D - Size D
 *  E - Size E
 *  F - Currently width-only
 *  G - Currently width-only
 *  H - Currently width-only
 */
const MinSize = {
    A: 100,
    B: 200,
    C: 300,
    D: 400,
    E: 600,
    F: 800,
    G: 1000,
    H: 1200,
};

/**
 * Constants: MaxSize
 * Maximum size of a card for each size class
 *
 * Members:
 *  A - Size A
 *  B - Size B
 *  C - Size C
 *  D - Size D
 *  E - This is the largest height class that a card will currently get.
 *      Even cards that are taller than this will still get class E.
 *  F - Currently width-only
 *  G - Currently width-only
 *  H - Currently width-only.
 *      Even cards that are wider than this will still get class H.
 */
const MaxSize = {
    A: MinSize.B - 1,
    B: MinSize.C - 1,
    C: MinSize.D - 1,
    D: MinSize.E - 1,
    E: MinSize.F - 1,
    F: MinSize.G - 1,
    G: MinSize.H - 1,
    H: 1399,
};

/**
 * Interface: Card
 * Interface for card modules
 *
 * Requires:
 *   Gtk.Widget
 */
const Card = new Lang.Interface({
    Name: 'Card',
    GTypeName: 'EknCard',
    Requires: [ Gtk.Widget, Module.Module ],

    Properties: {
        /**
         * Property: model
         * Record backing this card
         *
         * Every card is backed by a record in the database.
         * A card's record is represented by a <ContentObjectModel> or one of
         * its subclasses.
         *
         * Type:
         *   <ContentObjectModel>
         *
         * Flags:
         *   Construct only
         */
        'model': GObject.ParamSpec.object('model', 'Model',
            'Card model with which to create this card',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            ContentObjectModel.ContentObjectModel),
        /**
         * Property: title-capitalization
         * Manner in which the card's title is formatted
         *
         * This property is a temporary stand-in for achieving this via the CSS
         * *text-transform* property.
         */
        'title-capitalization': GObject.ParamSpec.enum('title-capitalization',
            'Title capitalization', 'Manner in which the title is formatted',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            EosKnowledgePrivate.TextTransformType,
            EosKnowledgePrivate.TextTransform.NONE),
        /**
         * Property: highlight-string
         * A substring within a card's title or synopsis to get highlighted
         *
         * Sometimes we want to highlight a particular substring with the
         * text of a card. This property specifies which substring to highlight.
         */
        'highlight-string': GObject.ParamSpec.string('highlight-string',
            'Highlight string', 'Substring to be highlighted on card',
            GObject.ParamFlags.READWRITE,
            ''),
        /**
         * Property: text-halign
         * Horizontal alignment of text when card is in _not_ in horizontal mode.
         *
         * Default value:
         *   **Gtk.Align.CENTER**
         */
        'text-halign': GObject.ParamSpec.enum('text-halign',
            'Title halign', 'Horizontal alignment of title text',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            Gtk.Align.$gtype, Gtk.Align.CENTER),

    },

    set css (v) {
        if (this._css === v)
            return;
        this._css = v;
        if (this._css) {
            Utils.apply_css_to_widget(this._css, this);
        }
        this.notify('css');
    },

    get css () {
        if (this._css)
            return this._css;
        return '';
    },

    get highlight_string () {
        if (this._highlight_string)
            return this._highlight_string;
        return '';
    },

    set highlight_string (value) {
        if (this._highlight_string === value)
            return;
        this._highlight_string = value;
        this.update_highlight_string();
        this.notify('highlight-string');
    },

    // Overridable in tests; otherwise keep synchronized with the CSS
    FADE_IN_TIME_MS: 1000,
    NUM_STYLE_VARIANTS: 3,

    /**
     * Method: set_label_or_hide
     *
     * Sets a label contents and hides if contents is empty.
     */
    set_label_or_hide: function (label, text) {
        label.label = GLib.markup_escape_text(text, -1);
        label.visible = !!text;
    },

    /**
     * Method: set_context_label_from_model
     *
     * Sets the text on a context label. The context is the list of tags
     * associated with this article. Tags are also incidentally the same
     * as set titles.
     *
     * According to designs (and based on what fits on screen), we only want
     * to show a maximum of two set titles in the context label.
     * Moreover, we want to exclude the 'Ekn'-prefixed tags which won't mean
     * anything to the user.
     */
    set_context_label_from_model: function (container) {
        this._space_container = new SpaceContainer.SpaceContainer({
            orientation: Gtk.Orientation.HORIZONTAL,
        });

        // Sort the context tags from shortest to longest in order to
        // maximise chances that we can fit two of them on the card.
        let tags = this.model.tags.filter((tag) => !tag.startsWith('Ekn'))
                              .sort((a, b) => a.length - b.length);
        if (tags.length > 0) {
            let first_tag = new Gtk.Label({
                lines: 1,
                label: tags[0].toLocaleUpperCase(),
            });
            this._space_container.add(first_tag);

            if (tags.length > 1) {
                let second_tag = new Gtk.Label({
                    lines: 1,
                    label: ' | ' + tags[1].toLocaleUpperCase(),
                });
                this._space_container.add(second_tag);
            }
        }

        this._space_container.get_style_context().add_class('card-context');
        this._space_container.show_all();
        container.add(this._space_container);
    },

    /**
     * Method: add_contextual_css_class
     *
     * Adds a css class based on the type of model this is.
     */
    add_contextual_css_class: function () {
        if (this.model instanceof SetObjectModel.SetObjectModel) {
            this.get_style_context().add_class(StyleClasses.SET);
        } else if (this.model instanceof ArticleObjectModel.ArticleObjectModel) {
            this.get_style_context().add_class(StyleClasses.ARTICLE);
        }
    },

    /**
     * Method: set_thumbnail_frame_from_model
     *
     * Sets up a frame to show the model's thumbnail uri.
     */
    set_thumbnail_frame_from_model: function (frame) {
        frame.visible = false;
        if (!this.model.thumbnail_uri)
            return;

        let scheme = Gio.File.new_for_uri(this.model.thumbnail_uri).get_uri_scheme();
        let coveredFrame = new ImageCoverFrame.ImageCoverFrame();
        frame.add(coveredFrame);
        let stream;
        // FIXME: to actually support ekn uris here, we'd need a gvfs
        // extension or something like that
        if (scheme === 'ekn') {
            Engine.get_default().get_object_by_id(this.model.thumbnail_uri, null, (engine, task) => {
                let media_object;
                try {
                    media_object = engine.get_object_by_id_finish(task);
                } catch (error) {
                    logError(error);
                    return;
                }
                coveredFrame.set_content(media_object.get_content_stream());
            });
        } else {
            coveredFrame.set_content(Gio.File.new_for_uri(this.model.thumbnail_uri).read(null));
        }
        frame.visible = true;
    },

    /**
     * Method: set_label_with_highlight
     *
     * Sets up a label that should highlight any substrings within it that
     * match the card's highlight-string property.
     * Does the same as <set_label_or_hide()> but highlights the highlight
     * string in the label as well.
     */
    set_label_with_highlight: function (label, str) {
        // parenthesize the targeted string so we can reference it later on in
        // the replace step using '$1'. This is so we can preserve case
        // sensitivity when doing the replacement.
        let regex = new RegExp('(' + this.highlight_string + ')', 'gi');
        let title = GLib.markup_escape_text(str, -1);
        if (this.highlight_string) {
            let context = label.get_style_context();
            context.save();
            context.add_class(StyleClasses.HIGHLIGHTED);
            let span = Utils.style_context_to_markup_span(label.get_style_context(), Gtk.StateFlags.NORMAL);
            title = title.replace(regex, span + '$1</span>');
            context.restore();
        }
        label.label = title;
        label.visible = !!title;
    },

    /**
     * Method: update_highlight_string
     * Implement for highlighting card titles and synopses
     *
     * Implement this method in order to allow highlighting search terms on
     * card titles and synopses.
     * If not overridden, then setting <highlight-string> will have no effect.
     *
     * Note that this may be called early in the construct phase of your object,
     * so make sure that you check whether any widgets you access exist.
     * Also, make sure to call it after chaining up in your constructor.
     *
     * There is no need to chain up when overriding this method, since it has no
     * effect by default.
     */
    update_highlight_string: function () {
        // no-op unless overridden, but not mandatory to override
    },

    /**
     * Method: set_title_label_from_model
     *
     * Sets up a label to show the model's title.
     */
    set_title_label_from_model: function (label) {
        this.set_label_or_hide(label,
            Utils.format_capitals(this.model.title, this.title_capitalization));
    },

    /**
     * Method: set_title_label_with_highlight
     *
     * Sets up a label to show the model's title, and also highlights the
     * highlight string in the title.
     */
    set_title_label_with_highlight: function (label) {
        this.set_label_with_highlight(label,
            Utils.format_capitals(this.model.title, this.title_capitalization));
    },

    /**
     * Method: set_author_label_from_model
     *
     * Sets up a label to show the model's authors.
     */
    set_author_label_from_model: function (label) {
        this.set_label_or_hide(label, Utils.format_authors(this.model.authors));
    },

    /**
     * Method: set_style_variant_from_model
     * Uses the article number to set a style variant CSS class
     *
     * Adds one of the CSS classes 'variant0', 'variant1', or 'variant2' to the
     * card, depending on the <ArticleObjectModel.article_number> property if it
     * is present.
     */
    set_style_variant_from_model: function () {
        if (this.model.article_number !== undefined) {
            let style = this.model.article_number % this.NUM_STYLE_VARIANTS;
            this.get_style_context().add_class('variant' + style);
        }
    },

    /**
     * Method: update_card_sizing_classes
     * Assigns the appropriate CSS classes based on the height and width
     *
     * This method takes the height and width of the card widget and assigns the
     * appropriate CSS classes, according to our design constraints.
     */
    update_card_sizing_classes: function (height, width) {
        let width_class, height_class;

        if (width <= MaxSize.A) {
            width_class = StyleClasses.CARD_WIDTH.A;
        } else if (width <= MaxSize.B) {
            width_class = StyleClasses.CARD_WIDTH.B;
        } else if (width <= MaxSize.C) {
            width_class = StyleClasses.CARD_WIDTH.C;
        } else if (width <= MaxSize.D) {
            width_class = StyleClasses.CARD_WIDTH.D;
        } else if (width <= MaxSize.E) {
            width_class = StyleClasses.CARD_WIDTH.E;
        } else if (width <= MaxSize.F) {
            width_class = StyleClasses.CARD_WIDTH.F;
        } else if (width <= MaxSize.G) {
            width_class = StyleClasses.CARD_WIDTH.G;
        } else {
            width_class = StyleClasses.CARD_WIDTH.H;
        }

        if (height <= MaxSize.A) {
            height_class = StyleClasses.CARD_HEIGHT.A;
        } else if (height <= MaxSize.B) {
            height_class = StyleClasses.CARD_HEIGHT.B;
        } else if (height <= MaxSize.C) {
            height_class = StyleClasses.CARD_HEIGHT.C;
        } else if (height <= MaxSize.D) {
            height_class = StyleClasses.CARD_HEIGHT.D;
        } else {
            height_class = StyleClasses.CARD_HEIGHT.E;
        }

        let context = this.get_style_context();
        if (typeof width_class !== undefined && !context.has_class(width_class)) {
            Object.keys(StyleClasses.CARD_WIDTH).forEach(name => context.remove_class(StyleClasses.CARD_WIDTH[name]));
            context.add_class(width_class);
        }

        if (typeof width_class !== undefined && !context.has_class(height_class)) {
            Object.keys(StyleClasses.CARD_HEIGHT).forEach(name => context.remove_class(StyleClasses.CARD_HEIGHT[name]));
            context.add_class(height_class);
        }
    },

    /**
     * Method: fade_in
     * Use instead of *Gtk.Widget.show()* or *Gtk.Widget.show_all()*.
     */
    fade_in: function () {
        if (Utils.low_performance_mode()) {
            this.show();
            return;
        }

        let context = this.get_style_context();
        context.add_class(StyleClasses.INVISIBLE);
        // FIXME: for some reason even if initial opacity = 0 in css, the
        // opacity will start at 1. Triggering a 'notify' on opacity seems to
        // get the actual initial opacity value in css to be respected
        this.opacity = 0;
        this.opacity = 1;
        // Cards not sensitive till fully faded in
        this.sensitive = false;
        Mainloop.timeout_add(this.FADE_IN_TIME_MS, () => {
            this.sensitive = true;
            context.remove_class(StyleClasses.INVISIBLE);
            context.remove_class(StyleClasses.FADE_IN);
            return GLib.SOURCE_REMOVE;
        });
        this.show();
        context.add_class(StyleClasses.FADE_IN);
    },
});
<commit_msg>Add get_filtered_tags helper to card interface<commit_after>// Copyright 2015 Endless Mobile, Inc.

/* exported Card, MinSize, MaxSize */

const EosKnowledgePrivate = imports.gi.EosKnowledgePrivate;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Mainloop = imports.mainloop;

const ArticleObjectModel = imports.search.articleObjectModel;
const ContentObjectModel = imports.search.contentObjectModel;
const Engine = imports.search.engine;
const ImageCoverFrame = imports.app.widgets.imageCoverFrame;
const Module = imports.app.interfaces.module;
const SetObjectModel = imports.search.setObjectModel;
const SpaceContainer = imports.app.widgets.spaceContainer;
const StyleClasses = imports.app.styleClasses;
const Utils = imports.app.utils;

/**
 * Constants: MinSize
 * Minimum size of cards for each size class
 *
 * Members:
 *  A - Smallest possible size for cards
 *  B - Size B
 *  C - Size C
 *  D - Size D
 *  E - Size E
 *  F - Currently width-only
 *  G - Currently width-only
 *  H - Currently width-only
 */
const MinSize = {
    A: 100,
    B: 200,
    C: 300,
    D: 400,
    E: 600,
    F: 800,
    G: 1000,
    H: 1200,
};

/**
 * Constants: MaxSize
 * Maximum size of a card for each size class
 *
 * Members:
 *  A - Size A
 *  B - Size B
 *  C - Size C
 *  D - Size D
 *  E - This is the largest height class that a card will currently get.
 *      Even cards that are taller than this will still get class E.
 *  F - Currently width-only
 *  G - Currently width-only
 *  H - Currently width-only.
 *      Even cards that are wider than this will still get class H.
 */
const MaxSize = {
    A: MinSize.B - 1,
    B: MinSize.C - 1,
    C: MinSize.D - 1,
    D: MinSize.E - 1,
    E: MinSize.F - 1,
    F: MinSize.G - 1,
    G: MinSize.H - 1,
    H: 1399,
};

/**
 * Interface: Card
 * Interface for card modules
 *
 * Requires:
 *   Gtk.Widget
 */
const Card = new Lang.Interface({
    Name: 'Card',
    GTypeName: 'EknCard',
    Requires: [ Gtk.Widget, Module.Module ],

    Properties: {
        /**
         * Property: model
         * Record backing this card
         *
         * Every card is backed by a record in the database.
         * A card's record is represented by a <ContentObjectModel> or one of
         * its subclasses.
         *
         * Type:
         *   <ContentObjectModel>
         *
         * Flags:
         *   Construct only
         */
        'model': GObject.ParamSpec.object('model', 'Model',
            'Card model with which to create this card',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            ContentObjectModel.ContentObjectModel),
        /**
         * Property: title-capitalization
         * Manner in which the card's title is formatted
         *
         * This property is a temporary stand-in for achieving this via the CSS
         * *text-transform* property.
         */
        'title-capitalization': GObject.ParamSpec.enum('title-capitalization',
            'Title capitalization', 'Manner in which the title is formatted',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            EosKnowledgePrivate.TextTransformType,
            EosKnowledgePrivate.TextTransform.NONE),
        /**
         * Property: highlight-string
         * A substring within a card's title or synopsis to get highlighted
         *
         * Sometimes we want to highlight a particular substring with the
         * text of a card. This property specifies which substring to highlight.
         */
        'highlight-string': GObject.ParamSpec.string('highlight-string',
            'Highlight string', 'Substring to be highlighted on card',
            GObject.ParamFlags.READWRITE,
            ''),
        /**
         * Property: text-halign
         * Horizontal alignment of text when card is in _not_ in horizontal mode.
         *
         * Default value:
         *   **Gtk.Align.CENTER**
         */
        'text-halign': GObject.ParamSpec.enum('text-halign',
            'Title halign', 'Horizontal alignment of title text',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            Gtk.Align.$gtype, Gtk.Align.CENTER),

    },

    set css (v) {
        if (this._css === v)
            return;
        this._css = v;
        if (this._css) {
            Utils.apply_css_to_widget(this._css, this);
        }
        this.notify('css');
    },

    get css () {
        if (this._css)
            return this._css;
        return '';
    },

    get highlight_string () {
        if (this._highlight_string)
            return this._highlight_string;
        return '';
    },

    set highlight_string (value) {
        if (this._highlight_string === value)
            return;
        this._highlight_string = value;
        this.update_highlight_string();
        this.notify('highlight-string');
    },

    // Overridable in tests; otherwise keep synchronized with the CSS
    FADE_IN_TIME_MS: 1000,
    NUM_STYLE_VARIANTS: 3,

    /**
     * Method: set_label_or_hide
     *
     * Sets a label contents and hides if contents is empty.
     */
    set_label_or_hide: function (label, text) {
        label.label = GLib.markup_escape_text(text, -1);
        label.visible = !!text;
    },

    /**
     * Method: set_context_label_from_model
     *
     * Sets the text on a context label. The context is the list of tags
     * associated with this article. Tags are also incidentally the same
     * as set titles.
     *
     * According to designs (and based on what fits on screen), we only want
     * to show a maximum of two set titles in the context label.
     * Moreover, we want to exclude the 'Ekn'-prefixed tags which won't mean
     * anything to the user.
     */
    set_context_label_from_model: function (container) {
        this._space_container = new SpaceContainer.SpaceContainer({
            orientation: Gtk.Orientation.HORIZONTAL,
        });

        // Sort the context tags from shortest to longest in order to
        // maximise chances that we can fit two of them on the card.
        let tags = this.get_filtered_tags();
        if (tags.length > 0) {
            let first_tag = new Gtk.Label({
                lines: 1,
                label: tags[0].toLocaleUpperCase(),
            });
            this._space_container.add(first_tag);

            if (tags.length > 1) {
                let second_tag = new Gtk.Label({
                    lines: 1,
                    label: ' | ' + tags[1].toLocaleUpperCase(),
                });
                this._space_container.add(second_tag);
            }
        }

        this._space_container.get_style_context().add_class('card-context');
        this._space_container.show_all();
        container.add(this._space_container);
    },

    /**
     * Method: get_filtered_tags
     *
     * Gets the list of tags that do not start with the Ekn prefix.
     * The list is sorted by the length of the tags in ascending order.
     */
    get_filtered_tags: function () {
        return this.model.tags.filter((tag) => !tag.startsWith('Ekn'))
                              .sort((a, b) => a.length - b.length);
    },

    /**
     * Method: add_contextual_css_class
     *
     * Adds a css class based on the type of model this is.
     */
    add_contextual_css_class: function () {
        if (this.model instanceof SetObjectModel.SetObjectModel) {
            this.get_style_context().add_class(StyleClasses.SET);
        } else if (this.model instanceof ArticleObjectModel.ArticleObjectModel) {
            this.get_style_context().add_class(StyleClasses.ARTICLE);
        }
    },

    /**
     * Method: set_thumbnail_frame_from_model
     *
     * Sets up a frame to show the model's thumbnail uri.
     */
    set_thumbnail_frame_from_model: function (frame) {
        frame.visible = false;
        if (!this.model.thumbnail_uri)
            return;

        let scheme = Gio.File.new_for_uri(this.model.thumbnail_uri).get_uri_scheme();
        let coveredFrame = new ImageCoverFrame.ImageCoverFrame();
        frame.add(coveredFrame);
        let stream;
        // FIXME: to actually support ekn uris here, we'd need a gvfs
        // extension or something like that
        if (scheme === 'ekn') {
            Engine.get_default().get_object_by_id(this.model.thumbnail_uri, null, (engine, task) => {
                let media_object;
                try {
                    media_object = engine.get_object_by_id_finish(task);
                } catch (error) {
                    logError(error);
                    return;
                }
                coveredFrame.set_content(media_object.get_content_stream());
            });
        } else {
            coveredFrame.set_content(Gio.File.new_for_uri(this.model.thumbnail_uri).read(null));
        }
        frame.visible = true;
    },

    /**
     * Method: set_label_with_highlight
     *
     * Sets up a label that should highlight any substrings within it that
     * match the card's highlight-string property.
     * Does the same as <set_label_or_hide()> but highlights the highlight
     * string in the label as well.
     */
    set_label_with_highlight: function (label, str) {
        // parenthesize the targeted string so we can reference it later on in
        // the replace step using '$1'. This is so we can preserve case
        // sensitivity when doing the replacement.
        let regex = new RegExp('(' + this.highlight_string + ')', 'gi');
        let title = GLib.markup_escape_text(str, -1);
        if (this.highlight_string) {
            let context = label.get_style_context();
            context.save();
            context.add_class(StyleClasses.HIGHLIGHTED);
            let span = Utils.style_context_to_markup_span(label.get_style_context(), Gtk.StateFlags.NORMAL);
            title = title.replace(regex, span + '$1</span>');
            context.restore();
        }
        label.label = title;
        label.visible = !!title;
    },

    /**
     * Method: update_highlight_string
     * Implement for highlighting card titles and synopses
     *
     * Implement this method in order to allow highlighting search terms on
     * card titles and synopses.
     * If not overridden, then setting <highlight-string> will have no effect.
     *
     * Note that this may be called early in the construct phase of your object,
     * so make sure that you check whether any widgets you access exist.
     * Also, make sure to call it after chaining up in your constructor.
     *
     * There is no need to chain up when overriding this method, since it has no
     * effect by default.
     */
    update_highlight_string: function () {
        // no-op unless overridden, but not mandatory to override
    },

    /**
     * Method: set_title_label_from_model
     *
     * Sets up a label to show the model's title.
     */
    set_title_label_from_model: function (label) {
        this.set_label_or_hide(label,
            Utils.format_capitals(this.model.title, this.title_capitalization));
    },

    /**
     * Method: set_title_label_with_highlight
     *
     * Sets up a label to show the model's title, and also highlights the
     * highlight string in the title.
     */
    set_title_label_with_highlight: function (label) {
        this.set_label_with_highlight(label,
            Utils.format_capitals(this.model.title, this.title_capitalization));
    },

    /**
     * Method: set_author_label_from_model
     *
     * Sets up a label to show the model's authors.
     */
    set_author_label_from_model: function (label) {
        this.set_label_or_hide(label, Utils.format_authors(this.model.authors));
    },

    /**
     * Method: set_style_variant_from_model
     * Uses the article number to set a style variant CSS class
     *
     * Adds one of the CSS classes 'variant0', 'variant1', or 'variant2' to the
     * card, depending on the <ArticleObjectModel.article_number> property if it
     * is present.
     */
    set_style_variant_from_model: function () {
        if (this.model.article_number !== undefined) {
            let style = this.model.article_number % this.NUM_STYLE_VARIANTS;
            this.get_style_context().add_class('variant' + style);
        }
    },

    /**
     * Method: update_card_sizing_classes
     * Assigns the appropriate CSS classes based on the height and width
     *
     * This method takes the height and width of the card widget and assigns the
     * appropriate CSS classes, according to our design constraints.
     */
    update_card_sizing_classes: function (height, width) {
        let width_class, height_class;

        if (width <= MaxSize.A) {
            width_class = StyleClasses.CARD_WIDTH.A;
        } else if (width <= MaxSize.B) {
            width_class = StyleClasses.CARD_WIDTH.B;
        } else if (width <= MaxSize.C) {
            width_class = StyleClasses.CARD_WIDTH.C;
        } else if (width <= MaxSize.D) {
            width_class = StyleClasses.CARD_WIDTH.D;
        } else if (width <= MaxSize.E) {
            width_class = StyleClasses.CARD_WIDTH.E;
        } else if (width <= MaxSize.F) {
            width_class = StyleClasses.CARD_WIDTH.F;
        } else if (width <= MaxSize.G) {
            width_class = StyleClasses.CARD_WIDTH.G;
        } else {
            width_class = StyleClasses.CARD_WIDTH.H;
        }

        if (height <= MaxSize.A) {
            height_class = StyleClasses.CARD_HEIGHT.A;
        } else if (height <= MaxSize.B) {
            height_class = StyleClasses.CARD_HEIGHT.B;
        } else if (height <= MaxSize.C) {
            height_class = StyleClasses.CARD_HEIGHT.C;
        } else if (height <= MaxSize.D) {
            height_class = StyleClasses.CARD_HEIGHT.D;
        } else {
            height_class = StyleClasses.CARD_HEIGHT.E;
        }

        let context = this.get_style_context();
        if (typeof width_class !== undefined && !context.has_class(width_class)) {
            Object.keys(StyleClasses.CARD_WIDTH).forEach(name => context.remove_class(StyleClasses.CARD_WIDTH[name]));
            context.add_class(width_class);
        }

        if (typeof width_class !== undefined && !context.has_class(height_class)) {
            Object.keys(StyleClasses.CARD_HEIGHT).forEach(name => context.remove_class(StyleClasses.CARD_HEIGHT[name]));
            context.add_class(height_class);
        }
    },

    /**
     * Method: fade_in
     * Use instead of *Gtk.Widget.show()* or *Gtk.Widget.show_all()*.
     */
    fade_in: function () {
        if (Utils.low_performance_mode()) {
            this.show();
            return;
        }

        let context = this.get_style_context();
        context.add_class(StyleClasses.INVISIBLE);
        // FIXME: for some reason even if initial opacity = 0 in css, the
        // opacity will start at 1. Triggering a 'notify' on opacity seems to
        // get the actual initial opacity value in css to be respected
        this.opacity = 0;
        this.opacity = 1;
        // Cards not sensitive till fully faded in
        this.sensitive = false;
        Mainloop.timeout_add(this.FADE_IN_TIME_MS, () => {
            this.sensitive = true;
            context.remove_class(StyleClasses.INVISIBLE);
            context.remove_class(StyleClasses.FADE_IN);
            return GLib.SOURCE_REMOVE;
        });
        this.show();
        context.add_class(StyleClasses.FADE_IN);
    },
});
<|endoftext|>


<commit_before>var _ = require('_'),
    _utils = require('../utils'),
    _regex = require('../regex'),
    _array = require('./array'),

    Query = require('./query/Query'),
    Is = require('./query/Is');

var _find = function(selector, isNew) {
    var query = Query(selector);
    return _array.unique(query.exec(this, isNew));
};

var _findWithin = function(selector, context) {
    var query = Query(selector);
    return _array.unique(query.exec(context));
};

var _filter = function(arr, qualifier) {
    // Early return, no qualifier. Everything matches
    if (!qualifier) { return arr; }

    // Function
    if (_.isFunction(qualifier)) {
        return _.filter(arr, qualifier);
    }

    // Element
    if (qualifier.nodeType) {
        return _.filter(arr, function(elem) {
            return (elem === qualifier);
        });
    }

    // Selector
    if (_.isString(qualifier)) {
        return _.filter(arr, function(elem) {
            return elem.nodeType === 1 && _isMatch(elem, qualifier);
        });
    }

    // Array qualifier
    return _.filter(arr, function(elem) {
        return arr.indexOf(qualifier) > -1;
    });
};

module.exports = {
    find: _find,
    is: function() {
        // TODO: Implement?
        debugger;
    },
    filter: _filter,

    fn: {
        has: function(target) {
            // TODO: Has
            /*var i,
                targets = jQuery( target, this ),
                len = targets.length;

            return this.filter(function() {
                for ( i = 0; i < len; i++ ) {
                    if ( jQuery.contains( this, targets[i] ) ) {
                        return true;
                    }
                }
            });*/
        },

        is: Overload()
            .args(String).use(function(selector) {
                if (selector === '') { return false; }

                var is = Is(selector);
                return is.exec(this);
            })
            .args(Function).use(function(iterator) {

                return _.any(this, function(elem, idx) {
                    if (iterator.call(elem, idx)) {
                        return true;
                    }
                });

            })
            .args(O.D).use(function(d) {

                return _.any(this, function(elem) {
                    if (d.indexOf(elem) !== -1) { return true; }
                });

            })
            .args(Element).use(function(context) {

                return _.any(this, function(elem) {
                    return (elem === context);
                });

            })
            .args(O.any(null, undefined, Number, Object)).use(function() {
                return false;
            })
            .expose(),

        not: function() {},

        find: Overload()
            .args(String).use(function(selector) {

                return _utils.merge(D(), _findWithin(selector, this));

            })
            .expose(),

        filter: Overload()
            .args(String).use(function(selector) {
                if (selector === '') { return d; }

                var is = Is(selector);
                return D(
                    _.filter(this, function(elem) {
                        is.match(elem);
                    })
                );

            })
            .args(Function).use(function(checker) {
                return D(
                    _.filter(this, function(elem) {
                        return checker.call(this[idx]);
                    })
                );
            })
            .expose()
    }
};
<commit_msg>Filter development<commit_after>var _ = require('_'),
    _utils = require('../utils'),
    _regex = require('../regex'),
    _array = require('./array'),

    Query = require('./query/Query'),
    Is = require('./query/Is');

var _find = function(selector, isNew) {
    var query = Query(selector);
    return _array.unique(query.exec(this, isNew));
};

var _findWithin = function(selector, context) {
    var query = Query(selector);
    return _array.unique(query.exec(context));
};

var _filter = function(arr, qualifier) {
    // Early return, no qualifier. Everything matches
    if (!qualifier) { return arr; }

    // Function
    if (_.isFunction(qualifier)) {
        return _.filter(arr, qualifier);
    }

    // Element
    if (qualifier.nodeType) {
        return _.filter(arr, function(elem) {
            return (elem === qualifier);
        });
    }

    // Selector
    if (_.isString(qualifier)) {
        return _.filter(arr, function(elem) {
            return elem.nodeType === 1 && _isMatch(elem, qualifier);
        });
    }

    // Array qualifier
    return _.filter(arr, function(elem) {
        return arr.indexOf(qualifier) > -1;
    });
};

module.exports = {
    find: _find,
    is: function() {
        // TODO: Implement?
        debugger;
    },
    filter: _filter,

    fn: {
        has: function(target) {
            // TODO: Has
            /*var i,
                targets = jQuery( target, this ),
                len = targets.length;

            return this.filter(function() {
                for ( i = 0; i < len; i++ ) {
                    if ( jQuery.contains( this, targets[i] ) ) {
                        return true;
                    }
                }
            });*/
        },

        is: Overload()
            .args(String).use(function(selector) {
                if (selector === '') { return false; }

                var is = Is(selector);
                return is.exec(this);
            })
            .args(Function).use(function(iterator) {

                return _.any(this, function(elem, idx) {
                    if (iterator.call(elem, idx)) {
                        return true;
                    }
                });

            })
            .args(O.D).use(function(d) {

                return _.any(this, function(elem) {
                    if (d.indexOf(elem) !== -1) { return true; }
                });

            })
            .args(Element).use(function(context) {

                return _.any(this, function(elem) {
                    return (elem === context);
                });

            })
            .args(O.any(null, undefined, Number, Object)).use(function() {
                return false;
            })
            .expose(),

        not: function() {},

        find: Overload()
            .args(String).use(function(selector) {

                return _utils.merge(D(), _findWithin(selector, this));

            })
            .expose(),

        filter: Overload()
            .args(String).use(function(selector) {
                if (selector === '') { return d; }

                var is = Is(selector);
                return D(
                    _.filter(this, function(elem) {
                        is.match(elem);
                    })
                );

            })
            .args(Array).use(function(arr) {

                return D(
                    _.filter(this, function(elem) {
                        if (arr.indexOf(elem) !== -1) { return true; }
                    })
                );

            })
            .args(Function).use(function(checker) {

                return D(
                    _.filter(this, function(elem) {
                        return checker.call(this[idx]);
                    })
                );

            })
            .expose()
    }
};
<|endoftext|>


<commit_before>var Backbone            = require('backbone'),
    $                   = require('jquery'),
    _                   = require('underscore'),
    Service             = require('cloud/models/service'),
    Hours               = require('cloud/models/hours'),
    gmaps               = require('google-maps'),
    editServiceTemplate = require('templates/_edit_service');

function modelSaveFailCallback(args) {
  this.$("#facilitySaveError").show().focus();
  console.log("failed.");
  console.log(args);
}

function modelSaveSuccessCallback(args) {
  this.$("#facilitySaved").show().focus();
  this.$("#facilitySaved").delay(5000).fadeOut();

  console.log("saved.");
  console.log(args);
}

var days = [
  {key: "MON", name: "Monday"},
  {key: "TUE", name: "Tuesday"},
  {key: "WED", name: "Wednesday"},
  {key: "THU", name: "Thursday"},
  {key: "FRI", name: "Friday"},
  {key: "SAT", name: "Saturday"},
  {key: "SUN", name: "Sunday"}
];

function saveFacility(model, services, successCallback, failCallback) {
  model.save().then(function(foo) {
    _.each(model.get("services"), function(service) {
      service.destroy();
    });

    var serviceObjects = _.map(services, function(serviceData) {
      var service = new Service();
      service.set("facility", model);

      delete serviceData.id;
      service.set(serviceData);

      return service;
    });

    Service.saveAll(serviceObjects, function(services, error) {
      if (services) {
        model.set("services", _.map(services, function(s) {
          var sObj = new Service();
          sObj.id = s.id;
          return sObj;
        }));

        model.save()
          .then(function(fac) {
            successCallback(fac);
          }, function(error) {
            failCallback(error);
          });

      } else {
        failCallback(error);
      }
    });
  },
  failCallback);
}

var EditView = Backbone.View.extend({
  template: require('templates/edit'),

  events: {
    'click #add_category':    'addCategory',
    'click #remove_category': 'removeCategory'
  },

  addCategory: function(argument) {
    var context = { category: this.$('#categories').val() };
    this.$('#services').prepend(editServiceTemplate(context));
    return false;
  },

  removeCategory: function(event) {
    var parent = this.$(event.target).closest('.service-edit-row');
    parent.remove();
    return false;
  },

  parseHourElement: function(hours, el) {
    var closedCheckbox = $(el).next("input.closed");

    if ( closedCheckbox.prop("checked") || el.value === "" || el.value === "CLOSED" ) {
      return;
    }

    try {
      hours.addDay(el.name, el.value);
    } catch(err) {
      $(el).closest("tr").after($("

").html('' + err.message + '	'));
    }
  },

  parseHours: function(container) {
    var serviceHours = new Hours();

    _($(container).find('input.day')).each(function(el) {
      this.parseHourElement(serviceHours, el);
    }.bind(this));

    return serviceHours;
  },

  setupForm: function() {
    var g = this.model.get('gender');
    var el;

    el = this.$('#gender_' + (g ? g : ''));
    el.prop('checked', true);

    this.$("#age_everyone").click(function() {
      this.$('[name="age"]').prop('checked', $(this).prop('checked'));
    }.bind(this));

    if ( this.model.get("age") ) {
      _(this.model.get("age")).each(function(age) {
        this.$("#age_" + age.toUpperCase()).prop('checked', true);
      }.bind(this));
    } else {
      this.$("#age_everyone").click();
    }

    this.$('#submit').click(function() {
      this.saveForm();
    }.bind(this));

    this.$("input.day").first().attr("placeholder", "example: 9am-3pm, 6pm-8pm");
    this.$("input.day").blur(function(ev) {
      this.parseHourElement(new Hours(), ev.target);
      return true;
    }.bind(this));

    this.$("input.closed").change(function(ev) {
      var el = ev.target;
      var textBox = $(el).closest("tr").find("input.day[name='" + el.name + "']");
      $(textBox).prop("disabled", $(el).prop("checked"));
      if ( $(el).prop("checked") ) {
        textBox.val("CLOSED");
      } else {
        textBox.val("");
      }
    });

    this.$("input.day").keyup(function(ev) {
      $(ev.target).closest("tr").next("tr.dayError").remove();
    });
  },

  saveForm: function() {
    // var self = this;
    var servicePromises = [];
    var formValues = this.$('#facilityForm').serializeObject();
    if ( formValues.gender === "" ) {
      formValues.gender = null;
    }

    if ( this.$("#age_everyone").prop('checked') ) {
      formValues.age = null;
    } else {
      var ages = _.map(this.$("[name=age]input:checked"), function(cb) {
        return $(cb).attr('value');
      });

      ages = _(ages).compact();
      formValues.age = ages;
    }

    var services = _.clone(formValues.services);

    _.each(services, function(service, i) {
      var hours = this.parseHours($('.hours')[i]);
      if ( !hours.isEmpty() )  {
        service.hours = hours.serialize();
      }
    }.bind(this));

    delete formValues.services;
    this.model.set(formValues);

    var save = _.bind(saveFacility, this);
    save(this.model, services, _.bind(modelSaveSuccessCallback, this), _.bind(modelSaveFailCallback, this));

  },

  render: function() {
    var hours = [];

    for(var idx = 0; idx <= 23; idx++) {
      if (idx < 10) { idx = '0' + idx; }
      hours.push(idx + ':00');
      hours.push(idx + ':30');
    }

    var templateData = this.model.presentJSON();
    templateData.services.forEach(function(service) {
      service.allHours = hours;
      service.days = days;
    });

    $(this.el).html(this.template({facility: templateData}));
    this.setupForm();
    return this;
  }
});

module.exports = EditView;
<commit_msg>get rid of MON TUE WED ETC keys in facility<commit_after>var Backbone            = require('backbone'),
    $                   = require('jquery'),
    _                   = require('underscore'),
    Service             = require('cloud/models/service'),
    Hours               = require('cloud/models/hours'),
    gmaps               = require('google-maps'),
    editServiceTemplate = require('templates/_edit_service');

function modelSaveFailCallback(args) {
  this.$("#facilitySaveError").show().focus();
  console.log("failed.");
  console.log(args);
}

function modelSaveSuccessCallback(args) {
  this.$("#facilitySaved").show().focus();
  this.$("#facilitySaved").delay(5000).fadeOut();

  console.log("saved.");
  console.log(args);
}

var days = [
  {key: "MON", name: "Monday"},
  {key: "TUE", name: "Tuesday"},
  {key: "WED", name: "Wednesday"},
  {key: "THU", name: "Thursday"},
  {key: "FRI", name: "Friday"},
  {key: "SAT", name: "Saturday"},
  {key: "SUN", name: "Sunday"}
];

function saveFacility(model, services, successCallback, failCallback) {
  model.save().then(function(foo) {
    _.each(model.get("services"), function(service) {
      service.destroy();
    });

    var serviceObjects = _.map(services, function(serviceData) {
      var service = new Service();
      service.set("facility", model);

      delete serviceData.id;
      service.set(serviceData);

      return service;
    });

    Service.saveAll(serviceObjects, function(services, error) {
      if (services) {
        model.set("services", _.map(services, function(s) {
          var sObj = new Service();
          sObj.id = s.id;
          return sObj;
        }));

        model.save()
          .then(function(fac) {
            successCallback(fac);
          }, function(error) {
            failCallback(error);
          });

      } else {
        failCallback(error);
      }
    });
  },
  failCallback);
}

var EditView = Backbone.View.extend({
  template: require('templates/edit'),

  events: {
    'click #add_category':    'addCategory',
    'click #remove_category': 'removeCategory'
  },

  addCategory: function(argument) {
    var context = { category: this.$('#categories').val() };
    this.$('#services').prepend(editServiceTemplate(context));
    return false;
  },

  removeCategory: function(event) {
    var parent = this.$(event.target).closest('.service-edit-row');
    parent.remove();
    return false;
  },

  parseHourElement: function(hours, el) {
    var closedCheckbox = $(el).next("input.closed");

    if ( closedCheckbox.prop("checked") || el.value === "" || el.value === "CLOSED" ) {
      return;
    }

    try {
      hours.addDay(el.name, el.value);
    } catch(err) {
      $(el).closest("tr").after($("

").html('' + err.message + '	'));
    }
  },

  parseHours: function(container) {
    var serviceHours = new Hours();

    _($(container).find('input.day')).each(function(el) {
      this.parseHourElement(serviceHours, el);
    }.bind(this));

    return serviceHours;
  },

  setupForm: function() {
    var g = this.model.get('gender');
    var el;

    el = this.$('#gender_' + (g ? g : ''));
    el.prop('checked', true);

    this.$("#age_everyone").click(function() {
      this.$('[name="age"]').prop('checked', $(this).prop('checked'));
    }.bind(this));

    if ( this.model.get("age") ) {
      _(this.model.get("age")).each(function(age) {
        this.$("#age_" + age.toUpperCase()).prop('checked', true);
      }.bind(this));
    } else {
      this.$("#age_everyone").click();
    }

    this.$('#submit').click(function() {
      this.saveForm();
    }.bind(this));

    this.$("input.day").first().attr("placeholder", "example: 9am-3pm, 6pm-8pm");
    this.$("input.day").blur(function(ev) {
      this.parseHourElement(new Hours(), ev.target);
      return true;
    }.bind(this));

    this.$("input.closed").change(function(ev) {
      var el = ev.target;
      var textBox = $(el).closest("tr").find("input.day[name='" + el.name + "']");
      $(textBox).prop("disabled", $(el).prop("checked"));
      if ( $(el).prop("checked") ) {
        textBox.val("CLOSED");
      } else {
        textBox.val("");
      }
    });

    this.$("input.day").keyup(function(ev) {
      $(ev.target).closest("tr").next("tr.dayError").remove();
    });
  },

  saveForm: function() {
    // var self = this;
    var servicePromises = [];
    var formValues = this.$('#facilityForm').serializeObject();

    days.forEach(function(d) { delete formValues[d.key]; });

    if ( formValues.gender === "" ) {
      formValues.gender = null;
    }

    if ( this.$("#age_everyone").prop('checked') ) {
      formValues.age = null;
    } else {
      var ages = _.map(this.$("[name=age]input:checked"), function(cb) {
        return $(cb).attr('value');
      });

      ages = _(ages).compact();
      formValues.age = ages;
    }

    var services = _.clone(formValues.services);

    _.each(services, function(service, i) {
      var hours = this.parseHours($('.hours')[i]);
      if ( !hours.isEmpty() )  {
        service.openHours = hours.serialize();
      }
    }.bind(this));

    delete formValues.services;
    this.model.set(formValues);

    var save = _.bind(saveFacility, this);
    save(this.model, services, _.bind(modelSaveSuccessCallback, this), _.bind(modelSaveFailCallback, this));

  },

  render: function() {
    var templateData = this.model.presentJSON();
    templateData.services.forEach(function(service) {
      service.days = days;
    });

    $(this.el).html(this.template({facility: templateData}));
    this.setupForm();
    return this;
  }
});

module.exports = EditView;
<|endoftext|>


<commit_before>Z.renderer.tilelayer.Canvas = Z.renderer.Canvas.extend({

    propertyOfPointOnTile   : '--maptalks-tile-point',
    propertyOfTileId        : '--maptalks-tile-id',
    propertyOfTileZoom      : '--maptalks-tile-zoom',

    initialize:function(layer) {
        this._layer = layer;
        this._mapRender = layer.getMap()._getRenderer();
        this._tileCache = new Z.TileLayer.TileCache();
        this._registerEvents();
        this._tileQueue = {};
    },

    remove:function() {
        var map = this.getMap();
        map.off('_moveend _resize _zoomend',this._onMapEvent,this);
        if (this._onMapMoving) {
            map.off('_moving',this._onMapMoving,this);
        }
        this._requestMapToRender();
    },

    getMap: function() {
        return this._layer.getMap();
    },

    show:function() {
        this.render();
    },

    hide:function() {
        this._requestMapToRender();
    },

    setZIndex:function(zIndex) {
        this._requestMapToRender();
    },

    clear:function() {
        this._clearCanvas();
        this._requestMapToRender();
    },

    clearExecutors:function() {
        clearTimeout(this._loadQueueTimeout);
    },

    initContainer:function() {

    },

    render:function() {
        var layer = this._layer;
        var tileGrid = layer._getTiles();
        if (!tileGrid) {
            return;
        }
        this._rending = true;
        if (!this._canvas) {
            this._createCanvas();
        }
        if (!this._tileRended) {
            this._tileRended = {};
        }
        this._tileZoom = this.getMap().getZoom();
        var tileRended = this._tileRended;
        this._tileRended = {};

        var tiles = tileGrid['tiles'],
            tileCache = this._tileCache,
            tileSize = layer.getTileSize();

        this._canvasFullExtent =  this.getMap()._getViewExtent();
        var viewExtent = this._canvasFullExtent;
        if (this._clipped) {
            this._context.restore();
            this._clipped=false;
        }
        this._clearCanvas();
        var mask = layer.getMask();
        if (mask) {
            var maskPxExtent = mask._getPainter().getPixelExtent();
            if (!maskPxExtent.intersects(viewExtent)) {
                return;
            }
            this._context.save();
            mask._getPainter().paint();
            this._context.clip();
            this._clipped = true;
            // viewExtent = viewExtent.intersection(maskPxExtent);
        }
        //遍历瓦片
        this._tileToLoadCounter = 0;

        for (var i = tiles.length - 1; i >= 0; i--) {
            var tile = tiles[i];
            var tileId = tiles[i]['id'];
            //如果缓存中已存有瓦片, 则从不再请求而从缓存中读取.
            var cached = tileRended[tileId] || tileCache.get(tileId);
            if (!viewExtent.intersects(new Z.PointExtent(tile['viewPoint'],
                                tile['viewPoint'].add(new Z.Point(tileSize['width'], tileSize['height']))))) {
                continue;
            }
            if (cached) {
                    //画瓦片
                this._drawTile(tile['viewPoint'], cached);
                this._tileRended[tileId] = cached;
            } else {

                this._tileToLoadCounter++;
                this._tileQueue[tileId+"@"+tile['viewPoint'].toString()] = tile;
            }
        }

        this._rending = false;
        if (this._tileToLoadCounter === 0){
            this._fireLoadedEvent();
            this._requestMapToRender();
        } else {
            this._totalTileToLoad = this._tileToLoadCounter;
            this._scheduleLoadTileQueue();
        }
    },

    getCanvasImage:function() {
        if (!this._canvasFullExtent || this._tileZoom !== this.getMap().getZoom()) {
            return null;
        }
         var gradualOpacity = null;
        if (this._gradualLoading && this._totalTileToLoad && this._layer.options['gradualLoading']) {
            gradualOpacity = ((this._totalTileToLoad - this._tileToLoadCounter) / this._totalTileToLoad)*1.5 ;
            if (gradualOpacity > 1) {
                gradualOpacity = 1;
            }
        }
        var size = this._canvasFullExtent.getSize();
        var point = this._canvasFullExtent.getMin();
        return {'image':this._canvas,'layer':this._layer,'point':this.getMap().viewPointToContainerPoint(point),'size':size,'opacity':gradualOpacity};
    },

    getPaintContext:function() {
        return [this._context];
    },

    _scheduleLoadTileQueue:function() {

        if (this._loadQueueTimeout) {
            clearTimeout(this._loadQueueTimeout);
        }

        var me = this;
        this._loadQueueTimeout = setTimeout(function(){me._loadTileQueue();},10);
    },

    _loadTileQueue:function() {
        var me = this;

        function onTileLoad() {
            me._tileCache.add(this[me.propertyOfTileId], this);
            me._tileRended[me.propertyOfTileId] = this;
            me._drawTileAndRequest(this);
        }
        function onTileError() {
            me._clearTileRectAndRequest(this);
        }
        var crossOrigin = this._layer.options['crossOrigin'];
        var tileSize = this._layer.getTileSize();
        for (var p in this._tileQueue) {
            if (this._tileQueue.hasOwnProperty(p)) {
                var tileId = p.split('@')[0];
                var tile = this._tileQueue[p];
                delete this._tileQueue[p];
                if (!this._tileCache[tileId]) {
                    var tileImage = new Image();
                    tileImage.width = tileSize['width'];
                    tileImage.height = tileSize['height'];
                    tileImage[this.propertyOfTileId]=tileId;
                    tileImage[this.propertyOfPointOnTile] = tile['viewPoint'];
                    tileImage[this.propertyOfTileZoom] = tile['zoom'];
                    tileImage.onload = onTileLoad;
                    tileImage.onabort = onTileError;
                    tileImage.onerror = onTileError;
                    if (crossOrigin) {
                        tileImage.crossOrigin = crossOrigin;
                    }
                    Z.Util.loadImage(tileImage, tile['url']);
                } else {
                    this._drawTileAndRequest(this._tileCache[tileId]);
                }

            }
        }

    },

    _drawTile:function(point, tileImage) {
        if (!point) {
            return;
        }
        var tileSize = this._layer.getTileSize();
        var opacity = this._layer.config()['opacity'];
        var isFaded = !Z.Util.isNil(opacity) && opacity < 1;
        var alpha;
        if (isFaded) {
            alpha = this._context.globalAlpha;
            if (opacity <= 0) {
                return;
            }
            this._context.globalAlpha = opacity;
        }
        Z.Canvas.image(this._context, point.substract(this._canvasFullExtent.getMin()), tileImage, tileSize['width'],tileSize['height']);
        if (isFaded) {
            this._context.globalAlpha = alpha;
        }
    },

    /**
     * 绘制瓦片, 并请求地图重绘
     * @param  {Point} point        瓦片左上角坐标
     * @param  {Image} tileImage 瓦片图片对象
     */
    _drawTileAndRequest:function(tileImage) {
        //sometimes, layer may be removed from map here.
        if (!this.getMap()) {
            return;
        }
        var zoom = this.getMap().getZoom();
        if (zoom !== tileImage[this.propertyOfTileZoom]) {
            return;
        }
        this._tileToLoadCounter--;
        var point = tileImage[this.propertyOfPointOnTile];
        this._drawTile(point, tileImage);

        if (!Z.runningInNode) {
            var tileSize = this._layer.getTileSize();
            var viewExtent = this.getMap()._getViewExtent();
            if (viewExtent.intersects(new Z.PointExtent(point, point.add(new Z.Point(tileSize['width'], tileSize['height']))))) {
                this._requestMapToRender();
            }
        }
        if (this._tileToLoadCounter === 0) {
            this._onTileLoadComplete();
        }
    },

    _onTileLoadComplete:function() {
        //In browser, map will be requested to render once a tile was loaded.
        //but in node, map will be requested to render when the layer is loaded.
        if (Z.runningInNode) {
            this._requestMapToRender();
        }
        this._fireLoadedEvent();
    },

    /**
     * 清除瓦片区域, 并请求地图重绘
     * @param  {Point} point        瓦片左上角坐标
     */
    _clearTileRectAndRequest:function(tileImage) {
        if (!this.getMap()) {
            return;
        }
        var zoom = this.getMap().getZoom();
        if (!Z.Util.isNil(tileImage[this.propertyOfTileZoom]) && zoom !== tileImage[this.propertyOfTileZoom]) {
            return;
        }
        this._tileToLoadCounter--;
        if (this._tileToLoadCounter === 0) {
            this._onTileLoadComplete();
        }
    },

    _requestMapToRender:function() {
        if (this.getMap() && !this.getMap().isBusy()) {
            this._mapRender.render();
        }
    //     if (this._mapRenderRequest) {
    //         clearTimeout(this._mapRenderRequest);
    //     }
    //     var me = this;
    //     this._mapRenderRequest = setTimeout(function() {
    //         if (me.getMap() && !me.getMap().isBusy()) {
    //             me._mapRender.render();
    //         }
    //     }, 10);
    },

    _registerEvents:function() {
        var map = this.getMap();
        map.on('_moveend _zoomend _resize',this._onMapEvent,this);
        if (this._layer.options['renderWhenPanning']) {
        var rendSpan = this._layer.options['renderSpanWhenPanning'];
            if (Z.Util.isNumber(rendSpan) && rendSpan >= 0) {
                if (rendSpan > 0) {
                    this._onMapMoving = Z.Util.throttle(function() {
                            this._gradualLoading = false;
                            this.render();
                        },rendSpan,this);
                } else {
                    this._onMapMoving = function() {
                        this._gradualLoading = false;
                        this.render();
                    };
                }
                map.on('_moving',this._onMapMoving,this);
            }
        }

    },

    _onMapEvent:function(param) {
        if (param['type'] === '_moveend' || param['type'] === '_zoomend') {
            if (param['type'] === '_zoomend') {
                this._gradualLoading = true;
            } else {
                this._gradualLoading = false;
            }
            this.render();
        } else if (param['type'] === '_resize') {
            this._resizeCanvas();
            this.render();
        }
    },

    _fireLoadedEvent:function() {
        this._layer.fire('layerload');
    }

});

Z.TileLayer.registerRenderer('canvas',Z.renderer.tilelayer.Canvas);
<commit_msg>tilelayer, request map to render immediately in node but postpone rendering in browser<commit_after>Z.renderer.tilelayer.Canvas = Z.renderer.Canvas.extend({

    propertyOfPointOnTile   : '--maptalks-tile-point',
    propertyOfTileId        : '--maptalks-tile-id',
    propertyOfTileZoom      : '--maptalks-tile-zoom',

    initialize:function(layer) {
        this._layer = layer;
        this._mapRender = layer.getMap()._getRenderer();
        this._tileCache = new Z.TileLayer.TileCache();
        this._registerEvents();
        this._tileQueue = {};
    },

    remove:function() {
        var map = this.getMap();
        map.off('_moveend _resize _zoomend',this._onMapEvent,this);
        if (this._onMapMoving) {
            map.off('_moving',this._onMapMoving,this);
        }
        this._requestMapToRender();
    },

    getMap: function() {
        return this._layer.getMap();
    },

    show:function() {
        this.render();
    },

    hide:function() {
        this._requestMapToRender();
    },

    setZIndex:function(zIndex) {
        this._requestMapToRender();
    },

    clear:function() {
        this._clearCanvas();
        this._requestMapToRender();
    },

    clearExecutors:function() {
        clearTimeout(this._loadQueueTimeout);
    },

    initContainer:function() {

    },

    render:function() {
        var layer = this._layer;
        var tileGrid = layer._getTiles();
        if (!tileGrid) {
            return;
        }
        this._rending = true;
        if (!this._canvas) {
            this._createCanvas();
        }
        if (!this._tileRended) {
            this._tileRended = {};
        }
        this._tileZoom = this.getMap().getZoom();
        var tileRended = this._tileRended;
        this._tileRended = {};

        var tiles = tileGrid['tiles'],
            tileCache = this._tileCache,
            tileSize = layer.getTileSize();

        this._canvasFullExtent =  this.getMap()._getViewExtent();
        var viewExtent = this._canvasFullExtent;
        if (this._clipped) {
            this._context.restore();
            this._clipped=false;
        }
        this._clearCanvas();
        var mask = layer.getMask();
        if (mask) {
            var maskPxExtent = mask._getPainter().getPixelExtent();
            if (!maskPxExtent.intersects(viewExtent)) {
                return;
            }
            this._context.save();
            mask._getPainter().paint();
            this._context.clip();
            this._clipped = true;
            // viewExtent = viewExtent.intersection(maskPxExtent);
        }
        //遍历瓦片
        this._tileToLoadCounter = 0;

        for (var i = tiles.length - 1; i >= 0; i--) {
            var tile = tiles[i];
            var tileId = tiles[i]['id'];
            //如果缓存中已存有瓦片, 则从不再请求而从缓存中读取.
            var cached = tileRended[tileId] || tileCache.get(tileId);
            if (!viewExtent.intersects(new Z.PointExtent(tile['viewPoint'],
                                tile['viewPoint'].add(new Z.Point(tileSize['width'], tileSize['height']))))) {
                continue;
            }
            if (cached) {
                    //画瓦片
                this._drawTile(tile['viewPoint'], cached);
                this._tileRended[tileId] = cached;
            } else {

                this._tileToLoadCounter++;
                this._tileQueue[tileId+"@"+tile['viewPoint'].toString()] = tile;
            }
        }

        this._rending = false;
        if (this._tileToLoadCounter === 0){
            this._fireLoadedEvent();
            this._requestMapToRender();
        } else {
            this._totalTileToLoad = this._tileToLoadCounter;
            this._scheduleLoadTileQueue();
        }
    },

    getCanvasImage:function() {
        if (!this._canvasFullExtent || this._tileZoom !== this.getMap().getZoom()) {
            return null;
        }
         var gradualOpacity = null;
        if (this._gradualLoading && this._totalTileToLoad && this._layer.options['gradualLoading']) {
            gradualOpacity = ((this._totalTileToLoad - this._tileToLoadCounter) / this._totalTileToLoad)*1.5 ;
            if (gradualOpacity > 1) {
                gradualOpacity = 1;
            }
        }
        var size = this._canvasFullExtent.getSize();
        var point = this._canvasFullExtent.getMin();
        return {'image':this._canvas,'layer':this._layer,'point':this.getMap().viewPointToContainerPoint(point),'size':size,'opacity':gradualOpacity};
    },

    getPaintContext:function() {
        return [this._context];
    },

    _scheduleLoadTileQueue:function() {

        if (this._loadQueueTimeout) {
            clearTimeout(this._loadQueueTimeout);
        }

        var me = this;
        this._loadQueueTimeout = setTimeout(function(){me._loadTileQueue();},10);
    },

    _loadTileQueue:function() {
        var me = this;

        function onTileLoad() {
            me._tileCache.add(this[me.propertyOfTileId], this);
            me._tileRended[me.propertyOfTileId] = this;
            me._drawTileAndRequest(this);
        }
        function onTileError() {
            me._clearTileRectAndRequest(this);
        }
        var crossOrigin = this._layer.options['crossOrigin'];
        var tileSize = this._layer.getTileSize();
        for (var p in this._tileQueue) {
            if (this._tileQueue.hasOwnProperty(p)) {
                var tileId = p.split('@')[0];
                var tile = this._tileQueue[p];
                delete this._tileQueue[p];
                if (!this._tileCache[tileId]) {
                    var tileImage = new Image();
                    tileImage.width = tileSize['width'];
                    tileImage.height = tileSize['height'];
                    tileImage[this.propertyOfTileId]=tileId;
                    tileImage[this.propertyOfPointOnTile] = tile['viewPoint'];
                    tileImage[this.propertyOfTileZoom] = tile['zoom'];
                    tileImage.onload = onTileLoad;
                    tileImage.onabort = onTileError;
                    tileImage.onerror = onTileError;
                    if (crossOrigin) {
                        tileImage.crossOrigin = crossOrigin;
                    }
                    Z.Util.loadImage(tileImage, tile['url']);
                } else {
                    this._drawTileAndRequest(this._tileCache[tileId]);
                }

            }
        }

    },

    _drawTile:function(point, tileImage) {
        if (!point) {
            return;
        }
        var tileSize = this._layer.getTileSize();
        var opacity = this._layer.config()['opacity'];
        var isFaded = !Z.Util.isNil(opacity) && opacity < 1;
        var alpha;
        if (isFaded) {
            alpha = this._context.globalAlpha;
            if (opacity <= 0) {
                return;
            }
            this._context.globalAlpha = opacity;
        }
        Z.Canvas.image(this._context, point.substract(this._canvasFullExtent.getMin()), tileImage, tileSize['width'],tileSize['height']);
        if (isFaded) {
            this._context.globalAlpha = alpha;
        }
    },

    /**
     * 绘制瓦片, 并请求地图重绘
     * @param  {Point} point        瓦片左上角坐标
     * @param  {Image} tileImage 瓦片图片对象
     */
    _drawTileAndRequest:function(tileImage) {
        //sometimes, layer may be removed from map here.
        if (!this.getMap()) {
            return;
        }
        var zoom = this.getMap().getZoom();
        if (zoom !== tileImage[this.propertyOfTileZoom]) {
            return;
        }
        this._tileToLoadCounter--;
        var point = tileImage[this.propertyOfPointOnTile];
        this._drawTile(point, tileImage);

        if (!Z.runningInNode) {
            var tileSize = this._layer.getTileSize();
            var viewExtent = this.getMap()._getViewExtent();
            if (viewExtent.intersects(new Z.PointExtent(point, point.add(new Z.Point(tileSize['width'], tileSize['height']))))) {
                this._requestMapToRender();
            }
        }
        if (this._tileToLoadCounter === 0) {
            this._onTileLoadComplete();
        }
    },

    _onTileLoadComplete:function() {
        //In browser, map will be requested to render once a tile was loaded.
        //but in node, map will be requested to render when the layer is loaded.
        if (Z.runningInNode) {
            this._requestMapToRender();
        }
        this._fireLoadedEvent();
    },

    /**
     * 清除瓦片区域, 并请求地图重绘
     * @param  {Point} point        瓦片左上角坐标
     */
    _clearTileRectAndRequest:function(tileImage) {
        if (!this.getMap()) {
            return;
        }
        var zoom = this.getMap().getZoom();
        if (!Z.Util.isNil(tileImage[this.propertyOfTileZoom]) && zoom !== tileImage[this.propertyOfTileZoom]) {
            return;
        }
        this._tileToLoadCounter--;
        if (this._tileToLoadCounter === 0) {
            this._onTileLoadComplete();
        }
    },

    _requestMapToRender:function() {
        if (Z.runningInNode) {
            if (this.getMap() && !this.getMap().isBusy()) {
                this._mapRender.render();
            }
            return;
        }
        if (this._mapRenderRequest) {
            clearTimeout(this._mapRenderRequest);
        }
        var me = this;
        this._mapRenderRequest = setTimeout(function() {
            if (me.getMap() && !me.getMap().isBusy()) {
                me._mapRender.render();
            }
        }, 1);
    },

    _registerEvents:function() {
        var map = this.getMap();
        map.on('_moveend _zoomend _resize',this._onMapEvent,this);
        if (this._layer.options['renderWhenPanning']) {
        var rendSpan = this._layer.options['renderSpanWhenPanning'];
            if (Z.Util.isNumber(rendSpan) && rendSpan >= 0) {
                if (rendSpan > 0) {
                    this._onMapMoving = Z.Util.throttle(function() {
                            this._gradualLoading = false;
                            this.render();
                        },rendSpan,this);
                } else {
                    this._onMapMoving = function() {
                        this._gradualLoading = false;
                        this.render();
                    };
                }
                map.on('_moving',this._onMapMoving,this);
            }
        }

    },

    _onMapEvent:function(param) {
        if (param['type'] === '_moveend' || param['type'] === '_zoomend') {
            if (param['type'] === '_zoomend') {
                this._gradualLoading = true;
            } else {
                this._gradualLoading = false;
            }
            this.render();
        } else if (param['type'] === '_resize') {
            this._resizeCanvas();
            this.render();
        }
    },

    _fireLoadedEvent:function() {
        this._layer.fire('layerload');
    }

});

Z.TileLayer.registerRenderer('canvas',Z.renderer.tilelayer.Canvas);
<|endoftext|>


<commit_before>import React from "react";
import {connect} from "alt-react";
import BindToChainState from "../Utility/BindToChainState";
import GatewayStore from "stores/GatewayStore";
import counterpart from "counterpart";
import {Select} from "bitshares-ui-style-guide";

class DepositWithdrawAssetSelector extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            assets: []
        };
    }

    componentDidMount() {
        this.getAssets();
    }

    getAssets() {
        let {backedCoins, include, includeBTS} = this.props;
        let {assets} = this.state;

        let idMap = {};

        backedCoins.forEach(coin => {
            assets = assets
                .concat(
                    coin.map(item => {
                        /* Gateway Specific Settings */
                        let [gateway, backedCoin] = item.symbol.split(".");

                        // Return null if backedCoin is already stored
                        if (!idMap[backedCoin] && backedCoin && gateway) {
                            idMap[backedCoin] = true;

                            return {
                                id: backedCoin,
                                label: backedCoin,
                                gateway: gateway,
                                gateFee: item.gateFee,
                                issuer: item.issuerId || "1.2.96397" //Fall back to open ledger
                            };
                        } else {
                            return null;
                        }
                    })
                )
                .filter(item => {
                    return item;
                })
                .filter(item => {
                    if (item.id == "BTS") {
                        return true;
                    }
                    if (include) {
                        return include.includes(item.id);
                    }
                    return true;
                });
        });

        if (!(includeBTS === false)) {
            assets.push({id: "BTS", label: "BTS", gateway: ""});
        }

        this.setState({
            assets: assets
        });
    }

    getSelectedAssetArray(selectedAsset) {
        let {assets} = this.state;

        let asset;

        assets.map(a => {
            if (a.id == selectedAsset) {
                asset = a;
            }
        });

        return asset;
    }

    _onSelect(selectedAsset) {
        let {onSelect} = this.props;
        let asset = this.getSelectedAssetArray(selectedAsset);

        if (onSelect) {
            onSelect(asset);
        }
    }

    _onInputChanged(selectedAsset) {
        let {onChange} = this.props;
        let asset = this.getSelectedAssetArray(selectedAsset);

        if (onChange) {
            onChange(asset.id);
        }
    }

    render() {
        let {usageContext} = this.props;

        let coinItems = this.state.assets.sort(function(a, b) {
            if (a.id && b.id) return a.id.localeCompare(b.id);
        });

        return (
            <Select
                onSelect={this._onSelect.bind(this)}
                onChange={this._onInputChanged.bind(this)}
                onSearch={this._onInputChanged.bind(this)}
                placeholder={counterpart.translate(
                    usageContext == "withdraw"
                        ? "gateway.asset_search_withdraw"
                        : "gateway.asset_search_deposit"
                )}
                value={this.props.defaultValue}
                optionLabelProp={"value"}
                showSearch
                style={{width: "100%"}}
            >
                {/*
                    NOTE
                    On Deposit, it would be useful to view Min Deposit
                    and Gateway Fee to the right of the selection so the
                    user doesn't have to select a specific gateway to view
                    this information.
                */}

                {coinItems.length > 0 ? (
                    coinItems.map(coin => (
                        <Select.Option key={coin.id} value={coin.label}>
                            {coin.label}
                        </Select.Option>
                    ))
                ) : (
                    <Select.Option disabled key={0} value={0}>
                        {counterpart.translate(
                            usageContext == "withdraw"
                                ? "modal.withdraw.no_assets"
                                : "modal.deposit.no_assets"
                        )}
                    </Select.Option>
                )}
            </Select>
        );
    }
}
DepositWithdrawAssetSelector = BindToChainState(DepositWithdrawAssetSelector);

export default connect(
    DepositWithdrawAssetSelector,
    {
        listenTo() {
            return [GatewayStore];
        },
        getProps() {
            return {
                backedCoins: GatewayStore.getState().backedCoins
            };
        }
    }
);
<commit_msg>Fix withdraw modal search though assets<commit_after>import React from "react";
import {connect} from "alt-react";
import BindToChainState from "../Utility/BindToChainState";
import GatewayStore from "stores/GatewayStore";
import counterpart from "counterpart";
import {Select} from "bitshares-ui-style-guide";

class DepositWithdrawAssetSelector extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            assets: []
        };
    }

    componentDidMount() {
        this.getAssets();
    }

    getAssets() {
        let {backedCoins, include, includeBTS} = this.props;
        let {assets} = this.state;

        let idMap = {};

        backedCoins.forEach(coin => {
            assets = assets
                .concat(
                    coin.map(item => {
                        /* Gateway Specific Settings */
                        let [gateway, backedCoin] = item.symbol.split(".");

                        // Return null if backedCoin is already stored
                        if (!idMap[backedCoin] && backedCoin && gateway) {
                            idMap[backedCoin] = true;

                            return {
                                id: backedCoin,
                                label: backedCoin,
                                gateway: gateway,
                                gateFee: item.gateFee,
                                issuer: item.issuerId || "1.2.96397" //Fall back to open ledger
                            };
                        } else {
                            return null;
                        }
                    })
                )
                .filter(item => {
                    return item;
                })
                .filter(item => {
                    if (item.id == "BTS") {
                        return true;
                    }
                    if (include) {
                        return include.includes(item.id);
                    }
                    return true;
                });
        });

        if (!(includeBTS === false)) {
            assets.push({id: "BTS", label: "BTS", gateway: ""});
        }

        this.setState({
            assets: assets
        });
    }

    getSelectedAssetArray(selectedAsset) {
        let {assets} = this.state;

        let asset;

        assets.map(a => {
            if (a.id == selectedAsset) {
                asset = a;
            }
        });

        return asset;
    }

    _onSelect(selectedAsset) {
        let {onSelect} = this.props;
        let asset = this.getSelectedAssetArray(selectedAsset);

        if (onSelect) {
            onSelect(asset);
        }
    }

    _onInputChanged(selectedAsset) {
        let {onChange} = this.props;
        let asset = this.getSelectedAssetArray(selectedAsset);

        if (onChange && asset && asset.id) {
            onChange(asset.id);
        }
    }

    render() {
        let {usageContext} = this.props;

        let coinItems = this.state.assets.sort(function(a, b) {
            if (a.id && b.id) return a.id.localeCompare(b.id);
        });

        return (
            <Select
                onSelect={this._onSelect.bind(this)}
                onChange={this._onInputChanged.bind(this)}
                onSearch={this._onInputChanged.bind(this)}
                placeholder={counterpart.translate(
                    usageContext == "withdraw"
                        ? "gateway.asset_search_withdraw"
                        : "gateway.asset_search_deposit"
                )}
                value={this.props.defaultValue}
                optionLabelProp={"value"}
                showSearch
                style={{width: "100%"}}
            >
                {/*
                    NOTE
                    On Deposit, it would be useful to view Min Deposit
                    and Gateway Fee to the right of the selection so the
                    user doesn't have to select a specific gateway to view
                    this information.
                */}

                {coinItems.length > 0 ? (
                    coinItems.map(coin => (
                        <Select.Option key={coin.id} value={coin.label}>
                            {coin.label}
                        </Select.Option>
                    ))
                ) : (
                    <Select.Option disabled key={0} value={0}>
                        {counterpart.translate(
                            usageContext == "withdraw"
                                ? "modal.withdraw.no_assets"
                                : "modal.deposit.no_assets"
                        )}
                    </Select.Option>
                )}
            </Select>
        );
    }
}
DepositWithdrawAssetSelector = BindToChainState(DepositWithdrawAssetSelector);

export default connect(
    DepositWithdrawAssetSelector,
    {
        listenTo() {
            return [GatewayStore];
        },
        getProps() {
            return {
                backedCoins: GatewayStore.getState().backedCoins
            };
        }
    }
);
<|endoftext|>


<commit_before>(function(global) {

  // map tells the System loader where to look for things
  var map = {
    'app':                        'dist', // 'dist',
    'rxjs':                       'node_modules/rxjs',
    '@angular':                   'node_modules/@angular',
    'angular2-modal':             'node_modules/angular2-modal',
    'angular2-modal/platform-browser': 'node_modules/angular2-modal/platform-browser',
    'angular2-modal/plugins/bootstrap': 'node_modules/angular2-modal/plugins/bootstrap'
  };

  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main:'bootstrap.js' ,defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' },
    'angular2-modal': {main: 'index.js', defaultExtension: 'js'},
    'angular2-modal/platform-browser': {main: 'index.js', defaultExtension: 'js'},
    'angular2-modal/plugins/bootstrap': {main: 'index.js', defaultExtension: 'js'},
  };

  var packageNames = [
    '@angular/common',
    '@angular/compiler',
    '@angular/core',
    '@angular/http',
    '@angular/platform-browser',
    '@angular/platform-browser-dynamic',
    '@angular/router',
    '@angular/testing'
  ];

  // add package entries for angular packages in the form '@angular/common': { main: 'index.js', defaultExtension: 'js' }
  packageNames.forEach(function(pkgName) {
    packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
  });

  var config = {
    map: map,
    packages: packages
  }

  // filterSystemConfig - index.html's chance to modify config before we register it.
  if (global.filterSystemConfig) { global.filterSystemConfig(config); }

  System.config(config);

})(this);<commit_msg>Updated systemjs to support bundled umd files<commit_after>(function (global) {

  // map tells the System loader where to look for things
  var map = {
    'app': 'dist', // 'dist',
    'rxjs': 'node_modules/rxjs',
    '@angular':                   'node_modules/@angular',
    'angular2-modal':             'node_modules/angular2-modal',
    'angular2-modal/platform-browser': 'node_modules/angular2-modal/platform-browser',
    'angular2-modal/plugins/bootstrap': 'node_modules/angular2-modal/plugins/bootstrap'
  };

  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app': { main: 'bootstrap.js', defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' },
    'angular2-modal': {main: 'index.js', defaultExtension: 'js'},
    'angular2-modal/platform-browser': {main: 'index.js', defaultExtension: 'js'},
    'angular2-modal/plugins/bootstrap': {main: 'index.js', defaultExtension: 'js'},
  };

  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'testing'
  ];

  // Individual files (~300 requests):
  function packIndex(pkgName) {
    packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' };
  }

  // add package entries for angular packages in the form '@angular/common': { main: 'index.js', defaultExtension: 'js' }
  // Bundled (~40 requests):
  function packUmd(pkgName) {
    packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
  }

  // Most environments should use UMD; some (Karma) need the individual index files
  var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;

  // Add package entries for angular packages
  ngPackageNames.forEach(setPackageConfig);

  var config = {
    map: map,
    packages: packages
  }

  // filterSystemConfig - index.html's chance to modify config before we register it.
  if (global.filterSystemConfig) { global.filterSystemConfig(config); }

  System.config(config);

})(this);<|endoftext|>


<commit_before>/* globals window */
var Backbone     = require('backbone'),
    $            = require('jquery'),
    Query        = require('lib/query'),
    _            = require('underscore'),
    facilities   = require('collections/facilities').instance,
    searchParams = ["fr"],
    parseParams  = require('lib/query_param_parser');

function generateQueryParams(queryString) {
  var params       = parseParams(queryString),
      categories   = _.compact((params.categories || '').split(',')),
      demographics = _.compact((params.demographics || '').split(',')),
      gender       = params.gender || null,
      search       = decodeURIComponent(params.search || ''),
      sort         = params.sort,
      queryParams  = { search: search },
      filterParams = {};

  if (categories.length > 0) {
    filterParams.categories = categories;
  }

  if (demographics.length > 0) {
    filterParams.age = demographics;
  }

  if (params.gender && params.gender !== 'A') {
    filterParams.gender = params.gender;
  }

  if (params.sort) {
    queryParams.sort = params.sort;
  }

  queryParams.filter = filterParams;

  return queryParams;
}

function validCategory(category) {
  return category && (/[a-z]+/).test(category.toString());
}

function getData($elements, dataAttrName) {
  var result = [];
  $elements.each(function(n, el) {
    result.push($(el).data(dataAttrName));
  });
  return result;
}

var CATEGORIES = [
  {
    key: "food",
    title: "Food",
    icon: "coffee"
  },
  {
    key: "medical",
    title: "Medical",
    icon: "medical"
  },
  {
    key: "housing",
    title: "Housing",
    icon: "house"
  },
  {
    key: "technology",
    title: "Technology",
    icon: "desktop"
  },
  {
    key: "hygiene",
    title: "Hygiene",
    icon: "droplet"
  }
];

var ListView = Backbone.View.extend({
  template: require('templates/list'),

  events: {
    "click #filter-button": 'goToFilter',
    "click #load-more":     'loadMore'
  },

  initialize: function() {
    this.listenTo(this.collection, 'reset', this.render);
  },

  reset: function() {
    this.offset = this.hideMore = null;
  },

  loadMore: function() {
    $('#loading-spinner').show();
    $('#load-more-container').hide();

    Query.submit(this.getFilterParams()).done(function(results) {
      $('#loading-spinner').hide();

      this.offset = results.offset;
      this.hideMore = (results.data.length < 10);

      this.collection.add(results.data);
      this.render();
    }.bind(this));
  },

  goToFilter: function() {
    var router = require('routers/router').instance;
    router.navigate("filter", {trigger: true});
  },

  generateQueryParams: generateQueryParams,

  getFilterParams: function () {
    var queryString  = window.location.hash.substring(window.location.hash.indexOf('?')+1),
        queryParams  = generateQueryParams(queryString);

    queryParams.offset = this.offset;

    this.options.categories = queryParams.filter.categories || [];

    return queryParams;
  },

  performQuery: function(params) {
    return Query.submit(params).done(function(results) {
      searchParams = params;
      // populate with results
      facilities.reset(results.data);
      this.offset = results.offset;
    }.bind(this));
  },

  resetFilters: function() {
    this.$(".query .selected").removeClass("selected");
    var self = this;
    _.each(this.options.categories, function(category) {
      self.$categoryOption(category).addClass("selected");
    });

    this.$(".query-option-gender [data-value='A']").addClass('selected');
  },

  $categoryOption: function(category) {
    if(validCategory(category)) {
      return this.$(".query-option-category [data-value=" + category + "]").addClass("selected");
    } else {
      return $();
    }

  },

  render: function() {
    var deepJson = this.deepToJson(this.collection);
    var templateJson = this.flattenServices(deepJson);

    // replace with template
    this.$el.html(this.template({
      facilities: templateJson,
      categories: CATEGORIES,
      searchParams: this.filterSelectCategories(this.options.categories),
      navButtons: [
        {class: 'left', id: 'backNav-button', text: 'Back'},
        {class: 'right', id: 'filter-button', text: 'Filter'}
      ]
    }));
    this.$('.query').hide();
    this.$('.option-group-exclusive .query-option').click(function() {
      $(this).closest(".option-group-exclusive").find(".query-option").removeClass("selected");
      $(this).toggleClass("selected");
    });

    this.$('.option-group .query-option').click(function() {
      $(this).toggleClass("selected");
    });
    this.$('#backNav-button').click(function(){
      require('routers/router').instance.back();
    });

    if ( !this.hideMore ) {
      this.$('#load-more-container').show();
    }

    this.resetFilters();
    return this;
  },

  deepToJson: function(collection) {
    var json = [],
        modelJson;

    json = _.map(collection.models, function(model) {
      modelJson = model.toJSON();
      modelJson.services = [];
      _.each(model.attributes.services, function(service) {
        modelJson.services.push(service.toJSON());
      });
      return modelJson;
    });

    return json;
  },

  filterSelectCategories: function(queryParams) {
    var match, selectedCategories = [];
    _.each(queryParams, function(queryName){
      match = _.find(CATEGORIES, function(e){ return e.key == queryName; });
      if (!_.contains(selectedCategories, match)) {
        selectedCategories.push(match);
      }
    });
    return selectedCategories;
  },

  flattenServices: function(jsonArray) {
    var serviceCategories,
        allNotes,
        flattened = [],
        self = this;

    _.each(jsonArray, function(jsonModel) {
      serviceCategories = [];
      allNotes = [];
      _.each(jsonModel.services, function(jsonService) {
        serviceCategories.push(jsonService.category);
        allNotes.push(jsonService.notes);
      });
      jsonModel.serviceCategories = self.filterSelectCategories(serviceCategories);
      // jsonModel.serviceCategories = serviceCategories.join(', ');
      jsonModel.allNotes = allNotes.join(' ');
      flattened.push(jsonModel);
    });

    return flattened;
  }
});

module.exports = ListView;
<commit_msg>consume lib/categories<commit_after>/* globals window */
var Backbone     = require('backbone'),
    $            = require('jquery'),
    Query        = require('lib/query'),
    _            = require('underscore'),
    facilities   = require('collections/facilities').instance,
    searchParams = ["fr"],
    parseParams  = require('lib/query_param_parser');

function generateQueryParams(queryString) {
  var params       = parseParams(queryString),
      categories   = _.compact((params.categories || '').split(',')),
      demographics = _.compact((params.demographics || '').split(',')),
      gender       = params.gender || null,
      search       = decodeURIComponent(params.search || ''),
      sort         = params.sort,
      queryParams  = { search: search },
      filterParams = {};

  if (categories.length > 0) {
    filterParams.categories = categories;
  }

  if (demographics.length > 0) {
    filterParams.age = demographics;
  }

  if (params.gender && params.gender !== 'A') {
    filterParams.gender = params.gender;
  }

  if (params.sort) {
    queryParams.sort = params.sort;
  }

  queryParams.filter = filterParams;

  return queryParams;
}

function validCategory(category) {
  return category && (/[a-z]+/).test(category.toString());
}

function getData($elements, dataAttrName) {
  var result = [];
  $elements.each(function(n, el) {
    result.push($(el).data(dataAttrName));
  });
  return result;
}

var ListView = Backbone.View.extend({
  categories: require('lib/categories'),
  template: require('templates/list'),

  events: {
    "click #filter-button": 'goToFilter',
    "click #load-more":     'loadMore'
  },

  initialize: function() {
    this.listenTo(this.collection, 'reset', this.render);
  },

  reset: function() {
    this.offset = this.hideMore = null;
  },

  loadMore: function() {
    $('#loading-spinner').show();
    $('#load-more-container').hide();

    Query.submit(this.getFilterParams()).done(function(results) {
      $('#loading-spinner').hide();

      this.offset = results.offset;
      this.hideMore = (results.data.length < 10);

      this.collection.add(results.data);
      this.render();
    }.bind(this));
  },

  goToFilter: function() {
    var router = require('routers/router').instance;
    router.navigate("filter", {trigger: true});
  },

  generateQueryParams: generateQueryParams,

  getFilterParams: function () {
    var queryString  = window.location.hash.substring(window.location.hash.indexOf('?')+1),
        queryParams  = generateQueryParams(queryString);

    queryParams.offset = this.offset;

    this.options.categories = queryParams.filter.categories || [];

    return queryParams;
  },

  performQuery: function(params) {
    return Query.submit(params).done(function(results) {
      searchParams = params;
      // populate with results
      facilities.reset(results.data);
      this.offset = results.offset;
    }.bind(this));
  },

  resetFilters: function() {
    this.$(".query .selected").removeClass("selected");
    var self = this;
    _.each(this.options.categories, function(category) {
      self.$categoryOption(category).addClass("selected");
    });

    this.$(".query-option-gender [data-value='A']").addClass('selected');
  },

  $categoryOption: function(category) {
    if(validCategory(category)) {
      return this.$(".query-option-category [data-value=" + category + "]").addClass("selected");
    } else {
      return $();
    }

  },

  render: function() {
    var deepJson = this.deepToJson(this.collection);
    var templateJson = this.flattenServices(deepJson);

    // replace with template
    this.$el.html(this.template({
      facilities: templateJson,
      categories: this.categories,
      searchParams: this.filterSelectCategories(this.options.categories),
      navButtons: [
        {class: 'left', id: 'backNav-button', text: 'Back'},
        {class: 'right', id: 'filter-button', text: 'Filter'}
      ]
    }));
    this.$('.query').hide();
    this.$('.option-group-exclusive .query-option').click(function() {
      $(this).closest(".option-group-exclusive").find(".query-option").removeClass("selected");
      $(this).toggleClass("selected");
    });

    this.$('.option-group .query-option').click(function() {
      $(this).toggleClass("selected");
    });
    this.$('#backNav-button').click(function(){
      require('routers/router').instance.back();
    });

    if ( !this.hideMore ) {
      this.$('#load-more-container').show();
    }

    this.resetFilters();
    return this;
  },

  deepToJson: function(collection) {
    var json = [],
        modelJson;

    json = _.map(collection.models, function(model) {
      modelJson = model.toJSON();
      modelJson.services = [];
      _.each(model.attributes.services, function(service) {
        modelJson.services.push(service.toJSON());
      });
      return modelJson;
    });

    return json;
  },

  filterSelectCategories: function(queryParams) {
    var match, selectedCategories = [];

    _.each(queryParams, function(queryName){
      var match = _.find(this.categories, function(e){ return e.key == queryName; });
      if (!_.contains(selectedCategories, match)) {
        selectedCategories.push(match);
      }
    });

    return selectedCategories;
  },

  flattenServices: function(jsonArray) {
    var serviceCategories,
        allNotes,
        flattened = [],
        self = this;

    _.each(jsonArray, function(jsonModel) {
      serviceCategories = [];
      allNotes = [];
      _.each(jsonModel.services, function(jsonService) {
        serviceCategories.push(jsonService.category);
        allNotes.push(jsonService.notes);
      });
      jsonModel.serviceCategories = self.filterSelectCategories(serviceCategories);
      // jsonModel.serviceCategories = serviceCategories.join(', ');
      jsonModel.allNotes = allNotes.join(' ');
      flattened.push(jsonModel);
    });

    return flattened;
  }
});

module.exports = ListView;
<|endoftext|>