<commit_before>const { remote } = require('electron');
const React = require('react');
const _ = require('lodash');

const Utils = require('../utils');

let isPlaylistPlaying = false;

const Player = React.createClass({
  getInitialState() {
    return {
      playlist: [],
      player: null,
      isReplayingVideo: false,
    };
  },
  onStateChange(event) {
    if (event.data === YT.PlayerState.UNSTARTED) return;
    if (event.data === YT.PlayerState.CUED) return;

    if (event.data === YT.PlayerState.ENDED) {
      if (this.state.isReplayingVideo)
        this.state.player.loadVideoById(this.state.playlist[0]);
      else if (this.isVideoEnded()) {
        window.dispatchEvent(new CustomEvent('playlist.endVideo', { detail: { video: this.state.playlist[0] } }));

        this.removeVideo();
      }
    } else {
      isPlaylistPlaying = true;
    }
  },
  removeVideo() {
    // No more video to play
    // (1 because we will remove the played video)
    if (this.state.playlist.length === 1)
      isPlaylistPlaying = false;

    // Remove last played video
    window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.state.playlist[0] } }));
  },
  updatePlaylist(forceNextVideo) {
    // ToDo gérer anciennes vidéos
    if (this.state.playlist.length === 0) return;

    const statesThatNeedCue = [
      YT.PlayerState.CUED,
    ];

    const playerState = this.state.player.getPlayerState();

    if (statesThatNeedCue.indexOf(playerState) > -1) {
      this.state.player.cueVideoById(this.state.playlist[0]);
    } else if (forceNextVideo) {
      this.state.player.loadVideoById(this.state.playlist[0]);
    }
  },
  componentWillReceiveProps(nextProps) {
    let updatePlaylist;
    if (this.state.playlist[0] === nextProps.playlist[0]) {
      updatePlaylist = this.updatePlaylist.bind(null, false);
    } else {
      updatePlaylist = this.updatePlaylist.bind(null, true);
    }

    this.setState({
      playlist: nextProps.playlist,
    }, updatePlaylist);
  },
  isFullScreen() {
    return Boolean(document.querySelector('#player:-webkit-full-screen'));
  },
  onWebkitFullScreenChange() {
    const isFullScreen = this.isFullScreen();

    Utils.Socket.send('player/floatontop', isFullScreen);

    if (isFullScreen) {
      document.body.classList.add('fullscreen');
      this.state.currentWindow.setMinimumSize(160, 90);
    } else {
      document.body.classList.remove('fullscreen');
      this.state.currentWindow.setMinimumSize(880, 370);
    }

    this.state.currentWindow.setAlwaysOnTop(isFullScreen);
    this.state.currentWindow.setHasShadow(!isFullScreen);
    this.state.currentWindow.setVisibleOnAllWorkspaces(isFullScreen);
  },
  componentDidMount() {
    window.addEventListener('player.playNextVideo', this.playNextVideo);
    window.addEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.addEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);

    Utils.Socket.on('number-of-display/update', this.updateMode);

    document.addEventListener('webkitfullscreenchange', this.onWebkitFullScreenChange);

    this.setState({
      currentWindow: remote.getCurrentWindow(),
      playlist: this.state.playlist,
      isReplayingVideo: this.state.isReplayingVideo,

      // YT may not be loaded at this time, need to find a solution...
      // That's probably why I can't put this in a getInitialState method
      player: new YT.Player('player', {
        width: 480,
        height: 270,
        events: {
          onStateChange: this.onStateChange,
        },
      }),
    }, this.updateMode);
  },
  componentWillUnmount() {
    document.removeEventListener('webkitfullscreenchange', this.onWebkitFullScreenChange);

    window.removeEventListener('player.playNextVideo', this.playNextVideo);
    window.removeEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.removeEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);

    Utils.Socket.removeAllListeners('number-of-display/update');
  },
  playNextVideo() {
    this.removeVideo();
  },
  replayCurrentVideo() {
    this.setState({
      isReplayingVideo: true,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  stopReplayCurrentVideo() {
    this.setState({
      isReplayingVideo: false,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  isVideoEnded() {
    return this.state.player.getCurrentTime() === this.state.player.getDuration();
  },
  updateMode() {
    this.state.currentWindow.setFullScreenable(!Utils.getMode());
  },
  render() {
    return
;
  },
});

const PlaylistItem = React.createClass({
  propTypes: {
    id: React.PropTypes.string,
    title: React.PropTypes.string,
    channel: React.PropTypes.string,
  },
  raise() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.raiseVideo', { detail: { video: this.props } }));
    }
  },
  remove() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.props } }));
    }
  },
  render() {
    return (

            ×

          {this.props.title}

          {this.props.channel}

    );
  },
});

const Playlist = React.createClass({
  propTypes: {
    videos: React.PropTypes.array,
  },
  render() {
    const videos = [];
    for (const video of this.props.videos) {
      videos.push(
        <PlaylistItem
          key={video.id}
          id={video.id}
          thumbnail={video.thumbnail}
          title={video.title}
          channel={video.channel}
          />
        );
    }

    return (

        {videos}

    );
  },
});

const Controls = React.createClass({
  propTypes: {
    numberOfPlayedVideos: React.PropTypes.number,
    numberOfVideos: React.PropTypes.number,
  },
  getInitialState() {
    return { isReplayingVideo: false };
  },
  replayCurrentVideo() {
    this.setState({ isReplayingVideo: true });

    window.dispatchEvent(new CustomEvent('player.replayCurrentVideo'));
  },
  stopReplayCurrentVideo() {
    this.setState({ isReplayingVideo: false });

    window.dispatchEvent(new CustomEvent('player.stopReplayCurrentVideo'));
  },
  isPreviousVideoDisabled() {
    return this.props.numberOfPlayedVideos === 0;
  },
  playPreviousVideo() {
    window.dispatchEvent(new CustomEvent('playlist.playPreviousVideo'));
  },
  isNextVideoDisabled() {
    return this.props.numberOfVideos <= 1;
  },
  playNextVideo() {
    window.dispatchEvent(new CustomEvent('player.playNextVideo'));
  },
  togglePlaylistVisibility() {
    window.dispatchEvent(new CustomEvent('playlist.togglePlaylistVisibility'));
  },
  render() {
    let replayVideoButton;
    if (this.state.isReplayingVideo) {
      replayVideoButton = <button className="btn" onClick={this.stopReplayCurrentVideo}></button>;
    } else {
      replayVideoButton = <button className="btn" onClick={this.replayCurrentVideo}></button>;
    }

    return (

        <button className="btn" onClick={this.playPreviousVideo} disabled={this.isPreviousVideoDisabled()}></button>
        {replayVideoButton}
        <button className="btn" onClick={this.playNextVideo} disabled={this.isNextVideoDisabled()}></button>
        <button className="btn" onClick={this.togglePlaylistVisibility}></button>

    );
  },
});

const CurrentPlaylist = React.createClass({
  getInitialState() {
    return {
      videos: [],
      playedVideos: [],
      playlistVisible: false,
    };
  },
  componentDidMount() {
    // ToDo retrieve playlist from backend

    window.addEventListener('playlist.addVideo', this.addVideo);
    window.addEventListener('playlist.cueVideo', this.cueVideo);
    window.addEventListener('playlist.endVideo', this.endVideo);
    window.addEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.addEventListener('playlist.removeVideo', this.removeVideo);
    window.addEventListener('playlist.raiseVideo', this.raiseVideo);
    window.addEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  componentWillUnmount() {
    window.removeEventListener('playlist.addVideo', this.addVideo);
    window.removeEventListener('playlist.cueVideo', this.cueVideo);
    window.removeEventListener('playlist.endVideo', this.endVideo);
    window.removeEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.removeEventListener('playlist.removeVideo', this.removeVideo);
    window.removeEventListener('playlist.raiseVideo', this.raiseVideo);
    window.removeEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  addVideo(event) {
    // Add the video in first position if no video playing
    // Else add it in second position

    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  cueVideo(event) {
    // Add the video in last position
    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos.push(video);

      return state;
    });
  },
  endVideo(event) {
    const video = this.hydrateVideoFromPlaylist(event.detail.video);

    this.setState((state) => {
      state.playedVideos.push(video);

      return state;
    });
  },
  playPreviousVideo() {
    this.setState((state) => {
      const latestVideo = _.last(state.playedVideos);

      state.playedVideos = _.reject(state.playedVideos, latestVideo);
      state.videos.splice(0, 0, latestVideo);

      return state;
    });
  },
  removeVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);

      return state;
    });
  },
  raiseVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  normalizeVideo(video) {
    if (_.isObject(video))
      return video;

    return { id: video };
  },
  hydrateVideoFromPlaylist(videoId) {
    return this.state.videos[_.findIndex(this.state.videos, { id: videoId })];
  },
  isInPlaylist(video) {
    return _.some(this.state.videos, video);
  },
  togglePlaylistVisibility() {
    this.setState({
      videos: this.state.videos,
      playedVideos: this.state.playedVideos,
      playlistVisible: document.querySelector('#playlist').classList.toggle('visible'),
    });
  },
  render() {
    return (

        <Playlist videos={this.state.videos} />
        <Controls numberOfPlayedVideos={this.state.playedVideos.length} numberOfVideos={this.state.videos.length} />
        <Player playlist={_.map(this.state.videos, 'id')} />

    );
  },
});

module.exports = CurrentPlaylist;
<commit_msg>Use new utils methods<commit_after>const { remote } = require('electron');
const React = require('react');
const _ = require('lodash');

const Utils = require('../utils');

let isPlaylistPlaying = false;

