<commit_before>/**!
 * Animated Recent Signers for Greenpeace Action Template v0.3
 * @name            p3.recent_signers.js
 * @fileOverview    Displays the most recent pledge signers, queued and
 *                  animated
 * @copyright       Copyright 2013, Greenpeace International
 * @license         MIT License (opensource.org/licenses/MIT)
 * @version         0.3.1
 * @author          Ray Walker <hello@raywalker.it>
 * @requires        jQuery 1.6+,
 *                  Modernizr,
 * @example         $.p3.recent_signers('#action-recent-signers'[, options]);
 *
 */
/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, jquery:true, indent:4, maxerr:50 */
/*global Modernizr */

(function($, M, window, document) {
    'use strict';

    var _p3 = $.p3 || {},
        defaults = {
            jsonURL: 'https://secured.greenpeace.org/international/en/api/v2/pledges/',
            /* parameters to be added to the request url */
            params: {},
            /* Force UTC timezone if no time zone data is returned */
            mangleTime: true,
            /* selects which element holds the time data attribute */
            timeSelector: '.since',
            /* selects which attribute contains timestamps */
            timeDataAttr: 'data-since',
            /* selector for the country dropdown to map country codes to names
             * set to false to force sourcing information from JSON instead */
            countrySelector: '#UserCountry',
            /* source country information from a JSON file */
            countryJSON: 'json/countries/countries_en.json',
            /* set to true to delay execution until externally triggered by event */
            externalTrigger: false,
            /* element to store returned pledge data */
            dataElement: 'body',
            /* name of data object to store pledge data */
            dataNamespace: 'pledgeData',
            /* trigger this event to fetch JSON data from the endpoint */
            fetchDataEvent: 'recentSignersFetch',
            /* trigger this event if you have fetched data externally and just
             * want to parse and update display */
            fetchCompleteEvent: 'fetchPledgeDataComplete',
            /* delay in milliseconds between signer check updates */
            updateInterval: 30000,
            /* interval between users added to the recent signer list from last
             * update */
            userQueueInterval: 750,
            /* maximum number of users to display at once, excess users are hidden
             * and then removed from the DOM */
            maxUsers: 5,
            /* number of times to check the server for new signers after the first
             * set to 0 to disable updates */
            maxRefreshes: 30,
            /* milliseconds to wait for the API request */
            timeout: 30000,
            /* stop processing if there is an error */
            abortOnError: false
        };

    $.timeago.settings.strings = {
        prefixAgo: null,
        prefixFromNow: null,
        suffixAgo: "ago",
        suffixFromNow: "from now",
        seconds: "%d seconds",
        minute: "a minute",
        minutes: "%d minutes",
        hour: "an hour",
        hours: "%d hours",
        day: "a day",
        days: "%d days",
        month: "a month",
        months: "%d months",
        year: "a year",
        years: "%d years",
        wordSeparator: " ",
        numbers: []
    };

    _p3.recent_signers = function(el, options) {
        var config = $.extend(true, defaults, options),
            $el = $(el),
            $ul = false,
            request = $.p3.request(config.jsonURL),
            users = [],
            countries = [],
            timer = false,
            refreshNum = 0,
            prefix = '$.p3.recent_signers :: ',
            pledgeQueue = {
                running: false,
                delay: config.userQueueInterval,
                actions: [],
                step: function() {
                    if (pledgeQueue.actions.length) {
                        try {
                            pledgeQueue.actions.pop()();
                        } catch (e) {
                            console.log(e);
                        }
                    } else {
                        pledgeQueue.running = false;
                    }
                },
                run: function() {
                    pledgeQueue.running = true;
                    if (pledgeQueue.actions.length) {
                        pledgeQueue.step();
                        setTimeout(pledgeQueue.run, pledgeQueue.delay);
                    } else {
                        pledgeQueue.running = false;
                    }
                }
            },
            fetchJSON = function() {
                if (pledgeQueue.running) {
                    // Still executing last update, restart timer
                    clearTimeout(timer);
                    timer = setTimeout(function() {
                        fetchJSON();
                    }, config.updateInterval);
                    // and exit
                    return false;
                }

                var params = $.extend(true, request.parameters, config.params);

                // http://stackoverflow.com/questions/20565330/ajax-call-for-json-fails-in-ie
                $.support.cors = true;

                $.ajax({
                    url: request.url,
                    timeout: config.timeout,
                    dataType: 'json',
                    data: params
                }).success(function(json) {
                    $(config.dataElement).data(config.dataNamespace, json);
                }).fail(function(e1) {
                    var message = prefix + 'Failed to load "' + request.url + '"';

                    if (config.abortOnError) {
                        throw new Error(message, e1);
                    }
                }).always(function() {
                    $.event.trigger(config.fetchCompleteEvent);
                });
            },
            parsePledgeData = function() {
                var jsonData = $(config.dataElement).data(config.dataNamespace);

                // Add fetch first, since array is popped not shifted,
                // because we're adding pledges from oldest to newest, but the
                // data is sorted newest to oldest
                if (!config.externalTrigger && refreshNum++ < config.maxRefreshes) {
                    pledgeQueue.actions.push(function() {
                        clearTimeout(timer);
                        timer = setTimeout(function() {
                            $.event.trigger(config.fetchDataEvent);
                        }, config.updateInterval);
                    });
                }

                if (!jsonData || jsonData.status === 'error') {
                    if (config.abortOnError) {
                        throw new Error(prefix + 'JSON data invalid');
                    } else {
                        // Trigger next fetch and stop parsing
                        pledgeQueue.run();
                        return;
                    }
                }

                $.each(jsonData.pledges, function(i, pledge) {

                    if (users[pledge.user.id]) {
                        // Already added, exit $.each
                        return false;
                    } else {
                        // Store this ID
                        users[pledge.user.id] = true;
                    }

                    if (pledge.user.dont_display_name === true) {
                        // Skip to next pledge ...
                        console.warn(prefix + 'pledge.user.dont_display_name is true');
                        return true;
                    }

                    pledgeQueue.actions.push(function() {
                        // Enqueue this user
                        showUser(pledge.user);
                    });

                });

                // Update existing timestamps
                pledgeQueue.actions.push(function() {
                    updateTimeStamps();
                });

                // Show all pledges in the queue
                pledgeQueue.run();

            },

            /**
             * Retrieves human readable time string from timestamp
             * Forces UTC if no timezone identifier is found
             * @param {string} timestamp
             * @returns {string}
             */
            getTimeString = function(time) {
                if (time) {
                    if (config.mangleTime) {
                        // Assume timestamp is UTC if trimmed string includes a space
                        time = $.trim(time).replace(/\s/,'Z');
                    }
                    return $.timeago(time);
                }
            },
            parseCountryData = function() {
                var deferred = $.Deferred(),
                    size = 0;

                if (config.countrySelector) {
                    // Parse the country element for code => name pairs
                    $.each($(config.countrySelector + ' option'), function (i, option) {

                        var $option = $(option),
                            val = $option.val();

                        if (val) {
                            // Force uppercase (not lowercase)
                            countries[val.toUpperCase()] = $option.text();
                            size++;
                        }
                    });

                    if (size > 0) {
                        // Happy with that.
                        deferred.resolve();
                        return deferred.promise();
                    } else {
                        // No countries found in the selector element
                        console.warn(prefix + 'No countries found in ' + config.countrySelector);
                        if (config.abortOnError) {
                            // Exit here with a failure
                            deferred.reject('Aborting', countries);
                            return deferred.promise();
                        }
                        // Continue with an attempt to read the country data from JSON
                    }
                }

                if (config.countryJSON) {
                    // fetch JSON country data
                    $.ajax({
                        url: config.countryJSON,
                        timeout: config.timeout,
                        dataType: 'json'
                    }).success( function(data) {
                        countries = data;
                        deferred.resolve();
                    }).fail(function (e) {
                        deferred.reject('Error fetching JSON from ' + config.countryJSON, e);
                    });
                }

                return deferred.promise();
            },
            getCountryString = function(country) {
                var string = countries.hasOwnProperty(country.toUpperCase()) ? countries[country.toUpperCase()] : false;

                if (string) {
                    return string;
                } else {
                    console.error(prefix + 'Country not found for code ' + country);
                    return country;
                }
            },
            showUser = function(user) {
                var timestamp = user.created || '',
                    timeCreated = getTimeString(timestamp),
                    country = user.country,
                    countryString = getCountryString(user.country),
                    firstname = user.firstname || '',
                    lastname = user.lastname || '',
                    $li = $('

');

                $li.append('' + timeCreated + '')
                    .append('')
                    .append('' + firstname + ' ' + lastname + '')
                    .append(' ' + countryString + '');

                // Add to DOM
                $ul.prepend($li);

                // And show
                $li.show(350);

                // Remove any excess users
                if (config.maxUsers && $('li', $ul).length > config.maxUsers) {
                    $('li:last', $ul).hide(350, function () {
                        this.remove();
                    });
                }
            },
            updateTimeStamps = function() {
                $(config.timeSelector, $ul).each(function() {
                    var $this = $(this);
                    $this.text(getTimeString($this.attr(config.timeDataAttr)));
                });
            };

        // initialise and run

        if (config.updateInterval < 5000) {
            // Update interval sanity check
            config.updateInterval = 5000;
        }

        $ul = $('ul:first', $el);
        if (!$ul.length) {
            $ul = $('

');
            $el.append($ul);
        }

        M.load({
            test: window.JSON,
            nope: [
                'js/compat/json.min.js'
            ],
            complete: function() {
                // parseCountryData() may rely on an AJAX call,
                // so we defer processing the rest of the application
                var promise = parseCountryData();

                promise.done(function () {
                    // Successfuly parsed Countries, we can now register events

                    // Apply human readable string to existing timestamps
                    updateTimeStamps();

                    // Event driven fetch and processing means we can decouple data
                    // from this plugin, allowing us to reuse the data without
                    // performing multiple requests

                    $(document).on(config.fetchDataEvent, function() {
                        fetchJSON();
                    });

                    $(document).on(config.fetchCompleteEvent, function() {
                        parsePledgeData();
                    });

                    // Trigger listen event unless configured to listen externally
                    if (!config.externalTrigger) {
                        $.event.trigger(config.fetchDataEvent);
                    }
                });

                promise.fail(function(message, error) {
                    // An error occurred parsing the countries list
                    console.error(message, error);
                });
            }
        });

    };

    $.p3 = _p3;

}(jQuery, Modernizr, this, document));
<commit_msg>p3.recent_signers - 0.3.2 - Bugfix for user pledge timestamps - was based on time of user creation, not pledge.  More robust timestamp coercion.<commit_after>/**!
 * Animated Recent Signers for Greenpeace Action Template v0.3
 * @name            p3.recent_signers.js
 * @fileOverview    Displays the most recent pledge signers, queued and
 *                  animated
 * @copyright       Copyright 2013, Greenpeace International
 * @license         MIT License (opensource.org/licenses/MIT)
 * @version         0.3.2
 * @author          Ray Walker <hello@raywalker.it>
 * @requires        jQuery 1.6+,
 *                  Modernizr,
 * @example         $.p3.recent_signers('#action-recent-signers'[, options]);
 *
 */
/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, jquery:true, indent:4, maxerr:50 */
/*global Modernizr */

(function($, M, window, document) {
    'use strict';

    var _p3 = $.p3 || {},
        defaults = {
            jsonURL: 'https://secured.greenpeace.org/international/en/api/v2/pledges/',
            /* parameters to be added to the request url */
            params: {},
            /* Force UTC timezone if no time zone data is returned */
            mangleTime: true,
            /* selects which element holds the time data attribute */
            timeSelector: '.since',
            /* selects which attribute contains timestamps */
            timeDataAttr: 'data-since',
            /* selector for the country dropdown to map country codes to names
             * set to false to force sourcing information from JSON instead */
            countrySelector: '#UserCountry',
            /* source country information from a JSON file */
            countryJSON: 'json/countries/countries_en.json',
            /* set to true to delay execution until externally triggered by event */
            externalTrigger: false,
            /* element to store returned pledge data */
            dataElement: 'body',
            /* name of data object to store pledge data */
            dataNamespace: 'pledgeData',
            /* trigger this event to fetch JSON data from the endpoint */
            fetchDataEvent: 'recentSignersFetch',
            /* trigger this event if you have fetched data externally and just
             * want to parse and update display */
            fetchCompleteEvent: 'fetchPledgeDataComplete',
            /* delay in milliseconds between signer check updates */
            updateInterval: 30000,
            /* interval between users added to the recent signer list from last
             * update */
            userQueueInterval: 750,
            /* maximum number of users to display at once, excess users are hidden
             * and then removed from the DOM */
            maxUsers: 5,
            /* number of times to check the server for new signers after the first
             * set to 0 to disable updates */
            maxRefreshes: 30,
            /* milliseconds to wait for the API request */
            timeout: 30000,
            /* stop processing if there is an error */
            abortOnError: false
        };

    $.timeago.settings.strings = {
        prefixAgo: null,
        prefixFromNow: null,
        suffixAgo: "ago",
        suffixFromNow: "from now",
        seconds: "%d seconds",
        minute: "a minute",
        minutes: "%d minutes",
        hour: "an hour",
        hours: "%d hours",
        day: "a day",
        days: "%d days",
        month: "a month",
        months: "%d months",
        year: "a year",
        years: "%d years",
        wordSeparator: " ",
        numbers: []
    };

    _p3.recent_signers = function(el, options) {
        var config = $.extend(true, defaults, options),
            $el = $(el),
            $ul = false,
            request = $.p3.request(config.jsonURL),
            users = [],
            countries = [],
            timer = false,
            refreshNum = 0,
            prefix = '$.p3.recent_signers :: ',
            pledgeQueue = {
                running: false,
                delay: config.userQueueInterval,
                actions: [],
                step: function() {
                    if (pledgeQueue.actions.length) {
                        try {
                            pledgeQueue.actions.pop()();
                        } catch (e) {
                            console.error(e);
                        }
                    } else {
                        pledgeQueue.running = false;
                    }
                },
                run: function() {
                    pledgeQueue.running = true;
                    if (pledgeQueue.actions.length) {
                        pledgeQueue.step();
                        setTimeout(pledgeQueue.run, pledgeQueue.delay);
                    } else {
                        pledgeQueue.running = false;
                    }
                }
            },
            fetchJSON = function() {
                if (pledgeQueue.running) {
                    // Still executing last update, restart timer
                    clearTimeout(timer);
                    timer = setTimeout(function() {
                        fetchJSON();
                    }, config.updateInterval);
                    // and exit
                    return false;
                }

                var params = $.extend(true, request.parameters, config.params);

                // http://stackoverflow.com/questions/20565330/ajax-call-for-json-fails-in-ie
                $.support.cors = true;

                $.ajax({
                    url: request.url,
                    timeout: config.timeout,
                    dataType: 'json',
                    data: params
                }).success(function(json) {
                    $(config.dataElement).data(config.dataNamespace, json);
                }).fail(function(e1) {
                    var message = prefix + 'Failed to load "' + request.url + '"';

                    if (config.abortOnError) {
                        throw new Error(message, e1);
                    }
                }).always(function() {
                    $.event.trigger(config.fetchCompleteEvent);
                });
            },
            parsePledgeData = function() {
                var jsonData = $(config.dataElement).data(config.dataNamespace);

                // Add fetch first, since array is popped not shifted,
                // because we're adding pledges from oldest to newest, but the
                // data is sorted newest to oldest
                if (!config.externalTrigger && refreshNum++ < config.maxRefreshes) {
                    pledgeQueue.actions.push(function() {
                        clearTimeout(timer);
                        timer = setTimeout(function() {
                            $.event.trigger(config.fetchDataEvent);
                        }, config.updateInterval);
                    });
                }

                if (!jsonData || jsonData.status === 'error') {
                    if (config.abortOnError) {
                        throw new Error(prefix + 'JSON data invalid');
                    } else {
                        // Trigger next fetch and stop parsing
                        pledgeQueue.run();
                        return;
                    }
                }

                $.each(jsonData.pledges, function(i, pledge) {

                    if (users[pledge.user.id]) {
                        // Already added, exit $.each
                        return false;
                    } else {
                        // Store this ID
                        users[pledge.user.id] = true;
                    }

                    if (pledge.user.dont_display_name === true) {
                        // Skip to next pledge ...
                        console.warn(prefix + 'pledge.user.dont_display_name is true');
                        return true;
                    }

                    pledgeQueue.actions.push(function() {
                        // Enqueue this user
                        showUser(pledge);
                    });

                });

                // Update existing timestamps
                pledgeQueue.actions.push(function() {
                    updateTimeStamps();
                });

                // Show all pledges in the queue
                pledgeQueue.run();

            },

            /**
             * Retrieves human readable time string from timestamp
             * Forces UTC if no timezone identifier is found
             * @param {string} timestamp
             * @returns {string}
             */
            getTimeString = function(time) {
                if (time) {
                    var t = $.trim(time);
                    if (config.mangleTime) {
                        // Assume timezone is UTC if trimmed string includes a space
                        if (t.match(/\s/)) {
                            // Convert to ISO8601 standard timestamp
                            t = t.replace(/\s/, 'T') + 'Z';
                        }
                    }
                    return $.timeago(t);
                }
            },
            parseCountryData = function() {
                var deferred = $.Deferred(),
                    size = 0;

                if (config.countrySelector) {
                    // Parse the country element for code => name pairs
                    $.each($(config.countrySelector + ' option'), function (i, option) {

                        var $option = $(option),
                            val = $option.val();

                        if (val) {
                            // Force uppercase (not lowercase)
                            countries[val.toUpperCase()] = $option.text();
                            size++;
                        }
                    });

                    if (size > 0) {
                        // Happy with that.
                        deferred.resolve();
                        return deferred.promise();
                    } else {
                        // No countries found in the selector element
                        console.warn(prefix + 'No countries found in ' + config.countrySelector);
                        if (config.abortOnError) {
                            // Exit here with a failure
                            deferred.reject('Aborting', countries);
                            return deferred.promise();
                        }
                        // Continue with an attempt to read the country data from JSON
                    }
                }

                if (config.countryJSON) {
                    // fetch JSON country data
                    $.ajax({
                        url: config.countryJSON,
                        timeout: config.timeout,
                        dataType: 'json'
                    }).success( function(data) {
                        countries = data;
                        deferred.resolve();
                    }).fail(function (e) {
                        deferred.reject('Error fetching JSON from ' + config.countryJSON, e);
                    });
                }

                return deferred.promise();
            },
            getCountryString = function(country) {
                var string = countries.hasOwnProperty(country.toUpperCase()) ? countries[country.toUpperCase()] : false;

                if (string) {
                    return string;
                } else {
                    console.error(prefix + 'Country not found for code ' + country);
                    return country;
                }
            },
            showUser = function(pledge) {
                var user = pledge.user,
                    timestamp = pledge.created,
                    timeCreated = getTimeString(timestamp),
                    country = user.country,
                    countryString = getCountryString(user.country),
                    firstname = user.firstname || '',
                    lastname = user.lastname || '',
                    $li = $('

');

                $li.append('' + timeCreated + '')
                    .append('')
                    .append('' + firstname + ' ' + lastname + '')
                    .append(' ' + countryString + '');

                // Add to DOM
                $ul.prepend($li);

                // And show
                $li.show(350);

                // Remove any excess users
                if (config.maxUsers && $('li', $ul).length > config.maxUsers) {
                    $('li:last', $ul).hide(350, function () {
                        this.remove();
                    });
                }
            },
            updateTimeStamps = function() {
                $(config.timeSelector, $ul).each(function() {
                    var $this = $(this);
                    $this.text(getTimeString($this.attr(config.timeDataAttr)));
                });
            };

        // initialise and run

        if (config.updateInterval < 5000) {
            // Update interval sanity check
            config.updateInterval = 5000;
        }

        $ul = $('ul:first', $el);
        if (!$ul.length) {
            $ul = $('

');
            $el.append($ul);
        }

        M.load({
            test: window.JSON,
            nope: [
                'js/compat/json.min.js'
            ],
            complete: function() {
                // parseCountryData() may rely on an AJAX call,
                // so we defer processing the rest of the application
                var promise = parseCountryData();

                promise.done(function () {
                    // Successfuly parsed Countries, we can now register events

                    // Apply human readable string to existing timestamps
                    updateTimeStamps();

                    // Event driven fetch and processing means we can decouple data
                    // from this plugin, allowing us to reuse the data without
                    // performing multiple requests

                    $(document).on(config.fetchDataEvent, function() {
                        fetchJSON();
                    });

                    $(document).on(config.fetchCompleteEvent, function() {
                        parsePledgeData();
                    });

                    // Trigger listen event unless configured to listen externally
                    if (!config.externalTrigger) {
                        $.event.trigger(config.fetchDataEvent);
                    }
                });

                promise.fail(function(message, error) {
                    // An error occurred parsing the countries list
                    console.error(message, error);
                });
            }
        });

    };

    $.p3 = _p3;

}(jQuery, Modernizr, this, document));
<|endoftext|>


<commit_before>import {takeLatest, all, call, put, select} from 'redux-saga/effects'
import {rest, externalEvents} from 'tocco-app-extensions'
import {selection as selectionUtil} from 'tocco-util'

import * as actions from './actions'
import {getDialogInfo, getEntitiesToDelete} from '../../utils/deleteRequestParser'

export const inputSelector = state => state.input
export const textResourceSelector = state => state.intl.messages
export const entitiesToDeleteSelector = state => state.del.entitiesToDelete

export function* getDeleteBodyFromSelection() {
  const {selection} = yield select(inputSelector)

  const entities = yield call(selectionUtil.getEntities, selection, rest.fetchEntities)

  return {
    entityModel: entities.entityName,
    keys: entities.keys
  }
}

export function* getDeleteEndpoint() {
  const {customDeleteEndpoint} = yield select(inputSelector)
  return customDeleteEndpoint || 'client/delete'
}

export function* loadDialogInfo() {
  const body = yield call(getDeleteBodyFromSelection)
  const deleteEndpoint = yield call(getDeleteEndpoint)

  const [deleteResponse, principal] = yield all([
    call(rest.requestSaga, `${deleteEndpoint}/dialog`, {method: 'POST', body}),
    call(rest.fetchPrincipal)
  ])

  const dialogInfo = yield call(getDialogInfo, deleteResponse.body, principal.currentBusinessUnit.id)
  yield put(actions.setDeleteDialogInfo(dialogInfo))

  const entitiesToDelete = yield call(getEntitiesToDelete, deleteResponse.body)
  yield put(actions.setEntitiesToDelete(entitiesToDelete))
}

export function* doDelete() {
  const entitiesToDelete = yield select(entitiesToDeleteSelector)

  yield put(actions.setDeletingInProgress(true))
  const body = {
    entityModel: entitiesToDelete.entityName,
    keys: entitiesToDelete.keys
  }

  const deleteEndpoint = yield call(getDeleteEndpoint)

  const response = yield call(rest.requestSaga, deleteEndpoint, {method: 'POST', body})

  const textResources = yield select(textResourceSelector)
  if (response.ok) {
    const {body} = response

    const entities = Object.keys(body.deletedEntities).reduce((acc, entityName) => {
      return [
        ...acc,
        ...body.deletedEntities[entityName].map(key => ({entityName, key}))
      ]
    }, [])

    const remoteEvents = [{
      type: 'entity-delete-event',
      payload: {
        entities
      }
    }]

    yield put(externalEvents.fireExternalEvent('onSuccess', {
      message: textResources['client.delete.successfullyMessage'],
      remoteEvents
    }))
  } else {
    yield put(externalEvents.fireExternalEvent('onError', {message: textResources['client.delete.errorMessage']}))
  }
}

export function* onCancel() {
  yield put(externalEvents.fireExternalEvent('onCancel'))
}

export default function* mainSagas() {
  yield all([
    takeLatest(actions.LOAD_DIALOG_INFO, loadDialogInfo),
    takeLatest(actions.DO_DELETE, doDelete),
    takeLatest(actions.ON_CANCEL, onCancel)
  ])
}
<commit_msg>feat(delete): handle information exceptions in delete action<commit_after>import {takeLatest, all, call, put, select} from 'redux-saga/effects'
import {rest, externalEvents} from 'tocco-app-extensions'
import {selection as selectionUtil} from 'tocco-util'

import * as actions from './actions'
import {getDialogInfo, getEntitiesToDelete} from '../../utils/deleteRequestParser'

export const inputSelector = state => state.input
export const textResourceSelector = state => state.intl.messages
export const entitiesToDeleteSelector = state => state.del.entitiesToDelete

export function* getDeleteBodyFromSelection() {
  const {selection} = yield select(inputSelector)

  const entities = yield call(selectionUtil.getEntities, selection, rest.fetchEntities)

  return {
    entityModel: entities.entityName,
    keys: entities.keys
  }
}

export function* getDeleteEndpoint() {
  const {customDeleteEndpoint} = yield select(inputSelector)
  return customDeleteEndpoint || 'client/delete'
}

export function* loadDialogInfo() {
  const body = yield call(getDeleteBodyFromSelection)
  const deleteEndpoint = yield call(getDeleteEndpoint)

  const [deleteResponse, principal] = yield all([
    call(rest.requestSaga, `${deleteEndpoint}/dialog`, {method: 'POST', body}),
    call(rest.fetchPrincipal)
  ])

  const dialogInfo = yield call(getDialogInfo, deleteResponse.body, principal.currentBusinessUnit.id)
  yield put(actions.setDeleteDialogInfo(dialogInfo))

  const entitiesToDelete = yield call(getEntitiesToDelete, deleteResponse.body)
  yield put(actions.setEntitiesToDelete(entitiesToDelete))
}

export function* doDelete() {
  const entitiesToDelete = yield select(entitiesToDeleteSelector)

  yield put(actions.setDeletingInProgress(true))
  const body = {
    entityModel: entitiesToDelete.entityName,
    keys: entitiesToDelete.keys
  }

  const deleteEndpoint = yield call(getDeleteEndpoint)

  const response = yield call(rest.requestSaga, deleteEndpoint, {
    method: 'POST',
    body,
    acceptedStatusCodes: [409]
  })

  const textResources = yield select(textResourceSelector)
  if (response.ok) {
    const {body} = response

    const entities = Object.keys(body.deletedEntities).reduce((acc, entityName) => {
      return [
        ...acc,
        ...body.deletedEntities[entityName].map(key => ({entityName, key}))
      ]
    }, [])

    const remoteEvents = [{
      type: 'entity-delete-event',
      payload: {
        entities
      }
    }]

    yield put(externalEvents.fireExternalEvent('onSuccess', {
      message: textResources['client.delete.successfullyMessage'],
      remoteEvents
    }))
  } else {
    if (response.status === 409 && response.body.information) {
      yield put(externalEvents.fireExternalEvent('onError', {message: response.body.information}))
    } else {
      yield put(externalEvents.fireExternalEvent('onError', {message: textResources['client.delete.errorMessage']}))
    }
  }
}

export function* onCancel() {
  yield put(externalEvents.fireExternalEvent('onCancel'))
}

export default function* mainSagas() {
  yield all([
    takeLatest(actions.LOAD_DIALOG_INFO, loadDialogInfo),
    takeLatest(actions.DO_DELETE, doDelete),
    takeLatest(actions.ON_CANCEL, onCancel)
  ])
}
<|endoftext|>


<commit_before>import { Group } from '../Three';
import { PointIndicator } from './PointIndicator';

function StationMarkers ( color ) {

	Group.call( this );

	this.markers = new Map();
	this.markerColor = color;

	return this;

}

StationMarkers.prototype = Object.create( Group.prototype );

StationMarkers.prototype.mark = function ( node ) {

	const markers = this.markers;

	if ( markers.has( node ) ) return;

	const marker = new PointIndicator( this.markerColor );

	marker.position.copy( node.p );
	marker.station = node;

	this.add( marker );

	markers.set( node, marker );

};

StationMarkers.prototype.unmark = function ( node ) {

	const markers = this.markers;

	const marker = markers.get( node );

	if ( marker === undefined ) return;

	this.remove( marker );

	markers.delete( node );

};

StationMarkers.prototype.clear = function () {

	const self = this;

	this.markers.forEach( function ( marker ) {

		self.remove( marker );

	} );

	this.markers.clear();

};

StationMarkers.prototype.getStations = function () {

	const list = [];

	this.markers.forEach( function ( value, key ) { list.push( key ); } );

	return list;

};

StationMarkers.prototype.setVisibility = function ( visible ) {

	this.markers.forEach( function ( marker ) { marker.visible = visible; } );

};

export { StationMarkers };<commit_msg>use common Layers() object for stationMarkers<commit_after>import { Group } from '../Three';
import { PointIndicator } from './PointIndicator';

function StationMarkers ( color ) {

	Group.call( this );

	this.markers = new Map();
	this.markerColor = color;

	return this;

}

StationMarkers.prototype = Object.create( Group.prototype );

StationMarkers.prototype.mark = function ( node ) {

	const markers = this.markers;

	if ( markers.has( node ) ) return;

	const marker = new PointIndicator( this.markerColor );

	marker.position.copy( node.p );
	marker.station = node;
	marker.layers = this.layers;

	this.add( marker );

	markers.set( node, marker );

};

StationMarkers.prototype.unmark = function ( node ) {

	const markers = this.markers;

	const marker = markers.get( node );

	if ( marker === undefined ) return;

	this.remove( marker );

	markers.delete( node );

};

StationMarkers.prototype.clear = function () {

	const self = this;

	this.markers.forEach( function ( marker ) {

		self.remove( marker );

	} );

	this.markers.clear();

};

StationMarkers.prototype.getStations = function () {

	const list = [];

	this.markers.forEach( function ( value, key ) { list.push( key ); } );

	return list;

};

StationMarkers.prototype.setVisibility = function ( visible ) {

	this.markers.forEach( function ( marker ) { marker.visible = visible; } );

};

export { StationMarkers };<|endoftext|>


<commit_before>export default class BeatSignalInputElement extends HTMLElement {
  static get observedAttributes() {
    return ['beats', 'length', 'step', 'zoom'];
  }

  constructor() {
    super();

    this.attachShadow({ mode: 'open' }).innerHTML = `
      <style>
        :host {
          display: block;
          position: relative;
          height: 20px;
          width: 300px;
          background: lightgrey;
        }
        canvas {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
        }
      </style>
      <canvas></canvas>
    `;

    this.color = 'white';

    this._canvas = this.shadowRoot.querySelector('canvas');
    this._context = this._canvas.getContext('2d');

    this._value = NaN;
    this._width = 1;
    this._position = 0;
    this._scrollLeft = 0;
    this._zoom = 1;
    this._length = 1;
    this._step = undefined;
    this._decimals = 0;

    const drawBinded = this.draw.bind(this);
    let requestAnimationFrameID = -1;
    const self = this;
    class Beats extends Set {
      add(value) {
        const returnValue = super.add(value);
        cancelAnimationFrame(requestAnimationFrameID);
        requestAnimationFrameID = requestAnimationFrame(drawBinded);
        return returnValue;
      }
      addOnStep(value) {
        value = Math.round(value / self.step) * self.step;
        this.add(Number(value.toFixed(self._decimals)));
      }
      delete(value) {
        const returnValue = super.delete(value);
        cancelAnimationFrame(requestAnimationFrameID);
        requestAnimationFrameID = requestAnimationFrame(drawBinded);
        return returnValue;
      }
      clear() {
        super.clear();
        cancelAnimationFrame(requestAnimationFrameID);
        requestAnimationFrameID = requestAnimationFrame(drawBinded);
      }
    };
    this._beats = new Beats();

    let previousbeat = null;
    const preventContextMenu = (event) => event.preventDefault();
    const pointerDown = (event) => {
      this._canvas.setPointerCapture(event.pointerId);
      this._canvas.addEventListener('pointermove', pointerMove);
      this._canvas.addEventListener('pointerup', pointerUp);
      this._canvas.addEventListener('pointerout', pointerUp);
      window.addEventListener('contextmenu', preventContextMenu);
      pointerMove(event);
    };
    const pointerMove = (event) => {
      const newbeat = ((event.offsetX + this.scrollLeft) / this.scrollWidth) * this.length;
      previousbeat = previousbeat !== null ? previousbeat : newbeat;
      const startBeat = newbeat > previousbeat ? previousbeat : newbeat;
      const endBeat = newbeat > previousbeat ? newbeat : previousbeat;
      if (event.buttons === 1) {
        if (this.step) {
          for (let beat = startBeat; beat <= endBeat; beat += this.step) {
            this.beats.addOnStep(beat);
          }
        } else {
          this.beats.add(newbeat);
        }
      } else {
        for (const beat of this.beats) {
          if (beat >= startBeat && beat <= endBeat) {
            this.beats.delete(beat);
          }
        }
      }
      previousbeat = newbeat;
      this.draw();
      this.dispatchEvent(new Event('input'));
    };
    const pointerUp = (event) => {
      previousbeat = null;
      this._canvas.releasePointerCapture(event.pointerId);
      this._canvas.removeEventListener('pointermove', pointerMove);
      this._canvas.removeEventListener('pointerup', pointerUp);
      this._canvas.removeEventListener('pointerout', pointerUp);
      requestAnimationFrame(() => {
        window.removeEventListener('contextmenu', preventContextMenu);
      });
    };
    this._canvas.addEventListener('pointerdown', pointerDown);

    const resizeObserver = new ResizeObserver((entries) => {
      this._width = entries[0].contentRect.width;
      this._canvas.width = entries[0].contentRect.width * devicePixelRatio;
      this._canvas.height = entries[0].contentRect.height * devicePixelRatio;
      this.draw();
    });
    resizeObserver.observe(this);
  }

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case 'beats':
        const newBeats = new Function(`return ${newValue}`)();
        this.beats.clear();
        for (const beat of newBeats) {
          this.beats.add(beat);
        }
        break;
      case 'length':
      case 'step':
      case 'zoom':
        this[name] = Number(newValue);
        break;
    }
  }

  get beats() {
    return this._beats;
  }

  get scrollWidth() {
    return this._width * this.zoom;
  }

  get scrollLeft() {
    return this._scrollLeft;
  }

  set scrollLeft(value) {
    this._scrollLeft = Math.max(0, Math.min(this.scrollWidth - this._width, value));
    this.draw();
  }

  get zoom() {
    return this._zoom;
  }

  set zoom(value) {
    this._zoom = value;
    this.draw();
  }

  get step() {
    return this._step;
  }

  set step(value) {
    this._step = value;
    this._decimals = this.step % 1 ? String(this.step).split('.')[1].length : 0;
    this.draw();
  }

  get length() {
    return this._length;
  }

  set length(value) {
    this._length = value;
    this.draw();
  }

  get position() {
    return this._position;
  }

  set position(value) {
    if (value === this._position) {
      return;
    }
    const backward = value < this._position;
    const start = backward ? value : this._position;
    const end = backward ? this._position : value;
    let changed = false;
    let maxBeat = -Infinity;
    let minBeat = Infinity;
    for (const beat of this.beats) {
      if (beat >= start && beat <= end) {
        changed = true;
        maxBeat = Math.max(maxBeat, beat);
        minBeat = Math.min(minBeat, beat);
      }
    }
    this._position = value;
    if (changed) {
      this._value = backward ? minBeat : maxBeat;
      this.dispatchEvent(new Event('change', { bubbles: true }));
    }
    this.draw();
  }

  get value() {
    return this._value;
  }

  get name() {
    return this.getAttribute('name');
  }

  set name(value) {
    this.setAttribute('name', value);
  }

  draw() {
    this._context.resetTransform();
    this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);

    this._context.strokeStyle = 'rgba(0, 0, 0, .2)';
    if (this.step) {
      let stepWidth = this.step / this.length * this.zoom * this._canvas.width;
      while (stepWidth < 1) {
        stepWidth *= 2;
      }
      for (let position = 0; position < this._canvas.width; position += stepWidth) {
        const x = position - ((this.scrollLeft * devicePixelRatio) % stepWidth);
        this._context.beginPath();
        this._context.moveTo(x, 0);
        this._context.lineTo(x, this._canvas.height);
        this._context.stroke();
      }
    }

    this._context.strokeStyle = 'red';
    const position = (this.position / this.length) * this.zoom * this._canvas.width - this.scrollLeft * devicePixelRatio;
    this._context.beginPath();
    this._context.moveTo(position, 0);
    this._context.lineTo(position, this._canvas.height);
    this._context.stroke();

    this._context.fillStyle = this.color;
    this._context.strokeStyle = 'black';
    const size = Math.min(10, this._canvas.height * .5) * devicePixelRatio;
    for (const beat of this.beats) {
      this._context.resetTransform();
      const x = (beat / this.length) * this._canvas.width * this.zoom - this.scrollLeft * devicePixelRatio;
      this._context.translate(x, this._canvas.height * .5);
      this._context.rotate(Math.PI * .25);
      this._context.fillRect(-size * .5, -size * .5, size, size);
      this._context.strokeRect(-size * .5, -size * .5, size, size);
    }
  }
}

if (!customElements.get('damo-input-signal-beat')) {
  customElements.define('damo-input-signal-beat', class extends BeatSignalInputElement { });
}
<commit_msg>add left click remove to beats<commit_after>export default class BeatSignalInputElement extends HTMLElement {
  static get observedAttributes() {
    return ['beats', 'length', 'step', 'zoom'];
  }

  constructor() {
    super();

    this.attachShadow({ mode: 'open' }).innerHTML = `
      <style>
        :host {
          display: block;
          position: relative;
          height: 20px;
          width: 300px;
          background: lightgrey;
        }
        canvas {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
        }
      </style>
      <canvas></canvas>
    `;

    this.color = 'white';

    this._canvas = this.shadowRoot.querySelector('canvas');
    this._context = this._canvas.getContext('2d');

    this._value = NaN;
    this._width = 1;
    this._position = 0;
    this._scrollLeft = 0;
    this._zoom = 1;
    this._length = 1;
    this._step = 0;
    this._decimals = 0;
    this._loopLength = 0;

    const drawBinded = this.draw.bind(this);
    let requestAnimationFrameID = -1;
    class Beats extends Set {
      add(value) {
        const returnValue = super.add(value);
        cancelAnimationFrame(requestAnimationFrameID);
        requestAnimationFrameID = requestAnimationFrame(drawBinded);
        return returnValue;
      }
      delete(value) {
        const returnValue = super.delete(value);
        cancelAnimationFrame(requestAnimationFrameID);
        requestAnimationFrameID = requestAnimationFrame(drawBinded);
        return returnValue;
      }
      clear() {
        super.clear();
        cancelAnimationFrame(requestAnimationFrameID);
        requestAnimationFrameID = requestAnimationFrame(drawBinded);
      }
    };
    this._beats = new Beats();

    let previousbeat = null;
    let mode = '';
    const preventContextMenu = (event) => event.preventDefault();
    const pointerDown = (event) => {
      this._canvas.setPointerCapture(event.pointerId);
      this._canvas.addEventListener('pointermove', pointerMove);
      this._canvas.addEventListener('pointerup', pointerUp);
      this._canvas.addEventListener('pointerout', pointerUp);
      window.addEventListener('contextmenu', preventContextMenu);
      pointerMove(event);
    };
    const pointerMove = (event) => {
      const newbeat = ((event.offsetX + this.scrollLeft) / this.scrollWidth) * this.length;
      previousbeat = previousbeat !== null ? previousbeat : newbeat;
      const startBeat = newbeat > previousbeat ? previousbeat : newbeat;
      const endBeat = newbeat > previousbeat ? newbeat : previousbeat;
      if (event.buttons === 1) {
        if (this.step) {
          for (let beat = startBeat; beat <= endBeat; beat += this.step) {
            beat = Math.round(beat / this.step) * this.step;
            beat = Number(beat.toFixed(this._decimals));
            if ((!mode && this.beats.has(beat)) || mode === 'delete') {
              mode = 'delete';
              this.beats.delete(beat);
            } else if (!mode || mode === 'add') {
              mode = 'add';
              this.beats.add(beat);
            }
          }
        } else {
          this.beats.add(newbeat);
        }
      } else {
        for (const beat of this.beats) {
          if (beat >= startBeat && beat <= endBeat) {
            this.beats.delete(beat);
          }
        }
      }
      previousbeat = newbeat;
      this.draw();
      this.dispatchEvent(new Event('input'));
    };
    const pointerUp = (event) => {
      mode = '';
      previousbeat = null;
      this._canvas.releasePointerCapture(event.pointerId);
      this._canvas.removeEventListener('pointermove', pointerMove);
      this._canvas.removeEventListener('pointerup', pointerUp);
      this._canvas.removeEventListener('pointerout', pointerUp);
      requestAnimationFrame(() => {
        window.removeEventListener('contextmenu', preventContextMenu);
      });
    };
    this._canvas.addEventListener('pointerdown', pointerDown);

    const resizeObserver = new ResizeObserver((entries) => {
      this._width = entries[0].contentRect.width;
      this._canvas.width = entries[0].contentRect.width * devicePixelRatio;
      this._canvas.height = entries[0].contentRect.height * devicePixelRatio;
      this.draw();
    });
    resizeObserver.observe(this);
  }

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case 'beats':
        const newBeats = new Function(`return ${newValue}`)();
        this.beats.clear();
        for (const beat of newBeats) {
          this.beats.add(beat);
        }
        break;
      case 'length':
      case 'step':
      case 'zoom':
        this[name] = Number(newValue);
        break;
    }
  }

  get beats() {
    return this._beats;
  }

  get scrollWidth() {
    return this._width * this.zoom;
  }

  get scrollLeft() {
    return this._scrollLeft;
  }

  set scrollLeft(value) {
    this._scrollLeft = Math.max(0, Math.min(this.scrollWidth - this._width, value));
    this.draw();
  }

  get zoom() {
    return this._zoom;
  }

  set zoom(value) {
    this._zoom = value;
    this.draw();
  }

  get step() {
    return this._step;
  }

  set step(value) {
    this._step = value;
    this._decimals = this.step % 1 ? String(this.step).split('.')[1].length : 0;
    this.draw();
  }

  get length() {
    return this._length;
  }

  set length(value) {
    this._length = value;
    this.draw();
  }

  get position() {
    return this._position;
  }

  set position(value) {
    if (value === this._position) {
      return;
    }
    const backward = value < this._position;
    const start = backward ? value : this._position;
    const end = backward ? this._position : value;
    let changed = false;
    let maxBeat = -Infinity;
    let minBeat = Infinity;
    for (const beat of this.beats) {
      if (beat >= start && beat <= end) {
        changed = true;
        maxBeat = Math.max(maxBeat, beat);
        minBeat = Math.min(minBeat, beat);
      }
    }
    this._position = value;
    if (changed) {
      this._value = backward ? minBeat : maxBeat;
      this.dispatchEvent(new Event('change', { bubbles: true }));
    }
    this.draw();
  }

  get value() {
    return this._value;
  }

  get name() {
    return this.getAttribute('name');
  }

  set name(value) {
    this.setAttribute('name', value);
  }

  draw() {
    this._context.resetTransform();
    this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);

    this._context.strokeStyle = 'rgba(0, 0, 0, .2)';
    if (this.step) {
      let stepWidth = this.step / this.length * this.zoom * this._canvas.width;
      while (stepWidth < 1) {
        stepWidth *= 2;
      }
      for (let position = 0; position < this._canvas.width; position += stepWidth) {
        const x = position - ((this.scrollLeft * devicePixelRatio) % stepWidth);
        this._context.beginPath();
        this._context.moveTo(x, 0);
        this._context.lineTo(x, this._canvas.height);
        this._context.stroke();
      }
    }

    this._context.strokeStyle = 'red';
    const position = (this.position / this.length) * this.zoom * this._canvas.width - this.scrollLeft * devicePixelRatio;
    this._context.beginPath();
    this._context.moveTo(position, 0);
    this._context.lineTo(position, this._canvas.height);
    this._context.stroke();

    this._context.fillStyle = this.color;
    this._context.strokeStyle = 'black';
    const size = Math.min(10, this._canvas.height * .5) * devicePixelRatio;
    for (const beat of this.beats) {
      this._context.resetTransform();
      const x = (beat / this.length) * this._canvas.width * this.zoom - this.scrollLeft * devicePixelRatio;
      this._context.translate(x, this._canvas.height * .5);
      this._context.rotate(Math.PI * .25);
      this._context.fillRect(-size * .5, -size * .5, size, size);
      this._context.strokeRect(-size * .5, -size * .5, size, size);
    }
  }
}

