function CreateUser (sources) { let actions = intent(sources), state$ = model(actions), alerts$ = state$.map(state => state.alerts).startWith([]), emailField = LabelInput({state$, props$: xs.of({ name: 'email', type: 'email', label: 'Email' })}), passwordField = LabelInput({state$, props$: xs.of({ name: 'password', type: 'password', label: 'Password' })}), render = ([state, email, password]) => { return form({class: classes(styles.form, styles.formHorizontal)}, [ fieldset([ legend({class: classes(styles.legend)}, 'Create User'), email, password, div([ button('.createUser', {class: classes(styles.submit), props: {type: 'button', disabled: state.submitting}}, [ (state.submitting ? i({class: classes(styles.fa, styles.faSpinner, styles.faSpin)}) : i({class: classes(styles.fa, styles.faUserPlus)}) ), ' ', state.submitting ? 'Creating...' : 'Create' ]) ]) ]) ]) } return { DOM: xs.combine(state$, emailField.DOM, passwordField.DOM).map(render), HTTP: xs.combine(actions.submit$, state$.take(1)).map(([action, state]) => ({ url: '/api/users', category: 'user', method: 'POST', type: 'application/x-www-form-urlencoded', send: state.data })), created$: actions.responses$, alerts$: actions.responses$.map(action => { let alert = { title: action.effect.title || '', text: action.effect.text } alert.className = (action.effect.success) ? 'alert-success' : 'alert-danger' return alert }) } }
function BmiCalculator({DOM}) { let WeightSlider = isolate(LabeledSlider); let HeightSlider = isolate(LabeledSlider); let weightProps$ = xs.of({ label: 'Weight', unit: 'kg', min: 40, initial: 70, max: 140 }); let heightProps$ = xs.of({ label: 'Height', unit: 'cm', min: 140, initial: 170, max: 210 }); let weightSlider = WeightSlider({DOM, props$: weightProps$}); let heightSlider = HeightSlider({DOM, props$: heightProps$}); let bmi$ = xs.combine(weightSlider.value$, heightSlider.value$) .map(([weight, height]) => { let heightMeters = height * 0.01; let bmi = Math.round(weight / (heightMeters * heightMeters)); return bmi; }); return { DOM: xs.combine(bmi$, weightSlider.DOM, heightSlider.DOM) .map(([bmi, weightVTree, heightVTree]) => div([ weightVTree, heightVTree, h2('BMI is ' + bmi) ]) ) }; }
function Task ({DOM, props}) { const delete$ = DOM .select('.delete') .events('click'); const changeText = DOM.select('.change-text'); const changeText$ = xs.merge( changeText.events('keydown') .filter(event => event.code === 'Enter'), changeText.events('blur') ).map(event => event.target.value); const editing$ = xs.merge( DOM.select('.text').events('click').mapTo(true), changeText$.compose(delay()).mapTo(false) ).startWith(false); const edit$ = editing$.map(editing => changeText$.filter(() => editing)).flatten(); return { DOM: xs.combine(props, editing$).map(taskView), complete$: props.map(({status}) => status === 'complete'), delete$, edit$, HTTP: props.map(({id}) => ({ url: `/tasks/${id}` })) }; }
const layout = ({DOM, view}) => { let externalLinkClick$ = DOM.select('a[rel="external"]').events('click').map(ev => { ev.target.target = '_blank'; return null }).startWith(null), render = ([content]) => { return div([ header([ h1('HapiCycle') ]), div({class: classes(styles.page)}, [ content ]), footer({class: classes(styles.container)}, [ small([ '© ' + new Date().getFullYear() + ' YourApp ', span({class: classes(styles.pullRight)}, `v${VERSION}-${BRANCH} (${ENV})`) ]) ]) ]) }, component = Object.assign({}, view, { DOM: xs.combine(view.DOM, externalLinkClick$).map(render), HTTP: view.HTTP || xs.of(null), Router: DOM.select('a').events('click').filter(ev => ev.target.attributes.href.textContent.match(/^\/.*/)).map(ev => { ev.preventDefault() return ev.target.attributes.href.value }) }) return component };
function Task ({DOM, props, deleteComplete$, filter$}) { const deleteClick$ = DOM .select('.delete') .events('click'); const deleteIfComplete$ = props .map(({status}) => deleteComplete$.filter(() => status === 'complete')) .flatten(); const delete$ = xs.merge(deleteClick$, deleteIfComplete$); const viewUnlessFiltered = ([props, filter]) => taskView({ ...props, visible: filter(props.status) }); return { DOM: xs.combine(props, filter$).map(viewUnlessFiltered), complete$: props.map(({status}) => status === 'complete').compose(dropRepeats()), HTTP: props .map(({id}) => delete$.mapTo({ url: `/tasks/${id}`, method: 'DELETE', type: 'application/json' })) .flatten() }; }
function main(sources) { const proxyAction$ = xs.create(); const state$ = model(proxyAction$); const freeGuests = GuestClicker({ ...sources, props$ : state$.map(({ free, available }) => ({ title : free.title, available })) }); const payingGuests = GuestClicker({ ...sources, props$ : state$.map(({ paying, available }) => ({ title : paying.title, available })) }); const action$ = xs.merge( freeGuests.data$.map(({ count }) => ({ type : 'FREE', payload : count })), payingGuests.data$.map(({ count }) => ({ type : 'PAYING', payload : count }))); proxyAction$.imitate(action$); const vtree$ = xs.combine(state$, freeGuests.DOM, payingGuests.DOM) .compose(debounce(1)) .map(([ state, free, paying ]) => div([ free, paying, pre(JSON.stringify(state, null, 4)) ])) .remember(); return { DOM: vtree$ } }
function view(clicker1, clicker2) { const vClicker = clicker => xs.combine(clicker.data$, clicker.DOM) .map(([ data, dom]) => div({ style : { display : 'table-cell', textAlign : 'center' }}, [ h1(`${data.count}`), dom ])) .remember(); const sum$ = xs.combine(clicker1.data$, clicker2.data$) .map(([ data1, data2 ]) => h1(`= ${ data1.count + data2.count } `)); const fnVtree = () => div([ div({ style : { display : 'table'}}, [ vClicker(clicker1), h1(" + "), vClicker(clicker2), div({ style : { display : 'table-cell'}}, [ sum$ ]) ]) ]); return xs.of(fnVtree()); }
const vClicker = clicker => xs.combine(clicker.data$, clicker.DOM) .map(([ data, dom]) => div({ style : { display : 'table-cell', textAlign : 'center' }}, [ h1(`${data.count}`), dom ])) .remember();
const view = (state$,confirmvtree$)=> xs.combine(state$,confirmvtree$).map(([state,confirmvtree])=> div('.child',[ input('.field', {props:{value: state.input}}), confirmvtree, p('Submitted value: '+state.submission) ]) )
return function wrappedAppFn(sources) { let vtree$ = appFn(sources).DOM; let wrappedVTree$ = xs.combine(wrapVTreeWithHTMLBoilerplate, vtree$, context$, bundle$ ).take(1); return { DOM: wrappedVTree$ }; };
function view(props$, value$) { return xs.combine(props$, value$).map(([props, value]) => div('.labeled-slider', [ span('.label', `${props.label} ${value}${props.unit}`), input('.slider', { attrs: {type: 'range', min: props.min, max: props.max, value} }) ]) ); };
return function app(sources) { const vdom$ = app_fn(sources).DOM; const wrappedVDOM$ = xs .combine(vdom$, context$) .map(virtual_html) .last(); return { DOM: wrappedVDOM$ }; };
export default sources => { const weightProps$ = xs.of({ label: 'Weight', unit: 'kg', min: 40, max: 150, value: 70 }); const heightProps$ = xs.of({ label: 'Height', unit: 'cm', min: 140, max: 210, value: 170 }); const weightSources = {DOM: sources.DOM, props: weightProps$}; const heightSources = {DOM: sources.DOM, props: heightProps$}; const weightSlider = isolate(LabeledSlider)(weightSources); const heightSlider = isolate(LabeledSlider)(heightSources); const weightVDom$ = weightSlider.DOM; const weightValue$ = weightSlider.value; const heightVDom$ = heightSlider.DOM; const heightValue$ = heightSlider.value; const bmi$ = xs.combine(weightValue$, heightValue$) .map(([weight, height]) => { const heightMeters = height * 0.01; const bmi = Math.round(weight / (heightMeters * heightMeters)); return bmi; }) .remember(); const vdom$ = xs.combine(bmi$, weightVDom$, heightVDom$) .map(([bmi, weightVDom, heightVDom]) => div([ weightVDom, heightVDom, h2('BMI is ' + bmi) ]) ); const sinks = {DOM: vdom$}; return sinks; };
function view(clickerVtrees$, sum$) { return xs .combine(clickerVtrees$, sum$) .map(([vtrees, sum]) => ( div([ ...vtrees, div(`Sum: ${sum}`), button('.reset.button', 'RESET') ]) ) ) }
function view(state$, clicker1, clicker2) { const vtree$ = xs.combine(state$, clicker1.DOM, clicker2.DOM) .map(([ state, clicker1, clicker2 ]) => div([ clicker1, clicker2, button('.reset.button', 'RESET'), spy(state) ])) .remember(); return vtree$; }
export default function view(state$, sources) { let intro$ = Intro(sources).DOM; let navigation$ = Navigation(sources).DOM; let footer$ = Footer().DOM; let ga$ = GA().DOM; let header$ = xs.combine(intro$, navigation$) .map(([intro, navigation]) => { return div('.page-header.flexcontainer.column', [ intro, navigation ]) }) let body$ = state$.map(c => c.DOM).flatten() let wholeVtree$ = xs.combine( header$, navigation$, body$, footer$, ga$) .map(([head, nav, body, foot, ga]) => { return div('.flexcontainer.column', [ head, body, foot, ga ]) }) return wholeVtree$ }
function main(sources) { const proxy$ = xs.create(); const input = Input({ ...sources, props$ : proxy$ }); const capitalize = Capitalize({ ...sources, props$ : input.action$ }); const vdom$ = xs.combine(input.DOM, capitalize.DOM) .map(([ input, caps ]) => div([ input, caps ])); proxy$.imitate(capitalize.caps$); return { DOM: vdom$ } }
export default sources => { const nombre$ = sources.DOM.select('.nombre-receta').events('input') .map(ev => ev.target.value) .startWith('Limon Frito'); const getRecipe$ = nombre$.map(nombre => { if (!!nombre) { return { url: `https://recetas-familiares.herokuapp.com/api/recetas/${nombre}`, category: 'recetas', method: 'GET' }; } }) .remember() const recetas$ = sources.HTTP.select('recetas') .flatten() .map(res => res.body) .startWith(null); const vdom$ = xs.combine(recetas$, nombre$).map(([receta, nombre]) => div('.page .receta', [ receta === null ? null : div('.cuerpo-receta', [ h1('.nombre', receta.nombre), hr(), p('.italic', [span(`Del recetario de ${receta.fuente},`), span('.grey-medium', ` agregada el ${StringUtils.formatDate(receta.fecha)}`)]), h2([span('Ingredientes'), span('.italic .large .grey-medium', ` (${receta.porciones} porciones)`)]), ul('.ingredientes', receta.ingredientes.map(ingrediente => li('.ingrediente', ingrediente) )), h2('Pasos'), ol('.pasos', receta.pasos.map(paso => li('.paso', paso) )) ]), div('.seleccionar', [ h3('Buscar otra receta:'), input('.nombre-receta', {attrs: {type: 'text', value: nombre}}) ]), ]) ); return {DOM: vdom$, HTTP: getRecipe$} // sinks };
function view(state$, clicker) { const fnVtree = ([state, clickerData, clickerDOM ]) => div([ div({ style : { display : 'table', padding : '10px', border : '1px solid #eee'}}, [ div({ style : { display : 'table-cell', width : '200px' }}, [ h1(state.title), h5(`${state.available} available`) ]), div({ style : { display : 'table-cell', width : '150px'}}, [ h1({ style : { textAlign : 'center'}}, `${clickerData.count}`), clickerDOM ]) ]) ]); return xs.combine(state$, clicker.data$, clicker.DOM) .map(fnVtree); }
function Thumbnails(sources) { let spread$ = xs.merge( sources.DOM.select('.photosLink').events('mouseenter').mapTo(true), sources.DOM.select('.photosLink').events('mouseleave').mapTo(false) ).startWith(false); let gotoFullscreen$ = sources.DOM.select('.photosLink').events('click').mapTo(); let containerStyle = { 'text-align': 'center', 'position': 'relative', 'width': '30vw', 'height': '30vw', 'margin': '0 auto', }; let state$ = xs.combine(sources.props, spread$).map(([props, spread]) => { return {props, spread}; }); let vdom$ = state$.map(({props, spread}) => div('.thumbnails', {style: containerStyle}, props.thumbnails.map((photoSrc, i) => img({ class: { [thumbnailStyles[i]]: true, spread: i !== props.thumbnails.length - 1 && spread, }, attrs: {src: photoSrc} }) ).concat( span(`.${photosLinkStyle}.photosLink`, props.label) ) ) ); return { DOM: vdom$, gotoFullscreen: gotoFullscreen$, }; }
export default function TodoList ({DOM}) { const removeComplete$ = DOM .select('.remove-complete') .events('click'); const todos = Collection(Todo, {DOM, removeComplete$}, { remove$ (todos, todo) { return todos.remove(todo); } }); const addTodoClick$ = DOM .select('.add-todo') .events('click'); const newTodoText$ = DOM .select('.new-todo-text') .events('change') .map(event => event.target.value) .startWith(''); const addTodo$ = newTodoText$ .map(text => addTodoClick$.mapTo((state) => addTodoReducer(state, text))) .flatten(); const reducer$ = xs.merge( addTodo$, todos.reducers ); const todos$ = reducer$ .fold((todos, reducer) => reducer(todos), todos); const todoVtrees$ = Collection.pluck(todos$, 'DOM'); const todosComplete$ = Collection.pluck(todos$, 'complete$'); return { DOM: xs.combine(view, todoVtrees$, todosComplete$) }; }
function model(props$, action$) { // THE SANITIZED PROPERTIES // If the list item has no data set it as empty and not completed. let sanitizedProps$ = props$.startWith({title: '', completed: false}); // THE EDITING STREAM // Create a stream that emits booleans that represent the // "is editing" state. let editing$ = xs.merge( action$.filter(a => a.type === 'startEdit').mapTo(true), action$.filter(a => a.type === 'doneEdit').mapTo(false), action$.filter(a => a.type === 'cancelEdit').mapTo(false) ) .startWith(false); return xs.combine(sanitizedProps$, editing$) .map(([{title, completed}, editing]) => ({ title, isCompleted: completed, isEditing: editing, })); }
.map(({ filter: currentFilter }) => xs.combine( filterLink({ props$: xs.of({ label: 'all', filter: constants.FILTER_ALL, currentFilter, }), }).DOM, filterLink({ props$: xs.of({ label: 'pending', filter: constants.FILTER_PENDING, currentFilter, }), }).DOM, filterLink({ props$: xs.of({ label: 'completed', filter: constants.FILTER_COMPLETED, currentFilter, }), }).DOM ))
function main(sources) { const headerComponent = Header(); const footerComponent = Footer(); const player$ = sources.player.getState(); const tabsComponent = Tabs({ DOM: sources.DOM, player$, data: sources.data }); const tracks$ = tabsComponent.tracks$; const controlsComponent = Controls({ DOM: sources.DOM, player$, tracks$, play: sources.player.play }); const sinks = { DOM: xs .combine( controlsComponent.DOM, headerComponent.DOM, footerComponent.DOM, tabsComponent.DOM ) .map(([controlsDOM, headerDOM, footerDOM, tabsDOM]) => { return div(".root", [ headerDOM, div(".mui-container", [controlsDOM, tabsDOM]), footerDOM ]); }), player: xs.merge(tabsComponent.play, controlsComponent.play) }; return sinks; }
.map(sinkStreams => xs.combine((...items) => items, ...sinkStreams))
function GraphSerializer(sources) { let zapQueue = []; let zap$ = xs.create(); let dsl$ = sources.DebugSinks .map(sinks => { const streamGraph = new StreamGraph(); for (let key in sinks) { if (sinks.hasOwnProperty(key)) { const node = streamGraph.registerNode(sinks[key]); queueForZapping(node, zapQueue); traverse(streamGraph, sinks[key], zapQueue); } } return streamGraph; }) .debug(() => { setupZapping(zapQueue, zap$); }) .map(graph => graph.edges.map(e => { const from = e.from.id; const to = e.to.id; const label = e.label ? `|${e.label}|` : ''; return ` ${from}(_)-->${label}${to}(_);` }).join('\n') ); let rawVisualZap$ = zap$ .map(zap => xs.of(zap).compose(delay(80))) .compose(flattenSequentially()) .startWith(null); let resetVisualZap$ = rawVisualZap$.compose(debounce(120)).mapTo(null); let visualZap$ = xs.merge(rawVisualZap$, resetVisualZap$); let finalDSL$ = xs.combine( (dsl, zap) => { const NEXT_STYLE = 'fill: #6DFFB3, stroke: #00C194, stroke-width:3px;'; const ERROR_STYLE = 'fill: #FFA382, stroke: #F53800, stroke-width:3px;'; const COMPLETE_STYLE = 'fill: #B5B5B5, stroke: #7D7D7D, stroke-width:3px;'; let zapStyle = ''; if (zap) { if (zap.type === 'next') { zapStyle = `\n style ${zap.id} ${NEXT_STYLE}`; } else if (zap.type === 'error') { zapStyle = `\n style ${zap.id} ${ERROR_STYLE}`; } else if (zap.type === 'complete') { zapStyle = `\n style ${zap.id} ${COMPLETE_STYLE}`; } } const dslWithZapStyle = dsl + zapStyle; if (zap && zap.value) { return dsl.replace( new RegExp(`${zap.id}\\(_\\)`, 'g'), `${zap.id}("${typeof zap.value === 'object' ? '_' : zap.value}")` ) + zapStyle; } else { return dsl + zapStyle; } }, dsl$, visualZap$ ); let sinks = { Mermaid: finalDSL$ } return sinks; }
export default { processResponse(HTTP) { return HTTP .select(CATEGORY) .flatten() .map(res => { return { station: JSON.parse(res.text) } }) }, getRequestURL(line$) { const interval$ = xs.periodic(INTERVAL_TIME).startWith(0); const lineURL$ = line$ .filter(l => l.value !== undefined) .startWith({ value: 'COMM' }) .map(l => ({ url: `${BASE_URL}/${l.value}`, method: 'GET', category: CATEGORY, headers: { "Content-Type": "application/json", "Accept-language": "fr_FR" }, })); const final$ = xs.combine(lineURL$, interval$).map(([line, int]) => line); return final$ } }
function main({DOM, Video, Render, Plugin}) { const play_ = DOM .select('#play') .events('click') .mapTo({type: PLAY}); const pause_ = DOM .select('#pause') .events('click') .mapTo({type: PAUSE}); const timebar_ = DOM .select('#timebar') .events('click') .map( ({clientX, target}) => ({type: SEEK, percent: (clientX - target.offsetLeft) / target.offsetWidth}) ) ; const videoLinks_ = DOM.select('.vlink').events('click').map( x => ({type: 'videolink', vref: x.target.vref, play: x.target.play, time: x.target.time}) ); const video_ = Video.events_; const nav_ = videoLinks_ .compose(navigator({ startLink: config.startLink, autoplay: true })); const videoUpdate_ = xs.merge( nav_, play_, pause_, timebar_ ); const plugins_ = xs .merge( Video.events_ .filter( x => x.type == 'update' ) .map( ({type, source}) => ({type, time: source.currentTime, length: source.duration}) ), nav_ ) .compose(pluginManager(config.plugins)); return { Navigator: videoLinks_, Render: Video.events_ .filter( x => x.type == 'update') .map( ({source}) => ({source, width: source.videoWidth, height: source.videoHeight})), Video: videoUpdate_, DOM: xs .combine( video_.startWith({}), videoUpdate_.startWith(0), plugins_.map(prop('currentPlugins')) ) .map( ([{source}, x, plugins]) => div(`.${s.player}`, [ div('.controls', [ Video.getState().playing ? button('#pause', 'Pause') : button('#play', 'Play'), button('.vlink', { props: {vref: 'video_0001', play: false}}, 'Video #1 and pause'), button('.vlink', { props: {vref: 'video_0002', play: true}}, 'Video #2 and play'), div(`#timebar.${s.timeBar}`, [ div(`.${s.progress}`, {style: {transform: `scaleX(${source ? (source.currentTime / source.duration) : 0})`}}) ]) ]), div(`.${s.canvasContainer}`, [ div(`#plugins.${s.plugins}`, map( p => p.view({DOM}), plugins) ), canvas(`#render-canvas.${s.renderCanvas}`, { props: {width: 640 }, hook:{ insert: pipe(prop('elm'), Render.setCanvas), remove: Render.unsetCanvas } }) ]) ]) ) }; }
function main ({Time, DOM, Mouse}) { const initialState = { nodes, links, dragging: null, addingLinkFrom: null }; const mousePosition$ = Mouse.positions(); const dblClick$ = DOM .select('svg') .events('dblclick'); const backgroundDblClick$ = dblClick$ .filter(ev => ev.target.tagName === 'svg'); const nodeDblClick$ = dblClick$ .filter(ev => ev.target.tagName === 'circle'); const placeNode$ = mousePosition$ .map(position => backgroundDblClick$.mapTo((state) => placeNode(position, state))) .flatten(); const startAddingLink$ = nodeDblClick$ .map(ev => (state) => startAddingLink(ev.target, state)); const startDragging$ = DOM .select('circle') .events('mousedown') .map(ev => (state) => startDragging(ev.target, state)); const stopDragging$ = Mouse .ups() .map(ev => stopDragging); const drag$ = mousePosition$ .map(position => (state) => drag(position, state)); const update$ = Time .map(({delta}) => delta / (1000 / 60)) .filter(delta => delta > 0 && delta < 10) // if you switch tab, you get huge deltas .map(delta => (state) => update(delta, state)); const reducer$ = xs.merge( update$, startDragging$, stopDragging$, drag$, placeNode$, startAddingLink$ ); const state$ = reducer$.fold(applyReducer, initialState); const hoverNode$ = DOM .select('circle') .events('mouseover') .map(ev => ev.target.attributes.key.value) .startWith(''); return { DOM: xs.combine(state$, mousePosition$, hoverNode$).map(view) }; }
export default function Tabs(sources) { const eq$ = sources.DOM.select(".tab-eq").events("click"); const songs$ = sources.DOM.select(".tab-songs").events("click"); const state$ = xs .merge(eq$.map(_ => "equalizer"), songs$.map(_ => "songs")) .startWith("equalizer"); const container$ = sources.DOM.select( `.${styles.spectrogramInnerContainer}` ).elements(); const searchComponent = Search(sources); const tracks$ = sources.data.getTracks( searchComponent.value.map(x => ({ q: x })) ); const tracksComponent = Tracks({ DOM: sources.DOM, tracks: tracks$, player: sources.player$ }); const spectrogramComponent = Spectrogram({ DOM: sources.DOM, state$: sources.player$, container$ }); const equalizerComponent = Equalizer({ DOM: sources.DOM, player$: sources.player$ }); const vtree$ = xs .combine( sources.player$, state$, searchComponent.value, searchComponent.DOM, tracksComponent.DOM, spectrogramComponent.DOM, equalizerComponent.DOM ) .map( ( [ player, state, value, searchDOM, tracksDOM, spectrogramDOM, equalizerDOM ] ) => { const tabs = player.playing ? div(`.${styles.tabsContainer}`, [ ul(`.mui-tabs__bar.${styles.tabs}`, [ li( `.tab-eq.${state === "equalizer" ? "mui--is-active" : ""}.${ styles.tab }`, ["Equalizer"] ), li( `.tab-songs.${state === "songs" ? "mui--is-active" : ""}.${ styles.tab }`, ["Playlist"] ) ]) ]) : null; let content; if (state === "equalizer" && player.playing) { content = div(".my-content", [ div(`.${styles.dataContainer}`, [ div(`.${styles.spectrogramContainer}`, [ div(`.${styles.spectrogramInnerContainer}`, [spectrogramDOM]) ]), div(`.${styles.equalizerContainer}`, [equalizerDOM]) ]) ]); } if (state === "songs" || player.playing === false) { const titleMarkup = value ? h1(".mui--text-center", ["Results for: " + value]) : h3(".mui--text-dark-secondary.mui--text-center", [ "Random songs" ]); content = div(".my-content", [titleMarkup, searchDOM, tracksDOM]); } return div([tabs, content]); } ); const sinks = { DOM: vtree$, tracks$, play: xs.merge(tracksComponent.play, equalizerComponent.gain$) }; return sinks; }