const Player = React.createClass({
  getInitialState() {
    return {
      playlist: [],
      player: null,
      isReplayingVideo: false,
    };
  },
  onStateChange(event) {
    if (event.data === YT.PlayerState.UNSTARTED) return;
    if (event.data === YT.PlayerState.CUED) return;

    if (event.data === YT.PlayerState.ENDED) {
      if (this.state.isReplayingVideo)
        this.state.player.loadVideoById(this.state.playlist[0]);
      else if (this.isVideoEnded()) {
        window.dispatchEvent(new CustomEvent('playlist.endVideo', { detail: { video: this.state.playlist[0] } }));

        this.removeVideo();
      }
    } else {
      isPlaylistPlaying = true;
    }
  },
  removeVideo() {
    // No more video to play
    // (1 because we will remove the played video)
    if (this.state.playlist.length === 1)
      isPlaylistPlaying = false;

    // Remove last played video
    window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.state.playlist[0] } }));
  },
  updatePlaylist(forceNextVideo) {
    // ToDo gérer anciennes vidéos
    if (this.state.playlist.length === 0) return;

    const statesThatNeedCue = [
      YT.PlayerState.CUED,
    ];

    const playerState = this.state.player.getPlayerState();

    if (statesThatNeedCue.indexOf(playerState) > -1) {
      this.state.player.cueVideoById(this.state.playlist[0]);
    } else if (forceNextVideo) {
      this.state.player.loadVideoById(this.state.playlist[0]);
    }
  },
  componentWillReceiveProps(nextProps) {
    let updatePlaylist;
    if (this.state.playlist[0] === nextProps.playlist[0]) {
      updatePlaylist = this.updatePlaylist.bind(null, false);
    } else {
      updatePlaylist = this.updatePlaylist.bind(null, true);
    }

    this.setState({
      playlist: nextProps.playlist,
    }, updatePlaylist);
  },
  isFullScreen() {
    return Boolean(document.querySelector('#player:-webkit-full-screen'));
  },
  onWebkitFullScreenChange() {
    const isFullScreen = this.isFullScreen();

    Utils.Socket.send('player/floatontop', isFullScreen);

    if (isFullScreen) {
      document.body.classList.add('fullscreen');
      this.state.currentWindow.setMinimumSize(160, 90);
    } else {
      document.body.classList.remove('fullscreen');
      this.state.currentWindow.setMinimumSize(880, 370);
    }

    this.state.currentWindow.setAlwaysOnTop(isFullScreen);
    this.state.currentWindow.setHasShadow(!isFullScreen);
    this.state.currentWindow.setVisibleOnAllWorkspaces(isFullScreen);
  },
  componentDidMount() {
    window.addEventListener('player.playNextVideo', this.playNextVideo);
    window.addEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.addEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);

    Utils.screen.on('display-added', this.updatePreferredMode);
    Utils.screen.on('display-removed', this.updatePreferredMode);

    document.addEventListener('webkitfullscreenchange', this.onWebkitFullScreenChange);

    this.setState({
      currentWindow: remote.getCurrentWindow(),
      playlist: this.state.playlist,
      isReplayingVideo: this.state.isReplayingVideo,

      // YT may not be loaded at this time, need to find a solution...
      // That's probably why I can't put this in a getInitialState method
      player: new YT.Player('player', {
        width: 480,
        height: 270,
        events: {
          onStateChange: this.onStateChange,
        },
      }),
    }, this.updatePreferredMode);
  },
  componentWillUnmount() {
    document.removeEventListener('webkitfullscreenchange', this.onWebkitFullScreenChange);

    window.removeEventListener('player.playNextVideo', this.playNextVideo);
    window.removeEventListener('player.replayCurrentVideo', this.replayCurrentVideo);
    window.removeEventListener('player.stopReplayCurrentVideo', this.stopReplayCurrentVideo);

    Utils.screen.removeAllListeners('display-added');
    Utils.screen.removeAllListeners('display-removed');
  },
  playNextVideo() {
    this.removeVideo();
  },
  replayCurrentVideo() {
    this.setState({
      isReplayingVideo: true,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  stopReplayCurrentVideo() {
    this.setState({
      isReplayingVideo: false,
      playlist: this.state.playlist,
      player: this.state.player,
    });
  },
  isVideoEnded() {
    return this.state.player.getCurrentTime() === this.state.player.getDuration();
  },
  updatePreferredMode() {
    this.state.currentWindow.setFullScreenable(Utils.isFullScreenPreferredMode());
  },
  render() {
    return
;
  },
});

const PlaylistItem = React.createClass({
  propTypes: {
    id: React.PropTypes.string,
    title: React.PropTypes.string,
    channel: React.PropTypes.string,
  },
  raise() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.raiseVideo', { detail: { video: this.props } }));
    }
  },
  remove() {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.props } }));
    }
  },
  render() {
    return (

            ×

          {this.props.title}

          {this.props.channel}

    );
  },
});

const Playlist = React.createClass({
  propTypes: {
    videos: React.PropTypes.array,
  },
  render() {
    const videos = [];
    for (const video of this.props.videos) {
      videos.push(
        <PlaylistItem
          key={video.id}
          id={video.id}
          thumbnail={video.thumbnail}
          title={video.title}
          channel={video.channel}
          />
        );
    }

    return (

        {videos}

    );
  },
});

const Controls = React.createClass({
  propTypes: {
    numberOfPlayedVideos: React.PropTypes.number,
    numberOfVideos: React.PropTypes.number,
  },
  getInitialState() {
    return { isReplayingVideo: false };
  },
  replayCurrentVideo() {
    this.setState({ isReplayingVideo: true });

    window.dispatchEvent(new CustomEvent('player.replayCurrentVideo'));
  },
  stopReplayCurrentVideo() {
    this.setState({ isReplayingVideo: false });

    window.dispatchEvent(new CustomEvent('player.stopReplayCurrentVideo'));
  },
  isPreviousVideoDisabled() {
    return this.props.numberOfPlayedVideos === 0;
  },
  playPreviousVideo() {
    window.dispatchEvent(new CustomEvent('playlist.playPreviousVideo'));
  },
  isNextVideoDisabled() {
    return this.props.numberOfVideos <= 1;
  },
  playNextVideo() {
    window.dispatchEvent(new CustomEvent('player.playNextVideo'));
  },
  togglePlaylistVisibility() {
    window.dispatchEvent(new CustomEvent('playlist.togglePlaylistVisibility'));
  },
  render() {
    let replayVideoButton;
    if (this.state.isReplayingVideo) {
      replayVideoButton = <button className="btn" onClick={this.stopReplayCurrentVideo}></button>;
    } else {
      replayVideoButton = <button className="btn" onClick={this.replayCurrentVideo}></button>;
    }

    return (

        <button className="btn" onClick={this.playPreviousVideo} disabled={this.isPreviousVideoDisabled()}></button>
        {replayVideoButton}
        <button className="btn" onClick={this.playNextVideo} disabled={this.isNextVideoDisabled()}></button>
        <button className="btn" onClick={this.togglePlaylistVisibility}></button>

    );
  },
});