if (!customElements.get('damo-input-signal-beat')) {
  customElements.define('damo-input-signal-beat', class extends BeatSignalInputElement { });
}
<|endoftext|>


<commit_before>import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'
import slugifyWithPrefix from '../src/slugifyWithPrefix'

const pickFirst = (obj, keys) => {
  if (!obj || typeof obj !== 'object') {
    return obj
  }
  const found = keys.find(key => (key in obj))
  return obj[found]
}

const LANGUAGE_PRIORITY = ['no', 'nn', 'en']

export default {
  name: 'blogpost',
  type: 'object',
  title: 'Blogpost',
  preview: {
    select: {
      title: 'title',
      createdAt: '_createdAt',
      lead: 'lead',
      imageUrl: 'mainImage.asset.url',
      author: 'authorRef.name'
    },
    prepare(value) {
      const timeSince = distanceInWordsToNow(value.createdAt, {addSuffix: true})
      return Object.assign({}, value, {
        title: value.title ? pickFirst(value.title, LANGUAGE_PRIORITY) : '',
        subtitle: value.author ? `By ${value.author}, ${timeSince}` : timeSince,
        description: value.lead
      })
    }
  },
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'localeString'
    },
    {
      name: 'readonlyField',
      title: 'A read only string',
      type: 'string',
      readOnly: true
    },
    {
      name: 'slug',
      title: 'Slug',
      description: 'The unique identifier for the blogpost in links and urls',
      type: 'slug',
      required: true,
      options: {
        source: document => pickFirst(document.title, LANGUAGE_PRIORITY),
        maxLength: 64,
        slugifyFn: slugifyWithPrefix('my-prefix')
      }
    },
    {
      name: 'myImage',
      title: 'My Image',
      type: 'myImage',
      required: true,
      readOnly: true
    },
    {
      name: 'publishAt',
      title: 'Publish at',
      type: 'date',
      description: 'Blogpost goes live at this date/time',
      options: {
        dateFormat: 'YYYY-MM-DD',
        timeFormat: 'HH:mm',
        timeStep: 60,
        calendarTodayLabel: 'Today',
        inputUtc: false,
        inputDate: true,
        inputTime: true
      }
    },
    {
      name: 'file',
      title: 'Plain file upload',
      type: 'file'
    },
    {
      name: 'video',
      title: 'A video embed',
      type: 'videoEmbed'
    },
    {
      name: 'myCode',
      title: 'Code editor (default)',
      description: 'Choose syntax on input.',
      type: 'code'
    },
    {
      name: 'simpleCode',
      title: 'Code editor simple (markdown)',
      description: 'set syntax in schema',
      type: 'code',
      options: {
        language: 'markdown'
      }
    },
    {
      name: 'mainImage',
      title: 'Main image',
      type: 'image',
      options: {
        hotspot: true
      },
      fields: [
        {
          name: 'caption',
          type: 'string',
          title: 'Caption'
        }
      ]
    },
    {
      name: 'rating',
      title: 'Rating',
      type: 'number',
      options: {
        range: {min: 0, max: 10, step: 0.2}
      },
      required: true
    },
    {
      name: 'selectMultipleStrings',
      title: 'Select multiple strings',
      type: 'array',
      options: {
        direction: 'vertical',
        list: [
          {title: 'Red', value: 'red'},
          {title: 'Green', value: 'green'},
          {title: 'Blue', value: 'blue'},
          {title: 'Black', value: 'black'},
        ]
      },
      of: [{
        type: 'string'
      }]
    },
    {
      name: 'pngImage',
      title: 'PNG image',
      type: 'image',
      options: {
        hotspot: true,
        accept: 'image/png'
      }
    },
    {
      name: 'tags',
      title: 'tags',
      type: 'array',
      options: {
        layout: 'tags'
      },
      of: [
        {type: 'string'}
      ]
    },
    {
      name: 'arrayOfStrings',
      title: 'Array of strings (sortable)',
      type: 'array',
      options: {
        sortable: true
      },
      of: [
        {type: 'string'}
      ]
    },

    {
      name: 'arrayOfStringsNonSortable',
      title: 'Array of strings (not sortable)',
      type: 'array',
      options: {
        sortable: false
      },
      of: [
        {type: 'string'}
      ]
    },

    {
      name: 'priority',
      title: 'Priority',
      type: 'number'
    },
    {
      name: 'checked',
      title: 'Checked',
      type: 'boolean'
    },
    {
      name: 'someObject',
      title: 'An object',
      type: 'object',
      fields: [
        {
          name: 'first',
          type: 'string',
          title: 'First field'
        },
        {
          name: 'second',
          type: 'string',
          title: 'Second field'
        }
      ]
    },
    {
      name: 'body',
      type: 'array',
      title: 'Blocks',
      of: [
        {
          title: 'Block',
          type: 'block'
        }
      ]
    },
    {
      name: 'imageGallery',
      title: 'Image gallery',
      type: 'array',
      options: {
        layout: 'grid',
        sortable: true
      },
      of: [
        {
          title: 'Image',
          type: 'image',
          preview: {
            select: {
              imageUrl: 'asset.url',
              title: 'caption'
            }
          },
          fields: [
            {
              name: 'caption',
              type: 'string',
              title: 'Caption',
              options: {
                isHighlighted: true
              }
            }
          ]
        }
      ]
    },
    {
      name: 'select',
      title: 'Select string',
      type: 'string',
      options: {
        list: [
          {
            title: 'One (1)',
            value: 'one'
          },
          {
            title: 'Two (2)',
            value: 'two'
          },
          {
            title: 'Three (3)',
            value: 'three'
          }
        ]
      }
    },
    {
      name: 'selectObjectOfString',
      title: 'Select string in object',
      description: '',
      type: 'string',
      options: {
        list: ['one', 'two', 'three']
      }
    },
    {
      name: 'radioSelect',
      title: 'Select (layout: radio)',
      type: 'string',
      options: {
        layout: 'radio',
        list: [
          {
            title: 'One (1)',
            value: 'one'
          },
          {
            title: 'Two (2)',
            value: 'two'
          },
          {
            title: 'Three (3)',
            value: 'three'
          }
        ]
      }
    },
    {
      name: 'lead',
      title: 'Lead',
      type: 'text',
      format: 'html',
      required: true
    },
    {
      name: 'email',
      title: 'Email',
      type: 'email'
    },
    {
      name: 'location',
      title: 'Location',
      type: 'geopoint'
    },
    {
      name: 'content',
      title: 'Content tags',
      type: 'array',
      of: [
        {
          title: 'String',
          type: 'string'
        }
      ]
    },
    {
      name: 'arrayOfReadOnly',
      title: 'ReadOnly test',
      description: 'Test readOnly on members of arrays',
      type: 'array',
      readOnly: true, // toggle comment to test readOnly array values
      of: [
        {
          title: 'Stuff',
          type: 'object',
          name: 'stuff',
          fields: [
            {name: 'something', type: 'string'},
            {name: 'otherThing', type: 'string'}
          ]
        },
        {
          title: 'Read only stuff',
          type: 'object',
          name: 'readOnlyStuff',
          readOnly: true,
          fields: [
            {name: 'something', type: 'string'},
            {name: 'otherThing', type: 'string'}
          ]
        }
      ]
    },
    {
      name: 'firstAuthor',
      title: 'Author',
      type: 'reference',
      to: {type: 'author'},
      required: true
    },
    {
      name: 'coauthors',
      title: 'Co authors',
      type: 'array',
      of: [{
        type: 'reference',
        title: 'Reference to co-author',
        to: {
          type: 'author'
        }
      }],
      required: true
    },
    {
      name: 'extraAuthors',
      title: 'Additional authors',
      description: 'Note: This is an inline array of authors',
      type: 'array',
      options: {
        editModal: 'fold'
      },
      of: [
        {
          title: 'Additional author reference',
          type: 'author'
        }
      ],
      required: true
    },
    {
      name: 'extraSuperAuthors',
      title: 'Additional super authors',
      description: 'Note: This is an inline array of authors',
      type: 'array',
      options: {
        editModal: 'fullscreen'
      },
      of: [
        {
          title: 'Additional super author reference',
          type: 'author'
        }
      ],
      required: true
    },
    {
      name: 'superAuthors',
      title: 'Super authors',
      type: 'array',
      options: {
        editModal: 'fullscreen'
      },
      of: [{
        title: 'Reference to super author',
        type: 'reference',
        to: {
          type: 'author'
        }
      }],
      required: true
    },
  ]
}
<commit_msg>[example-studio] Fix wrong language priority<commit_after>import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'
import slugifyWithPrefix from '../src/slugifyWithPrefix'

