modules/ui/intro/building.js (604 lines of code) (raw):
import { dispatch as d3_dispatch } from 'd3-dispatch';
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { t, textDirection } from '../../util/locale';
import { modeBrowse } from '../../modes/browse';
import { modeSelect } from '../../modes/select';
import { utilArrayUniq, utilRebind } from '../../util';
import { icon, pad, isMostlySquare, selectMenuItem, transitionTime } from './helper';
export function uiIntroBuilding(context, reveal) {
var dispatch = d3_dispatch('done');
var house = [-85.62815, 41.95638];
var tank = [-85.62732, 41.95347];
var buildingCatetory = context.presets().item('category-building');
var housePreset = context.presets().item('building/house');
var tankPreset = context.presets().item('man_made/storage_tank');
var timeouts = [];
var _houseID = null;
var _tankID = null;
var chapter = {
title: 'intro.buildings.title'
};
function timeout(f, t) {
timeouts.push(window.setTimeout(f, t));
}
function eventCancel() {
d3_event.stopPropagation();
d3_event.preventDefault();
}
function revealHouse(center, text, options) {
var padding = 160 * Math.pow(2, context.map().zoom() - 20);
var box = pad(center, padding, context);
reveal(box, text, options);
}
function revealTank(center, text, options) {
var padding = 190 * Math.pow(2, context.map().zoom() - 19.5);
var box = pad(center, padding, context);
reveal(box, text, options);
}
function revealEditMenu(loc, text, options) {
var rect = context.surfaceRect();
var point = context.curtainProjection(loc);
var pad = 40;
var width = 250 + (2 * pad);
var height = 350;
var startX = rect.left + point[0];
var left = (textDirection === 'rtl') ? (startX - width + pad) : (startX - pad);
var box = {
left: left,
top: point[1] + rect.top - 60,
width: width,
height: height
};
reveal(box, text, options);
}
function addHouse() {
context.enter(modeBrowse(context));
context.history().reset('initial');
_houseID = null;
var msec = transitionTime(house, context.map().center());
if (msec) { reveal(null, null, { duration: 0 }); }
context.map().centerZoomEase(house, 19, msec);
timeout(function() {
var tooltip = reveal('button.add-area',
t('intro.buildings.add_building', { button: icon('#iD-icon-area', 'pre-text') }));
tooltip.selectAll('.tooltip-inner')
.insert('svg', 'span')
.attr('class', 'tooltip-illustration')
.append('use')
.attr('xlink:href', '#iD-graphic-buildings');
context.on('enter.intro', function(mode) {
if (mode.id !== 'add-area') return;
continueTo(startHouse);
});
}, msec + 100);
function continueTo(nextStep) {
context.on('enter.intro', null);
nextStep();
}
}
function startHouse() {
if (context.mode().id !== 'add-area') {
return continueTo(addHouse);
}
_houseID = null;
context.map().zoomEase(20, 500);
timeout(function() {
revealHouse(house, t('intro.buildings.start_building'));
context.map().on('move.intro drawn.intro', function() {
revealHouse(house, t('intro.buildings.start_building'), { duration: 0 });
});
context.on('enter.intro', function(mode) {
if (mode.id !== 'draw-area') return chapter.restart();
continueTo(continueHouse);
});
}, 550); // after easing
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function continueHouse() {
if (context.mode().id !== 'draw-area') {
return continueTo(addHouse);
}
_houseID = null;
revealHouse(house, t('intro.buildings.continue_building'));
context.map().on('move.intro drawn.intro', function() {
revealHouse(house, t('intro.buildings.continue_building'), { duration: 0 });
});
context.on('enter.intro', function(mode) {
if (mode.id === 'draw-area') {
return;
} else if (mode.id === 'select') {
var graph = context.graph();
var way = context.entity(context.selectedIDs()[0]);
var nodes = graph.childNodes(way);
var points = utilArrayUniq(nodes)
.map(function(n) { return context.projection(n.loc); });
if (isMostlySquare(points)) {
_houseID = way.id;
return continueTo(chooseCategoryBuilding);
} else {
return continueTo(retryHouse);
}
} else {
return chapter.restart();
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function retryHouse() {
var onClick = function() { continueTo(addHouse); };
revealHouse(house, t('intro.buildings.retry_building'),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
context.map().on('move.intro drawn.intro', function() {
revealHouse(house, t('intro.buildings.retry_building'),
{ duration: 0, buttonText: t('intro.ok'), buttonCallback: onClick }
);
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
nextStep();
}
}
function chooseCategoryBuilding() {
if (!_houseID || !context.hasEntity(_houseID)) {
return addHouse();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
context.enter(modeSelect(context, [_houseID]));
}
// disallow scrolling
d3_select('.inspector-wrap').on('wheel.intro', eventCancel);
timeout(function() {
// reset pane, in case user somehow happened to change it..
d3_select('.inspector-wrap .panewrap').style('right', '-100%');
var button = d3_select('.preset-category-building .preset-list-button');
reveal(button.node(),
t('intro.buildings.choose_category_building', { category: buildingCatetory.name() })
);
button.on('click.intro', function() {
button.on('click.intro', null);
continueTo(choosePresetHouse);
});
}, 400); // after preset list pane visible..
context.on('enter.intro', function(mode) {
if (!_houseID || !context.hasEntity(_houseID)) {
return continueTo(addHouse);
}
var ids = context.selectedIDs();
if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
return continueTo(chooseCategoryBuilding);
}
});
function continueTo(nextStep) {
d3_select('.inspector-wrap').on('wheel.intro', null);
d3_select('.preset-list-button').on('click.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function choosePresetHouse() {
if (!_houseID || !context.hasEntity(_houseID)) {
return addHouse();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
context.enter(modeSelect(context, [_houseID]));
}
// disallow scrolling
d3_select('.inspector-wrap').on('wheel.intro', eventCancel);
timeout(function() {
// reset pane, in case user somehow happened to change it..
d3_select('.inspector-wrap .panewrap').style('right', '-100%');
var button = d3_select('.preset-building-house .preset-list-button');
reveal(button.node(),
t('intro.buildings.choose_preset_house', { preset: housePreset.name() }),
{ duration: 300 }
);
button.on('click.intro', function() {
button.on('click.intro', null);
continueTo(closeEditorHouse);
});
}, 400); // after preset list pane visible..
context.on('enter.intro', function(mode) {
if (!_houseID || !context.hasEntity(_houseID)) {
return continueTo(addHouse);
}
var ids = context.selectedIDs();
if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
return continueTo(chooseCategoryBuilding);
}
});
function continueTo(nextStep) {
d3_select('.inspector-wrap').on('wheel.intro', null);
d3_select('.preset-list-button').on('click.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function closeEditorHouse() {
if (!_houseID || !context.hasEntity(_houseID)) {
return addHouse();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
context.enter(modeSelect(context, [_houseID]));
}
context.history().checkpoint('hasHouse');
context.on('exit.intro', function() {
continueTo(rightClickHouse);
});
timeout(function() {
reveal('.entity-editor-pane',
t('intro.buildings.close', { button: icon('#iD-icon-apply', 'pre-text') })
);
}, 500);
function continueTo(nextStep) {
context.on('exit.intro', null);
nextStep();
}
}
function rightClickHouse() {
if (!_houseID) return chapter.restart();
context.enter(modeBrowse(context));
context.history().reset('hasHouse');
var zoom = context.map().zoom();
if (zoom < 20) {
zoom = 20;
}
context.map().centerZoomEase(house, zoom, 500);
context.on('enter.intro', function(mode) {
if (mode.id !== 'select') return;
var ids = context.selectedIDs();
if (ids.length !== 1 || ids[0] !== _houseID) return;
timeout(function() {
var node = selectMenuItem('orthogonalize').node();
if (!node) return;
continueTo(clickSquare);
}, 300); // after menu visible
});
context.map().on('move.intro drawn.intro', function() {
revealHouse(house, t('intro.buildings.rightclick_building'), { duration: 0 });
});
context.history().on('change.intro', function() {
continueTo(rightClickHouse);
});
function continueTo(nextStep) {
context.on('enter.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function clickSquare() {
if (!_houseID) return chapter.restart();
var entity = context.hasEntity(_houseID);
if (!entity) return continueTo(rightClickHouse);
var node = selectMenuItem('orthogonalize').node();
if (!node) { return continueTo(rightClickHouse); }
var wasChanged = false;
var menuCoords = context.map().mouseCoordinates();
revealEditMenu(menuCoords,
t('intro.buildings.square_building', { button: icon('#iD-operation-orthogonalize', 'pre-text') })
);
context.on('enter.intro', function(mode) {
if (mode.id === 'browse') {
continueTo(rightClickHouse);
} else if (mode.id === 'move' || mode.id === 'rotate') {
continueTo(retryClickSquare);
}
});
context.map().on('move.intro drawn.intro', function() {
var node = selectMenuItem('orthogonalize').node();
if (!wasChanged && !node) { return continueTo(rightClickHouse); }
revealEditMenu(menuCoords,
t('intro.buildings.square_building', { button: icon('#iD-operation-orthogonalize', 'pre-text') }),
{ duration: 0 }
);
});
context.history().on('change.intro', function() {
wasChanged = true;
context.history().on('change.intro', null);
// Something changed. Wait for transition to complete and check undo annotation.
timeout(function() {
if (context.history().undoAnnotation() === t('operations.orthogonalize.annotation.area')) {
continueTo(doneSquare);
} else {
continueTo(retryClickSquare);
}
}, 500); // after transitioned actions
});
function continueTo(nextStep) {
context.on('enter.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function retryClickSquare() {
context.enter(modeBrowse(context));
revealHouse(house, t('intro.buildings.retry_square'), {
buttonText: t('intro.ok'),
buttonCallback: function() { continueTo(rightClickHouse); }
});
function continueTo(nextStep) {
nextStep();
}
}
function doneSquare() {
context.history().checkpoint('doneSquare');
revealHouse(house, t('intro.buildings.done_square'), {
buttonText: t('intro.ok'),
buttonCallback: function() { continueTo(addTank); }
});
function continueTo(nextStep) {
nextStep();
}
}
function addTank() {
context.enter(modeBrowse(context));
context.history().reset('doneSquare');
_tankID = null;
var msec = transitionTime(tank, context.map().center());
if (msec) { reveal(null, null, { duration: 0 }); }
context.map().centerZoomEase(tank, 19.5, msec);
timeout(function() {
reveal('button.add-area',
t('intro.buildings.add_tank', { button: icon('#iD-icon-area', 'pre-text') })
);
context.on('enter.intro', function(mode) {
if (mode.id !== 'add-area') return;
continueTo(startTank);
});
}, msec + 100);
function continueTo(nextStep) {
context.on('enter.intro', null);
nextStep();
}
}
function startTank() {
if (context.mode().id !== 'add-area') {
return continueTo(addTank);
}
_tankID = null;
timeout(function() {
revealTank(tank, t('intro.buildings.start_tank'));
context.map().on('move.intro drawn.intro', function() {
revealTank(tank, t('intro.buildings.start_tank'), { duration: 0 });
});
context.on('enter.intro', function(mode) {
if (mode.id !== 'draw-area') return chapter.restart();
continueTo(continueTank);
});
}, 550); // after easing
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function continueTank() {
if (context.mode().id !== 'draw-area') {
return continueTo(addTank);
}
_tankID = null;
revealTank(tank, t('intro.buildings.continue_tank'));
context.map().on('move.intro drawn.intro', function() {
revealTank(tank, t('intro.buildings.continue_tank'), { duration: 0 });
});
context.on('enter.intro', function(mode) {
if (mode.id === 'draw-area') {
return;
} else if (mode.id === 'select') {
_tankID = context.selectedIDs()[0];
return continueTo(searchPresetTank);
} else {
return continueTo(addTank);
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function searchPresetTank() {
if (!_tankID || !context.hasEntity(_tankID)) {
return addTank();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
context.enter(modeSelect(context, [_tankID]));
}
// disallow scrolling
d3_select('.inspector-wrap').on('wheel.intro', eventCancel);
timeout(function() {
// reset pane, in case user somehow happened to change it..
d3_select('.inspector-wrap .panewrap').style('right', '-100%');
d3_select('.preset-search-input')
.on('keydown.intro', null)
.on('keyup.intro', checkPresetSearch);
reveal('.preset-search-input',
t('intro.buildings.search_tank', { preset: tankPreset.name() })
);
}, 400); // after preset list pane visible..
context.on('enter.intro', function(mode) {
if (!_tankID || !context.hasEntity(_tankID)) {
return continueTo(addTank);
}
var ids = context.selectedIDs();
if (mode.id !== 'select' || !ids.length || ids[0] !== _tankID) {
// keep the user's area selected..
context.enter(modeSelect(context, [_tankID]));
// reset pane, in case user somehow happened to change it..
d3_select('.inspector-wrap .panewrap').style('right', '-100%');
// disallow scrolling
d3_select('.inspector-wrap').on('wheel.intro', eventCancel);
d3_select('.preset-search-input')
.on('keydown.intro', null)
.on('keyup.intro', checkPresetSearch);
reveal('.preset-search-input',
t('intro.buildings.search_tank', { preset: tankPreset.name() })
);
context.history().on('change.intro', null);
}
});
function checkPresetSearch() {
var first = d3_select('.preset-list-item:first-child');
if (first.classed('preset-man_made-storage_tank')) {
reveal(first.select('.preset-list-button').node(),
t('intro.buildings.choose_tank', { preset: tankPreset.name() }),
{ duration: 300 }
);
d3_select('.preset-search-input')
.on('keydown.intro', eventCancel, true)
.on('keyup.intro', null);
context.history().on('change.intro', function() {
continueTo(closeEditorTank);
});
}
}
function continueTo(nextStep) {
d3_select('.inspector-wrap').on('wheel.intro', null);
context.on('enter.intro', null);
context.history().on('change.intro', null);
d3_select('.preset-search-input').on('keydown.intro keyup.intro', null);
nextStep();
}
}
function closeEditorTank() {
if (!_tankID || !context.hasEntity(_tankID)) {
return addTank();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
context.enter(modeSelect(context, [_tankID]));
}
context.history().checkpoint('hasTank');
context.on('exit.intro', function() {
continueTo(rightClickTank);
});
timeout(function() {
reveal('.entity-editor-pane',
t('intro.buildings.close', { button: icon('#iD-icon-apply', 'pre-text') })
);
}, 500);
function continueTo(nextStep) {
context.on('exit.intro', null);
nextStep();
}
}
function rightClickTank() {
if (!_tankID) return continueTo(addTank);
context.enter(modeBrowse(context));
context.history().reset('hasTank');
context.map().centerEase(tank, 500);
timeout(function() {
context.on('enter.intro', function(mode) {
if (mode.id !== 'select') return;
var ids = context.selectedIDs();
if (ids.length !== 1 || ids[0] !== _tankID) return;
timeout(function() {
var node = selectMenuItem('circularize').node();
if (!node) return;
continueTo(clickCircle);
}, 300); // after menu visible
});
revealTank(tank, t('intro.buildings.rightclick_tank'));
context.map().on('move.intro drawn.intro', function() {
revealTank(tank, t('intro.buildings.rightclick_tank'), { duration: 0 });
});
context.history().on('change.intro', function() {
continueTo(rightClickTank);
});
}, 600);
function continueTo(nextStep) {
context.on('enter.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function clickCircle() {
if (!_tankID) return chapter.restart();
var entity = context.hasEntity(_tankID);
if (!entity) return continueTo(rightClickTank);
var node = selectMenuItem('circularize').node();
if (!node) { return continueTo(rightClickTank); }
var wasChanged = false;
var menuCoords = context.map().mouseCoordinates();
revealEditMenu(menuCoords,
t('intro.buildings.circle_tank', { button: icon('#iD-operation-circularize', 'pre-text') })
);
context.on('enter.intro', function(mode) {
if (mode.id === 'browse') {
continueTo(rightClickTank);
} else if (mode.id === 'move' || mode.id === 'rotate') {
continueTo(retryClickCircle);
}
});
context.map().on('move.intro drawn.intro', function() {
var node = selectMenuItem('circularize').node();
if (!wasChanged && !node) { return continueTo(rightClickTank); }
revealEditMenu(menuCoords,
t('intro.buildings.circle_tank', { button: icon('#iD-operation-circularize', 'pre-text') }),
{ duration: 0 }
);
});
context.history().on('change.intro', function() {
wasChanged = true;
context.history().on('change.intro', null);
// Something changed. Wait for transition to complete and check undo annotation.
timeout(function() {
if (context.history().undoAnnotation() === t('operations.circularize.annotation.area')) {
continueTo(play);
} else {
continueTo(retryClickCircle);
}
}, 500); // after transitioned actions
});
function continueTo(nextStep) {
context.on('enter.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function retryClickCircle() {
context.enter(modeBrowse(context));
revealTank(tank, t('intro.buildings.retry_circle'), {
buttonText: t('intro.ok'),
buttonCallback: function() { continueTo(rightClickTank); }
});
function continueTo(nextStep) {
nextStep();
}
}
function play() {
dispatch.call('done');
reveal('#id-container',
t('intro.buildings.play', { next: t('intro.startediting.title') }), {
tooltipBox: '.intro-nav-wrap .chapter-startEditing',
buttonText: t('intro.ok'),
buttonCallback: function() { reveal('#id-container'); }
}
);
}
chapter.enter = function() {
addHouse();
};
chapter.exit = function() {
timeouts.forEach(window.clearTimeout);
context.on('enter.intro exit.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
d3_select('.inspector-wrap').on('wheel.intro', null);
d3_select('.preset-search-input').on('keydown.intro keyup.intro', null);
d3_select('.more-fields .combobox-input').on('click.intro', null);
};
chapter.restart = function() {
chapter.exit();
chapter.enter();
};
return utilRebind(chapter, dispatch, 'on');
}