function update()

in modules/ui/tools/quick_presets.js [58:310]


    function update() {

        tool.willUpdate();

        var items = tool.itemsToDraw();

        var modes = items.map(function(d) {
            var presetName = d.preset.name().split(' – ')[0];
            var markerClass = 'add-preset add-preset-' + presetName.replace(/\s+/g, '_')
                + ' add-' + d.source; // replace spaces with underscores to avoid css interpretation
            if (d.preset.isFallback()) {
                markerClass += ' add-generic-preset';
            }

            var geometry = d.preset.defaultAddGeometry(context);

            var protoMode = Object.assign({}, d);  // shallow copy
            protoMode.geometry = geometry;
            protoMode.button = markerClass;
            protoMode.title = presetName;

            if (geometry) {
                protoMode.description = t('modes.add_preset.title', { feature: '<strong>' + presetName + '</strong>' });
            } else {
                var hiddenPresetFeatures = context.features().isHiddenPreset(d.preset, d.preset.geometry[0]);
                var isAutoHidden = context.features().autoHidden(hiddenPresetFeatures.key);
                var tooltipIdSuffix = isAutoHidden ? 'zoom' : 'manual';
                protoMode.description = t('inspector.hidden_preset.' + tooltipIdSuffix, { features: hiddenPresetFeatures.title });
                protoMode.key = null;
            }

            var mode;
            switch (geometry) {
                case 'point':
                case 'vertex':
                    mode = modeAddPoint(context, protoMode);
                    break;
                case 'line':
                    mode = modeAddLine(context, protoMode);
                    break;
                case 'area':
                    mode = modeAddArea(context, protoMode);
            }

            if (protoMode.key && mode) {
                context.keybinding().off(protoMode.key);
                context.keybinding().on(protoMode.key, function() {
                     toggleMode(mode);
                });
            }

            return mode || protoMode;
        });

        var buttons = selection.selectAll('button.add-button')
            .data(modes, function(d) { return d.button; })
            .order();

        // exit
        buttons.exit()
            .remove();

        // enter
        var buttonsEnter = buttons.enter()
            .append('button')
            .attr('tabindex', -1)
            .attr('class', function(d) {
                return d.button + ' add-button bar-button';
            })
            .attr('id', function(d) {
                return d.button.replace(/ /g, '-');
            })
            .on('click.mode-buttons', function(d) {
                if (d3_select(this).classed('disabled')) return;
                toggleMode(d);
            })
            .call(tooltip()
                .placement('bottom')
                .html(true)
                .title(function(d) {
                    return d.key ? uiTooltipHtml(d.description, d.key) : d.description;
                })
                .scrollContainer(d3_select('#bar'))
            );

        buttonsEnter
            .each(function(d) {

                var geometry = d.preset.geometry[0];
                if (d.preset.geometry.length !== 1 ||
                    (geometry !== 'area' && geometry !== 'line' && geometry !== 'vertex')) {
                    geometry = null;
                }

                d3_select(this)
                    .call(uiPresetIcon(context)
                        .geometry(geometry)
                        .preset(d.preset)
                        .sizeClass('small')
                        .pointMarker(true)
                    );
            });

        var scrollNode = d3_select('#bar').node();
        var dragOrigin, dragMoved, targetData;
        var ltr = textDirection === 'ltr',
            rtl = !ltr;

        buttonsEnter
            .filter('.add-favorite, .add-recent')
            .call(d3_drag()
            .on('start', function() {
                var node = d3_select(this).node();
                dragOrigin = {
                    x: d3_event.x,
                    y: d3_event.y,
                    nodeLeft: node.offsetLeft,
                    nodeTop: node.offsetTop,
                };
                targetData = null;
                dragMoved = false;
            })
            .on('drag', function(d) {
                dragMoved = true;

                var deltaX = d3_event.x - dragOrigin.x,
                    deltaY = d3_event.y - dragOrigin.y;

                var button = d3_select(this);

                if (!button.classed('dragging')) {
                    // haven't committed to dragging yet

                    // don't display drag until dragging beyond a distance threshold
                    if (Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)) <= 5) return;

                    // setup dragging

                    d3_select(this.parentNode)
                        .insert('div', '#' + button.attr('id'))
                        .attr('class', 'drag-placeholder');

                    button
                        .classed('dragging', true)
                        // must use absolute position so button will display if dragged out of the toolbar
                        .style('position', 'absolute');
                }

                var draggingNode = button.node();
                var eventX = d3_event.x + draggingNode.parentNode.offsetLeft;
                var origLeft = dragOrigin.nodeLeft;

                button
                    .classed('removing', deltaY > 50)
                    .style('left', dragOrigin.nodeLeft + deltaX - scrollNode.scrollLeft + 'px')
                    .style('top', dragOrigin.nodeTop + deltaY + 'px');

                targetData = null;

                d3_selectAll('#bar button.add-favorite, #bar button.add-recent')
                    .style('transform', function(d2) {

                        if (d.button === d2.button) return null;

                        // no need to reposition elements if dragging out of toolbar
                        if (deltaY > 50) return null;

                        var node = d3_select(this).node(),
                            nodeLeft = node.offsetLeft,
                            nodeRight = nodeLeft + node.offsetWidth;

                        if ((ltr && nodeLeft > origLeft && eventX > nodeLeft) ||
                            (rtl && nodeLeft < origLeft && eventX < nodeRight)) {

                            if ((ltr && eventX < nodeRight) ||
                                (rtl && eventX > nodeLeft)) {
                                targetData = d2;
                            }
                            return 'translateX(' + (ltr ? '-' : '') + '100%)';

                        } else if ((ltr && nodeLeft < origLeft && eventX < nodeRight) ||
                                   (rtl && nodeLeft > origLeft && eventX > nodeLeft)) {

                            if ((ltr && eventX > nodeLeft) ||
                                (rtl && eventX < nodeRight)) {
                                targetData = d2;
                            }
                            return 'translateX(' + (ltr ? '' : '-') + '100%)';
                        }

                        return null;
                    });
            })
            .on('end', function(d) {

                if (dragMoved && !d3_select(this).classed('dragging')) {
                    // didn't move, interpret as a click
                    toggleMode(d);
                    return;
                }

                d3_selectAll('#bar .drag-placeholder')
                    .remove();

                d3_select(this)
                    .classed('dragging', false)
                    .classed('removing', false)
                    .style('position', null);

                d3_selectAll('#bar button.add-favorite, #bar button.add-recent')
                    .style('transform', null);

                var deltaY = d3_event.y - dragOrigin.y;
                if (deltaY > 50) {
                    // dragged out of the top bar, remove

                    if (d.isFavorite()) {
                        context.presets().removeFavorite(d.preset);
                        // also remove this as a recent so it doesn't still appear
                        context.presets().removeRecent(d.preset);
                    } else if (d.isRecent()) {
                        context.presets().removeRecent(d.preset);
                    }
                } else if (targetData !== null) {
                    // dragged to a new position, reorder

                    if (d.isFavorite()) {
                        context.presets().removeFavorite(d.preset);
                        if (targetData.isRecent()) {
                            // also remove this as a recent so it doesn't appear twice
                            context.presets().removeRecent(d.preset);
                        }
                    } else if (d.isRecent()) {
                        context.presets().removeRecent(d.preset);
                    }

                    var draggingAfter = (ltr && d3_event.x > dragOrigin.x) ||
                                        (rtl && d3_event.x < dragOrigin.x);

                    if (targetData.isFavorite()) {
                        context.presets().addFavorite(d.preset, targetData.preset, draggingAfter);
                    } else if (targetData.isRecent()) {
                        context.presets().addRecent(d.preset, targetData.preset, draggingAfter);
                    }
                }
            })
        );

        // update
        buttons = buttons
            .merge(buttonsEnter)
            .classed('disabled', function(d) { return !enabled(d); });
    }