const pickFirst = (obj, keys) => {
  if (!obj || typeof obj !== 'object') {
    return obj
  }
  const found = keys.find(key => (key in obj))
  return obj[found]
}

const LANGUAGE_PRIORITY = ['nb', 'nn', 'en']

export default {
  name: 'blogpost',
  type: 'object',
  title: 'Blogpost',
  preview: {
    select: {
      title: 'title',
      createdAt: '_createdAt',
      lead: 'lead',
      imageUrl: 'mainImage.asset.url',
      author: 'authorRef.name'
    },
    prepare(value) {
      const timeSince = distanceInWordsToNow(value.createdAt, {addSuffix: true})
      return Object.assign({}, value, {
        title: value.title ? pickFirst(value.title, LANGUAGE_PRIORITY) : '',
        subtitle: value.author ? `By ${value.author}, ${timeSince}` : timeSince,
        description: value.lead
      })
    }
  },
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'localeString'
    },
    {
      name: 'readonlyField',
      title: 'A read only string',
      type: 'string',
      readOnly: true
    },
    {
      name: 'slug',
      title: 'Slug',
      description: 'The unique identifier for the blogpost in links and urls',
      type: 'slug',
      required: true,
      options: {
        source: document => pickFirst(document.title, LANGUAGE_PRIORITY),
        maxLength: 64,
        slugifyFn: slugifyWithPrefix('my-prefix')
      }
    },
    {
      name: 'myImage',
      title: 'My Image',
      type: 'myImage',
      required: true,
      readOnly: true
    },
    {
      name: 'publishAt',
      title: 'Publish at',
      type: 'date',
      description: 'Blogpost goes live at this date/time',
      options: {
        dateFormat: 'YYYY-MM-DD',
        timeFormat: 'HH:mm',
        timeStep: 60,
        calendarTodayLabel: 'Today',
        inputUtc: false,
        inputDate: true,
        inputTime: true
      }
    },
    {
      name: 'file',
      title: 'Plain file upload',
      type: 'file'
    },
    {
      name: 'video',
      title: 'A video embed',
      type: 'videoEmbed'
    },
    {
      name: 'myCode',
      title: 'Code editor (default)',
      description: 'Choose syntax on input.',
      type: 'code'
    },
    {
      name: 'simpleCode',
      title: 'Code editor simple (markdown)',
      description: 'set syntax in schema',
      type: 'code',
      options: {
        language: 'markdown'
      }
    },
    {
      name: 'mainImage',
      title: 'Main image',
      type: 'image',
      options: {
        hotspot: true
      },
      fields: [
        {
          name: 'caption',
          type: 'string',
          title: 'Caption'
        }
      ]
    },
    {
      name: 'rating',
      title: 'Rating',
      type: 'number',
      options: {
        range: {min: 0, max: 10, step: 0.2}
      },
      required: true
    },
    {
      name: 'selectMultipleStrings',
      title: 'Select multiple strings',
      type: 'array',
      options: {
        direction: 'vertical',
        list: [
          {title: 'Red', value: 'red'},
          {title: 'Green', value: 'green'},
          {title: 'Blue', value: 'blue'},
          {title: 'Black', value: 'black'},
        ]
      },
      of: [{
        type: 'string'
      }]
    },
    {
      name: 'pngImage',
      title: 'PNG image',
      type: 'image',
      options: {
        hotspot: true,
        accept: 'image/png'
      }
    },
    {
      name: 'tags',
      title: 'tags',
      type: 'array',
      options: {
        layout: 'tags'
      },
      of: [
        {type: 'string'}
      ]
    },
    {
      name: 'arrayOfStrings',
      title: 'Array of strings (sortable)',
      type: 'array',
      options: {
        sortable: true
      },
      of: [
        {type: 'string'}
      ]
    },

    {
      name: 'arrayOfStringsNonSortable',
      title: 'Array of strings (not sortable)',
      type: 'array',
      options: {
        sortable: false
      },
      of: [
        {type: 'string'}
      ]
    },

    {
      name: 'priority',
      title: 'Priority',
      type: 'number'
    },
    {
      name: 'checked',
      title: 'Checked',
      type: 'boolean'
    },
    {
      name: 'someObject',
      title: 'An object',
      type: 'object',
      fields: [
        {
          name: 'first',
          type: 'string',
          title: 'First field'
        },
        {
          name: 'second',
          type: 'string',
          title: 'Second field'
        }
      ]
    },
    {
      name: 'body',
      type: 'array',
      title: 'Blocks',
      of: [
        {
          title: 'Block',
          type: 'block'
        }
      ]
    },
    {
      name: 'imageGallery',
      title: 'Image gallery',
      type: 'array',
      options: {
        layout: 'grid',
        sortable: true
      },
      of: [
        {
          title: 'Image',
          type: 'image',
          preview: {
            select: {
              imageUrl: 'asset.url',
              title: 'caption'
            }
          },
          fields: [
            {
              name: 'caption',
              type: 'string',
              title: 'Caption',
              options: {
                isHighlighted: true
              }
            }
          ]
        }
      ]
    },
    {
      name: 'select',
      title: 'Select string',
      type: 'string',
      options: {
        list: [
          {
            title: 'One (1)',
            value: 'one'
          },
          {
            title: 'Two (2)',
            value: 'two'
          },
          {
            title: 'Three (3)',
            value: 'three'
          }
        ]
      }
    },
    {
      name: 'selectObjectOfString',
      title: 'Select string in object',
      description: '',
      type: 'string',
      options: {
        list: ['one', 'two', 'three']
      }
    },
    {
      name: 'radioSelect',
      title: 'Select (layout: radio)',
      type: 'string',
      options: {
        layout: 'radio',
        list: [
          {
            title: 'One (1)',
            value: 'one'
          },
          {
            title: 'Two (2)',
            value: 'two'
          },
          {
            title: 'Three (3)',
            value: 'three'
          }
        ]
      }
    },
    {
      name: 'lead',
      title: 'Lead',
      type: 'text',
      format: 'html',
      required: true
    },
    {
      name: 'email',
      title: 'Email',
      type: 'email'
    },
    {
      name: 'location',
      title: 'Location',
      type: 'geopoint'
    },
    {
      name: 'content',
      title: 'Content tags',
      type: 'array',
      of: [
        {
          title: 'String',
          type: 'string'
        }
      ]
    },
    {
      name: 'arrayOfReadOnly',
      title: 'ReadOnly test',
      description: 'Test readOnly on members of arrays',
      type: 'array',
      readOnly: true, // toggle comment to test readOnly array values
      of: [
        {
          title: 'Stuff',
          type: 'object',
          name: 'stuff',
          fields: [
            {name: 'something', type: 'string'},
            {name: 'otherThing', type: 'string'}
          ]
        },
        {
          title: 'Read only stuff',
          type: 'object',
          name: 'readOnlyStuff',
          readOnly: true,
          fields: [
            {name: 'something', type: 'string'},
            {name: 'otherThing', type: 'string'}
          ]
        }
      ]
    },
    {
      name: 'firstAuthor',
      title: 'Author',
      type: 'reference',
      to: {type: 'author'},
      required: true
    },
    {
      name: 'coauthors',
      title: 'Co authors',
      type: 'array',
      of: [{
        type: 'reference',
        title: 'Reference to co-author',
        to: {
          type: 'author'
        }
      }],
      required: true
    },
    {
      name: 'extraAuthors',
      title: 'Additional authors',
      description: 'Note: This is an inline array of authors',
      type: 'array',
      options: {
        editModal: 'fold'
      },
      of: [
        {
          title: 'Additional author reference',
          type: 'author'
        }
      ],
      required: true
    },
    {
      name: 'extraSuperAuthors',
      title: 'Additional super authors',
      description: 'Note: This is an inline array of authors',
      type: 'array',
      options: {
        editModal: 'fullscreen'
      },
      of: [
        {
          title: 'Additional super author reference',
          type: 'author'
        }
      ],
      required: true
    },
    {
      name: 'superAuthors',
      title: 'Super authors',
      type: 'array',
      options: {
        editModal: 'fullscreen'
      },
      of: [{
        title: 'Reference to super author',
        type: 'reference',
        to: {
          type: 'author'
        }
      }],
      required: true
    },
  ]
}
<|endoftext|>