const CurrentPlaylist = React.createClass({
  getInitialState() {
    return {
      videos: [],
      playedVideos: [],
      playlistVisible: false,
    };
  },
  componentDidMount() {
    // ToDo retrieve playlist from backend

    window.addEventListener('playlist.addVideo', this.addVideo);
    window.addEventListener('playlist.cueVideo', this.cueVideo);
    window.addEventListener('playlist.endVideo', this.endVideo);
    window.addEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.addEventListener('playlist.removeVideo', this.removeVideo);
    window.addEventListener('playlist.raiseVideo', this.raiseVideo);
    window.addEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  componentWillUnmount() {
    window.removeEventListener('playlist.addVideo', this.addVideo);
    window.removeEventListener('playlist.cueVideo', this.cueVideo);
    window.removeEventListener('playlist.endVideo', this.endVideo);
    window.removeEventListener('playlist.playPreviousVideo', this.playPreviousVideo);
    window.removeEventListener('playlist.removeVideo', this.removeVideo);
    window.removeEventListener('playlist.raiseVideo', this.raiseVideo);
    window.removeEventListener('playlist.togglePlaylistVisibility', this.togglePlaylistVisibility);
  },
  addVideo(event) {
    // Add the video in first position if no video playing
    // Else add it in second position

    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  cueVideo(event) {
    // Add the video in last position
    const video = this.normalizeVideo(event.detail.video);

    if (this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos.push(video);

      return state;
    });
  },
  endVideo(event) {
    const video = this.hydrateVideoFromPlaylist(event.detail.video);

    this.setState((state) => {
      state.playedVideos.push(video);

      return state;
    });
  },
  playPreviousVideo() {
    this.setState((state) => {
      const latestVideo = _.last(state.playedVideos);

      state.playedVideos = _.reject(state.playedVideos, latestVideo);
      state.videos.splice(0, 0, latestVideo);

      return state;
    });
  },
  removeVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);

      return state;
    });
  },
  raiseVideo(event) {
    const video = this.normalizeVideo(event.detail.video);

    if (!this.isInPlaylist(video))
      return;

    this.setState((state) => {
      state.videos = _.reject(state.videos, video);
      if (isPlaylistPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  normalizeVideo(video) {
    if (_.isObject(video))
      return video;

    return { id: video };
  },
  hydrateVideoFromPlaylist(videoId) {
    return this.state.videos[_.findIndex(this.state.videos, { id: videoId })];
  },
  isInPlaylist(video) {
    return _.some(this.state.videos, video);
  },
  togglePlaylistVisibility() {
    this.setState({
      videos: this.state.videos,
      playedVideos: this.state.playedVideos,
      playlistVisible: document.querySelector('#playlist').classList.toggle('visible'),
    });
  },
  render() {
    return (

        <Playlist videos={this.state.videos} />
        <Controls numberOfPlayedVideos={this.state.playedVideos.length} numberOfVideos={this.state.videos.length} />
        <Player playlist={_.map(this.state.videos, 'id')} />

    );
  },
});

module.exports = CurrentPlaylist;
<|endoftext|>


<commit_before>'use strict'

export function loadIndexJson(l4idxFile, options) {
  // TODO
}

export function saveIndexJson(jsonIndex, filename, options) {
  // TODO
}

async function getFilepaths(options) {
  let query = ".js";
  let fileLimit = 10000;
  let req = `https://api.dropboxapi.com/1/search/auto${options.options.subfolder}?query=${query}&file_limit=${fileLimit}`;

  let responseJson = await fetch(req, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${options.options.token}`,
      "Content-Type": "application/json"
    }
  }).then(resp => { return resp.json(); });

  let filepaths = responseJson.filter(file => {
    // ensure that this is really a js-file
    return file.path.slice(-3) === ".js";
  }).map(file => {
    // remove the subfolder from the file path
    return file.path.slice(options.options.subfolder.length);
  });

  return filepaths.slice(0, 5);
}

export async function* FileReader(filepath, options) {
  let filepaths;
  if (typeof filepath === "object") {
    // no filepath given
    options = filepath;
    filepaths = await getFilepaths(options);
  } else {
    filepaths = [filepath];
  }

  for (let i = 0; i < filepaths.length; i++) {
    var path = filepaths[i];
    var content = await fetch(`https://lively4${options.path}${path}`).then(resp => { return resp.text(); });
    yield {
      path: path,
      filename: path.slice(path.lastIndexOf("/") + 1),
      content: content
    }
  }
}
<commit_msg>add file extension in dropbox content provider<commit_after>'use strict'

export function loadIndexJson(l4idxFile, options) {
  // TODO
}

export function saveIndexJson(jsonIndex, filename, options) {
  // TODO
}

async function getFilepaths(options) {
  let query = ".js";
  let fileLimit = 10000;
  let req = `https://api.dropboxapi.com/1/search/auto${options.options.subfolder}?query=${query}&file_limit=${fileLimit}`;

  let responseJson = await fetch(req, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${options.options.token}`,
      "Content-Type": "application/json"
    }
  }).then(resp => { return resp.json(); });

  let filepaths = responseJson.filter(file => {
    // ensure that this is really a js-file
    return file.path.slice(-3) === ".js";
  }).map(file => {
    // remove the subfolder from the file path
    return file.path.slice(options.options.subfolder.length);
  });

  return filepaths.slice(0, 5);
}

export async function* FileReader(filepath, options) {
  let filepaths;
  if (typeof filepath === "object") {
    // no filepath given
    options = filepath;
    filepaths = await getFilepaths(options);
  } else {
    filepaths = [filepath];
  }

  for (let i = 0; i < filepaths.length; i++) {
    var path = filepaths[i];
    var content = await fetch(`https://lively4${options.path}${path}`).then(resp => { return resp.text(); });
    yield {
      path: path,
      filename: path.slice(path.lastIndexOf("/") + 1),
      ext: path.slice(path.lastIndexOf(".") + 1),
      content: content
    }
  }
}
<|endoftext|>


<commit_before>/* Extend jQuery Shake */
jQuery.fn.shake = function(intShakes, intDistance, intDuration) {
    this.each(function() {
        $(this).css("position","relative");
        for (var x=1; x<=intShakes; x++) {
        $(this).animate({left:(intDistance*-1)}, (((intDuration/intShakes)/3)))
    .animate({left:intDistance}, ((intDuration/intShakes)/2))
    .animate({left:0}, (((intDuration/intShakes)/3)));
    }
  });
    return this;
};
/* End extend */

var SESSION_KEY = 'browser-property';
function toggleLock() {
	if (window.parent && window.parent != window.self && window.parent.document) {
		$( window.parent.document).find('body').toggleClass('lock');
	}
	$('body').toggleClass('lock');
}

function removePropertyHandler(e) {
	e.preventDefault();
	e.stopPropagation();
	var _tr = $(this).closest('tr');
	var propStr = encodeURIComponent(_tr.data('name'))+'@Delete=';

	$.post(resourcePath+"?"+propStr).done(function(data){
		_tr.fadeOut(800, function() { _tr.remove(); })
	}).fail(function() {
		_tr.shake(5,5,800);
	});
}

$('.glyphicon-trash').one('click dblclick', removePropertyHandler);

var propertyFormTmpl = $('#propertyFormTmpl').clone().removeAttr('id');
$('tr.alert:not(.readonly)').on('dblclick', function() {
	var _self = $(this);
	_self.toggleClass('editing');
	toggleLock();
	// Create the form only once
	if (!_self.data('formRendered')) {
		_self.data('formRendered',true);
		createEditPanel(_self);
	}
	if (_self.is('.editing')) {
		var valueEdit = _self.find('.value-edit')
		var clientRect = valueEdit[0].getBoundingClientRect();
		valueEdit.css({position:'fixed',width:clientRect.width , left:clientRect.left, top:clientRect.top});
	}
})
// JCR PropertyDefinition String,Date,Binary,Double,Long,Boolean,Name,Path,Reference,Undefined

function createFormElementByType(name, type, value, isMultiple) {
	var out = [];
	out.push('
')
	if (type == 'Boolean') {
		/* Because checkbox is not submitted, we need to provide default value */
		out.push('<input type="checkbox" name="'+name+'" onclick="this.value=this.checked?true:false" value="'+value+'" '+(value=='true' ? 'checked="checked"': '')+' />');
		out.push('<input type="hidden" name="'+name+'@DefaultValue" value="false"/>');
		out.push('<input type="hidden" name="'+name+'@UseDefaultWhenMissing" value="true"/>');
	} else if (type == 'Long'){
		out.push('<input type="text" required pattern="[0-9]+" name="'+name+'" value="'+value+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
	} else if (type == 'Double') {
		out.push('<input type="text" required pattern="\\d+(\\.\\d+)?" name="'+name+'" value="'+value+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
	} else if (type == 'String') {
		if (isMultiple) {
			out.push('<input type="text" required name="'+name+'" value="'+value+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
		} else {
			out.push('<textarea required name="'+name+'">'+value+'</textarea>');
		}
	} else if (type == 'Reference') {
		//TODO
	} else if (type == 'Date') {
		out.push('<input type="date" required name="'+name+'" value="'+(value=='' ? value : value.substring(0,value.indexOf('T')))+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
	} else if (type == 'Name') {
		//TODO
	} else if (type == 'Path') {
		//TODO
	} else if (type == 'Undefined') {
		//TODO
	}
	if (isMultiple) {
		out.push('')
	}
	out.push('
');
	return out.join('');
}
function createEditPanel(trElement) {
	var name = trElement.data('name');
	var type = trElement.data('type');
	var isMultiple =  trElement.data('multiple')
	var valueEdit = trElement.find('.value-edit');
	var out = [];

	var originalValues =  valueEdit.find('span').map(function() { return $(this).text(); }).get();
	for (var i=0,j=originalValues.length;i<j;i++) {
		out.push(createFormElementByType(name, type, originalValues[i], isMultiple));
	}
	if (isMultiple) {
		out.push('');
	}
	valueEdit.empty().append(propertyFormTmpl.clone().prepend(out.join('')));
	valueEdit.on('click', function(e) {
		var _propRoot = $(this).closest('tr');
		if (e.target.nodeName == 'SPAN') {
			e.preventDefault();
			e.stopPropagation();
			var $target = $(e.target);
			action = $target.data('action');
			if (!action) return;
			if (action == 'cancel') {
				$target.closest('tr').trigger('dblclick');
			} else if (action == 'remove-prop') {
				$target.parent().remove();
			} else if (action == 'add-prop') {
				$(createFormElementByType(_propRoot.data('name'), _propRoot.data('type'), '', true)).insertBefore($target);
			}

			else if (action == 'ok') {
				var $form = $target.closest('form');
				var isValid = true;
				var fields = $form.find('[required]');
				if (fields.length) {
					fields.each(function() {
						if (!isValidField(this)) {
							isValid = false;
							return;
						}
					})
				}
				if (!isValid) {
					//HTML5 form validation
					valueEdit.find('input[type=submit]').trigger('click');
				}
				else {
					$.post($form.attr('action'), $form.serialize())
					.done(function(data) {
						var dataHtml = $(data);
						var status = dataHtml.find('#Status').text();
						var message = dataHtml.find('#Message').text();
						if (status == '200' && message == 'OK') {
							if (type == 'Boolean') {
								valueEdit.prev().text($form.find('[name='+name+']')[0].checked);
							} if (type == 'Date') {
								valueEdit.prev().text($form.find('[name='+name+']').val() + 'T00:00:00.000-05:00');
							} else {
								valueEdit.prev().text($form.find('[name='+name+']').map(function() {return this.value }).get().join(', '));
							}
							visualUpdate(valueEdit.closest('tr').trigger('dblclick'));
						}
					}).fail(function(jqXHR, textStatus, errorThrown) {
						valueEdit.find('form').shake(5,5,800);
					})
				}
			}
		}
	})

}
/*
		if (fieldValue.indexOf('<') > -1) {
			var textarea  = $('<textarea></textarea>').attr('name',field).attr('value',fieldValue).appendTo(fieldSet);
			textarea.wysiwyg({
			rmUnusedControls: true,
			controls: {
				bold: { visible : true },
				html: { visible : true },
				italic: {visible: true},
				insertOrderedList: { visible: true},
				insertUnorderedList: {visible: true},
				undo: {visible: true},
				redo: {visible: true},
				removeFormat: { visible : true }
			}
		});
*/

	$('#mixinBtn').on('click', function(e) {
		e.preventDefault();
		$('.mixinContainer').toggleClass('editing');
		toggleLock();
	})

	$('#mixinCancelBtn').on('click', function() {
		$('.mixinContainer').toggleClass('editing');
		toggleLock();
	});

	$('#propCancelBtn').on('click', function() {
		$('#addPropModal').modal('hide');
		toggleLock();
	});

	$('#mixinSubmitBtn').on('click', function() {
		var $form = $('#mixinForm');
		var $errorMsg = $form.find('.errorMsg');
		$errorMsg.empty().hide();
		$.post($form.attr('action'), $form.serialize())
		.done(function(data) {
			var dataHtml = $(data);
			var status = dataHtml.find('#Status').text();
			var message = dataHtml.find('#Message').text();

			if (status == '200' && message == 'OK') {
				toggleLock();
				setSessionStorage(SESSION_KEY, {highlight: ['jcr:mixinTypes']});
				window.location.reload(true);
			}
		}).fail(function(jqXHR, textStatus, errorThrown) {
			var dataHtml = $(jqXHR.responseText);
			var status = dataHtml.find('#Status').text();
			var message = dataHtml.find('#Message').text();
			$errorMsg.text(status+": Error saving "+resourcePath+" caused by "+message).show();
			$('#mixinSubmitBtn').shake(5,5,800);
		})

	});

	$(document).on('keyup', function(e) {
		  if (e.keyCode === 27 && $('body.lock').length) {
			  $('.editing').toggleClass('editing');
			  toggleLock();
		  }
	});

	function isValidField(field) {
		if (typeof field.willValidate !== "undefined") {
			field.checkValidity();
			return field.validity.valid;
		}
		// Legacy browser, will let server handle the validation so returning true
		return true;
	}

	function addPropMenuitemHandler(e) {
		e.preventDefault();
		var propName = $('#propName');
		if (!isValidField(propName[0])) {
			propName.closest('form').find('input[type=submit]').trigger('click');
			return;
		}
		var name = propName.val();
		var isMultiple = $('#propMultiple:checked').length > 0;
		var type = $(this).text();
		var addPropModal = $('#addPropModal');

		var out = [];
		out.push('<input type="hidden" name="'+name+'@TypeHint" value="'+type+(isMultiple?'[]':'')+'" />');
		out.push(createFormElementByType(name, type, '', isMultiple));
		if (isMultiple) {
			out.push('');
		}

		addPropModal.removeData().data({name:name, type:type, multiple:isMultiple});
		addPropModal.find('.modal-title').text(type+(isMultiple?'[]':'')+': '+propName.val());
		addPropModal.find('.modal-body').empty().append(out.join(''));
		addPropModal.modal('show');
		toggleLock();
	}

	$('#addPropMenu .dropdown-menu a').on('click', addPropMenuitemHandler);

	$('#addPropModal .modal-content').on('click', function(e) {
		if (e.target && e.target.nodeName == 'SPAN') {
			e.preventDefault();
			e.stopPropagation();
			var $target = $(e.target);
			action = $target.data('action');
			if (!action) return false;
			var _propRoot = $('#addPropModal');
			_propRoot.find('.fieldItem.alert').removeClass('alert alert-danger')
			if (action == 'add-prop') {
				$(createFormElementByType(_propRoot.data('name'), _propRoot.data('type'), '', true)).insertBefore($target);
			}
			else if (action == 'remove-prop') {
				$target.parent().remove();
			}
			else if (action == 'ok') {
				var $form = $target.closest('form');
				var isValid = true;
				var invaldField = null;
				var fields = $form.find('[required]');
				if (fields.length) {
					fields.each(function() {
						if (!isValidField(this)) {
							isValid = false;
							invalidField = this;
							return false;
						}
					})
				}
				var $errorMsg = $form.find('.errorMsg');
				$errorMsg.empty().hide();
				if (!isValid) {
					//HTML5 form validation
					$form.find('input[type=submit]').trigger('click');
				} else {
					$.post($form.attr('action'), $form.serialize())
					.done(function(data) {
						var dataHtml = $(data);
						var status = dataHtml.find('#Status').text();
						var message = dataHtml.find('#Message').text();
						if (status == '200' && message == 'OK') {
							toggleLock();
							setSessionStorage(SESSION_KEY, {highlight: [$('#addPropModal').data('name')]});
							window.location.reload(true);
						}
					}).fail(function(jqXHR, textStatus, errorThrown) {
						var dataHtml = $(jqXHR.responseText);
						var status = dataHtml.find('#Status').text();
						var message = dataHtml.find('#Message').text();

						$errorMsg.text(status+": Error saving "+resourcePath+" caused by "+message).show();
					})
				}

			}
		}
	});

	function visualUpdate(ele) {
		ele.addClass('alert-success').fadeOut(500).fadeIn(1000,function() {$(this).removeClass('alert-success')})
	}

	function restoreSession() {
		var storage = getJsonSessionStorage(SESSION_KEY);
		if (storage && storage.highlight) {
			for (var i=0;i<storage.highlight.length;i++) {
				visualUpdate($('tr[data-name=\''+storage.highlight[i]+'\']'));
			}
			clearSessionStorage(SESSION_KEY);
		}
	}
	restoreSession();

<commit_msg>moved code to common.js<commit_after>

var SESSION_KEY = 'browser-property';
function toggleLock() {
	if (window.parent && window.parent != window.self && window.parent.document) {
		$( window.parent.document).find('body').toggleClass('lock');
	}
	$('body').toggleClass('lock');
}

function removePropertyHandler(e) {
	e.preventDefault();
	e.stopPropagation();
	var _tr = $(this).closest('tr');
	var propStr = encodeURIComponent(_tr.data('name'))+'@Delete=';

	$.post(resourcePath+"?"+propStr).done(function(data){
		_tr.fadeOut(800, function() { _tr.remove(); })
	}).fail(function() {
		_tr.shake(5,5,800);
	});
}

$('.glyphicon-trash').one('click dblclick', removePropertyHandler);

var propertyFormTmpl = $('#propertyFormTmpl').clone().removeAttr('id');
$('tr.alert:not(.readonly)').on('dblclick', function() {
	var _self = $(this);
	_self.toggleClass('editing');
	toggleLock();
	// Create the form only once
	if (!_self.data('formRendered')) {
		_self.data('formRendered',true);
		createEditPanel(_self);
	}
	if (_self.is('.editing')) {
		var valueEdit = _self.find('.value-edit')
		var clientRect = valueEdit[0].getBoundingClientRect();
		valueEdit.css({position:'fixed',width:clientRect.width , left:clientRect.left, top:clientRect.top});
	}
})
// JCR PropertyDefinition String,Date,Binary,Double,Long,Boolean,Name,Path,Reference,Undefined

function createFormElementByType(name, type, value, isMultiple) {
	var out = [];
	out.push('
')
	if (type == 'Boolean') {
		/* Because checkbox is not submitted, we need to provide default value */
		out.push('<input type="checkbox" name="'+name+'" onclick="this.value=this.checked?true:false" value="'+value+'" '+(value=='true' ? 'checked="checked"': '')+' />');
		out.push('<input type="hidden" name="'+name+'@DefaultValue" value="false"/>');
		out.push('<input type="hidden" name="'+name+'@UseDefaultWhenMissing" value="true"/>');
	} else if (type == 'Long'){
		out.push('<input type="text" required pattern="[0-9]+" name="'+name+'" value="'+value+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
	} else if (type == 'Double') {
		out.push('<input type="text" required pattern="\\d+(\\.\\d+)?" name="'+name+'" value="'+value+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
	} else if (type == 'String') {
		if (isMultiple) {
			out.push('<input type="text" required name="'+name+'" value="'+value+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
		} else {
			out.push('<textarea required name="'+name+'">'+value+'</textarea>');
		}
	} else if (type == 'Reference') {
		//TODO
	} else if (type == 'Date') {
		out.push('<input type="date" required name="'+name+'" value="'+(value=='' ? value : value.substring(0,value.indexOf('T')))+'" autocapitalize="off" autocorrect="off" autocomplete="off" />');
	} else if (type == 'Name') {
		//TODO
	} else if (type == 'Path') {
		//TODO
	} else if (type == 'Undefined') {
		//TODO
	}
	if (isMultiple) {
		out.push('')
	}
	out.push('
');
	return out.join('');
}
function createEditPanel(trElement) {
	var name = trElement.data('name');
	var type = trElement.data('type');
	var isMultiple =  trElement.data('multiple')
	var valueEdit = trElement.find('.value-edit');
	var out = [];

	var originalValues =  valueEdit.find('span').map(function() { return $(this).text(); }).get();
	for (var i=0,j=originalValues.length;i<j;i++) {
		out.push(createFormElementByType(name, type, originalValues[i], isMultiple));
	}
	if (isMultiple) {
		out.push('');
	}
	valueEdit.empty().append(propertyFormTmpl.clone().prepend(out.join('')));
	valueEdit.on('click', function(e) {
		var _propRoot = $(this).closest('tr');
		if (e.target.nodeName == 'SPAN') {
			e.preventDefault();
			e.stopPropagation();
			var $target = $(e.target);
			action = $target.data('action');
			if (!action) return;
			if (action == 'cancel') {
				$target.closest('tr').trigger('dblclick');
			} else if (action == 'remove-prop') {
				$target.parent().remove();
			} else if (action == 'add-prop') {
				$(createFormElementByType(_propRoot.data('name'), _propRoot.data('type'), '', true)).insertBefore($target);
			}

			else if (action == 'ok') {
				var $form = $target.closest('form');
				var isValid = true;
				var fields = $form.find('[required]');
				if (fields.length) {
					fields.each(function() {
						if (!isValidField(this)) {
							isValid = false;
							return;
						}
					})
				}
				if (!isValid) {
					//HTML5 form validation
					valueEdit.find('input[type=submit]').trigger('click');
				}
				else {
					$.post($form.attr('action'), $form.serialize())
					.done(function(data) {
						var dataHtml = $(data);
						var status = dataHtml.find('#Status').text();
						var message = dataHtml.find('#Message').text();
						if (status == '200' && message == 'OK') {
							if (type == 'Boolean') {
								valueEdit.prev().text($form.find('[name='+name+']')[0].checked);
							} if (type == 'Date') {
								valueEdit.prev().text($form.find('[name='+name+']').val() + 'T00:00:00.000-05:00');
							} else {
								valueEdit.prev().text($form.find('[name='+name+']').map(function() {return this.value }).get().join(', '));
							}
							visualUpdate(valueEdit.closest('tr').trigger('dblclick'));
						}
					}).fail(function(jqXHR, textStatus, errorThrown) {
						valueEdit.find('form').shake(5,5,800);
					})
				}
			}
		}
	})

}
/*
		if (fieldValue.indexOf('<') > -1) {
			var textarea  = $('<textarea></textarea>').attr('name',field).attr('value',fieldValue).appendTo(fieldSet);
			textarea.wysiwyg({
			rmUnusedControls: true,
			controls: {
				bold: { visible : true },
				html: { visible : true },
				italic: {visible: true},
				insertOrderedList: { visible: true},
				insertUnorderedList: {visible: true},
				undo: {visible: true},
				redo: {visible: true},
				removeFormat: { visible : true }
			}
		});
*/

	$('#mixinBtn').on('click', function(e) {
		e.preventDefault();
		$('.mixinContainer').toggleClass('editing');
		toggleLock();
	})

	$('#mixinCancelBtn').on('click', function() {
		$('.mixinContainer').toggleClass('editing');
		toggleLock();
	});

	$('#propCancelBtn').on('click', function() {
		$('#addPropModal').modal('hide');
		toggleLock();
	});

	$('#mixinSubmitBtn').on('click', function() {
		var $form = $('#mixinForm');
		var $errorMsg = $form.find('.errorMsg');
		$errorMsg.empty().hide();
		$.post($form.attr('action'), $form.serialize())
		.done(function(data) {
			var dataHtml = $(data);
			var status = dataHtml.find('#Status').text();
			var message = dataHtml.find('#Message').text();

			if (status == '200' && message == 'OK') {
				toggleLock();
				setSessionStorage(SESSION_KEY, {highlight: ['jcr:mixinTypes']});
				window.location.reload(true);
			}
		}).fail(function(jqXHR, textStatus, errorThrown) {
			var dataHtml = $(jqXHR.responseText);
			var status = dataHtml.find('#Status').text();
			var message = dataHtml.find('#Message').text();
			$errorMsg.text(status+": Error saving "+resourcePath+" caused by "+message).show();
			$('#mixinSubmitBtn').shake(5,5,800);
		})

	});

	$(document).on('keyup', function(e) {
		  if (e.keyCode === 27 && $('body.lock').length) {
			  $('.editing').toggleClass('editing');
			  toggleLock();
		  }
	});

	function isValidField(field) {
		if (typeof field.willValidate !== "undefined") {
			field.checkValidity();
			return field.validity.valid;
		}
		// Legacy browser, will let server handle the validation so returning true
		return true;
	}

	function addPropMenuitemHandler(e) {
		e.preventDefault();
		var propName = $('#propName');
		if (!isValidField(propName[0])) {
			propName.closest('form').find('input[type=submit]').trigger('click');
			return;
		}
		var name = propName.val();
		var isMultiple = $('#propMultiple:checked').length > 0;
		var type = $(this).text();
		var addPropModal = $('#addPropModal');

		var out = [];
		out.push('<input type="hidden" name="'+name+'@TypeHint" value="'+type+(isMultiple?'[]':'')+'" />');
		out.push(createFormElementByType(name, type, '', isMultiple));
		if (isMultiple) {
			out.push('');
		}

		addPropModal.removeData().data({name:name, type:type, multiple:isMultiple});
		addPropModal.find('.modal-title').text(type+(isMultiple?'[]':'')+': '+propName.val());
		addPropModal.find('.modal-body').empty().append(out.join(''));
		addPropModal.modal('show');
		toggleLock();
	}

	$('#addPropMenu .dropdown-menu a').on('click', addPropMenuitemHandler);

	$('#addPropModal .modal-content').on('click', function(e) {
		if (e.target && e.target.nodeName == 'SPAN') {
			e.preventDefault();
			e.stopPropagation();
			var $target = $(e.target);
			action = $target.data('action');
			if (!action) return false;
			var _propRoot = $('#addPropModal');
			_propRoot.find('.fieldItem.alert').removeClass('alert alert-danger')
			if (action == 'add-prop') {
				$(createFormElementByType(_propRoot.data('name'), _propRoot.data('type'), '', true)).insertBefore($target);
			}
			else if (action == 'remove-prop') {
				$target.parent().remove();
			}
			else if (action == 'ok') {
				var $form = $target.closest('form');
				var isValid = true;
				var invaldField = null;
				var fields = $form.find('[required]');
				if (fields.length) {
					fields.each(function() {
						if (!isValidField(this)) {
							isValid = false;
							invalidField = this;
							return false;
						}
					})
				}
				var $errorMsg = $form.find('.errorMsg');
				$errorMsg.empty().hide();
				if (!isValid) {
					//HTML5 form validation
					$form.find('input[type=submit]').trigger('click');
				} else {
					$.post($form.attr('action'), $form.serialize())
					.done(function(data) {
						var dataHtml = $(data);
						var status = dataHtml.find('#Status').text();
						var message = dataHtml.find('#Message').text();
						if (status == '200' && message == 'OK') {
							toggleLock();
							setSessionStorage(SESSION_KEY, {highlight: [$('#addPropModal').data('name')]});
							window.location.reload(true);
						}
					}).fail(function(jqXHR, textStatus, errorThrown) {
						var dataHtml = $(jqXHR.responseText);
						var status = dataHtml.find('#Status').text();
						var message = dataHtml.find('#Message').text();

						$errorMsg.text(status+": Error saving "+resourcePath+" caused by "+message).show();
					})
				}

			}
		}
	});

	function visualUpdate(ele) {
		ele.addClass('alert-success').fadeOut(500).fadeIn(1000,function() {$(this).removeClass('alert-success')})
	}

	function restoreSession() {
		var storage = getJsonSessionStorage(SESSION_KEY);
		if (storage && storage.highlight) {
			for (var i=0;i<storage.highlight.length;i++) {
				visualUpdate($('tr[data-name=\''+storage.highlight[i]+'\']'));
			}
			clearSessionStorage(SESSION_KEY);
		}
	}
	restoreSession();

<|endoftext|>


<commit_before>// Libraries
import React from 'react';

function countTagsByDifficulty(data) {
    if (data.children.length === 0) {
        if (data.difficulty && data.difficulty.title) {
            return {
                [data.difficulty.title]: 1
            };
        } else {
            return {
                'undefined': 1
            };
        }
    } else {
        return data.children
            .map((childNode) => countTagsByDifficulty(childNode))
            .reduce((acc, node) => {
                Object.keys(node).forEach((k) => {
                    acc[k] = acc[k] ? acc[k] + node[k] : node[k];
                });
                return acc;
            }, {});
    }
}

function orderByDifficulty(a, b) {
    const MAP = {
        'undefined': 0,
        'Nothing': 1,
        'Easy': 2,
        'Medium': 3,
        'Hard': 4,
        'Very Hard': 5
    };
    return MAP[b] - MAP[a];
}

export default function NodeLabel({ data, onClickHandler, isLeaf, key, owners, selected }) {
    const nodeClasses = `node ${isLeaf ? 'leaf' : ''} ${selected ? 'node--selected' : ''}`;
    let tagsHtml = null,
        total;

    if (!isLeaf) {
        tagsHtml = data.children.map((childNode) => {
            return countTagsByDifficulty(childNode);
        }).reduce((acc, diff) => {
            Object.keys(diff).forEach((k) => {
                acc[k] = acc[k] ? acc[k] + diff[k] : diff[k];
            });
            return acc;
        }, {});
        total = Object.keys(tagsHtml).reduce((acc, x) => {
            return acc + tagsHtml[x];
        }, 0);
        tagsHtml = [(

                {total}

        )].concat(Object.keys(tagsHtml).sort(orderByDifficulty).map((diff, i) => {
            let classes = `tree-node-badge tree-node-badge--${diff.replace(' ', '-')}`;
            return (

                    {tagsHtml[diff]}

            );
        }));
    } else {
        tagsHtml = [data.primary_owner, data.secondary_owner].map((ownerId, i) =>{
            let iconHtml = i === 0 ?
                (looks_one) :
                (looks_two);
            if (ownerId && owners.length > 0) {
                return (

                        {iconHtml}
                        {owners.filter(owner => +owner.id === +ownerId)[0].name}

                );
            }
            return null;
        });
    }

    return (

                {data.name}

            {tagsHtml}

    );
}
<commit_msg>Fix empty user issue<commit_after>// Libraries
import React from 'react';

function countTagsByDifficulty(data) {
    if (data.children.length === 0) {
        if (data.difficulty && data.difficulty.title) {
            return {
                [data.difficulty.title]: 1
            };
        } else {
            return {
                'undefined': 1
            };
        }
    } else {
        return data.children
            .map((childNode) => countTagsByDifficulty(childNode))
            .reduce((acc, node) => {
                Object.keys(node).forEach((k) => {
                    acc[k] = acc[k] ? acc[k] + node[k] : node[k];
                });
                return acc;
            }, {});
    }
}

function orderByDifficulty(a, b) {
    const MAP = {
        'undefined': 0,
        'Nothing': 1,
        'Easy': 2,
        'Medium': 3,
        'Hard': 4,
        'Very Hard': 5
    };
    return MAP[b] - MAP[a];
}

export default function NodeLabel({ data, onClickHandler, isLeaf, key, owners, selected }) {
    const nodeClasses = `node ${isLeaf ? 'leaf' : ''} ${selected ? 'node--selected' : ''}`;
    let tagsHtml = null,
        total;

    if (!isLeaf) {
        tagsHtml = data.children.map((childNode) => {
            return countTagsByDifficulty(childNode);
        }).reduce((acc, diff) => {
            Object.keys(diff).forEach((k) => {
                acc[k] = acc[k] ? acc[k] + diff[k] : diff[k];
            });
            return acc;
        }, {});
        total = Object.keys(tagsHtml).reduce((acc, x) => {
            return acc + tagsHtml[x];
        }, 0);
        tagsHtml = [(

                {total}

        )].concat(Object.keys(tagsHtml).sort(orderByDifficulty).map((diff, i) => {
            let classes = `tree-node-badge tree-node-badge--${diff.replace(' ', '-')}`;
            return (

                    {tagsHtml[diff]}

            );
        }));
    } else {
        tagsHtml = [data.primary_owner, data.secondary_owner].map((ownerId, i) =>{
            let iconHtml = i === 0 ?
                (looks_one) :
                (looks_two);
            if (ownerId && owners.length > 0) {
                let filteredUser = owners.filter(owner => +owner.id === +ownerId)[0];
                if (filteredUser) {
                    return (

                            {iconHtml}
                            {filteredUser ? filteredUser.name : ''}

                    );
                }
            }
            return null;
        });
    }

    return (

                {data.name}

            {tagsHtml}

    );
}
<|endoftext|>


<commit_before>"use strict";

var ObjectId = require("mongodb").ObjectId;
require("mongodb-toolkit");
var generateCode = require("../../../utils/code-generator");

var PackingManager = require('../../production/finishing-printing/packing-manager');
var ProductManager = require('../../master/product-manager');
var StorageManager = require('../../master/storage-manager');
var InventoryDocumentManager = require('../inventory-document-manager');

var Models = require("dl-models");
var Map = Models.map;
var PackingReceiptModel = Models.inventory.finishingPrinting.FPPackingReceipt;

var BaseManager = require("module-toolkit").BaseManager;
var i18n = require("dl-i18n");
var moment = require("moment");

module.exports = class FPPackingReceiptManager extends BaseManager {
    constructor(db, user) {
        super(db, user);
        this.collection = this.db.use(Map.inventory.finishingPrinting.collection.FPPackingReceipt);

        this.packingManager = new PackingManager(db, user);
        this.productManager = new ProductManager(db, user);
        this.storageManager = new StorageManager(db, user);
        this.inventoryDocumentManager = new InventoryDocumentManager(db, user);
    }

    _getQuery(paging) {
        var _default = {
            _deleted: false
        },
            pagingFilter = paging.filter || {},
            keywordFilter = {},
            query = {};

        if (paging.keyword) {
            var regex = new RegExp(paging.keyword, "i");
            var packingCodeFilter = {
                "packingCode": {
                    "$regex": regex
                }
            };
            var dateFilter = {
                "date": {
                    "$regex": regex
                }
            };
            var buyerFilter = {
                "buyer": {
                    "$regex": regex
                }
            };
            var productionOrderNoFilter = {
                "productionOrderNo": {
                    "$regex": regex
                }
            };
            var colorNameFilter = {
                "colorName": {
                    "$regex": regex
                }
            };
            var constructionFilter = {
                "construction": {
                    "$regex": regex
                }
            };
            var createdByFilter = {
                "_createdBy": {
                    "$regex": regex
                }
            };
            keywordFilter["$or"] = [packingCodeFilter, dateFilter, buyerFilter, productionOrderNoFilter, colorNameFilter, constructionFilter, createdByFilter];
        }
        query["$and"] = [_default, keywordFilter, pagingFilter];
        return query;
    }

    _beforeInsert(data) {
        data.code = generateCode();
        return Promise.resolve(data);
    }

    _validate(packingReceipt) {
        var errors = {};
        var valid = packingReceipt;

        var getDbPackingReceipt = this.collection.singleOrDefault({
            _id: new ObjectId(valid._id)
        });

        var getDuplicatePackingReceipt = this.collection.singleOrDefault({
            _id: {
                '$ne': new ObjectId(valid._id)
            },
            code: valid.code
        });

        var getPacking = valid.packingId && ObjectId.isValid(valid.packingId) ? this.packingManager.getSingleByIdOrDefault(valid.packingId) : Promise.resolve(null);

        var getStorage = valid.storageName ? this.storageManager.collection.find({ name: valid.storageName }).toArray() : Promise.resolve([]);

        valid.items = valid.items instanceof Array ? valid.items : [];
        var products = valid.items.map((item) => item.product ? item.product : null);
        var getProducts = products.length > 0 ? this.productManager.collection.find({ name: { "$in": products } }).toArray() : Promise.resolve([]);

        return Promise.all([getDbPackingReceipt, getDuplicatePackingReceipt, getPacking, getStorage, getProducts])
            .then((results) => {
                var _dbPackingReceipt = results[0];
                var _duplicatePackingReceipt = results[1];
                var _packing = results[2];
                var _storages = results[3];
                var _products = results[4];

                if (_dbPackingReceipt)
                    valid.code = _dbPackingReceipt.code; // prevent code changes.

                if (_duplicatePackingReceipt)
                    errors["code"] = i18n.__("PackingReceipt.code.isExist: %s is exist", i18n.__("PackingReceipt.code._:Code"));

                if (!valid.packingId || valid.packingId === '')
                    errors["packingId"] = i18n.__("PackingReceipt.packingId.isRequired:%s is required", i18n.__("PackingReceipt.packingId._:Packing")); //"Grade harus diisi";
                else if (!_packing)
                    errors["packingId"] = i18n.__("PackingReceipt.packingId: %s not found", i18n.__("PackingReceipt.KanbanId._:Packing"));

                if (!valid.date)
                    errors["date"] = i18n.__("PackingReceipt.date.isRequired:%s is required", i18n.__("PackingReceipt.date._:Date")); //"Grade harus diisi";

                if (!valid.accepted && !valid.declined) {
                    errors["accepted"] = i18n.__("PackingReceipt.accepted.isRequired:%s is required", i18n.__("PackingReceipt.accepted._:Accepted")); //"Grade harus diisi";
                    errors["declined"] = i18n.__("PackingReceipt.declined.isRequired:%s is required", i18n.__("PackingReceipt.declined._:Declined")); //"Grade harus diisi";
                }

                if (valid.items.length > 0) {
                    var itemErrors = [];
                    for (var i = 0; i < _packing.items.length; i++) {
                        var itemError = {};
                        if (valid.items[i].quantity !== _packing.items[i].quantity && (!valid.items[i].notes || valid.items[i].notes === "")) {
                            itemError["notes"] = i18n.__("PackingReceipt.items.notes.isRequired:%s is required", i18n.__("PackingReceipt.items.notes._:Notes")); //"Note harus diisi";
                        }
                        itemErrors.push(itemError);
                    }

                    for (var itemError of itemErrors) {
                        if (Object.getOwnPropertyNames(itemError).length > 0) {
                            errors.items = itemErrors;
                            break;
                        }
                    }
                }

                if (Object.getOwnPropertyNames(errors).length > 0) {
                    var ValidationError = require('module-toolkit').ValidationError;
                    return Promise.reject(new ValidationError('data does not pass validation', errors));
                }

                valid.packingId = _packing._id;
                valid.packingCode = _packing.code;

                //Inventory Document Validation
                valid.storageId = _storages.length > 0 ? new ObjectId(_storages[0]._id) : null;
                valid.referenceType = `Penerimaan Packing ${valid.storageName}`;
                valid.type = "IN";

                for (var i = 0; i < valid.items.length; i++) {
                    valid.items[i].uomId = _products[i].uomId;
                    valid.items[i].productId = _products[i]._id;
                }

                valid.buyer = _packing.buyerName;
                valid.date = new Date(valid.date);
                valid.productionOrderNo = _packing.productionOrderNo;
                valid.colorName = _packing.colorName;
                valid.construction = _packing.construction;
                valid.packingUom = _packing.packingUom;
                valid.finishWidth = _packing.materialWidthFinish;

                valid.orderType = _packing.orderType;
                valid.colorType = _packing.colorType;
                valid.designCode = _packing.designCode;
                valid.designNumber = _packing.designNumber;

                if (!valid.stamp) {
                    valid = new PackingReceiptModel(valid);
                }

                valid.stamp(this.user.username, "manager");
                return Promise.resolve(valid);

            })
    }

    _createIndexes() {
        var dateIndex = {
            name: `ix_${Map.inventory.finishingPrinting.collection.PackingReceipt}__updatedDate`,
            key: {
                _updatedDate: -1
            }
        }
        var codeIndex = {
            name: `ix_${Map.inventory.finishingPrinting.collection.PackingReceipt}_code`,
            key: {
                code: 1
            },
            unique: true
        };

        return this.collection.createIndexes([dateIndex, codeIndex]);
    }

    _afterInsert(id) {
        return this.getSingleById(id)
            .then((packingReceipt) => {
                var packingReceiptId = id;
                var packingReceipt = packingReceipt;
                return this.packingManager.getSingleById(packingReceipt.packingId)
                    .then((packing) => {
                        packing.accepted = true;
                        return this.packingManager.update(packing)
                            .then((id) => {
                                packingReceipt.referenceNo = `RFNO-${packingReceipt.code}`;
                                return this.inventoryDocumentManager.create(packingReceipt)
                                    .then((inventoryDocument) => Promise.resolve(packingReceiptId))
                            })
                    })
            })
    }

    getReport(info) {
        var _defaultFilter = {
            _deleted: false
        };
        var query = {};

        var packingCodeFilter = {};
        var buyerFilter = {};
        var productionOrderNoFilter = {};
        var _createdByFilter = {};

        var dateFrom = info.dateFrom ? (new Date(info.dateFrom)) : (new Date(1900, 1, 1));
        var dateTo = info.dateTo ? (new Date(info.dateTo + "T23:59")) : (new Date());

        if (info.packingCode && info.packingCode !== "") {
            packingCodeFilter = { "packingCode": info.packingCode };
        }

        if (info.buyer && info.buyer !== "") {
            buyerFilter = { "buyer": info.buyer };
        }

        if (info.productionOrderNo && info.productionOrderNo !== "") {
            productionOrderNoFilter = { "productionOrderNo": info.productionOrderNo };
        }

        if (info._createdBy && info._createdBy !== "") {
            _createdByFilter = { "_createdBy": info._createdBy };
        }

        var dateFilter = {
            "date": {
                $gte: new Date(dateFrom),
                $lte: new Date(dateTo)
            }
        };

        query = { "$and": [_defaultFilter, packingCodeFilter, buyerFilter, productionOrderNoFilter, _createdByFilter, dateFilter] }

        return this._createIndexes()
            .then((createdIndexResults) => {
                return this.collection
                    .where(query)
                    .execute();
            })
    }

    getXls(results, query) {
        var xls = [];
        xls.data = [];
        xls.options = [];
        xls.name = "";

        var index = 1;
        var dateFormat = "DD/MM/YYYY";

        for (var result of results.data) {
            if (result.items) {
                for (var item of result.items) {
                    var detail = {};
                    detail["No"] = index;
                    detail["Kode Paking"] = result.packingCode ? result.packingCode : "";
                    detail["Tanggal"] = result.date ? moment(result.date).format(dateFormat) : "";
                    detail["Buyer"] = result.buyer ? result.buyer : "";
                    detail["Nomor Order"] = result.productionOrderNo ? result.productionOrderNo : "";
                    detail["Warna"] = result.colorName ? result.colorName : "";
                    detail["Konstruksi"] = result.construction ? result.construction : "";
                    detail["Diterima Oleh"] = result._createdBy ? result._createdBy : "";
                    detail["UOM"] = result.packingUom ? result.packingUom : "";
                    detail["Nama Barang"] = item.product ? item.product : "";
                    detail["Kuantiti Diterima"] = item.quantity ? item.quantity : 0;
                    detail["Remark"] = item.remark ? item.remark : "";
                    detail["Catatan"] = item.notes ? item.notes : "";

                    xls.options["No"] = "number";
                    xls.options["Kode Paking"] = "string";
                    xls.options["Tanggal"] = "string";
                    xls.options["Buyer"] = "string";
                    xls.options["Nomor Order"] = "string";
                    xls.options["Warna"] = "string";
                    xls.options["Konstruksi"] = "string";
                    xls.options["Diterima Oleh"] = "string";
                    xls.options["UOM"] = "string";
                    xls.options["Nama Barang"] = "string";
                    xls.options["Kuantiti Diterima"] = "number";
                    xls.options["Remark"] = "string";
                    xls.options["Catatan"] = "string";

                    index++;
                    xls.data.push(detail);
                }
            }
        }

        if (query.dateFrom && query.dateTo) {
            xls.name = `Laporan Penerimaan Packing ${moment(new Date(query.dateFrom)).format(dateFormat)} - ${moment(new Date(query.dateTo)).format(dateFormat)}.xlsx`
        } else if (!query.dateFrom && query.dateTo) {
            xls.name = `Laporan Penerimaan Packing ${moment(new Date(query.dateTo)).format(dateFormat)}.xlsx`
        } else if (query.dateFrom && !query.dateTo) {
            xls.name = `Laporan Penerimaan Packing ${moment(new Date(query.dateFrom)).format(dateFormat)}.xlsx`
        } else {
            xls.name = `Laporan Penerimaan Packing.xlsx`;
        }

        return Promise.resolve(xls);
    }
};
<commit_msg>update packing receipt unit test<commit_after>"use strict";

var ObjectId = require("mongodb").ObjectId;
require("mongodb-toolkit");
var generateCode = require("../../../utils/code-generator");

var PackingManager = require('../../production/finishing-printing/packing-manager');
var ProductManager = require('../../master/product-manager');
var StorageManager = require('../../master/storage-manager');
var InventoryDocumentManager = require('../inventory-document-manager');

var Models = require("dl-models");
var Map = Models.map;
var PackingReceiptModel = Models.inventory.finishingPrinting.FPPackingReceipt;

var BaseManager = require("module-toolkit").BaseManager;
var i18n = require("dl-i18n");
var moment = require("moment");

module.exports = class FPPackingReceiptManager extends BaseManager {
    constructor(db, user) {
        super(db, user);
        this.collection = this.db.use(Map.inventory.finishingPrinting.collection.FPPackingReceipt);

        this.packingManager = new PackingManager(db, user);
        this.productManager = new ProductManager(db, user);
        this.storageManager = new StorageManager(db, user);
        this.inventoryDocumentManager = new InventoryDocumentManager(db, user);
    }

    _getQuery(paging) {
        var _default = {
            _deleted: false
        },
            pagingFilter = paging.filter || {},
            keywordFilter = {},
            query = {};

        if (paging.keyword) {
            var regex = new RegExp(paging.keyword, "i");
            var packingCodeFilter = {
                "packingCode": {
                    "$regex": regex
                }
            };
            var dateFilter = {
                "date": {
                    "$regex": regex
                }
            };
            var buyerFilter = {
                "buyer": {
                    "$regex": regex
                }
            };
            var productionOrderNoFilter = {
                "productionOrderNo": {
                    "$regex": regex
                }
            };
            var colorNameFilter = {
                "colorName": {
                    "$regex": regex
                }
            };
            var constructionFilter = {
                "construction": {
                    "$regex": regex
                }
            };
            var createdByFilter = {
                "_createdBy": {
                    "$regex": regex
                }
            };
            keywordFilter["$or"] = [packingCodeFilter, dateFilter, buyerFilter, productionOrderNoFilter, colorNameFilter, constructionFilter, createdByFilter];
        }
        query["$and"] = [_default, keywordFilter, pagingFilter];
        return query;
    }

    _beforeInsert(data) {
        data.code = generateCode();
        return Promise.resolve(data);
    }

    _validate(packingReceipt) {
        var errors = {};
        var valid = packingReceipt;

        var getDbPackingReceipt = this.collection.singleOrDefault({
            _id: new ObjectId(valid._id)
        });

        var getDuplicatePackingReceipt = this.collection.singleOrDefault({
            _id: {
                '$ne': new ObjectId(valid._id)
            },
            code: valid.code
        });

        var getPacking = valid.packingId && ObjectId.isValid(valid.packingId) ? this.packingManager.getSingleByIdOrDefault(valid.packingId) : Promise.resolve(null);

        var storageName = valid.storageName ? valid.storageName : null;
        var storageId = valid.storageId ? new ObjectId(valid.storageId) : null;

        var getStorage = valid.storageName || valid.storageId ? this.storageManager.collection.find({ "$or": [{ "name": storageName }, { "_id": storageId }] }).toArray() : Promise.resolve([]);

        valid.items = valid.items instanceof Array ? valid.items : [];
        var products = valid.items.map((item) => item.product ? item.product : null);
        var getProducts = products.length > 0 ? this.productManager.collection.find({ name: { "$in": products } }).toArray() : Promise.resolve([]);

        return Promise.all([getDbPackingReceipt, getDuplicatePackingReceipt, getPacking, getStorage, getProducts])
            .then((results) => {
                var _dbPackingReceipt = results[0];
                var _duplicatePackingReceipt = results[1];
                var _packing = results[2];
                var _storage = results[3].length > 0 ? results[3][0] : null;
                var _products = results[4];

                if (_dbPackingReceipt)
                    valid.code = _dbPackingReceipt.code; // prevent code changes.

                if (_duplicatePackingReceipt)
                    errors["code"] = i18n.__("PackingReceipt.code.isExist: %s is exist", i18n.__("PackingReceipt.code._:Code"));

                if (!valid.packingId || valid.packingId === '')
                    errors["packingId"] = i18n.__("PackingReceipt.packingId.isRequired:%s is required", i18n.__("PackingReceipt.packingId._:Packing")); //"Grade harus diisi";
                else if (!_packing)
                    errors["packingId"] = i18n.__("PackingReceipt.packingId: %s not found", i18n.__("PackingReceipt.KanbanId._:Packing"));

                if (!valid.date)
                    errors["date"] = i18n.__("PackingReceipt.date.isRequired:%s is required", i18n.__("PackingReceipt.date._:Date")); //"Grade harus diisi";

                if (!valid.accepted && !valid.declined) {
                    errors["accepted"] = i18n.__("PackingReceipt.accepted.isRequired:%s is required", i18n.__("PackingReceipt.accepted._:Accepted")); //"Grade harus diisi";
                    errors["declined"] = i18n.__("PackingReceipt.declined.isRequired:%s is required", i18n.__("PackingReceipt.declined._:Declined")); //"Grade harus diisi";
                }

                if (valid.items.length > 0) {
                    var itemErrors = [];
                    for (var i = 0; i < _packing.items.length; i++) {
                        var itemError = {};
                        if (valid.items[i].quantity !== _packing.items[i].quantity && (!valid.items[i].notes || valid.items[i].notes === "")) {
                            itemError["notes"] = i18n.__("PackingReceipt.items.notes.isRequired:%s is required", i18n.__("PackingReceipt.items.notes._:Notes")); //"Note harus diisi";
                        }
                        itemErrors.push(itemError);
                    }

                    for (var itemError of itemErrors) {
                        if (Object.getOwnPropertyNames(itemError).length > 0) {
                            errors.items = itemErrors;
                            break;
                        }
                    }
                }

                if (Object.getOwnPropertyNames(errors).length > 0) {
                    var ValidationError = require('module-toolkit').ValidationError;
                    return Promise.reject(new ValidationError('data does not pass validation', errors));
                }

                valid.packingId = _packing._id;
                valid.packingCode = _packing.code;

                //Inventory Document Validation
                valid.storageId = _storage ? new ObjectId(_storage._id) : null;
                valid.referenceType = `Penerimaan Packing ${_storage ? _storage.name : null}`;
                valid.type = "IN";

                for (var i = 0; i < valid.items.length; i++) {
                    valid.items[i].uomId = _products[i].uomId;
                    valid.items[i].productId = _products[i]._id;
                }

                valid.buyer = _packing.buyerName;
                valid.date = new Date(valid.date);
                valid.productionOrderNo = _packing.productionOrderNo;
                valid.colorName = _packing.colorName;
                valid.construction = _packing.construction;
                valid.packingUom = _packing.packingUom;
                valid.finishWidth = _packing.materialWidthFinish;

                valid.orderType = _packing.orderType;
                valid.colorType = _packing.colorType;
                valid.designCode = _packing.designCode;
                valid.designNumber = _packing.designNumber;

                if (!valid.stamp) {
                    valid = new PackingReceiptModel(valid);
                }

                valid.stamp(this.user.username, "manager");
                return Promise.resolve(valid);

            })
    }

    _createIndexes() {
        var dateIndex = {
            name: `ix_${Map.inventory.finishingPrinting.collection.PackingReceipt}__updatedDate`,
            key: {
                _updatedDate: -1
            }
        }
        var codeIndex = {
            name: `ix_${Map.inventory.finishingPrinting.collection.PackingReceipt}_code`,
            key: {
                code: 1
            },
            unique: true
        };

        return this.collection.createIndexes([dateIndex, codeIndex]);
    }

    _afterInsert(id) {
        return this.getSingleById(id)
            .then((packingReceipt) => {
                var packingReceiptId = id;
                var packingReceipt = packingReceipt;
                return this.packingManager.getSingleById(packingReceipt.packingId)
                    .then((packing) => {
                        packing.accepted = true;
                        return this.packingManager.update(packing)
                            .then((id) => {
                                packingReceipt.referenceNo = `RFNO-${packingReceipt.code}`;
                                return this.inventoryDocumentManager.create(packingReceipt)
                                    .then((inventoryDocument) => Promise.resolve(packingReceiptId))
                            })
                    })
            })
    }

    getReport(info) {
        var _defaultFilter = {
            _deleted: false
        };
        var query = {};

        var packingCodeFilter = {};
        var buyerFilter = {};
        var productionOrderNoFilter = {};
        var _createdByFilter = {};

        var dateFrom = info.dateFrom ? (new Date(info.dateFrom)) : (new Date(1900, 1, 1));
        var dateTo = info.dateTo ? (new Date(info.dateTo + "T23:59")) : (new Date());

        if (info.packingCode && info.packingCode !== "") {
            packingCodeFilter = { "packingCode": info.packingCode };
        }

        if (info.buyer && info.buyer !== "") {
            buyerFilter = { "buyer": info.buyer };
        }

        if (info.productionOrderNo && info.productionOrderNo !== "") {
            productionOrderNoFilter = { "productionOrderNo": info.productionOrderNo };
        }

        if (info._createdBy && info._createdBy !== "") {
            _createdByFilter = { "_createdBy": info._createdBy };
        }

        var dateFilter = {
            "date": {
                $gte: new Date(dateFrom),
                $lte: new Date(dateTo)
            }
        };

        query = { "$and": [_defaultFilter, packingCodeFilter, buyerFilter, productionOrderNoFilter, _createdByFilter, dateFilter] }

        return this._createIndexes()
            .then((createdIndexResults) => {
                return this.collection
                    .where(query)
                    .execute();
            })
    }

    getXls(results, query) {
        var xls = [];
        xls.data = [];
        xls.options = [];
        xls.name = "";

        var index = 1;
        var dateFormat = "DD/MM/YYYY";

        for (var result of results.data) {
            if (result.items) {
                for (var item of result.items) {
                    var detail = {};
                    detail["No"] = index;
                    detail["Kode Paking"] = result.packingCode ? result.packingCode : "";
                    detail["Tanggal"] = result.date ? moment(result.date).format(dateFormat) : "";
                    detail["Buyer"] = result.buyer ? result.buyer : "";
                    detail["Nomor Order"] = result.productionOrderNo ? result.productionOrderNo : "";
                    detail["Warna"] = result.colorName ? result.colorName : "";
                    detail["Konstruksi"] = result.construction ? result.construction : "";
                    detail["Diterima Oleh"] = result._createdBy ? result._createdBy : "";
                    detail["UOM"] = result.packingUom ? result.packingUom : "";
                    detail["Nama Barang"] = item.product ? item.product : "";
                    detail["Kuantiti Diterima"] = item.quantity ? item.quantity : 0;
                    detail["Remark"] = item.remark ? item.remark : "";
                    detail["Catatan"] = item.notes ? item.notes : "";

                    xls.options["No"] = "number";
                    xls.options["Kode Paking"] = "string";
                    xls.options["Tanggal"] = "string";
                    xls.options["Buyer"] = "string";
                    xls.options["Nomor Order"] = "string";
                    xls.options["Warna"] = "string";
                    xls.options["Konstruksi"] = "string";
                    xls.options["Diterima Oleh"] = "string";
                    xls.options["UOM"] = "string";
                    xls.options["Nama Barang"] = "string";
                    xls.options["Kuantiti Diterima"] = "number";
                    xls.options["Remark"] = "string";
                    xls.options["Catatan"] = "string";

                    index++;
                    xls.data.push(detail);
                }
            }
        }

        if (query.dateFrom && query.dateTo) {
            xls.name = `Laporan Penerimaan Packing ${moment(new Date(query.dateFrom)).format(dateFormat)} - ${moment(new Date(query.dateTo)).format(dateFormat)}.xlsx`
        } else if (!query.dateFrom && query.dateTo) {
            xls.name = `Laporan Penerimaan Packing ${moment(new Date(query.dateTo)).format(dateFormat)}.xlsx`
        } else if (query.dateFrom && !query.dateTo) {
            xls.name = `Laporan Penerimaan Packing ${moment(new Date(query.dateFrom)).format(dateFormat)}.xlsx`
        } else {
            xls.name = `Laporan Penerimaan Packing.xlsx`;
        }

        return Promise.resolve(xls);
    }
};
<|endoftext|>


<commit_before>import React from "react";
import { connect } from "react-redux";
import queryString from "query-string";
import { changePage, EXPERIMENTAL_showMainDisplayMarkdown } from "../../actions/navigation";
import { TOGGLE_NARRATIVE } from "../../actions/types";
import {
  linkStyles,
  MobileBannerTop,
  MobileBannerBottom,
  MobileContentContainer,
  EndOfNarrative
} from "./styles";
import Tree from "../tree";
import Map from "../map/map";
import MainDisplayMarkdown from "./MainDisplayMarkdown";

/**
 * A React component which takes up the entire screen and displays narratives in
 * "mobile" format. Instead of each narrative page displayed as narrative-text in
 * the sidebar & viz on the right hand section of the page, as we do for laptops
 * we render both in a "scrolly" fashion. Banners at the top/bottom of the page
 * allow you to navigate between pages.
 *
 * TODO: a lot of code is duplicated between this and `narratives/index.js` +
 * `main/index.js`. These should be brought out into functions. Same with
 * styling.
 * TODO: entropy / frequencies panels
 */
@connect((state) => ({
  loaded: state.narrative.loaded,
  blocks: state.narrative.blocks,
  currentInFocusBlockIdx: state.narrative.blockIdx,
  panelsToDisplay: state.controls.panelsToDisplay
}))
class MobileNarrativeDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      showingEndOfNarrativePage: false
    };
    this.exitNarrativeMode = () => {
      this.props.dispatch({type: TOGGLE_NARRATIVE, display: false});
    };
    this.goToNextPage = () => {
      if (this.state.showingEndOfNarrativePage) return; // no-op
      if (this.props.currentInFocusBlockIdx+1 === this.props.blocks.length) {
        this.setState({showingEndOfNarrativePage: true});
        return;
      }
      this._goToPage(this.props.currentInFocusBlockIdx+1);
    };
    this.goToPreviousPage = () => {
      if (this.props.currentInFocusBlockIdx === 0) return; // no-op
      this._goToPage(this.props.currentInFocusBlockIdx-1);
    };
    this._goToPage = (idx) => {
      // TODO: this `if` statement should be moved to the `changePage` function or similar
      if (this.props.blocks[idx] && this.props.blocks[idx].mainDisplayMarkdown) {
        this.props.dispatch(EXPERIMENTAL_showMainDisplayMarkdown({
          query: queryString.parse(this.props.blocks[idx].query),
          queryToDisplay: {n: idx}
        }));
        return;
      }
      this.props.dispatch(changePage({
        changeDataset: false,
        query: queryString.parse(this.props.blocks[idx].query),
        queryToDisplay: {n: idx},
        push: true
      }));
    };
    // TODO: bind down & up arrows (is this ok since we also have scollable content?)
  }

  pageNarrativeContent() {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    const block = this.props.blocks[this.props.currentInFocusBlockIdx];
    return (
      <div
        id={`MobileNarrativeBlock_${this.props.currentInFocusBlockIdx}`}
        key={block.__html}
        style={{
          padding: "10px 20px",
          height: "inherit",
          overflow: "hidden"
        }}
        dangerouslySetInnerHTML={block}
      />
    );
  }
  renderMainMarkdown() {
    if (this.props.panelsToDisplay.includes("EXPERIMENTAL_MainDisplayMarkdown")) {
      return <MainDisplayMarkdown width={window.innerWidth}/>;
    }
    return null;
  }
  renderVizCards(height) {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    if (this.props.currentInFocusBlockIdx === 0) {
      /* don't display viz panels for intro page */
      return null;
    }
    const width = window.innerWidth - 50; // TODO
    return (
      <>
        {this.props.panelsToDisplay.includes("tree")
          ? <Tree width={width} height={height} /> : null}
        {this.props.panelsToDisplay.includes("map")
          ? <Map width={width} height={height} justGotNewDatasetRenderNewMap={false} /> : null}
      </>
    );
  }
  renderEndOfNarrative(bannerHeight, contentHeight) {  // todo add as class properties
    return (
      <>
        <MobileBannerTop height={bannerHeight} onClick={this.goToPreviousPage}>
          Previous
        </MobileBannerTop>
        <MobileContentContainer height={contentHeight+bannerHeight}>
          <EndOfNarrative>

End of Narrative

             this._goToPage(0)}
            >
              Jump to the beginning

              Leave the narrative & explore the data yourself

          </EndOfNarrative>
        </MobileContentContainer>
      </>
    );
  }

  renderStartOfNarrative(bannerHeight, contentHeight) {
    return (
      <>
        <MobileContentContainer height={contentHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <MobileBannerBottom height={bannerHeight} onClick={this.goToNextPage}>
          Next
        </MobileBannerBottom>
      </>
    );
  }

  render() {
    const bannerHeight = 50;
    const contentHeight = window.innerHeight - 2*bannerHeight;

    if (this.state.showingEndOfNarrativePage) {
      return this.renderEndOfNarrative(bannerHeight, contentHeight);

    } else if (this.props.currentInFocusBlockIdx === 0) {
      return this.renderStartOfNarrative(bannerHeight, contentHeight);
    }

    return (
      <>
        <MobileBannerTop height={bannerHeight} onClick={this.goToPreviousPage}>
          Previous
        </MobileBannerTop>
        <MobileContentContainer height={contentHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <MobileBannerBottom height={bannerHeight} onClick={this.goToNextPage}>
          Next
        </MobileBannerBottom>
      </>
    );
  }
}

export default MobileNarrativeDisplay;
<commit_msg>narratives: Move height constants to state<commit_after>import React from "react";
import { connect } from "react-redux";
import queryString from "query-string";
import { changePage, EXPERIMENTAL_showMainDisplayMarkdown } from "../../actions/navigation";
import { TOGGLE_NARRATIVE } from "../../actions/types";
import {
  linkStyles,
  MobileBannerTop,
  MobileBannerBottom,
  MobileContentContainer,
  EndOfNarrative
} from "./styles";
import Tree from "../tree";
import Map from "../map/map";
import MainDisplayMarkdown from "./MainDisplayMarkdown";

const BANNER_HEIGHT = 50;
/**
 * A React component which takes up the entire screen and displays narratives in
 * "mobile" format. Instead of each narrative page displayed as narrative-text in
 * the sidebar & viz on the right hand section of the page, as we do for laptops
 * we render both in a "scrolly" fashion. Banners at the top/bottom of the page
 * allow you to navigate between pages.
 *
 * TODO: a lot of code is duplicated between this and `narratives/index.js` +
 * `main/index.js`. These should be brought out into functions. Same with
 * styling.
 * TODO: entropy / frequencies panels
 */
@connect((state) => ({
  loaded: state.narrative.loaded,
  blocks: state.narrative.blocks,
  currentInFocusBlockIdx: state.narrative.blockIdx,
  panelsToDisplay: state.controls.panelsToDisplay
}))
class MobileNarrativeDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      showingEndOfNarrativePage: false,
      bannerHeight: BANNER_HEIGHT,
      contentHeight: window.innerHeight - 2*BANNER_HEIGHT
    };
    this.exitNarrativeMode = () => {
      this.props.dispatch({type: TOGGLE_NARRATIVE, display: false});
    };
    this.goToNextPage = () => {
      if (this.state.showingEndOfNarrativePage) return; // no-op
      if (this.props.currentInFocusBlockIdx+1 === this.props.blocks.length) {
        this.setState({showingEndOfNarrativePage: true});
        return;
      }
      this._goToPage(this.props.currentInFocusBlockIdx+1);
    };
    this.goToPreviousPage = () => {
      if (this.props.currentInFocusBlockIdx === 0) return; // no-op
      this._goToPage(this.props.currentInFocusBlockIdx-1);
    };
    this._goToPage = (idx) => {
      // TODO: this `if` statement should be moved to the `changePage` function or similar
      if (this.props.blocks[idx] && this.props.blocks[idx].mainDisplayMarkdown) {
        this.props.dispatch(EXPERIMENTAL_showMainDisplayMarkdown({
          query: queryString.parse(this.props.blocks[idx].query),
          queryToDisplay: {n: idx}
        }));
        return;
      }
      this.props.dispatch(changePage({
        changeDataset: false,
        query: queryString.parse(this.props.blocks[idx].query),
        queryToDisplay: {n: idx},
        push: true
      }));
    };
    // TODO: bind down & up arrows (is this ok since we also have scollable content?)
  }

  pageNarrativeContent() {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    const block = this.props.blocks[this.props.currentInFocusBlockIdx];
    return (
      <div
        id={`MobileNarrativeBlock_${this.props.currentInFocusBlockIdx}`}
        key={block.__html}
        style={{
          padding: "10px 20px",
          height: "inherit",
          overflow: "hidden"
        }}
        dangerouslySetInnerHTML={block}
      />
    );
  }
  renderMainMarkdown() {
    if (this.props.panelsToDisplay.includes("EXPERIMENTAL_MainDisplayMarkdown")) {
      return <MainDisplayMarkdown width={window.innerWidth}/>;
    }
    return null;
  }
  renderVizCards(height) {
    if (this.props.blocks[this.props.currentInFocusBlockIdx].mainDisplayMarkdown) {
      /* don't display normal narrative content if the block defines `mainDisplayMarkdown` */
      return null;
    }
    if (this.props.currentInFocusBlockIdx === 0) {
      /* don't display viz panels for intro page */
      return null;
    }
    const width = window.innerWidth - 50; // TODO
    return (
      <>
        {this.props.panelsToDisplay.includes("tree")
          ? <Tree width={width} height={height} /> : null}
        {this.props.panelsToDisplay.includes("map")
          ? <Map width={width} height={height} justGotNewDatasetRenderNewMap={false} /> : null}
      </>
    );
  }
  renderEndOfNarrative() {
    return (
      <>
        <MobileBannerTop height={this.state.bannerHeight} onClick={this.goToPreviousPage}>
          Previous
        </MobileBannerTop>
        <MobileContentContainer height={this.state.contentHeight+this.state.bannerHeight}>
          <EndOfNarrative>

End of Narrative

             this._goToPage(0)}
            >
              Jump to the beginning

              Leave the narrative & explore the data yourself

          </EndOfNarrative>
        </MobileContentContainer>
      </>
    );
  }

  renderStartOfNarrative() {
    return (
      <>
        <MobileContentContainer height={this.state.contentHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(this.state.contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <MobileBannerBottom height={bannerHeight} onClick={this.goToNextPage}>
          Next
        </MobileBannerBottom>
      </>
    );
  }

  render() {

    if (this.state.showingEndOfNarrativePage) {
      return this.renderEndOfNarrative();

    } else if (this.props.currentInFocusBlockIdx === 0) {
      return this.renderStartOfNarrative();
    }

    return (
      <>
        <MobileBannerTop height={this.state.bannerHeight} onClick={this.goToPreviousPage}>
          Previous
        </MobileBannerTop>
        <MobileContentContainer height={this.state.contentHeight}>
          {this.pageNarrativeContent()}
          {this.renderVizCards(this.state.contentHeight)}
          {this.renderMainMarkdown()}
        </MobileContentContainer>
        <MobileBannerBottom height={bannerHeight} onClick={this.goToNextPage}>
          Next
        </MobileBannerBottom>
      </>
    );
  }
}

export default MobileNarrativeDisplay;
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import withAsyncData from '../common/hocs/AsyncDataContainer';

/**
 * propsToRefetchOn: optional array of property names that should be watched; if any of them change then
 * a refetch will happen
 */
const withFilteredAsyncData = (fetchAsyncData, propsToRefetchOn) => {
  const withFilteredAsyncDataInner = (ChildComponent) => {
    const FilteredAsyncDataContainer = props => <ChildComponent {...props} />;

    FilteredAsyncDataContainer.propTypes = {
      // from store
      filters: PropTypes.shape({
        snapshotId: PropTypes.number,
        focusId: PropTypes.number,
        timespanId: PropTypes.number,
        q: PropTypes.string,
      }),
      // from compositional chain
      dispatch: PropTypes.func.isRequired,
    };

    const mapStateToProps = state => ({
      filters: state.topics.selected.filters,
    });

    let propsToListenTo = ['filters'];
    if (propsToRefetchOn) {
      propsToListenTo = ['filters', ...propsToListenTo];
    }

    return connect(mapStateToProps)(
      withAsyncData(fetchAsyncData, ['filters', ...propsToListenTo])(
        FilteredAsyncDataContainer
      )
    );
  };

  return withFilteredAsyncDataInner;
};

export default withFilteredAsyncData;
<commit_msg>Fix filteredasyncdata listening<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import withAsyncData from '../common/hocs/AsyncDataContainer';

/**
 * propsToRefetchOn: optional array of property names that should be watched; if any of them change then
 * a refetch will happen
 */
const withFilteredAsyncData = (fetchAsyncData, propsToRefetchOn) => {
  const withFilteredAsyncDataInner = (ChildComponent) => {
    const FilteredAsyncDataContainer = props => <ChildComponent {...props} />;

    FilteredAsyncDataContainer.propTypes = {
      // from store
      filters: PropTypes.shape({
        snapshotId: PropTypes.number,
        focusId: PropTypes.number,
        timespanId: PropTypes.number,
        q: PropTypes.string,
      }),
      // from compositional chain
      dispatch: PropTypes.func.isRequired,
    };

    const mapStateToProps = state => ({
      filters: state.topics.selected.filters,
    });

    let propsToListenTo = ['filters'];
    if (propsToRefetchOn) {
      propsToListenTo = ['filters', ...propsToRefetchOn];
    }

    return connect(mapStateToProps)(
      withAsyncData(fetchAsyncData, propsToListenTo)(
        FilteredAsyncDataContainer
      )
    );
  };

  return withFilteredAsyncDataInner;
};

export default withFilteredAsyncData;
<|endoftext|>