functional application without amazon update

This commit is contained in:
GotPPay
2017-12-02 22:48:45 +01:00
parent 0e193fa5a8
commit d5120a1ba2
12 changed files with 375 additions and 98 deletions

View File

@@ -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);

View File

@@ -0,0 +1,7 @@
export const sendUpdateToAmazon = (id)=>{
//id - skill ID in database
}

View File

@@ -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"
}
}

View File

@@ -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 (
<div className="App">
<div className="App-header">
<h1> Tell All </h1>
if(this.state.launchRequest){
return (
<div className="App">
<div className="App-header">
<h1> Tell All </h1>
</div>
<IntentList allIntents={this.state.allIntents}
onLaunchRequestClick={this.handleLaunchRequestClick}
onIntentClick={this.handleIntentClick}
onAddIntentClick={this.handleAddIntentClick}
selectedIndex={this.state.selectedIndex}>
</IntentList>
<LaunchRequest invocationName={this.state.invocationName}
invocationAnswer={this.state.invocationAnswer}
onSaveClick={this.handleSaveLaunchRequestClick}> </LaunchRequest>
</div>
<IntentList allIntents={this.state.allIntents}
onIntentClick={this.handleIntentClick}
onAddIntentClick={this.handleAddIntentClick}
selectedIndex={this.state.selectedIndex}>
</IntentList>
<IntentDetails selectedIntent={this.state.selectedIntent}
onDeleteIntentClick={this.handleDeleteIntentClick}
onSaveIntentClick={this.handleSaveIntentClick}>
</IntentDetails>
</div>
);
}
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 (
<div className="App">
<div className="App-header">
<h1> Tell All </h1>
</div>
<IntentList allIntents={this.state.allIntents}
onLaunchRequestClick={this.handleLaunchRequestClick}
onIntentClick={this.handleIntentClick}
onAddIntentClick={this.handleAddIntentClick}
selectedIndex={this.state.selectedIndex}>
</IntentList>
<IntentDetails selectedIntent={this.state.selectedIntent}
onDeleteIntentClick={this.handleDeleteIntentClick}
onSaveIntentClick={this.handleSaveIntentClick}>
</IntentDetails>
</div>
);
}
}
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:''}});
}
}

View File

@@ -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 (
<div className="IntentDetails">
<div className="QuestionBox">
<TextField
id="intent name"
lineDirection="center"
placeholder="Intent name"
label="Intent name"
className="md-cell md-cell--bottom"
style={{width:'60%'}}
onChange={this.handleIntentNameEdit}
maxLength={INTENT_NAME_MAX_LENGTH}
value={this.state.intent.intentName} />
</div>
<h5 style={{marginTop:'20px', marginLeft: '30px', float: 'left'}}>Questions</h5>
{
this.state.intent.questions.map((question, index)=>{
return (
<div key={index} className="QuestionBox">
<TextField
id="floating-center-title"
id="intent question"
lineDirection="center"
placeholder="Question"
className="md-cell md-cell--bottom"
style={{width:'60%'}}
maxLength={QUESTION_MAX_LENGTH}
rightIcon={<SVGIcon onClick={()=>{this.deleteQuestion(question)}}> <path fill="#000000" d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"/> </SVGIcon>}
onChange={(e)=>{this.handleQuestionEdit(e,index)}}
value={question}/>
</div>
);
@@ -40,12 +58,14 @@ class IntentDetails extends Component {
{
<div className="QuestionBox">
<TextField
id="floating-center-title"
id="intent answer"
lineDirection="center"
label="Answer"
placeholder="Answer"
style={{width:'60%'}}
maxLength={ANSWER_MAX_LENGTH}
className="md-cell md-cell--bottom"
onChange={this.handleAnswerEdit}
value={this.state.intent.answer}/>
</div>
}
@@ -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;

View File

@@ -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() {
/*
<div className={this.props.selectedIndex===this.state.index ? 'IntentItem-selected' : 'IntentItem'} onClick={()=>{this.state.onClick(this.state.intent,this.state.index)}}>
<Button className={"IntentItem"} flat swapTheming>OK</Button>
<p> {this.state.index+1}. {this.state.intent.questions[0]} </p>
</div>
*/
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 (
<div>
<Button className={this.props.selectedIndex===this.state.index ? 'IntentItem-selected' : 'IntentItem'}
onClick={()=>{this.state.onClick(this.state.intent,this.state.index)}}
flat>
{this.state.index+1}. {this.state.intent.questions[0]}
flat
tooltipDelay={INTENT_TITLE_TOOLTIP_DELAY}
tooltipLabel={this.state.intent.questions[0].length>INTENT_TITLE_MAX_LENGTH ? this.state.intent.questions[0] : ''}>
{this.state.index+1}. {buttonTitle}
</Button>
<br></br>
</div>

View File

@@ -17,6 +17,8 @@ class IntentList extends Component {
render() {
return (
<div className="IntentList">
<Button className={this.props.selectedIndex===-2 ? "LaunchRequest-selected" : "LaunchRequest"} flat primary
onClick={this.props.onLaunchRequestClick}>Launch request</Button>
<div className="IntentList-title">
<h3>Intents</h3>
</div>
@@ -30,7 +32,7 @@ class IntentList extends Component {
})
}
<br></br>
<Button className={"AddIntent"} flat primary swapTheming onClick={this.props.handleAddIntentClick}>Add intent</Button>
<Button className={"AddIntent"} flat primary swapTheming onClick={this.props.onAddIntentClick}>Add intent</Button>
</div>
);
}

View File

@@ -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 (
<div className="LaunchRequestBox">
<h5 style={{textAlign:'left', marginTop: '30px', marginLeft: '20px'}}> Invocation name customers use to activate the skill. For example "Open Saburly" or "Talk to Saburly" </h5>
<TextField
id="invocation name"
lineDirection="center"
placeholder="Saburly"
label="Invocation name"
className="md-cell md-cell--bottom"
style={{width:'60%', marginLeft: '20px'}}
maxLength={INVOCATION_NAME_MAX_LENGTH}
onChange={this.handleNameEdit}
value={this.state.invocationName}/>
<br></br>
<h5 style={{textAlign:'left', marginLeft: '20px', marginTop: '30px'}}>Answer customers get from Alexa when they activate the skill.</h5>
<TextField
id="invocation answer"
lineDirection="center"
placeholder="We are Saburly, ask us something about us"
label="Answer"
className="md-cell md-cell--bottom"
style={{width:'60%', marginLeft: '20px'}}
maxLength={INVOCATION_ANSWER_MAX_LENGTH}
onChange={this.handleAnswerEdit}
value={this.state.invocationAnswer}/>
<br></br>
<br></br>
<Button style={{float:'right', marginRight: '20px'}} flat primary swapTheming onClick={()=>{this.props.onSaveClick(this.state.invocationName, this.state.invocationAnswer)}}>Save</Button>
</div>
);
}
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;

View File

@@ -1 +1,11 @@
export const BASE_URL = 'localhost:5000';
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;

View File

@@ -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; }

View File

@@ -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;
}

View File

@@ -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),
});
}