<commit_before>import FieldController from '../../Controller';

export default class TextController extends FieldController {
  getFilterGraphQL = ({ type, value }) => {
    const key = type === 'is' ? `${this.path}` : `${this.path}_${type}`;
    return `${key}: "${value}"`;
  };
  getFilterLabel = ({ label }) => {
    return `${this.label} ${label.toLowerCase()}`;
  };
  formatFilter = ({ label, value }) => {
    return `${this.getFilterLabel({ label })}: "${value}"`;
  };
  getValue = data => {
    const value = data[this.config.path];
    console.log(typeof value, value);
    // Make the value a string to prevent loss of accuracy and precision.
    if (typeof value === 'string') {
      return value;
    } else if (typeof value === 'number') {
      return String(value);
    } else {
      // If it is neither string nor number then it must be empty.
      return '';
    }

  };
  getFilterTypes = () => [
    {
      type: 'is',
      label: 'Is exactly',
      getInitialValue: () => '',
    },
    {
      type: 'not',
      label: 'Is not exactly',
      getInitialValue: () => '',
    },
    {
      type: 'gt',
      label: 'Is greater than',
      getInitialValue: () => '',
    },
    {
      type: 'lt',
      label: 'Is less than',
      getInitialValue: () => '',
    },
    {
      type: 'gte',
      label: 'Is greater than or equal to',
      getInitialValue: () => '',
    },
    {
      type: 'lte',
      label: 'Is less than or equal to',
      getInitialValue: () => '',
    },
  ];
}
<commit_msg>Removing console.log<commit_after>import FieldController from '../../Controller';

