Merge pull request #3921 from openstreetmap/walkthrough-updates

Walkthrough updates
This commit is contained in:
Bryan Housel
2017-04-18 14:13:14 -04:00
committed by GitHub
57 changed files with 34216 additions and 1044 deletions
+114 -60
View File
@@ -2420,6 +2420,7 @@ div.full-screen > button:hover {
margin-top: 10px;
border-bottom: 1px solid #ccc;
border-radius: 4px;
text-align: center;
}
.help-wrap .nav {
@@ -3016,20 +3017,14 @@ img.tile-removing {
background-color: #ececec;
}
.modal-actions button:before,
.save-success a.button.osm:before,
.walkthrough a:before {
display: block;
content: '';
.logo {
height: 100px;
width: 100%;
max-width: 100px;
margin: auto;
margin-bottom: 10px;
background:transparent url(img/iD-sprite.svg) no-repeat -200px -460px;
}
.modal-actions :first-child {
.modal-actions > :first-child {
border-right: 1px solid #CCC;
}
@@ -3039,57 +3034,48 @@ img.tile-removing {
/* Restore Modal
------------------------------------------------------- */
.modal-actions .restore:before {
background-position: -500px -460px;
.modal-actions .logo-restore {
color: #7092FF;
}
.modal-actions .reset:before {
background-position: -600px -460px;
.modal-actions .logo-reset {
color: #E06C5E;
}
/* Success Modal
------------------------------------------------------- */
.save-success p {
padding: 15px 15px 0 15px;
}
.save-success a.details {
padding-left: 15px;
padding-left: 15px;
}
.save-success .button {
padding-top: 15px;
}
.save-success .logo-osm {
color: #7092FF;
margin-bottom: 10px;
}
.save-success a.button.social {
height: auto;
border-bottom: none;
}
.save-success .icon.social {
height: 80px;
width: 80px;
color: #7092FF;
}
.save-success .button.osm:before {
background-position: -200px -460px;
}
/* Splash Modal
------------------------------------------------------- */
.modal-actions .walkthrough:before,
.walkthrough a:before {
background-position: -300px -460px;
.modal-actions .logo-walkthrough,
.modal-actions .logo-features {
color: #7092FF;
}
.modal-actions .start:before {
background-position: -400px -460px;
}
/* Commit Modal
/* Save Mode
------------------------------------------------------- */
.mode-save a.user-info {
display: inline-block;
}
@@ -3249,7 +3235,6 @@ img.tile-removing {
position: absolute;
display: none;
color:#333;
text-align: left;
font-size: 12px;
}
@@ -3267,6 +3252,7 @@ img.tile-removing {
.tooltip.right {
margin-left: 20px;
text-align: left;
}
.tooltip.bottom {
@@ -3443,15 +3429,19 @@ img.tile-removing {
.map-control .tooltip {
min-width: 160px;
}
/* Move over tooltips that are near the edge of screen */
.add-point .tooltip {
left: 33.3333% !important;
}
.curtain-tooltip.intro-points-add .tooltip-arrow,
.add-point .tooltip .tooltip-arrow {
left: 60px;
}
[dir='rtl'] .add-point .tooltip .tooltip-arrow {
left: auto;
right: 60px;
}
/* radial menu (deprecated) */
@@ -3601,6 +3591,8 @@ img.tile-removing {
}
.intro-nav-wrap {
display: flex;
flex-direction: row;
position: absolute;
left: 0;
right: 0;
@@ -3609,34 +3601,67 @@ img.tile-removing {
z-index: 1001;
}
.intro-nav-wrap button.step {
width: 20%;
.intro-nav-wrap .intro-nav-wrap-logo {
flex: 0 0 auto;
height: 40px;
width: 40px;
color: white;
margin: 0px 20px;
vertical-align: middle;
}
.intro-nav-wrap button.step.finished {
.intro-nav-wrap .joined {
flex: 1 1 auto;
display: flex;
flex-direction: row;
}
.intro-nav-wrap button.chapter {
flex: 1 1 100%;
padding: 0px 20px;
}
.intro-nav-wrap button.chapter.next {
animation-duration: 1s;
animation-name: pulse;
animation-iteration-count: infinite;
animation-direction: alternate;
}
@keyframes pulse {
from { background: #7092ff; }
to { background: #c6d4ff; }
}
.intro-nav-wrap button.chapter.finished {
background: #8cd05f;
}
.intro-nav-wrap button.step .status {
margin-left: 3px;
.intro-nav-wrap button.chapter .status {
display: none;
}
.intro-nav-wrap button.step.finished .status {
.intro-nav-wrap button.chapter.finished .status {
display: inline-block;
}
.curtain-tooltip .tooltip-inner {
.curtain-tooltip.tooltip.in {
opacity: 1;
}
.curtain-tooltip.tooltip {
text-align: left;
padding: 20px;
}
[dir='rtl'] .curtain-tooltip.tooltip {
text-align: right;
}
.curtain-tooltip .tooltip-inner {
font-size: 15px;
position: relative;
padding: 20px;
}
.curtain-tooltip .tooltip-inner .bold {
.curtain-tooltip .tooltip-inner .button-section,
.curtain-tooltip .tooltip-inner .instruction {
font-weight: bold;
display: block;
border-top: 1px solid #CCC;
@@ -3646,35 +3671,65 @@ img.tile-removing {
padding: 10px 20px 0 20px;
}
.curtain-tooltip .tooltip-inner .bold:only-child {
[dir='rtl'] .curtain-tooltip .tooltip-inner .button-section button.col8 {
float: right;
}
.curtain-tooltip .tooltip-inner .instruction:only-child {
border: 0;
padding: 0;
margin: 0;
}
.curtain-tooltip .tooltip-inner .icon.pre-text {
vertical-align: text-top;
margin-right: 0;
margin-left: 0;
display: inline-block;
}
.curtain-tooltip.intro-points-describe {
top: 133px !important;
}
/* Tooltip illustrations */
.intro-points-add .tooltip-inner::before,
.intro-areas-add .tooltip-inner::before,
.intro-lines-add .tooltip-inner::before {
margin-left: -20px;
display: block;
content: "";
.tooltip-illustration {
height: 80px;
width: 200px;
background:transparent url(img/iD-sprite.svg) no-repeat 0 -320px;
margin-left: -20px;
margin-top: -10px;
}
[dir='rtl'] .tooltip-illustration {
margin-left: auto;
margin-right: -20px;
}
.intro-areas-add .tooltip-inner::before {
background-position: 0 -400px;
.curtain-tooltip.intro-mouse {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.intro-lines-add .tooltip-inner::before {
background-position: 0 -480px;
.curtain-tooltip.intro-mouse .counter {
position: absolute;
display: block;
top: 50px;
width: 100%;
text-align: center;
font-weight: bold;
font-size: 14px;
z-index: 1003;
}
.curtain-tooltip.intro-mouse .tooltip-illustration use {
fill: rgba(112, 146, 255, 0);
color: rgba(112, 146, 255, 0);
}
.curtain-tooltip.intro-mouse.leftclick .tooltip-illustration use {
fill: rgba(112, 146, 255, 1);
}
.curtain-tooltip.intro-mouse.rightclick .tooltip-illustration use {
color: rgba(112, 146, 255, 1);
}
.huge-modal-button {
@@ -3686,8 +3741,7 @@ img.tile-removing {
.huge-modal-button .illustration {
height: 100px;
width: 100px;
background: rgba(0, 0, 0, 0) url(img/iD-sprite.svg) no-repeat -300px -460px;
margin: auto;
color: #7092FF;
}
.mapillary-wrap {
+213 -60
View File
@@ -454,7 +454,7 @@ en:
help_link_text: Details
help_link_url: "https://wiki.openstreetmap.org/wiki/FAQ#I_have_just_made_some_changes_to_the_map._How_do_I_get_to_see_my_changes.3F"
confirm:
okay: "Okay"
okay: "OK"
cancel: "Cancel"
splash:
welcome: Welcome to the iD OpenStreetMap editor
@@ -778,76 +778,229 @@ en:
click the "Merge" (+) button.
intro:
done: done
ok: OK
graph:
city_hall: Three Rivers City Hall
fire_department: Three Rivers Fire Department
memory_isle_park: Memory Isle Park
riverwalk_trail: Riverwalk Trail
w_michigan_ave: West Michigan Avenue
e_michigan_ave: East Michigan Avenue
spring_st: Spring Street
scidmore_park: Scidmore Park
petting_zoo: Scidmore Park Petting Zoo
n_andrews_st: North Andrews Street
s_andrews_st: South Andrews Street
n_constantine_st: North Constantine Street
s_constantine_st: South Constantine Street
rocky_river: Rocky River
railroad_dr: Railroad Drive
conrail_rr: Conrail Railroad
st_joseph_river: Saint Joseph River
n_main_st: North Main Street
s_main_st: South Main Street
water_st: Water Street
foster_st: Foster Street
portage_river: Portage River
flower_st: Flower Street
elm_st: Elm Street
walnut_st: Walnut Street
morris_ave: Morris Avenue
east_st: East Street
portage_ave: Portage Avenue
city: Three Rivers
state: MI
postcode: "49093"
name:
1st-avenue: 1st Avenue
2nd-avenue: 2nd Avenue
4th-avenue: 4th Avenue
5th-avenue: 5th Avenue
6th-avenue: 6th Avenue
6th-street: 6th Street
7th-avenue: 7th Avenue
8th-avenue: 8th Avenue
9th-avenue: 9th Avenue
10th-avenue: 10th Avenue
11th-avenue: 11th Avenue
12th-avenue: 12th Avenue
access-point-employment: Access Point Employment
adams-street: Adams Street
andrews-elementary-school: Andrews Elementary School
andrews-street: Andrews Street
armitage-street: Armitage Street
barrows-school: Barrows School
battle-street: Battle Street
bennett-street: Bennett Street
bowman-park: Bowman Park
collins-drive: Collins Drive
conrail-railroad: Conrail Railroad
conservation-park: Conservation Park
constantine-street: Constantine Street
cushman-street: Cushman Street
dollar-tree: Dollar Tree
douglas-avenue: Douglas Avenue
east-street: East Street
elm-street: Elm Street
flower-street: Flower Street
foster-street: Foster Street
french-street: French Street
garden-street: Garden Street
gem-pawnbroker: Gem Pawnbroker
golden-finch-framing: Golden Finch Framing
grant-avenue: Grant Avenue
hoffman-pond: Hoffman Pond
hoffman-street: Hoffman Street
hook-avenue: Hook Avenue
jefferson-street: Jefferson Street
kelsey-street: Kelsey Street
lafayette-park: LaFayette Park
las-coffee-cafe: L.A.'s Coffee Cafe
lincoln-avenue: Lincoln Avenue
lowrys-books: Lowry's Books
lynns-garage: Lynn's Garage
main-street-barbell: Main Street Barbell
main-street-cafe: Main Street Cafe
main-street-fitness: Main Street Fitness
main-street: Main Street
maple-street: Maple Street
marina-park: Marina Park
market-street: Market Street
memory-isle-park: Memory Isle Park
memory-isle: Memory Isle
michigan-avenue: Michigan Avenue
middle-street: Middle Street
millard-street: Millard Street
moore-street: Moore Street
morris-avenue: Morris Avenue
mural-mall: Mural Mall
paisanos-bar-and-grill: Paisano's Bar and Grill
paisley-emporium: Paisley Emporium
paparazzi-tattoo: Paparazzi Tattoo
pealer-street: Pealer Street
pine-street: Pine Street
pizza-hut: Pizza Hut
portage-avenue: Portage Avenue
portage-river: Portage River
preferred-insurance-services: Preferred Insurance Services
railroad-drive: Railroad Drive
river-city-appliance: River City Appliance
river-drive: River Drive
river-road: River Road
river-street: River Street
riverside-cemetery: Riverside Cemetery
riverwalk-trail: Riverwalk Trail
riviera-theatre: Riviera Theatre
rocky-river: Rocky River
saint-joseph-river: Saint Joseph River
scidmore-park-petting-zoo: Scidmore Park Petting Zoo
scidmore-park: Scidmore Park
scouter-park: Scouter Park
sherwin-williams: Sherwin-Williams
south-street: South Street
southern-michigan-bank: Southern Michigan Bank
spring-street: Spring Street
sturgeon-river-road: Sturgeon River Road
three-rivers-city-hall: Three Rivers City Hall
three-rivers-elementary-school: Three Rivers Elementary School
three-rivers-fire-department: Three Rivers Fire Department
three-rivers-high-school: Three Rivers High School
three-rivers-middle-school: Three Rivers Middle School
three-rivers-municipal-airport: Three Rivers Municipal Airport
three-rivers-post-office: Three Rivers Post Office
three-rivers-public-library: Three Rivers Public Library
three-rivers: Three Rivers
unique-jewelry: Unique Jewelry
walnut-street: Walnut Street
washington-street: Washington Street
water-street: Water Street
west-street: West Street
wheeler-street: Wheeler Street
william-towing: William Towing
willow-drive: Willow Drive
wood-street: Wood Street
world-fare: World Fare
welcome:
title: "Welcome"
welcome: "Welcome! This walkthrough will teach you the basics of editing on OpenStreetMap."
practice: "All of the data in this walkthrough is just for practicing, and any edits that you make in the walkthrough will not be saved."
words: "This walkthrough will introduce some new words and concepts. When we introduce a new word, we'll use *italics*."
mouse: "You can use any input device to edit the map, but this walkthrough assumes you have a mouse with left and right buttons. **If you want to attach a mouse, do so now, then click OK.**"
leftclick: "When this tutorial asks you to click or double-click, we mean with the left button. On a trackpad it might be a single-click or single-finger tap. **Left-click {num} times.**"
rightclick: "Sometimes we'll also ask you to right-click. This might be the same as control-click, or two-finger tap on a trackpad. Your keyboard might even have a 'menu' key that works like right-click. **Right-click {num} times.**"
chapters: "So far, so good! You can use the buttons below to skip chapters at any time or to restart a chapter if you get stuck. Let's begin! **Click '{next}' to continue.**"
navigation:
title: "Navigation"
drag: "The main map area shows OpenStreetMap data on top of a background. You can navigate by dragging and scrolling, just like any web map. **Drag the map!**"
select: "Map features are represented three ways: using points, lines or areas. All features can be selected by clicking on them. **Click on the point to select it.**"
pane: "When a feature is selected, the feature editor is displayed. The header shows us the feature type and the main pane shows the feature's attributes, such as its name and address. **Close the feature editor by pressing the {button} button in the top right.**"
search: "You can also search for features in the current view, or worldwide. **Search for '{name}'**"
choose: "**Choose {name} from the list to select it.**"
chosen: "Great! {name} is now selected. **Close the feature editor by pressing the {button} button.**"
zoom: "You can zoom in or out by scrolling with the mouse wheel or trackpad. **Zoom the map!**"
features: "We use the word *features* to describe the things that appear on the map. Anything in the real world can be mapped as a feature on OpenStreetMap."
points_lines_areas: "Map features are represented using *points, lines, or areas.*"
nodes_ways: "In OpenStreetMap, points are sometimes called *nodes*, and lines and areas are sometimes called *ways*."
click_townhall: "All features on the map can be selected by clicking on them. **Click on the point to select it.**"
selected_townhall: "Great! The point is now selected. Selected features are drawn with a pulsing glow."
editor_townhall: "When a feature is selected, the *feature editor* is displayed alongside the map."
preset_townhall: "The top part of the feature editor shows the feature's type. This point is a {preset}."
fields_townhall: "The middle part of the feature editor contains *fields* showing the feature's attributes, such as its name and address."
close_townhall: "**Close the feature editor by hitting escape or pressing the {button} button in the upper corner.**"
search_street: "You can also search for features in the current view, or worldwide. **Search for '{name}'**"
choose_street: "**Choose {name} from the list to select it.**"
selected_street: "Great! {name} is now selected."
editor_street: "The fields shown for a street are different than the fields that were shown for the town hall.{br}For this selected street, the feature editor shows fields like '{field1}' and '{field2}'. **Close the feature editor by hitting escape or pressing the {button} button.**"
play: "Try moving the map and clicking on some other features to see what kinds of things can be added to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**"
points:
title: "Points"
add: "Points can be used to represent features such as shops, restaurants, and monuments. They mark a specific location, and describe what's there. **Click the {button} Point button to add a new point.**"
place: "The point can be placed by clicking on the map. **Click the map to place the new point on top of the building.**"
search: "There are many different features that can be represented by points. The point you just added is a Cafe. **Search for '{name}'**"
choose: "**Choose Cafe from the list.**"
describe: "The point is now marked as a cafe. Using the feature editor, we can add more information about the feature. **Add a name**"
close: "The feature editor will remember all of your changes automatically. When you change a feature, the close button will change to a checkmark. **Click the {button} button to close the feature editor**"
reselect: "Often points will already exist, but have mistakes or be incomplete. We can edit existing points. **Click to select the point you just created.**"
fixname: "**Change the name, then click the {button} button to close the feature editor.**"
rightclick: "You can right-click on features to see the list of operations that can be performed on them. **Right-click to select the point you created.**"
delete: "**Click on the {button} button to delete the point.**"
add_point: "*Points* can be used to represent features such as shops, restaurants, and monuments.{br}They mark a specific location, and describe what's there. **Click the {button} Point button to add a new point.**"
place_point: "To place the new point on the map, position your mouse cursor where the point should go, then left-click or press the spacebar. **Move the mouse pointer over this building, then left-click or press the spacebar.**"
search_cafe: "There are many different features that can be represented by points. The point you just added is a cafe. **Search for '{preset}'**"
choose_cafe: "**Choose {preset} from the list.**"
feature_editor: "The point is now marked as a cafe. Using the feature editor, we can add more information about the cafe."
add_name: "In OpenStreetMap, all of the fields are optional, and it's OK to leave a field blank if you are unsure.{br}Let's pretend that you have local knowledge of this cafe, and you know its name. **Add a name for the cafe**"
add_close: "The feature editor will remember all of your changes automatically. **When you are finished adding the name, hit escape, return, or click the {button} button to close the feature editor**"
reselect: "Often points will already exist, but have mistakes or be incomplete. We can edit existing points. **Click to select the cafe you just created.**"
update: "Let's fill in some more details for this cafe. You can change its name, add a cuisine, or add an address. **Change the cafe details**"
update_close: "**When you are finished updating the cafe, hit escape, return, or click the {button} button to close the feature editor**"
rightclick: "You can right-click on any feature to see the *edit menu*, which shows a list of editing operations that can be performed. **Right-click to select the point you created and show the edit menu.**"
delete: "It's OK to delete features that don't exist in the real world.{br}Deleting a feature from OpenStreetMap removes it from the map that everyone uses, so you should make sure a feature is really gone before you delete it. **Click on the {button} button to delete the point.**"
undo: "You can always undo any changes up until you save your edits to OpenStreetMap. **Click on the {button} button to undo the delete and get the point back.**"
play: "Now that you know how to create and edit points, try creating a few more points for practice! **When you are ready to continue to the next chapter, click '{next}'.**"
areas:
title: "Areas"
add: "Areas are used to show the boundaries of features like lakes, buildings, and residential areas. They can be also be used for more detailed mapping of many features you might normally map as points. **Click the {button} Area button to add a new area.**"
corner: "Areas are drawn by placing nodes that mark the boundary of the area. **Click to place a starting node on one of the corners of the playground.**"
place: "Draw the area by placing more nodes. Finish the area by clicking on the starting node. **Draw an area for the playground.**"
search: "**Search for '{name}'.**"
choose: "**Choose Playground from the list.**"
describe: "**Add a name, then click the {button} button to close the feature editor**"
add_playground: "*Areas* are used to show the boundaries of features like lakes, buildings, and residential areas.{br}They can be also be used for more detailed mapping of many features you might normally map as points. **Click the {button} Area button to add a new area.**"
start_playground: "Let's add this playground to the map by drawing an area. Areas are drawn by placing *nodes* along the outer edge of the feature. **Click or press spacebar to place a starting node on one of the corners of the playground.**"
continue_playground: "Continue drawing the area by placing more nodes along the playground's edge. It is OK to connect the area to the existing walking paths.{br}Tip: You can hold down the Alt key to prevent nodes from connecting to other features. **Continue drawing an area for the playground.**"
finish_playground: "Finish the area by pressing return, or clicking again on either the first or last node. **Finish drawing an area for the playground.**"
search_playground: "**Search for '{preset}'.**"
choose_playground: "**Choose {preset} from the list.**"
add_field: "This playground doesn't have an official name, so we won't add anything in the Name field.{br}Instead let's add some additional details about the playground to the Description field. **Open the Add Field list.**"
choose_field: "**Choose {field} from the list.**"
retry_add_field: "You didn't select the {field} field. Let's try again."
describe_playground: "**Add a description, then click the {button} button to close the feature editor.**"
play: "Good job! Try drawing a few more areas, and see what other kinds of area features you can add to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**"
lines:
title: "Lines"
add: "Lines are used to represent features such as roads, railroads, and rivers. **Click the {button} Line button to add a new line.**"
start: "**Start the line by clicking on the end of the road.**"
intersect: "Click to add more nodes to the line. You can drag the map while drawing if necessary. Roads, and many other types of lines, are part of a larger network. It is important for these lines to be connected properly in order for routing applications to work. **Click on {name} to create an intersection connecting the two lines.**"
finish: "Lines can be finished by clicking on the last node again. **Finish drawing the road.**"
road: "**Select Road from the list**"
residential: "There are different types of roads, the most common of which is Residential. **Choose the Residential road type**"
describe: "**Name the road, then click the {button} button to close the feature editor.**"
restart: "The road needs to intersect {name}."
"wrong_preset": "You didn't select the Residential road type. **Click here to choose again**"
add_line: "*Lines* are used to represent features such as roads, railroads, and rivers. **Click the {button} Line button to add a new line.**"
start_line: "Here is a road that is missing. Let's add it!{br}In OpenStreetMap, lines should be drawn down the center of the road. You can drag and zoom the map while drawing if necessary. **Start a new line by clicking at the top end of this missing road.**"
intersect: "Click or press spacebar to add more nodes to the line.{br}Roads, and many other types of lines, are part of a larger network. It is important for these lines to be connected properly in order for routing applications to work. **Click on {name} to create an intersection connecting the two lines.**"
retry_intersect: "The road needs to intersect {name}. Let's try again!"
continue_line: "Continue drawing the line for the new road. Remember that you can drag and zoom the map if needed.{br}When you are finished drawing, click on the last node again. **Finish drawing the road.**"
choose_category_road: "**Select {category} from the list**"
choose_preset_residential: "There are many different types of roads, but this one is a residential road. **Choose the {preset} type**"
retry_preset_residential: "You didn't select the {preset} type. **Click here to choose again**"
name_road: "**Give this road a name, then hit escape, return, or click the {button} button to close the feature editor.**"
did_name_road: "Looks good! Next we will learn how to update the shape of a line."
update_line: "Sometimes you will need to change the shape of an existing line. Here is a road that doesn't look quite right."
add_node: "We can add some nodes to this line to improve its shape. One way to add a node is to double-click the line where you want to add a node. **Double-click on the line to create a new node.**"
start_drag_endpoint: "When a line is selected, you can drag any of its nodes by clicking and holding down the left mouse button while you drag. **Drag the endpoint to the place where these roads should intersect.**"
finish_drag_endpoint: "This spot looks good. **Release the left mouse button to finish dragging.**"
start_drag_midpoint: "Small triangles are drawn at the *midpoints* between nodes. Another way to create a new node is to drag a midpoint to a new location. **Drag the midpoint triangle to create a new node along the curve of the road.**"
continue_drag_midpoint: "This line is looking much better! Continue to adjust this line by double-clicking or dragging midpoints until the curve matches the road shape. **When you're happy with how the line looks, click OK.**"
delete_lines: "It's OK to delete lines for roads that don't exist in the real world.{br}Here's an example where the city planned a {street} but never built it. We can improve this part of the map by deleting the extra lines."
rightclick_intersection: "The last real street is {street1}, so we will *split* {street2} at this intersection and remove everything above it. **Right click on the intersection node**"
split_intersection: "**Click on the {button} button to split {street}.**"
retry_split: "You didn't click the Split button. Try again."
did_split_multi: "Good Job! {street1} is now split into two pieces. The top part can be removed. **Click the top part of {street2} to select it.**"
did_split_single: "**Click the top part of {street2} to select it.**"
multi_select: "{selected} is now selected. Let's also select {other1}. You can shift-click to select multiple things. **Shift-click on {other2}.**"
multi_rightclick: "Good! Both lines to delete are now selected. **Right-click on one of the lines to show the edit menu.**"
multi_delete: "**Click on the {button} button to delete the extra lines.**"
retry_delete: "You didn't click the Delete button. Try again."
play: "Great! Use the skills that you've learned in this chapter to practice editing some more lines. **When you are ready to continue to the next chapter, click '{next}'.**"
buildings:
title: "Buildings"
add_building: "OpenStreetMap is the world's largest database of buildings.{br}You can help improve this database by tracing buildings that aren't already mapped. **Click the {button} Area button to add a new area.**"
start_building: "Let's add this house to the map by tracing its outline.{br}Buildings should be traced around their footprint as accurately as possible. **Click or press spacebar to place a starting node on one of the corners of the building.**"
continue_building: "Continue adding more nodes to trace the outline of the building. Remember that you can zoom in if you want to add more details.{br}Finish the building by pressing return, or clicking again on either the first or last node. **Finish tracing the building.**"
retry_building: "It looks like you had some trouble placing the nodes at the building corners. Try again!"
choose_category_building: "**Choose {category} from the list**"
choose_preset_house: "There are many different types of buildings, but this one is clearly a house.{br}If you're not sure of the type, it's OK to just choose the generic Building type. **Choose the {preset} type**"
close: "**Hit escape or click the {button} button to close the feature editor**"
rightclick_building: "**Right-click to select the building you created and show the edit menu.**"
square_building: "The house that you just added will look even better with perfectly square corners. **Click on the {button} button to square the building shape.**"
retry_square: "You didn't click the Square button. Try again."
done_square: "See how the corners of the building moved into place? Let's learn another useful trick."
add_tank: "Next we'll trace this circular storage tank. **Click the {button} Area button to add a new area.**"
start_tank: "Don't worry, you won't need to draw a perfect circle. Just draw an area inside the tank that touches its edge. **Click or press spacebar to place a starting node on the edge of the tank.**"
continue_tank: "Add a few more nodes around the edge. The circle will be created outside the nodes that you draw.{br}Finish the area by pressing return, or clicking again on either the first or last node. **Finish tracing the tank.**"
search_tank: "**Search for '{preset}'**"
choose_tank: "**Choose {preset} from the list.**"
rightclick_tank: "**Right-click to select the storage tank you created and show the edit menu.**"
circle_tank: "**Click on the {button} button to make the tank a circle.**"
retry_circle: "You didn't click the Circularize button. Try again."
play: "Great Job! Practice tracing a few more buildings, and try some of the other commands on the edit menu. **When you are ready to continue to the next chapter, click '{next}'.**"
startediting:
title: "Start Editing"
help: "You can replay this walkthrough or view more documentation by clicking the {button} Help button."
help: "You're now ready to edit OpenStreetMap!{br}You can replay this walkthrough anytime or view more documentation by clicking the {button} Help button."
save: "Don't forget to regularly save your changes!"
start: "Start mapping!"
+29261 -1
View File
File diff suppressed because one or more lines are too long
+216 -60
View File
@@ -561,7 +561,7 @@
"help_link_url": "https://wiki.openstreetmap.org/wiki/FAQ#I_have_just_made_some_changes_to_the_map._How_do_I_get_to_see_my_changes.3F"
},
"confirm": {
"okay": "Okay",
"okay": "OK",
"cancel": "Cancel"
},
"splash": {
@@ -635,82 +635,238 @@
},
"intro": {
"done": "done",
"ok": "OK",
"graph": {
"city_hall": "Three Rivers City Hall",
"fire_department": "Three Rivers Fire Department",
"memory_isle_park": "Memory Isle Park",
"riverwalk_trail": "Riverwalk Trail",
"w_michigan_ave": "West Michigan Avenue",
"e_michigan_ave": "East Michigan Avenue",
"spring_st": "Spring Street",
"scidmore_park": "Scidmore Park",
"petting_zoo": "Scidmore Park Petting Zoo",
"n_andrews_st": "North Andrews Street",
"s_andrews_st": "South Andrews Street",
"n_constantine_st": "North Constantine Street",
"s_constantine_st": "South Constantine Street",
"rocky_river": "Rocky River",
"railroad_dr": "Railroad Drive",
"conrail_rr": "Conrail Railroad",
"st_joseph_river": "Saint Joseph River",
"n_main_st": "North Main Street",
"s_main_st": "South Main Street",
"water_st": "Water Street",
"foster_st": "Foster Street",
"portage_river": "Portage River",
"flower_st": "Flower Street",
"elm_st": "Elm Street",
"walnut_st": "Walnut Street",
"morris_ave": "Morris Avenue",
"east_st": "East Street",
"portage_ave": "Portage Avenue"
"city": "Three Rivers",
"state": "MI",
"postcode": "49093",
"name": {
"1st-avenue": "1st Avenue",
"2nd-avenue": "2nd Avenue",
"4th-avenue": "4th Avenue",
"5th-avenue": "5th Avenue",
"6th-avenue": "6th Avenue",
"6th-street": "6th Street",
"7th-avenue": "7th Avenue",
"8th-avenue": "8th Avenue",
"9th-avenue": "9th Avenue",
"10th-avenue": "10th Avenue",
"11th-avenue": "11th Avenue",
"12th-avenue": "12th Avenue",
"access-point-employment": "Access Point Employment",
"adams-street": "Adams Street",
"andrews-elementary-school": "Andrews Elementary School",
"andrews-street": "Andrews Street",
"armitage-street": "Armitage Street",
"barrows-school": "Barrows School",
"battle-street": "Battle Street",
"bennett-street": "Bennett Street",
"bowman-park": "Bowman Park",
"collins-drive": "Collins Drive",
"conrail-railroad": "Conrail Railroad",
"conservation-park": "Conservation Park",
"constantine-street": "Constantine Street",
"cushman-street": "Cushman Street",
"dollar-tree": "Dollar Tree",
"douglas-avenue": "Douglas Avenue",
"east-street": "East Street",
"elm-street": "Elm Street",
"flower-street": "Flower Street",
"foster-street": "Foster Street",
"french-street": "French Street",
"garden-street": "Garden Street",
"gem-pawnbroker": "Gem Pawnbroker",
"golden-finch-framing": "Golden Finch Framing",
"grant-avenue": "Grant Avenue",
"hoffman-pond": "Hoffman Pond",
"hoffman-street": "Hoffman Street",
"hook-avenue": "Hook Avenue",
"jefferson-street": "Jefferson Street",
"kelsey-street": "Kelsey Street",
"lafayette-park": "LaFayette Park",
"las-coffee-cafe": "L.A.'s Coffee Cafe",
"lincoln-avenue": "Lincoln Avenue",
"lowrys-books": "Lowry's Books",
"lynns-garage": "Lynn's Garage",
"main-street-barbell": "Main Street Barbell",
"main-street-cafe": "Main Street Cafe",
"main-street-fitness": "Main Street Fitness",
"main-street": "Main Street",
"maple-street": "Maple Street",
"marina-park": "Marina Park",
"market-street": "Market Street",
"memory-isle-park": "Memory Isle Park",
"memory-isle": "Memory Isle",
"michigan-avenue": "Michigan Avenue",
"middle-street": "Middle Street",
"millard-street": "Millard Street",
"moore-street": "Moore Street",
"morris-avenue": "Morris Avenue",
"mural-mall": "Mural Mall",
"paisanos-bar-and-grill": "Paisano's Bar and Grill",
"paisley-emporium": "Paisley Emporium",
"paparazzi-tattoo": "Paparazzi Tattoo",
"pealer-street": "Pealer Street",
"pine-street": "Pine Street",
"pizza-hut": "Pizza Hut",
"portage-avenue": "Portage Avenue",
"portage-river": "Portage River",
"preferred-insurance-services": "Preferred Insurance Services",
"railroad-drive": "Railroad Drive",
"river-city-appliance": "River City Appliance",
"river-drive": "River Drive",
"river-road": "River Road",
"river-street": "River Street",
"riverside-cemetery": "Riverside Cemetery",
"riverwalk-trail": "Riverwalk Trail",
"riviera-theatre": "Riviera Theatre",
"rocky-river": "Rocky River",
"saint-joseph-river": "Saint Joseph River",
"scidmore-park-petting-zoo": "Scidmore Park Petting Zoo",
"scidmore-park": "Scidmore Park",
"scouter-park": "Scouter Park",
"sherwin-williams": "Sherwin-Williams",
"south-street": "South Street",
"southern-michigan-bank": "Southern Michigan Bank",
"spring-street": "Spring Street",
"sturgeon-river-road": "Sturgeon River Road",
"three-rivers-city-hall": "Three Rivers City Hall",
"three-rivers-elementary-school": "Three Rivers Elementary School",
"three-rivers-fire-department": "Three Rivers Fire Department",
"three-rivers-high-school": "Three Rivers High School",
"three-rivers-middle-school": "Three Rivers Middle School",
"three-rivers-municipal-airport": "Three Rivers Municipal Airport",
"three-rivers-post-office": "Three Rivers Post Office",
"three-rivers-public-library": "Three Rivers Public Library",
"three-rivers": "Three Rivers",
"unique-jewelry": "Unique Jewelry",
"walnut-street": "Walnut Street",
"washington-street": "Washington Street",
"water-street": "Water Street",
"west-street": "West Street",
"wheeler-street": "Wheeler Street",
"william-towing": "William Towing",
"willow-drive": "Willow Drive",
"wood-street": "Wood Street",
"world-fare": "World Fare"
}
},
"welcome": {
"title": "Welcome",
"welcome": "Welcome! This walkthrough will teach you the basics of editing on OpenStreetMap.",
"practice": "All of the data in this walkthrough is just for practicing, and any edits that you make in the walkthrough will not be saved.",
"words": "This walkthrough will introduce some new words and concepts. When we introduce a new word, we'll use *italics*.",
"mouse": "You can use any input device to edit the map, but this walkthrough assumes you have a mouse with left and right buttons. **If you want to attach a mouse, do so now, then click OK.**",
"leftclick": "When this tutorial asks you to click or double-click, we mean with the left button. On a trackpad it might be a single-click or single-finger tap. **Left-click {num} times.**",
"rightclick": "Sometimes we'll also ask you to right-click. This might be the same as control-click, or two-finger tap on a trackpad. Your keyboard might even have a 'menu' key that works like right-click. **Right-click {num} times.**",
"chapters": "So far, so good! You can use the buttons below to skip chapters at any time or to restart a chapter if you get stuck. Let's begin! **Click '{next}' to continue.**"
},
"navigation": {
"title": "Navigation",
"drag": "The main map area shows OpenStreetMap data on top of a background. You can navigate by dragging and scrolling, just like any web map. **Drag the map!**",
"select": "Map features are represented three ways: using points, lines or areas. All features can be selected by clicking on them. **Click on the point to select it.**",
"pane": "When a feature is selected, the feature editor is displayed. The header shows us the feature type and the main pane shows the feature's attributes, such as its name and address. **Close the feature editor by pressing the {button} button in the top right.**",
"search": "You can also search for features in the current view, or worldwide. **Search for '{name}'**",
"choose": "**Choose {name} from the list to select it.**",
"chosen": "Great! {name} is now selected. **Close the feature editor by pressing the {button} button.**"
"zoom": "You can zoom in or out by scrolling with the mouse wheel or trackpad. **Zoom the map!**",
"features": "We use the word *features* to describe the things that appear on the map. Anything in the real world can be mapped as a feature on OpenStreetMap.",
"points_lines_areas": "Map features are represented using *points, lines, or areas.*",
"nodes_ways": "In OpenStreetMap, points are sometimes called *nodes*, and lines and areas are sometimes called *ways*.",
"click_townhall": "All features on the map can be selected by clicking on them. **Click on the point to select it.**",
"selected_townhall": "Great! The point is now selected. Selected features are drawn with a pulsing glow.",
"editor_townhall": "When a feature is selected, the *feature editor* is displayed alongside the map.",
"preset_townhall": "The top part of the feature editor shows the feature's type. This point is a {preset}.",
"fields_townhall": "The middle part of the feature editor contains *fields* showing the feature's attributes, such as its name and address.",
"close_townhall": "**Close the feature editor by hitting escape or pressing the {button} button in the upper corner.**",
"search_street": "You can also search for features in the current view, or worldwide. **Search for '{name}'**",
"choose_street": "**Choose {name} from the list to select it.**",
"selected_street": "Great! {name} is now selected.",
"editor_street": "The fields shown for a street are different than the fields that were shown for the town hall.{br}For this selected street, the feature editor shows fields like '{field1}' and '{field2}'. **Close the feature editor by hitting escape or pressing the {button} button.**",
"play": "Try moving the map and clicking on some other features to see what kinds of things can be added to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**"
},
"points": {
"title": "Points",
"add": "Points can be used to represent features such as shops, restaurants, and monuments. They mark a specific location, and describe what's there. **Click the {button} Point button to add a new point.**",
"place": "The point can be placed by clicking on the map. **Click the map to place the new point on top of the building.**",
"search": "There are many different features that can be represented by points. The point you just added is a Cafe. **Search for '{name}'**",
"choose": "**Choose Cafe from the list.**",
"describe": "The point is now marked as a cafe. Using the feature editor, we can add more information about the feature. **Add a name**",
"close": "The feature editor will remember all of your changes automatically. When you change a feature, the close button will change to a checkmark. **Click the {button} button to close the feature editor**",
"reselect": "Often points will already exist, but have mistakes or be incomplete. We can edit existing points. **Click to select the point you just created.**",
"fixname": "**Change the name, then click the {button} button to close the feature editor.**",
"rightclick": "You can right-click on features to see the list of operations that can be performed on them. **Right-click to select the point you created.**",
"delete": "**Click on the {button} button to delete the point.**"
"add_point": "*Points* can be used to represent features such as shops, restaurants, and monuments.{br}They mark a specific location, and describe what's there. **Click the {button} Point button to add a new point.**",
"place_point": "To place the new point on the map, position your mouse cursor where the point should go, then left-click or press the spacebar. **Move the mouse pointer over this building, then left-click or press the spacebar.**",
"search_cafe": "There are many different features that can be represented by points. The point you just added is a cafe. **Search for '{preset}'**",
"choose_cafe": "**Choose {preset} from the list.**",
"feature_editor": "The point is now marked as a cafe. Using the feature editor, we can add more information about the cafe.",
"add_name": "In OpenStreetMap, all of the fields are optional, and it's OK to leave a field blank if you are unsure.{br}Let's pretend that you have local knowledge of this cafe, and you know its name. **Add a name for the cafe**",
"add_close": "The feature editor will remember all of your changes automatically. **When you are finished adding the name, hit escape, return, or click the {button} button to close the feature editor**",
"reselect": "Often points will already exist, but have mistakes or be incomplete. We can edit existing points. **Click to select the cafe you just created.**",
"update": "Let's fill in some more details for this cafe. You can change its name, add a cuisine, or add an address. **Change the cafe details**",
"update_close": "**When you are finished updating the cafe, hit escape, return, or click the {button} button to close the feature editor**",
"rightclick": "You can right-click on any feature to see the *edit menu*, which shows a list of editing operations that can be performed. **Right-click to select the point you created and show the edit menu.**",
"delete": "It's OK to delete features that don't exist in the real world.{br}Deleting a feature from OpenStreetMap removes it from the map that everyone uses, so you should make sure a feature is really gone before you delete it. **Click on the {button} button to delete the point.**",
"undo": "You can always undo any changes up until you save your edits to OpenStreetMap. **Click on the {button} button to undo the delete and get the point back.**",
"play": "Now that you know how to create and edit points, try creating a few more points for practice! **When you are ready to continue to the next chapter, click '{next}'.**"
},
"areas": {
"title": "Areas",
"add": "Areas are used to show the boundaries of features like lakes, buildings, and residential areas. They can be also be used for more detailed mapping of many features you might normally map as points. **Click the {button} Area button to add a new area.**",
"corner": "Areas are drawn by placing nodes that mark the boundary of the area. **Click to place a starting node on one of the corners of the playground.**",
"place": "Draw the area by placing more nodes. Finish the area by clicking on the starting node. **Draw an area for the playground.**",
"search": "**Search for '{name}'.**",
"choose": "**Choose Playground from the list.**",
"describe": "**Add a name, then click the {button} button to close the feature editor**"
"add_playground": "*Areas* are used to show the boundaries of features like lakes, buildings, and residential areas.{br}They can be also be used for more detailed mapping of many features you might normally map as points. **Click the {button} Area button to add a new area.**",
"start_playground": "Let's add this playground to the map by drawing an area. Areas are drawn by placing *nodes* along the outer edge of the feature. **Click or press spacebar to place a starting node on one of the corners of the playground.**",
"continue_playground": "Continue drawing the area by placing more nodes along the playground's edge. It is OK to connect the area to the existing walking paths.{br}Tip: You can hold down the Alt key to prevent nodes from connecting to other features. **Continue drawing an area for the playground.**",
"finish_playground": "Finish the area by pressing return, or clicking again on either the first or last node. **Finish drawing an area for the playground.**",
"search_playground": "**Search for '{preset}'.**",
"choose_playground": "**Choose {preset} from the list.**",
"add_field": "This playground doesn't have an official name, so we won't add anything in the Name field.{br}Instead let's add some additional details about the playground to the Description field. **Open the Add Field list.**",
"choose_field": "**Choose {field} from the list.**",
"retry_add_field": "You didn't select the {field} field. Let's try again.",
"describe_playground": "**Add a description, then click the {button} button to close the feature editor.**",
"play": "Good job! Try drawing a few more areas, and see what other kinds of area features you can add to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**"
},
"lines": {
"title": "Lines",
"add": "Lines are used to represent features such as roads, railroads, and rivers. **Click the {button} Line button to add a new line.**",
"start": "**Start the line by clicking on the end of the road.**",
"intersect": "Click to add more nodes to the line. You can drag the map while drawing if necessary. Roads, and many other types of lines, are part of a larger network. It is important for these lines to be connected properly in order for routing applications to work. **Click on {name} to create an intersection connecting the two lines.**",
"finish": "Lines can be finished by clicking on the last node again. **Finish drawing the road.**",
"road": "**Select Road from the list**",
"residential": "There are different types of roads, the most common of which is Residential. **Choose the Residential road type**",
"describe": "**Name the road, then click the {button} button to close the feature editor.**",
"restart": "The road needs to intersect {name}.",
"wrong_preset": "You didn't select the Residential road type. **Click here to choose again**"
"add_line": "*Lines* are used to represent features such as roads, railroads, and rivers. **Click the {button} Line button to add a new line.**",
"start_line": "Here is a road that is missing. Let's add it!{br}In OpenStreetMap, lines should be drawn down the center of the road. You can drag and zoom the map while drawing if necessary. **Start a new line by clicking at the top end of this missing road.**",
"intersect": "Click or press spacebar to add more nodes to the line.{br}Roads, and many other types of lines, are part of a larger network. It is important for these lines to be connected properly in order for routing applications to work. **Click on {name} to create an intersection connecting the two lines.**",
"retry_intersect": "The road needs to intersect {name}. Let's try again!",
"continue_line": "Continue drawing the line for the new road. Remember that you can drag and zoom the map if needed.{br}When you are finished drawing, click on the last node again. **Finish drawing the road.**",
"choose_category_road": "**Select {category} from the list**",
"choose_preset_residential": "There are many different types of roads, but this one is a residential road. **Choose the {preset} type**",
"retry_preset_residential": "You didn't select the {preset} type. **Click here to choose again**",
"name_road": "**Give this road a name, then hit escape, return, or click the {button} button to close the feature editor.**",
"did_name_road": "Looks good! Next we will learn how to update the shape of a line.",
"update_line": "Sometimes you will need to change the shape of an existing line. Here is a road that doesn't look quite right.",
"add_node": "We can add some nodes to this line to improve its shape. One way to add a node is to double-click the line where you want to add a node. **Double-click on the line to create a new node.**",
"start_drag_endpoint": "When a line is selected, you can drag any of its nodes by clicking and holding down the left mouse button while you drag. **Drag the endpoint to the place where these roads should intersect.**",
"finish_drag_endpoint": "This spot looks good. **Release the left mouse button to finish dragging.**",
"start_drag_midpoint": "Small triangles are drawn at the *midpoints* between nodes. Another way to create a new node is to drag a midpoint to a new location. **Drag the midpoint triangle to create a new node along the curve of the road.**",
"continue_drag_midpoint": "This line is looking much better! Continue to adjust this line by double-clicking or dragging midpoints until the curve matches the road shape. **When you're happy with how the line looks, click OK.**",
"delete_lines": "It's OK to delete lines for roads that don't exist in the real world.{br}Here's an example where the city planned a {street} but never built it. We can improve this part of the map by deleting the extra lines.",
"rightclick_intersection": "The last real street is {street1}, so we will *split* {street2} at this intersection and remove everything above it. **Right click on the intersection node**",
"split_intersection": "**Click on the {button} button to split {street}.**",
"retry_split": "You didn't click the Split button. Try again.",
"did_split_multi": "Good Job! {street1} is now split into two pieces. The top part can be removed. **Click the top part of {street2} to select it.**",
"did_split_single": "**Click the top part of {street2} to select it.**",
"multi_select": "{selected} is now selected. Let's also select {other1}. You can shift-click to select multiple things. **Shift-click on {other2}.**",
"multi_rightclick": "Good! Both lines to delete are now selected. **Right-click on one of the lines to show the edit menu.**",
"multi_delete": "**Click on the {button} button to delete the extra lines.**",
"retry_delete": "You didn't click the Delete button. Try again.",
"play": "Great! Use the skills that you've learned in this chapter to practice editing some more lines. **When you are ready to continue to the next chapter, click '{next}'.**"
},
"buildings": {
"title": "Buildings",
"add_building": "OpenStreetMap is the world's largest database of buildings.{br}You can help improve this database by tracing buildings that aren't already mapped. **Click the {button} Area button to add a new area.**",
"start_building": "Let's add this house to the map by tracing its outline.{br}Buildings should be traced around their footprint as accurately as possible. **Click or press spacebar to place a starting node on one of the corners of the building.**",
"continue_building": "Continue adding more nodes to trace the outline of the building. Remember that you can zoom in if you want to add more details.{br}Finish the building by pressing return, or clicking again on either the first or last node. **Finish tracing the building.**",
"retry_building": "It looks like you had some trouble placing the nodes at the building corners. Try again!",
"choose_category_building": "**Choose {category} from the list**",
"choose_preset_house": "There are many different types of buildings, but this one is clearly a house.{br}If you're not sure of the type, it's OK to just choose the generic Building type. **Choose the {preset} type**",
"close": "**Hit escape or click the {button} button to close the feature editor**",
"rightclick_building": "**Right-click to select the building you created and show the edit menu.**",
"square_building": "The house that you just added will look even better with perfectly square corners. **Click on the {button} button to square the building shape.**",
"retry_square": "You didn't click the Square button. Try again.",
"done_square": "See how the corners of the building moved into place? Let's learn another useful trick.",
"add_tank": "Next we'll trace this circular storage tank. **Click the {button} Area button to add a new area.**",
"start_tank": "Don't worry, you won't need to draw a perfect circle. Just draw an area inside the tank that touches its edge. **Click or press spacebar to place a starting node on the edge of the tank.**",
"continue_tank": "Add a few more nodes around the edge. The circle will be created outside the nodes that you draw.{br}Finish the area by pressing return, or clicking again on either the first or last node. **Finish tracing the tank.**",
"search_tank": "**Search for '{preset}'**",
"choose_tank": "**Choose {preset} from the list.**",
"rightclick_tank": "**Right-click to select the storage tank you created and show the edit menu.**",
"circle_tank": "**Click on the {button} button to make the tank a circle.**",
"retry_circle": "You didn't click the Circularize button. Try again.",
"play": "Great Job! Practice tracing a few more buildings, and try some of the other commands on the edit menu. **When you are ready to continue to the next chapter, click '{next}'.**"
},
"startediting": {
"title": "Start Editing",
"help": "You can replay this walkthrough or view more documentation by clicking the {button} Help button.",
"help": "You're now ready to edit OpenStreetMap!{br}You can replay this walkthrough anytime or view more documentation by clicking the {button} Help button.",
"save": "Don't forget to regularly save your changes!",
"start": "Start mapping!"
}
-1
View File
@@ -42,7 +42,6 @@ export function behaviorCopy(context) {
function doCopy() {
d3.event.preventDefault();
if (context.inIntro()) return;
var graph = context.graph(),
selected = groupEntities(context.selectedIDs(), graph),
+2 -2
View File
@@ -4,7 +4,7 @@ import { uiFlash } from '../ui';
/* Creates a keybinding behavior for an operation */
export function behaviorOperation(context) {
export function behaviorOperation() {
var which, keybinding;
@@ -33,7 +33,7 @@ export function behaviorOperation(context) {
var behavior = function () {
if (which && which.available() && !context.inIntro()) {
if (which && which.available()) {
keybinding = d3keybinding('behavior.key.' + which.id);
keybinding.on(which.keys, function() {
d3.event.preventDefault();
-1
View File
@@ -39,7 +39,6 @@ export function behaviorPaste(context) {
function doPaste() {
d3.event.preventDefault();
if (context.inIntro()) return;
var baseGraph = context.graph(),
mouse = context.mouse(),
+4 -3
View File
@@ -74,10 +74,11 @@ export function behaviorSelect(context) {
mode = context.mode();
if (datum.type === 'midpoint') {
// clicked midpoint, do nothing..
if (datum && datum.type === 'midpoint') {
datum = datum.parents[0];
}
} else if (!(datum instanceof osmEntity)) {
if (!(datum instanceof osmEntity)) {
// clicked nothing..
if (!isMultiselect && mode.id !== 'browse') {
context.enter(modeBrowse(context));
+4 -6
View File
@@ -257,12 +257,8 @@ export function coreContext() {
context.layers = function() { return map.layers; };
context.surface = function() { return map.surface; };
context.editable = function() { return map.editable(); };
context.surfaceRect = function() {
// Work around a bug in Firefox.
// http://stackoverflow.com/questions/18153989/
// https://bugzilla.mozilla.org/show_bug.cgi?id=530985
return context.surface().node().parentNode.getBoundingClientRect();
return map.surface.node().getBoundingClientRect();
};
@@ -289,13 +285,14 @@ export function coreContext() {
/* Container */
var container, embed;
var container = d3.select(document.body);
context.container = function(_) {
if (!arguments.length) return container;
container = _;
container.classed('id-container', true);
return context;
};
var embed;
context.embed = function(_) {
if (!arguments.length) return embed;
embed = _;
@@ -383,6 +380,7 @@ export function coreContext() {
context.version = '2.1.3';
context.projection = geoRawMercator();
context.curtainProjection = geoRawMercator();
locale = utilDetect().locale;
if (locale && !dataLocales.hasOwnProperty(locale)) {
+86 -4
View File
@@ -15,6 +15,7 @@ export function coreHistory(context) {
dispatch = d3.dispatch('change', 'undone', 'redone'),
lock = utilSessionMutex('lock'),
duration = 150,
checkpoints = {},
stack, index, tree;
@@ -278,15 +279,96 @@ export function coreHistory(context) {
},
reset: function() {
stack = [{graph: coreGraph()}];
index = 0;
tree = coreTree(stack[0].graph);
// save the current history state
checkpoint: function(key) {
checkpoints[key] = {
stack: _.cloneDeep(stack),
index: index
};
return history;
},
// restore history state to a given checkpoint or reset completely
reset: function(key) {
if (key !== undefined && checkpoints.hasOwnProperty(key)) {
stack = _.cloneDeep(checkpoints[key].stack);
index = checkpoints[key].index;
} else {
stack = [{graph: coreGraph()}];
index = 0;
tree = coreTree(stack[0].graph);
checkpoints = {};
}
dispatch.call('change');
return history;
},
toIntroGraph: function() {
var nextId = { n: 0, r: 0, w: 0 },
permIds = {},
graph = this.graph(),
baseEntities = {};
// clone base entities..
_.forEach(graph.base().entities, function(entity) {
var copy = _.cloneDeepWith(entity, customizer);
baseEntities[copy.id] = copy;
});
// replace base entities with head entities..
_.forEach(graph.entities, function(entity, id) {
if (entity) {
var copy = _.cloneDeepWith(entity, customizer);
baseEntities[copy.id] = copy;
} else {
delete baseEntities[id];
}
});
// swap temporary for permanent ids..
_.forEach(baseEntities, function(entity) {
if (Array.isArray(entity.nodes)) {
entity.nodes = entity.nodes.map(function(node) {
return permIds[node] || node;
});
}
if (Array.isArray(entity.members)) {
entity.members = entity.members.map(function(member) {
member.id = permIds[member.id] || member.id;
return member;
});
}
});
return JSON.stringify({ dataIntroGraph: baseEntities });
function customizer(src) {
var copy = _.omit(_.cloneDeep(src), ['type', 'user', 'v', 'version', 'visible']);
if (_.isEmpty(copy.tags)) {
delete copy.tags;
}
if (Array.isArray(copy.loc)) {
copy.loc[0] = +copy.loc[0].toFixed(6);
copy.loc[1] = +copy.loc[1].toFixed(6);
}
var match = src.id.match(/([nrw])-\d*/); // temporary id
if (match !== null) {
var nrw = match[1], permId;
do { permId = nrw + (++nextId[nrw]); }
while (baseEntities.hasOwnProperty(permId));
copy.id = permIds[src.id] = permId;
}
return copy;
}
},
toJSON: function() {
if (!this.hasChanges()) return;
+2 -1
View File
@@ -40,6 +40,7 @@ export { utilDetect as Detect } from './util/detect';
export var debug = false;
import * as d3 from 'd3';
import * as _ from 'lodash';
import * as lib from './lib/index';
export { d3, lib };
export { d3, _, lib };
+19 -12
View File
@@ -4,6 +4,7 @@ import { utilRebind } from '../../modules/util/rebind';
export function d3combobox() {
var event = d3.dispatch('accept'),
container = d3.select(document.body),
data = [],
suggestions = [],
minItems = 2,
@@ -20,10 +21,10 @@ export function d3combobox() {
var combobox = function(input, attachTo) {
var idx = -1,
container = d3.select(document.body)
wrapper = container
.selectAll('div.combobox')
.filter(function(d) { return d === input.node(); }),
shown = !container.empty();
shown = !wrapper.empty();
input
.classed('combobox-input', true)
@@ -70,7 +71,7 @@ export function d3combobox() {
function show() {
if (!shown) {
container = d3.select(document.body)
wrapper = container
.insert('div', ':first-child')
.datum(input.node())
.attr('class', 'combobox')
@@ -82,7 +83,7 @@ export function d3combobox() {
d3.event.preventDefault();
});
d3.select(document.body)
d3.select('body')
.on('scroll.combobox', render, true);
shown = true;
@@ -92,9 +93,9 @@ export function d3combobox() {
function hide() {
if (shown) {
idx = -1;
container.remove();
wrapper.remove();
d3.select(document.body)
d3.select('body')
.on('scroll.combobox', null);
shown = false;
@@ -116,7 +117,7 @@ export function d3combobox() {
break;
// tab
case 9:
container.selectAll('a.selected').each(function (d) {
wrapper.selectAll('a.selected').each(function (d) {
event.call('accept', this, d);
});
hide();
@@ -147,7 +148,7 @@ export function d3combobox() {
break;
// return
case 13:
container.selectAll('a.selected').each(function (d) {
wrapper.selectAll('a.selected').each(function (d) {
event.call('accept', this, d);
});
hide();
@@ -217,7 +218,7 @@ export function d3combobox() {
return;
}
var options = container
var options = wrapper
.selectAll('a.combobox-option')
.data(suggestions, function(d) { return d.value; });
@@ -239,7 +240,7 @@ export function d3combobox() {
var node = attachTo ? attachTo.node() : input.node(),
rect = node.getBoundingClientRect();
container
wrapper
.style('left', rect.left + 'px')
.style('width', rect.width + 'px')
.style('top', rect.height + rect.top + 'px');
@@ -251,7 +252,7 @@ export function d3combobox() {
}
function ensureVisible() {
var node = container.selectAll('a.selected').node();
var node = wrapper.selectAll('a.selected').node();
if (node) node.scrollIntoView();
}
@@ -289,6 +290,12 @@ export function d3combobox() {
return combobox;
};
combobox.container = function(_) {
if (!arguments.length) return container;
container = _;
return combobox;
};
return utilRebind(combobox, event, 'on');
}
@@ -306,6 +313,6 @@ d3combobox.off = function(input) {
.on('mousedown', null);
});
d3.select(document.body)
d3.select('body')
.on('scroll.combobox', null);
};
+1 -3
View File
@@ -301,9 +301,7 @@ export function modeSelect(context, selectedIDs) {
function esc() {
if (!context.inIntro()) {
context.enter(modeBrowse(context));
}
context.enter(modeBrowse(context));
}
+4 -4
View File
@@ -12,14 +12,14 @@ export function presetCollection(collection) {
item: function(id) {
return _.find(collection, function(d) {
return _.find(this.collection, function(d) {
return d.id === id;
});
},
matchGeometry: function(geometry) {
return presetCollection(collection.filter(function(d) {
return presetCollection(this.collection.filter(function(d) {
return d.matchGeometry(geometry);
}));
},
@@ -44,10 +44,10 @@ export function presetCollection(collection) {
value = value.toLowerCase();
var searchable = _.filter(collection, function(a) {
var searchable = _.filter(this.collection, function(a) {
return a.searchable !== false && a.suggestion !== true;
}),
suggestions = _.filter(collection, function(a) {
suggestions = _.filter(this.collection, function(a) {
return a.suggestion === true;
});
+6
View File
@@ -107,6 +107,12 @@ export function presetIndex() {
all.init = function() {
var d = data.presets;
all.collection = [];
recent.collection = [];
fields = {};
universal = [];
index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
if (d.fields) {
_.forEach(d.fields, function(d, id) {
fields[id] = presetField(id, d);
+2
View File
@@ -47,6 +47,8 @@ export function rendererBackground(context) {
background.updateImagery = function() {
if (context.inIntro()) return;
var b = background.baseLayerSource(),
o = overlayLayers
.filter(function (d) { return !d.source().isLocatorOverlay(); })
+13 -1
View File
@@ -33,6 +33,7 @@ export function rendererMap(context) {
var dimensions = [1, 1],
dispatch = d3.dispatch('move', 'drawn'),
projection = context.projection,
curtainProjection = context.curtainProjection,
dblclickEnabled = true,
redrawEnabled = true,
transformStart = projection.transform(),
@@ -73,7 +74,7 @@ export function rendererMap(context) {
context.history()
.on('change.map', immediateRedraw)
.on('undone.context redone.context', function(stack) {
.on('undone.map redone.map', function(stack) {
var followSelected = false;
if (Array.isArray(stack.selectedIDs)) {
followSelected = (stack.selectedIDs.length === 1 && stack.selectedIDs[0][0] === 'n');
@@ -323,6 +324,14 @@ export function rendererMap(context) {
tX = (eventTransform.x / scale - transformStart.x) * scale,
tY = (eventTransform.y / scale - transformStart.y) * scale;
if (context.inIntro()) {
curtainProjection.transform({
x: eventTransform.x - tX,
y: eventTransform.y - tY,
k: eventTransform.k
});
}
transformed = true;
transformLast = eventTransform;
utilSetTransform(supersurface, tX, tY, scale);
@@ -339,6 +348,9 @@ export function rendererMap(context) {
surface.selectAll('.edit-menu, .radial-menu').interrupt().remove();
utilSetTransform(supersurface, 0, 0);
transformed = false;
if (context.inIntro()) {
curtainProjection.transform(projection.transform());
}
return true;
}
+1
View File
@@ -89,6 +89,7 @@ export function uiCommit(context) {
commentField
.call(d3combobox()
.container(context.container())
.caseSensitive(true)
.data(_.uniqBy(comments, 'title'))
);
+276
View File
@@ -0,0 +1,276 @@
import * as d3 from 'd3';
import { textDirection } from '../util/locale';
import { uiToggle } from './toggle';
// Tooltips and svg mask used to highlight certain features
export function uiCurtain() {
var surface = d3.select(null),
tooltip = d3.select(null),
darkness = d3.select(null);
function curtain(selection) {
surface = selection
.append('svg')
.attr('id', 'curtain')
.style('z-index', 1000)
.style('pointer-events', 'none')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0);
darkness = surface.append('path')
.attr('x', 0)
.attr('y', 0)
.attr('class', 'curtain-darkness');
d3.select(window).on('resize.curtain', resize);
tooltip = selection.append('div')
.attr('class', 'tooltip')
.style('z-index', 1002);
tooltip
.append('div')
.attr('class', 'tooltip-arrow');
tooltip
.append('div')
.attr('class', 'tooltip-inner');
resize();
function resize() {
surface
.attr('width', window.innerWidth)
.attr('height', window.innerHeight);
curtain.cut(darkness.datum());
}
}
/**
* Reveal cuts the curtain to highlight the given box,
* and shows a tooltip with instructions next to the box.
*
* @param {String|ClientRect} [box] box used to cut the curtain
* @param {String} [text] text for a tooltip
* @param {Object} [options]
* @param {string} [options.tooltipClass] optional class to add to the tooltip
* @param {integer} [options.duration] transition time in milliseconds
* @param {string} [options.buttonText] if set, create a button with this text label
* @param {function} [options.buttonCallback] if set, the callback for the button
* @param {String|ClientRect} [options.tooltipBox] box for tooltip position, if different from box for the curtain
*/
curtain.reveal = function(box, text, options) {
if (typeof box === 'string') {
box = d3.select(box).node();
}
if (box && box.getBoundingClientRect) {
box = copyBox(box.getBoundingClientRect());
}
options = options || {};
var tooltipBox;
if (options.tooltipBox) {
tooltipBox = options.tooltipBox;
if (typeof tooltipBox === 'string') {
tooltipBox = d3.select(tooltipBox).node();
}
if (tooltipBox && tooltipBox.getBoundingClientRect) {
tooltipBox = copyBox(tooltipBox.getBoundingClientRect());
}
} else {
tooltipBox = box;
}
if (tooltipBox && text) {
// pseudo markdown bold text for the instruction section..
var parts = text.split('**');
var html = parts[0] ? '<span>' + parts[0] + '</span>' : '';
if (parts[1]) {
html += '<span class="instruction">' + parts[1] + '</span>';
}
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>'); // emphasis
html = html.replace(/\{br\}/g, '<br/><br/>'); // linebreak
if (options.buttonText && options.buttonCallback) {
html += '<div class="button-section">' +
'<button href="#" class="button action col8">' + options.buttonText + '</button></div>';
}
var classes = 'curtain-tooltip tooltip in ' + (options.tooltipClass || '');
tooltip
.classed(classes, true)
.selectAll('.tooltip-inner')
.html(html);
if (options.buttonText && options.buttonCallback) {
var button = tooltip.selectAll('.button-section .button.action');
button
.on('click', function() {
d3.event.preventDefault();
options.buttonCallback();
});
}
var tip = copyBox(tooltip.node().getBoundingClientRect()),
w = window.innerWidth,
h = window.innerHeight,
tooltipWidth = 200,
tooltipArrow = 5,
side, pos;
// hack: this will have bottom placement,
// so need to reserve extra space for the tooltip illustration.
if (options.tooltipClass === 'intro-mouse') {
tip.height += 80;
}
// trim box dimensions to just the portion that fits in the window..
if (tooltipBox.top + tooltipBox.height > h) {
tooltipBox.height -= (tooltipBox.top + tooltipBox.height - h);
}
if (tooltipBox.left + tooltipBox.width > w) {
tooltipBox.width -= (tooltipBox.left + tooltipBox.width - w);
}
// determine tooltip placement..
if (tooltipBox.top + tooltipBox.height < 100) {
// tooltip below box..
side = 'bottom';
pos = [
tooltipBox.left + tooltipBox.width / 2 - tip.width / 2,
tooltipBox.top + tooltipBox.height
];
} else if (tooltipBox.top > h - 140) {
// tooltip above box..
side = 'top';
pos = [
tooltipBox.left + tooltipBox.width / 2 - tip.width / 2,
tooltipBox.top - tip.height
];
} else {
// tooltip to the side of the tooltipBox..
var tipY = tooltipBox.top + tooltipBox.height / 2 - tip.height / 2;
if (textDirection === 'rtl') {
if (tooltipBox.left - tooltipWidth - tooltipArrow < 70) {
side = 'right';
pos = [tooltipBox.left + tooltipBox.width + tooltipArrow, tipY];
} else {
side = 'left';
pos = [tooltipBox.left - tooltipWidth - tooltipArrow, tipY];
}
} else {
if (tooltipBox.left + tooltipBox.width + tooltipArrow + tooltipWidth > w - 70) {
side = 'left';
pos = [tooltipBox.left - tooltipWidth - tooltipArrow, tipY];
}
else {
side = 'right';
pos = [tooltipBox.left + tooltipBox.width + tooltipArrow, tipY];
}
}
}
if (options.duration !== 0 || !tooltip.classed(side)) {
tooltip.call(uiToggle(true));
}
tooltip
.style('top', pos[1] + 'px')
.style('left', pos[0] + 'px')
.attr('class', classes + ' ' + side);
// shift tooltip-inner if it is very close to the top or bottom edge
// (doesn't affect the placement of the tooltip-arrow)
var shiftY = 0;
if (side === 'left' || side === 'right') {
if (pos[1] < 60) {
shiftY = 60 - pos[1];
}
else if (pos[1] + tip.height > h - 100) {
shiftY = h - pos[1] - tip.height - 100;
}
}
tooltip.selectAll('.tooltip-inner')
.style('top', shiftY + 'px');
} else {
tooltip
.classed('in', false)
.call(uiToggle(false));
}
curtain.cut(box, options.duration);
return tooltip;
};
curtain.cut = function(datum, duration) {
darkness.datum(datum)
.interrupt();
var selection;
if (duration === 0) {
selection = darkness;
} else {
selection = darkness
.transition()
.duration(duration || 600)
.ease(d3.easeLinear);
}
selection
.attr('d', function(d) {
var string = 'M 0,0 L 0,' + window.innerHeight + ' L ' +
window.innerWidth + ',' + window.innerHeight + 'L' +
window.innerWidth + ',0 Z';
if (!d) return string;
return string + 'M' +
d.left + ',' + d.top + 'L' +
d.left + ',' + (d.top + d.height) + 'L' +
(d.left + d.width) + ',' + (d.top + d.height) + 'L' +
(d.left + d.width) + ',' + (d.top) + 'Z';
});
};
curtain.remove = function() {
surface.remove();
tooltip.remove();
d3.select(window).on('resize.curtain', null);
};
// ClientRects are immutable, so copy them to an object,
// in case we need to trim the height/width.
function copyBox(src) {
return {
top: src.top,
right: src.right,
bottom: src.bottom,
left: src.left,
width: src.width,
height: src.height
};
}
return curtain;
}
+4 -2
View File
@@ -8,7 +8,7 @@ import {
} from '../../util';
export function uiFieldAccess(field) {
export function uiFieldAccess(field, context) {
var dispatch = d3.dispatch('change'),
items;
@@ -55,7 +55,9 @@ export function uiFieldAccess(field) {
.each(function(d) {
d3.select(this)
.call(d3combobox()
.data(access.options(d)));
.container(context.container())
.data(access.options(d))
);
});
+3 -1
View File
@@ -178,10 +178,12 @@ export function uiFieldAddress(field, context) {
wrap.selectAll('input.addr-' + tag)
.call(d3combobox()
.container(context.container())
.minItems(1)
.fetcher(function(value, callback) {
callback(nearValues('addr:' + tag));
}));
})
);
});
wrap.selectAll('input')
+3 -1
View File
@@ -27,7 +27,9 @@ export function uiFieldCombo(field, context) {
optstrings = field.strings && field.strings.options,
optarray = field.options,
snake_case = (field.snake_case || (field.snake_case === undefined)),
combobox = d3combobox().minItems(isMulti || isSemi ? 1 : 2),
combobox = d3combobox()
.container(context.container())
.minItems(isMulti || isSemi ? 1 : 2),
comboData = [],
multiData = [],
container,
+6 -2
View File
@@ -7,7 +7,7 @@ import {
} from '../../util';
export function uiFieldCycleway(field) {
export function uiFieldCycleway(field, context) {
var dispatch = d3.dispatch('change'),
items = d3.select(null);
@@ -57,7 +57,11 @@ export function uiFieldCycleway(field) {
.attr('class', function(d) { return 'preset-input-cycleway preset-input-' + stripcolon(d); })
.call(utilNoAuto)
.each(function(d) {
d3.select(this).call(d3combobox().data(cycleway.options(d)));
d3.select(this)
.call(d3combobox()
.container(context.container())
.data(cycleway.options(d))
);
});
+9 -4
View File
@@ -39,9 +39,11 @@ export function uiFieldLocalized(field, context) {
if (field.id === 'name') {
var preset = context.presets().match(entity, context.graph());
input.call(d3combobox().fetcher(
utilSuggestNames(preset, dataSuggestions)
));
input
.call(d3combobox()
.container(context.container())
.fetcher(utilSuggestNames(preset, dataSuggestions))
);
}
input
@@ -171,7 +173,10 @@ export function uiFieldLocalized(field, context) {
innerWrap.attr('class', 'entry')
.each(function() {
var wrap = d3.select(this);
var langcombo = d3combobox().fetcher(fetcher).minItems(0);
var langcombo = d3combobox()
.container(context.container())
.fetcher(fetcher)
.minItems(0);
var label = wrap
.append('label')
+6 -2
View File
@@ -23,8 +23,12 @@ export function uiFieldMaxspeed(field, context) {
function maxspeed(selection) {
combobox = d3combobox();
var unitCombobox = d3combobox().data(['km/h', 'mph'].map(comboValues));
combobox = d3combobox()
.container(context.container());
var unitCombobox = d3combobox()
.container(context.container())
.data(['km/h', 'mph'].map(comboValues));
input = selection.selectAll('#preset-input-' + field.id)
.data([0]);
+5 -3
View File
@@ -13,7 +13,7 @@ import {
export { uiFieldRadio as uiFieldStructureRadio };
export function uiFieldRadio(field) {
export function uiFieldRadio(field, context) {
var dispatch = d3.dispatch('change'),
taginfo = services.taginfo,
placeholder = d3.select(null),
@@ -26,7 +26,6 @@ export function uiFieldRadio(field) {
entity;
function selectedKey() {
var selector = '.form-field-structure .toggle-list label.active input',
node = d3.selectAll(selector);
@@ -157,7 +156,10 @@ export function uiFieldRadio(field) {
if (taginfo) {
typeInput
.call(d3combobox().fetcher(typeFetcher));
.call(d3combobox()
.container(context.container())
.fetcher(typeFetcher)
);
}
typeInput
+2
View File
@@ -27,6 +27,7 @@ export function uiFieldWikipedia(field, context) {
function wiki(selection) {
var langcombo = d3combobox()
.container(context.container())
.fetcher(function(value, cb) {
var v = value.toLowerCase();
@@ -40,6 +41,7 @@ export function uiFieldWikipedia(field, context) {
});
var titlecombo = d3combobox()
.container(context.container())
.fetcher(function(value, cb) {
if (!value) {
value = context.entity(entity.id).tags.name || '';
+1
View File
@@ -13,6 +13,7 @@ export function uiGeolocate(context) {
function click() {
if (context.inIntro()) return;
context.enter(modeBrowse(context));
context.container().call(locating);
navigator.geolocation.getCurrentPosition(success, error, geoOptions);
+16 -4
View File
@@ -129,7 +129,8 @@ export function uiHelp(context) {
function clickWalkthrough() {
d3.select(document.body).call(uiIntro(context));
if (context.inIntro()) return;
context.container().call(uiIntro(context));
setVisible(false);
}
@@ -159,12 +160,23 @@ export function uiHelp(context) {
.html(function(d) { return d.title; })
.on('click', clickHelp);
toc.append('li')
.attr('class','walkthrough')
var walkthrough = toc
.append('li')
.attr('class', 'walkthrough')
.append('a')
.text(t('splash.walkthrough'))
.on('click', clickWalkthrough);
walkthrough
.append('svg')
.attr('class', 'logo logo-walkthrough')
.append('use')
.attr('xlink:href', '#logo-walkthrough');
walkthrough
.append('div')
.text(t('splash.walkthrough'));
var content = pane.append('div')
.attr('class', 'left-content');
+1
View File
@@ -7,6 +7,7 @@ export { uiCommit } from './commit';
export { uiConfirm } from './confirm';
export { uiConflicts } from './conflicts';
export { uiContributors } from './contributors';
export { uiCurtain } from './curtain';
export { uiDisclosure } from './disclosure';
export { uiEditMenu } from './edit_menu';
export { uiEntityEditor } from './entity_editor';
+1 -3
View File
@@ -255,9 +255,7 @@ export function uiInit(context) {
function pan(d) {
return function() {
d3.event.preventDefault();
if (!context.inIntro()) {
context.pan(d, 100);
}
context.pan(d, 100);
};
}
+407 -68
View File
@@ -1,105 +1,444 @@
import * as d3 from 'd3';
import { t } from '../../util/locale';
import { modeBrowse, modeSelect } from '../../modes';
import { utilRebind } from '../../util/rebind';
import { utilBindOnce } from '../../util/bind_once';
import { icon, pad } from './helper';
import { icon, pad, transitionTime } from './helper';
export function uiIntroArea(context, reveal) {
var dispatch = d3.dispatch('done'),
timeout;
playground = [-85.63552, 41.94159],
playgroundPreset = context.presets().item('leisure/playground'),
descriptionField = context.presets().field('description'),
timeouts = [],
areaId;
var step = {
var chapter = {
title: 'intro.areas.title'
};
step.enter = function() {
var playground = [-85.63552, 41.94159],
corner = [-85.63565411045074, 41.9417715536927];
context.map().centerZoom(playground, 19);
reveal('button.add-area',
t('intro.areas.add', { button: icon('#icon-area', 'pre-text') }),
{ tooltipClass: 'intro-areas-add' });
context.on('enter.intro', addArea);
function timeout(f, t) {
timeouts.push(window.setTimeout(f, t));
}
function addArea(mode) {
if (mode.id !== 'add-area') return;
context.on('enter.intro', drawArea);
function eventCancel() {
d3.event.stopPropagation();
d3.event.preventDefault();
}
var padding = 120 * Math.pow(2, context.map().zoom() - 19);
var pointBox = pad(corner, padding, context);
reveal(pointBox, t('intro.areas.corner'));
context.map().on('move.intro', function() {
padding = 120 * Math.pow(2, context.map().zoom() - 19);
pointBox = pad(corner, padding, context);
reveal(pointBox, t('intro.areas.corner'), {duration: 0});
function revealPlayground(center, text, options) {
var padding = 180 * Math.pow(2, context.map().zoom() - 19.5);
var box = pad(center, padding, context);
reveal(box, text, options);
}
function addArea() {
context.enter(modeBrowse(context));
context.history().reset('initial');
areaId = null;
var msec = transitionTime(playground, context.map().center());
if (msec) { reveal(null, null, { duration: 0 }); }
context.map().zoom(19).centerEase(playground, msec);
timeout(function() {
var tooltip = reveal('button.add-area',
t('intro.areas.add_playground', { button: icon('#icon-area', 'pre-text') }));
tooltip.selectAll('.tooltip-inner')
.insert('svg', 'span')
.attr('class', 'tooltip-illustration')
.append('use')
.attr('xlink:href', '#landuse-images');
context.on('enter.intro', function(mode) {
if (mode.id !== 'add-area') return;
continueTo(startPlayground);
});
}
}, msec + 100);
function drawArea(mode) {
if (mode.id !== 'draw-area') return;
context.on('enter.intro', enterSelect);
var padding = 150 * Math.pow(2, context.map().zoom() - 19);
var pointBox = pad(playground, padding, context);
reveal(pointBox, t('intro.areas.place'));
context.map().on('move.intro', function() {
padding = 150 * Math.pow(2, context.map().zoom() - 19);
pointBox = pad(playground, padding, context);
reveal(pointBox, t('intro.areas.place'), {duration: 0});
});
}
function enterSelect(mode) {
if (mode.id !== 'select') return;
context.map().on('move.intro', null);
function continueTo(nextStep) {
context.on('enter.intro', null);
nextStep();
}
}
timeout = setTimeout(function() {
reveal('.preset-search-input',
t('intro.areas.search',
{ name: context.presets().item('leisure/playground').name() }));
d3.select('.preset-search-input').on('keyup.intro', keySearch);
}, 500);
function startPlayground() {
if (context.mode().id !== 'add-area') {
return chapter.restart();
}
areaId = null;
context.map().zoomEase(19.5, 500);
function keySearch() {
timeout(function() {
revealPlayground(playground,
t('intro.areas.start_playground'), { duration: 250 }
);
timeout(function() {
context.map().on('move.intro drawn.intro', function() {
revealPlayground(playground,
t('intro.areas.start_playground'), { duration: 0 }
);
});
context.on('enter.intro', function(mode) {
if (mode.id !== 'draw-area') return chapter.restart();
continueTo(continuePlayground);
});
}, 250); // after reveal
}, 550); // after easing
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function continuePlayground() {
if (context.mode().id !== 'draw-area') {
return chapter.restart();
}
areaId = null;
revealPlayground(playground,
t('intro.areas.continue_playground'), { duration: 250 }
);
timeout(function() {
context.map().on('move.intro drawn.intro', function() {
revealPlayground(playground,
t('intro.areas.continue_playground'), { duration: 0 }
);
});
}, 250); // after reveal
context.on('enter.intro', function(mode) {
if (mode.id === 'draw-area') {
var entity = context.hasEntity(context.selectedIDs()[0]);
if (entity && entity.nodes.length >= 6) {
return continueTo(finishPlayground);
} else {
return;
}
} else if (mode.id === 'select') {
areaId = context.selectedIDs()[0];
return continueTo(searchPresets);
} else {
return chapter.restart();
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function finishPlayground() {
if (context.mode().id !== 'draw-area') {
return chapter.restart();
}
areaId = null;
revealPlayground(playground,
t('intro.areas.finish_playground'), { duration: 250 }
);
timeout(function() {
context.map().on('move.intro drawn.intro', function() {
revealPlayground(playground,
t('intro.areas.finish_playground'), { duration: 0 }
);
});
}, 250); // after reveal
context.on('enter.intro', function(mode) {
if (mode.id === 'draw-area') {
return;
} else if (mode.id === 'select') {
areaId = context.selectedIDs()[0];
return continueTo(searchPresets);
} else {
return chapter.restart();
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function searchPresets() {
if (!areaId || !context.hasEntity(areaId)) {
return addArea();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
context.enter(modeSelect(context, [areaId]));
}
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.areas.search_playground', { preset: playgroundPreset.name() })
);
}, 400); // after preset list pane visible..
context.on('enter.intro', function(mode) {
if (!areaId || !context.hasEntity(areaId)) {
return continueTo(addArea);
}
var ids = context.selectedIDs();
if (mode.id !== 'select' || !ids.length || ids[0] !== areaId) {
// keep the user's area selected..
context.enter(modeSelect(context, [areaId]));
// 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.areas.search_playground', { preset: playgroundPreset.name() })
);
context.history().on('change.intro', null);
}
});
function checkPresetSearch() {
var first = d3.select('.preset-list-item:first-child');
if (first.classed('preset-leisure-playground')) {
reveal(first.select('.preset-list-button').node(), t('intro.areas.choose'));
utilBindOnce(context.history(), 'change.intro', selectedPreset);
d3.select('.preset-search-input').on('keyup.intro', null);
reveal(first.select('.preset-list-button').node(),
t('intro.areas.choose_playground', { preset: playgroundPreset.name() }),
{ duration: 300 }
);
d3.select('.preset-search-input')
.on('keydown.intro', eventCancel, true)
.on('keyup.intro', null);
context.history().on('change.intro', function() {
continueTo(clickAddField);
});
}
}
function selectedPreset() {
reveal('.pane',
t('intro.areas.describe', { button: icon('#icon-apply', 'pre-text') }));
context.on('exit.intro', function() {
dispatch.call('done');
});
function continueTo(nextStep) {
context.on('enter.intro', null);
context.history().on('change.intro', null);
d3.select('.preset-search-input').on('keydown.intro keyup.intro', null);
nextStep();
}
}
function clickAddField() {
if (!areaId || !context.hasEntity(areaId)) {
return addArea();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
return searchPresets();
}
timeout(function() {
// reset pane, in case user somehow happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
// It's possible for the user to add a description in a previous step..
// If they did this already, just continue to next step.
var entity = context.entity(areaId);
if (entity.tags.description) {
return continueTo(play);
}
reveal('.more-fields .combobox-input',
t('intro.areas.add_field'),
{ duration: 300 }
);
d3.select('.more-fields .combobox-input')
.on('click.intro', function() {
continueTo(chooseDescriptionField);
});
}, 400); // after editor pane visible
context.on('exit.intro', function() {
return continueTo(searchPresets);
});
function continueTo(nextStep) {
d3.select('.more-fields .combobox-input').on('click.intro', null);
context.on('exit.intro', null);
nextStep();
}
}
function chooseDescriptionField() {
if (!areaId || !context.hasEntity(areaId)) {
return addArea();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
return searchPresets();
}
// Make sure combobox is ready..
if (d3.select('div.combobox').empty()) {
return continueTo(clickAddField);
}
// Watch for the combobox to go away..
var watcher;
watcher = window.setInterval(function() {
if (d3.select('div.combobox').empty()) {
window.clearInterval(watcher);
timeout(function() {
if (d3.select('.form-field-description').empty()) {
continueTo(retryChooseDescription);
} else {
continueTo(describePlayground);
}
}, 300); // after description field added.
}
}, 300);
reveal('div.combobox',
t('intro.areas.choose_field', { field: descriptionField.label() }),
{ duration: 300 }
);
context.on('exit.intro', function() {
return continueTo(searchPresets);
});
function continueTo(nextStep) {
if (watcher) window.clearInterval(watcher);
context.on('exit.intro', null);
nextStep();
}
}
function describePlayground() {
if (!areaId || !context.hasEntity(areaId)) {
return addArea();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
return searchPresets();
}
// reset pane, in case user happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
if (d3.select('.form-field-description').empty()) {
return continueTo(retryChooseDescription);
}
context.on('exit.intro', function() {
continueTo(play);
});
reveal('.entity-editor-pane',
t('intro.areas.describe_playground', { button: icon('#icon-apply', 'pre-text') }),
{ duration: 300 }
);
function continueTo(nextStep) {
context.on('exit.intro', null);
nextStep();
}
}
function retryChooseDescription() {
if (!areaId || !context.hasEntity(areaId)) {
return addArea();
}
var ids = context.selectedIDs();
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
return searchPresets();
}
// reset pane, in case user happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
reveal('.entity-editor-pane',
t('intro.areas.retry_add_field', { field: descriptionField.label() }), {
buttonText: t('intro.ok'),
buttonCallback: function() { continueTo(clickAddField); }
});
context.on('exit.intro', function() {
return continueTo(searchPresets);
});
function continueTo(nextStep) {
context.on('exit.intro', null);
nextStep();
}
}
function play() {
dispatch.call('done');
reveal('#id-container',
t('intro.areas.play', { next: t('intro.lines.title') }), {
tooltipBox: '.intro-nav-wrap .chapter-line',
buttonText: t('intro.ok'),
buttonCallback: function() { reveal('#id-container'); }
}
);
}
chapter.enter = function() {
addArea();
};
step.exit = function() {
window.clearTimeout(timeout);
context.on('enter.intro', null);
context.on('exit.intro', null);
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);
context.map().on('move.intro', null);
d3.select('.preset-search-input').on('keyup.intro', null);
d3.select('.preset-search-input').on('keydown.intro keyup.intro', null);
d3.select('.more-fields .combobox-input').on('click.intro', null);
};
return utilRebind(step, dispatch, 'on');
chapter.restart = function() {
chapter.exit();
chapter.enter();
};
return utilRebind(chapter, dispatch, 'on');
}
+772
View File
@@ -0,0 +1,772 @@
import * as d3 from 'd3';
import _ from 'lodash';
import { t, textDirection } from '../../util/locale';
import { modeBrowse, modeSelect } from '../../modes';
import { utilRebind } from '../../util/rebind';
import { icon, pad, isMostlySquare, selectMenuItem, transitionTime } from './helper';
export function uiIntroBuilding(context, reveal) {
var dispatch = d3.dispatch('done'),
house = [-85.62815, 41.95638],
tank = [-85.62732, 41.95347],
buildingCatetory = context.presets().item('category-building'),
housePreset = context.presets().item('building/house'),
tankPreset = context.presets().item('man_made/storage_tank'),
timeouts = [],
houseId = null,
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().zoom(19).centerEase(house, msec);
timeout(function() {
var tooltip = reveal('button.add-area',
t('intro.buildings.add_building', { button: icon('#icon-area', 'pre-text') }));
tooltip.selectAll('.tooltip-inner')
.insert('svg', 'span')
.attr('class', 'tooltip-illustration')
.append('use')
.attr('xlink:href', '#building-images');
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(),
way = context.entity(context.selectedIDs()[0]),
nodes = graph.childNodes(way),
points = _.uniq(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]));
}
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('.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]));
}
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('.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('#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');
context.map().centerEase(house, 500);
timeout(function() {
if (context.map().zoom() < 20) {
context.map().zoomEase(20, 500);
}
}, 520);
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('#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('#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().zoom(19.5).centerEase(tank, msec);
timeout(function() {
reveal('button.add-area',
t('intro.buildings.add_tank', { button: icon('#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]));
}
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%');
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) {
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('#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('#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('#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('.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');
}
+142 -13
View File
@@ -1,24 +1,33 @@
export function pointBox(point, context) {
import * as d3 from 'd3';
import { t } from '../../util/locale';
import { geoSphericalDistance } from '../../geo';
export function pointBox(loc, context) {
var rect = context.surfaceRect();
point = context.projection(point);
var point = context.curtainProjection(loc);
return {
left: point[0] + rect.left - 30,
top: point[1] + rect.top - 50,
width: 60,
height: 70
};
left: point[0] + rect.left - 40,
top: point[1] + rect.top - 60,
width: 80,
height: 90
};
}
export function pad(box, padding, context) {
if (box instanceof Array) {
export function pad(locOrBox, padding, context) {
var box;
if (locOrBox instanceof Array) {
var rect = context.surfaceRect();
box = context.projection(box);
var point = context.curtainProjection(locOrBox);
box = {
left: box[0] + rect.left,
top: box[1] + rect.top
left: point[0] + rect.left,
top: point[1] + rect.top
};
} else {
box = locOrBox;
}
return {
left: box.left - padding,
top: box.top - padding,
@@ -31,4 +40,124 @@ export function pad(box, padding, context) {
export function icon(name, svgklass) {
return '<svg class="icon ' + (svgklass || '') + '">' +
'<use xlink:href="' + name + '"></use></svg>';
}
}
function slugify(text) {
return text.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
}
export var missingStrings = {};
function checkKey(key, text) {
if (t(key, { default: undefined}) === undefined) {
if (missingStrings.hasOwnProperty(key)) return; // warn once
missingStrings[key] = text;
var missing = key + ': ' + text;
if (typeof console !== 'undefined') console.log(missing); // eslint-disable-line
}
}
export function localize(obj) {
var key;
var name = obj.tags && obj.tags.name;
if (name) {
key = 'intro.graph.name.' + slugify(name);
obj.tags.name = t(key, { default: name });
checkKey(key, name);
}
var city = obj.tags && obj.tags['addr:city'];
if (city) {
key = 'intro.graph.city';
obj.tags['addr:city'] = t(key, { default: city });
checkKey(key, city);
}
var state = obj.tags && obj.tags['addr:state'];
if (state) {
key = 'intro.graph.state';
obj.tags['addr:state'] = t(key, { default: state });
checkKey(key, state);
}
var postcode = obj.tags && obj.tags['addr:postcode'];
if (postcode) {
key = 'intro.graph.postcode';
obj.tags['addr:postcode'] = t(key, { default: postcode });
checkKey(key, postcode);
}
return obj;
}
// Used to detect squareness.. some duplicataion of code from actionOrthogonalize.
export function isMostlySquare(points) {
// note: uses 15 here instead of the 12 from actionOrthogonalize because
// actionOrthogonalize can actually straighten some larger angles as it iterates
var threshold = 15, // degrees within right or straight
lowerBound = Math.cos((90 - threshold) * Math.PI / 180), // near right
upperBound = Math.cos(threshold * Math.PI / 180), // near straight
mag;
for (var i = 0; i < points.length; i++) {
mag = Math.abs(normalizedDotProduct(i, points));
if (mag > lowerBound && mag < upperBound) {
return false;
}
}
return true;
function normalizedDotProduct(i, points) {
var a = points[(i - 1 + points.length) % points.length],
b = points[i],
c = points[(i + 1) % points.length],
p = subtractPoints(a, b),
q = subtractPoints(c, b);
p = normalizePoint(p);
q = normalizePoint(q);
return p[0] * q[0] + p[1] * q[1];
function subtractPoints(a, b) {
return [a[0] - b[0], a[1] - b[1]];
}
function normalizePoint(point) {
var vector = [0, 0];
var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]);
if (length !== 0) {
vector[0] = point[0] / length;
vector[1] = point[1] / length;
}
return vector;
}
}
}
export function selectMenuItem(operation) {
var selector = '.edit-menu .edit-menu-item-' + operation +
', .radial-menu .radial-menu-item-' + operation;
return d3.select(selector);
}
export function transitionTime(point1, point2) {
var distance = geoSphericalDistance(point1, point2);
if (distance === 0)
return 0;
else if (distance < 80)
return 500;
else
return 1000;
}
+68 -100
View File
@@ -1,87 +1,52 @@
import * as d3 from 'd3';
import { t } from '../../util/locale';
import { coreGraph } from '../../core/graph';
import { modeBrowse } from '../../modes/index';
import { osmEntity } from '../../osm/entity';
import { d3curtain } from '../../util/curtain';
import { dataIntroGraph } from '../../../data/intro_graph.json';
import { t, textDirection } from '../../util/locale';
import { localize } from './helper';
import { coreGraph } from '../../core/graph';
import { dataIntroGraph } from '../../../data/intro_graph.json';
import { modeBrowse } from '../../modes/browse';
import { osmEntity } from '../../osm/entity';
import { svgIcon } from '../../svg/icon';
import { uiCurtain } from '../curtain';
import { uiIntroWelcome } from './welcome';
import { uiIntroNavigation } from './navigation';
import { uiIntroPoint } from './point';
import { uiIntroArea } from './area';
import { uiIntroLine } from './line';
import { uiIntroBuilding } from './building';
import { uiIntroStartEditing } from './start_editing';
var sampleIntros = {
var chapterUi = {
welcome: uiIntroWelcome,
navigation: uiIntroNavigation,
point: uiIntroPoint,
area: uiIntroArea,
line: uiIntroLine,
building: uiIntroBuilding,
startEditing: uiIntroStartEditing
};
var chapterFlow = [
'welcome',
'navigation',
'point',
'area',
'line',
'building',
'startEditing'
];
export function uiIntro(context) {
var step;
function localizedName(id) {
var features = {
n2140018997: 'city_hall',
n367813436: 'fire_department',
w203988286: 'memory_isle_park',
w203972937: 'riverwalk_trail',
w203972938: 'riverwalk_trail',
w203972940: 'riverwalk_trail',
w41785752: 'w_michigan_ave',
w134150789: 'w_michigan_ave',
w134150795: 'w_michigan_ave',
w134150800: 'w_michigan_ave',
w134150811: 'w_michigan_ave',
w134150802: 'e_michigan_ave',
w134150836: 'e_michigan_ave',
w41074896: 'e_michigan_ave',
w17965834: 'spring_st',
w203986457: 'scidmore_park',
w203049587: 'petting_zoo',
w17967397: 'n_andrews_st',
w17967315: 's_andrews_st',
w17967326: 'n_constantine_st',
w17966400: 's_constantine_st',
w170848823: 'rocky_river',
w170848824: 'rocky_river',
w170848331: 'rocky_river',
w17967752: 'railroad_dr',
w17965998: 'conrail_rr',
w134150845: 'conrail_rr',
w170989131: 'st_joseph_river',
w143497377: 'n_main_st',
w134150801: 's_main_st',
w134150830: 's_main_st',
w17966462: 's_main_st',
w17967734: 'water_st',
w17964996: 'foster_st',
w170848330: 'portage_river',
w17965351: 'flower_st',
w17965502: 'elm_st',
w17965402: 'walnut_st',
w17964793: 'morris_ave',
w17967444: 'east_st',
w17966984: 'portage_ave'
};
return features[id] && t('intro.graph.' + features[id]);
}
var introGraph = {},
currChapter;
var introGraph = {};
for (var key in dataIntroGraph) {
introGraph[key] = osmEntity(dataIntroGraph[key]);
var name = localizedName(key);
if (name) {
introGraph[key].tags.name = name;
}
// create entities for intro graph and localize names
for (var id in dataIntroGraph) {
introGraph[id] = osmEntity(localize(dataIntroGraph[id]));
}
@@ -104,37 +69,34 @@ export function uiIntro(context) {
// Load semi-real data used in intro
context.connection().toggle(false).reset();
context.history().reset();
context.history().merge(d3.values(coreGraph().load(introGraph).entities));
context.history().checkpoint('initial');
context.background().bing();
d3.selectAll('#map .layer-background').style('opacity', 1);
var curtain = d3curtain();
var curtain = uiCurtain();
selection.call(curtain);
function reveal(box, text, options) {
options = options || {};
curtain.reveal(box,
text || '',
options.tooltipClass || '',
options.duration
);
}
var steps = ['navigation', 'point', 'area', 'line', 'startEditing'].map(function(step, i) {
var s = sampleIntros[step](context, reveal)
var chapters = chapterFlow.map(function(chapter, i) {
var s = chapterUi[chapter](context, curtain.reveal)
.on('done', function() {
entered.filter(function(d) {
context.presets().init(); // clear away "recent" presets
buttons.filter(function(d) {
return d.title === s.title;
}).classed('finished', true);
enter(steps[i + 1]);
if (i < chapterFlow.length - 1) {
var next = chapterFlow[i + 1];
d3.select('button.chapter-' + next)
.classed('next', true);
}
});
return s;
});
steps[steps.length - 1].on('startEditing', function() {
chapters[chapters.length - 1].on('startEditing', function() {
curtain.remove();
navwrap.remove();
d3.selectAll('#map .layer-background').style('opacity', opacity);
@@ -151,43 +113,49 @@ export function uiIntro(context) {
.append('div')
.attr('class', 'intro-nav-wrap fillD');
navwrap
.append('svg')
.attr('class', 'intro-nav-wrap-logo')
.append('use')
.attr('xlink:href', '#logo-walkthrough');
var buttonwrap = navwrap
.append('div')
.attr('class', 'joined')
.selectAll('button.step');
.selectAll('button.chapter');
var entered = buttonwrap
.data(steps)
var buttons = buttonwrap
.data(chapters)
.enter()
.append('button')
.attr('class', 'step')
.on('click', enter);
.attr('class', function(d, i) { return 'chapter chapter-' + chapterFlow[i]; })
.on('click', enterChapter);
entered
.append('label')
buttons
.append('span')
.text(function(d) { return t(d.title); });
entered
buttons
.append('span')
.attr('class', 'status')
.text(' - ' + t('intro.done'));
.call(svgIcon((textDirection === 'rtl' ? '#icon-backward' : '#icon-forward'), 'inline'));
enter(steps[0]);
enterChapter(chapters[0]);
function enter(newStep) {
if (step) { step.exit(); }
function enterChapter(newChapter) {
if (currChapter) { currChapter.exit(); }
context.enter(modeBrowse(context));
step = newStep;
step.enter();
currChapter = newChapter;
currChapter.enter();
entered.classed('active', function(d) {
return d.title === step.title;
});
buttons
.classed('next', false)
.classed('active', function(d) {
return d.title === currChapter.title;
});
}
}
+1015 -143
View File
File diff suppressed because it is too large Load Diff
+503 -84
View File
@@ -1,20 +1,28 @@
import * as d3 from 'd3';
import _ from 'lodash';
import { t } from '../../util/locale';
import { t, textDirection } from '../../util/locale';
import { modeBrowse, modeSelect } from '../../modes';
import { utilRebind } from '../../util/rebind';
import { icon, pointBox } from './helper';
import { icon, pointBox, transitionTime } from './helper';
export function uiIntroNavigation(context, reveal) {
var dispatch = d3.dispatch('done'),
timeouts = [];
timeouts = [],
hallId = 'n2061',
townHall = [-85.63591, 41.94285],
springStreetId = 'w397',
springStreetEndId = 'n1834',
springStreet = [-85.63582, 41.94255],
onewayField = context.presets().field('oneway'),
maxspeedField = context.presets().field('maxspeed');
var step = {
var chapter = {
title: 'intro.navigation.title'
};
function set(f, t) {
function timeout(f, t) {
timeouts.push(window.setTimeout(f, t));
}
@@ -25,109 +33,520 @@ export function uiIntroNavigation(context, reveal) {
}
step.enter = function() {
var rect = context.surfaceRect(),
map = {
left: rect.left + 10,
top: rect.top + 70,
width: rect.width - 70,
height: rect.height - 170
};
context.map().centerZoom([-85.63591, 41.94285], 19);
reveal(map, t('intro.navigation.drag'));
context.map().on('move.intro', _.debounce(function() {
context.map().on('move.intro', null);
townhall();
context.on('enter.intro', inspectTownHall);
}, 400));
function isTownHallSelected() {
var ids = context.selectedIDs();
return ids.length === 1 && ids[0] === hallId;
}
function townhall() {
var hall = [-85.63645945147184, 41.942986488012565];
var point = context.projection(hall);
function trimmedMap() {
var rect = d3.select('#map').node().getBoundingClientRect();
return {
left: rect.left + (textDirection === 'rtl' ? 60 : 10),
top: rect.top + 70,
width: rect.width - 60,
height: rect.height - 170
};
}
if (point[0] < 0 || point[0] > rect.width ||
point[1] < 0 || point[1] > rect.height) {
context.map().center(hall);
}
var box = pointBox(hall, context);
reveal(box, t('intro.navigation.select'));
function dragMap() {
context.enter(modeBrowse(context));
context.history().reset('initial');
var msec = transitionTime(townHall, context.map().center());
if (msec) { reveal(null, null, { duration: 0 }); }
context.map().zoom(19).centerEase(townHall, msec);
timeout(function() {
var centerStart = context.map().center();
reveal(trimmedMap(), t('intro.navigation.drag'));
context.map().on('drawn.intro', function() {
reveal(trimmedMap(), t('intro.navigation.drag'), { duration: 0 });
});
context.map().on('move.intro', function() {
var box = pointBox(hall, context);
reveal(box, t('intro.navigation.select'), {duration: 0});
var centerNow = context.map().center();
if (centerStart[0] !== centerNow[0] || centerStart[1] !== centerNow[1]) {
context.map().on('move.intro', null);
timeout(function() { continueTo(zoomMap); }, 1500);
}
});
}, msec + 100);
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
nextStep();
}
}
function inspectTownHall(mode) {
if (mode.id !== 'select') return;
context.on('enter.intro', null);
context.map().on('move.intro', null);
set(function() {
reveal('.entity-editor-pane',
t('intro.navigation.pane', { button: icon('#icon-close', 'pre-text') }));
context.on('exit.intro', streetSearch);
}, 700);
}
function zoomMap() {
var zoomStart = context.map().zoom();
reveal(trimmedMap(), t('intro.navigation.zoom'));
context.map().on('drawn.intro', function() {
reveal(trimmedMap(), t('intro.navigation.zoom'), { duration: 0 });
});
function streetSearch() {
context.on('exit.intro', null);
reveal('.search-header input',
t('intro.navigation.search', { name: t('intro.graph.spring_st') }));
d3.select('.search-header input')
.on('keyup.intro', searchResult);
}
function searchResult() {
var first = d3.select('.feature-list-item:nth-child(0n+2)'), // skip No Results item
firstName = first.select('.entity-name'),
name = t('intro.graph.spring_st');
if (!firstName.empty() && firstName.text() === name) {
reveal(first.node(), t('intro.navigation.choose', { name: name }));
context.on('exit.intro', selectedStreet);
d3.select('.search-header input')
.on('keydown.intro', eventCancel, true)
.on('keyup.intro', null);
context.map().on('move.intro', function() {
if (context.map().zoom() !== zoomStart) {
context.map().on('move.intro', null);
timeout(function() { continueTo(features); }, 1500);
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
nextStep();
}
}
function selectedStreet() {
var springSt = [-85.63585099140167, 41.942506848938926];
context.map().center(springSt);
function features() {
var onClick = function() { continueTo(pointsLinesAreas); };
reveal(trimmedMap(), t('intro.navigation.features'),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
context.map().on('drawn.intro', function() {
reveal(trimmedMap(), t('intro.navigation.features'),
{ duration: 0, buttonText: t('intro.ok'), buttonCallback: onClick }
);
});
function continueTo(nextStep) {
context.map().on('drawn.intro', null);
nextStep();
}
}
function pointsLinesAreas() {
var onClick = function() { continueTo(nodesWays); };
reveal(trimmedMap(), t('intro.navigation.points_lines_areas'),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
context.map().on('drawn.intro', function() {
reveal(trimmedMap(), t('intro.navigation.points_lines_areas'),
{ duration: 0, buttonText: t('intro.ok'), buttonCallback: onClick }
);
});
function continueTo(nextStep) {
context.map().on('drawn.intro', null);
nextStep();
}
}
function nodesWays() {
var onClick = function() { continueTo(clickTownHall); };
reveal(trimmedMap(), t('intro.navigation.nodes_ways'),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
context.map().on('drawn.intro', function() {
reveal(trimmedMap(), t('intro.navigation.nodes_ways'),
{ duration: 0, buttonText: t('intro.ok'), buttonCallback: onClick }
);
});
function continueTo(nextStep) {
context.map().on('drawn.intro', null);
nextStep();
}
}
function clickTownHall() {
context.enter(modeBrowse(context));
context.history().reset('initial');
reveal(null, null, { duration: 0 });
context.map().zoomEase(19, 500);
timeout(function() {
var entity = context.hasEntity(hallId);
if (!entity) return;
context.map().centerEase(entity.loc, 500);
timeout(function() {
var entity = context.hasEntity(hallId);
if (!entity) return;
var box = pointBox(entity.loc, context);
reveal(box, t('intro.navigation.click_townhall'));
context.map().on('move.intro drawn.intro', function() {
var entity = context.hasEntity(hallId);
if (!entity) return;
var box = pointBox(entity.loc, context);
reveal(box, t('intro.navigation.click_townhall'), { duration: 0 });
});
context.on('enter.intro', function() {
if (isTownHallSelected()) continueTo(selectedTownHall);
});
}, 550); // after centerEase
}, 550); // after zoomEase
context.history().on('change.intro', function() {
if (!context.hasEntity(hallId)) {
continueTo(clickTownHall);
}
});
function continueTo(nextStep) {
context.on('enter.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function selectedTownHall() {
if (!isTownHallSelected()) return clickTownHall();
var entity = context.hasEntity(hallId);
if (!entity) return clickTownHall();
var box = pointBox(entity.loc, context);
var onClick = function() { continueTo(editorTownHall); };
reveal(box, t('intro.navigation.selected_townhall'),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
context.map().on('move.intro drawn.intro', function() {
var entity = context.hasEntity(hallId);
if (!entity) return;
var box = pointBox(entity.loc, context);
reveal(box, t('intro.navigation.selected_townhall'),
{ duration: 0, buttonText: t('intro.ok'), buttonCallback: onClick }
);
});
context.history().on('change.intro', function() {
if (!context.hasEntity(hallId)) {
continueTo(clickTownHall);
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function editorTownHall() {
if (!isTownHallSelected()) return clickTownHall();
var onClick = function() { continueTo(presetTownHall); };
reveal('.entity-editor-pane',
t('intro.navigation.editor_townhall'),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
context.on('exit.intro', function() {
continueTo(clickTownHall);
});
context.history().on('change.intro', function() {
if (!context.hasEntity(hallId)) {
continueTo(clickTownHall);
}
});
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function presetTownHall() {
if (!isTownHallSelected()) return clickTownHall();
// reset pane, in case user happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
// preset match, in case the user happened to change it.
var entity = context.entity(context.selectedIDs()[0]);
var preset = context.presets().match(entity, context.graph());
var onClick = function() { continueTo(fieldsTownHall); };
context.on('exit.intro', function() {
continueTo(clickTownHall);
});
context.history().on('change.intro', function() {
if (!context.hasEntity(hallId)) {
continueTo(clickTownHall);
}
});
reveal('.inspector-body .preset-list-item.inspector-inner',
t('intro.navigation.preset_townhall', { preset: preset.name() }),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function fieldsTownHall() {
if (!isTownHallSelected()) return clickTownHall();
// reset pane, in case user happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
var onClick = function() { continueTo(closeTownHall); };
reveal('.inspector-body .inspector-preset',
t('intro.navigation.fields_townhall'),
{ buttonText: t('intro.ok'), buttonCallback: onClick }
);
context.on('exit.intro', function() {
continueTo(clickTownHall);
});
context.history().on('change.intro', function() {
if (!context.hasEntity(hallId)) {
continueTo(clickTownHall);
}
});
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function closeTownHall() {
if (!isTownHallSelected()) return clickTownHall();
var selector = '.entity-editor-pane button.preset-close svg use';
var href = d3.select(selector).attr('href') || '#icon-close';
reveal('.entity-editor-pane',
t('intro.navigation.close_townhall', { button: icon(href, 'pre-text') })
);
context.on('exit.intro', function() {
continueTo(searchStreet);
});
context.history().on('change.intro', function() {
// update the close icon in the tooltip if the user edits something.
var selector = '.entity-editor-pane button.preset-close svg use';
var href = d3.select(selector).attr('href') || '#icon-close';
reveal('.entity-editor-pane',
t('intro.navigation.close_townhall', { button: icon(href, 'pre-text') }),
{ duration: 0 }
);
});
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function searchStreet() {
context.enter(modeBrowse(context));
context.history().reset('initial'); // ensure spring street exists
var msec = transitionTime(springStreet, context.map().center());
if (msec) { reveal(null, null, { duration: 0 }); }
context.map().zoom(19).centerEase(springStreet, msec); // ..and user can see it
timeout(function() {
reveal('.search-header input',
t('intro.navigation.search_street', { name: t('intro.graph.name.spring-street') })
);
d3.select('.search-header input')
.on('keyup.intro', checkSearchResult);
}, msec + 100);
}
function checkSearchResult() {
var first = d3.select('.feature-list-item:nth-child(0n+2)'), // skip "No Results" item
firstName = first.select('.entity-name'),
name = t('intro.graph.name.spring-street');
if (!firstName.empty() && firstName.text() === name) {
reveal(first.node(),
t('intro.navigation.choose_street', { name: name }),
{ duration: 300 }
);
context.on('exit.intro', function() {
dispatch.call('done');
continueTo(selectedStreet);
});
set(function() {
reveal('.entity-editor-pane',
t('intro.navigation.chosen', {
name: t('intro.graph.spring_st'),
button: icon('#icon-close', 'pre-text')
}));
}, 400);
d3.select('.search-header input')
.on('keydown.intro', eventCancel, true)
.on('keyup.intro', null);
}
function continueTo(nextStep) {
context.on('exit.intro', null);
d3.select('.search-header input')
.on('keydown.intro', null)
.on('keyup.intro', null);
nextStep();
}
}
function selectedStreet() {
if (!context.hasEntity(springStreetEndId) || !context.hasEntity(springStreetId)) {
return searchStreet();
}
var onClick = function() { continueTo(editorStreet); };
var entity = context.entity(springStreetEndId);
var box = pointBox(entity.loc, context);
box.height = 500;
reveal(box,
t('intro.navigation.selected_street', { name: t('intro.graph.name.spring-street') }),
{ duration: 600, buttonText: t('intro.ok'), buttonCallback: onClick }
);
timeout(function() {
context.map().on('move.intro drawn.intro', function() {
var entity = context.hasEntity(springStreetEndId);
if (!entity) return;
var box = pointBox(entity.loc, context);
box.height = 500;
reveal(box,
t('intro.navigation.selected_street', { name: t('intro.graph.name.spring-street') }),
{ duration: 0, buttonText: t('intro.ok'), buttonCallback: onClick }
);
});
}, 600); // after reveal.
context.on('enter.intro', function(mode) {
if (!context.hasEntity(springStreetId)) {
return continueTo(searchStreet);
}
var ids = context.selectedIDs();
if (mode.id !== 'select' || !ids.length || ids[0] !== springStreetId) {
// keep Spring Street selected..
context.enter(modeSelect(context, [springStreetId]));
}
});
context.history().on('change.intro', function() {
if (!context.hasEntity(springStreetEndId) || !context.hasEntity(springStreetId)) {
timeout(function() {
continueTo(searchStreet);
}, 300); // after any transition (e.g. if user deleted intersection)
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function editorStreet() {
var selector = '.entity-editor-pane button.preset-close svg use';
var href = d3.select(selector).attr('href') || '#icon-close';
reveal('.entity-editor-pane',
t('intro.navigation.editor_street', {
button: icon(href, 'pre-text'),
field1: onewayField.label().toLowerCase(),
field2: maxspeedField.label().toLowerCase()
})
);
context.on('exit.intro', function() {
continueTo(play);
});
context.history().on('change.intro', function() {
// update the close icon in the tooltip if the user edits something.
var selector = '.entity-editor-pane button.preset-close svg use';
var href = d3.select(selector).attr('href') || '#icon-close';
reveal('.entity-editor-pane',
t('intro.navigation.editor_street', {
button: icon(href, 'pre-text'),
field1: onewayField.label().toLowerCase(),
field2: maxspeedField.label().toLowerCase()
}), { duration: 0 }
);
});
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function play() {
dispatch.call('done');
reveal('#id-container',
t('intro.navigation.play', { next: t('intro.points.title') }), {
tooltipBox: '.intro-nav-wrap .chapter-point',
buttonText: t('intro.ok'),
buttonCallback: function() { reveal('#id-container'); }
}
);
}
chapter.enter = function() {
dragMap();
};
step.exit = function() {
chapter.exit = function() {
timeouts.forEach(window.clearTimeout);
context.map().on('move.intro', null);
context.on('enter.intro', null);
context.on('exit.intro', null);
d3.select('.search-header input')
.on('keydown.intro', null)
.on('keyup.intro', null);
context.on('enter.intro exit.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
d3.select('.search-header input').on('keydown.intro keyup.intro', null);
};
return utilRebind(step, dispatch, 'on');
chapter.restart = function() {
chapter.exit();
chapter.enter();
};
return utilRebind(chapter, dispatch, 'on');
}
+422 -105
View File
@@ -1,180 +1,497 @@
import * as d3 from 'd3';
import { t } from '../../util/locale';
import { utilBindOnce } from '../../util/bind_once';
import { t, textDirection } from '../../util/locale';
import { actionChangePreset } from '../../actions';
import { modeBrowse, modeSelect } from '../../modes';
import { utilRebind } from '../../util/rebind';
import { icon, pad } from './helper';
import { icon, pointBox, pad, selectMenuItem, transitionTime } from './helper';
export function uiIntroPoint(context, reveal) {
var dispatch = d3.dispatch('done'),
timeouts = [];
timeouts = [],
intersection = [-85.63279, 41.94394],
building = [-85.632422, 41.944045],
cafePreset = context.presets().item('amenity/cafe'),
pointId = null;
var step = {
var chapter = {
title: 'intro.points.title'
};
function setTimeout(f, t) {
function timeout(f, t) {
timeouts.push(window.setTimeout(f, t));
}
function revealEditMenu(loc, text, options) {
var rect = context.surfaceRect();
var point = context.curtainProjection(loc);
var pad = 40;
var width = 250 + (2 * pad);
var height = 250;
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 eventCancel() {
d3.event.stopPropagation();
d3.event.preventDefault();
}
step.enter = function() {
context.map().centerZoom([-85.63279, 41.94394], 19);
reveal('button.add-point',
t('intro.points.add', { button: icon('#icon-point', 'pre-text') }),
{ tooltipClass: 'intro-points-add' });
function addPoint() {
context.enter(modeBrowse(context));
context.history().reset('initial');
var corner = [-85.632481, 41.944094];
var msec = transitionTime(intersection, context.map().center());
if (msec) { reveal(null, null, { duration: 0 }); }
context.map().zoom(19).centerEase(intersection, msec);
context.on('enter.intro', addPoint);
timeout(function() {
var tooltip = reveal('button.add-point',
t('intro.points.add_point', { button: icon('#icon-point', 'pre-text') }));
pointId = null;
function addPoint(mode) {
if (mode.id !== 'add-point') return;
context.on('enter.intro', enterSelect);
tooltip.selectAll('.tooltip-inner')
.insert('svg', 'span')
.attr('class', 'tooltip-illustration')
.append('use')
.attr('xlink:href', '#poi-images');
var pointBox = pad(corner, 150, context);
reveal(pointBox, t('intro.points.place'));
context.map().on('move.intro', function() {
pointBox = pad(corner, 150, context);
reveal(pointBox, t('intro.points.place'), {duration: 0});
context.on('enter.intro', function(mode) {
if (mode.id !== 'add-point') return;
continueTo(placePoint);
});
}
}, msec + 100);
function enterSelect(mode) {
if (mode.id !== 'select') return;
context.map().on('move.intro', null);
function continueTo(nextStep) {
context.on('enter.intro', null);
nextStep();
}
}
setTimeout(function() {
reveal('.preset-search-input',
t('intro.points.search', {name: context.presets().item('amenity/cafe').name()}));
d3.select('.preset-search-input').on('keyup.intro', keySearch);
}, 500);
function placePoint() {
if (context.mode().id !== 'add-point') {
return chapter.restart();
}
var pointBox = pad(building, 150, context);
reveal(pointBox, t('intro.points.place_point'));
function keySearch() {
context.map().on('move.intro drawn.intro', function() {
pointBox = pad(building, 150, context);
reveal(pointBox, t('intro.points.place_point'), { duration: 0 });
});
context.on('enter.intro', function(mode) {
if (mode.id !== 'select') return chapter.restart();
pointId = context.mode().selectedIDs()[0];
continueTo(searchPreset);
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function searchPreset() {
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
return addPoint();
}
d3.select('.preset-search-input')
.on('keydown.intro', null)
.on('keyup.intro', checkPresetSearch);
reveal('.preset-search-input',
t('intro.points.search_cafe', { preset: cafePreset.name() })
);
context.on('enter.intro', function(mode) {
if (!pointId || !context.hasEntity(pointId)) {
return continueTo(addPoint);
}
var ids = context.selectedIDs();
if (mode.id !== 'select' || !ids.length || ids[0] !== pointId) {
// keep the user's point selected..
context.enter(modeSelect(context, [pointId]));
d3.select('.preset-search-input')
.on('keydown.intro', null)
.on('keyup.intro', checkPresetSearch);
reveal('.preset-search-input',
t('intro.points.search_cafe', { preset: cafePreset.name() })
);
context.history().on('change.intro', null);
}
});
function checkPresetSearch() {
var first = d3.select('.preset-list-item:first-child');
if (first.classed('preset-amenity-cafe')) {
reveal(first.select('.preset-list-button').node(), t('intro.points.choose'));
utilBindOnce(context.history(), 'change.intro', selectedPreset);
d3.select('.preset-search-input')
.on('keydown.intro', eventCancel, true)
.on('keyup.intro', null);
reveal(first.select('.preset-list-button').node(),
t('intro.points.choose_cafe', { preset: cafePreset.name() }),
{ duration: 300 }
);
context.history().on('change.intro', function() {
continueTo(aboutFeatureEditor);
});
}
}
function continueTo(nextStep) {
context.on('enter.intro', null);
context.history().on('change.intro', null);
d3.select('.preset-search-input').on('keydown.intro keyup.intro', null);
nextStep();
}
}
function selectedPreset() {
setTimeout(function() {
reveal('.entity-editor-pane', t('intro.points.describe'), {tooltipClass: 'intro-points-describe'});
context.history().on('change.intro', closeEditor);
context.on('exit.intro', selectPoint);
}, 400);
function aboutFeatureEditor() {
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
return addPoint();
}
timeout(function() {
reveal('.entity-editor-pane', t('intro.points.feature_editor'), {
tooltipClass: 'intro-points-describe',
buttonText: t('intro.ok'),
buttonCallback: function() { continueTo(addName); }
});
}, 400);
function closeEditor() {
d3.select('.preset-search-input').on('keydown.intro', null);
context.on('exit.intro', function() {
// if user leaves select mode here, just continue with the tutorial.
continueTo(reselectPoint);
});
function continueTo(nextStep) {
context.on('exit.intro', null);
nextStep();
}
}
function addName() {
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
return addPoint();
}
// reset pane, in case user happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
timeout(function() {
// It's possible for the user to add a name in a previous step..
// If so, don't tell them to add the name in this step.
// Give them an OK button instead.
var entity = context.entity(pointId);
if (entity.tags.name) {
var tooltip = reveal('.entity-editor-pane', t('intro.points.add_name'), {
tooltipClass: 'intro-points-describe',
buttonText: t('intro.ok'),
buttonCallback: function() { continueTo(addCloseEditor); }
});
tooltip.select('.instruction').style('display', 'none');
} else {
reveal('.entity-editor-pane', t('intro.points.add_name'),
{ tooltipClass: 'intro-points-describe' }
);
}
}, 400);
context.history().on('change.intro', function() {
continueTo(addCloseEditor);
});
context.on('exit.intro', function() {
// if user leaves select mode here, just continue with the tutorial.
continueTo(reselectPoint);
});
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function addCloseEditor() {
// reset pane, in case user happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
var selector = '.entity-editor-pane button.preset-close svg use';
var href = d3.select(selector).attr('href') || '#icon-close';
context.on('exit.intro', function() {
continueTo(reselectPoint);
});
reveal('.entity-editor-pane',
t('intro.points.add_close', { button: icon(href, 'pre-text') })
);
function continueTo(nextStep) {
context.on('exit.intro', null);
nextStep();
}
}
function reselectPoint() {
if (!pointId) return chapter.restart();
var entity = context.hasEntity(pointId);
if (!entity) return chapter.restart();
// make sure it's still a cafe, in case user somehow changed it..
var oldPreset = context.presets().match(entity, context.graph());
context.replace(actionChangePreset(pointId, oldPreset, cafePreset));
context.enter(modeBrowse(context));
var msec = transitionTime(entity.loc, context.map().center());
if (msec) { reveal(null, null, { duration: 0 }); }
context.map().centerEase(entity.loc, msec);
timeout(function() {
var box = pointBox(entity.loc, context);
reveal(box, t('intro.points.reselect'), { duration: 600 });
timeout(function() {
context.map().on('move.intro drawn.intro', function() {
var entity = context.hasEntity(pointId);
if (!entity) return chapter.restart();
var box = pointBox(entity.loc, context);
reveal(box, t('intro.points.reselect'), { duration: 0 });
});
}, 600); // after reveal..
context.on('enter.intro', function(mode) {
if (mode.id !== 'select') return;
continueTo(updatePoint);
});
}, msec + 100);
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.on('enter.intro', null);
nextStep();
}
}
function updatePoint() {
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
return continueTo(reselectPoint);
}
// reset pane, in case user happened to untag the point..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
context.on('exit.intro', function() {
continueTo(reselectPoint);
});
context.history().on('change.intro', function() {
continueTo(updateCloseEditor);
});
timeout(function() {
reveal('.entity-editor-pane', t('intro.points.update'),
{ tooltipClass: 'intro-points-describe' }
);
}, 400);
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
nextStep();
}
}
function updateCloseEditor() {
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
return continueTo(reselectPoint);
}
// reset pane, in case user happened to change it..
d3.select('.inspector-wrap .panewrap').style('right', '0%');
context.on('exit.intro', function() {
continueTo(rightClickPoint);
});
timeout(function() {
reveal('.entity-editor-pane',
t('intro.points.close', { button: icon('#icon-apply', 'pre-text') }));
}
t('intro.points.update_close', { button: icon('#icon-apply', 'pre-text') })
);
}, 500);
function selectPoint() {
function continueTo(nextStep) {
context.on('exit.intro', null);
context.history().on('change.intro', null);
context.on('enter.intro', enterReselect);
nextStep();
}
}
var pointBox = pad(corner, 150, context);
reveal(pointBox, t('intro.points.reselect'));
context.map().on('move.intro', function() {
pointBox = pad(corner, 150, context);
reveal(pointBox, t('intro.points.reselect'), {duration: 0});
function rightClickPoint() {
if (!pointId) return chapter.restart();
var entity = context.hasEntity(pointId);
if (!entity) return chapter.restart();
context.enter(modeBrowse(context));
var box = pointBox(entity.loc, context);
reveal(box, t('intro.points.rightclick'), { duration: 600 });
timeout(function() {
context.map().on('move.intro drawn.intro', function() {
var entity = context.hasEntity(pointId);
if (!entity) return chapter.restart();
var box = pointBox(entity.loc, context);
reveal(box, t('intro.points.rightclick'), { duration: 0 });
});
}
}, 600); // after reveal
function enterReselect(mode) {
context.on('enter.intro', function(mode) {
if (mode.id !== 'select') return;
context.map().on('move.intro', null);
var ids = context.selectedIDs();
if (ids.length !== 1 || ids[0] !== pointId) return;
timeout(function() {
var node = selectMenuItem('delete').node();
if (!node) return;
continueTo(enterDelete);
}, 300); // after menu visible
});
function continueTo(nextStep) {
context.on('enter.intro', null);
setTimeout(function() {
reveal('.entity-editor-pane',
t('intro.points.fixname', { button: icon('#icon-apply', 'pre-text') }));
context.on('exit.intro', deletePoint);
}, 500);
context.map().on('move.intro drawn.intro', null);
nextStep();
}
}
function deletePoint() {
context.on('exit.intro', null);
context.on('enter.intro', enterDelete);
function enterDelete() {
if (!pointId) return chapter.restart();
var entity = context.hasEntity(pointId);
if (!entity) return chapter.restart();
var pointBox = pad(corner, 150, context);
reveal(pointBox, t('intro.points.rightclick'));
var node = selectMenuItem('delete').node();
if (!node) { return continueTo(rightClickPoint); }
context.map().on('move.intro', function() {
pointBox = pad(corner, 150, context);
reveal(pointBox, t('intro.points.rightclick'), {duration: 0});
revealEditMenu(entity.loc,
t('intro.points.delete', { button: icon('#operation-delete', 'pre-text') })
);
timeout(function() {
context.map().on('move.intro drawn.intro', function() {
revealEditMenu(entity.loc,
t('intro.points.delete', { button: icon('#operation-delete', 'pre-text') }),
{ duration: 0}
);
});
}
}, 300); // after menu visible
context.on('exit.intro', function() {
if (!pointId) return chapter.restart();
var entity = context.hasEntity(pointId);
if (entity) return continueTo(rightClickPoint); // point still exists
});
function enterDelete(mode) {
if (mode.id !== 'select') return;
context.map().on('move.intro', null);
context.on('enter.intro', null);
context.on('exit.intro', deletePoint);
context.map().on('move.intro', deletePoint);
context.history().on('change.intro', deleted);
setTimeout(function() {
// deprecation warning - Radial Menu to be removed in iD v3
var node = d3.select('.edit-menu-item-delete, .radial-menu-item-delete').node();
if (!node) {
deletePoint();
} else {
var pointBox = pad(node.getBoundingClientRect(), 50, context);
reveal(pointBox,
t('intro.points.delete', { button: icon('#operation-delete', 'pre-text') }));
}
}, 300);
}
function deleted(changed) {
context.history().on('change.intro', function(changed) {
if (changed.deleted().length) {
dispatch.call('done');
continueTo(undo);
}
});
function continueTo(nextStep) {
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
context.on('exit.intro', null);
nextStep();
}
}
function undo() {
context.history().on('change.intro', function() {
continueTo(play);
});
var iconName = '#icon-' + (textDirection === 'rtl' ? 'redo' : 'undo');
reveal('#bar button.undo-button',
t('intro.points.undo', { button: icon(iconName, 'pre-text') })
);
function continueTo(nextStep) {
context.history().on('change.intro', null);
nextStep();
}
}
function play() {
dispatch.call('done');
reveal('#id-container',
t('intro.points.play', { next: t('intro.areas.title') }), {
tooltipBox: '.intro-nav-wrap .chapter-area',
buttonText: t('intro.ok'),
buttonCallback: function() { reveal('#id-container'); }
}
);
}
chapter.enter = function() {
addPoint();
};
step.exit = function() {
chapter.exit = function() {
timeouts.forEach(window.clearTimeout);
context.on('exit.intro', null);
context.on('enter.intro', null);
context.map().on('move.intro', null);
context.on('enter.intro exit.intro', null);
context.map().on('move.intro drawn.intro', null);
context.history().on('change.intro', null);
d3.select('.preset-search-input')
.on('keyup.intro', null)
.on('keydown.intro', null);
d3.select('.preset-search-input').on('keydown.intro keyup.intro', null);
};
return utilRebind(step, dispatch, 'on');
chapter.restart = function() {
chapter.exit();
chapter.enter();
};
return utilRebind(chapter, dispatch, 'on');
}
+53 -47
View File
@@ -7,65 +7,71 @@ import { utilRebind } from '../../util/rebind';
export function uiIntroStartEditing(context, reveal) {
var dispatch = d3.dispatch('done', 'startEditing'),
modalSelection,
timeouts = [];
modalSelection = d3.select(null);
var step = {
var chapter = {
title: 'intro.startediting.title'
};
function timeout(f, t) {
timeouts.push(window.setTimeout(f, t));
function showHelp() {
reveal('.map-control.help-control',
t('intro.startediting.help', { button: icon('#icon-help', 'pre-text') }), {
buttonText: t('intro.ok'),
buttonCallback: function() { showSave(); }
}
);
}
function showSave() {
reveal('#bar button.save',
t('intro.startediting.save'), {
buttonText: t('intro.ok'),
buttonCallback: function() { showStart(); }
}
);
}
function showStart() {
modalSelection = uiModal(context.container());
modalSelection.select('.modal')
.attr('class', 'modal-splash modal col6');
modalSelection.selectAll('.close').remove();
var startbutton = modalSelection.select('.content')
.attr('class', 'fillL')
.append('button')
.attr('class', 'modal-section huge-modal-button')
.on('click', function() {
modalSelection.remove();
});
startbutton
.append('svg')
.attr('class', 'illustration')
.append('use')
.attr('xlink:href', '#logo-walkthrough');
startbutton
.append('h2')
.text(t('intro.startediting.start'));
dispatch.call('startEditing');
}
step.enter = function() {
reveal('.map-control.help-control',
t('intro.startediting.help', { button: icon('#icon-help', 'pre-text') }));
timeout(function() {
reveal('#bar button.save', t('intro.startediting.save'));
}, 5000);
timeout(function() {
reveal('#surface');
}, 10000);
timeout(function() {
modalSelection = uiModal(context.container());
modalSelection.select('.modal')
.attr('class', 'modal-splash modal col6');
modalSelection.selectAll('.close').remove();
var startbutton = modalSelection.select('.content')
.attr('class', 'fillL')
.append('button')
.attr('class', 'modal-section huge-modal-button')
.on('click', function() {
modalSelection.remove();
});
startbutton
.append('div')
.attr('class','illustration');
startbutton
.append('h2')
.text(t('intro.startediting.start'));
dispatch.call('startEditing');
}, 10500);
chapter.enter = function() {
showHelp();
};
step.exit = function() {
if (modalSelection) { modalSelection.remove(); }
timeouts.forEach(window.clearTimeout);
chapter.exit = function() {
modalSelection.remove();
};
return utilRebind(step, dispatch, 'on');
return utilRebind(chapter, dispatch, 'on');
}
+254
View File
@@ -0,0 +1,254 @@
import * as d3 from 'd3';
import { t } from '../../util/locale';
import { utilRebind } from '../../util/rebind';
export function uiIntroWelcome(context, reveal) {
var dispatch = d3.dispatch('done'),
listener = clickListener();
var chapter = {
title: 'intro.welcome.title'
};
function welcome() {
context.map().centerZoom([-85.63591, 41.94285], 19);
reveal('.intro-nav-wrap .chapter-welcome',
t('intro.welcome.welcome'),
{ buttonText: t('intro.ok'), buttonCallback: practice }
);
}
function practice() {
reveal('.intro-nav-wrap .chapter-welcome',
t('intro.welcome.practice'),
{ buttonText: t('intro.ok'), buttonCallback: words }
);
}
function words() {
reveal('.intro-nav-wrap .chapter-welcome',
t('intro.welcome.words'),
{ buttonText: t('intro.ok'), buttonCallback: mouse }
);
}
function mouse() {
reveal('.intro-nav-wrap .chapter-welcome',
t('intro.welcome.mouse'),
{ buttonText: t('intro.ok'), buttonCallback: leftClick }
);
}
function leftClick() {
var counter = 0,
times = 5;
var tooltip = reveal('.intro-nav-wrap .chapter-welcome',
t('intro.welcome.leftclick', { num: times }),
{ tooltipClass: 'intro-mouse' }
);
tooltip.selectAll('.tooltip-inner')
.insert('svg', 'span')
.attr('class', 'tooltip-illustration')
.append('use')
.attr('xlink:href', '#walkthrough-mouse');
tooltip
.append('div')
.attr('class', 'counter');
tooltip.call(listener);
listener.on('click', function(which) {
if (which === 'left') {
d3.select('.curtain-tooltip.intro-mouse .counter')
.text(String(++counter));
if (counter === times) {
window.setTimeout(function() { continueTo(rightClick); }, 1000);
}
}
});
function continueTo(nextStep) {
listener.on('click', null);
tooltip.call(listener.off);
tooltip.select('.counter').remove();
nextStep();
}
}
function rightClick() {
var counter = 0,
times = 5;
var tooltip = reveal('.intro-nav-wrap .chapter-welcome',
t('intro.welcome.rightclick', { num: times }),
{ tooltipClass: 'intro-mouse' }
);
tooltip.selectAll('.tooltip-inner')
.insert('svg', 'span')
.attr('class', 'tooltip-illustration')
.append('use')
.attr('xlink:href', '#walkthrough-mouse');
tooltip
.append('div')
.attr('class', 'counter');
tooltip.call(listener);
listener.on('click', function(which) {
if (which === 'right') {
d3.select('.curtain-tooltip.intro-mouse .counter')
.text(String(++counter));
if (counter === times) {
window.setTimeout(function() { continueTo(chapters); }, 1000);
}
}
});
function continueTo(nextStep) {
listener.on('click', null);
tooltip.call(listener.off);
tooltip.select('.counter').remove();
nextStep();
}
}
function chapters() {
dispatch.call('done');
reveal('.intro-nav-wrap .chapter-navigation',
t('intro.welcome.chapters', { next: t('intro.navigation.title') })
);
}
chapter.enter = function() {
welcome();
};
chapter.exit = function() {
listener.off();
};
chapter.restart = function() {
chapter.exit();
chapter.enter();
};
return utilRebind(chapter, dispatch, 'on');
}
function clickListener() {
var dispatch = d3.dispatch('click'),
minTime = 120,
tooltip = d3.select(null),
down = {};
function keydown() {
if (d3.event.keyCode === 93) { //context menu
d3.event.preventDefault();
d3.event.stopPropagation();
down.menu = d3.event.timeStamp;
tooltip.classed('rightclick', true);
}
}
function keyup() {
if (d3.event.keyCode === 93) { //context menu
d3.event.preventDefault();
d3.event.stopPropagation();
var endTime = d3.event.timeStamp,
startTime = down.menu || endTime,
delay = (endTime - startTime < minTime) ? minTime : 0;
window.setTimeout(function() { tooltip.classed('rightclick', false); }, delay);
dispatch.call('click', this, 'right');
down.menu = undefined;
}
}
function mousedown() {
if (d3.event.button === 0 && !d3.event.ctrlKey) {
tooltip.classed('leftclick', true);
} else if (d3.event.button === 2) {
tooltip.classed('rightclick', true);
}
down[d3.event.button] = d3.event.timeStamp;
}
function mouseup() {
var endTime = d3.event.timeStamp,
startTime = down[d3.event.button] || endTime,
delay = (endTime - startTime < minTime) ? minTime : 0;
if (d3.event.button === 0 && !d3.event.ctrlKey) {
window.setTimeout(function() { tooltip.classed('leftclick', false); }, delay);
dispatch.call('click', this, 'left');
} else if (d3.event.button === 2) {
window.setTimeout(function() { tooltip.classed('rightclick', false); }, delay);
dispatch.call('click', this, 'right');
}
down[d3.event.button] = undefined;
}
function contextmenu() {
d3.event.preventDefault();
d3.event.stopPropagation();
if (!down[2] && !down.menu) {
tooltip.classed('rightclick', true);
window.setTimeout(function() { tooltip.classed('rightclick', false); }, minTime);
dispatch.call('click', this, 'right');
}
}
var behavior = function(selection) {
tooltip = selection;
down = {};
d3.select(window)
.on('keydown.intro', keydown)
.on('keyup.intro', keyup)
.on('mousedown.intro', mousedown)
.on('mouseup.intro', mouseup)
.on('contextmenu.intro', contextmenu);
};
behavior.off = function() {
d3.select(window)
.on('keydown.intro', null)
.on('keyup.intro', null)
.on('mousedown.intro', null)
.on('mouseup.intro', null)
.on('contextmenu.intro', null);
tooltip
.classed('leftclick', false)
.classed('rightclick', false);
};
return utilRebind(behavior, dispatch, 'on');
}
+5 -2
View File
@@ -255,9 +255,12 @@ export function uiPreset(context) {
}
return placeholder.slice(0,3).join(', ') + ((placeholder.length > 3) ? '…' : '');
})
.call(d3combobox().data(notShown)
.call(d3combobox()
.container(context.container())
.data(notShown)
.minItems(1)
.on('accept', show));
.on('accept', show)
);
function show(field) {
+1
View File
@@ -169,6 +169,7 @@ export function uiRawMemberEditor(context) {
}
role.call(d3combobox()
.container(context.container())
.fetcher(function(role, callback) {
var rtype = entity.tags.type;
taginfo.roles({
+2
View File
@@ -244,6 +244,7 @@ export function uiRawMembershipEditor(context) {
newrow.selectAll('.member-entity-input')
.call(d3combobox()
.container(context.container())
.minItems(1)
.fetcher(function(value, callback) { callback(relations(value)); })
.on('accept', onAccept)
@@ -291,6 +292,7 @@ export function uiRawMembershipEditor(context) {
}
role.call(d3combobox()
.container(context.container())
.fetcher(function(role, callback) {
var rtype = d.relation.tags.type;
taginfo.roles({
+2
View File
@@ -187,6 +187,7 @@ export function uiRawTagEditor(context) {
var geometry = context.geometry(id);
key.call(d3combobox()
.container(context.container())
.fetcher(function(value, callback) {
taginfo.keys({
debounce: true,
@@ -198,6 +199,7 @@ export function uiRawTagEditor(context) {
}));
value.call(d3combobox()
.container(context.container())
.fetcher(function(value, callback) {
taginfo.values({
debounce: true,
+21 -3
View File
@@ -37,21 +37,39 @@ export function uiRestore(context) {
var restore = buttonWrap
.append('button')
.attr('class', 'restore col6')
.text(t('restore.restore'))
.on('click', function() {
context.history().restore();
modalSelection.remove();
});
buttonWrap
restore
.append('svg')
.attr('class', 'logo logo-restore')
.append('use')
.attr('xlink:href', '#logo-restore');
restore
.append('div')
.text(t('restore.restore'));
var reset = buttonWrap
.append('button')
.attr('class', 'reset col6')
.text(t('restore.reset'))
.on('click', function() {
context.history().clearSaved();
modalSelection.remove();
});
reset
.append('svg')
.attr('class', 'logo logo-reset')
.append('use')
.attr('xlink:href', '#logo-reset');
reset
.append('div')
.text(t('restore.reset'));
restore.node().focus();
};
}
+27 -9
View File
@@ -1,4 +1,3 @@
import * as d3 from 'd3';
import { t } from '../util/locale';
import { uiIntro } from './intro/index';
import { uiModal } from './modal';
@@ -36,25 +35,44 @@ export function uiSplash(context) {
github: '<a href="https://github.com/openstreetmap/iD">github.com</a>'
}));
var buttons = introModal
var buttonWrap = introModal
.append('div')
.attr('class', 'modal-actions cf');
buttons
var walkthrough = buttonWrap
.append('button')
.attr('class', 'col6 walkthrough')
.text(t('splash.walkthrough'))
.attr('class', 'walkthrough col6')
.on('click', function() {
d3.select(document.body).call(uiIntro(context));
context.container().call(uiIntro(context));
modalSelection.close();
});
buttons
walkthrough
.append('svg')
.attr('class', 'logo logo-walkthrough')
.append('use')
.attr('xlink:href', '#logo-walkthrough');
walkthrough
.append('div')
.text(t('splash.walkthrough'));
var startEditing = buttonWrap
.append('button')
.attr('class', 'col6 start')
.text(t('splash.start'))
.attr('class', 'start-editing col6')
.on('click', modalSelection.close);
startEditing
.append('svg')
.attr('class', 'logo logo-features')
.append('use')
.attr('xlink:href', '#logo-features');
startEditing
.append('div')
.text(t('splash.start'));
modalSelection.select('button.close')
.attr('class','hide');
+12 -2
View File
@@ -45,11 +45,21 @@ export function uiSuccess(context) {
var changesetURL = context.connection().changesetURL(changeset.id);
body
var viewOnOsm = body
.append('a')
.attr('class', 'button col12 osm')
.attr('target', '_blank')
.attr('href', changesetURL)
.attr('href', changesetURL);
viewOnOsm
.append('svg')
.attr('class', 'logo logo-osm')
.append('use')
.attr('xlink:href', '#logo-osm');
viewOnOsm
.append('div')
.text(t('success.view_on_osm'));
+3 -3
View File
@@ -11,12 +11,12 @@ export function uiUndoRedo(context) {
var commands = [{
id: 'undo',
cmd: uiCmd('⌘Z'),
action: function() { if (!(context.inIntro() || saving())) context.undo(); },
action: function() { if (!saving()) context.undo(); },
annotation: function() { return context.history().undoAnnotation(); }
}, {
id: 'redo',
cmd: uiCmd('⌘⇧Z'),
action: function() {if (!(context.inIntro() || saving())) context.redo(); },
action: function() { if (!saving()) context.redo(); },
annotation: function() { return context.history().redoAnnotation(); }
}];
@@ -40,7 +40,7 @@ export function uiUndoRedo(context) {
.data(commands)
.enter()
.append('button')
.attr('class', 'col6 disabled')
.attr('class', function(d) { return 'col6 disabled ' + d.id + '-button'; })
.on('click', function(d) { return d.action(); })
.call(tooltipBehavior);
+4 -4
View File
@@ -26,25 +26,25 @@ export function uiZoom(context) {
function zoomIn() {
d3.event.preventDefault();
if (!context.inIntro()) context.zoomIn();
context.zoomIn();
}
function zoomOut() {
d3.event.preventDefault();
if (!context.inIntro()) context.zoomOut();
context.zoomOut();
}
function zoomInFurther() {
d3.event.preventDefault();
if (!context.inIntro()) context.zoomInFurther();
context.zoomInFurther();
}
function zoomOutFurther() {
d3.event.preventDefault();
if (!context.inIntro()) context.zoomOutFurther();
context.zoomOutFurther();
}
-152
View File
@@ -1,152 +0,0 @@
import * as d3 from 'd3';
import { utilGetDimensions } from './dimensions';
import { utilRebind } from './rebind';
import { uiToggle } from '../ui/toggle';
// Tooltips and svg mask used to highlight certain features
export function d3curtain() {
var dispatch = d3.dispatch(),
surface = d3.select(null),
tooltip = d3.select(null),
darkness = d3.select(null);
function curtain(selection) {
surface = selection
.append('svg')
.attr('id', 'curtain')
.style('z-index', 1000)
.style('pointer-events', 'none')
.style('position', 'absolute')
.style('top', 0)
.style('left', 0);
darkness = surface.append('path')
.attr('x', 0)
.attr('y', 0)
.attr('class', 'curtain-darkness');
d3.select(window).on('resize.curtain', resize);
tooltip = selection.append('div')
.attr('class', 'tooltip')
.style('z-index', 1002);
tooltip
.append('div')
.attr('class', 'tooltip-arrow');
tooltip
.append('div')
.attr('class', 'tooltip-inner');
resize();
function resize() {
surface
.attr('width', window.innerWidth)
.attr('height', window.innerHeight);
curtain.cut(darkness.datum());
}
}
curtain.reveal = function(box, text, tooltipclass, duration) {
if (typeof box === 'string') box = d3.select(box).node();
if (box.getBoundingClientRect) box = box.getBoundingClientRect();
curtain.cut(box, duration);
if (text) {
// pseudo markdown bold text hack
var parts = text.split('**');
var html = parts[0] ? '<span>' + parts[0] + '</span>' : '';
if (parts[1]) html += '<span class="bold">' + parts[1] + '</span>';
var selection = tooltip
.classed('in', true)
.selectAll('.tooltip-inner')
.html(html);
var dimensions = utilGetDimensions(selection, true),
w = window.innerWidth,
h = window.innerHeight,
side, pos;
// trim box dimensions to just the portion that fits in the window..
if (box.top + box.height > h) {
box.height -= (box.top + box.height - h);
}
if (box.left + box.width > w) {
box.width -= (box.left + box.width - w);
}
// determine tooltip placement..
if (box.top + box.height < Math.min(100, box.width + box.left)) {
side = 'bottom';
pos = [box.left + box.width / 2 - dimensions[0] / 2, box.top + box.height];
} else if (box.left + box.width + 300 < w) {
side = 'right';
pos = [box.left + box.width, box.top + box.height / 2 - dimensions[1] / 2];
} else if (box.left > 300) {
side = 'left';
pos = [box.left - 200, box.top + box.height / 2 - dimensions[1] / 2];
} else {
side = 'bottom';
pos = [box.left, box.top + box.height];
}
pos = [
Math.min(Math.max(10, pos[0]), w - dimensions[0] - 10),
Math.min(Math.max(10, pos[1]), h - dimensions[1] - 10)
];
if (duration !== 0 || !tooltip.classed(side)) {
tooltip.call(uiToggle(true));
}
tooltip
.style('top', pos[1] + 'px')
.style('left', pos[0] + 'px')
.attr('class', 'curtain-tooltip tooltip in ' + side + ' ' + tooltipclass);
} else {
tooltip.call(uiToggle(false));
}
};
curtain.cut = function(datum, duration) {
darkness.datum(datum)
.interrupt();
(duration === 0 ? darkness : darkness.transition().duration(duration || 600))
.attr('d', function(d) {
var string = 'M 0,0 L 0,' + window.innerHeight + ' L ' +
window.innerWidth + ',' + window.innerHeight + 'L' +
window.innerWidth + ',0 Z';
if (!d) return string;
return string + 'M' +
d.left + ',' + d.top + 'L' +
d.left + ',' + (d.top + d.height) + 'L' +
(d.left + d.width) + ',' + (d.top + d.height) + 'L' +
(d.left + d.width) + ',' + (d.top) + 'Z';
});
};
curtain.remove = function() {
surface.remove();
tooltip.remove();
};
return utilRebind(curtain, dispatch, 'on');
}
+26 -1
View File
@@ -383,5 +383,30 @@
"logo-twitter-shape": { "fill": "currentColor" },
"logo-facebook-shape": { "fill": "currentColor" },
"logo-google-shape": { "fill": "currentColor" }
"logo-google-shape": { "fill": "currentColor" },
"logo-osm": { "viewBox": "200 460 100 100" },
"logo-walkthrough": { "viewBox": "300 460 100 100" },
"logo-features": { "viewBox": "400 460 100 100" },
"logo-restore": { "viewBox": "500 460 100 100" },
"logo-reset": { "viewBox": "600 460 100 100" },
"logo-osm-shape": { "fill": "currentColor" },
"logo-walkthrough-shape": { "fill": "currentColor" },
"logo-features-shape1": { "fill": "currentColor" },
"logo-features-shape2": { "fill": "currentColor" },
"logo-features-shape3": { "fill": "currentColor" },
"logo-restore-shape": { "fill": "currentColor" },
"logo-reset-shape": { "fill": "currentColor" },
"poi-images": { "viewBox": "0 320 200 80" },
"landuse-images": { "viewBox": "0 400 200 80" },
"feature-images": { "viewBox": "0 480 200 80" },
"building-images": { "viewBox": "700 480 200 80" },
"walkthrough-mouse": { "viewBox": "400 411 25 43" },
"walkthrough-mouse-shape": { "fill": "#000000" },
"walkthrough-mouse-left": { "fill": "inherit" },
"walkthrough-mouse-right": { "fill": "currentColor" }
}
Binary file not shown.
+121 -41
View File
@@ -29,64 +29,139 @@
<path d="M349.5,4 C352.543,4 355,6.457 355,9.5 C355,12.543 352.543,15 349.5,15 C346.457,15 344,12.543 344,9.5 C344,6.457 346.457,4 349.5,4 z"/>
</clipPath>
</defs>
<g id="building-images">
<path d="M700,480 L900,480 L900,560 L700,560 z" fill="#FFFFFF"/>
<g>
<path d="M720,538.544 L744.051,548.671 L744.051,527.152 L720,517.025 z" fill="#E16E5F"/>
<path d="M770,537.911 L744.051,548.671 L744.051,527.152 L757.342,515.127 L770,517.025 z" fill="#9F655C"/>
<path d="M720,517.025 L734.557,505 L757.342,515.127 L744.051,527.152 L720,517.025 z" fill="#695757"/>
<path d="M770,517.025 L734.557,505 L757.342,515.127" fill="#4B4442"/>
<g>
<path d="M729.494,542.342 L729.494,536.013 L733.924,537.911 L733.924,544.24 L729.494,542.342 z" fill="#695757"/>
<path d="M737.089,545.506 L737.089,539.177 L741.519,541.076 L741.519,547.405 L737.089,545.506 z" fill="#695757"/>
<path d="M721.899,539.177 L721.899,532.848 L726.329,534.747 L726.329,541.076 L721.899,539.177 z" fill="#695757"/>
<path d="M737.089,534.114 L737.089,527.785 L741.519,529.684 L741.519,536.013 L737.089,534.114 z" fill="#695757"/>
<path d="M729.494,530.949 L729.494,524.62 L733.924,526.519 L733.924,532.848 L729.494,530.949 z" fill="#695757"/>
<path d="M721.899,527.785 L721.899,521.456 L726.329,523.354 L726.329,529.684 L721.899,527.785 z" fill="#695757"/>
</g>
<g>
<path d="M752.278,544.873 L752.278,538.544 L747.848,540.443 L747.848,546.772 L752.278,544.873 z" fill="#554646"/>
<path d="M752.278,533.481 L752.278,527.152 L747.848,529.051 L747.848,535.38 L752.278,533.481 z" fill="#554646"/>
<path d="M759.873,541.709 L759.873,535.38 L755.443,537.279 L755.443,543.608 L759.873,541.709 z" fill="#554646"/>
<path d="M759.873,530.316 L759.873,523.987 L755.443,525.886 L755.443,532.215 L759.873,530.316 z" fill="#554646"/>
<path d="M767.468,538.544 L767.468,532.215 L763.038,534.114 L763.038,540.443 L767.468,538.544 z" fill="#554646"/>
<path d="M767.468,527.152 L767.468,520.823 L763.038,522.722 L763.038,529.051 L767.468,527.152 z" fill="#554646"/>
</g>
</g>
<path d="M900,560 C900,560 700,480 700,480" fill-opacity="0" stroke="#000000" stroke-width="0.5" display="none"/>
<g display="none">
<path d="M700,560 L900,480" fill="#FFFFFF"/>
<path d="M700,560 L900,480" fill-opacity="0" stroke="#000000" stroke-width="0.5"/>
</g>
<path d="M800,480 L800,558.5" fill-opacity="0" stroke="#000000" stroke-width="0.5" display="none"/>
<g>
<path d="M775.5,516.544 L799.551,526.671 L799.551,505.152 L775.5,495.025 z" fill="#E16E5F"/>
<path d="M825.5,515.911 L799.551,526.671 L799.551,505.152 L812.842,493.127 L825.5,495.025 z" fill="#9F655C"/>
<path d="M775.5,495.025 L790.057,483 L812.842,493.127 L799.551,505.152 L775.5,495.025 z" fill="#695757"/>
<path d="M825.5,495.025 L790.057,483 L812.842,493.127" fill="#4B4442"/>
<g>
<path d="M784.994,520.342 L784.994,514.013 L789.424,515.911 L789.424,522.24 L784.994,520.342 z" fill="#695757"/>
<path d="M792.589,523.506 L792.589,517.177 L797.019,519.076 L797.019,525.405 L792.589,523.506 z" fill="#695757"/>
<path d="M777.399,517.177 L777.399,510.848 L781.829,512.747 L781.829,519.076 L777.399,517.177 z" fill="#695757"/>
<path d="M792.589,512.114 L792.589,505.785 L797.019,507.684 L797.019,514.013 L792.589,512.114 z" fill="#695757"/>
<path d="M784.994,508.949 L784.994,502.62 L789.424,504.519 L789.424,510.848 L784.994,508.949 z" fill="#695757"/>
<path d="M777.399,505.785 L777.399,499.456 L781.829,501.354 L781.829,507.684 L777.399,505.785 z" fill="#695757"/>
</g>
<g>
<path d="M807.778,522.873 L807.778,516.544 L803.348,518.443 L803.348,524.772 L807.778,522.873 z" fill="#554646"/>
<path d="M807.778,511.481 L807.778,505.152 L803.348,507.051 L803.348,513.38 L807.778,511.481 z" fill="#554646"/>
<path d="M815.373,519.709 L815.373,513.38 L810.943,515.279 L810.943,521.608 L815.373,519.709 z" fill="#554646"/>
<path d="M815.373,508.316 L815.373,501.987 L810.943,503.886 L810.943,510.215 L815.373,508.316 z" fill="#554646"/>
<path d="M822.968,516.544 L822.968,510.215 L818.538,512.114 L818.538,518.443 L822.968,516.544 z" fill="#554646"/>
<path d="M822.968,505.152 L822.968,498.823 L818.538,500.722 L818.538,507.051 L822.968,505.152 z" fill="#554646"/>
</g>
</g>
<g>
<path d="M830.5,538.544 L854.551,548.671 L854.551,527.152 L830.5,517.025 z" fill="#E16E5F"/>
<path d="M880.5,537.911 L854.551,548.671 L854.551,527.152 L867.842,515.127 L880.5,517.025 z" fill="#9F655C"/>
<path d="M830.5,517.025 L845.057,505 L867.842,515.127 L854.551,527.152 L830.5,517.025 z" fill="#695757"/>
<path d="M880.5,517.025 L845.057,505 L867.842,515.127" fill="#4B4442"/>
<g>
<path d="M839.994,542.342 L839.994,536.013 L844.424,537.911 L844.424,544.24 L839.994,542.342 z" fill="#695757"/>
<path d="M847.589,545.506 L847.589,539.177 L852.019,541.076 L852.019,547.405 L847.589,545.506 z" fill="#695757"/>
<path d="M832.399,539.177 L832.399,532.848 L836.829,534.747 L836.829,541.076 L832.399,539.177 z" fill="#695757"/>
<path d="M847.589,534.114 L847.589,527.785 L852.019,529.684 L852.019,536.013 L847.589,534.114 z" fill="#695757"/>
<path d="M839.994,530.949 L839.994,524.62 L844.424,526.519 L844.424,532.848 L839.994,530.949 z" fill="#695757"/>
<path d="M832.399,527.785 L832.399,521.456 L836.829,523.354 L836.829,529.684 L832.399,527.785 z" fill="#695757"/>
</g>
<g>
<path d="M862.778,544.873 L862.778,538.544 L858.348,540.443 L858.348,546.772 L862.778,544.873 z" fill="#554646"/>
<path d="M862.778,533.481 L862.778,527.152 L858.348,529.051 L858.348,535.38 L862.778,533.481 z" fill="#554646"/>
<path d="M870.373,541.709 L870.373,535.38 L865.943,537.279 L865.943,543.608 L870.373,541.709 z" fill="#554646"/>
<path d="M870.373,530.316 L870.373,523.987 L865.943,525.886 L865.943,532.215 L870.373,530.316 z" fill="#554646"/>
<path d="M877.968,538.544 L877.968,532.215 L873.538,534.114 L873.538,540.443 L877.968,538.544 z" fill="#554646"/>
<path d="M877.968,527.152 L877.968,520.823 L873.538,522.722 L873.538,529.051 L877.968,527.152 z" fill="#554646"/>
</g>
</g>
</g>
<g id="feature-images">
<path d="M0,480 L200,480 L200,560 L0,560 z" fill="#FFFFFF" id="rect8477"/>
<path d="M0,480 L200,480 L200,560 L0,560 z" fill="#FFFFFF"/>
<g id="img-waterway-river">
<path d="M140,530 L180,530 L180,533 L140,533 z" fill="#60D4DE" id="rect24309"/>
<path d="M146.762,526 C146.762,526 146.226,524.154 146.417,523.25 C146.638,522.203 148.071,520.5 148.071,520.5 C148.071,520.5 149.505,518.797 149.726,517.75 C149.917,516.846 149.381,515 149.381,515 C149.381,515 148.845,513.154 149.036,512.25 C149.257,511.203 150.69,509.5 150.69,509.5 C150.69,509.5 152.124,507.797 152.345,506.75 C152.536,505.846 152,504 152,504 L168.5,504 C168.5,504 169.933,505.703 170.155,506.75 C170.346,507.654 169.81,509.5 169.81,509.5 C169.81,509.5 169.273,511.346 169.464,512.25 C169.686,513.297 171.119,515 171.119,515 C171.119,515 172.552,516.703 172.774,517.75 C172.965,518.654 172.429,520.5 172.429,520.5 C172.429,520.5 171.892,522.346 172.083,523.25 C172.305,524.297 173.738,526 173.738,526 z" fill="#60D4DE" id="245"/>
<path d="M147.966,517.759 C147.752,518.559 147.145,519.092 146.612,518.949 C146.078,518.806 145.82,518.041 146.034,517.241 C146.248,516.441 146.855,515.908 147.388,516.051 C147.922,516.194 148.18,516.959 147.966,517.759 z" fill="#60D4DE" id="path25543" opacity="0.5"/>
<path d="M149.799,507.75 C149.247,508.707 148.217,509.146 147.5,508.732 C146.783,508.318 146.649,507.207 147.201,506.25 C147.753,505.293 148.783,504.854 149.5,505.268 C150.217,505.682 150.351,506.793 149.799,507.75 z" fill="#60D4DE" id="path25545" opacity="0.5"/>
<path d="M171.422,511.708 C171.637,512.508 172.243,513.041 172.776,512.898 C173.31,512.755 173.569,511.99 173.354,511.19 C173.14,510.39 172.533,509.857 172,510 C171.467,510.143 171.208,510.908 171.422,511.708 z" fill="#60D4DE" id="path25547" opacity="0.5"/>
<path d="M174.299,522.75 C174.851,523.707 175.881,524.146 176.598,523.732 C177.316,523.318 177.449,522.207 176.897,521.25 C176.345,520.293 175.316,519.854 174.598,520.268 C173.881,520.682 173.747,521.793 174.299,522.75 z" fill="#60D4DE" id="path25549" opacity="0.5"/>
<path d="M182,531.5 C182,532.881 180.881,534 179.5,534 C178.119,534 177,532.881 177,531.5 C177,530.119 178.119,529 179.5,529 C180.881,529 182,530.119 182,531.5 z" fill="#60D4DE" id="path26103"/>
<path d="M181,531.5 C181,532.328 180.328,533 179.5,533 C178.672,533 178,532.328 178,531.5 C178,530.672 178.672,530 179.5,530 C180.328,530 181,530.672 181,531.5 z" fill="#FFFFFF" id="path26105"/>
<path d="M143,531.5 C143,532.881 141.881,534 140.5,534 C139.119,534 138,532.881 138,531.5 C138,530.119 139.119,529 140.5,529 C141.881,529 143,530.119 143,531.5 z" fill="#60D4DE" id="path26107"/>
<path d="M142,531.5 C142,532.328 141.328,533 140.5,533 C139.672,533 139,532.328 139,531.5 C139,530.672 139.672,530 140.5,530 C141.328,530 142,530.672 142,531.5 z" fill="#FFFFFF" id="path26109"/>
<path d="M140,530 L180,530 L180,533 L140,533 z" fill="#60D4DE"/>
<path d="M146.762,526 C146.762,526 146.226,524.154 146.417,523.25 C146.638,522.203 148.071,520.5 148.071,520.5 C148.071,520.5 149.505,518.797 149.726,517.75 C149.917,516.846 149.381,515 149.381,515 C149.381,515 148.845,513.154 149.036,512.25 C149.257,511.203 150.69,509.5 150.69,509.5 C150.69,509.5 152.124,507.797 152.345,506.75 C152.536,505.846 152,504 152,504 L168.5,504 C168.5,504 169.933,505.703 170.155,506.75 C170.346,507.654 169.81,509.5 169.81,509.5 C169.81,509.5 169.273,511.346 169.464,512.25 C169.686,513.297 171.119,515 171.119,515 C171.119,515 172.552,516.703 172.774,517.75 C172.965,518.654 172.429,520.5 172.429,520.5 C172.429,520.5 171.892,522.346 172.083,523.25 C172.305,524.297 173.738,526 173.738,526 z" fill="#60D4DE"/>
<path d="M147.966,517.759 C147.752,518.559 147.145,519.092 146.612,518.949 C146.078,518.806 145.82,518.041 146.034,517.241 C146.248,516.441 146.855,515.908 147.388,516.051 C147.922,516.194 148.18,516.959 147.966,517.759 z" fill="#60D4DE" opacity="0.5"/>
<path d="M149.799,507.75 C149.247,508.707 148.217,509.146 147.5,508.732 C146.783,508.318 146.649,507.207 147.201,506.25 C147.753,505.293 148.783,504.854 149.5,505.268 C150.217,505.682 150.351,506.793 149.799,507.75 z" fill="#60D4DE" opacity="0.5"/>
<path d="M171.422,511.708 C171.637,512.508 172.243,513.041 172.776,512.898 C173.31,512.755 173.569,511.99 173.354,511.19 C173.14,510.39 172.533,509.857 172,510 C171.467,510.143 171.208,510.908 171.422,511.708 z" fill="#60D4DE" opacity="0.5"/>
<path d="M174.299,522.75 C174.851,523.707 175.881,524.146 176.598,523.732 C177.316,523.318 177.449,522.207 176.897,521.25 C176.345,520.293 175.316,519.854 174.598,520.268 C173.881,520.682 173.747,521.793 174.299,522.75 z" fill="#60D4DE" opacity="0.5"/>
<path d="M182,531.5 C182,532.881 180.881,534 179.5,534 C178.119,534 177,532.881 177,531.5 C177,530.119 178.119,529 179.5,529 C180.881,529 182,530.119 182,531.5 z" fill="#60D4DE"/>
<path d="M181,531.5 C181,532.328 180.328,533 179.5,533 C178.672,533 178,532.328 178,531.5 C178,530.672 178.672,530 179.5,530 C180.328,530 181,530.672 181,531.5 z" fill="#FFFFFF"/>
<path d="M143,531.5 C143,532.881 141.881,534 140.5,534 C139.119,534 138,532.881 138,531.5 C138,530.119 139.119,529 140.5,529 C141.881,529 143,530.119 143,531.5 z" fill="#60D4DE"/>
<path d="M142,531.5 C142,532.328 141.328,533 140.5,533 C139.672,533 139,532.328 139,531.5 C139,530.672 139.672,530 140.5,530 C141.328,530 142,530.672 142,531.5 z" fill="#FFFFFF"/>
</g>
<g id="img-railway-rail">
<path d="M93.5,503 L92,504 L91.531,506 L88.25,506 C87.696,506 87.384,506.463 87.25,507 L87,508 C86.866,508.537 87.446,509 88,509 L112,509 C112.554,509 113.134,508.537 113,508 L112.75,507 C112.616,506.463 112.304,506 111.75,506 L108.969,506 L108.5,504 L107,503 L106,504 L106.469,506 L94.031,506 L94.5,504 L93.5,503 z M91.5,507 C91.776,507 92,507.224 92,507.5 C92,507.776 91.776,508 91.5,508 C91.224,508 91,507.776 91,507.5 C91,507.224 91.224,507 91.5,507 z M108.5,507 C108.776,507 109,507.224 109,507.5 C109,507.776 108.776,508 108.5,508 C108.224,508 108,507.776 108,507.5 C108,507.224 108.224,507 108.5,507 z M90.562,510 L90.094,512 L87.25,512 C86.696,512 86.384,512.463 86.25,513 L86,514 C85.866,514.537 86.446,515 87,515 L113,515 C113.554,515 114.134,514.537 114,514 L113.75,513 C113.616,512.463 113.304,512 112.75,512 L110.406,512 L109.938,510 L107.438,510 L107.906,512 L92.594,512 L93.062,510 L90.562,510 z M90.5,513 C90.776,513 91,513.224 91,513.5 C91,513.776 90.776,514 90.5,514 C90.224,514 90,513.776 90,513.5 C90,513.224 90.224,513 90.5,513 z M109.5,513 C109.776,513 110,513.224 110,513.5 C110,513.776 109.776,514 109.5,514 C109.224,514 109,513.776 109,513.5 C109,513.224 109.224,513 109.5,513 z M89.156,516 L88.688,518 L86.25,518 C85.696,518 85.384,518.463 85.25,519 L85,520 C84.866,520.537 85.446,521 86,521 L114,521 C114.554,521 115.134,520.537 115,520 L114.75,519 C114.616,518.463 114.304,518 113.75,518 L111.812,518 L111.344,516 L108.844,516 L109.312,518 L91.188,518 L91.656,516 L89.156,516 z M89.5,519 C89.776,519 90,519.224 90,519.5 C90,519.776 89.776,520 89.5,520 C89.224,520 89,519.776 89,519.5 C89,519.224 89.224,519 89.5,519 z M110.5,519 C110.776,519 111,519.224 111,519.5 C111,519.776 110.776,520 110.5,520 C110.224,520 110,519.776 110,519.5 C110,519.224 110.224,519 110.5,519 z M87.719,522 L87,525 L88.5,526 L89.5,525 L90.219,522 L87.719,522 z M110.281,522 L111,525 L112,526 L113.5,525 L112.781,522 L110.281,522 z" fill="#A9A9A9" id="rect17528-7"/>
<g id="g22103">
<path d="M80,530 L120,530 L120,533 L80,533 z" fill="#A9A9A9" id="rect22091"/>
<path d="M114,531 L119,531 L119,532 L114,532 z" fill="#FFFFFF" id="rect22093"/>
<path d="M103,531 L108,531 L108,532 L103,532 z" fill="#FFFFFF" id="rect22095"/>
<path d="M92,531 L97,531 L97,532 L92,532 z" fill="#FFFFFF" id="rect22097"/>
<path d="M81,531 L86,531 L86,532 L81,532 z" fill="#FFFFFF" id="rect22099"/>
<path d="M93.5,503 L92,504 L91.531,506 L88.25,506 C87.696,506 87.384,506.463 87.25,507 L87,508 C86.866,508.537 87.446,509 88,509 L112,509 C112.554,509 113.134,508.537 113,508 L112.75,507 C112.616,506.463 112.304,506 111.75,506 L108.969,506 L108.5,504 L107,503 L106,504 L106.469,506 L94.031,506 L94.5,504 L93.5,503 z M91.5,507 C91.776,507 92,507.224 92,507.5 C92,507.776 91.776,508 91.5,508 C91.224,508 91,507.776 91,507.5 C91,507.224 91.224,507 91.5,507 z M108.5,507 C108.776,507 109,507.224 109,507.5 C109,507.776 108.776,508 108.5,508 C108.224,508 108,507.776 108,507.5 C108,507.224 108.224,507 108.5,507 z M90.562,510 L90.094,512 L87.25,512 C86.696,512 86.384,512.463 86.25,513 L86,514 C85.866,514.537 86.446,515 87,515 L113,515 C113.554,515 114.134,514.537 114,514 L113.75,513 C113.616,512.463 113.304,512 112.75,512 L110.406,512 L109.938,510 L107.438,510 L107.906,512 L92.594,512 L93.062,510 L90.562,510 z M90.5,513 C90.776,513 91,513.224 91,513.5 C91,513.776 90.776,514 90.5,514 C90.224,514 90,513.776 90,513.5 C90,513.224 90.224,513 90.5,513 z M109.5,513 C109.776,513 110,513.224 110,513.5 C110,513.776 109.776,514 109.5,514 C109.224,514 109,513.776 109,513.5 C109,513.224 109.224,513 109.5,513 z M89.156,516 L88.688,518 L86.25,518 C85.696,518 85.384,518.463 85.25,519 L85,520 C84.866,520.537 85.446,521 86,521 L114,521 C114.554,521 115.134,520.537 115,520 L114.75,519 C114.616,518.463 114.304,518 113.75,518 L111.812,518 L111.344,516 L108.844,516 L109.312,518 L91.188,518 L91.656,516 L89.156,516 z M89.5,519 C89.776,519 90,519.224 90,519.5 C90,519.776 89.776,520 89.5,520 C89.224,520 89,519.776 89,519.5 C89,519.224 89.224,519 89.5,519 z M110.5,519 C110.776,519 111,519.224 111,519.5 C111,519.776 110.776,520 110.5,520 C110.224,520 110,519.776 110,519.5 C110,519.224 110.224,519 110.5,519 z M87.719,522 L87,525 L88.5,526 L89.5,525 L90.219,522 L87.719,522 z M110.281,522 L111,525 L112,526 L113.5,525 L112.781,522 L110.281,522 z" fill="#A9A9A9"/>
<g>
<path d="M80,530 L120,530 L120,533 L80,533 z" fill="#A9A9A9"/>
<path d="M114,531 L119,531 L119,532 L114,532 z" fill="#FFFFFF"/>
<path d="M103,531 L108,531 L108,532 L103,532 z" fill="#FFFFFF"/>
<path d="M92,531 L97,531 L97,532 L92,532 z" fill="#FFFFFF"/>
<path d="M81,531 L86,531 L86,532 L81,532 z" fill="#FFFFFF"/>
</g>
<path d="M122,531.5 C122,532.881 120.881,534 119.5,534 C118.119,534 117,532.881 117,531.5 C117,530.119 118.119,529 119.5,529 C120.881,529 122,530.119 122,531.5 z" fill="#A9A9A9" id="path26156"/>
<path d="M121,531.5 C121,532.328 120.328,533 119.5,533 C118.672,533 118,532.328 118,531.5 C118,530.672 118.672,530 119.5,530 C120.328,530 121,530.672 121,531.5 z" fill="#FFFFFF" id="path26158"/>
<path d="M83,531.5 C83,532.881 81.881,534 80.5,534 C79.119,534 78,532.881 78,531.5 C78,530.119 79.119,529 80.5,529 C81.881,529 83,530.119 83,531.5 z" fill="#A9A9A9" id="path26160"/>
<path d="M82,531.5 C82,532.328 81.328,533 80.5,533 C79.672,533 79,532.328 79,531.5 C79,530.672 79.672,530 80.5,530 C81.328,530 82,530.672 82,531.5 z" fill="#FFFFFF" id="path26162"/>
<path d="M122,531.5 C122,532.881 120.881,534 119.5,534 C118.119,534 117,532.881 117,531.5 C117,530.119 118.119,529 119.5,529 C120.881,529 122,530.119 122,531.5 z" fill="#A9A9A9"/>
<path d="M121,531.5 C121,532.328 120.328,533 119.5,533 C118.672,533 118,532.328 118,531.5 C118,530.672 118.672,530 119.5,530 C120.328,530 121,530.672 121,531.5 z" fill="#FFFFFF"/>
<path d="M83,531.5 C83,532.881 81.881,534 80.5,534 C79.119,534 78,532.881 78,531.5 C78,530.119 79.119,529 80.5,529 C81.881,529 83,530.119 83,531.5 z" fill="#A9A9A9"/>
<path d="M82,531.5 C82,532.328 81.328,533 80.5,533 C79.672,533 79,532.328 79,531.5 C79,530.672 79.672,530 80.5,530 C81.328,530 82,530.672 82,531.5 z" fill="#FFFFFF"/>
</g>
<g id="img-highway-unclassified">
<path d="M32.5,505 L29.625,511 L28,511 L27,512 L27,514 L28.406,514 L28,515 L28,520 L28,521 L28,521 C28,521 28.608,521.608 29,522 L30,522 L34,522 L36,523 L44,523 L46,522 L50,522 L51,522 L52,521 L52,521 L52,520 L52,515 L51.594,514 L53,514 L53,512 L52,511 L50.375,511 L47.5,505 z M33.5,508 L46.5,508 L48,512 L47,513 L33,513 L32,512 z M31,516 L34,516 L35,518 L35,519 L31,519 L30,518 L30,517 z M46,516 L49,516 L50,517 L50,518 L49,519 L45,519 L45,518 z M29,523 L29,525 L30,526 L33,526 L34,525 L34,523 z M46,523 L46,525 L47,526 L50,526 L51,525 L51,523 z" fill="#A9A9A9" id="path7143"/>
<path d="M21,531 L59,531 L59,532 L21,532 z" fill="#7092FF" id="rect7157"/>
<g id="g7159">
<path d="M20.5,530 C19.119,530 18,531.119 18,532.5 C18,533.881 19.119,535 20.5,535 L59.5,535 C60.881,535 62,533.881 62,532.5 C62,531.119 60.881,530 59.5,530 z" fill="#A9A9A9" id="path7161"/>
<path d="M22.5,531 C22.81,531.416 23,531.941 23,532.5 C23,533.059 22.81,533.584 22.5,534 L57.5,534 C57.19,533.584 57,533.059 57,532.5 C57,531.941 57.19,531.416 57.5,531 L22.5,531 z" fill="#FFFFFF" id="path7163"/>
<path d="M22,532.5 C22,533.328 21.328,534 20.5,534 C19.672,534 19,533.328 19,532.5 C19,531.672 19.672,531 20.5,531 C21.328,531 22,531.672 22,532.5 z" fill="#FFFFFF" id="path7165"/>
<path d="M61,532.5 C61,533.328 60.328,534 59.5,534 C58.672,534 58,533.328 58,532.5 C58,531.672 58.672,531 59.5,531 C60.328,531 61,531.672 61,532.5 z" fill="#FFFFFF" id="path7169"/>
<path d="M32.5,505 L29.625,511 L28,511 L27,512 L27,514 L28.406,514 L28,515 L28,520 L28,521 L28,521 C28,521 28.608,521.608 29,522 L30,522 L34,522 L36,523 L44,523 L46,522 L50,522 L51,522 L52,521 L52,521 L52,520 L52,515 L51.594,514 L53,514 L53,512 L52,511 L50.375,511 L47.5,505 z M33.5,508 L46.5,508 L48,512 L47,513 L33,513 L32,512 z M31,516 L34,516 L35,518 L35,519 L31,519 L30,518 L30,517 z M46,516 L49,516 L50,517 L50,518 L49,519 L45,519 L45,518 z M29,523 L29,525 L30,526 L33,526 L34,525 L34,523 z M46,523 L46,525 L47,526 L50,526 L51,525 L51,523 z" fill="#A9A9A9"/>
<path d="M21,531 L59,531 L59,532 L21,532 z" fill="#7092FF"/>
<g>
<path d="M20.5,530 C19.119,530 18,531.119 18,532.5 C18,533.881 19.119,535 20.5,535 L59.5,535 C60.881,535 62,533.881 62,532.5 C62,531.119 60.881,530 59.5,530 z" fill="#A9A9A9"/>
<path d="M22.5,531 C22.81,531.416 23,531.941 23,532.5 C23,533.059 22.81,533.584 22.5,534 L57.5,534 C57.19,533.584 57,533.059 57,532.5 C57,531.941 57.19,531.416 57.5,531 L22.5,531 z" fill="#FFFFFF"/>
<path d="M22,532.5 C22,533.328 21.328,534 20.5,534 C19.672,534 19,533.328 19,532.5 C19,531.672 19.672,531 20.5,531 C21.328,531 22,531.672 22,532.5 z" fill="#FFFFFF"/>
<path d="M61,532.5 C61,533.328 60.328,534 59.5,534 C58.672,534 58,533.328 58,532.5 C58,531.672 58.672,531 59.5,531 C60.328,531 61,531.672 61,532.5 z" fill="#FFFFFF"/>
</g>
</g>
</g>
<g id="landuse-images">
<path d="M0,400 L200,400 L200,480 L0,480 z" fill="#FFFFFF" id="rect9903"/>
<path d="M128.955,427.415 C126.526,433.621 112.429,437.423 97.471,435.906 C82.513,434.39 72.36,428.129 74.795,421.924 C77.229,415.721 91.324,411.921 106.278,413.437 C121.231,414.953 131.384,421.211 128.955,427.415 z" fill="#7092FF" id="path10275"/>
<path d="M53,436 L18,446 L18,440 L53,430 z" fill="#A9A9A9" id="path9990"/>
<path d="M54,430.5 L54,436.5 L83,451 L83,445 L54,430.5 z" fill="#A9A9A9" id="path9986"/>
<path d="M173.5,428 C172.271,428 171.272,428.893 171.062,430.062 C170.879,430.02 170.697,430 170.5,430 C169.119,430 168,431.119 168,432.5 C168,433.139 168.23,433.714 168.625,434.156 C167.68,434.513 167,435.43 167,436.5 C167,437.881 168,439 169.5,439 L170,439 L172,442.5 L172,443.938 C170.521,443.506 169.114,443.057 168,442.5 C163,440 158.566,435.517 153,435 C146.959,434.439 141.333,438.333 135.5,440 C129.667,441.667 118.561,441.959 118,448 L148,463 L183,453 C183.326,449.495 179.292,446.013 175,444.781 L175,442.5 L177,439 L177.719,439 C179,439.014 180,437.805 180,436.5 C180,435.43 179.32,434.513 178.375,434.156 C178.77,433.714 179,433.139 179,432.5 C179,431.119 177.881,430 176.5,430 C176.303,430 176.121,430.02 175.938,430.062 C175.728,428.893 174.729,428 173.5,428 z M172,439 L175,439 L174,441 L173,441 z" fill="#8CD05F" id="path10000"/>
<g id="path10048">
<path d="M0,400 L200,400 L200,480 L0,480 z" fill="#FFFFFF"/>
<path d="M128.955,427.415 C126.526,433.621 112.429,437.423 97.471,435.906 C82.513,434.39 72.36,428.129 74.795,421.924 C77.229,415.721 91.324,411.921 106.278,413.437 C121.231,414.953 131.384,421.211 128.955,427.415 z" fill="#7092FF"/>
<path d="M53,436 L18,446 L18,440 L53,430 z" fill="#A9A9A9"/>
<path d="M54,430.5 L54,436.5 L83,451 L83,445 L54,430.5 z" fill="#A9A9A9"/>
<path d="M173.5,428 C172.271,428 171.272,428.893 171.062,430.062 C170.879,430.02 170.697,430 170.5,430 C169.119,430 168,431.119 168,432.5 C168,433.139 168.23,433.714 168.625,434.156 C167.68,434.513 167,435.43 167,436.5 C167,437.881 168,439 169.5,439 L170,439 L172,442.5 L172,443.938 C170.521,443.506 169.114,443.057 168,442.5 C163,440 158.566,435.517 153,435 C146.959,434.439 141.333,438.333 135.5,440 C129.667,441.667 118.561,441.959 118,448 L148,463 L183,453 C183.326,449.495 179.292,446.013 175,444.781 L175,442.5 L177,439 L177.719,439 C179,439.014 180,437.805 180,436.5 C180,435.43 179.32,434.513 178.375,434.156 C178.77,433.714 179,433.139 179,432.5 C179,431.119 177.881,430 176.5,430 C176.303,430 176.121,430.02 175.938,430.062 C175.728,428.893 174.729,428 173.5,428 z M172,439 L175,439 L174,441 L173,441 z" fill="#8CD05F"/>
<g>
<path d="M18,446 L48,461 L48,455 L18,440 z" fill="#C1C1C1"/>
<path d="M18,446 L48,461 L48,455 L18,440 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="2"/>
</g>
<g id="path10050">
<g>
<path d="M83,451 L48,461 L48,455 L83,445 z" fill="#C1C1C1"/>
<path d="M83,451 L48,461 L48,455 L83,445 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="2"/>
</g>
<path d="M18,446 L48,461 L48,455 L18,440 z" fill="#A9A9A9" id="path9980"/>
<path d="M83,451 L48,461 L48,455 L83,445 z" fill="#A9A9A9" id="path9982"/>
<path d="M107,417 C107,417 109.636,420.529 112.438,420.969 C107.96,420.816 107,419 107,419 C107,419 106.04,420.816 101.562,420.969 C104.364,420.529 107,417 107,417 z" fill="#FFFFFF" id="path10271"/>
<path d="M93,419 C93,419 95.636,422.529 98.438,422.969 C93.96,422.816 93,421 93,421 C93,421 92.04,422.816 87.562,422.969 C90.364,422.529 93,419 93,419 z" fill="#FFFFFF" id="path10269"/>
<path d="M103,424 C103,424 105.636,427.529 108.438,427.969 C103.96,427.816 103,426 103,426 C103,426 102.04,427.816 97.562,427.969 C100.364,427.529 103,424 103,424 z" fill="#FFFFFF" id="path10253"/>
<path d="M18,446 L48,461 L48,455 L18,440 z" fill="#A9A9A9"/>
<path d="M83,451 L48,461 L48,455 L83,445 z" fill="#A9A9A9"/>
<path d="M107,417 C107,417 109.636,420.529 112.438,420.969 C107.96,420.816 107,419 107,419 C107,419 106.04,420.816 101.562,420.969 C104.364,420.529 107,417 107,417 z" fill="#FFFFFF"/>
<path d="M93,419 C93,419 95.636,422.529 98.438,422.969 C93.96,422.816 93,421 93,421 C93,421 92.04,422.816 87.562,422.969 C90.364,422.529 93,419 93,419 z" fill="#FFFFFF"/>
<path d="M103,424 C103,424 105.636,427.529 108.438,427.969 C103.96,427.816 103,426 103,426 C103,426 102.04,427.816 97.562,427.969 C100.364,427.529 103,424 103,424 z" fill="#FFFFFF"/>
</g>
<g id="poi-images">
<path d="M0,320 L200,320 L200,400 L0,400 z" fill="#FFFFFF"/>
@@ -119,6 +194,11 @@
<g id="logo-osm">
<path d="M204,465 L211.812,485.5 L204,506 L211.812,526.531 L204,547.031 L208.812,548.875 L234.844,522.812 L239.531,523.531 L245.219,517.844 C241.87,513.964 239.339,509.332 237.969,504.219 L240.656,502.062 C240.199,499.939 239.938,497.726 239.938,495.469 C239.938,484.191 245.95,474.277 254.938,468.781 L245.031,465 L224.5,472.812 L204,465 z M271.094,469.312 C256.734,469.312 245.094,480.953 245.094,495.312 C245.094,509.672 256.734,521.312 271.094,521.312 C285.453,521.312 297.094,509.672 297.094,495.312 C297.094,480.953 285.453,469.312 271.094,469.312 z M291.156,519.469 C285.735,523.987 278.776,526.719 271.188,526.719 C268.93,526.719 266.717,526.458 264.594,526 L262.438,528.656 C257.582,527.355 253.131,525.067 249.375,521.969 L243.5,527.844 L244.219,532.188 L222.406,554.031 L224.5,554.844 L245.031,547.031 L265.531,554.844 L286.031,547.031 L293.844,526.531 L291.156,519.469 z" fill="#7092FF" id="logo-osm-shape"/>
</g>
<g id="walkthrough-mouse">
<path d="M412.5,412 L412.5,428 L425,428 L425,424.5 C424.886,417.43 419.385,412.348 412.5,412 z" fill="#7092FF" id="walkthrough-mouse-right"/>
<path d="M412.5,411 L412.5,427 L400,427 L400,423.5 C400.114,416.43 405.615,411.348 412.5,411 z" fill="#7092FF" id="walkthrough-mouse-left"/>
<path d="M412.5,411 C419.404,411 425,416.596 425,423.5 L425,441.5 C425,448.404 419.404,454 412.5,454 C405.596,454 400,448.404 400,441.5 L400,423.5 C400,416.596 405.596,411 412.5,411 z M402,428 L402,441.5 C402,447.299 406.701,452 412.5,452 C418.299,452 423,447.299 423,441.5 L423,428 L402,428 z M413.573,413.054 L413.5,413.051 L413.5,426 L423,426 L423,423.5 C422.823,417.619 419.076,414.176 413.573,413.054 z M402,426 L411.5,426 L411.5,413.051 C405.988,413.815 402.279,417.971 402,423.5 L402,426 z" fill="#000000" id="walkthrough-mouse-shape"/>
</g>
<g id="logo-google">
<path d="M333.5,411 C332.115,411 331,412.115 331,413.5 L331,451 C331,452.385 332.115,453.5 333.5,453.5 L368.5,453.5 C369.885,453.5 371,452.385 371,451 L371,413.5 C371,412.115 369.885,411 368.5,411 L333.5,411 z M345.922,419.953 L353.656,419.953 L351.922,421.219 L349.469,421.219 C351.095,421.844 351.953,423.727 351.953,425.672 C351.953,427.306 351.06,428.72 349.781,429.719 C348.534,430.693 348.297,431.093 348.297,431.922 C348.297,432.629 349.629,433.833 350.328,434.328 C352.371,435.773 353.031,437.12 353.031,439.359 C353.031,442.152 350.333,444.922 345.438,444.922 C341.144,444.922 337.516,443.183 337.516,440.391 C337.516,437.555 340.816,434.812 345.109,434.812 L346.453,434.812 C345.866,434.241 345.406,433.536 345.406,432.672 C345.406,432.159 345.568,431.657 345.797,431.219 C345.564,431.236 345.324,431.25 345.078,431.25 C341.551,431.25 339.188,428.734 339.188,425.625 C339.188,422.583 342.455,419.953 345.922,419.953 z M360.5,420.5 L362,420.5 L362,425 L366.5,425 L366.5,426.5 L362,426.5 L362,431 L360.5,431 L360.5,426.5 L356,426.5 L356,425 L360.5,425 L360.5,420.5 z M344.594,421.062 C342.807,421.243 341.647,423.156 341.969,425.609 C342.312,428.225 344.211,430.394 346.203,430.453 C346.231,430.454 346.253,430.453 346.281,430.453 C348.226,430.453 349.526,428.408 349.187,425.828 C348.843,423.212 346.945,421.122 344.953,421.062 C344.829,421.059 344.713,421.051 344.594,421.062 z M345.938,435.531 C342.97,435.499 340.312,437.351 340.312,439.562 C340.312,441.819 342.454,443.688 345.422,443.688 C349.594,443.688 351.047,441.929 351.047,439.672 C351.047,439.4 351.014,439.134 350.953,438.875 C350.627,437.598 349.471,436.964 347.859,435.844 C347.273,435.654 346.63,435.538 345.938,435.531 z" fill="#7092FF" id="logo-google-shape"/>
</g>

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 256 KiB

+30
View File
@@ -283,6 +283,36 @@ describe('iD.History', function () {
});
});
describe('#checkpoint', function () {
it('saves and resets to checkpoints', function () {
history.perform(action, 'annotation1');
history.perform(action, 'annotation2');
history.perform(action, 'annotation3');
history.checkpoint('check1');
history.perform(action, 'annotation4');
history.perform(action, 'annotation5');
history.checkpoint('check2');
history.perform(action, 'annotation6');
history.perform(action, 'annotation7');
history.perform(action, 'annotation8');
history.reset('check1');
expect(history.undoAnnotation()).to.equal('annotation3');
history.reset('check2');
expect(history.undoAnnotation()).to.equal('annotation5');
history.reset('check1');
expect(history.undoAnnotation()).to.equal('annotation3');
});
it('emits a change event', function () {
history.on('change', spy);
history.reset();
expect(spy).to.have.been.called;
});
});
describe('#toJSON', function() {
it('doesn\'t generate unsaveable changes', function() {
var node_1 = iD.Node({id: 'n-1'});
+19 -3
View File
@@ -1,5 +1,5 @@
describe('d3.combobox', function() {
var body, content, input, combobox;
var body, container, content, input, combobox;
var data = [
{title: 'foo', value: 'foo'},
@@ -62,14 +62,16 @@ describe('d3.combobox', function() {
beforeEach(function() {
body = d3.select('body');
content = body.append('div');
container = body.append('div').attr('class', 'id-container');
content = container.append('div');
input = content.append('input');
combobox = iD.lib.d3combobox();
});
afterEach(function() {
content.remove();
body.selectAll('.combobox').remove();
content.remove();
container.remove();
});
function focusTypeahead(input) {
@@ -82,6 +84,20 @@ describe('d3.combobox', function() {
expect(input).to.be.classed('combobox-input');
});
it('adds combobox under body by default', function() {
input.call(combobox.data(data));
focusTypeahead(input);
expect(d3.select('body > div.combobox').nodes().length).to.equal(1);
expect(d3.select('.id-container > div.combobox').nodes().length).to.equal(0);
});
it('adds combobox under container with container option', function() {
input.call(combobox.container(container).data(data));
focusTypeahead(input);
expect(d3.select('body > div.combobox').nodes().length).to.equal(0);
expect(d3.select('.id-container > div.combobox').nodes().length).to.equal(1);
});
it('shows a menu of entries on focus', function() {
input.call(combobox.data(data));
focusTypeahead(input);
+15 -13
View File
@@ -1,12 +1,14 @@
describe('iD.uiFieldAccess', function() {
var selection, field;
var context, selection, field;
beforeEach(function() {
context = iD.Context();
selection = d3.select(document.createElement('div'));
field = iD.Context().presets().field('access');
field = context.presets().field('access');
});
it('creates inputs for a variety of modes of access', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
expect(selection.selectAll('.preset-access-access').size()).to.equal(1);
expect(selection.selectAll('.preset-access-foot').size()).to.equal(1);
@@ -16,20 +18,20 @@ describe('iD.uiFieldAccess', function() {
});
it('does not include "yes", "designated", "dismount" options for general access (#934), (#2213)', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
expect(_.map(access.options('access'), 'value')).not.to.include('yes');
expect(_.map(access.options('access'), 'value')).not.to.include('designated');
expect(_.map(access.options('access'), 'value')).not.to.include('dismount');
});
it('does include a "dismount" option for bicycles (#2726)', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
expect(_.map(access.options('bicycle'), 'value')).to.include('dismount');
expect(_.map(access.options('foot'), 'value')).not.to.include('dismount');
});
it('sets foot placeholder to "yes" for steps and pedestrian', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
access.tags({highway: 'steps'});
@@ -40,7 +42,7 @@ describe('iD.uiFieldAccess', function() {
});
it('sets foot placeholder to "designated" for footways', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
access.tags({highway: 'footway'});
@@ -48,7 +50,7 @@ describe('iD.uiFieldAccess', function() {
});
it('sets bicycle placeholder to "designated" for cycleways', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
access.tags({highway: 'cycleway'});
@@ -56,7 +58,7 @@ describe('iD.uiFieldAccess', function() {
});
it('sets horse placeholder to "designated" for bridleways', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
access.tags({highway: 'bridleway'});
@@ -64,7 +66,7 @@ describe('iD.uiFieldAccess', function() {
});
it('sets motor_vehicle placeholder to "no" for footways, steps, pedestrian, cycleway, bridleway, and path', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
['footway', 'steps', 'pedestrian', 'cycleway', 'bridleway', 'path'].forEach(function(value) {
access.tags({highway: value});
@@ -73,7 +75,7 @@ describe('iD.uiFieldAccess', function() {
});
it('sets motor_vehicle placeholder to "yes" for various other highway tags', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
['residential', 'motorway', 'trunk', 'primary', 'secondary', 'tertiary', 'service',
'unclassified', 'motorway_link', 'trunk_link', 'primary_link', 'secondary_link', 'tertiary_link'].forEach(function(value) {
@@ -83,7 +85,7 @@ describe('iD.uiFieldAccess', function() {
});
it('overrides a "yes" or "designated" placeholder with more specific access tag (#2213)', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
access.tags({highway: 'service', access: 'emergency'});
@@ -94,7 +96,7 @@ describe('iD.uiFieldAccess', function() {
});
it('overrides a "no" placeholder with more specific access tag (#2763)', function() {
var access = iD.uiFieldAccess(field);
var access = iD.uiFieldAccess(field, context);
selection.call(access);
access.tags({highway: 'cycleway', access: 'destination'});
+11 -10
View File
@@ -1,13 +1,14 @@
describe('iD.uiFieldLocalized', function() {
var selection, field;
var context, selection, field;
beforeEach(function() {
context = iD.Context();
selection = d3.select(document.createElement('div'));
field = iD.presetField('test', {key: 'name'});
});
it('adds a blank set of fields when the + button is clicked', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
happen.click(selection.selectAll('.localized-add').node());
expect(selection.selectAll('.localized-lang').nodes().length).to.equal(1);
@@ -15,7 +16,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('doesn\'t create a tag when the value is empty', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
happen.click(selection.selectAll('.localized-add').node());
@@ -29,7 +30,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('doesn\'t create a tag when the name is empty', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
happen.click(selection.selectAll('.localized-add').node());
@@ -43,7 +44,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('creates a tag after setting language then value', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
happen.click(selection.selectAll('.localized-add').node());
@@ -59,7 +60,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('creates a tag after setting value then language', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
happen.click(selection.selectAll('.localized-add').node());
@@ -75,7 +76,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('changes an existing language', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
localized.tags({'name:de': 'Value'});
@@ -90,7 +91,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('ignores similar keys like `old_name`', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
localized.tags({'old_name:de': 'Value'});
@@ -99,7 +100,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('removes the tag when the language is emptied', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
localized.tags({'name:de': 'Value'});
@@ -112,7 +113,7 @@ describe('iD.uiFieldLocalized', function() {
});
it('removes the tag when the value is emptied', function() {
var localized = iD.uiFieldLocalized(field, {});
var localized = iD.uiFieldLocalized(field, context);
selection.call(localized);
localized.tags({'name:de': 'Value'});