diff --git a/examples/actionbank/sleep/src/SleepAction.ts b/examples/actionbank/sleep/src/SleepAction.ts index 6a62e840..57653cd6 100644 --- a/examples/actionbank/sleep/src/SleepAction.ts +++ b/examples/actionbank/sleep/src/SleepAction.ts @@ -1,12 +1,20 @@ -import xs from 'xstream'; -import dropRepeats from 'xstream/extra/dropRepeats'; -import {Stream} from 'xstream'; -import {TimeSource} from '@cycle/time'; +import xs from "xstream"; +import dropRepeats from "xstream/extra/dropRepeats"; +import { Stream } from "xstream"; +import { TimeSource } from "@cycle/time"; import { - GoalID, Goal, Status, GoalStatus, Result, - ActionSources, ActionSinks, - initGoal, generateGoalStatus, isEqualGoalStatus, isEqualGoalID, -} from '@cycle-robot-drivers/action'; + GoalID, + Goal, + Status, + GoalStatus, + Result, + ActionSources, + ActionSinks, + initGoal, + generateGoalStatus, + isEqualGoalStatus, + isEqualGoalID +} from "@cycle-robot-drivers/action"; // import { // GoalID, Goal, Status, GoalStatus, Result, // ActionSources, ActionSinks, @@ -15,122 +23,132 @@ import { // initGoal, generateGoalStatus, isEqualGoalStatus, isEqualGoalID, // } from './utils'; - enum State { - RUN = 'RUN', - WAIT = 'WAIT', + RUN = "RUN", + WAIT = "WAIT" } type Variables = { - goal_id: GoalID, + goal_id: GoalID; }; type SleepInput = { - goal_id: GoalID, - duration: number, -} + goal_id: GoalID; + duration: number; +}; type SleepOutput = { - goal_id: GoalID, - targetTime: number, -} + goal_id: GoalID; + targetTime: number; +}; type Outputs = { - Time?: SleepInput, - result?: Result, + Time?: SleepInput; + result?: Result; }; type ReducerState = { - state: State, - variables: Variables, - outputs: Outputs, + state: State; + variables: Variables; + outputs: Outputs; }; type Reducer = (prev?: ReducerState) => ReducerState | undefined; enum InputType { - GOAL = 'GOAL', - CANCEL = 'CANCEL', - SLEEP_DONE = 'SLEEP_DONE', + GOAL = "GOAL", + CANCEL = "CANCEL", + SLEEP_DONE = "SLEEP_DONE" } type Input = { - type: InputType, - value: Goal | GoalID | SleepOutput, + type: InputType; + value: Goal | GoalID | SleepOutput; }; function input( goal$: Stream, cancel$: Stream, - sleepDone$: Stream, + sleepDone$: Stream ) { return xs.merge( - goal$.filter(g => typeof g !== 'undefined' && g !== null).map(g => { - const goal: Goal = initGoal(g); - return { - type: InputType.GOAL, - value: typeof goal.goal === 'number' - ? { - goal_id: goal.goal_id, - goal: {goal_id: goal.goal_id, duration: goal.goal}, - } : goal, - }; - }), - cancel$.map(val => ({type: InputType.CANCEL, value: val})), - sleepDone$.map(val => ({type: InputType.SLEEP_DONE, value: val})), + goal$ + .filter(g => typeof g !== "undefined" && g !== null) + .map(g => { + const goal: Goal = initGoal(g); + return { + type: InputType.GOAL, + value: + typeof goal.goal === "number" + ? { + goal_id: goal.goal_id, + goal: { goal_id: goal.goal_id, duration: goal.goal } + } + : goal + }; + }), + cancel$.map(val => ({ type: InputType.CANCEL, value: val })), + sleepDone$.map(val => ({ type: InputType.SLEEP_DONE, value: val })) ); } function transition(prev: ReducerState, input: Input): ReducerState { if (prev.state === State.WAIT) { if (input.type === InputType.GOAL) { - const goal = (input.value as Goal); + const goal = input.value as Goal; return { ...prev, state: State.RUN, variables: { - goal_id: goal.goal_id, + goal_id: goal.goal_id }, outputs: { - Time: goal.goal, - }, + Time: goal.goal + } }; } } else if (prev.state === State.RUN) { - if (input.type === InputType.GOAL || input.type === InputType.CANCEL - && (input.value === null || - isEqualGoalID(input.value as GoalID, prev.variables.goal_id))) { + if ( + input.type === InputType.GOAL || + (input.type === InputType.CANCEL && + (input.value === null || + isEqualGoalID(input.value as GoalID, prev.variables.goal_id))) + ) { return { ...prev, state: input.type === InputType.GOAL ? State.RUN : State.WAIT, variables: { - goal_id: input.type === InputType.GOAL ? - (input.value as Goal).goal_id : prev.variables.goal_id, + goal_id: + input.type === InputType.GOAL + ? (input.value as Goal).goal_id + : prev.variables.goal_id }, outputs: { - Time: (input.value as Goal).goal, - }, + Time: (input.value as Goal).goal + } }; } else if ( - input.type === InputType.SLEEP_DONE - && isEqualGoalID( - (input.value as SleepOutput).goal_id, prev.variables.goal_id) + input.type === InputType.SLEEP_DONE && + isEqualGoalID( + (input.value as SleepOutput).goal_id, + prev.variables.goal_id + ) ) { return { ...prev, state: State.WAIT, variables: { - goal_id: null, + goal_id: null }, outputs: { result: { status: { goal_id: prev.variables.goal_id, - status: Status.SUCCEEDED, + status: Status.SUCCEEDED }, - result: input.value, - }, - }, + result: input.value + } + } }; } } @@ -138,23 +156,25 @@ function transition(prev: ReducerState, input: Input): ReducerState { } function transitionReducer(input$: Stream): Stream { - const initReducer$: Stream = xs.of( - function initReducer(prev: ReducerState): ReducerState { - return { - state: State.WAIT, - variables: { - goal_id: null, - }, - outputs: null, + const initReducer$: Stream = xs.of(function initReducer( + prev: ReducerState + ): ReducerState { + return { + state: State.WAIT, + variables: { + goal_id: null + }, + outputs: null + }; + }); + + const inputReducer$: Stream = input$.map( + input => + function inputReducer(prev: ReducerState): ReducerState { + return transition(prev, input); } - } ); - const inputReducer$: Stream = input$ - .map(input => function inputReducer(prev: ReducerState): ReducerState { - return transition(prev, input); - }); - return xs.merge(initReducer$, inputReducer$); } @@ -164,9 +184,10 @@ function status(reducerState$): Stream { .map(rs => rs.outputs.result.status); const active$: Stream = reducerState$ .filter(rs => rs.state === State.RUN) - .map(rs => ({goal_id: rs.variables.goal_id, status: Status.ACTIVE})); - const initGoalStatus = generateGoalStatus({status: Status.SUCCEEDED}); - return xs.merge(done$, active$) + .map(rs => ({ goal_id: rs.variables.goal_id, status: Status.ACTIVE })); + const initGoalStatus = generateGoalStatus({ status: Status.SUCCEEDED }); + return xs + .merge(done$, active$) .compose(dropRepeats(isEqualGoalStatus)) .startWith(initGoalStatus); } @@ -176,17 +197,13 @@ function output(reducerState$) { .filter(rs => !!rs.outputs) .map(rs => rs.outputs); return { - result: outputs$ - .filter(o => !!o.result) - .map(o => o.result), - Time: outputs$ - .filter(o => typeof o.Time !== 'undefined') - .map(o => o.Time), + result: outputs$.filter(o => !!o.result).map(o => o.result), + Time: outputs$.filter(o => typeof o.Time !== "undefined").map(o => o.Time) }; -}; +} export interface Sources extends ActionSources { - Time: TimeSource, + Time: TimeSource; } function sleep(timeSource) { @@ -195,13 +212,13 @@ function sleep(timeSource) { return xs.create({ start(listener) { - const {schedule, currentTime} = timeSource.createOperator(); + const { schedule, currentTime } = timeSource.createOperator(); sourceListener = stream.addListener({ - next({goal_id, duration}) { + next({ goal_id, duration }) { schedule.next(listener, currentTime() + duration, { goal_id, - targetTime: currentTime() + duration, + targetTime: currentTime() + duration }); }, @@ -211,7 +228,7 @@ function sleep(timeSource) { complete() { schedule.complete(listener, currentTime()); - }, + } }); }, @@ -220,7 +237,7 @@ function sleep(timeSource) { } }); }; -}; +} export function SleepAction(sources: Sources): ActionSinks { const status$ = status(sources.state.stream); @@ -229,7 +246,7 @@ export function SleepAction(sources: Sources): ActionSinks { const input$ = input( sources.goal || xs.never(), sources.cancel || xs.never(), - outputs.Time.compose(sleep(sources.Time)), + outputs.Time.compose(sleep(sources.Time)) ); const reducer = transitionReducer(input$); @@ -238,4 +255,4 @@ export function SleepAction(sources: Sources): ActionSinks { status: status$, ...outputs }; -} \ No newline at end of file +} diff --git a/examples/actionbank/sleep/src/index.ts b/examples/actionbank/sleep/src/index.ts index e129fa9a..1bb94714 100644 --- a/examples/actionbank/sleep/src/index.ts +++ b/examples/actionbank/sleep/src/index.ts @@ -1,25 +1,26 @@ -import xs from 'xstream'; -import delay from 'xstream/extra/delay'; -import {withState} from '@cycle/state'; -import {run} from '@cycle/run'; -import {timeDriver} from '@cycle/time'; -import {SleepAction} from './SleepAction'; - +import xs from "xstream"; +import delay from "xstream/extra/delay"; +import { withState } from "@cycle/state"; +import { run } from "@cycle/run"; +import { timeDriver } from "@cycle/time"; +import { SleepAction } from "./SleepAction"; function main(sources) { - sources.state.stream.addListener({next: s => console.debug('reducer state', s)}); + sources.state.stream.addListener({ + next: s => console.debug("reducer state", s) + }); const sleepAction = SleepAction({ state: sources.state, goal: xs.of(1000).compose(delay(1000)) as any, - Time: sources.Time, + Time: sources.Time }); - return { - state: sleepAction.state, - } -}; + return { + state: sleepAction.state + }; +} run(withState(main), { - Time: timeDriver, -}); \ No newline at end of file + Time: timeDriver +}); diff --git a/examples/actionbank/twospeechbubbles/src/index.ts b/examples/actionbank/twospeechbubbles/src/index.ts index b8a4fd41..e30a30b6 100644 --- a/examples/actionbank/twospeechbubbles/src/index.ts +++ b/examples/actionbank/twospeechbubbles/src/index.ts @@ -1,45 +1,50 @@ -import xs from 'xstream'; -import delay from 'xstream/extra/delay'; -import {makeDOMDriver} from '@cycle/dom'; -import {withState} from '@cycle/state'; -import {run} from '@cycle/run'; -import {selectActionResult} from '@cycle-robot-drivers/action'; -import {TwoSpeechbubbles} from '@cycle-robot-drivers/actionbank'; - +import xs from "xstream"; +import delay from "xstream/extra/delay"; +import { makeDOMDriver } from "@cycle/dom"; +import { withState } from "@cycle/state"; +import { run } from "@cycle/run"; +import { selectActionResult } from "@cycle-robot-drivers/action"; +import { TwoSpeechbubbles } from "@cycle-robot-drivers/actionbank"; function main(sources) { - sources.state.stream.addListener({next: s => console.debug('reducer state', s)}); + sources.state.stream.addListener({ + next: s => console.debug("reducer state", s) + }); - const TwoSpeechbubblesRaceAction = {result: sources.state.stream - .compose(selectActionResult('TwoSpeechbubblesRaceAction'))}; + const TwoSpeechbubblesRaceAction = { + result: sources.state.stream.compose( + selectActionResult("TwoSpeechbubblesRaceAction") + ) + }; // "main" component const sinks: any = TwoSpeechbubbles({ state: sources.state, TwoSpeechbubblesAllAction: { goal: TwoSpeechbubblesRaceAction.result.mapTo({ - RobotSpeechbubbleAction: 'Have a good day!', - HumanSpeechbubbleAction: '', + RobotSpeechbubbleAction: "Have a good day!", + HumanSpeechbubbleAction: "" }), - cancel: xs.never(), + cancel: xs.never() }, TwoSpeechbubblesRaceAction: { - goal: xs.of({ - RobotSpeechbubbleAction: 'Hello!', - HumanSpeechbubbleAction: ['Hi'], - }).compose(delay(1000)), - cancel: xs.never(), + goal: xs + .of({ + RobotSpeechbubbleAction: "Hello!", + HumanSpeechbubbleAction: ["Hi"] + }) + .compose(delay(1000)), + cancel: xs.never() }, - DOM: sources.DOM, + DOM: sources.DOM }); return { DOM: sinks.DOM, - state: sinks.state, + state: sinks.state }; } - run(withState(main), { - DOM: makeDOMDriver('#app'), -}); \ No newline at end of file + DOM: makeDOMDriver("#app") +}); diff --git a/examples/apps/exercise_coach/src/index.js b/examples/apps/exercise_coach/src/index.js index e5a3ba6e..aa7fed62 100644 --- a/examples/apps/exercise_coach/src/index.js +++ b/examples/apps/exercise_coach/src/index.js @@ -1,55 +1,53 @@ -import xs from 'xstream'; -import delay from 'xstream/extra/delay'; -import throttle from 'xstream/extra/throttle'; -import pairwise from 'xstream/extra/pairwise'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import delay from "xstream/extra/delay"; +import throttle from "xstream/extra/throttle"; +import pairwise from "xstream/extra/pairwise"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; const State = { - PEND: 'PEND', - INSTRUCT: 'INSTRUCT', + PEND: "PEND", + INSTRUCT: "INSTRUCT" }; const InputType = { - GOAL: 'GOAL', - INSTRUCT_DONE: 'INSTRUCT_DONE', - REP_END: 'REP_DONE', - MOVED_FACE: 'MOVED_FACE', + GOAL: "GOAL", + INSTRUCT_DONE: "INSTRUCT_DONE", + REP_END: "REP_DONE", + MOVED_FACE: "MOVED_FACE" }; const Instruction = { - FORWARD: 'Let\'s start from looking forward', - RIGHT: 'and slowly rotate to your right', - LEFT: 'and now slowly rotate to your left', - GREAT: 'Great job!', + FORWARD: "Let's start from looking forward", + RIGHT: "and slowly rotate to your right", + LEFT: "and now slowly rotate to your left", + GREAT: "Great job!" }; -function input( - start$, - speechSynthesisActionSource, - poseDetectionSource, -) { - const repDuration = 3000; // in ms - const deltaThreshold = 0.1; // in % +function input(start$, speechSynthesisActionSource, poseDetectionSource) { + const repDuration = 3000; // in ms + const deltaThreshold = 0.1; // in % const throttleDelay = 1000; // 1hz return xs.merge( - start$.mapTo({type: InputType.GOAL}), + start$.mapTo({ type: InputType.GOAL }), speechSynthesisActionSource.result .filter(result => result.status.goal_id.id !== InputType.REP_END) - .mapTo({type: InputType.INSTRUCT_DONE}), + .mapTo({ type: InputType.INSTRUCT_DONE }), speechSynthesisActionSource.result .filter(result => result.status.goal_id.id === InputType.REP_END) .compose(delay(repDuration)) - .mapTo({type: InputType.REP_END}), + .mapTo({ type: InputType.REP_END }), poseDetectionSource - .filter(poses => - poses.length === 1 - && poses[0].keypoints.filter(kpt => kpt.part === 'nose').length === 1 - ).map(poses => { - const nose = poses[0].keypoints.filter(kpt => kpt.part === 'nose')[0]; + .filter( + poses => + poses.length === 1 && + poses[0].keypoints.filter(kpt => kpt.part === "nose").length === 1 + ) + .map(poses => { + const nose = poses[0].keypoints.filter(kpt => kpt.part === "nose")[0]; return { - x: nose.position.x / 640, // max value of position.x is 640 - y: nose.position.y / 480, // max value of position.y is 480 + x: nose.position.x / 640, // max value of position.x is 640 + y: nose.position.y / 480 // max value of position.y is 480 }; }) .compose(throttle(throttleDelay)) @@ -58,23 +56,23 @@ function input( return cur.x - prev.x; }) .filter(delta => Math.abs(delta) > deltaThreshold) - .map(delta => ({type: InputType.MOVED_FACE, value: delta})) + .map(delta => ({ type: InputType.MOVED_FACE, value: delta })) ); } function createTransition() { const transitionTable = { [State.PEND]: { - [InputType.GOAL]: (variables) => State.INSTRUCT, + [InputType.GOAL]: variables => State.INSTRUCT }, [State.INSTRUCT]: { - [InputType.INSTRUCT_DONE]: (variables) => { + [InputType.INSTRUCT_DONE]: variables => { if (variables.instruction === Instruction.GREAT) { return State.PEND; } else { return State.INSTRUCT; } - }, + } } }; @@ -82,9 +80,9 @@ function createTransition() { return !transitionTable[state] ? state : !transitionTable[state][input.type] - ? state - : transitionTable[state][input.type](variables); - } + ? state + : transitionTable[state][input.type](variables); + }; } function createEmission() { @@ -93,27 +91,31 @@ function createEmission() { const emissionTable = { [State.PEND]: { [InputType.GOAL]: (variables, input) => ({ - variables: {instruction: Instruction.FORWARD, rep: 0}, - outputs: {SpeechSynthesisAction: {goal: Instruction.FORWARD}}, - }), + variables: { instruction: Instruction.FORWARD, rep: 0 }, + outputs: { SpeechSynthesisAction: { goal: Instruction.FORWARD } } + }) }, [State.INSTRUCT]: { [InputType.INSTRUCT_DONE]: (variables, input) => { - if (variables.instruction === Instruction.FORWARD) { // exercise start + if (variables.instruction === Instruction.FORWARD) { + // exercise start return { variables: { instruction: Instruction.RIGHT, - rep: variables.rep, + rep: variables.rep }, outputs: { - SpeechSynthesisAction: {goal: { - goal_id: {stamp: new Date(), id: InputType.REP_END}, - goal: Instruction.RIGHT, - }}, - }, + SpeechSynthesisAction: { + goal: { + goal_id: { stamp: new Date(), id: InputType.REP_END }, + goal: Instruction.RIGHT + } + } + } }; - } else if (variables.instruction === Instruction.GREAT) {// exercise end - return {variables, outputs: {done: true}}; + } else if (variables.instruction === Instruction.GREAT) { + // exercise end + return { variables, outputs: { done: true } }; } }, [InputType.REP_END]: (variables, input) => { @@ -121,73 +123,80 @@ function createEmission() { return { variables: { instruction: Instruction.LEFT, - rep: variables.rep, + rep: variables.rep }, outputs: { - SpeechSynthesisAction: {goal: { - goal_id: {stamp: new Date(), id: InputType.REP_END}, - goal: Instruction.LEFT, - }}, - }, + SpeechSynthesisAction: { + goal: { + goal_id: { stamp: new Date(), id: InputType.REP_END }, + goal: Instruction.LEFT + } + } + } }; - } else if (variables.instruction === Instruction.LEFT) { // rep ended - if (variables.rep < maxRep - 1) { // repeat + } else if (variables.instruction === Instruction.LEFT) { + // rep ended + if (variables.rep < maxRep - 1) { + // repeat return { variables: { instruction: Instruction.RIGHT, - rep: variables.rep + 1, + rep: variables.rep + 1 }, outputs: { - SpeechSynthesisAction: {goal: { - goal_id: {stamp: new Date(), id: InputType.REP_END}, - goal: Instruction.RIGHT, - }}, - }, + SpeechSynthesisAction: { + goal: { + goal_id: { stamp: new Date(), id: InputType.REP_END }, + goal: Instruction.RIGHT + } + } + } }; } else { return { variables: { instruction: Instruction.GREAT, - rep: variables.rep, + rep: variables.rep }, outputs: { - SpeechSynthesisAction: {goal: Instruction.GREAT}, - }, + SpeechSynthesisAction: { goal: Instruction.GREAT } + } }; } } }, [InputType.MOVED_FACE]: (variables, input) => { if ( - variables.instruction === Instruction.RIGHT - || variables.instruction === Instruction.LEFT + variables.instruction === Instruction.RIGHT || + variables.instruction === Instruction.LEFT ) { return { variables, outputs: { AudioPlayerAction: { - goal: variables.instruction === Instruction.RIGHT ? - input.value > 0 - ? 'https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IGotIt1.ogg' // pos - : 'https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IHey1.ogg' // neg - : input.value < 0 - ? 'https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IGotIt1.ogg' // neg - : 'https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IHey1.ogg' // pos + goal: + variables.instruction === Instruction.RIGHT + ? input.value > 0 + ? "https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IGotIt1.ogg" // pos + : "https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IHey1.ogg" // neg + : input.value < 0 + ? "https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IGotIt1.ogg" // neg + : "https://raw.githubusercontent.com/aramadia/willow-sound/master/I/IHey1.ogg" // pos } } }; } } - }, + } }; return function(state, variables, input) { return !emissionTable[state] - ? {variables, outputs: null} + ? { variables, outputs: null } : !emissionTable[state][input.type] - ? {variables, outputs: null} - : emissionTable[state][input.type](variables, input); - } + ? { variables, outputs: null } + : emissionTable[state][input.type](variables, input); + }; } const transition = createTransition(); @@ -195,44 +204,49 @@ const emission = createEmission(); function update(prevState, prevVariables, input) { const state = transition(prevState, prevVariables, input); - const {variables, outputs} = emission(prevState, prevVariables, input); + const { variables, outputs } = emission(prevState, prevVariables, input); return { state, variables, - outputs, + outputs }; } function main(sources) { const input$ = input( - sources.TabletFace.events('load').mapTo({}), + sources.TabletFace.events("load").mapTo({}), sources.SpeechSynthesisAction, - sources.PoseDetection.events('poses'), + sources.PoseDetection.events("poses") ); const defaultMachine = { state: State.PEND, variables: { instruction: null, - rep: 0, + rep: 0 }, - outputs: null, + outputs: null }; - const machine$ = input$.fold((machine, input) => update( - machine.state, machine.variables, input - ), defaultMachine); + const machine$ = input$.fold( + (machine, input) => update(machine.state, machine.variables, input), + defaultMachine + ); const outputs$ = machine$ .filter(machine => !!machine.outputs) .map(machine => machine.outputs); return { - SpeechSynthesisAction: {goal: outputs$ - .filter(outputs => !!outputs.SpeechSynthesisAction) - .map(output => output.SpeechSynthesisAction.goal)}, - AudioPlayerAction: {goal: outputs$ - .filter(outputs => !!outputs.AudioPlayerAction) - .map(output => output.AudioPlayerAction.goal)}, + SpeechSynthesisAction: { + goal: outputs$ + .filter(outputs => !!outputs.SpeechSynthesisAction) + .map(output => output.SpeechSynthesisAction.goal) + }, + AudioPlayerAction: { + goal: outputs$ + .filter(outputs => !!outputs.AudioPlayerAction) + .map(output => output.AudioPlayerAction.goal) + } }; } diff --git a/examples/apps/news_delivery_bot/src/index.js b/examples/apps/news_delivery_bot/src/index.js index 9f54aa19..08ab6d11 100644 --- a/examples/apps/news_delivery_bot/src/index.js +++ b/examples/apps/news_delivery_bot/src/index.js @@ -1,35 +1,39 @@ -import {makeHTTPDriver} from '@cycle/http'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; +import { makeHTTPDriver } from "@cycle/http"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; function main(sources) { - const apiKey = ''; // News API (https://newsapi.org) API key - const request$ = sources.TabletFace.events('load').mapTo({ + const apiKey = ""; // News API (https://newsapi.org) API key + const request$ = sources.TabletFace.events("load").mapTo({ url: `https://newsapi.org/v2/top-headlines?country=us&apiKey=${apiKey}`, - category: 'newsapi', + category: "newsapi" }); const maxArticle = 5; - const goal$ = sources.HTTP.select('newsapi') + const goal$ = sources.HTTP.select("newsapi") .flatten() - .filter(response => - response.statusText === 'OK' - && JSON.parse(response.text).articles.length > 0 - ).map(response => { - const articles = JSON.parse(response.text).articles.slice(0,maxArticle); + .filter( + response => + response.statusText === "OK" && + JSON.parse(response.text).articles.length > 0 + ) + .map(response => { + const articles = JSON.parse(response.text).articles.slice(0, maxArticle); const summary = articles.reduce((acc, article, index) => { - return acc - + (index === 0 ? ' First headline, ' : '. Next headline, ') - + article.title + return ( + acc + + (index === 0 ? " First headline, " : ". Next headline, ") + + article.title + ); }, `Here are top ${articles.length} news headlines.`); return summary; }); return { - SpeechSynthesisAction: {goal: goal$}, - HTTP: request$, + SpeechSynthesisAction: { goal: goal$ }, + HTTP: request$ }; } runTabletRobotFaceApp(main, { - HTTP: makeHTTPDriver(), + HTTP: makeHTTPDriver() }); diff --git a/examples/apps/personality_quiz/src/index.js b/examples/apps/personality_quiz/src/index.js index e2d69fef..ca884661 100644 --- a/examples/apps/personality_quiz/src/index.js +++ b/examples/apps/personality_quiz/src/index.js @@ -1,13 +1,13 @@ -import xs from 'xstream'; -import pairwise from 'xstream/extra/pairwise'; -import delay from 'xstream/extra/delay'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run' +import xs from "xstream"; +import pairwise from "xstream/extra/pairwise"; +import delay from "xstream/extra/delay"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; const State = { - PEND: 'PEND', - SAY: 'SAY', //_SENTENCE - LISTEN: 'LISTEN', //_FOR_RESPONSE - WAIT: 'WAIT', //_FOR_PERSON + PEND: "PEND", + SAY: "SAY", //_SENTENCE + LISTEN: "LISTEN", //_FOR_RESPONSE + WAIT: "WAIT" //_FOR_PERSON }; const InputType = { @@ -16,9 +16,9 @@ const InputType = { VALID_RESPONSE: `VALID_RESPONSE`, INVALID_RESPONSE: `INVALID_RESPONSE`, DETECTED_FACE: `DETECTED_FACE`, - FOUND_PERSON: 'FOUND_PERSON', - LOST_PERSON: 'LOST_PERSON', - TIMED_OUT: 'TIMED_OUT', + FOUND_PERSON: "FOUND_PERSON", + LOST_PERSON: "LOST_PERSON", + TIMED_OUT: "TIMED_OUT" }; /** @@ -51,117 +51,134 @@ const InputType = { */ const Response = { - YES: 'yes', - NO: 'no', -} + YES: "yes", + NO: "no" +}; function input( start$, speechRecognitionActionResult$, speechSynthesisActionResult$, - poses$, + poses$ ) { const validResponse$ = speechRecognitionActionResult$ - .filter(result => - result.status.status === 'SUCCEEDED' - && (result.result === Response.YES || result.result === Response.NO) - ).map(result => ({ + .filter( + result => + result.status.status === "SUCCEEDED" && + (result.result === Response.YES || result.result === Response.NO) + ) + .map(result => ({ type: InputType.VALID_RESPONSE, - value: result.result, - })) + value: result.result + })); const lostOrFoundPerson$ = poses$ .map(poses => poses.length) .compose(pairwise) .filter(([prev, cur]) => prev !== cur) .map(([prev, cur]) => { if (prev < cur) { - return {type: InputType.FOUND_PERSON}; + return { type: InputType.FOUND_PERSON }; } else if (prev > cur) { - return {type: InputType.LOST_PERSON}; + return { type: InputType.LOST_PERSON }; } }); return xs.merge( - start$.mapTo({type: InputType.START}), + start$.mapTo({ type: InputType.START }), validResponse$, speechSynthesisActionResult$ - .filter(result => result.status.status === 'SUCCEEDED') - .mapTo({type: InputType.SAY_DONE}), + .filter(result => result.status.status === "SUCCEEDED") + .mapTo({ type: InputType.SAY_DONE }), speechRecognitionActionResult$ - .filter(result => - result.status.status !== 'SUCCEEDED' - || (result.result !== Response.YES && result.result !== Response.NO) - ).mapTo({type: InputType.INVALID_RESPONSE}), + .filter( + result => + result.status.status !== "SUCCEEDED" || + (result.result !== Response.YES && result.result !== Response.NO) + ) + .mapTo({ type: InputType.INVALID_RESPONSE }), poses$ - .filter(poses => - poses.length === 1 - && poses[0].keypoints.filter(kpt => kpt.part === 'nose').length === 1 - ).map(poses => { - const nose = poses[0].keypoints.filter(kpt => kpt.part === 'nose')[0]; + .filter( + poses => + poses.length === 1 && + poses[0].keypoints.filter(kpt => kpt.part === "nose").length === 1 + ) + .map(poses => { + const nose = poses[0].keypoints.filter(kpt => kpt.part === "nose")[0]; return { type: InputType.DETECTED_FACE, value: { - x: nose.position.x / 640, // max value of position.x is 640 - y: nose.position.y / 480, // max value of position.y is 480 - }, + x: nose.position.x / 640, // max value of position.x is 640 + y: nose.position.y / 480 // max value of position.y is 480 + } }; }), lostOrFoundPerson$, - xs.merge( - xs.merge( - validResponse$, - lostOrFoundPerson$.filter(input => input.type == InputType.FOUND_PERSON), - ).mapTo(xs.never()), // clear previous timeout, see https://github.com/staltz/xstream#flatten - xs.merge( - speechSynthesisActionResult$, - lostOrFoundPerson$.filter(input => input.type == InputType.LOST_PERSON), - ).mapTo(xs.of({type: InputType.TIMED_OUT}).compose(delay(30000))), // 30s - ).flatten().debug(), + xs + .merge( + xs + .merge( + validResponse$, + lostOrFoundPerson$.filter( + input => input.type == InputType.FOUND_PERSON + ) + ) + .mapTo(xs.never()), // clear previous timeout, see https://github.com/staltz/xstream#flatten + xs + .merge( + speechSynthesisActionResult$, + lostOrFoundPerson$.filter( + input => input.type == InputType.LOST_PERSON + ) + ) + .mapTo(xs.of({ type: InputType.TIMED_OUT }).compose(delay(30000))) // 30s + ) + .flatten() + .debug() ); } function createTransition() { const Sentence = { - CAREER: 'Is it important that you reach your full career potential?', - ONLINE: 'Can you see yourself working online?', - FAMILY: 'Do you have to be near my family/friends/pets?', - TRIPS: 'Do you think short trips are awesome?', - HOME: 'Do you want to have a home and nice things?', - ROUTINE: 'Do you think a routine gives your life structure?', - JOB: 'Do you need a secure job and a stable income?', - VACATIONER: 'You are a vacationer!', - EXPAT: 'You are an expat!', - NOMAD: 'You are a nomad!', + CAREER: "Is it important that you reach your full career potential?", + ONLINE: "Can you see yourself working online?", + FAMILY: "Do you have to be near my family/friends/pets?", + TRIPS: "Do you think short trips are awesome?", + HOME: "Do you want to have a home and nice things?", + ROUTINE: "Do you think a routine gives your life structure?", + JOB: "Do you need a secure job and a stable income?", + VACATIONER: "You are a vacationer!", + EXPAT: "You are an expat!", + NOMAD: "You are a nomad!" }; const flowchart = { [Sentence.CAREER]: { [Response.YES]: Sentence.ONLINE, - [Response.NO]: Sentence.FAMILY, + [Response.NO]: Sentence.FAMILY }, [Sentence.ONLINE]: { [Response.YES]: Sentence.NOMAD, - [Response.NO]: Sentence.VACATIONER, + [Response.NO]: Sentence.VACATIONER }, [Sentence.FAMILY]: { [Response.YES]: Sentence.VACATIONER, - [Response.NO]: Sentence.TRIPS, + [Response.NO]: Sentence.TRIPS }, [Sentence.TRIPS]: { [Response.YES]: Sentence.VACATIONER, - [Response.NO]: Sentence.HOME, + [Response.NO]: Sentence.HOME }, [Sentence.HOME]: { [Response.YES]: Sentence.EXPAT, - [Response.NO]: Sentence.ROUTINE, + [Response.NO]: Sentence.ROUTINE }, [Sentence.ROUTINE]: { [Response.YES]: Sentence.EXPAT, - [Response.NO]: Sentence.JOB, + [Response.NO]: Sentence.JOB }, [Sentence.JOB]: { [Response.YES]: Sentence.ONLINE, - [Response.NO]: Sentence.NOMAD, - }, + [Response.NO]: Sentence.NOMAD + } }; // this transitionTable is a dictionary of dictionaries and returns a function @@ -171,65 +188,74 @@ function createTransition() { [State.PEND]: { [InputType.START]: (prevVariables, prevInputValue) => ({ state: State.SAY, - variables: {sentence: Sentence.CAREER}, - outputs: {SpeechSynthesisAction: {goal: Sentence.CAREER}}, - }), + variables: { sentence: Sentence.CAREER }, + outputs: { SpeechSynthesisAction: { goal: Sentence.CAREER } } + }) }, [State.SAY]: { - [InputType.SAY_DONE]: (prevVariables, prevInputValue) => ( - prevVariables.sentence !== Sentence.VACATIONER - && prevVariables.sentence !== Sentence.EXPAT - && prevVariables.sentence !== Sentence.NOMAD - ) ? { // SAY_DONE - state: State.LISTEN, - variables: prevVariables, - outputs: {SpeechRecognitionAction: {goal: {}}}, - } : { // QUIZ_DONE - state: State.PEND, - variables: prevVariables, - outputs: {done: true}, - }, + [InputType.SAY_DONE]: (prevVariables, prevInputValue) => + prevVariables.sentence !== Sentence.VACATIONER && + prevVariables.sentence !== Sentence.EXPAT && + prevVariables.sentence !== Sentence.NOMAD + ? { + // SAY_DONE + state: State.LISTEN, + variables: prevVariables, + outputs: { SpeechRecognitionAction: { goal: {} } } + } + : { + // QUIZ_DONE + state: State.PEND, + variables: prevVariables, + outputs: { done: true } + }, [InputType.LOST_PERSON]: (prevVariables, prevInputValue) => ({ state: State.WAIT, variables: prevVariables, outputs: { - SpeechSynthesisAction: {goal: null} + SpeechSynthesisAction: { goal: null } } - }), + }) }, [State.LISTEN]: { [InputType.VALID_RESPONSE]: (prevVariables, prevInputValue) => ({ state: State.SAY, - variables: {sentence: flowchart[prevVariables.sentence][prevInputValue]}, + variables: { + sentence: flowchart[prevVariables.sentence][prevInputValue] + }, outputs: { SpeechSynthesisAction: { - goal: flowchart[prevVariables.sentence][prevInputValue], + goal: flowchart[prevVariables.sentence][prevInputValue] }, - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: {x: 0.5, y: 0.5}, - rightEye: {x: 0.5, y: 0.5}, - }, - }}, - }, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: { x: 0.5, y: 0.5 }, + rightEye: { x: 0.5, y: 0.5 } + } + } + } + } }), [InputType.INVALID_RESPONSE]: (prevVariables, prevInputValue) => ({ state: State.LISTEN, variables: prevVariables, - outputs: {SpeechRecognitionAction: {goal: {}}}, + outputs: { SpeechRecognitionAction: { goal: {} } } }), [InputType.DETECTED_FACE]: (prevVariables, prevInputValue) => ({ state: State.LISTEN, variables: prevVariables, outputs: { - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: prevInputValue, - rightEye: prevInputValue, - }, - }}, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: prevInputValue, + rightEye: prevInputValue + } + } + } } }), [InputType.TIMED_OUT]: (prevVariables, prevInputValue) => ({ @@ -237,15 +263,17 @@ function createTransition() { variables: prevVariables, outputs: { done: true, - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: {x: 0.5, y: 0.5}, - rightEye: {x: 0.5, y: 0.5}, - }, - }}, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: { x: 0.5, y: 0.5 }, + rightEye: { x: 0.5, y: 0.5 } + } + } + } } - }), + }) }, [State.WAIT]: { [InputType.FOUND_PERSON]: (prevVariables, prevInputValue) => ({ @@ -253,8 +281,8 @@ function createTransition() { variables: prevVariables, outputs: { SpeechSynthesisAction: { - goal: prevVariables.sentence, - }, + goal: prevVariables.sentence + } } }), [InputType.TIMED_OUT]: (prevVariables, prevInputValue) => ({ @@ -262,28 +290,33 @@ function createTransition() { variables: prevVariables, outputs: { done: true, - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: {x: 0.5, y: 0.5}, - rightEye: {x: 0.5, y: 0.5}, - }, - }}, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: { x: 0.5, y: 0.5 }, + rightEye: { x: 0.5, y: 0.5 } + } + } + } } - }), + }) } }; return function(prevState, prevVariables, prevInput) { - (prevInput.type !== "DETECTED_FACE") && + prevInput.type !== "DETECTED_FACE" && console.log(prevState, prevVariables, prevInput); // excuse me for abusing ternary return !transitionTable[prevState] - ? {state: prevState, variables: prevVariables, outputs: null} + ? { state: prevState, variables: prevVariables, outputs: null } : !transitionTable[prevState][prevInput.type] - ? {state: prevState, variables: prevVariables, outputs: null} - : transitionTable[prevState][prevInput.type](prevVariables, prevInput.value); - } + ? { state: prevState, variables: prevVariables, outputs: null } + : transitionTable[prevState][prevInput.type]( + prevVariables, + prevInput.value + ); + }; } const transition = createTransition(); @@ -294,39 +327,44 @@ function output(machine$) { .map(machine => machine.outputs); return { - SpeechSynthesisAction: {goal: outputs$ - .filter(outputs => !!outputs.SpeechSynthesisAction) - .map(output => output.SpeechSynthesisAction.goal)}, - SpeechRecognitionAction: {goal: outputs$ - .filter(outputs => !!outputs.SpeechRecognitionAction) - .map(output => output.SpeechRecognitionAction.goal)}, + SpeechSynthesisAction: { + goal: outputs$ + .filter(outputs => !!outputs.SpeechSynthesisAction) + .map(output => output.SpeechSynthesisAction.goal) + }, + SpeechRecognitionAction: { + goal: outputs$ + .filter(outputs => !!outputs.SpeechRecognitionAction) + .map(output => output.SpeechRecognitionAction.goal) + }, TabletFace: outputs$ .filter(outputs => !!outputs.TabletFace) - .map(output => output.TabletFace.goal), + .map(output => output.TabletFace.goal) }; } function main(sources) { const input$ = input( - sources.TabletFace.events('load'), + sources.TabletFace.events("load"), sources.SpeechRecognitionAction.result, sources.SpeechSynthesisAction.result, - sources.PoseDetection.events('poses'), + sources.PoseDetection.events("poses") ); const defaultMachine = { state: State.PEND, variables: { - sentence: null, + sentence: null }, - outputs: null, + outputs: null }; - const machine$ = input$.fold((machine, input) => transition( - machine.state, machine.variables, input - ), defaultMachine); + const machine$ = input$.fold( + (machine, input) => transition(machine.state, machine.variables, input), + defaultMachine + ); const sinks = output(machine$); return sinks; } -runTabletRobotFaceApp(main); \ No newline at end of file +runTabletRobotFaceApp(main); diff --git a/examples/demos/all_and_race/src/index.js b/examples/demos/all_and_race/src/index.js index b77b4f88..5e1d8c9d 100644 --- a/examples/demos/all_and_race/src/index.js +++ b/examples/demos/all_and_race/src/index.js @@ -1,84 +1,96 @@ -import xs from 'xstream'; -import delay from 'xstream/extra/delay'; -import isolate from '@cycle/isolate'; -import {run} from '@cycle/run'; -import {withState} from '@cycle/state'; -import {div, makeDOMDriver} from '@cycle/dom'; +import xs from "xstream"; +import delay from "xstream/extra/delay"; +import isolate from "@cycle/isolate"; +import { run } from "@cycle/run"; +import { withState } from "@cycle/state"; +import { div, makeDOMDriver } from "@cycle/dom"; import { makeTabletFaceDriver, FacialExpressionAction, - SpeechbubbleAction, -} from '@cycle-robot-drivers/screen'; + SpeechbubbleAction +} from "@cycle-robot-drivers/screen"; import { - createConcurrentAction, selectActionResult -} from '@cycle-robot-drivers/action'; - + createConcurrentAction, + selectActionResult +} from "@cycle-robot-drivers/action"; function main(sources) { - sources.state.stream.addListener({next: s => console.debug('reducer state', s)}); + sources.state.stream.addListener({ + next: s => console.debug("reducer state", s) + }); // "main" component const AllAction = createConcurrentAction( - ['FacialExpressionAction', 'SpeechbubbleAction'], - false, + ["FacialExpressionAction", "SpeechbubbleAction"], + false ); - const childSinks = isolate(AllAction, 'AllAction')({ - // const RaceAction = makeConcurrentAction( - // ['FacialExpressionAction', 'SpeechbubbleAction'], - // true, - // ); - // const childSinks = isolate(RaceAction, 'RaceAction')({ + const childSinks = isolate(AllAction, "AllAction")({ + // const RaceAction = makeConcurrentAction( + // ['FacialExpressionAction', 'SpeechbubbleAction'], + // true, + // ); + // const childSinks = isolate(RaceAction, 'RaceAction')({ ...sources, - goal: xs.of({ - FacialExpressionAction: 'HAPPY', - SpeechbubbleAction: ['Hello'], - }).compose(delay(1000)), + goal: xs + .of({ + FacialExpressionAction: "HAPPY", + SpeechbubbleAction: ["Hello"] + }) + .compose(delay(1000)), cancel: xs.never(), - FacialExpressionAction: {result: sources.state.stream - .compose(selectActionResult('FacialExpressionAction'))}, - SpeechbubbleAction: {result: sources.state.stream - .compose(selectActionResult('SpeechbubbleAction'))}, + FacialExpressionAction: { + result: sources.state.stream.compose( + selectActionResult("FacialExpressionAction") + ) + }, + SpeechbubbleAction: { + result: sources.state.stream.compose( + selectActionResult("SpeechbubbleAction") + ) + } }); - childSinks.result.addListener({next: r => console.log('result', r)}); - + childSinks.result.addListener({ next: r => console.log("result", r) }); // Define Actions - const facialExpressionAction = isolate(FacialExpressionAction, 'FacialExpressionAction')({ + const facialExpressionAction = isolate( + FacialExpressionAction, + "FacialExpressionAction" + )({ ...childSinks.FacialExpressionAction, state: sources.state, - TabletFace: sources.TabletFace, + TabletFace: sources.TabletFace }); - const speechbubbleAction = isolate(SpeechbubbleAction, 'SpeechbubbleAction')({ + const speechbubbleAction = isolate(SpeechbubbleAction, "SpeechbubbleAction")({ ...childSinks.SpeechbubbleAction, state: sources.state, - DOM: sources.DOM, + DOM: sources.DOM }); - // Define Reducers const reducer$ = xs.merge( facialExpressionAction.state, speechbubbleAction.state, - childSinks.state, + childSinks.state ); - // Define Sinks - const vdom$ = xs.combine( - speechbubbleAction.DOM.startWith(''), - sources.TabletFace.events('dom').startWith(''), - ).map(vdoms => div(vdoms)); + const vdom$ = xs + .combine( + speechbubbleAction.DOM.startWith(""), + sources.TabletFace.events("dom").startWith("") + ) + .map(vdoms => div(vdoms)); return { DOM: vdom$, TabletFace: facialExpressionAction.TabletFace, - state: reducer$, + state: reducer$ }; } run(withState(main), { - DOM: makeDOMDriver('#app'), - TabletFace: makeTabletFaceDriver(), + DOM: makeDOMDriver("#app"), + TabletFace: makeTabletFaceDriver() }); diff --git a/examples/demos/gyronorm/src/index.js b/examples/demos/gyronorm/src/index.js index 5966986d..aa87f97e 100644 --- a/examples/demos/gyronorm/src/index.js +++ b/examples/demos/gyronorm/src/index.js @@ -1,25 +1,33 @@ -import xs from 'xstream'; -import {div, a, pre, makeDOMDriver} from '@cycle/dom'; -import {run} from '@cycle/run'; -import {makeGyronormDriver} from 'cycle-gyronorm-driver'; +import xs from "xstream"; +import { div, a, pre, makeDOMDriver } from "@cycle/dom"; +import { run } from "@cycle/run"; +import { makeGyronormDriver } from "cycle-gyronorm-driver"; function main(sources) { - sources.GyroNorm.addListener({next: f => console.log(f)}); - const vdom$ = sources.GyroNorm - .replaceError((err) => xs.of(false)) - .map(data => !data + sources.GyroNorm.addListener({ next: f => console.log(f) }); + const vdom$ = sources.GyroNorm.replaceError(err => xs.of(false)).map(data => + !data ? div([ - 'To view this demo, browse to ', - a({props: {href: 'https://cycle-robot-drivers-demos-gyronorm.stackblitz.io'}},'https://cycle-robot-drivers-demos-gyronorm.stackblitz.io'), - ' on your mobile device' - ]) : pre(`DeviceOrientation: ${JSON.stringify(data.do)} -DeviceMotion: ${JSON.stringify(data.dm)}`)); + "To view this demo, browse to ", + a( + { + props: { + href: "https://cycle-robot-drivers-demos-gyronorm.stackblitz.io" + } + }, + "https://cycle-robot-drivers-demos-gyronorm.stackblitz.io" + ), + " on your mobile device" + ]) + : pre(`DeviceOrientation: ${JSON.stringify(data.do)} +DeviceMotion: ${JSON.stringify(data.dm)}`) + ); return { - DOM: vdom$, + DOM: vdom$ }; } run(main, { - DOM: makeDOMDriver('#app'), - GyroNorm: makeGyronormDriver(), + DOM: makeDOMDriver("#app"), + GyroNorm: makeGyronormDriver() }); diff --git a/examples/demos/meyda/src/index.js b/examples/demos/meyda/src/index.js index 8a12d025..e74c2c15 100644 --- a/examples/demos/meyda/src/index.js +++ b/examples/demos/meyda/src/index.js @@ -1,43 +1,45 @@ -import {div, h4, pre, makeDOMDriver} from '@cycle/dom'; -import {run} from '@cycle/run'; -import {makeMeydaDriver} from 'cycle-meyda-driver'; +import { div, h4, pre, makeDOMDriver } from "@cycle/dom"; +import { run } from "@cycle/run"; +import { makeMeydaDriver } from "cycle-meyda-driver"; function main(sources) { - sources.Meyda.addListener({next: f => console.log(f)}); + sources.Meyda.addListener({ next: f => console.log(f) }); - const vdom$ = sources.Meyda.map(features => div([ - h4('Meyda Audio Features'), - pre( - Object.keys(features).map( - key => `${key}: ${JSON.stringify(features[key])}\n` + const vdom$ = sources.Meyda.map(features => + div([ + h4("Meyda Audio Features"), + pre( + Object.keys(features).map( + key => `${key}: ${JSON.stringify(features[key])}\n` + ) ) - ) - ])); + ]) + ); return { - DOM: vdom$, + DOM: vdom$ }; } run(main, { - DOM: makeDOMDriver('#app'), + DOM: makeDOMDriver("#app"), Meyda: makeMeydaDriver({ featureExtractors: [ - 'rms', - 'energy', - 'spectralSlope', - 'spectralCentroid', - 'spectralRolloff', - 'spectralFlatness', - 'spectralSpread', - 'spectralSkewness', - 'spectralKurtosis', - 'zcr', - 'loudness', - 'perceptualSpread', - 'perceptualSharpness', - 'mfcc', - 'chroma', - 'powerSpectrum', - ], - }), + "rms", + "energy", + "spectralSlope", + "spectralCentroid", + "spectralRolloff", + "spectralFlatness", + "spectralSpread", + "spectralSkewness", + "spectralKurtosis", + "zcr", + "loudness", + "perceptualSpread", + "perceptualSharpness", + "mfcc", + "chroma", + "powerSpectrum" + ] + }) }); diff --git a/examples/demos/posenet/src/index.js b/examples/demos/posenet/src/index.js index dd9e4585..050a5baf 100644 --- a/examples/demos/posenet/src/index.js +++ b/examples/demos/posenet/src/index.js @@ -1,31 +1,31 @@ -import xs from 'xstream'; -import {makeDOMDriver} from '@cycle/dom'; -import {run} from '@cycle/run'; -import {makePoseDetectionDriver} from 'cycle-posenet-driver'; - +import xs from "xstream"; +import { makeDOMDriver } from "@cycle/dom"; +import { run } from "@cycle/run"; +import { makePoseDetectionDriver } from "cycle-posenet-driver"; function main(sources) { const params$ = xs.of({ - algorithm: 'single-pose', - singlePoseDetection: {minPoseConfidence: 0.2}, + algorithm: "single-pose", + singlePoseDetection: { minPoseConfidence: 0.2 } }); - const vdom$ = sources.PoseDetection.events('dom'); + const vdom$ = sources.PoseDetection.events("dom"); - sources.PoseDetection.events('poses').addListener({ - next: (poses) => { - if (poses.length === 1) { // found 1 person - console.log('poses', poses) + sources.PoseDetection.events("poses").addListener({ + next: poses => { + if (poses.length === 1) { + // found 1 person + console.log("poses", poses); } } - }) + }); return { DOM: vdom$, - PoseDetection: params$, - } + PoseDetection: params$ + }; } run(main, { - DOM: makeDOMDriver('#app'), - PoseDetection: makePoseDetectionDriver(), + DOM: makeDOMDriver("#app"), + PoseDetection: makePoseDetectionDriver() }); diff --git a/examples/demos/run/src/index.js b/examples/demos/run/src/index.js index 6f900dbb..52ba4055 100644 --- a/examples/demos/run/src/index.js +++ b/examples/demos/run/src/index.js @@ -1,57 +1,60 @@ -import xs from 'xstream'; -import delay from 'xstream/extra/delay'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; -import {initGoal} from '@cycle-robot-drivers/action'; - +import xs from "xstream"; +import delay from "xstream/extra/delay"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; +import { initGoal } from "@cycle-robot-drivers/action"; function main(sources) { - const goals$ = sources.TabletFace.events('load').mapTo({ - face: initGoal('HAPPY'), - sound: initGoal('https://raw.githubusercontent.com/aramadia/willow-sound/master/G/G15.ogg'), - robotSpeechbubble: initGoal('How are you?'), - humanSpeechbubble: initGoal(['Good', 'Bad']), - synthesis: initGoal('How are you?'), - recognition: initGoal({}), + const goals$ = sources.TabletFace.events("load").mapTo({ + face: initGoal("HAPPY"), + sound: initGoal( + "https://raw.githubusercontent.com/aramadia/willow-sound/master/G/G15.ogg" + ), + robotSpeechbubble: initGoal("How are you?"), + humanSpeechbubble: initGoal(["Good", "Bad"]), + synthesis: initGoal("How are you?"), + recognition: initGoal({}) }); - sources.HumanSpeechbubbleAction.result - .addListener({next: result => { - if (result.status.status === 'SUCCEEDED') { + sources.HumanSpeechbubbleAction.result.addListener({ + next: result => { + if (result.status.status === "SUCCEEDED") { console.log(`I received "${result.result}"`); } - }}); - sources.SpeechRecognitionAction.result - .addListener({next: result => { - if (result.status.status === 'SUCCEEDED') { + } + }); + sources.SpeechRecognitionAction.result.addListener({ + next: result => { + if (result.status.status === "SUCCEEDED") { console.log(`I heard "${result.result}"`); } - }}); - sources.PoseDetection.events('poses').addListener({next: () => {}}); + } + }); + sources.PoseDetection.events("poses").addListener({ next: () => {} }); return { FacialExpressionAction: { - goal: goals$.map(goals => goals.face), + goal: goals$.map(goals => goals.face) }, RobotSpeechbubbleAction: { - goal: goals$.map(goals => goals.robotSpeechbubble), + goal: goals$.map(goals => goals.robotSpeechbubble) }, HumanSpeechbubbleAction: { - goal: goals$.map(goals => goals.humanSpeechbubble), + goal: goals$.map(goals => goals.humanSpeechbubble) }, AudioPlayerAction: { - goal: goals$.map(goals => goals.sound), + goal: goals$.map(goals => goals.sound) }, SpeechSynthesisAction: { - goal: goals$.map(goals => goals.synthesis), + goal: goals$.map(goals => goals.synthesis) }, SpeechRecognitionAction: { - goal: goals$.map(goals => goals.recognition), + goal: goals$.map(goals => goals.recognition) }, PoseDetection: xs.of({ - algorithm: 'single-pose', - singlePoseDetection: {minPoseConfidence: 0.2}, - }), - } + algorithm: "single-pose", + singlePoseDetection: { minPoseConfidence: 0.2 } + }) + }; } runTabletRobotFaceApp(main); diff --git a/examples/demos/screen/src/index.js b/examples/demos/screen/src/index.js index 017514c3..45ce937d 100644 --- a/examples/demos/screen/src/index.js +++ b/examples/demos/screen/src/index.js @@ -1,85 +1,98 @@ -import xs from 'xstream'; -import delay from 'xstream/extra/delay'; -import isolate from '@cycle/isolate'; -import {run} from '@cycle/run'; -import {withState} from '@cycle/state'; -import {div, makeDOMDriver} from '@cycle/dom'; +import xs from "xstream"; +import delay from "xstream/extra/delay"; +import isolate from "@cycle/isolate"; +import { run } from "@cycle/run"; +import { withState } from "@cycle/state"; +import { div, makeDOMDriver } from "@cycle/dom"; import { makeTabletFaceDriver, SpeechbubbleAction, - FacialExpressionAction, -} from '@cycle-robot-drivers/screen'; + FacialExpressionAction +} from "@cycle-robot-drivers/screen"; function main(sources) { - sources.state.stream.addListener({next: s => console.debug('reducer state', s)}); + sources.state.stream.addListener({ + next: s => console.debug("reducer state", s) + }); const speechbubbleActionResult = xs.create(); const speechbubbles$ = xs.merge( - xs.of({goal_id: {stamp: Date.now(), id: `sb`}, goal: 'Hello there!'}) + xs + .of({ goal_id: { stamp: Date.now(), id: `sb` }, goal: "Hello there!" }) .compose(delay(1000)), - xs.of({goal_id: {stamp: Date.now(), id: `sb`}, goal: ['Hello!', 'Bye']}) + xs + .of({ goal_id: { stamp: Date.now(), id: `sb` }, goal: ["Hello!", "Bye"] }) .compose(delay(2000)), speechbubbleActionResult .filter(result => !!result.result) .map(result => { - if (result.result === 'Hello!') { - return {goal_id: {stamp: Date.now(), id: `sb`}, goal: ['Hello?', 'Bye']}; - } else if (result.result === 'Hello?') { - return {goal_id: {stamp: Date.now(), id: `sb`}, goal: ['Hello!', 'Bye']}; - } else if (result.result === 'Bye') { - return {goal_id: {stamp: Date.now(), id: `sb`}, goal: 'Bye now'}; + if (result.result === "Hello!") { + return { + goal_id: { stamp: Date.now(), id: `sb` }, + goal: ["Hello?", "Bye"] + }; + } else if (result.result === "Hello?") { + return { + goal_id: { stamp: Date.now(), id: `sb` }, + goal: ["Hello!", "Bye"] + }; + } else if (result.result === "Bye") { + return { goal_id: { stamp: Date.now(), id: `sb` }, goal: "Bye now" }; } }) - .filter(g => !!g), + .filter(g => !!g) ); const speechbubbleAction = isolate(SpeechbubbleAction)({ state: sources.state, DOM: sources.DOM, - goal: speechbubbles$, + goal: speechbubbles$ }); speechbubbleActionResult.imitate(speechbubbleAction.result); - speechbubbleAction.status.addListener({next: s => - console.log('SpeechbubbleAction status', s)}); + speechbubbleAction.status.addListener({ + next: s => console.log("SpeechbubbleAction status", s) + }); const expression$ = speechbubbleActionResult .filter(result => !!result.result) - .map((result) => { - if (result.result === 'Hello!') { - return {goal_id: {stamp: Date.now(), id: `fe`}, goal: 'HAPPY'}; - } else if (result.result === 'Hello?') { - return {goal_id: {stamp: Date.now(), id: `fe`}, goal: 'CONFUSED'}; - } else if (result.result === 'Bye') { - return {goal_id: {stamp: Date.now(), id: `fe`}, goal: 'SAD'}; + .map(result => { + if (result.result === "Hello!") { + return { goal_id: { stamp: Date.now(), id: `fe` }, goal: "HAPPY" }; + } else if (result.result === "Hello?") { + return { goal_id: { stamp: Date.now(), id: `fe` }, goal: "CONFUSED" }; + } else if (result.result === "Bye") { + return { goal_id: { stamp: Date.now(), id: `fe` }, goal: "SAD" }; } }) .filter(g => !!g); const facialExpressionAction = isolate(FacialExpressionAction)({ state: sources.state, TabletFace: sources.TabletFace, - goal: expression$, + goal: expression$ + }); + facialExpressionAction.status.addListener({ + next: s => console.log("FacialExpressionAction status", s) }); - facialExpressionAction.status.addListener({next: s => - console.log('FacialExpressionAction status', s)}); - // UI - const vdom$ = xs.combine( - speechbubbleAction.DOM.startWith(''), - sources.TabletFace.events('dom').startWith(''), - ).map(vdoms => div(vdoms)); + const vdom$ = xs + .combine( + speechbubbleAction.DOM.startWith(""), + sources.TabletFace.events("dom").startWith("") + ) + .map(vdoms => div(vdoms)); const reducer = xs.merge( speechbubbleAction.state, - facialExpressionAction.state, + facialExpressionAction.state ); return { DOM: vdom$, TabletFace: facialExpressionAction.TabletFace, - state: reducer, + state: reducer }; } run(withState(main), { - DOM: makeDOMDriver('#app'), - TabletFace: makeTabletFaceDriver(), + DOM: makeDOMDriver("#app"), + TabletFace: makeTabletFaceDriver() }); diff --git a/examples/simple/face_follower/src/index.js b/examples/simple/face_follower/src/index.js index cd053876..7a29c455 100644 --- a/examples/simple/face_follower/src/index.js +++ b/examples/simple/face_follower/src/index.js @@ -1,25 +1,27 @@ -import xs from 'xstream'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; const videoWidth = 640; const videoHeight = 480; function main(sources) { - const face$ = sources.PoseDetection.events('poses') - .filter(poses => - poses.length === 1 - && poses[0].keypoints.filter(kpt => kpt.part === 'nose').length === 1 - ).map(poses => { - const nose = poses[0].keypoints.filter(kpt => kpt.part === 'nose')[0]; + const face$ = sources.PoseDetection.events("poses") + .filter( + poses => + poses.length === 1 && + poses[0].keypoints.filter(kpt => kpt.part === "nose").length === 1 + ) + .map(poses => { + const nose = poses[0].keypoints.filter(kpt => kpt.part === "nose")[0]; const eyePosition = { x: nose.position.x / videoWidth, - y: nose.position.y / videoHeight, + y: nose.position.y / videoHeight }; return { - type: 'SET_STATE', + type: "SET_STATE", value: { leftEye: eyePosition, - rightEye: eyePosition, + rightEye: eyePosition } }; }); @@ -27,9 +29,9 @@ function main(sources) { return { TabletFace: face$, PoseDetection: xs.of({ - algorithm: 'single-pose', - singlePoseDetection: {minPoseConfidence: 0.2} - }), + algorithm: "single-pose", + singlePoseDetection: { minPoseConfidence: 0.2 } + }) }; } diff --git a/examples/simple/faintheart/src/index.js b/examples/simple/faintheart/src/index.js index 48cd9d37..9a905fa8 100644 --- a/examples/simple/faintheart/src/index.js +++ b/examples/simple/faintheart/src/index.js @@ -1,12 +1,12 @@ -import xs from 'xstream'; -import {run} from '@cycle/run'; -import {div, h1, h2, makeDOMDriver} from '@cycle/dom'; -import {makeMeydaDriver} from 'cycle-meyda-driver'; +import xs from "xstream"; +import { run } from "@cycle/run"; +import { div, h1, h2, makeDOMDriver } from "@cycle/dom"; +import { makeMeydaDriver } from "cycle-meyda-driver"; function makeVibrationDriver() { - const vibrationDriver = (sink$) => { + const vibrationDriver = sink$ => { sink$.addListener({ - next: (pattern) => { + next: pattern => { window.navigator.vibrate(pattern); } }); @@ -21,24 +21,28 @@ function isAndroid() { function main(sources) { const vdom$ = !isAndroid() - ? xs.of(div([ - h1("This app only works on Android devices"), - h2("Please try it on an an Android device") - ])) : sources.Meyda.map(v => div(`loudness: ${v.loudness.total}`)); + ? xs.of( + div([ + h1("This app only works on Android devices"), + h2("Please try it on an an Android device") + ]) + ) + : sources.Meyda.map(v => div(`loudness: ${v.loudness.total}`)); - const vibration$ = sources.Meyda.filter(v => v.loudness.total > 50) - .mapTo(500); + const vibration$ = sources.Meyda.filter(v => v.loudness.total > 50).mapTo( + 500 + ); return { DOM: vdom$, - Vibration: vibration$, + Vibration: vibration$ }; } run(main, { - DOM: makeDOMDriver('#app'), + DOM: makeDOMDriver("#app"), Meyda: makeMeydaDriver({ - featureExtractors: ['loudness'] + featureExtractors: ["loudness"] }), - Vibration: makeVibrationDriver(), + Vibration: makeVibrationDriver() }); diff --git a/examples/simple/hello_and_goodbye/src/index.js b/examples/simple/hello_and_goodbye/src/index.js index 7d68c591..9ebfef7e 100644 --- a/examples/simple/hello_and_goodbye/src/index.js +++ b/examples/simple/hello_and_goodbye/src/index.js @@ -1,21 +1,26 @@ -import xs from 'xstream'; -import pairwise from 'xstream/extra/pairwise'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import pairwise from "xstream/extra/pairwise"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; function main(sources) { - - const numFaces$ = sources.PoseDetection.events('poses') - .map(poses => poses.length); - const say$ = numFaces$.compose(pairwise).map(([prev, cur]) => { - if (prev < cur) { // found a person - return 'Hello'; - } else if (prev > cur) { // lost a person - return 'Goodbye'; - } - }).filter(str => !!str); + const numFaces$ = sources.PoseDetection.events("poses").map( + poses => poses.length + ); + const say$ = numFaces$ + .compose(pairwise) + .map(([prev, cur]) => { + if (prev < cur) { + // found a person + return "Hello"; + } else if (prev > cur) { + // lost a person + return "Goodbye"; + } + }) + .filter(str => !!str); return { - SpeechSynthesisAction: {goal: say$}, + SpeechSynthesisAction: { goal: say$ } }; } diff --git a/examples/simple/personality_quiz/src/index.js b/examples/simple/personality_quiz/src/index.js index 8621e014..679627a1 100644 --- a/examples/simple/personality_quiz/src/index.js +++ b/examples/simple/personality_quiz/src/index.js @@ -1,86 +1,91 @@ -import xs from 'xstream'; -import sampleCombine from 'xstream/extra/sampleCombine'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import sampleCombine from "xstream/extra/sampleCombine"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; const Question = { - CAREER: 'Is it important that you reach your full career potential?', - ONLINE: 'Can you see yourself working online?', - FAMILY: 'Do you have to be near my family/friends/pets?', - TRIPS: 'Do you think short trips are awesome?', - HOME: 'Do you want to have a home and nice things?', - ROUTINE: 'Do you think a routine gives your life structure?', - JOB: 'Do you need a secure job and a stable income?', - VACATIONER: 'You are a vacationer!', - EXPAT: 'You are an expat!', - NOMAD: 'You are a nomad!' + CAREER: "Is it important that you reach your full career potential?", + ONLINE: "Can you see yourself working online?", + FAMILY: "Do you have to be near my family/friends/pets?", + TRIPS: "Do you think short trips are awesome?", + HOME: "Do you want to have a home and nice things?", + ROUTINE: "Do you think a routine gives your life structure?", + JOB: "Do you need a secure job and a stable income?", + VACATIONER: "You are a vacationer!", + EXPAT: "You are an expat!", + NOMAD: "You are a nomad!" }; const Response = { - YES: 'yes', - NO: 'no' + YES: "yes", + NO: "no" }; const transitionTable = { [Question.CAREER]: { [Response.YES]: Question.ONLINE, - [Response.NO]: Question.FAMILY, + [Response.NO]: Question.FAMILY }, [Question.ONLINE]: { [Response.YES]: Question.NOMAD, - [Response.NO]: Question.VACATIONER, + [Response.NO]: Question.VACATIONER }, [Question.FAMILY]: { [Response.YES]: Question.VACATIONER, - [Response.NO]: Question.TRIPS, + [Response.NO]: Question.TRIPS }, [Question.TRIPS]: { [Response.YES]: Question.VACATIONER, - [Response.NO]: Question.HOME, + [Response.NO]: Question.HOME }, [Question.HOME]: { [Response.YES]: Question.EXPAT, - [Response.NO]: Question.ROUTINE, + [Response.NO]: Question.ROUTINE }, [Question.ROUTINE]: { [Response.YES]: Question.EXPAT, - [Response.NO]: Question.JOB, + [Response.NO]: Question.JOB }, [Question.JOB]: { [Response.YES]: Question.ONLINE, - [Response.NO]: Question.NOMAD, + [Response.NO]: Question.NOMAD } }; function main(sources) { sources.SpeechRecognitionAction.result.addListener({ - next: (result) => console.log('result', result) + next: result => console.log("result", result) }); const lastQuestion$ = xs.create(); const question$ = xs.merge( - sources.TabletFace.events('load').mapTo(Question.CAREER), - sources.SpeechRecognitionAction.result.filter(result => - result.status.status === 'SUCCEEDED' // must succeed - && (result.result === 'yes' || result.result === 'no') // only yes or no - ).map(result => result.result) - .startWith('') - .compose(sampleCombine( - lastQuestion$ - )).map(([response, question]) => { - return transitionTable[question][response]; - }) + sources.TabletFace.events("load").mapTo(Question.CAREER), + sources.SpeechRecognitionAction.result + .filter( + result => + result.status.status === "SUCCEEDED" && // must succeed + (result.result === "yes" || result.result === "no") // only yes or no + ) + .map(result => result.result) + .startWith("") + .compose(sampleCombine(lastQuestion$)) + .map(([response, question]) => { + return transitionTable[question][response]; + }) ); lastQuestion$.imitate(question$); const sinks = { - SpeechSynthesisAction: {goal: question$}, + SpeechSynthesisAction: { goal: question$ }, SpeechRecognitionAction: { - goal: xs.merge( - sources.SpeechSynthesisAction.result, - sources.SpeechRecognitionAction.result.filter(result => - result.status.status !== 'SUCCEEDED' - || (result.result !== 'yes' && result.result !== 'no') + goal: xs + .merge( + sources.SpeechSynthesisAction.result, + sources.SpeechRecognitionAction.result.filter( + result => + result.status.status !== "SUCCEEDED" || + (result.result !== "yes" && result.result !== "no") + ) ) - ).mapTo({}), + .mapTo({}) } }; return sinks; diff --git a/examples/simple/pose_for_gif/src/index.js b/examples/simple/pose_for_gif/src/index.js index 24193802..6f0d0578 100644 --- a/examples/simple/pose_for_gif/src/index.js +++ b/examples/simple/pose_for_gif/src/index.js @@ -1,29 +1,32 @@ -import xs from 'xstream'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; function main(sources) { const face$ = xs.merge( - sources.TabletFace.events('load').mapTo({ - type: 'START_BLINKING', + sources.TabletFace.events("load").mapTo({ + type: "START_BLINKING" }), - xs.periodic(2000).map(i => { - if (i === 0) { - return {type: 'EXPRESS', value: {type: 'HAPPY'}}; - } else if (i === 1) { - return {type: 'EXPRESS', value: {type: 'SAD'}}; - } else if (i === 2) { - return {type: 'EXPRESS', value: {type: 'ANGRY'}}; - } else if (i === 3) { - return {type: 'EXPRESS', value: {type: 'FOCUSED'}}; - } else if (i === 4) { - return {type: 'EXPRESS', value: {type: 'CONFUSED'}}; - } - }).filter(expression => !!expression), + xs + .periodic(2000) + .map(i => { + if (i === 0) { + return { type: "EXPRESS", value: { type: "HAPPY" } }; + } else if (i === 1) { + return { type: "EXPRESS", value: { type: "SAD" } }; + } else if (i === 2) { + return { type: "EXPRESS", value: { type: "ANGRY" } }; + } else if (i === 3) { + return { type: "EXPRESS", value: { type: "FOCUSED" } }; + } else if (i === 4) { + return { type: "EXPRESS", value: { type: "CONFUSED" } }; + } + }) + .filter(expression => !!expression) ); return { - DOM: sources.TabletFace.events('dom'), - TabletFace: face$, + DOM: sources.TabletFace.events("dom"), + TabletFace: face$ }; } diff --git a/examples/simple/speaking_floor_yielder/src/index.js b/examples/simple/speaking_floor_yielder/src/index.js index 44c62581..1ec75518 100644 --- a/examples/simple/speaking_floor_yielder/src/index.js +++ b/examples/simple/speaking_floor_yielder/src/index.js @@ -1,23 +1,23 @@ -import xs from 'xstream'; -import delay from 'xstream/extra/delay'; -import {runTabletRobotFaceApp} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import delay from "xstream/extra/delay"; +import { runTabletRobotFaceApp } from "@cycle-robot-drivers/run"; function main(sources) { const start$ = xs.merge( xs.of(null), - sources.SpeechSynthesisAction.result.compose(delay(5000)), + sources.SpeechSynthesisAction.result.compose(delay(5000)) ); - const speechstart$ = sources.SpeechRecognition.events('speechstart'); + const speechstart$ = sources.SpeechRecognition.events("speechstart"); const say$ = start$.mapTo( - 'You can interrupt me by saying something while I\'m speaking.' + "You can interrupt me by saying something while I'm speaking." ); const stop$ = speechstart$.mapTo(null); const listen$ = start$.mapTo({}); return { - SpeechSynthesisAction: {goal: say$, cancel: stop$}, - SpeechRecognitionAction: {goal: listen$}, + SpeechSynthesisAction: { goal: say$, cancel: stop$ }, + SpeechRecognitionAction: { goal: listen$ } }; } diff --git a/examples/tutorials/01_personality_quiz/index.js b/examples/tutorials/01_personality_quiz/index.js index 803468f3..54f7e197 100644 --- a/examples/tutorials/01_personality_quiz/index.js +++ b/examples/tutorials/01_personality_quiz/index.js @@ -1,104 +1,112 @@ -import xs from 'xstream'; -import sampleCombine from 'xstream/extra/sampleCombine'; -import {runRobotProgram} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import sampleCombine from "xstream/extra/sampleCombine"; +import { runRobotProgram } from "@cycle-robot-drivers/run"; const Question = { - CAREER: 'Is it important that you reach your full career potential?', - ONLINE: 'Can you see yourself working online?', - FAMILY: 'Do you have to be near my family/friends/pets?', - TRIPS: 'Do you think short trips are awesome?', - HOME: 'Do you want to have a home and nice things?', - ROUTINE: 'Do you think a routine gives your life structure?', - JOB: 'Do you need a secure job and a stable income?', - VACATIONER: 'You are a vacationer!', - EXPAT: 'You are an expat!', - NOMAD: 'You are a nomad!' + CAREER: "Is it important that you reach your full career potential?", + ONLINE: "Can you see yourself working online?", + FAMILY: "Do you have to be near my family/friends/pets?", + TRIPS: "Do you think short trips are awesome?", + HOME: "Do you want to have a home and nice things?", + ROUTINE: "Do you think a routine gives your life structure?", + JOB: "Do you need a secure job and a stable income?", + VACATIONER: "You are a vacationer!", + EXPAT: "You are an expat!", + NOMAD: "You are a nomad!" }; const Response = { - YES: 'yes', - NO: 'no' + YES: "yes", + NO: "no" }; const transitionTable = { [Question.CAREER]: { [Response.YES]: Question.ONLINE, - [Response.NO]: Question.FAMILY, + [Response.NO]: Question.FAMILY }, [Question.ONLINE]: { [Response.YES]: Question.NOMAD, - [Response.NO]: Question.VACATIONER, + [Response.NO]: Question.VACATIONER }, [Question.FAMILY]: { [Response.YES]: Question.VACATIONER, - [Response.NO]: Question.TRIPS, + [Response.NO]: Question.TRIPS }, [Question.TRIPS]: { [Response.YES]: Question.VACATIONER, - [Response.NO]: Question.HOME, + [Response.NO]: Question.HOME }, [Question.HOME]: { [Response.YES]: Question.EXPAT, - [Response.NO]: Question.ROUTINE, + [Response.NO]: Question.ROUTINE }, [Question.ROUTINE]: { [Response.YES]: Question.EXPAT, - [Response.NO]: Question.JOB, + [Response.NO]: Question.JOB }, [Question.JOB]: { [Response.YES]: Question.ONLINE, - [Response.NO]: Question.NOMAD, + [Response.NO]: Question.NOMAD } }; function main(sources) { sources.SpeechRecognitionAction.result.addListener({ - next: (result) => console.log('result', result) + next: result => console.log("result", result) }); const lastQuestion$ = xs.create(); const question$ = xs.merge( sources.TabletFace.load.mapTo(Question.CAREER), - sources.SpeechRecognitionAction.result.filter(result => - result.status.status === 'SUCCEEDED' // must succeed - && (result.result === 'yes' || result.result === 'no') // only yes or no - ).map(result => result.result) - .startWith('') - .compose(sampleCombine( - lastQuestion$ - )).map(([response, question]) => { - return transitionTable[question][response]; - }) + sources.SpeechRecognitionAction.result + .filter( + result => + result.status.status === "SUCCEEDED" && // must succeed + (result.result === "yes" || result.result === "no") // only yes or no + ) + .map(result => result.result) + .startWith("") + .compose(sampleCombine(lastQuestion$)) + .map(([response, question]) => { + return transitionTable[question][response]; + }) ); lastQuestion$.imitate(question$); const sinks = { TabletFace: sources.PoseDetection.poses - .filter(poses => - // must see one person - poses.length === 1 - // must see the nose - && poses[0].keypoints.filter(kpt => kpt.part === 'nose').length === 1 - ).map(poses => { - const nose = poses[0].keypoints.filter(kpt => kpt.part === 'nose')[0]; + .filter( + poses => + // must see one person + poses.length === 1 && + // must see the nose + poses[0].keypoints.filter(kpt => kpt.part === "nose").length === 1 + ) + .map(poses => { + const nose = poses[0].keypoints.filter(kpt => kpt.part === "nose")[0]; return { - x: nose.position.x / 640, // max value of position.x is 640 - y: nose.position.y / 480 // max value of position.y is 480 + x: nose.position.x / 640, // max value of position.x is 640 + y: nose.position.y / 480 // max value of position.y is 480 }; - }).map(position => ({ - type: 'SET_STATE', + }) + .map(position => ({ + type: "SET_STATE", value: { leftEye: position, rightEye: position } })), SpeechSynthesisAction: question$, - SpeechRecognitionAction: xs.merge( - sources.SpeechSynthesisAction.result, - sources.SpeechRecognitionAction.result.filter(result => - result.status.status !== 'SUCCEEDED' - || (result.result !== 'yes' && result.result !== 'no') + SpeechRecognitionAction: xs + .merge( + sources.SpeechSynthesisAction.result, + sources.SpeechRecognitionAction.result.filter( + result => + result.status.status !== "SUCCEEDED" || + (result.result !== "yes" && result.result !== "no") + ) ) - ).mapTo({}) + .mapTo({}) }; return sinks; } diff --git a/examples/tutorials/02_fsm/index.js b/examples/tutorials/02_fsm/index.js index 8094e0d7..0f494d38 100644 --- a/examples/tutorials/02_fsm/index.js +++ b/examples/tutorials/02_fsm/index.js @@ -1,13 +1,13 @@ -import xs from 'xstream'; -import pairwise from 'xstream/extra/pairwise'; -import delay from 'xstream/extra/delay'; -import {runRobotProgram} from '@cycle-robot-drivers/run'; +import xs from "xstream"; +import pairwise from "xstream/extra/pairwise"; +import delay from "xstream/extra/delay"; +import { runRobotProgram } from "@cycle-robot-drivers/run"; const State = { - PEND: 'PEND', - SAY: 'SAY', //_SENTENCE - LISTEN: 'LISTEN', //_FOR_RESPONSE - WAIT: 'WAIT', //_FOR_PERSON + PEND: "PEND", + SAY: "SAY", //_SENTENCE + LISTEN: "LISTEN", //_FOR_RESPONSE + WAIT: "WAIT" //_FOR_PERSON }; const InputType = { @@ -16,9 +16,9 @@ const InputType = { VALID_RESPONSE: `VALID_RESPONSE`, INVALID_RESPONSE: `INVALID_RESPONSE`, DETECTED_FACE: `DETECTED_FACE`, - FOUND_PERSON: 'FOUND_PERSON', - LOST_PERSON: 'LOST_PERSON', - TIMED_OUT: 'TIMED_OUT', + FOUND_PERSON: "FOUND_PERSON", + LOST_PERSON: "LOST_PERSON", + TIMED_OUT: "TIMED_OUT" }; /** @@ -51,117 +51,133 @@ const InputType = { */ const Response = { - YES: 'yes', - NO: 'no', -} + YES: "yes", + NO: "no" +}; function input( start$, speechRecognitionActionResult$, speechSynthesisActionResult$, - poses$, + poses$ ) { const validResponse$ = speechRecognitionActionResult$ - .filter(result => - result.status.status === 'SUCCEEDED' - && (result.result === Response.YES || result.result === Response.NO) - ).map(result => ({ + .filter( + result => + result.status.status === "SUCCEEDED" && + (result.result === Response.YES || result.result === Response.NO) + ) + .map(result => ({ type: InputType.VALID_RESPONSE, - value: result.result, - })) + value: result.result + })); const lostOrFoundPerson$ = poses$ .map(poses => poses.length) .compose(pairwise) .filter(([prev, cur]) => prev !== cur) .map(([prev, cur]) => { if (prev < cur) { - return {type: InputType.FOUND_PERSON}; + return { type: InputType.FOUND_PERSON }; } else if (prev > cur) { - return {type: InputType.LOST_PERSON}; + return { type: InputType.LOST_PERSON }; } }); return xs.merge( - start$.mapTo({type: InputType.START}), + start$.mapTo({ type: InputType.START }), validResponse$, speechSynthesisActionResult$ - .filter(result => result.status.status === 'SUCCEEDED') - .mapTo({type: InputType.SAY_DONE}), + .filter(result => result.status.status === "SUCCEEDED") + .mapTo({ type: InputType.SAY_DONE }), speechRecognitionActionResult$ - .filter(result => - result.status.status !== 'SUCCEEDED' - || (result.result !== Response.YES && result.result !== Response.NO) - ).mapTo({type: InputType.INVALID_RESPONSE}), + .filter( + result => + result.status.status !== "SUCCEEDED" || + (result.result !== Response.YES && result.result !== Response.NO) + ) + .mapTo({ type: InputType.INVALID_RESPONSE }), poses$ - .filter(poses => - poses.length === 1 - && poses[0].keypoints.filter(kpt => kpt.part === 'nose').length === 1 - ).map(poses => { - const nose = poses[0].keypoints.filter(kpt => kpt.part === 'nose')[0]; + .filter( + poses => + poses.length === 1 && + poses[0].keypoints.filter(kpt => kpt.part === "nose").length === 1 + ) + .map(poses => { + const nose = poses[0].keypoints.filter(kpt => kpt.part === "nose")[0]; return { type: InputType.DETECTED_FACE, value: { - x: nose.position.x / 640, // max value of position.x is 640 - y: nose.position.y / 480, // max value of position.y is 480 - }, + x: nose.position.x / 640, // max value of position.x is 640 + y: nose.position.y / 480 // max value of position.y is 480 + } }; }), lostOrFoundPerson$, - xs.merge( - xs.merge( - validResponse$, - lostOrFoundPerson$.filter(input => input.type == InputType.FOUND_PERSON), - ).mapTo(xs.never()), // clear previous timeout, see https://github.com/staltz/xstream#flatten - xs.merge( - speechSynthesisActionResult$, - lostOrFoundPerson$.filter(input => input.type == InputType.LOST_PERSON), - ).mapTo(xs.of({type: InputType.TIMED_OUT}).compose(delay(30000))), // 30s - ).flatten(), + xs + .merge( + xs + .merge( + validResponse$, + lostOrFoundPerson$.filter( + input => input.type == InputType.FOUND_PERSON + ) + ) + .mapTo(xs.never()), // clear previous timeout, see https://github.com/staltz/xstream#flatten + xs + .merge( + speechSynthesisActionResult$, + lostOrFoundPerson$.filter( + input => input.type == InputType.LOST_PERSON + ) + ) + .mapTo(xs.of({ type: InputType.TIMED_OUT }).compose(delay(30000))) // 30s + ) + .flatten() ); } function createTransition() { const Sentence = { - CAREER: 'Is it important that you reach your full career potential?', - ONLINE: 'Can you see yourself working online?', - FAMILY: 'Do you have to be near my family/friends/pets?', - TRIPS: 'Do you think short trips are awesome?', - HOME: 'Do you want to have a home and nice things?', - ROUTINE: 'Do you think a routine gives your life structure?', - JOB: 'Do you need a secure job and a stable income?', - VACATIONER: 'You are a vacationer!', - EXPAT: 'You are an expat!', - NOMAD: 'You are a nomad!', + CAREER: "Is it important that you reach your full career potential?", + ONLINE: "Can you see yourself working online?", + FAMILY: "Do you have to be near my family/friends/pets?", + TRIPS: "Do you think short trips are awesome?", + HOME: "Do you want to have a home and nice things?", + ROUTINE: "Do you think a routine gives your life structure?", + JOB: "Do you need a secure job and a stable income?", + VACATIONER: "You are a vacationer!", + EXPAT: "You are an expat!", + NOMAD: "You are a nomad!" }; const flowchart = { [Sentence.CAREER]: { [Response.YES]: Sentence.ONLINE, - [Response.NO]: Sentence.FAMILY, + [Response.NO]: Sentence.FAMILY }, [Sentence.ONLINE]: { [Response.YES]: Sentence.NOMAD, - [Response.NO]: Sentence.VACATIONER, + [Response.NO]: Sentence.VACATIONER }, [Sentence.FAMILY]: { [Response.YES]: Sentence.VACATIONER, - [Response.NO]: Sentence.TRIPS, + [Response.NO]: Sentence.TRIPS }, [Sentence.TRIPS]: { [Response.YES]: Sentence.VACATIONER, - [Response.NO]: Sentence.HOME, + [Response.NO]: Sentence.HOME }, [Sentence.HOME]: { [Response.YES]: Sentence.EXPAT, - [Response.NO]: Sentence.ROUTINE, + [Response.NO]: Sentence.ROUTINE }, [Sentence.ROUTINE]: { [Response.YES]: Sentence.EXPAT, - [Response.NO]: Sentence.JOB, + [Response.NO]: Sentence.JOB }, [Sentence.JOB]: { [Response.YES]: Sentence.ONLINE, - [Response.NO]: Sentence.NOMAD, - }, + [Response.NO]: Sentence.NOMAD + } }; // this transitionTable is a dictionary of dictionaries and returns a function @@ -171,65 +187,74 @@ function createTransition() { [State.PEND]: { [InputType.START]: (prevVariables, prevInputValue) => ({ state: State.SAY, - variables: {sentence: Sentence.CAREER}, - outputs: {SpeechSynthesisAction: {goal: Sentence.CAREER}}, - }), + variables: { sentence: Sentence.CAREER }, + outputs: { SpeechSynthesisAction: { goal: Sentence.CAREER } } + }) }, [State.SAY]: { - [InputType.SAY_DONE]: (prevVariables, prevInputValue) => ( - prevVariables.sentence !== Sentence.VACATIONER - && prevVariables.sentence !== Sentence.EXPAT - && prevVariables.sentence !== Sentence.NOMAD - ) ? { // SAY_DONE - state: State.LISTEN, - variables: prevVariables, - outputs: {SpeechRecognitionAction: {goal: {}}}, - } : { // QUIZ_DONE - state: State.PEND, - variables: prevVariables, - outputs: {done: true}, - }, + [InputType.SAY_DONE]: (prevVariables, prevInputValue) => + prevVariables.sentence !== Sentence.VACATIONER && + prevVariables.sentence !== Sentence.EXPAT && + prevVariables.sentence !== Sentence.NOMAD + ? { + // SAY_DONE + state: State.LISTEN, + variables: prevVariables, + outputs: { SpeechRecognitionAction: { goal: {} } } + } + : { + // QUIZ_DONE + state: State.PEND, + variables: prevVariables, + outputs: { done: true } + }, [InputType.LOST_PERSON]: (prevVariables, prevInputValue) => ({ state: State.WAIT, variables: prevVariables, outputs: { - SpeechSynthesisAction: {goal: null} + SpeechSynthesisAction: { goal: null } } - }), + }) }, [State.LISTEN]: { [InputType.VALID_RESPONSE]: (prevVariables, prevInputValue) => ({ state: State.SAY, - variables: {sentence: flowchart[prevVariables.sentence][prevInputValue]}, + variables: { + sentence: flowchart[prevVariables.sentence][prevInputValue] + }, outputs: { SpeechSynthesisAction: { - goal: flowchart[prevVariables.sentence][prevInputValue], + goal: flowchart[prevVariables.sentence][prevInputValue] }, - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: {x: 0.5, y: 0.5}, - rightEye: {x: 0.5, y: 0.5}, - }, - }}, - }, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: { x: 0.5, y: 0.5 }, + rightEye: { x: 0.5, y: 0.5 } + } + } + } + } }), [InputType.INVALID_RESPONSE]: (prevVariables, prevInputValue) => ({ state: State.LISTEN, variables: prevVariables, - outputs: {SpeechRecognitionAction: {goal: {}}}, + outputs: { SpeechRecognitionAction: { goal: {} } } }), [InputType.DETECTED_FACE]: (prevVariables, prevInputValue) => ({ state: State.LISTEN, variables: prevVariables, outputs: { - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: prevInputValue, - rightEye: prevInputValue, - }, - }}, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: prevInputValue, + rightEye: prevInputValue + } + } + } } }), [InputType.TIMED_OUT]: (prevVariables, prevInputValue) => ({ @@ -237,15 +262,17 @@ function createTransition() { variables: prevVariables, outputs: { done: true, - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: {x: 0.5, y: 0.5}, - rightEye: {x: 0.5, y: 0.5}, - }, - }}, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: { x: 0.5, y: 0.5 }, + rightEye: { x: 0.5, y: 0.5 } + } + } + } } - }), + }) }, [State.WAIT]: { [InputType.FOUND_PERSON]: (prevVariables, prevInputValue) => ({ @@ -253,8 +280,8 @@ function createTransition() { variables: prevVariables, outputs: { SpeechSynthesisAction: { - goal: prevVariables.sentence, - }, + goal: prevVariables.sentence + } } }), [InputType.TIMED_OUT]: (prevVariables, prevInputValue) => ({ @@ -262,28 +289,33 @@ function createTransition() { variables: prevVariables, outputs: { done: true, - TabletFace: {goal: { - type: 'SET_STATE', - value: { - leftEye: {x: 0.5, y: 0.5}, - rightEye: {x: 0.5, y: 0.5}, - }, - }}, + TabletFace: { + goal: { + type: "SET_STATE", + value: { + leftEye: { x: 0.5, y: 0.5 }, + rightEye: { x: 0.5, y: 0.5 } + } + } + } } - }), + }) } }; return function(prevState, prevVariables, prevInput) { - (prevInput.type !== "DETECTED_FACE") && + prevInput.type !== "DETECTED_FACE" && console.log(prevState, prevVariables, prevInput); // excuse me for abusing ternary return !transitionTable[prevState] - ? {state: prevState, variables: prevVariables, outputs: null} + ? { state: prevState, variables: prevVariables, outputs: null } : !transitionTable[prevState][prevInput.type] - ? {state: prevState, variables: prevVariables, outputs: null} - : transitionTable[prevState][prevInput.type](prevVariables, prevInput.value); - } + ? { state: prevState, variables: prevVariables, outputs: null } + : transitionTable[prevState][prevInput.type]( + prevVariables, + prevInput.value + ); + }; } const transition = createTransition(); @@ -302,7 +334,7 @@ function output(machine$) { .map(output => output.SpeechRecognitionAction.goal), TabletFace: outputs$ .filter(outputs => !!outputs.TabletFace) - .map(output => output.TabletFace.goal), + .map(output => output.TabletFace.goal) }; } @@ -311,19 +343,20 @@ function main(sources) { sources.TabletFace.load, sources.SpeechRecognitionAction.result, sources.SpeechSynthesisAction.result, - sources.PoseDetection.poses, + sources.PoseDetection.poses ); const defaultMachine = { state: State.PEND, variables: { - sentence: null, + sentence: null }, - outputs: null, + outputs: null }; - const machine$ = input$.fold((machine, input) => transition( - machine.state, machine.variables, input - ), defaultMachine); + const machine$ = input$.fold( + (machine, input) => transition(machine.state, machine.variables, input), + defaultMachine + ); const sinks = output(machine$); return sinks;