export default class TextController extends FieldController {
  getFilterGraphQL = ({ type, value }) => {
    const key = type === 'is' ? `${this.path}` : `${this.path}_${type}`;
    return `${key}: "${value}"`;
  };
  getFilterLabel = ({ label }) => {
    return `${this.label} ${label.toLowerCase()}`;
  };
  formatFilter = ({ label, value }) => {
    return `${this.getFilterLabel({ label })}: "${value}"`;
  };
  getValue = data => {
    const value = data[this.config.path];
    // Make the value a string to prevent loss of accuracy and precision.
    if (typeof value === 'string') {
      return value;
    } else if (typeof value === 'number') {
      return String(value);
    } else {
      // If it is neither string nor number then it must be empty.
      return '';
    }

  };
  getFilterTypes = () => [
    {
      type: 'is',
      label: 'Is exactly',
      getInitialValue: () => '',
    },
    {
      type: 'not',
      label: 'Is not exactly',
      getInitialValue: () => '',
    },
    {
      type: 'gt',
      label: 'Is greater than',
      getInitialValue: () => '',
    },
    {
      type: 'lt',
      label: 'Is less than',
      getInitialValue: () => '',
    },
    {
      type: 'gte',
      label: 'Is greater than or equal to',
      getInitialValue: () => '',
    },
    {
      type: 'lte',
      label: 'Is less than or equal to',
      getInitialValue: () => '',
    },
  ];
}
<|endoftext|>


