diff --git a/Node/core/lib/botbuilder-location.js b/Node/core/lib/botbuilder-location.js index b258dec..3af0224 100644 --- a/Node/core/lib/botbuilder-location.js +++ b/Node/core/lib/botbuilder-location.js @@ -4,22 +4,24 @@ var botbuilder_1 = require("botbuilder"); var common = require("./common"); var consts_1 = require("./consts"); var place_1 = require("./place"); -var defaultLocationDialog = require("./dialogs/default-location-dialog"); -var facebookLocationDialog = require("./dialogs/facebook-location-dialog"); -var requiredFieldsDialog = require("./dialogs/required-fields-dialog"); var addFavoriteLocationDialog = require("./dialogs/add-favorite-location-dialog"); -exports.LocationRequiredFields = requiredFieldsDialog.LocationRequiredFields; -exports.getFormattedAddressFromPlace = common.getFormattedAddressFromPlace; +var confirmDialog = require("./dialogs/confirm-dialog"); +var retrieveLocationDialog = require("./dialogs/retrieve-location-dialog"); +var requireFieldsDialog = require("./dialogs/require-fields-dialog"); +var retrieveFavoriteLocationDialog = require("./dialogs/retrieve-favorite-location-dialog"); +exports.LocationRequiredFields = requireFieldsDialog.LocationRequiredFields; +exports.getFormattedAddressFromLocation = common.getFormattedAddressFromLocation; exports.Place = place_1.Place; exports.createLibrary = function (apiKey) { if (typeof apiKey === "undefined") { throw "'apiKey' parameter missing"; } var lib = new botbuilder_1.Library(consts_1.LibraryName); - requiredFieldsDialog.register(lib); - defaultLocationDialog.register(lib, apiKey); - facebookLocationDialog.register(lib, apiKey); + retrieveFavoriteLocationDialog.register(lib, apiKey); + retrieveLocationDialog.register(lib, apiKey); + requireFieldsDialog.register(lib); addFavoriteLocationDialog.register(lib); + confirmDialog.register(lib); lib.localePath(path.join(__dirname, 'locale/')); lib.dialog('locationPickerPrompt', getLocationPickerPrompt()); return lib; @@ -33,36 +35,33 @@ exports.getLocation = function (session, options) { }; function getLocationPickerPrompt() { return [ - function (session, args) { + function (session, args, next) { session.dialogData.args = args; - if (args.useNativeControl && session.message.address.channelId == 'facebook') { - session.beginDialog('facebook-location-dialog', args); + if (!args.skipFavorites) { + botbuilder_1.Prompts.choice(session, session.gettext(consts_1.Strings.DialogStartBranchAsk), [session.gettext(consts_1.Strings.FavoriteLocations), session.gettext(consts_1.Strings.OtherLocation)], { listStyle: botbuilder_1.ListStyle.button, retryPrompt: session.gettext(consts_1.Strings.InvalidStartBranchResponse) }); } else { - session.beginDialog('default-location-dialog', args); - } - }, - function (session, results, next) { - if (results.response && results.response.place) { - session.beginDialog('required-fields-dialog', { - place: results.response.place, - requiredFields: session.dialogData.args.requiredFields - }); - } - else { - next(results); + next(); + } + }, + function (session, results, next) { + if (results && results.response && results.response.entity === session.gettext(consts_1.Strings.FavoriteLocations)) { + session.beginDialog('retrieve-favorite-location-dialog', session.dialogData.args); + } + else { + session.beginDialog('retrieve-location-dialog', session.dialogData.args); } }, function (session, results, next) { if (results.response && results.response.place) { + session.dialogData.place = results.response.place; if (session.dialogData.args.skipConfirmationAsk) { - session.endDialogWithResult({ response: results.response.place }); + next({ response: { confirmed: true } }); } else { var separator = session.gettext(consts_1.Strings.AddressSeparator); - var promptText = session.gettext(consts_1.Strings.ConfirmationAsk, common.getFormattedAddressFromPlace(results.response.place, separator)); - session.dialogData.place = results.response.place; - botbuilder_1.Prompts.confirm(session, promptText, { listStyle: botbuilder_1.ListStyle.none }); + var promptText = session.gettext(consts_1.Strings.ConfirmationAsk, common.getFormattedAddressFromLocation(results.response.place, separator)); + session.beginDialog('confirm-dialog', { confirmationPrompt: promptText }); } } else { @@ -70,7 +69,8 @@ function getLocationPickerPrompt() { } }, function (session, results, next) { - if (!session.dialogData.args.skipFavorites && results.response && !results.response.reset) { + session.dialogData.confirmed = results.response.confirmed; + if (results.response && results.response.confirmed && !session.dialogData.args.skipFavorites) { session.beginDialog('add-favorite-location-dialog', { place: session.dialogData.place }); } else { @@ -78,12 +78,12 @@ function getLocationPickerPrompt() { } }, function (session, results, next) { - if (results.response && results.response.reset) { + if (!session.dialogData.confirmed || (results.response && results.response.reset)) { session.send(consts_1.Strings.ResetPrompt); session.replaceDialog('locationPickerPrompt', session.dialogData.args); } else { - next({ response: session.dialogData.place }); + next({ response: common.processLocation(session.dialogData.place) }); } } ]; diff --git a/Node/core/lib/common.js b/Node/core/lib/common.js index 3d6854e..d9bded5 100644 --- a/Node/core/lib/common.js +++ b/Node/core/lib/common.js @@ -18,7 +18,7 @@ function createBaseDialog(options) { }); } exports.createBaseDialog = createBaseDialog; -function processLocation(location, includeStreetAddress) { +function processLocation(location) { var place = new place_1.Place(); place.type = location.entityType; place.name = location.name; @@ -28,43 +28,25 @@ function processLocation(location, includeStreetAddress) { place.locality = location.address.locality; place.postalCode = location.address.postalCode; place.region = location.address.adminDistrict; - if (includeStreetAddress) { - place.streetAddress = location.address.addressLine; - } + place.streetAddress = location.address.addressLine; } if (location.point && location.point.coordinates && location.point.coordinates.length == 2) { place.geo = new place_1.Geo(); - place.geo.latitude = location.point.coordinates[0]; - place.geo.longitude = location.point.coordinates[1]; + place.geo.latitude = location.point.coordinates[0].toString(); + place.geo.longitude = location.point.coordinates[1].toString(); } return place; } exports.processLocation = processLocation; -function buildPlaceFromGeo(latitude, longitude) { - var place = new place_1.Place(); - place.geo = new place_1.Geo(); - place.geo.latitude = latitude; - place.geo.longitude = longitude; - return place; -} -exports.buildPlaceFromGeo = buildPlaceFromGeo; -function getFormattedAddressFromPlace(place, separator) { +function getFormattedAddressFromLocation(location, separator) { var addressParts = new Array(); - if (place.streetAddress) { - addressParts.push(place.streetAddress); + if (location.address) { + addressParts = [location.address.addressLine, + location.address.locality, + location.address.adminDistrict, + location.address.postalCode, + location.address.countryRegion]; } - if (place.locality) { - addressParts.push(place.locality); - } - if (place.region) { - addressParts.push(place.region); - } - if (place.postalCode) { - addressParts.push(place.postalCode); - } - if (place.country) { - addressParts.push(place.country); - } - return addressParts.join(separator); + return addressParts.filter(function (i) { return i; }).join(separator); } -exports.getFormattedAddressFromPlace = getFormattedAddressFromPlace; +exports.getFormattedAddressFromLocation = getFormattedAddressFromLocation; diff --git a/Node/core/lib/consts.js b/Node/core/lib/consts.js index e549e5c..cd6912b 100644 --- a/Node/core/lib/consts.js +++ b/Node/core/lib/consts.js @@ -10,17 +10,32 @@ exports.Strings = { "ConfirmationAsk": "ConfirmationAsk", "Country": "Country", "DefaultPrompt": "DefaultPrompt", + "DeleteCommand": "DeleteCommand", + "DeleteFavoriteAbortion": "DeleteFavoriteAbortion", + "DeleteFavoriteConfirmationAsk": "DeleteFavoriteConfirmationAsk", + "DialogStartBranchAsk": "DialogStartBranchAsk", + "EditCommand": "EditCommand", + "EditFavoritePrompt": "EditFavoritePrompt", "EnterNewFavoriteLocationName": "EnterNewFavoriteLocationName", "FavoriteAddedConfirmation": "FavoriteAddedConfirmation", + "FavoriteDeletedConfirmation": "FavoriteDeletedConfirmation", + "FavoriteEdittedConfirmation": "FavoriteEdittedConfirmation", + "FavoriteLocations": "FavoriteLocations", "HelpMessage": "HelpMessage", + "InvalidFavoriteLocationSelection": "InvalidFavoriteLocationSelection", "InvalidLocationResponse": "InvalidLocationResponse", "InvalidLocationResponseFacebook": "InvalidLocationResponseFacebook", + "InvalidStartBranchResponse": "InvalidStartBranchResponse", "LocationNotFound": "LocationNotFound", "Locality": "Locality", "MultipleResultsFound": "MultipleResultsFound", + "NoFavoriteLocationsFound": "NoFavoriteLocationsFound", + "OtherComand": "OtherComand", + "OtherLocation": "OtherLocation", "PostalCode": "PostalCode", "Region": "Region", "ResetPrompt": "ResetPrompt", + "SelectFavoriteLocationPrompt": "SelectFavoriteLocationPrompt", "SingleResultFound": "SingleResultFound", "StreetAddress": "StreetAddress", "TitleSuffixFacebook": "TitleSuffixFacebook", diff --git a/Node/core/lib/dialogs/add-favorite-location-dialog.js b/Node/core/lib/dialogs/add-favorite-location-dialog.js index d32820e..cd10e8e 100644 --- a/Node/core/lib/dialogs/add-favorite-location-dialog.js +++ b/Node/core/lib/dialogs/add-favorite-location-dialog.js @@ -2,9 +2,7 @@ var common = require("../common"); var consts_1 = require("../consts"); var favorites_manager_1 = require("../services/favorites-manager"); -var confirmDialog = require("./confirm-dialog"); function register(library) { - confirmDialog.register(library); library.dialog('add-favorite-location-dialog', createDialog()); library.dialog('name-favorite-location-dialog', createNameFavoriteLocationDialog()); } diff --git a/Node/core/lib/dialogs/choice-dialog.js b/Node/core/lib/dialogs/choose-location-dialog.js similarity index 86% rename from Node/core/lib/dialogs/choice-dialog.js rename to Node/core/lib/dialogs/choose-location-dialog.js index 84a7ca1..997bc3e 100644 --- a/Node/core/lib/dialogs/choice-dialog.js +++ b/Node/core/lib/dialogs/choose-location-dialog.js @@ -3,7 +3,7 @@ var common = require("../common"); var consts_1 = require("../consts"); var place_1 = require("../place"); function register(library) { - library.dialog('choice-dialog', createDialog()); + library.dialog('choose-location-dialog', createDialog()); } exports.register = register; function createDialog() { @@ -21,8 +21,7 @@ function createDialog() { if (match) { var currentNumber = Number(match[0]); if (currentNumber > 0 && currentNumber <= session.dialogData.locations.length) { - var place = common.processLocation(session.dialogData.locations[currentNumber - 1], true); - session.endDialogWithResult({ response: { place: place } }); + session.endDialogWithResult({ response: { place: session.dialogData.locations[currentNumber - 1] } }); return; } } diff --git a/Node/core/lib/dialogs/single-location-confirm-dialog.js b/Node/core/lib/dialogs/confirm-single-location-dialog.js similarity index 77% rename from Node/core/lib/dialogs/single-location-confirm-dialog.js rename to Node/core/lib/dialogs/confirm-single-location-dialog.js index 906184d..acaa273 100644 --- a/Node/core/lib/dialogs/single-location-confirm-dialog.js +++ b/Node/core/lib/dialogs/confirm-single-location-dialog.js @@ -1,8 +1,7 @@ "use strict"; -var common = require("../common"); var consts_1 = require("../consts"); function register(library) { - library.dialog('single-location-confirm-dialog', createDialog()); + library.dialog('confirm-single-location-dialog', createDialog()); } exports.register = register; function createDialog() { @@ -13,8 +12,7 @@ function createDialog() { }, function (session, results, next) { if (results.response && results.response.confirmed) { - var place = common.processLocation(session.dialogData.locations[0], true); - session.endDialogWithResult({ response: { place: place } }); + session.endDialogWithResult({ response: { place: session.dialogData.locations[0] } }); } else { session.endDialogWithResult({ response: { reset: true } }); diff --git a/Node/core/lib/dialogs/delete-favorite-location-dialog.js b/Node/core/lib/dialogs/delete-favorite-location-dialog.js new file mode 100644 index 0000000..150678c --- /dev/null +++ b/Node/core/lib/dialogs/delete-favorite-location-dialog.js @@ -0,0 +1,32 @@ +"use strict"; +var consts_1 = require("../consts"); +var favorites_manager_1 = require("../services/favorites-manager"); +function register(library, apiKey) { + library.dialog('delete-favorite-location-dialog', createDialog()); +} +exports.register = register; +function createDialog() { + return [ + function (session, args) { + session.dialogData.args = args; + session.dialogData.toBeDeleted = args.toBeDeleted; + var deleteFavoriteConfirmationAsk = session.gettext(consts_1.Strings.DeleteFavoriteConfirmationAsk, args.toBeDeleted.name); + session.beginDialog('confirm-dialog', { confirmationPrompt: deleteFavoriteConfirmationAsk }); + }, + function (session, results, next) { + if (results.response && results.response.confirmed) { + var favoritesManager = new favorites_manager_1.FavoritesManager(session.userData); + favoritesManager.delete(session.dialogData.toBeDeleted); + session.send(session.gettext(consts_1.Strings.FavoriteDeletedConfirmation, session.dialogData.toBeDeleted.name)); + session.replaceDialog('retrieve-favorite-location-dialog', session.dialogData.args); + } + else if (results.response && results.response.confirmed === false) { + session.send(session.gettext(consts_1.Strings.DeleteFavoriteAbortion)); + session.replaceDialog('retrieve-favorite-location-dialog', session.dialogData.args); + } + else { + next(results); + } + } + ]; +} diff --git a/Node/core/lib/dialogs/edit-fravorite-location-dialog.js b/Node/core/lib/dialogs/edit-fravorite-location-dialog.js new file mode 100644 index 0000000..3eff6ab --- /dev/null +++ b/Node/core/lib/dialogs/edit-fravorite-location-dialog.js @@ -0,0 +1,32 @@ +"use strict"; +var consts_1 = require("../consts"); +var favorites_manager_1 = require("../services/favorites-manager"); +function register(library, apiKey) { + library.dialog('edit-favorite-location-dialog', createDialog()); +} +exports.register = register; +function createDialog() { + return [ + function (session, args) { + session.dialogData.args = args; + session.dialogData.toBeEditted = args.toBeEditted; + session.send(session.gettext(consts_1.Strings.EditFavoritePrompt, args.toBeEditted.name)); + session.beginDialog('retrieve-location-dialog', session.dialogData.args); + }, + function (session, results, next) { + if (results.response && results.response.place) { + var favoritesManager = new favorites_manager_1.FavoritesManager(session.userData); + var newfavoriteLocation = { + location: results.response.place, + name: session.dialogData.toBeEditted.name + }; + favoritesManager.update(session.dialogData.toBeEditted, newfavoriteLocation); + session.send(session.gettext(consts_1.Strings.FavoriteEdittedConfirmation, session.dialogData.toBeEditted.name)); + session.endDialogWithResult({ response: { place: results.response.place } }); + } + else { + next(results); + } + } + ]; +} diff --git a/Node/core/lib/dialogs/required-fields-dialog.js b/Node/core/lib/dialogs/require-fields-dialog.js similarity index 86% rename from Node/core/lib/dialogs/required-fields-dialog.js rename to Node/core/lib/dialogs/require-fields-dialog.js index bd264f9..cd4ac8c 100644 --- a/Node/core/lib/dialogs/required-fields-dialog.js +++ b/Node/core/lib/dialogs/require-fields-dialog.js @@ -12,15 +12,15 @@ var LocationRequiredFields; LocationRequiredFields[LocationRequiredFields["country"] = 16] = "country"; })(LocationRequiredFields = exports.LocationRequiredFields || (exports.LocationRequiredFields = {})); function register(library) { - library.dialog('required-fields-dialog', createDialog()); + library.dialog('require-fields-dialog', createDialog()); } exports.register = register; var fields = [ - { name: "streetAddress", prompt: consts_1.Strings.StreetAddress, flag: LocationRequiredFields.streetAddress }, + { name: "addressLine", prompt: consts_1.Strings.StreetAddress, flag: LocationRequiredFields.streetAddress }, { name: "locality", prompt: consts_1.Strings.Locality, flag: LocationRequiredFields.locality }, - { name: "region", prompt: consts_1.Strings.Region, flag: LocationRequiredFields.region }, + { name: "adminDistrict", prompt: consts_1.Strings.Region, flag: LocationRequiredFields.region }, { name: "postalCode", prompt: consts_1.Strings.PostalCode, flag: LocationRequiredFields.postalCode }, - { name: "country", prompt: consts_1.Strings.Country, flag: LocationRequiredFields.country }, + { name: "countryRegion", prompt: consts_1.Strings.Country, flag: LocationRequiredFields.country }, ]; function createDialog() { return common.createBaseDialog({ recognizeMode: botbuilder_1.RecognizeMode.onBegin }) @@ -61,11 +61,11 @@ function createDialog() { }); } function completeFieldIfMissing(session, field) { - if ((field.flag & session.dialogData.requiredFieldsFlag) && !session.dialogData.place[field.name]) { + if ((field.flag & session.dialogData.requiredFieldsFlag) && !session.dialogData.place.address[field.name]) { var prefix = ""; var prompt = ""; if (typeof session.dialogData.lastInput === "undefined") { - var formattedAddress = common.getFormattedAddressFromPlace(session.dialogData.place, session.gettext(consts_1.Strings.AddressSeparator)); + var formattedAddress = common.getFormattedAddressFromLocation(session.dialogData.place, session.gettext(consts_1.Strings.AddressSeparator)); if (formattedAddress) { prefix = session.gettext(consts_1.Strings.AskForPrefix, formattedAddress); prompt = session.gettext(consts_1.Strings.AskForTemplate, session.gettext(field.prompt)); diff --git a/Node/core/lib/dialogs/default-location-dialog.js b/Node/core/lib/dialogs/resolve-bing-location-dialog.js similarity index 60% rename from Node/core/lib/dialogs/default-location-dialog.js rename to Node/core/lib/dialogs/resolve-bing-location-dialog.js index da673cc..fb7965e 100644 --- a/Node/core/lib/dialogs/default-location-dialog.js +++ b/Node/core/lib/dialogs/resolve-bing-location-dialog.js @@ -1,15 +1,14 @@ "use strict"; var common = require("../common"); var consts_1 = require("../consts"); -var botbuilder_1 = require("botbuilder"); -var map_card_1 = require("../map-card"); var locationService = require("../services/bing-geospatial-service"); -var singleLocationConfirmDialog = require("./single-location-confirm-dialog"); -var choiceDialog = require("./choice-dialog"); +var confirmSingleLocationDialog = require("./confirm-single-location-dialog"); +var chooseLocationDialog = require("./choose-location-dialog"); +var location_card_builder_1 = require("../services/location-card-builder"); function register(library, apiKey) { - singleLocationConfirmDialog.register(library); - choiceDialog.register(library); - library.dialog('default-location-dialog', createDialog()); + confirmSingleLocationDialog.register(library); + chooseLocationDialog.register(library); + library.dialog('resolve-bing-location-dialog', createDialog()); library.dialog('location-resolve-dialog', createLocationResolveDialog(apiKey)); } exports.register = register; @@ -23,10 +22,10 @@ function createDialog() { if (results.response && results.response.locations) { var locations = results.response.locations; if (locations.length == 1) { - session.beginDialog('single-location-confirm-dialog', { locations: locations }); + session.beginDialog('confirm-single-location-dialog', { locations: locations }); } else { - session.beginDialog('choice-dialog', { locations: locations }); + session.beginDialog('choose-location-dialog', { locations: locations }); } } else { @@ -50,30 +49,10 @@ function createLocationResolveDialog(apiKey) { } var locationCount = Math.min(MAX_CARD_COUNT, locations.length); locations = locations.slice(0, locationCount); - var reply = createLocationsCard(apiKey, session, locations); + var reply = new location_card_builder_1.LocationCardBuilder(apiKey).createHeroCards(session, locations); session.send(reply); session.endDialogWithResult({ response: { locations: locations } }); }) .catch(function (error) { return session.error(error); }); }); } -function createLocationsCard(apiKey, session, locations) { - var cards = new Array(); - for (var i = 0; i < locations.length; i++) { - cards.push(constructCard(apiKey, session, locations, i)); - } - return new botbuilder_1.Message(session) - .attachmentLayout(botbuilder_1.AttachmentLayout.carousel) - .attachments(cards); -} -function constructCard(apiKey, session, locations, index) { - var location = locations[index]; - var card = new map_card_1.MapCard(apiKey, session); - if (locations.length > 1) { - card.location(location, index + 1); - } - else { - card.location(location); - } - return card; -} diff --git a/Node/core/lib/dialogs/facebook-location-dialog.js b/Node/core/lib/dialogs/retrieve-facebook-location-dialog.js similarity index 85% rename from Node/core/lib/dialogs/facebook-location-dialog.js rename to Node/core/lib/dialogs/retrieve-facebook-location-dialog.js index 26db8be..66e23a4 100644 --- a/Node/core/lib/dialogs/facebook-location-dialog.js +++ b/Node/core/lib/dialogs/retrieve-facebook-location-dialog.js @@ -4,7 +4,7 @@ var common = require("../common"); var botbuilder_1 = require("botbuilder"); var locationService = require("../services/bing-geospatial-service"); function register(library, apiKey) { - library.dialog('facebook-location-dialog', createDialog(apiKey)); + library.dialog('retrive-facebook-location-dialog', createDialog(apiKey)); library.dialog('facebook-location-resolve-dialog', createLocationResolveDialog()); } exports.register = register; @@ -16,11 +16,11 @@ function createDialog(apiKey) { }, function (session, results, next) { if (session.dialogData.args.reverseGeocode && results.response && results.response.place) { - locationService.getLocationByPoint(apiKey, results.response.place.geo.latitude, results.response.place.geo.longitude) + locationService.getLocationByPoint(apiKey, results.response.place.point.coordinates[0], results.response.place.point.coordinates[1]) .then(function (locations) { var place; if (locations.length) { - place = common.processLocation(locations[0], false); + place = locations[0]; } else { place = results.response.place; @@ -46,7 +46,7 @@ function createLocationResolveDialog() { var entities = session.message.entities; for (var i = 0; i < entities.length; i++) { if (entities[i].type == "Place" && entities[i].geo && entities[i].geo.latitude && entities[i].geo.longitude) { - session.endDialogWithResult({ response: { place: common.buildPlaceFromGeo(entities[i].geo.latitude, entities[i].geo.longitude) } }); + session.endDialogWithResult({ response: { place: buildLocationFromGeo(entities[i].geo.latitude, entities[i].geo.longitude) } }); return; } } @@ -66,3 +66,7 @@ function sendLocationPrompt(session, prompt) { }); return session.send(message); } +function buildLocationFromGeo(latitude, longitude) { + var coordinates = [latitude, longitude]; + return { point: { coordinates: coordinates } }; +} diff --git a/Node/core/lib/dialogs/retrieve-favorite-location-dialog.js b/Node/core/lib/dialogs/retrieve-favorite-location-dialog.js new file mode 100644 index 0000000..c1326a3 --- /dev/null +++ b/Node/core/lib/dialogs/retrieve-favorite-location-dialog.js @@ -0,0 +1,87 @@ +"use strict"; +var common = require("../common"); +var consts_1 = require("../consts"); +var location_card_builder_1 = require("../services/location-card-builder"); +var favorites_manager_1 = require("../services/favorites-manager"); +var deleteFavoriteLocationDialog = require("./delete-favorite-location-dialog"); +var editFavoriteLocationDialog = require("./edit-fravorite-location-dialog"); +function register(library, apiKey) { + library.dialog('retrieve-favorite-location-dialog', createDialog(apiKey)); + deleteFavoriteLocationDialog.register(library, apiKey); + editFavoriteLocationDialog.register(library, apiKey); +} +exports.register = register; +function createDialog(apiKey) { + return common.createBaseDialog() + .onBegin(function (session, args) { + session.dialogData.args = args; + var favoritesManager = new favorites_manager_1.FavoritesManager(session.userData); + var userFavorites = favoritesManager.getFavorites(); + if (userFavorites.length == 0) { + session.send(session.gettext(consts_1.Strings.NoFavoriteLocationsFound)); + session.replaceDialog('retrieve-location-dialog', session.dialogData.args); + return; + } + session.dialogData.userFavorites = userFavorites; + var locations = []; + var names = []; + for (var i = 0; i < userFavorites.length; i++) { + locations.push(userFavorites[i].location); + names.push(userFavorites[i].name); + } + session.send(new location_card_builder_1.LocationCardBuilder(apiKey).createHeroCards(session, locations, true, names)); + session.send(session.gettext(consts_1.Strings.SelectFavoriteLocationPrompt)).sendBatch(); + }).onDefault(function (session) { + var text = session.message.text; + if (text === session.gettext(consts_1.Strings.OtherComand)) { + session.replaceDialog('retrieve-location-dialog', session.dialogData.args); + } + else { + var selection = tryParseCommandSelection(text, session.dialogData.userFavorites.length); + if (selection.command === "select") { + session.replaceDialog('require-fields-dialog', { + place: session.dialogData.userFavorites[selection.index - 1].location, + requiredFields: session.dialogData.args.requiredFields + }); + } + else if (selection.command === session.gettext(consts_1.Strings.DeleteCommand)) { + session.dialogData.args.toBeDeleted = session.dialogData.userFavorites[selection.index - 1]; + session.replaceDialog('delete-favorite-location-dialog', session.dialogData.args); + } + else if (selection.command === session.gettext(consts_1.Strings.EditCommand)) { + session.dialogData.args.toBeEditted = session.dialogData.userFavorites[selection.index - 1]; + session.replaceDialog('edit-favorite-location-dialog', session.dialogData.args); + } + else { + session.send(session.gettext(consts_1.Strings.InvalidFavoriteLocationSelection)).sendBatch(); + } + } + }); +} +function tryParseNumberSelection(text) { + var tokens = text.trim().split(' '); + if (tokens.length == 1) { + var numberExp = /[+-]?(?:\d+\.?\d*|\d*\.?\d+)/; + var match = numberExp.exec(text); + if (match) { + return Number(match[0]); + } + } + return -1; +} +function tryParseCommandSelection(text, maxIndex) { + var tokens = text.trim().split(' '); + if (tokens.length == 1) { + var index = tryParseNumberSelection(text); + if (index > 0 && index <= maxIndex) { + return { index: index, command: "select" }; + } + } + else if (tokens.length == 2) { + var index = tryParseNumberSelection(tokens[1]); + if (index > 0 && index <= maxIndex) { + return { index: index, command: tokens[0] }; + } + } + return { command: "" }; +} diff --git a/Node/core/lib/dialogs/retrieve-location-dialog.js b/Node/core/lib/dialogs/retrieve-location-dialog.js new file mode 100644 index 0000000..ddda242 --- /dev/null +++ b/Node/core/lib/dialogs/retrieve-location-dialog.js @@ -0,0 +1,33 @@ +"use strict"; +var resolveBingLocationDialog = require("./resolve-bing-location-dialog"); +var retrieveFacebookLocationDialog = require("./retrieve-facebook-location-dialog"); +function register(library, apiKey) { + library.dialog('retrieve-location-dialog', createDialog()); + resolveBingLocationDialog.register(library, apiKey); + retrieveFacebookLocationDialog.register(library, apiKey); +} +exports.register = register; +function createDialog() { + return [ + function (session, args) { + session.dialogData.args = args; + if (args.useNativeControl && session.message.address.channelId == 'facebook') { + session.beginDialog('retrieve-facebook-location-dialog', args); + } + else { + session.beginDialog('resolve-bing-location-dialog', args); + } + }, + function (session, results, next) { + if (results.response && results.response.place) { + session.beginDialog('require-fields-dialog', { + place: results.response.place, + requiredFields: session.dialogData.args.requiredFields + }); + } + else { + next(results); + } + } + ]; +} diff --git a/Node/core/lib/locale/en/botbuilder-location.json b/Node/core/lib/locale/en/botbuilder-location.json index 66abf9b..e38b310 100644 --- a/Node/core/lib/locale/en/botbuilder-location.json +++ b/Node/core/lib/locale/en/botbuilder-location.json @@ -1,6 +1,6 @@ { "AddressSeparator": ", ", - "AddToFavoritesAsk": "Do you want me to add this address to your favorite locations?", + "AddToFavoritesAsk": "Do you want me to add this address to your favorite locations?", "AskForEmptyAddressTemplate": "Please provide the %s.", "AskForPrefix": "OK %s.", "AskForTemplate": " Please also provide the %s.", @@ -8,17 +8,32 @@ "ConfirmationAsk": "OK, I will ship to %s. Is that correct? Enter 'yes' or 'no'.", "Country": "country", "DefaultPrompt": "Where should I ship your order?", + "DeleteCommand": "delete", + "DeleteFavoriteAbortion": "OK, deletion aborted.", + "DeleteFavoriteConfirmationAsk": "Are you sure you want to delete %s from your favorite locations?", + "DialogStartBranchAsk": "How would you like to pick a location?", + "EditCommand": "edit", + "EditFavoritePrompt": "OK, let's edit %s. Enter a new address.", "EnterNewFavoriteLocationName": "OK, please enter a friendly name for this address. You can use 'home', 'work' or any other name you prefer.", "FavoriteAddedConfirmation": "OK, I added %s to your favorite locations.", - "HelpMessage": "Say or type a valid address when asked, and I will try to find it using Bing. You can provide the full address information (street no. / name, city, region, postal/zip code, country) or a part of it. If you want to change the address, say or type 'reset'. Finally, say or type 'cancel' to exit without providing an address.", + "FavoriteDeletedConfirmation": "OK, I deleted %s from your favorite locations.", + "FavoriteEdittedConfirmation": "OK, I editted %s in your favorite locations with this new address.", + "FavoriteLocations": "Favorite Locations", + "HelpMessage": "Say or type a valid address when asked, and I will try to find it using Bing. You can provide the full address information (street no. / name, city, region, postal/zip code, country) or a part of it. If you want to change the address, say or type 'reset'. Finally, say or type 'cancel' to exit without providing an address.", + "InvalidFavoriteLocationSelection": "Type or say a number to choose the address, enter 'other' to create a new favorite location, or enter 'cancel' to exit. You can also type or say 'edit' or 'delete' followed by a number to edit or delete the respective location.", "InvalidLocationResponse": "Didn't get that. Choose a location or cancel.", "InvalidLocationResponseFacebook": "Tap on Send Location to proceed; type or say cancel to exit.", + "InvalidStartBranchResponse": "Tap one of the options to proceed; type or say cancel to exit.", "LocationNotFound": "I could not find this address. Please try again.", "Locality": "city or locality", "MultipleResultsFound": "I found these results. Type or say a number to choose the address, or enter 'other' to select another address.", + "NoFavoriteLocationsFound": "You do not seem to have any favorite locations at the moment. Enter an address and you will be able to save it to your favorite locations.", + "OtherComand": "other", + "OtherLocation": "Other Location", "PostalCode": "zip or postal code", "Region": "state or region", "ResetPrompt": "OK, let's start over.", + "SelectFavoriteLocationPrompt": "Here are your favorite locations. Type or say a number to use the respective location, or 'other' to use a different location. You can also type or say 'edit' or 'delete' followed by a number to edit or delete the respective location.", "SingleResultFound": "I found this result. Is this the correct address?", "StreetAddress": "street address", "TitleSuffix": " Type or say an address", diff --git a/Node/core/lib/map-card.js b/Node/core/lib/map-card.js index 5568dc4..04f3829 100644 --- a/Node/core/lib/map-card.js +++ b/Node/core/lib/map-card.js @@ -13,12 +13,15 @@ var MapCard = (function (_super) { _this.apiKey = apiKey; return _this; } - MapCard.prototype.location = function (location, index) { - var indexText = ""; + MapCard.prototype.location = function (location, index, locationName) { + var prefixText = ""; if (index !== undefined) { - indexText = index + ". "; + prefixText = index + ". "; } - this.subtitle(indexText + location.address.formattedAddress); + if (locationName !== undefined) { + prefixText += locationName + ": "; + } + this.subtitle(prefixText + location.address.formattedAddress); if (location.point) { var locationUrl; try { diff --git a/Node/core/lib/rawLocation.js b/Node/core/lib/rawLocation.js new file mode 100644 index 0000000..5bd4846 --- /dev/null +++ b/Node/core/lib/rawLocation.js @@ -0,0 +1,17 @@ +"use strict"; +var RawLocation = (function () { + function RawLocation() { + } + return RawLocation; +}()); +exports.RawLocation = RawLocation; +var Address = (function () { + function Address() { + } + return Address; +}()); +var Point = (function () { + function Point() { + } + return Point; +}()); diff --git a/Node/core/lib/services/favorites-manager.js b/Node/core/lib/services/favorites-manager.js index 782cf72..336290b 100644 --- a/Node/core/lib/services/favorites-manager.js +++ b/Node/core/lib/services/favorites-manager.js @@ -2,16 +2,16 @@ var FavoritesManager = (function () { function FavoritesManager(userData) { this.userData = userData; - this.MAX_FAVORITE_COUNT = 5; - this.FAVORITES_KEY = 'favorites'; + this.maxFavoriteCount = 5; + this.favoritesKey = 'favorites'; } FavoritesManager.prototype.maxCapacityReached = function () { - return this.getFavorites().length >= this.MAX_FAVORITE_COUNT; + return this.getFavorites().length >= this.maxFavoriteCount; }; FavoritesManager.prototype.isFavorite = function (location) { var favorites = this.getFavorites(); for (var i = 0; i < favorites.length; i++) { - if (favorites[i].location.formattedAddress === location.formattedAddress) { + if (this.areEqual(favorites[i].location, location)) { return true; } } @@ -19,14 +19,37 @@ var FavoritesManager = (function () { }; FavoritesManager.prototype.add = function (favoriteLocation) { var favorites = this.getFavorites(); - if (favorites.length >= this.MAX_FAVORITE_COUNT) { + if (favorites.length >= this.maxFavoriteCount) { throw ('The max allowed number of favorite locations has already been reached.'); } favorites.push(favoriteLocation); - this.userData[this.FAVORITES_KEY] = favorites; + this.userData[this.favoritesKey] = favorites; + }; + FavoritesManager.prototype.delete = function (favoriteLocation) { + var favorites = this.getFavorites(); + var newFavorites = []; + for (var i = 0; i < favorites.length; i++) { + if (!this.areEqual(favorites[i].location, favoriteLocation.location)) { + newFavorites.push(favorites[i]); + } + } + this.userData[this.favoritesKey] = newFavorites; + }; + FavoritesManager.prototype.update = function (currentValue, newValue) { + var favorites = this.getFavorites(); + var newFavorites = []; + for (var i = 0; i < favorites.length; i++) { + if (this.areEqual(favorites[i].location, currentValue.location)) { + newFavorites.push(newValue); + } + else { + newFavorites.push(favorites[i]); + } + } + this.userData[this.favoritesKey] = newFavorites; }; FavoritesManager.prototype.getFavorites = function () { - var storedFavorites = this.userData[this.FAVORITES_KEY]; + var storedFavorites = this.userData[this.favoritesKey]; if (storedFavorites) { return storedFavorites; } @@ -34,6 +57,9 @@ var FavoritesManager = (function () { return []; } }; + FavoritesManager.prototype.areEqual = function (location0, location1) { + return location0.address.formattedAddress === location1.address.formattedAddress; + }; return FavoritesManager; }()); exports.FavoritesManager = FavoritesManager; diff --git a/Node/core/lib/services/location-card-builder.js b/Node/core/lib/services/location-card-builder.js new file mode 100644 index 0000000..1659fbd --- /dev/null +++ b/Node/core/lib/services/location-card-builder.js @@ -0,0 +1,35 @@ +"use strict"; +var botbuilder_1 = require("botbuilder"); +var map_card_1 = require("../map-card"); +var LocationCardBuilder = (function () { + function LocationCardBuilder(apiKey) { + this.apiKey = apiKey; + } + LocationCardBuilder.prototype.createHeroCards = function (session, locations, alwaysShowNumericPrefix, locationNames) { + var cards = new Array(); + for (var i = 0; i < locations.length; i++) { + cards.push(this.constructCard(session, locations, i, alwaysShowNumericPrefix, locationNames)); + } + return new botbuilder_1.Message(session) + .attachmentLayout(botbuilder_1.AttachmentLayout.carousel) + .attachments(cards); + }; + LocationCardBuilder.prototype.constructCard = function (session, locations, index, alwaysShowNumericPrefix, locationNames) { + var location = locations[index]; + var card = new map_card_1.MapCard(this.apiKey, session); + if (alwaysShowNumericPrefix || locations.length > 1) { + if (locationNames) { + card.location(location, index + 1, locationNames[index]); + } + else { + card.location(location, index + 1); + } + } + else { + card.location(location); + } + return card; + }; + return LocationCardBuilder; +}()); +exports.LocationCardBuilder = LocationCardBuilder; diff --git a/Node/core/src/botbuilder-location.d.ts b/Node/core/src/botbuilder-location.d.ts index 92cc5b7..3c35db7 100644 --- a/Node/core/src/botbuilder-location.d.ts +++ b/Node/core/src/botbuilder-location.d.ts @@ -1,4 +1,5 @@ import * as builder from "botbuilder"; +import { RawLocation } from "./rawLocation"; //============================================================================= // @@ -130,7 +131,7 @@ export function getLocation(session: builder.Session, options: ILocationPromptOp /** * Gets a formatted address string. - * @param place Place object containing the address. + * @param location object containing the address. * @param separator The string separating the address parts. */ -export function getFormattedAddressFromPlace(place: Place, separator: string): string; \ No newline at end of file +export function getFormattedAddressFromLocation(location: RawLocation, separator: string): string; diff --git a/Node/core/src/botbuilder-location.ts b/Node/core/src/botbuilder-location.ts index df0d28c..717e8fa 100644 --- a/Node/core/src/botbuilder-location.ts +++ b/Node/core/src/botbuilder-location.ts @@ -1,23 +1,25 @@ import * as path from 'path'; -import { Library, Session, IDialogResult, Prompts, ListStyle } from 'botbuilder'; +import { IDialogResult, IPromptOptions, Library, ListStyle, Prompts, Session} from 'botbuilder'; import * as common from './common'; import { Strings, LibraryName } from './consts'; import { Place } from './place'; -import * as defaultLocationDialog from './dialogs/default-location-dialog'; -import * as facebookLocationDialog from './dialogs/facebook-location-dialog' -import * as requiredFieldsDialog from './dialogs/required-fields-dialog'; import * as addFavoriteLocationDialog from './dialogs/add-favorite-location-dialog'; +import * as confirmDialog from './dialogs/confirm-dialog'; +import * as retrieveLocationDialog from './dialogs/retrieve-location-dialog' +import * as requireFieldsDialog from './dialogs/require-fields-dialog'; +import * as retrieveFavoriteLocationDialog from './dialogs/retrieve-favorite-location-dialog' export interface ILocationPromptOptions { prompt: string; - requiredFields?: requiredFieldsDialog.LocationRequiredFields; + requiredFields?: requireFieldsDialog.LocationRequiredFields; skipConfirmationAsk?: boolean; useNativeControl?: boolean, - reverseGeocode?: boolean + reverseGeocode?: boolean, + skipFavorites?: boolean } -exports.LocationRequiredFields = requiredFieldsDialog.LocationRequiredFields; -exports.getFormattedAddressFromPlace = common.getFormattedAddressFromPlace; +exports.LocationRequiredFields = requireFieldsDialog.LocationRequiredFields; +exports.getFormattedAddressFromLocation = common.getFormattedAddressFromLocation; exports.Place = Place; //========================================================= @@ -31,11 +33,11 @@ exports.createLibrary = (apiKey: string): Library => { } var lib = new Library(LibraryName); - - requiredFieldsDialog.register(lib); - defaultLocationDialog.register(lib, apiKey); - facebookLocationDialog.register(lib, apiKey); + retrieveFavoriteLocationDialog.register(lib, apiKey); + retrieveLocationDialog.register(lib, apiKey); + requireFieldsDialog.register(lib); addFavoriteLocationDialog.register(lib); + confirmDialog.register(lib); lib.localePath(path.join(__dirname, 'locale/')); lib.dialog('locationPickerPrompt', getLocationPickerPrompt()); @@ -58,46 +60,50 @@ exports.getLocation = function (session: Session, options: ILocationPromptOption function getLocationPickerPrompt() { return [ - // retrieve the location - (session: Session, args: ILocationPromptOptions) => { + // handle different ways of retrieving a location (favorite, other, etc) + (session: Session, args: ILocationPromptOptions, next: (results?: IDialogResult) => void) => { session.dialogData.args = args; - if (args.useNativeControl && session.message.address.channelId == 'facebook') { - session.beginDialog('facebook-location-dialog', args); + if (!args.skipFavorites) { + Prompts.choice( + session, + session.gettext(Strings.DialogStartBranchAsk), + [ session.gettext(Strings.FavoriteLocations), session.gettext(Strings.OtherLocation) ], + { listStyle: ListStyle.button, retryPrompt: session.gettext(Strings.InvalidStartBranchResponse)}); } else { - session.beginDialog('default-location-dialog', args); + next(); } }, - // complete required fields, if applicable + // retrieve location (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { - if (results.response && results.response.place) { - session.beginDialog('required-fields-dialog', { - place: results.response.place, - requiredFields: session.dialogData.args.requiredFields - }) - } else { - next(results); + if (results && results.response && results.response.entity === session.gettext(Strings.FavoriteLocations)) { + session.beginDialog('retrieve-favorite-location-dialog', session.dialogData.args); + } + else { + session.beginDialog('retrieve-location-dialog', session.dialogData.args); } }, - // make final confirmation + // make final confirmation (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { if (results.response && results.response.place) { + session.dialogData.place = results.response.place; if (session.dialogData.args.skipConfirmationAsk) { - session.endDialogWithResult({ response: results.response.place }); + next({ response: { confirmed: true }}); } else { var separator = session.gettext(Strings.AddressSeparator); - var promptText = session.gettext(Strings.ConfirmationAsk, common.getFormattedAddressFromPlace(results.response.place, separator)); - session.dialogData.place = results.response.place; - Prompts.confirm(session, promptText, { listStyle: ListStyle.none }) + var promptText = session.gettext(Strings.ConfirmationAsk, common.getFormattedAddressFromLocation(results.response.place, separator)); + session.beginDialog('confirm-dialog' , { confirmationPrompt: promptText }); } - } else { + } + else { next(results); } }, // offer add to favorites, if applicable (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { - if(!session.dialogData.args.skipFavorites && results.response && !results.response.reset) { + session.dialogData.confirmed = results.response.confirmed; + if(results.response && results.response.confirmed && !session.dialogData.args.skipFavorites) { session.beginDialog('add-favorite-location-dialog', { place : session.dialogData.place }); } else { @@ -105,12 +111,12 @@ function getLocationPickerPrompt() { } }, (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { - if (results.response && results.response.reset) { - session.send(Strings.ResetPrompt) + if ( !session.dialogData.confirmed || (results.response && results.response.reset)) { + session.send(Strings.ResetPrompt); session.replaceDialog('locationPickerPrompt', session.dialogData.args); } else { - next({ response: session.dialogData.place }); + next({ response: common.processLocation(session.dialogData.place) }); } } ]; diff --git a/Node/core/src/common.ts b/Node/core/src/common.ts index ba4822a..3809305 100644 --- a/Node/core/src/common.ts +++ b/Node/core/src/common.ts @@ -1,6 +1,7 @@ import { Session, IntentDialog } from 'botbuilder'; import { Strings } from './consts'; import { Place, Geo } from './place'; +import { RawLocation } from './rawLocation' export function createBaseDialog(options?: any): IntentDialog { return new IntentDialog(options) @@ -18,7 +19,7 @@ export function createBaseDialog(options?: any): IntentDialog { }); } -export function processLocation(location: any, includeStreetAddress: boolean): Place { +export function processLocation(location: RawLocation): Place { var place: Place = new Place(); place.type = location.entityType; place.name = location.name; @@ -29,51 +30,28 @@ export function processLocation(location: any, includeStreetAddress: boolean): P place.locality = location.address.locality; place.postalCode = location.address.postalCode; place.region = location.address.adminDistrict; - if (includeStreetAddress) { - place.streetAddress = location.address.addressLine; - } + place.streetAddress = location.address.addressLine; } if (location.point && location.point.coordinates && location.point.coordinates.length == 2) { place.geo = new Geo(); - place.geo.latitude = location.point.coordinates[0]; - place.geo.longitude = location.point.coordinates[1]; + place.geo.latitude = location.point.coordinates[0].toString(); + place.geo.longitude = location.point.coordinates[1].toString(); } return place; } -export function buildPlaceFromGeo(latitude: string, longitude: string) { - var place = new Place(); - place.geo = new Geo(); - place.geo.latitude = latitude; - place.geo.longitude = longitude; +export function getFormattedAddressFromLocation(location: RawLocation, separator: string): string { + let addressParts: Array = new Array(); - return place; -} - -export function getFormattedAddressFromPlace(place: Place, separator: string): string { - var addressParts: Array = new Array(); - - if (place.streetAddress) { - addressParts.push(place.streetAddress); + if (location.address) { + addressParts = [ location.address.addressLine, + location.address.locality, + location.address.adminDistrict, + location.address.postalCode, + location.address.countryRegion]; } - if (place.locality) { - addressParts.push(place.locality); - } - - if (place.region) { - addressParts.push(place.region); - } - - if (place.postalCode) { - addressParts.push(place.postalCode); - } - - if (place.country) { - addressParts.push(place.country); - } - - return addressParts.join(separator); + return addressParts.filter(i => i).join(separator); } \ No newline at end of file diff --git a/Node/core/src/consts.ts b/Node/core/src/consts.ts index 3ee3d49..bfe52aa 100644 --- a/Node/core/src/consts.ts +++ b/Node/core/src/consts.ts @@ -11,17 +11,32 @@ export const Strings = { "ConfirmationAsk": "ConfirmationAsk", "Country": "Country", "DefaultPrompt": "DefaultPrompt", + "DeleteCommand": "DeleteCommand", + "DeleteFavoriteAbortion" : "DeleteFavoriteAbortion", + "DeleteFavoriteConfirmationAsk": "DeleteFavoriteConfirmationAsk", + "DialogStartBranchAsk": "DialogStartBranchAsk", + "EditCommand": "EditCommand", + "EditFavoritePrompt": "EditFavoritePrompt", "EnterNewFavoriteLocationName": "EnterNewFavoriteLocationName", "FavoriteAddedConfirmation": "FavoriteAddedConfirmation", + "FavoriteDeletedConfirmation": "FavoriteDeletedConfirmation", + "FavoriteEdittedConfirmation": "FavoriteEdittedConfirmation", + "FavoriteLocations": "FavoriteLocations", "HelpMessage": "HelpMessage", + "InvalidFavoriteLocationSelection": "InvalidFavoriteLocationSelection", "InvalidLocationResponse": "InvalidLocationResponse", "InvalidLocationResponseFacebook": "InvalidLocationResponseFacebook", + "InvalidStartBranchResponse": "InvalidStartBranchResponse", "LocationNotFound": "LocationNotFound", "Locality": "Locality", "MultipleResultsFound": "MultipleResultsFound", + "NoFavoriteLocationsFound": "NoFavoriteLocationsFound", + "OtherComand": "OtherComand", + "OtherLocation": "OtherLocation", "PostalCode": "PostalCode", "Region": "Region", "ResetPrompt": "ResetPrompt", + "SelectFavoriteLocationPrompt" : "SelectFavoriteLocationPrompt", "SingleResultFound": "SingleResultFound", "StreetAddress": "StreetAddress", "TitleSuffixFacebook": "TitleSuffixFacebook", diff --git a/Node/core/src/dialogs/add-favorite-location-dialog.ts b/Node/core/src/dialogs/add-favorite-location-dialog.ts index 2ee065c..0268eeb 100644 --- a/Node/core/src/dialogs/add-favorite-location-dialog.ts +++ b/Node/core/src/dialogs/add-favorite-location-dialog.ts @@ -3,10 +3,8 @@ import * as common from '../common'; import { Strings } from '../consts'; import { FavoriteLocation } from '../favorite-location'; import { FavoritesManager } from '../services/favorites-manager'; -import * as confirmDialog from './confirm-dialog'; export function register(library: Library): void { - confirmDialog.register(library); library.dialog('add-favorite-location-dialog', createDialog()); library.dialog('name-favorite-location-dialog', createNameFavoriteLocationDialog()); } @@ -53,7 +51,7 @@ function createNameFavoriteLocationDialog() { location: session.dialogData.place, name : session.message.text }; - const favoritesManager = new FavoritesManager(session.userData); + const favoritesManager = new FavoritesManager(session.userData); favoritesManager.add(favoriteLocation); session.send(session.gettext(Strings.FavoriteAddedConfirmation, favoriteLocation.name)); session.endDialogWithResult({ response: {} }); diff --git a/Node/core/src/dialogs/choice-dialog.ts b/Node/core/src/dialogs/choose-location-dialog.ts similarity index 86% rename from Node/core/src/dialogs/choice-dialog.ts rename to Node/core/src/dialogs/choose-location-dialog.ts index 4315cc0..7608251 100644 --- a/Node/core/src/dialogs/choice-dialog.ts +++ b/Node/core/src/dialogs/choose-location-dialog.ts @@ -4,7 +4,7 @@ import { Strings } from '../consts'; import { Place } from '../place'; export function register(library: Library): void { - library.dialog('choice-dialog', createDialog()); + library.dialog('choose-location-dialog', createDialog()); } function createDialog() { @@ -23,8 +23,7 @@ function createDialog() { if (match) { var currentNumber = Number(match[0]); if (currentNumber > 0 && currentNumber <= session.dialogData.locations.length) { - var place = common.processLocation(session.dialogData.locations[currentNumber - 1], true); - session.endDialogWithResult({ response: { place: place } }); + session.endDialogWithResult({ response: { place: session.dialogData.locations[currentNumber - 1] } }); return; } } diff --git a/Node/core/src/dialogs/single-location-confirm-dialog.ts b/Node/core/src/dialogs/confirm-single-location-dialog.ts similarity index 77% rename from Node/core/src/dialogs/single-location-confirm-dialog.ts rename to Node/core/src/dialogs/confirm-single-location-dialog.ts index e1e1a8a..1ea8333 100644 --- a/Node/core/src/dialogs/single-location-confirm-dialog.ts +++ b/Node/core/src/dialogs/confirm-single-location-dialog.ts @@ -1,10 +1,8 @@ import { IDialogResult, Library, Session } from 'botbuilder'; -import * as common from '../common'; import { Strings } from '../consts'; -import * as confirmDialog from './confirm-dialog'; export function register(library: Library): void { - library.dialog('single-location-confirm-dialog', createDialog()); + library.dialog('confirm-single-location-dialog', createDialog()); } function createDialog() { @@ -16,8 +14,7 @@ function createDialog() { (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { if (results.response && results.response.confirmed) { // User did confirm the single location offered - const place = common.processLocation(session.dialogData.locations[0], true); - session.endDialogWithResult({ response: { place: place } }); + session.endDialogWithResult({ response: { place: session.dialogData.locations[0] } }); } else { // User said no diff --git a/Node/core/src/dialogs/delete-favorite-location-dialog.ts b/Node/core/src/dialogs/delete-favorite-location-dialog.ts new file mode 100644 index 0000000..4adcfba --- /dev/null +++ b/Node/core/src/dialogs/delete-favorite-location-dialog.ts @@ -0,0 +1,35 @@ +import { IDialogResult, Library, Session } from 'botbuilder'; +import { Strings } from '../consts'; +import { FavoritesManager } from '../services/favorites-manager'; + +export function register(library: Library, apiKey: string): void { + library.dialog('delete-favorite-location-dialog', createDialog()); +} + +function createDialog() { + return [ + // Ask the user to confirm deleting the favorite location + (session: Session, args: any) => { + session.dialogData.args = args; + session.dialogData.toBeDeleted = args.toBeDeleted; + const deleteFavoriteConfirmationAsk = session.gettext(Strings.DeleteFavoriteConfirmationAsk, args.toBeDeleted.name) + session.beginDialog('confirm-dialog', { confirmationPrompt: deleteFavoriteConfirmationAsk }); + }, + // Check whether the user confirmed + (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { + if (results.response && results.response.confirmed) { + const favoritesManager = new FavoritesManager(session.userData); + favoritesManager.delete(session.dialogData.toBeDeleted); + session.send(session.gettext(Strings.FavoriteDeletedConfirmation, session.dialogData.toBeDeleted.name)); + session.replaceDialog('retrieve-favorite-location-dialog', session.dialogData.args); + } + else if (results.response && results.response.confirmed === false) { + session.send(session.gettext(Strings.DeleteFavoriteAbortion)); + session.replaceDialog('retrieve-favorite-location-dialog', session.dialogData.args); + } + else { + next(results); + } + } + ] +} \ No newline at end of file diff --git a/Node/core/src/dialogs/edit-fravorite-location-dialog.ts b/Node/core/src/dialogs/edit-fravorite-location-dialog.ts new file mode 100644 index 0000000..e42aeaa --- /dev/null +++ b/Node/core/src/dialogs/edit-fravorite-location-dialog.ts @@ -0,0 +1,34 @@ +import { IDialogResult, Library, Session } from 'botbuilder'; +import { Strings } from '../consts'; +import { FavoriteLocation } from '../favorite-location'; +import { FavoritesManager } from '../services/favorites-manager'; + +export function register(library: Library, apiKey: string): void { + library.dialog('edit-favorite-location-dialog', createDialog()); +} + +function createDialog() { + return [ + (session: Session, args: any) => { + session.dialogData.args = args; + session.dialogData.toBeEditted = args.toBeEditted; + session.send(session.gettext(Strings.EditFavoritePrompt, args.toBeEditted.name)); + session.beginDialog('retrieve-location-dialog', session.dialogData.args); + }, + (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { + if (results.response && results.response.place) { + const favoritesManager = new FavoritesManager(session.userData); + const newfavoriteLocation: FavoriteLocation = { + location: results.response.place, + name: session.dialogData.toBeEditted.name + }; + favoritesManager.update(session.dialogData.toBeEditted, newfavoriteLocation); + session.send(session.gettext(Strings.FavoriteEdittedConfirmation, session.dialogData.toBeEditted.name)); + session.endDialogWithResult({ response: { place: results.response.place } }); + } + else { + next(results); + } + } + ] +} \ No newline at end of file diff --git a/Node/core/src/dialogs/required-fields-dialog.ts b/Node/core/src/dialogs/require-fields-dialog.ts similarity index 85% rename from Node/core/src/dialogs/required-fields-dialog.ts rename to Node/core/src/dialogs/require-fields-dialog.ts index 217f692..058a7d2 100644 --- a/Node/core/src/dialogs/required-fields-dialog.ts +++ b/Node/core/src/dialogs/require-fields-dialog.ts @@ -12,15 +12,15 @@ export enum LocationRequiredFields { } export function register(library: Library): void { - library.dialog('required-fields-dialog', createDialog()); + library.dialog('require-fields-dialog', createDialog()); } const fields: Array = [ - { name: "streetAddress", prompt: Strings.StreetAddress, flag: LocationRequiredFields.streetAddress }, + { name: "addressLine", prompt: Strings.StreetAddress, flag: LocationRequiredFields.streetAddress }, { name: "locality", prompt: Strings.Locality, flag: LocationRequiredFields.locality }, - { name: "region", prompt: Strings.Region, flag: LocationRequiredFields.region }, + { name: "adminDistrict", prompt: Strings.Region, flag: LocationRequiredFields.region }, { name: "postalCode", prompt: Strings.PostalCode, flag: LocationRequiredFields.postalCode }, - { name: "country", prompt: Strings.Country, flag: LocationRequiredFields.country }, + { name: "countryRegion", prompt: Strings.Country, flag: LocationRequiredFields.country }, ]; function createDialog() { @@ -68,12 +68,12 @@ function createDialog() { } function completeFieldIfMissing(session: Session, field: any) { - if ((field.flag & session.dialogData.requiredFieldsFlag) && !session.dialogData.place[field.name]) { + if ((field.flag & session.dialogData.requiredFieldsFlag) && !session.dialogData.place.address[field.name]) { var prefix: string = ""; var prompt: string = ""; if (typeof session.dialogData.lastInput === "undefined") { - var formattedAddress: string = common.getFormattedAddressFromPlace(session.dialogData.place, session.gettext(Strings.AddressSeparator)); + var formattedAddress: string = common.getFormattedAddressFromLocation(session.dialogData.place, session.gettext(Strings.AddressSeparator)); if (formattedAddress) { prefix = session.gettext(Strings.AskForPrefix, formattedAddress); prompt = session.gettext(Strings.AskForTemplate, session.gettext(field.prompt)); diff --git a/Node/core/src/dialogs/default-location-dialog.ts b/Node/core/src/dialogs/resolve-bing-location-dialog.ts similarity index 60% rename from Node/core/src/dialogs/default-location-dialog.ts rename to Node/core/src/dialogs/resolve-bing-location-dialog.ts index e0b2f9e..4bc8bd3 100644 --- a/Node/core/src/dialogs/default-location-dialog.ts +++ b/Node/core/src/dialogs/resolve-bing-location-dialog.ts @@ -1,16 +1,15 @@ import * as common from '../common'; import { Strings } from '../consts'; -import { Session, IDialogResult, Library, AttachmentLayout, HeroCard, CardImage, Message } from 'botbuilder'; -import { Place } from '../Place'; -import { MapCard } from '../map-card' +import { Session, IDialogResult, Library } from 'botbuilder'; import * as locationService from '../services/bing-geospatial-service'; -import * as singleLocationConfirmDialog from './single-location-confirm-dialog'; -import * as choiceDialog from './choice-dialog'; +import * as confirmSingleLocationDialog from './confirm-single-location-dialog'; +import * as chooseLocationDialog from './choose-location-dialog'; +import { LocationCardBuilder } from '../services/location-card-builder'; export function register(library: Library, apiKey: string): void { - singleLocationConfirmDialog.register(library); - choiceDialog.register(library); - library.dialog('default-location-dialog', createDialog()); + confirmSingleLocationDialog.register(library); + chooseLocationDialog.register(library); + library.dialog('resolve-bing-location-dialog', createDialog()); library.dialog('location-resolve-dialog', createLocationResolveDialog(apiKey)); } @@ -25,9 +24,9 @@ function createDialog() { var locations = results.response.locations; if (locations.length == 1) { - session.beginDialog('single-location-confirm-dialog', { locations: locations }); + session.beginDialog('confirm-single-location-dialog', { locations: locations }); } else { - session.beginDialog('choice-dialog', { locations: locations }); + session.beginDialog('choose-location-dialog', { locations: locations }); } } else { @@ -55,37 +54,11 @@ function createLocationResolveDialog(apiKey: string) { var locationCount = Math.min(MAX_CARD_COUNT, locations.length); locations = locations.slice(0, locationCount); - var reply = createLocationsCard(apiKey, session, locations); + var reply = new LocationCardBuilder(apiKey).createHeroCards(session, locations); session.send(reply); session.endDialogWithResult({ response: { locations: locations } }); }) .catch(error => session.error(error)); }); -} - -function createLocationsCard(apiKey: string, session: Session, locations: any) { - var cards = new Array(); - - for (var i = 0; i < locations.length; i++) { - cards.push(constructCard(apiKey, session, locations, i)); - } - - return new Message(session) - .attachmentLayout(AttachmentLayout.carousel) - .attachments(cards); -} - -function constructCard(apiKey: string, session: Session, locations: Array, index: number): HeroCard { - var location = locations[index]; - var card = new MapCard(apiKey, session); - - if (locations.length > 1) { - card.location(location, index + 1); - } - else { - card.location(location); - } - - return card; } \ No newline at end of file diff --git a/Node/core/src/dialogs/facebook-location-dialog.ts b/Node/core/src/dialogs/retrieve-facebook-location-dialog.ts similarity index 80% rename from Node/core/src/dialogs/facebook-location-dialog.ts rename to Node/core/src/dialogs/retrieve-facebook-location-dialog.ts index 1eab5b1..8a527a8 100644 --- a/Node/core/src/dialogs/facebook-location-dialog.ts +++ b/Node/core/src/dialogs/retrieve-facebook-location-dialog.ts @@ -1,11 +1,11 @@ import { Strings } from '../consts'; import * as common from '../common'; -import { Session, IDialogResult, Library, AttachmentLayout, HeroCard, CardImage, Message } from 'botbuilder'; -import { Place } from '../Place'; +import { Session, IDialogResult, Library, Message } from 'botbuilder'; import * as locationService from '../services/bing-geospatial-service'; +import { RawLocation } from '../rawLocation'; export function register(library: Library, apiKey: string): void { - library.dialog('facebook-location-dialog', createDialog(apiKey)); + library.dialog('retrive-facebook-location-dialog', createDialog(apiKey)); library.dialog('facebook-location-resolve-dialog', createLocationResolveDialog()); } @@ -17,11 +17,11 @@ function createDialog(apiKey: string) { }, (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { if (session.dialogData.args.reverseGeocode && results.response && results.response.place) { - locationService.getLocationByPoint(apiKey, results.response.place.geo.latitude, results.response.place.geo.longitude) + locationService.getLocationByPoint(apiKey, results.response.place.point.coordinates[0], results.response.place.point.coordinates[1]) .then(locations => { - var place: Place; + var place: RawLocation; if (locations.length) { - place = common.processLocation(locations[0], false); + place = locations[0]; } else { place = results.response.place; } @@ -47,7 +47,7 @@ function createLocationResolveDialog() { var entities = session.message.entities; for (var i = 0; i < entities.length; i++) { if (entities[i].type == "Place" && entities[i].geo && entities[i].geo.latitude && entities[i].geo.longitude) { - session.endDialogWithResult({ response: { place: common.buildPlaceFromGeo(entities[i].geo.latitude, entities[i].geo.longitude) } }); + session.endDialogWithResult({ response: { place: buildLocationFromGeo(entities[i].geo.latitude, entities[i].geo.longitude) } }); return; } } @@ -69,4 +69,9 @@ function sendLocationPrompt(session: Session, prompt: string): Session { }); return session.send(message); +} + +function buildLocationFromGeo(latitude: string, longitude: string) { + let coordinates = [ latitude, longitude]; + return { point : { coordinates : coordinates } }; } \ No newline at end of file diff --git a/Node/core/src/dialogs/retrieve-favorite-location-dialog.ts b/Node/core/src/dialogs/retrieve-favorite-location-dialog.ts new file mode 100644 index 0000000..5d2df07 --- /dev/null +++ b/Node/core/src/dialogs/retrieve-favorite-location-dialog.ts @@ -0,0 +1,99 @@ +import * as common from '../common'; +import { Strings } from '../consts'; +import { Session, Library } from 'botbuilder'; +import { LocationCardBuilder } from '../services/location-card-builder'; +import { FavoritesManager } from '../services/favorites-manager'; +import * as deleteFavoriteLocationDialog from './delete-favorite-location-dialog'; +import * as editFavoriteLocationDialog from './edit-fravorite-location-dialog'; +import { RawLocation } from '../rawLocation'; + +export function register(library: Library, apiKey: string): void { + library.dialog('retrieve-favorite-location-dialog', createDialog(apiKey)); + deleteFavoriteLocationDialog.register(library, apiKey); + editFavoriteLocationDialog.register(library, apiKey); +} + +function createDialog(apiKey: string) { + return common.createBaseDialog() + .onBegin(function (session, args) { + session.dialogData.args = args; + const favoritesManager = new FavoritesManager(session.userData); + const userFavorites = favoritesManager.getFavorites(); + + // If the user has no favorite locations, switch to a normal location retriever dialog + if (userFavorites.length == 0) { + session.send(session.gettext(Strings.NoFavoriteLocationsFound)); + session.replaceDialog('retrieve-location-dialog', session.dialogData.args); + return; + } + + session.dialogData.userFavorites = userFavorites; + + let locations: RawLocation[] = []; + let names: string[] = []; + for (let i = 0; i < userFavorites.length; i++) { + locations.push(userFavorites[i].location); + names.push(userFavorites[i].name); + } + + session.send(new LocationCardBuilder(apiKey).createHeroCards(session, locations, true, names)); + session.send(session.gettext(Strings.SelectFavoriteLocationPrompt)).sendBatch(); + }).onDefault((session) => { + const text: string = session.message.text; + if (text === session.gettext(Strings.OtherComand)) { + session.replaceDialog('retrieve-location-dialog', session.dialogData.args); + } + else { + const selection = tryParseCommandSelection(text, session.dialogData.userFavorites.length); + if ( selection.command === "select" ) { + // complete required fields + session.replaceDialog('require-fields-dialog', { + place: session.dialogData.userFavorites[selection.index - 1].location, + requiredFields: session.dialogData.args.requiredFields + }); + } + else if (selection.command === session.gettext(Strings.DeleteCommand) ) { + session.dialogData.args.toBeDeleted = session.dialogData.userFavorites[selection.index - 1]; + session.replaceDialog('delete-favorite-location-dialog', session.dialogData.args); + } + else if (selection.command === session.gettext(Strings.EditCommand)) { + session.dialogData.args.toBeEditted = session.dialogData.userFavorites[selection.index - 1]; + session.replaceDialog('edit-favorite-location-dialog', session.dialogData.args); + } + else { + session.send(session.gettext(Strings.InvalidFavoriteLocationSelection)).sendBatch(); + } + } + }); +} + +function tryParseNumberSelection(text: string): number { + const tokens = text.trim().split(' '); + if (tokens.length == 1) { + const numberExp = /[+-]?(?:\d+\.?\d*|\d*\.?\d+)/; + const match = numberExp.exec(text); + if (match) { + return Number(match[0]); + } + } + return -1; +} + +function tryParseCommandSelection(text: string, maxIndex: number): any { + const tokens = text.trim().split(' '); + + if (tokens.length == 1) { + const index = tryParseNumberSelection(text); + if (index > 0 && index <= maxIndex) { + return { index: index, command: "select" }; + } + } + else if (tokens.length == 2) { + const index = tryParseNumberSelection(tokens[1]); + if (index > 0 && index <= maxIndex) { + return { index: index, command: tokens[0] }; + } + } + + return { command: ""}; +} \ No newline at end of file diff --git a/Node/core/src/dialogs/retrieve-location-dialog.ts b/Node/core/src/dialogs/retrieve-location-dialog.ts new file mode 100644 index 0000000..6d37753 --- /dev/null +++ b/Node/core/src/dialogs/retrieve-location-dialog.ts @@ -0,0 +1,34 @@ +import { IDialogResult, Library, Session } from 'botbuilder'; +import * as resolveBingLocationDialog from './resolve-bing-location-dialog'; +import * as retrieveFacebookLocationDialog from './retrieve-facebook-location-dialog'; + +export function register(library: Library, apiKey: string): void { + library.dialog('retrieve-location-dialog', createDialog()); + resolveBingLocationDialog.register(library, apiKey); + retrieveFacebookLocationDialog.register(library, apiKey); +} + +function createDialog() { + return [ + (session: Session, args: any) => { + session.dialogData.args = args; + if (args.useNativeControl && session.message.address.channelId == 'facebook') { + session.beginDialog('retrieve-facebook-location-dialog', args); + } + else { + session.beginDialog('resolve-bing-location-dialog', args); + } + }, + // complete required fields, if applicable + (session: Session, results: IDialogResult, next: (results?: IDialogResult) => void) => { + if (results.response && results.response.place) { + session.beginDialog('require-fields-dialog', { + place: results.response.place, + requiredFields: session.dialogData.args.requiredFields + }) + } else { + next(results); + } + } + ] +} \ No newline at end of file diff --git a/Node/core/src/favorite-location.ts b/Node/core/src/favorite-location.ts index 6c4e23d..71a5a13 100644 --- a/Node/core/src/favorite-location.ts +++ b/Node/core/src/favorite-location.ts @@ -1,6 +1,6 @@ -import { Place } from './place'; +import { RawLocation } from './rawLocation'; export class FavoriteLocation { name: string; - location: Place; + location: RawLocation; } \ No newline at end of file diff --git a/Node/core/src/locale/en/botbuilder-location.json b/Node/core/src/locale/en/botbuilder-location.json index d5593c2..e38b310 100644 --- a/Node/core/src/locale/en/botbuilder-location.json +++ b/Node/core/src/locale/en/botbuilder-location.json @@ -8,17 +8,32 @@ "ConfirmationAsk": "OK, I will ship to %s. Is that correct? Enter 'yes' or 'no'.", "Country": "country", "DefaultPrompt": "Where should I ship your order?", + "DeleteCommand": "delete", + "DeleteFavoriteAbortion": "OK, deletion aborted.", + "DeleteFavoriteConfirmationAsk": "Are you sure you want to delete %s from your favorite locations?", + "DialogStartBranchAsk": "How would you like to pick a location?", + "EditCommand": "edit", + "EditFavoritePrompt": "OK, let's edit %s. Enter a new address.", "EnterNewFavoriteLocationName": "OK, please enter a friendly name for this address. You can use 'home', 'work' or any other name you prefer.", "FavoriteAddedConfirmation": "OK, I added %s to your favorite locations.", + "FavoriteDeletedConfirmation": "OK, I deleted %s from your favorite locations.", + "FavoriteEdittedConfirmation": "OK, I editted %s in your favorite locations with this new address.", + "FavoriteLocations": "Favorite Locations", "HelpMessage": "Say or type a valid address when asked, and I will try to find it using Bing. You can provide the full address information (street no. / name, city, region, postal/zip code, country) or a part of it. If you want to change the address, say or type 'reset'. Finally, say or type 'cancel' to exit without providing an address.", + "InvalidFavoriteLocationSelection": "Type or say a number to choose the address, enter 'other' to create a new favorite location, or enter 'cancel' to exit. You can also type or say 'edit' or 'delete' followed by a number to edit or delete the respective location.", "InvalidLocationResponse": "Didn't get that. Choose a location or cancel.", "InvalidLocationResponseFacebook": "Tap on Send Location to proceed; type or say cancel to exit.", + "InvalidStartBranchResponse": "Tap one of the options to proceed; type or say cancel to exit.", "LocationNotFound": "I could not find this address. Please try again.", "Locality": "city or locality", "MultipleResultsFound": "I found these results. Type or say a number to choose the address, or enter 'other' to select another address.", + "NoFavoriteLocationsFound": "You do not seem to have any favorite locations at the moment. Enter an address and you will be able to save it to your favorite locations.", + "OtherComand": "other", + "OtherLocation": "Other Location", "PostalCode": "zip or postal code", "Region": "state or region", "ResetPrompt": "OK, let's start over.", + "SelectFavoriteLocationPrompt": "Here are your favorite locations. Type or say a number to use the respective location, or 'other' to use a different location. You can also type or say 'edit' or 'delete' followed by a number to edit or delete the respective location.", "SingleResultFound": "I found this result. Is this the correct address?", "StreetAddress": "street address", "TitleSuffix": " Type or say an address", diff --git a/Node/core/src/map-card.ts b/Node/core/src/map-card.ts index 58445be..9030e42 100644 --- a/Node/core/src/map-card.ts +++ b/Node/core/src/map-card.ts @@ -1,5 +1,6 @@ import { Session, HeroCard, CardImage } from 'botbuilder'; import { Place, Geo } from './place'; +import { RawLocation } from './rawLocation' import * as locationService from './services/bing-geospatial-service'; export class MapCard extends HeroCard { @@ -8,14 +9,18 @@ export class MapCard extends HeroCard { super(session); } - public location(location: any, index?: number): this { - var indexText = ""; + public location(location: RawLocation, index?: number, locationName?: string): this { + var prefixText = ""; if (index !== undefined) { - indexText = index + ". "; + prefixText = index + ". "; } - this.subtitle(indexText + location.address.formattedAddress) + if (locationName !== undefined) { + prefixText += locationName + ": "; + } + this.subtitle(prefixText + location.address.formattedAddress); + if (location.point) { var locationUrl: string; try { diff --git a/Node/core/src/rawLocation.ts b/Node/core/src/rawLocation.ts new file mode 100644 index 0000000..60e85f2 --- /dev/null +++ b/Node/core/src/rawLocation.ts @@ -0,0 +1,23 @@ +export class RawLocation { + address: Address; + bbox: Array; + confidence: string; + entityType: string; + name: string; + point: Point; +} + +class Address { + addressLine: string; + adminDistrict: string; + adminDistrict2: string; + countryRegion: string; + formattedAddress: string; + locality: string; + postalCode: string; +} + +class Point { + coordinates: Array; + calculationMethod: string; +} \ No newline at end of file diff --git a/Node/core/src/services/bing-geospatial-service.ts b/Node/core/src/services/bing-geospatial-service.ts index 6fcf338..8164927 100644 --- a/Node/core/src/services/bing-geospatial-service.ts +++ b/Node/core/src/services/bing-geospatial-service.ts @@ -1,5 +1,6 @@ import * as rp from 'request-promise'; import { sprintf } from 'sprintf-js'; +import { RawLocation } from '../rawLocation' const formAugmentation = "&form=BTCTRL" const findLocationByQueryUrl = "https://dev.virtualearth.net/REST/v1/Locations?" + formAugmentation; @@ -7,18 +8,18 @@ const findLocationByPointUrl = "https://dev.virtualearth.net/REST/v1/Locations/% const findImageByPointUrl = "https://dev.virtualearth.net/REST/V1/Imagery/Map/Road/%1$s,%2$s/15?mapSize=500,280&pp=%1$s,%2$s;1;%3$s&dpi=1&logo=always" + formAugmentation; const findImageByBBoxUrl = "https://dev.virtualearth.net/REST/V1/Imagery/Map/Road?mapArea=%1$s,%2$s,%3$s,%4$s&mapSize=500,280&pp=%5$s,%6$s;1;%7$s&dpi=1&logo=always" + formAugmentation; -export function getLocationByQuery(apiKey: string, address: string): Promise> { +export function getLocationByQuery(apiKey: string, address: string): Promise> { var url = addKeyToUrl(findLocationByQueryUrl, apiKey) + "&q=" + encodeURIComponent(address); return getLocation(url); } -export function getLocationByPoint(apiKey: string, latitude: string, longitude: string): Promise> { +export function getLocationByPoint(apiKey: string, latitude: string, longitude: string): Promise> { var url: string = sprintf(findLocationByPointUrl, latitude, longitude); url = addKeyToUrl(url, apiKey) + "&q="; return getLocation(url); } -export function GetLocationMapImageUrl(apiKey: string, location: any, index?: number) { +export function GetLocationMapImageUrl(apiKey: string, location: RawLocation, index?: number) { if (location && location.point && location.point.coordinates && location.point.coordinates.length == 2) { var point = location.point; @@ -40,7 +41,7 @@ export function GetLocationMapImageUrl(apiKey: string, location: any, index?: nu throw "Invalid Location Format: " + location; } -function getLocation(url: string): Promise> { +function getLocation(url: string): Promise> { const requestData = { url: url, json: true diff --git a/Node/core/src/services/favorites-manager.ts b/Node/core/src/services/favorites-manager.ts index fc14600..d00b5aa 100644 --- a/Node/core/src/services/favorites-manager.ts +++ b/Node/core/src/services/favorites-manager.ts @@ -1,23 +1,23 @@ import { FavoriteLocation } from '../favorite-location'; -import { Place } from '../place'; +import { RawLocation } from '../rawLocation'; export class FavoritesManager { - readonly MAX_FAVORITE_COUNT = 5; - readonly FAVORITES_KEY = 'favorites'; + readonly maxFavoriteCount = 5; + readonly favoritesKey = 'favorites'; constructor (private userData : any) { } public maxCapacityReached(): boolean { - return this.getFavorites().length >= this.MAX_FAVORITE_COUNT; + return this.getFavorites().length >= this.maxFavoriteCount; } - public isFavorite(location: Place) : boolean { + public isFavorite(location: RawLocation) : boolean { let favorites = this.getFavorites(); for (let i = 0; i < favorites.length; i++) { - if (favorites[i].location.formattedAddress === location.formattedAddress) { + if (this.areEqual(favorites[i].location, location)) { return true; } } @@ -28,16 +28,45 @@ export class FavoritesManager { public add(favoriteLocation: FavoriteLocation): void { let favorites = this.getFavorites(); - if (favorites.length >= this.MAX_FAVORITE_COUNT) { + if (favorites.length >= this.maxFavoriteCount) { throw ('The max allowed number of favorite locations has already been reached.'); } favorites.push(favoriteLocation); - this.userData[this.FAVORITES_KEY] = favorites; + this.userData[this.favoritesKey] = favorites; + } + + public delete(favoriteLocation: FavoriteLocation): void { + let favorites = this.getFavorites(); + let newFavorites = []; + + for (let i = 0; i < favorites.length; i++) { + if ( !this.areEqual(favorites[i].location, favoriteLocation.location)) { + newFavorites.push(favorites[i]); + } + } + + this.userData[this.favoritesKey] = newFavorites; + } + + public update(currentValue: FavoriteLocation, newValue: FavoriteLocation): void { + let favorites = this.getFavorites(); + let newFavorites = []; + + for (let i = 0; i < favorites.length; i++) { + if ( this.areEqual(favorites[i].location, currentValue.location)) { + newFavorites.push(newValue); + } + else { + newFavorites.push(favorites[i]); + } + } + + this.userData[this.favoritesKey] = newFavorites; } public getFavorites(): FavoriteLocation[] { - let storedFavorites = this.userData[this.FAVORITES_KEY]; + let storedFavorites = this.userData[this.favoritesKey]; if (storedFavorites) { return storedFavorites; @@ -47,4 +76,12 @@ export class FavoritesManager { return []; } } + + private areEqual(location0: RawLocation, location1: RawLocation): boolean { + // Other attributes of a location such as its Confidence, BoundaryBox, etc + // should not be considered as distinguishing factors. + // On the other hand, attributes of a location that are shown to the users + // are what distinguishes one location from another. + return location0.address.formattedAddress === location1.address.formattedAddress; + } } \ No newline at end of file diff --git a/Node/core/src/services/location-card-builder.ts b/Node/core/src/services/location-card-builder.ts new file mode 100644 index 0000000..f35d629 --- /dev/null +++ b/Node/core/src/services/location-card-builder.ts @@ -0,0 +1,42 @@ +import { AttachmentLayout, HeroCard, Message, Session } from 'botbuilder'; +import { MapCard } from '../map-card' +import {RawLocation} from '../rawLocation' + +export class LocationCardBuilder { + + constructor (private apiKey : string) { + } + + public createHeroCards(session: Session, locations: Array, alwaysShowNumericPrefix?: boolean, locationNames?: Array): Message { + let cards = new Array(); + + for (let i = 0; i < locations.length; i++) { + cards.push(this.constructCard(session, locations, i, alwaysShowNumericPrefix, locationNames)); + } + + return new Message(session) + .attachmentLayout(AttachmentLayout.carousel) + .attachments(cards); + } + + private constructCard(session: Session, locations: Array, index: number, alwaysShowNumericPrefix?: boolean, locationNames?: Array): HeroCard { + const location = locations[index]; + let card = new MapCard(this.apiKey, session); + + if (alwaysShowNumericPrefix || locations.length > 1) { + if (locationNames) + { + card.location(location, index + 1, locationNames[index]); + } + else + { + card.location(location, index + 1); + } + } + else { + card.location(location); + } + + return card; + } +} \ No newline at end of file diff --git a/Node/sample/app.js b/Node/sample/app.js index 91a952b..94ccd03 100644 --- a/Node/sample/app.js +++ b/Node/sample/app.js @@ -40,7 +40,13 @@ bot.dialog("/", [ function (session, results) { if (results.response) { var place = results.response; - session.send("Thanks, I will ship to " + locationDialog.getFormattedAddressFromPlace(place, ", ")); + var formattedAddress = + session.send("Thanks, I will ship to " + getFormattedAddressFromPlace(place, ", ")); } } -]); \ No newline at end of file +]); + +function getFormattedAddressFromPlace(place, separator) { + var addressParts = [place.streetAddress, place.locality, place.region, place.postalCode, place.country]; + return addressParts.filter(i => i).join(separator); +} \ No newline at end of file