Пример #1
0
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
		})
	}
}
Пример #2
0
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)
        ])
      )
  };
}
Пример #3
0
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}`
    }))
  };
}
Пример #4
0
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
};
Пример #5
0
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()
  };
}
Пример #6
0
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$

    }
}
Пример #7
0
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());

}
Пример #8
0
 const vClicker      = clicker => xs.combine(clicker.data$, clicker.DOM)
     .map(([ data, dom]) => div({ style : {
         display : 'table-cell',
         textAlign : 'center' }}, [
         h1(`${data.count}`),
         dom
     ]))
     .remember();
Пример #9
0
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)
    ])
  )
Пример #10
0
 return function wrappedAppFn(sources) {
   let vtree$ = appFn(sources).DOM;
   let wrappedVTree$ = xs.combine(wrapVTreeWithHTMLBoilerplate,
     vtree$, context$, bundle$
   ).take(1);
   return {
     DOM: wrappedVTree$
   };
 };
Пример #11
0
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}
      })
    ])
  );
};
Пример #12
0
            return function app(sources) {
                const vdom$ = app_fn(sources).DOM;
                const wrappedVDOM$ = xs
                    .combine(vdom$, context$)
                    .map(virtual_html)
                    .last();

                return {
                    DOM: wrappedVDOM$
                };
            };
Пример #13
0
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;
};
Пример #14
0
function view(clickerVtrees$, sum$) {
    return xs
        .combine(clickerVtrees$, sum$)
        .map(([vtrees, sum]) => (
                div([
                    ...vtrees,
                    div(`Sum: ${sum}`),
                    button('.reset.button', 'RESET')
                ])
            )
        )
}
Пример #15
0
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$;

}
Пример #16
0
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$
}
Пример #17
0
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$
    }
}
Пример #18
0
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
};
Пример #19
0
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);

}
Пример #20
0
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$,
  };
}
Пример #21
0
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$)
  };
}
Пример #22
0
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,
    }));
}
Пример #23
0
 .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
 ))
Пример #24
0
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;
}
Пример #25
0
 .map(sinkStreams => xs.combine((...items) => items, ...sinkStreams))
Пример #26
0
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;
}
Пример #27
0
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$
  }
}
Пример #28
0
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
							}
						})
					])
				])
			)
	};
}
Пример #29
0
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)
  };
}
Пример #30
0
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;
}