<commit_before>module.directive('portrait', [
	'pageService', function (page) {
		return {
			restrict: 'E',
			templateUrl: 'portrait.html',
			replace: true,
			link: {
				pre: function ($scope, $element, $attrs) {
					$scope.portraitExtra = page.$parse($attrs.extra)($scope);
				},
				post: function ($scope, $element, $attrs) {
					$element.find('.portrait').addClass($attrs.class);
					$element.attr('class', '');
				}
			}
		};
	}
]);
<commit_msg>portrait image disappearance bug fixed<commit_after>module.directive('portrait', [
	'pageService', function (page) {
		return {
			restrict: 'E',
			templateUrl: 'portrait.html',
			link: {
				pre: function ($scope, $element, $attrs) {
					$scope.portraitExtra = page.$parse($attrs.extra)($scope);
				},
				post: function ($scope, $element, $attrs) {
					$element.find('.portrait').addClass($attrs.class);
					$element.attr('class', '');
				}
			}
		};
	}
]);
<|endoftext|>


<commit_before>define([
  'backbone',
  'extensions/models/model'
], function (Backbone, Model) {

  var Controller = function (options) {
    options = options || {};
    _.extend(this, options);
    this.initialize.call(this, options);
  };

  _.extend(Controller.prototype, Backbone.Events, {

    initialize: function () {},

    viewOptions: function () {},
    collectionOptions: function () {},
    collectionData: function () { return []; },

    renderView: function (options) {
      options = _.extend({}, this.viewOptions(), options);

      if (!this.view) {
        this.view = new this.viewClass(options);
      }

      var view = this.view;
      view.render();

      this.html = view.html || view.$el[0].outerHTML;
      this.ready();
    },

    ready: function () {
      setTimeout(_.bind(function () {
        this.trigger('ready');
      }, this), 0);
    },

    render: function (options) {
      options = options || {};

      if (this.collectionClass && !this.collection) {
        this.collection = new this.collectionClass(this.collectionData(), _.extend({
          'data-type': this.model.get('data-type'),
          'data-group': this.model.get('data-group')
        }, this.collectionOptions()));
      }

      var renderViewOptions = _.merge({
        collection: this.collection,
        model: this.model
      }, options);

      if (this.collection && this.collection.isEmpty()) {
        this.listenToOnce(this.collection, 'sync reset error', function () {
          this.renderView(renderViewOptions);
        }, this);

        this.collection.fetch();
      } else {
        this.renderView(renderViewOptions);
      }
    },

    renderModules: function (modules, parentModel, moduleOptions, renderOptions, callback) {
      var remaining = modules.length;

      callback = _.isFunction(callback) ? callback : function () {};
      if (remaining === 0) {
        callback();
        return;
      }

      var loaded = _.bind(function () {
        remaining--;
        if (remaining === 0) {
          this.trigger('loaded');
          callback();
        }
      }, this);

      return _.map(modules, function (definition) {

        if (!definition.controller) {
          // some modules don't have client-side controllers
          loaded();
          return;
        }

        var model = new Model(definition);
        model.set('parent', parentModel);

        var options = _.isFunction(moduleOptions) ? moduleOptions(model) : moduleOptions;

        var module = new definition.controller(_.merge({ model: model }, options));

        module.once('ready', loaded);

        module.render(_.isFunction(renderOptions) ? renderOptions(model) : renderOptions);

        return module;
      }, this);
    }
  });

  Controller.extend = Backbone.Model.extend;

  return Controller;

});
<commit_msg>Only listen to reset event on collections<commit_after>define([
  'backbone',
  'extensions/models/model'
], function (Backbone, Model) {

  var Controller = function (options) {
    options = options || {};
    _.extend(this, options);
    this.initialize.call(this, options);
  };

  _.extend(Controller.prototype, Backbone.Events, {

    initialize: function () {},

    viewOptions: function () {},
    collectionOptions: function () {},
    collectionData: function () { return []; },

    renderView: function (options) {
      options = _.extend({}, this.viewOptions(), options);

      if (!this.view) {
        this.view = new this.viewClass(options);
      }

      var view = this.view;
      view.render();

      this.html = view.html || view.$el[0].outerHTML;
      this.ready();
    },

    ready: function () {
      setTimeout(_.bind(function () {
        this.trigger('ready');
      }, this), 0);
    },

    render: function (options) {
      options = options || {};

      if (this.collectionClass && !this.collection) {
        this.collection = new this.collectionClass(this.collectionData(), _.extend({
          'data-type': this.model.get('data-type'),
          'data-group': this.model.get('data-group')
        }, this.collectionOptions()));
      }

      var renderViewOptions = _.merge({
        collection: this.collection,
        model: this.model
      }, options);

      if (this.collection && this.collection.isEmpty()) {
        this.listenToOnce(this.collection, 'reset error', function () {
          this.renderView(renderViewOptions);
        }, this);

        this.collection.fetch({ reset: true });
      } else {
        this.renderView(renderViewOptions);
      }
    },

    renderModules: function (modules, parentModel, moduleOptions, renderOptions, callback) {
      var remaining = modules.length;

      callback = _.isFunction(callback) ? callback : function () {};
      if (remaining === 0) {
        callback();
        return;
      }

      var loaded = _.bind(function () {
        remaining--;
        if (remaining === 0) {
          this.trigger('loaded');
          callback();
        }
      }, this);

      return _.map(modules, function (definition) {

        if (!definition.controller) {
          // some modules don't have client-side controllers
          loaded();
          return;
        }

        var model = new Model(definition);
        model.set('parent', parentModel);

        var options = _.isFunction(moduleOptions) ? moduleOptions(model) : moduleOptions;

        var module = new definition.controller(_.merge({ model: model }, options));

        module.once('ready', loaded);

        module.render(_.isFunction(renderOptions) ? renderOptions(model) : renderOptions);

        return module;
      }, this);
    }
  });

  Controller.extend = Backbone.Model.extend;

  return Controller;

});
<|endoftext|>