diff --git a/backend/express.js b/backend/express.js
index 252e564..e40491a 100644
--- a/backend/express.js
+++ b/backend/express.js
@@ -1,3 +1,5 @@
+import { sendUpdateToAmazon } from './helpers/amazon';
+
var express = require('express');
var alexa = require('alexa-app');
@@ -12,13 +14,22 @@ var ObjectID = require ('mongodb').ObjectID;
const router = express.Router ();
-router.get ('/intents', async (req, res, next) => {
+router.get ('/getSkill/:id', async (req, res, next) => {
try {
const id = req.params.id;
- db.collection ('intent_list').find({}).toArray((err,result)=>{
- if (err) throw err;
- res.json(result);
+ if (id.length !== 24){
+ res.json([]);
+ throw("error");
+ }
+
+ db.collection ('skill_list').find({_id: ObjectID(id)}).toArray((err,result)=>{
+ if (err){
+ throw err;
+ res.json([]);
+ }else{
+ res.json(result);
+ }
});
} catch (e) {
@@ -27,10 +38,11 @@ router.get ('/intents', async (req, res, next) => {
}
});
-router.get ('/deleteIntent/:id', async (req, res, next) => {
+
+router.get ('/deleteSkill/:skillID', async (req, res, next) => {
try {
let id = req.params.id;
- let result = db.collection('intent_list').remove({_id: ObjectID(id)},(err,result)=>{
+ let result = db.collection('skill_list').remove({_id: ObjectID(id)},(err,result)=>{
if (err) throw err;
res.json(result);
});
@@ -40,15 +52,20 @@ router.get ('/deleteIntent/:id', async (req, res, next) => {
}
});
-router.post ('/updateIntent/:id', async (req, res, next) => {
+router.post ('/updateSkill/:id', async (req, res, next) => {
try {
let id = req.params.id;
- let intent = req.body;
- delete intent._id;
- let result = db.collection('intent_list').update({_id: ObjectID(id)}, intent,{upsert:true}, (err, result)=>{
- if (err) throw(err);
- res.json(result);
- });
+ let skill = req.body;
+ delete skill._id;
+ if (id !== '-1'){
+ let result = db.collection('skill_list').update({_id: ObjectID(id)}, skill,{upsert:true}, (err, result)=>{
+ if (err) throw(err);
+ res.json(result);
+ });
+ }else{
+ //no new skills for now
+ }
+
} catch (e) {
console.log ('error:', e);
next (e);
diff --git a/backend/helpers/amazon.js b/backend/helpers/amazon.js
new file mode 100644
index 0000000..7789579
--- /dev/null
+++ b/backend/helpers/amazon.js
@@ -0,0 +1,7 @@
+
+
+
+export const sendUpdateToAmazon = (id)=>{
+ //id - skill ID in database
+
+}
\ No newline at end of file
diff --git a/web/package.json b/web/package.json
index fe7f721..0c49f3b 100644
--- a/web/package.json
+++ b/web/package.json
@@ -6,7 +6,8 @@
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-md": "^1.2.8",
- "react-scripts": "1.0.17"
+ "react-scripts": "1.0.17",
+ "webfontloader": "^1.6.28"
},
"scripts": {
"build-css": "node-sass-chokidar --include-path ./node_modules src/ -o src/",
@@ -20,6 +21,7 @@
},
"devDependencies": {
"node-sass": "^4.7.2",
- "nodemon": "^1.12.1"
+ "nodemon": "^1.12.1",
+ "npm-run-all": "^4.1.2"
}
}
diff --git a/web/src/App.js b/web/src/App.js
index 34e7b2b..7761b92 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -2,86 +2,152 @@ import React, { Component } from 'react';
import './css/App.css';
import IntentList from './components/IntentList';
import IntentDetails from './components/IntentDetails';
+import LaunchRequest from './components/LaunchRequest';
-import {getAllIntents, deleteIntent, updateIntent} from './lib/api'
+import {getSkill, updateSkill} from './lib/api'
class App extends Component {
constructor(props){
super(props);
- this.state={allIntents:[], selectedIntent: {questions:[''],answer:''}, selectedIndex:-1};
+ this.state={_id:'5a22ffd36ce046c749739453',
+ skillID:'',
+ invocationName:'Saburly',
+ invocationAnswer:'We are saburly',
+ allIntents:[],
+ selectedIntent: {intentName:'',questions:[''],answer:''},
+ selectedIndex:-1,
+ launchRequest:false};
- getAllIntents().then(l=> l.text()).then(result=>{
- this.setState({allIntents: JSON.parse(result), selectedIntent: this.state.selectedIntent})
+ getSkill(this.state._id).then(l=> l.text()).then(result=>{
+ let jResult = JSON.parse(result)[0];
+ if (jResult===undefined) return;
+ this.setState({ skillID:jResult.skillID, invocationName: jResult.invocationName,
+ invocationAnswer: jResult.invocationAnswer,
+ allIntents: jResult.intents})
})
this.handleIntentClick = this.handleIntentClick.bind(this);
+ this.handleLaunchRequestClick = this.handleLaunchRequestClick.bind(this);
this.handleDeleteIntentClick = this.handleDeleteIntentClick.bind(this);
this.handleSaveIntentClick = this.handleSaveIntentClick.bind(this);
this.handleAddIntentClick = this.handleAddIntentClick.bind(this);
+ this.handleSaveLaunchRequestClick = this.handleSaveLaunchRequestClick.bind(this);
+ this.createSkill = this.createSkill.bind(this);
}
render() {
- return (
-
-
-
Tell All
+ if(this.state.launchRequest){
+ return (
+
-
-
-
-
-
- );
- }
-
- handleIntentClick(selectedIntent, index){
- this.setState({selectedIntent:selectedIntent, selectedIndex: index});
- }
-
- handleDeleteIntentClick(selectedIntent){
- if (selectedIntent._id){
- deleteIntent(selectedIntent._id).then(l=>l.text()).then(result=>{
- if (JSON.parse(result).n===1){
- let id = this.state.allIntents.indexOf(selectedIntent);
- if (id!==-1){
- this.state.allIntents.splice(id,1);
- this.setState({allIntents: this.state.allIntents, selectedIntent: {questions:[''],answer:''}});
- }else{
- alert("Error");
- }
- }else{
- alert("Error");
- }
- });
+ );
+ }else{
+ return (
+
+ );
}
}
- handleSaveIntentClick(selectedIntent){
- updateIntent(selectedIntent._id, selectedIntent).then(l=>l.text()).then(result=>{
- if (JSON.parse(result).nModified===1){
- this.state.allIntents.map((intent,index)=>{
- if (intent._id === selectedIntent._id){
- let newAllIntents = this.state.allIntents;
- newAllIntents[index] = selectedIntent;
- this.setState({allIntents: newAllIntents, selectedIntent: selectedIntent});
- return 1;
- }
+ createSkill(intents, name, answer){
+ return {
+ _id: this.state._id,
+ skillID: this.state.skillID,
+ intents: intents,
+ invocationName: (name===undefined) ? this.state.invocationName : name,
+ invocationAnswer: (answer===undefined)? this.state.invocationAnswer: answer
+ };
+ }
+
+ handleIntentClick(selectedIntent, index){
+ this.setState({selectedIntent:selectedIntent, selectedIndex: index, launchRequest:false});
+ }
+
+ handleLaunchRequestClick(){
+ this.setState({selectedIndex: -2, launchRequest:true});
+ }
+
+ handleSaveLaunchRequestClick(name, answer){
+ this.setState({invocationName: name, invocationAnswer: answer});
+ try{
+ updateSkill(this.createSkill(this.state.allIntents,name,answer)).then(l=>l.text()).then(result=>{
+ if (JSON.parse(result).nModified!==1)
+ alert("Error");
+ });
+ }catch(e){
+ alert("exception");
+ }
+ }
+
+ handleDeleteIntentClick(selectedIntent){
+ let id = this.state.allIntents.indexOf(selectedIntent);
+ if (id!==-1){
+ try{
+ //I don't like this, state in database is different than component state, for some time
+ //TODO : move database operation in componentWillUpdate or componentDidUpdate
+ let newAllIntents = this.state.allIntents;
+ newAllIntents.splice(id,1);
+ this.setState({allIntents: newAllIntents, selectedIntent: {intentName:'', questions:[''],answer:''}});
+ updateSkill(this.createSkill(newAllIntents)).then(l=>l.text()).then(result=>{
+ if (JSON.parse(result).nModified!==1)
+ alert("Error");
});
- }else{
- alert("error - update went wrong");
+ }catch(e){
+ alert("exception");
}
- });
+ }
+ }
+
+
+ handleSaveIntentClick(selectedIntent){
+ let newAllIntents = this.state.allIntents;
+ if (this.state.selectedIndex === -1){
+ //new intent
+ newAllIntents.push(selectedIntent);
+ this.setState({allIntents: newAllIntents, selectedIntent: selectedIntent, selectedIndex: newAllIntents.length-1});
+ }else{
+ newAllIntents[this.state.selectedIndex] = selectedIntent;
+ this.setState({allIntents: newAllIntents, selectedIntent: selectedIntent});
+ }
+ try{
+ updateSkill(this.createSkill(newAllIntents)).then(l=>l.text()).then(result=>{
+ if (JSON.parse(result).nModified!==1)
+ alert("Error");
+ });
+ }catch(e){
+ alert("exception");
+ }
}
handleAddIntentClick(){
- this.setState({allIntents: this.state.allIntents, selectedIntent: {questions:[''], answer:''}});
+ this.setState({allIntents: this.state.allIntents, selectedIndex: -1,launchRequest:false,selectedIntent: {intentName:'',questions:[''], answer:''}});
}
}
diff --git a/web/src/components/IntentDetails.js b/web/src/components/IntentDetails.js
index 52af133..9eb0b0d 100644
--- a/web/src/components/IntentDetails.js
+++ b/web/src/components/IntentDetails.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import {Button, SVGIcon, TextField} from 'react-md';
import '../css/Intent.css'
+import {QUESTION_MAX_LENGTH, ANSWER_MAX_LENGTH, INTENT_NAME_MAX_LENGTH} from '../config';
class IntentDetails extends Component {
constructor(props){
@@ -10,6 +11,9 @@ class IntentDetails extends Component {
this.addQuestion = this.addQuestion.bind(this);
this.deleteQuestion = this.deleteQuestion.bind(this);
+ this.handleQuestionEdit = this.handleQuestionEdit.bind(this);
+ this.handleAnswerEdit = this.handleAnswerEdit.bind(this);
+ this.handleIntentNameEdit = this.handleIntentNameEdit.bind(this);
}
componentWillReceiveProps(props){
@@ -19,18 +23,32 @@ class IntentDetails extends Component {
render() {
return (
+
+
+
Questions
{
this.state.intent.questions.map((question, index)=>{
return (
{this.deleteQuestion(question)}}> }
+ onChange={(e)=>{this.handleQuestionEdit(e,index)}}
value={question}/>
);
@@ -40,12 +58,14 @@ class IntentDetails extends Component {
{
}
@@ -60,7 +80,7 @@ class IntentDetails extends Component {
addQuestion(){
let newIntent = this.state.intent;
- newIntent.questions.push('New question');
+ newIntent.questions.push('');
this.setState({intent: newIntent});
}
@@ -72,6 +92,28 @@ class IntentDetails extends Component {
this.setState({intent: newIntent});
}
+
+ handleQuestionEdit(e,index){
+ if (e.length === QUESTION_MAX_LENGTH) return;
+ let newIntent = this.state.intent;
+ newIntent.questions[index] = e;
+ this.setState({intent: newIntent});
+ }
+
+ handleAnswerEdit(e){
+ if (e.length === ANSWER_MAX_LENGTH) return;
+ let newIntent = this.state.intent;
+ newIntent.answer = e;
+ this.setState({intent: newIntent});
+ }
+
+ handleIntentNameEdit(e){
+ if (e.length === INTENT_NAME_MAX_LENGTH) return;
+ //e.replace(/\s/g,'');
+ let newIntent = this.state.intent;
+ newIntent.intentName = e;
+ this.setState({intent: newIntent});
+ }
}
export default IntentDetails;
diff --git a/web/src/components/IntentItem.js b/web/src/components/IntentItem.js
index 45f5185..a4fb43b 100644
--- a/web/src/components/IntentItem.js
+++ b/web/src/components/IntentItem.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import {Button} from 'react-md';
import '../css/Intent.css'
+import {INTENT_TITLE_MAX_LENGTH, INTENT_TITLE_TOOLTIP_DELAY} from '../config'
class IntentItem extends Component {
@@ -11,18 +12,18 @@ class IntentItem extends Component {
}
render() {
- /*
-
{this.state.onClick(this.state.intent,this.state.index)}}>
-
-
{this.state.index+1}. {this.state.intent.questions[0]}
-
- */
+ let buttonTitle = this.state.intent.intentName;
+ if (buttonTitle.length > INTENT_TITLE_MAX_LENGTH){
+ buttonTitle = this.state.intent.intentName.substr(0,INTENT_TITLE_MAX_LENGTH-1) + '. . .';
+ }
return (
diff --git a/web/src/components/IntentList.js b/web/src/components/IntentList.js
index 264c2f3..b97f990 100644
--- a/web/src/components/IntentList.js
+++ b/web/src/components/IntentList.js
@@ -17,6 +17,8 @@ class IntentList extends Component {
render() {
return (
+
Intents
@@ -30,7 +32,7 @@ class IntentList extends Component {
})
}
-
+
);
}
diff --git a/web/src/components/LaunchRequest.js b/web/src/components/LaunchRequest.js
new file mode 100644
index 0000000..28d5099
--- /dev/null
+++ b/web/src/components/LaunchRequest.js
@@ -0,0 +1,64 @@
+import React, { Component } from 'react';
+import {Button, TextField} from 'react-md';
+import '../css/Intent.css'
+import { INVOCATION_NAME_MAX_LENGTH, INVOCATION_ANSWER_MAX_LENGTH } from '../config';
+
+class LaunchRequest extends Component {
+ constructor(props){
+ super(props);
+
+ this.state = {invocationName: props.invocationName, invocationAnswer: props.invocationAnswer};
+
+ this.handleNameEdit = this.handleNameEdit.bind(this);
+ this.handleAnswerEdit = this.handleAnswerEdit.bind(this);
+ }
+
+ componentWillReceiveProps(props){
+ this.setState({invocationName: props.invocationName, invocationAnswer: props.invocationAnswer});
+ }
+
+ render() {
+ return (
+
+
Invocation name customers use to activate the skill. For example "Open Saburly" or "Talk to Saburly"
+
+
+ Answer customers get from Alexa when they activate the skill.
+
+
+
+
+
+ );
+ }
+
+ handleNameEdit(e){
+ if (e.length === INVOCATION_NAME_MAX_LENGTH) return;
+ this.setState({invocationName: e});
+ }
+
+ handleAnswerEdit(e){
+ if (e.length === INVOCATION_ANSWER_MAX_LENGTH) return;
+ this.setState({invocationAnswer: e});
+ }
+}
+
+export default LaunchRequest;
diff --git a/web/src/config.js b/web/src/config.js
index 0fefd14..1a4de76 100644
--- a/web/src/config.js
+++ b/web/src/config.js
@@ -1 +1,11 @@
-export const BASE_URL = 'localhost:5000';
\ No newline at end of file
+export const BASE_URL = 'localhost:5000';
+
+export const INTENT_NAME_MAX_LENGTH = 30;
+export const QUESTION_MAX_LENGTH = 150;
+export const ANSWER_MAX_LENGTH = 150;
+
+export const INTENT_TITLE_MAX_LENGTH = 20;
+export const INTENT_TITLE_TOOLTIP_DELAY = 700;
+
+export const INVOCATION_NAME_MAX_LENGTH = 15;
+export const INVOCATION_ANSWER_MAX_LENGTH = 100;
diff --git a/web/src/css/Intent.css b/web/src/css/Intent.css
index 490da92..62b35e2 100644
--- a/web/src/css/Intent.css
+++ b/web/src/css/Intent.css
@@ -4,13 +4,26 @@
min-height: 9999px;
height: 9999px;
float: left;
- background-color: #d5dae2; }
+ background-color: #eff0f0; }
.IntentList-title {
font-size: 1.5em;
height: 70px;
padding: 20px;
- text-align: left; }
+ text-align: left;
+ background-color: #eff0f0; }
+
+.LaunchRequest {
+ text-align: left;
+ width: 100%;
+ height: 50px;
+ background-color: #d8d8d8; }
+
+.LaunchRequest-selected {
+ text-align: left;
+ width: 100%;
+ height: 50px;
+ background-color: #f5f5f5; }
.AddIntent {
float: right;
@@ -22,7 +35,7 @@
height: 50px;
width: 100%;
text-align: left;
- background-color: #adb0b5; }
+ background-color: #d8d8d8; }
.IntentItem-selected {
margin-top: 2px;
@@ -30,7 +43,7 @@
height: 50px;
width: 100%;
text-align: left;
- background-color: #e8ecf2; }
+ background-color: #f5f5f5; }
/*IntentDetails CSS*/
.IntentDetails {
@@ -38,7 +51,21 @@
width: 70%;
min-height: 9999px;
height: 9999px;
- background-color: #e8ecf2; }
+ background-color: #f5f5f5; }
.QuestionBox {
margin: 25px; }
+
+/*LaunchRequest CSS*/
+.LaunchRequestBox {
+ float: left;
+ width: 70%;
+ min-height: 9999px;
+ height: 9999px;
+ background-color: #f5f5f5; }
+
+.ExplanationText {
+ float: left;
+ margin-top: 30px;
+ margin-left: 20px;
+ text-align: left; }
diff --git a/web/src/css/Intent.scss b/web/src/css/Intent.scss
index c954fe7..b35497e 100644
--- a/web/src/css/Intent.scss
+++ b/web/src/css/Intent.scss
@@ -5,7 +5,7 @@
min-height:9999px;
height:9999px;
float:left;
- background-color: #d5dae2;
+ background-color: #eff0f0;
}
.IntentList-title{
@@ -13,6 +13,21 @@
height: 70px;
padding: 20px;
text-align:left;
+ background-color: #eff0f0;
+}
+
+.LaunchRequest{
+ text-align: left;
+ width: 100%;
+ height: 50px;
+ background-color: #d8d8d8
+}
+
+.LaunchRequest-selected{
+ text-align: left;
+ width: 100%;
+ height: 50px;
+ background-color: #f5f5f5;
}
.AddIntent{
@@ -26,7 +41,7 @@
height: 50px;
width:100%;
text-align:left;
- background-color: #adb0b5;
+ background-color: #d8d8d8;
}
.IntentItem-selected{
@@ -35,7 +50,7 @@
height: 50px;
width:100%;
text-align:left;
- background-color:#e8ecf2;
+ background-color: #f5f5f5;
}
@@ -46,9 +61,26 @@
width: 70%;
min-height:9999px;
height:9999px;
- background-color:#e8ecf2;
+ background-color: #f5f5f5;
}
.QuestionBox{
margin:25px;
+}
+
+/*LaunchRequest CSS*/
+
+.LaunchRequestBox{
+ float: left;
+ width:70%;
+ min-height: 9999px;
+ height: 9999px;
+ background-color: #f5f5f5;
+}
+
+.ExplanationText{
+ float: left;
+ margin-top: 30px;
+ margin-left: 20px;
+ text-align: left;
}
\ No newline at end of file
diff --git a/web/src/lib/api.js b/web/src/lib/api.js
index 6891bef..06964bb 100644
--- a/web/src/lib/api.js
+++ b/web/src/lib/api.js
@@ -1,24 +1,31 @@
import fetch from 'isomorphic-fetch';
import {BASE_URL} from '../config';
-export const getAllIntents = ()=>{
- let url = `http://${BASE_URL}/intents`
+export const getAllIntents = (id)=>{
+ let url = `http://${BASE_URL}/intents/${id}`
return fetch(url, {method: 'GET'});
}
-export const deleteIntent = id=>{
- let url = `http://${BASE_URL}/deleteIntent/${id}`
+export const getSkill = (id)=>{
+ let url = `http://${BASE_URL}/getSkill/${id}`
return fetch(url, {method: 'GET'});
}
-export const updateIntent = (id,intent)=>{
- let url = `http://${BASE_URL}/updateIntent/${id}`
+export const deleteSkill = (id)=>{
+ let url = `http://${BASE_URL}/deleteSkill/${id}`
+ return fetch(url, {method: 'GET'});
+}
+
+export const updateSkill = (skill)=>{
+ console.log(skill);
+ let id = (skill._id) ? skill._id : -1;
+ let url = `http://${BASE_URL}/updateSkill/${id}`
return fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
- body: JSON.stringify(intent),
+ body: JSON.stringify(skill),
});
}
\ No newline at end of file