<commit_before>/*
License:

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { html, useReducer, useState, useEffect } from '../preact.js'
import { Router } from '../web_modules/preact-router.js'
import Hammer from '../web_modules/hammerjs.js'
import qs from '../web_modules/querystringify.js'

import FirstRunDialog from './first-run.js'
import Header from './header.js'
import Impressum from './impressum.js'
import InstallPrompt from './install-prompt.js'
import Main from './main.js'

import { scrollToADay } from '../lib/utils.js'
import {
  shiftModelNames,
  shift66Name,
  shiftModelNumberOfGroups
} from '../lib/constants.js'

const initialState = {
  url: window.location.pathname,
  didSelectModel: false, // did the user select a shift-model once?
  fullYear: false, // should the full year be displayed
  shiftModel: shift66Name, // Which shift-model is it, the 6-4 model or the 6-6 model?
  search: null,
  year: 2020, // Selected year
  month: 1, // Selected month
  group: 0 // group to display; 0 = all, 1 - 6 is group number
}

/**
 * The App main component
 * @returns {JSX.Element}
 */
export default function App () {
  const [state, dispatch] = useReducer(reducer, initialState, initReducer)

  const today = useToday()
  useHammer(dispatch, state.fullYear)

  useEffect(() => {
    window.localStorage.setItem('settings', JSON.stringify({
      didSelectModel: state.didSelectModel,
      group: state.group,
      shiftModel: state.shiftModel
    }))
  }, [state.didSelectModel, state.group, state.shiftModel])

  useEffect(() => {
    // on first render, and every time the shift model changes.
    if (state.didSelectModel) {
      const now = new Date()
      scrollToADay(now.getFullYear(), now.getMonth(), now.getDate())
    }
  }, [state.shiftModel, state.didSelectModel])

  useEffect(() => {
    if (state.search) {
      scrollToADay(...state.search)
    }
  }, [state.search])

  return html`

      <${Header}
        year=${state.year}
        month=${state.month}
        isFullYear=${state.fullYear}
        shiftModel=${state.shiftModel}
        search=${state.search}
        group=${state.group}
        url=${state.url}
        dispatch=${dispatch}
      />
      <${Router}
        onChange=${event => {
          dispatch({ type: 'url_change', url: event.url })
        }}
      >
        <${Main}
          path="/"
          isFullYear=${state.fullYear}
          year=${state.year}
          month=${state.month}
          shiftModel=${state.shiftModel}
          today=${today}
          search=${state.search}
          group=${state.group}
        />
        <${Impressum} path="/impressum/" />
      </${Router}>

      <${InstallPrompt} />

      ${state.didSelectModel
        ? null
        : html`<${FirstRunDialog}
          onClick=${model => {
            dispatch({
              type: 'model_change',
              payload: model
            })
          }}
        />`
      }

  `
}

/**
 * Load the stored settings from localStorage or the share hash.
 * And sets year and month to today.
 * @param {object} initialState The initial Reducer state
 */
function initReducer (initialState) {
  const now = new Date()

  const state = {
    ...initialState,
    year: now.getFullYear(),
    month: now.getMonth()
  }

  // load the stored settings
  const storedSettings = JSON.parse(window.localStorage.getItem('settings') || '{}')

  if (storedSettings.didSelectModel != null) {
    state.didSelectModel = storedSettings.didSelectModel
  }
  if (typeof storedSettings.group === 'number') {
    state.group = storedSettings.group
  }
  if (storedSettings.shiftModel != null && shiftModelNames.includes(storedSettings.shiftModel)) {
    state.shiftModel = storedSettings.shiftModel
  }

  // Load the settings from the share hash
  if (window.location.hash.length > 1) {
    const hashSettings = qs.parse(window.location.hash.slice(1))

    const group = +hashSettings.group
    if (!Number.isNaN(group) && group > 0 && group <= 6) {
      state.group = group
    }

    const schichtmodell = hashSettings.schichtmodell
    if (schichtmodell != null && shiftModelNames.includes(schichtmodell)) {
      state.shiftModel = schichtmodell

      if (state.group > shiftModelNumberOfGroups[schichtmodell]) {
        state.group = 0
      }
    }

    if (hashSettings.search != null && hashSettings.search.length >= 8) {
      const date = new Date(hashSettings.search)
      const year = date.getFullYear()
      const month = date.getMonth()
      const day = date.getDate()

      state.search = [year, month, day]
      state.year = year
      state.month = month
    }

    window.location.hash = ''
  }

  return state
}

function reducer (state, action) {
  switch (action.type) {
    case 'url_change':
      return {
        ...state,
        url: action.url
      }

    case 'goto':
      return {
        ...state,
        fullYear: action.fullYear != null
          ? action.fullYear
          : state.fullYear,
        year: action.year || state.year,
        month: action.month || state.month
      }

    case 'move':
    {
      let month = state.month + action.payload
      let year = state.year
      if (month < 0) {
        year -= 1
        month += 12
      } else if (month >= 12) {
        year += 1
        month -= 12
      }
      return {
        ...state,
        fullYear: false,
        year,
        month
      }
    }

    case 'toggle_full_year':
      return {
        ...state,
        fullYear: !state.fullYear
      }

    case 'group_change':
      return {
        ...state,
        group: action.payload
      }

    case 'model_change':
      if (shiftModelNames.every(model => model !== action.payload)) {
        throw new TypeError(`Unknown shift-model! "${action.payload}" is unknown!`)
      }
      return {
        ...state,
        shiftModel: action.payload,
        didSelectModel: true,
        group: state.group > shiftModelNumberOfGroups[action.payload]
          ? 0
          : state.group
      }

    case 'search':
      return {
        ...state,
        year: action.year,
        month: action.month,
        search: [action.year, action.month, action.day]
      }

    case 'clear_search':
      return {
        ...state,
        search: null
      }

    default:
      return state
  }
}

/**
 * Get today as a array of [year, month, day, hours].
 * @returns {[number, number, number, number]} Today-array
 */
function useToday () {
  const [today, setToday] = useState(() => {
    const now = new Date()
    return [now.getFullYear(), now.getMonth(), now.getDate(), now.getHours()]
  })

  useEffect(() => {
    const onFocus = () => {
      const now = new Date()
      const nextToday = [now.getFullYear(), now.getMonth(), now.getDate(), now.getHours()]
      if (nextToday.some((n, index) => n !== today[index])) {
        setToday(nextToday)
      }
    }

    const nextHour = new Date()
    nextHour.setHours(nextHour.getHours() + 1)
    nextHour.setMinutes(0)
    nextHour.setSeconds(0)

    const resetTimeout = setTimeout(() => {
      onFocus()
    }, Date.now() - nextHour.getTime())

    window.addEventListener('focus', onFocus)
    return () => {
      window.removeEventListener('focus', onFocus)
      clearTimeout(resetTimeout)
    }
  }, [today])

  return today
}

/**
 * Setup Hammer and handle swipes.
 * @param {function} dispatch   Dispatch function of the reducer.
 * @param {boolean}  isFullYear Is the full year displayed?
 */
function useHammer (dispatch, isFullYear) {
  useEffect(() => {
    if (isFullYear) return

    const handler = event => {
      switch (event.direction) {
        case 2: // right to left
          dispatch({
            type: 'move',
            payload: 1
          })
          break

        case 4: // left to right
          dispatch({
            type: 'move',
            payload: -1
          })
          break
      }
    }

    const hammertime = new Hammer(document.getElementById('app'))
    hammertime.on('swipe', handler)
    return () => {
      hammertime.off('swipe', handler)
    }
  }, [isFullYear])
}
<commit_msg>fix(hammer): don't handle swipe event when in impressum<commit_after>/*
License:

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { html, useReducer, useState, useEffect } from '../preact.js'
import { Router } from '../web_modules/preact-router.js'
import Hammer from '../web_modules/hammerjs.js'
import qs from '../web_modules/querystringify.js'

import FirstRunDialog from './first-run.js'
import Header from './header.js'
import Impressum from './impressum.js'
import InstallPrompt from './install-prompt.js'
import Main from './main.js'

import { scrollToADay } from '../lib/utils.js'
import {
  shiftModelNames,
  shift66Name,
  shiftModelNumberOfGroups
} from '../lib/constants.js'

const initialState = {
  url: window.location.pathname,
  didSelectModel: false, // did the user select a shift-model once?
  fullYear: false, // should the full year be displayed
  shiftModel: shift66Name, // Which shift-model is it, the 6-4 model or the 6-6 model?
  search: null,
  year: 2020, // Selected year
  month: 1, // Selected month
  group: 0 // group to display; 0 = all, 1 - 6 is group number
}

/**
 * The App main component
 * @returns {JSX.Element}
 */
export default function App () {
  const [state, dispatch] = useReducer(reducer, initialState, initReducer)

  const today = useToday()
  useHammer(dispatch, state.fullYear, state.url)

  useEffect(() => {
    window.localStorage.setItem('settings', JSON.stringify({
      didSelectModel: state.didSelectModel,
      group: state.group,
      shiftModel: state.shiftModel
    }))
  }, [state.didSelectModel, state.group, state.shiftModel])

  useEffect(() => {
    // on first render, and every time the shift model changes.
    if (state.didSelectModel) {
      const now = new Date()
      scrollToADay(now.getFullYear(), now.getMonth(), now.getDate())
    }
  }, [state.shiftModel, state.didSelectModel])

  useEffect(() => {
    if (state.search) {
      scrollToADay(...state.search)
    }
  }, [state.search])

  return html`

      <${Header}
        year=${state.year}
        month=${state.month}
        isFullYear=${state.fullYear}
        shiftModel=${state.shiftModel}
        search=${state.search}
        group=${state.group}
        url=${state.url}
        dispatch=${dispatch}
      />
      <${Router}
        onChange=${event => {
          dispatch({ type: 'url_change', url: event.url })
        }}
      >
        <${Main}
          path="/"
          isFullYear=${state.fullYear}
          year=${state.year}
          month=${state.month}
          shiftModel=${state.shiftModel}
          today=${today}
          search=${state.search}
          group=${state.group}
        />
        <${Impressum} path="/impressum/" />
      </${Router}>

      <${InstallPrompt} />

      ${state.didSelectModel
        ? null
        : html`<${FirstRunDialog}
          onClick=${model => {
            dispatch({
              type: 'model_change',
              payload: model
            })
          }}
        />`
      }

  `
}

/**
 * Load the stored settings from localStorage or the share hash.
 * And sets year and month to today.
 * @param {object} initialState The initial Reducer state
 */
function initReducer (initialState) {
  const now = new Date()

  const state = {
    ...initialState,
    year: now.getFullYear(),
    month: now.getMonth()
  }

  // load the stored settings
  const storedSettings = JSON.parse(window.localStorage.getItem('settings') || '{}')

  if (storedSettings.didSelectModel != null) {
    state.didSelectModel = storedSettings.didSelectModel
  }
  if (typeof storedSettings.group === 'number') {
    state.group = storedSettings.group
  }
  if (storedSettings.shiftModel != null && shiftModelNames.includes(storedSettings.shiftModel)) {
    state.shiftModel = storedSettings.shiftModel
  }

  // Load the settings from the share hash
  if (window.location.hash.length > 1) {
    const hashSettings = qs.parse(window.location.hash.slice(1))

    const group = +hashSettings.group
    if (!Number.isNaN(group) && group > 0 && group <= 6) {
      state.group = group
    }

    const schichtmodell = hashSettings.schichtmodell
    if (schichtmodell != null && shiftModelNames.includes(schichtmodell)) {
      state.shiftModel = schichtmodell

      if (state.group > shiftModelNumberOfGroups[schichtmodell]) {
        state.group = 0
      }
    }

    if (hashSettings.search != null && hashSettings.search.length >= 8) {
      const date = new Date(hashSettings.search)
      const year = date.getFullYear()
      const month = date.getMonth()
      const day = date.getDate()

      state.search = [year, month, day]
      state.year = year
      state.month = month
    }

    window.location.hash = ''
  }

  return state
}

function reducer (state, action) {
  switch (action.type) {
    case 'url_change':
      return {
        ...state,
        url: action.url
      }

    case 'goto':
      return {
        ...state,
        fullYear: action.fullYear != null
          ? action.fullYear
          : state.fullYear,
        year: action.year || state.year,
        month: action.month || state.month
      }

    case 'move':
    {
      let month = state.month + action.payload
      let year = state.year
      if (month < 0) {
        year -= 1
        month += 12
      } else if (month >= 12) {
        year += 1
        month -= 12
      }
      return {
        ...state,
        fullYear: false,
        year,
        month
      }
    }

    case 'toggle_full_year':
      return {
        ...state,
        fullYear: !state.fullYear
      }

    case 'group_change':
      return {
        ...state,
        group: action.payload
      }

    case 'model_change':
      if (shiftModelNames.every(model => model !== action.payload)) {
        throw new TypeError(`Unknown shift-model! "${action.payload}" is unknown!`)
      }
      return {
        ...state,
        shiftModel: action.payload,
        didSelectModel: true,
        group: state.group > shiftModelNumberOfGroups[action.payload]
          ? 0
          : state.group
      }

    case 'search':
      return {
        ...state,
        year: action.year,
        month: action.month,
        search: [action.year, action.month, action.day]
      }

    case 'clear_search':
      return {
        ...state,
        search: null
      }

    default:
      return state
  }
}

/**
 * Get today as a array of [year, month, day, hours].
 * @returns {[number, number, number, number]} Today-array
 */
function useToday () {
  const [today, setToday] = useState(() => {
    const now = new Date()
    return [now.getFullYear(), now.getMonth(), now.getDate(), now.getHours()]
  })

  useEffect(() => {
    const onFocus = () => {
      const now = new Date()
      const nextToday = [now.getFullYear(), now.getMonth(), now.getDate(), now.getHours()]
      if (nextToday.some((n, index) => n !== today[index])) {
        setToday(nextToday)
      }
    }

    const nextHour = new Date()
    nextHour.setHours(nextHour.getHours() + 1)
    nextHour.setMinutes(0)
    nextHour.setSeconds(0)

    const resetTimeout = setTimeout(() => {
      onFocus()
    }, Date.now() - nextHour.getTime())

    window.addEventListener('focus', onFocus)
    return () => {
      window.removeEventListener('focus', onFocus)
      clearTimeout(resetTimeout)
    }
  }, [today])

  return today
}

/**
 * Setup Hammer and handle swipes.
 * @param {function} dispatch   Dispatch function of the reducer.
 * @param {boolean}  isFullYear Is the full year displayed?
 * @param {string}   path       The URL path.
 */
function useHammer (dispatch, isFullYear, path) {
  useEffect(() => {
    if (isFullYear || path === '/impressum/') return

    const handler = event => {
      switch (event.direction) {
        case 2: // right to left
          dispatch({
            type: 'move',
            payload: 1
          })
          break

        case 4: // left to right
          dispatch({
            type: 'move',
            payload: -1
          })
          break
      }
    }

    const hammertime = new Hammer(document.getElementById('app'))
    hammertime.on('swipe', handler)
    return () => {
      hammertime.off('swipe', handler)
    }
  }, [isFullYear, path])
}
<|endoftext|>


<commit_before>import moment from 'moment'
import { GMT, HHmm, MealKey } from '../const'

const machineTimezoneOffset = new Date().getTimezoneOffset()

/**
 * GMT 에 맞는 현재 시각 정보가 담긴 moment 객체를 반환한다.
 * @param date {number|Date}
 * @param [gmt='+0000'] {string}
 * @returns {Moment}
 */
function getMomentWithGMT (date, gmt = '+0000') {
  const addOrSubtract = gmt[ 0 ] === '+' ? 'add' : 'subtract'
  const num = gmt.slice(1)
  const [ h, m ] = [ num.slice(0, 2), num.slice(2) ].map(str => parseInt(str, 10))
  return moment(date)
    [ addOrSubtract ](h, 'hours')
    [ addOrSubtract ](m, 'minutes')
    .add(machineTimezoneOffset, 'minutes')
}

export default class Now {
  /**
   * @param gmt {string}
   * @param timestamp {?number}
   */
  constructor ({ gmt = GMT, timestamp = null } = {}) {
    this._moment = getMomentWithGMT(timestamp || Date.now(), gmt)
    this.formattedString = this._moment.format('YYYY-MM-DD HH:mm:ss')
    const [ dateString, timeString ] = this.formattedString.split(' ')
    this._dateString = dateString
    this._timeString = timeString
  }

  getDateBeforeHoursOf (num) {
    return this._moment.subtract(num, 'h').toDate()
  }

  get mealString () {
    return this.isMorning() ? '아침' : (this.isLunch() ? '점심' : '저녁')
  }

  get timeKey () {
    if (!this._timeKey) {
      this._timeKey = this._timeString.split(':').slice(0, 2).join(':')
    }
    return this._timeKey
  }

  get dateKey () {
    return this._dateString
  }

  get mealKey () {
    if (!this._mealKey) {
      if (this.isMorning()) {
        this._mealKey = MealKey.Morning
      } else if (this.isLunch()) {
        this._mealKey = MealKey.Lunch
      } else if (this.isDinner()) {
        this._mealKey = MealKey.Dinner
      } else {
        this._mealKey = MealKey.LateNightMeal
      }
    }
    return this._mealKey
  }

  get HHmmss () {
    return this.formattedString.split(' ')[ 1 ]
  }

  /**
   * @returns {boolean}
   */
  isMorning () {
    return this.timeKey >= HHmm.Morning && this.timeKey < HHmm.Lunch
  }

  /**
   * @returns {boolean}
   */
  isLunch () {
    return this.timeKey >= HHmm.Lunch && this.timeKey < HHmm.Dinner
  }

  /**
   * @returns {boolean}
   */
  isDinner () {
    return this.timeKey >= HHmm.Dinner && this.timeKey < HHmm.LateNightMeal
  }

  valueOf () {
    return this._moment.valueOf()
  }

  getDate () {
    return this._moment.toDate()
  }

  /**
   * 시간의 처음을 표현하고 싶을 때 사용한다. unix timestamp === 0 인 Now 객체를 반환한다.
   * @return {Now}
   */
  static initDate () {
    return new Now({ timestamp: 0 })
  }
}
<commit_msg>초기 시각 데이터를 생성하는데 발생하는 버그 수정<commit_after>import moment from 'moment'
import { GMT, HHmm, MealKey } from '../const'

const machineTimezoneOffset = new Date().getTimezoneOffset()

/**
 * GMT 에 맞는 현재 시각 정보가 담긴 moment 객체를 반환한다.
 * @param date {number|Date}
 * @param [gmt='+0000'] {string}
 * @returns {Moment}
 */
function getMomentWithGMT (date, gmt = '+0000') {
  const addOrSubtract = gmt[ 0 ] === '+' ? 'add' : 'subtract'
  const num = gmt.slice(1)
  const [ h, m ] = [ num.slice(0, 2), num.slice(2) ].map(str => parseInt(str, 10))
  return moment(date)
    [ addOrSubtract ](h, 'hours')
    [ addOrSubtract ](m, 'minutes')
    .add(machineTimezoneOffset, 'minutes')
}

export default class Now {
  /**
   * @param gmt {string}
   * @param timestamp {?number}
   */
  constructor ({ gmt = GMT, timestamp = null } = {}) {
    this._moment = getMomentWithGMT(timestamp || Date.now(), gmt)
    this.formattedString = this._moment.format('YYYY-MM-DD HH:mm:ss')
    const [ dateString, timeString ] = this.formattedString.split(' ')
    this._dateString = dateString
    this._timeString = timeString
  }

  getDateBeforeHoursOf (num) {
    return this._moment.subtract(num, 'h').toDate()
  }

  get mealString () {
    return this.isMorning() ? '아침' : (this.isLunch() ? '점심' : '저녁')
  }

  get timeKey () {
    if (!this._timeKey) {
      this._timeKey = this._timeString.split(':').slice(0, 2).join(':')
    }
    return this._timeKey
  }

  get dateKey () {
    return this._dateString
  }

  get mealKey () {
    if (!this._mealKey) {
      if (this.isMorning()) {
        this._mealKey = MealKey.Morning
      } else if (this.isLunch()) {
        this._mealKey = MealKey.Lunch
      } else if (this.isDinner()) {
        this._mealKey = MealKey.Dinner
      } else {
        this._mealKey = MealKey.LateNightMeal
      }
    }
    return this._mealKey
  }

  get HHmmss () {
    return this.formattedString.split(' ')[ 1 ]
  }

  /**
   * @returns {boolean}
   */
  isMorning () {
    return this.timeKey >= HHmm.Morning && this.timeKey < HHmm.Lunch
  }

  /**
   * @returns {boolean}
   */
  isLunch () {
    return this.timeKey >= HHmm.Lunch && this.timeKey < HHmm.Dinner
  }

  /**
   * @returns {boolean}
   */
  isDinner () {
    return this.timeKey >= HHmm.Dinner && this.timeKey < HHmm.LateNightMeal
  }

  valueOf () {
    return this._moment.valueOf()
  }

  getDate () {
    return this._moment.toDate()
  }

  /**
   * 시간의 처음을 표현하고 싶을 때 사용한다. unix timestamp === 0 인 Now 객체를 반환한다.
   * @return {Now}
   */
  static initDate () {
    return new Now({ gmt: '+0000', timestamp: 0 })
  }
}
<|endoftext|>


<commit_before>// Native
const { existsSync } = require('fs');
const { join, resolve } = require('path');

// External
const importLazy = require('import-lazy')(require);
const CopyPlugin = importLazy('copy-webpack-plugin');
const MiniCssExtractPlugin = importLazy('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = importLazy('optimize-css-assets-webpack-plugin');
const sveltePreprocess = importLazy('svelte-preprocess');
const TerserPlugin = importLazy('terser-webpack-plugin');
const { VueLoaderPlugin } = importLazy('vue-loader');
const EnvironmentPlugin = importLazy('webpack/lib/EnvironmentPlugin');

// Ours
const { combine, merge } = require('../utils/structures');
const { getBabelConfig } = require('./babel');
const { getBuildConfig } = require('./build');
const { getProjectConfig } = require('./project');

const URL_LOADER_LIMIT = 10000;
const PROJECT_TYPES_CONFIG = {
  preact: {
    resolve: {
      alias: {
      }
        react: 'preact',
        'react-dom': 'preact/compat'
    }
  },
  svelte: config => {
    const { include, loader, options } = getHintedRule(config, 'scripts');

    include.push(/(node_modules\/svelte)/);

    config.module.rules.push({
      test: /\.svelte$/,
      include,
      use: [
        {
          loader,
          options
        },
        {
          loader: require.resolve('svelte-loader'),
          options: {
            dev: config.mode === 'development',
            emitCss: getHintedRule(config, 'styles').use[0] === MiniCssExtractPlugin.loader,
            preprocess: sveltePreprocess()
          }
        }
      ]
    });

    return config;
  },
  vue: config => {
    getHintedRule(config, 'styles').use[0] = { loader: require.resolve('vue-style-loader') };

    config.module.rules.push({
      test: /\.vue$/,
      loader: require.resolve('vue-loader')
    });

    config.plugins.push(new VueLoaderPlugin());

    return config;
  }
};
const WEBPACK_CONFIG_FILE = 'webpack.config.js';

module.exports.getWebpackConfig = () => {
  const { root } = getProjectConfig();
  const { addModernJS, showDeprecations } = getBuildConfig();
  const customWebpackConfigFilePath = join(root, WEBPACK_CONFIG_FILE);

  if (showDeprecations) {
    process.traceDeprecation = true;
  } else {
    process.noDeprecation = true;
  }

  let config;

  // If the project has a webpack config file, use it, otherwise create our own
  if (existsSync(customWebpackConfigFilePath)) {
    config = require(customWebpackConfigFilePath);

    if (!Array.isArray(config)) {
      config = [config];
    }

    // Ensure functions are resolved to objects
    config = config.map(combine);
  } else {
    config = [createWebpackConfig()];

    // Duplicate the config if the project expects an additional modern JS build
    if (addModernJS) {
      config.push(createWebpackConfig({ isModernJS: true }));
    }
  }

  return config;
};

function createWebpackConfig({ isModernJS } = {}) {
  const { pkg, root, type, webpack: projectWebpackConfig } = getProjectConfig();
  const { entry, extractCSS, from, staticDir, to, useCSSModules } = getBuildConfig();
  const isProd = process.env.NODE_ENV === 'production';

  const config = merge(
    {
      mode: isProd ? 'production' : 'development',
      cache: true,
      entry: {
        index: [join(root, from, entry)]
      },
      devtool: 'source-map',
      output: {
        path: join(root, to),
        publicPath: '/',
        filename: isModernJS ? '[name]_modern.js' : '[name].js',
        jsonpFunction: `webpackJsonp__${pkg.name.replace(/[^a-z]/g, '_')}`,
        // The update file hash was causing 404s and full page reloads.
        // This will make the file name more predictable.
        // See https://github.com/webpack/webpack-dev-server/issues/79#issuecomment-244596129
        hotUpdateChunkFilename: 'hot/hot-update.js',
        hotUpdateMainFilename: 'hot/hot-update.json'
      },
      module: {
        rules: [
          {
            __hint__: 'scripts',
            test: /\.m?js$/,
            include: [resolve(root, from)],
            loader: require.resolve('babel-loader'),
            options: getBabelConfig({ isModernJS })
          },
          {
            __hint__: 'styles',
            test: /\.(css|scss)$/,
            use: [
              extractCSS
                ? MiniCssExtractPlugin.loader
                : {
                    loader: require.resolve('style-loader')
                  },
              {
                loader: require.resolve('css-loader'),
                options: {
                  localsConvention: 'camelCase',
                  modules: useCSSModules && {
                    context: __dirname,
                    //  ^^^ https://github.com/webpack-contrib/css-loader/issues/413#issuecomment-299578180
                    localIdentName: `${isProd ? '' : '[folder]-[name]__[local]-'}[hash:base64:6]`,
                    hashPrefix: `${pkg.name}@${pkg.version}`
                  },
                  sourceMap: !isProd
                }
              },
              {
                loader: require.resolve('sass-loader')
              }
            ]
          },
          {
            test: /\.(jpg|png|gif|mp4|m4v|flv|mp3|wav|m4a)$/,
            loader: require.resolve('file-loader'),
            options: {
              name: '[name]-[hash].[ext]'
            }
          },
          {
            test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('url-loader'),
            options: {
              limit: URL_LOADER_LIMIT,
              mimetype: 'application/font-woff'
            }
          },
          {
            test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('url-loader'),
            options: {
              limit: URL_LOADER_LIMIT,
              mimetype: 'application/octet-stream'
            }
          },
          {
            test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('file-loader')
          },
          {
            test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('url-loader'),
            options: {
              limit: URL_LOADER_LIMIT,
              mimetype: 'image/svg+xml'
            }
          },
          {
            test: /\.html$/,
            loader: require.resolve('html-loader')
          }
        ]
      },
      plugins: [
        new EnvironmentPlugin(Object.keys(process.env)),
        extractCSS
          ? new MiniCssExtractPlugin({
              filename: `[name].css`
            })
          : null,
        extractCSS
          ? new OptimizeCssAssetsPlugin({
              assetNameRegExp: /\.css$/,
              cssProcessorOptions: {
                safe: true,
                discardComments: { removeAll: isProd },
                normalizeUrl: { stripWWW: false },
                canPrint: false
              }
            })
          : null,
        new CopyPlugin({
          patterns: [
            {
              from: join(root, staticDir)
            }
          ]
        })
      ].filter(x => x),
      optimization: {
        minimizer: [],
        namedModules: !isProd
      }
    },
    PROJECT_TYPES_CONFIG[type],
    projectWebpackConfig
  );

  if (isProd) {
    config.optimization.minimize = true;
    config.optimization.minimizer.push(
      new TerserPlugin({
        extractComments: false,
        terserOptions: {
          output: {
            comments: /@license/i
          }
        }
      })
    );
  }

  // Cleanup hints
  config.module.rules.forEach(rule => {
    if (rule.__hint__) {
      delete rule.__hint__;
    }
  });

  return config;
}

function getHintedRule(config, hint) {
  return config.module.rules.find(rule => rule.__hint__ === hint);
}
<commit_msg>Enable hot module reloading for mini-css-extract-plugin<commit_after>// Native
const { existsSync } = require('fs');
const { join, resolve } = require('path');

// External
const importLazy = require('import-lazy')(require);
const CopyPlugin = importLazy('copy-webpack-plugin');
const MiniCssExtractPlugin = importLazy('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = importLazy('optimize-css-assets-webpack-plugin');
const sveltePreprocess = importLazy('svelte-preprocess');
const TerserPlugin = importLazy('terser-webpack-plugin');
const { VueLoaderPlugin } = importLazy('vue-loader');
const EnvironmentPlugin = importLazy('webpack/lib/EnvironmentPlugin');

// Ours
const { combine, merge } = require('../utils/structures');
const { getBabelConfig } = require('./babel');
const { getBuildConfig } = require('./build');
const { getProjectConfig } = require('./project');

const URL_LOADER_LIMIT = 10000;
const PROJECT_TYPES_CONFIG = {
  preact: {
    resolve: {
      alias: {
      }
        react: 'preact',
        'react-dom': 'preact/compat'
    }
  },
  svelte: config => {
    const { include, loader, options } = getHintedRule(config, 'scripts');

    include.push(/(node_modules\/svelte)/);

    config.module.rules.push({
      test: /\.svelte$/,
      include,
      use: [
        {
          loader,
          options
        },
        {
          loader: require.resolve('svelte-loader'),
          options: {
            dev: config.mode === 'development',
            emitCss: getHintedRule(config, 'styles').use[0] === MiniCssExtractPlugin.loader,
            preprocess: sveltePreprocess()
          }
        }
      ]
    });

    return config;
  },
  vue: config => {
    getHintedRule(config, 'styles').use[0] = { loader: require.resolve('vue-style-loader') };

    config.module.rules.push({
      test: /\.vue$/,
      loader: require.resolve('vue-loader')
    });

    config.plugins.push(new VueLoaderPlugin());

    return config;
  }
};
const WEBPACK_CONFIG_FILE = 'webpack.config.js';

module.exports.getWebpackConfig = () => {
  const { root } = getProjectConfig();
  const { addModernJS, showDeprecations } = getBuildConfig();
  const customWebpackConfigFilePath = join(root, WEBPACK_CONFIG_FILE);

  if (showDeprecations) {
    process.traceDeprecation = true;
  } else {
    process.noDeprecation = true;
  }

  let config;

  // If the project has a webpack config file, use it, otherwise create our own
  if (existsSync(customWebpackConfigFilePath)) {
    config = require(customWebpackConfigFilePath);

    if (!Array.isArray(config)) {
      config = [config];
    }

    // Ensure functions are resolved to objects
    config = config.map(combine);
  } else {
    config = [createWebpackConfig()];

    // Duplicate the config if the project expects an additional modern JS build
    if (addModernJS) {
      config.push(createWebpackConfig({ isModernJS: true }));
    }
  }

  return config;
};

function createWebpackConfig({ isModernJS } = {}) {
  const { pkg, root, type, webpack: projectWebpackConfig } = getProjectConfig();
  const { entry, extractCSS, from, staticDir, to, useCSSModules } = getBuildConfig();
  const isProd = process.env.NODE_ENV === 'production';

  const config = merge(
    {
      mode: isProd ? 'production' : 'development',
      cache: true,
      entry: {
        index: [join(root, from, entry)]
      },
      devtool: 'source-map',
      output: {
        path: join(root, to),
        publicPath: '/',
        filename: isModernJS ? '[name]_modern.js' : '[name].js',
        jsonpFunction: `webpackJsonp__${pkg.name.replace(/[^a-z]/g, '_')}`,
        // The update file hash was causing 404s and full page reloads.
        // This will make the file name more predictable.
        // See https://github.com/webpack/webpack-dev-server/issues/79#issuecomment-244596129
        hotUpdateChunkFilename: 'hot/hot-update.js',
        hotUpdateMainFilename: 'hot/hot-update.json'
      },
      module: {
        rules: [
          {
            __hint__: 'scripts',
            test: /\.m?js$/,
            include: [resolve(root, from)],
            loader: require.resolve('babel-loader'),
            options: getBabelConfig({ isModernJS })
          },
          {
            __hint__: 'styles',
            test: /\.(css|scss)$/,
            use: [
              extractCSS
                ? {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                      hmr: !isProd
                    }
                  }
                : {
                    loader: require.resolve('style-loader')
                  },
              {
                loader: require.resolve('css-loader'),
                options: {
                  localsConvention: 'camelCase',
                  modules: useCSSModules && {
                    context: __dirname,
                    //  ^^^ https://github.com/webpack-contrib/css-loader/issues/413#issuecomment-299578180
                    localIdentName: `${isProd ? '' : '[folder]-[name]__[local]-'}[hash:base64:6]`,
                    hashPrefix: `${pkg.name}@${pkg.version}`
                  },
                  sourceMap: !isProd
                }
              },
              {
                loader: require.resolve('sass-loader')
              }
            ]
          },
          {
            test: /\.(jpg|png|gif|mp4|m4v|flv|mp3|wav|m4a)$/,
            loader: require.resolve('file-loader'),
            options: {
              name: '[name]-[hash].[ext]'
            }
          },
          {
            test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('url-loader'),
            options: {
              limit: URL_LOADER_LIMIT,
              mimetype: 'application/font-woff'
            }
          },
          {
            test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('url-loader'),
            options: {
              limit: URL_LOADER_LIMIT,
              mimetype: 'application/octet-stream'
            }
          },
          {
            test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('file-loader')
          },
          {
            test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
            loader: require.resolve('url-loader'),
            options: {
              limit: URL_LOADER_LIMIT,
              mimetype: 'image/svg+xml'
            }
          },
          {
            test: /\.html$/,
            loader: require.resolve('html-loader')
          }
        ]
      },
      plugins: [
        new EnvironmentPlugin(Object.keys(process.env)),
        extractCSS
          ? new MiniCssExtractPlugin({
              filename: `[name].css`
            })
          : null,
        extractCSS
          ? new OptimizeCssAssetsPlugin({
              assetNameRegExp: /\.css$/,
              cssProcessorOptions: {
                safe: true,
                discardComments: { removeAll: isProd },
                normalizeUrl: { stripWWW: false },
                canPrint: false
              }
            })
          : null,
        new CopyPlugin({
          patterns: [
            {
              from: join(root, staticDir)
            }
          ]
        })
      ].filter(x => x),
      optimization: {
        minimizer: [],
        namedModules: !isProd
      }
    },
    PROJECT_TYPES_CONFIG[type],
    projectWebpackConfig
  );

  if (isProd) {
    config.optimization.minimize = true;
    config.optimization.minimizer.push(
      new TerserPlugin({
        extractComments: false,
        terserOptions: {
          output: {
            comments: /@license/i
          }
        }
      })
    );
  }

  // Cleanup hints
  config.module.rules.forEach(rule => {
    if (rule.__hint__) {
      delete rule.__hint__;
    }
  });

  return config;
}

function getHintedRule(config, hint) {
  return config.module.rules.find(rule => rule.__hint__ === hint);
}
<|endoftext|>


<commit_before>import keyBy from 'lodash/keyBy';

export default keyBy([
  {
    id: 'Hairstyles',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '10': {
        left: 194,
        top: 57
      },
      '11': {
        left: 197,
        top: 65
      },
      '12': {
        left: 195,
        top: 71
      },
      '13': {
        left: 195,
        top: 71
      },
      '14': {
        left: 194,
        top: 76
      },
      '15': {
        left: 200,
        top: 56
      },
      '16': {
        left: 194,
        top: 54
      },
      '17': {
        left: 195,
        top: 65
      },
      '18': {
        left: 195,
        top: 57
      },
      '19': {
        left: 194,
        top: 46
      },
      '20': {
        left: 195,
        top: 56
      },
      '21': {
        left: 194,
        top: 56
      },
      '22': {
        left: 174,
        top: 46
      },
      '23': {
        left: 193,
        top: 39
      },
      '01': {
        left: 194,
        top: 36
      },
      '02': {
        left: 194,
        top: 48
      },
      '03': {
        left: 194,
        top: 28
      },
      '04': {
        left: 194,
        top: 42
      },
      '05': {
        left: 193,
        top: 38
      },
      '06': {
        left: 194,
        top: 48
      },
      '07': {
        left: 194,
        top: 47
      },
      '08': {
        left: 195,
        top: 42
      },
      '09': {
        left: 196,
        top: 39
      }
    }
  },
  {
    id: 'Beards',
    required: false,
    clickable: true,
    sortOrder: 43,
    styles: {
      '10': {
        left: 212,
        top: 273
      },
      '01': {
        left: 207,
        top: 309
      },
      '02': {
        left: 209,
        top: 312
      },
      '03': {
        left: 208,
        top: 311
      },
      '04': {
        left: 209,
        top: 313
      },
      '05': {
        left: 209,
        top: 310
      },
      '06': {
        left: 209,
        top: 312
      },
      '07': {
        left: 209,
        top: 313
      },
      '08': {
        left: 209,
        top: 312
      },
      '09': {
        left: 211,
        top: 270
      }
    }
  },
  {
    id: 'Body',
    required: true,
    clickable: false,
    sortOrder: 0,
    styles: {
      '01': {
        left: 148,
        top: 165
      }
    }
  },
  {
    id: 'Coats',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 60,
        top: 411
      },
      '02': {
        left: 60,
        top: 400
      },
      '03': {
        left: 59,
        top: 400
      },
      '04': {
        left: 58,
        top: 397
      },
      '05': {
        left: 60,
        top: 403
      },
      '06': {
        left: 60,
        top: 399
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Glasses',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '10': {
        left: 226,
        top: 296
      },
      '01': {
        left: 226,
        top: 299
      },
      '02': {
        left: 225,
        top: 298
      },
      '03': {
        left: 228,
        top: 295
      },
      '04': {
        left: 228,
        top: 295
      },
      '05': {
        left: 225,
        top: 295
      },
      '06': {
        left: 227,
        top: 296
      },
      '07': {
        left: 226,
        top: 296
      },
      '08': {
        left: 224,
        top: 296
      },
      '09': {
        left: 227,
        top: 297
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Scarves',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 155,
        top: 434
      },
      '02': {
        left: 153,
        top: 426
      },
      '03': {
        left: 154,
        top: 426
      },
      '04': {
        left: 155,
        top: 429
      },
      '05': {
        left: 155,
        top: 426
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: ['T-shirts', 'Sweatshirts']
      },
      warning: {
        types: [
          '01',
          '02',
          '03'
        ],
        assets: [
          'Ties'
        ],
        message: 'Sorry but you can\'t use ties with this shirt'
      }
    },
    styles: {
      '01': {
        left: 110,
        top: 465
      },
      '02': {
        left: 111,
        top: 465
      },
      '03': {
        left: 111,
        top: 465
      },
      '04': {
        left: 110,
        top: 465
      },
      '05': {
        left: 109,
        top: 465
      },
      '06': {
        left: 110,
        top: 465
      },
      '07': {
        left: 110,
        top: 465
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Sweatshirts',
    required: false,
    clickable: true,
    sortOrder: 30,
    conflicts: {
      replace: {
        assets: ['T-shirts', 'Shirts']
      }
    },
    styles: {
      '01': {
        left: 114,
        top: 453
      },
      '02': {
        left: 115,
        top: 451
      },
      '03': {
        left: 116,
        top: 447
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'T-shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: ['Shirts', 'Sweatshirts']
      }
    },
    styles: {
      '01': {
        left: 110,
        top: 473
      },
      '02': {
        left: 113,
        top: 473
      },
      '03': {
        left: 110,
        top: 472
      },
      '04': {
        left: 110,
        top: 470
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Ties',
    required: false,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 308,
        top: 536
      },
      '02': {
        left: 309,
        top: 539
      },
      '03': {
        left: 310,
        top: 536
      },
      '04': {
        left: 310,
        top: 539
      },
      '05': {
        left: 310,
        top: 539
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Eyes',
    required: true,
    clickable: true,
    sortOrder: 70,
    styles: {
      '01': {
        left: 286,
        top: 288
      },
      '02': {
        left: 286,
        top: 285
      },
      '03': {
        left: 284,
        top: 294
      }
    }
  },
  {
    id: 'Jackets',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 62,
        top: 406
      },
      '02': {
        left: 62,
        top: 411
      },
      '03': {
        left: 62,
        top: 406
      },
      '04': {
        left: 62,
        top: 406
      },
      '05': {
        left: 64,
        top: 406
      },
      '06': {
        left: 62,
        top: 406
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Mouths',
    required: true,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 311,
        top: 405
      },
      '02': {
        left: 311,
        top: 407
      },
      '03': {
        left: 311,
        top: 407
      }
    }
  },
  {
    id: 'Vests',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 61,
        top: 411
      },
      '02': {
        left: 62,
        top: 408
      },
      '03': {
        left: 63,
        top: 404
      },
      '04': {
        left: 63,
        top: 404
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Hats',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '01': {
        left: 113,
        top: -78
      },
      '02': {
        left: 116,
        top: -78
      },
      '03': {
        left: 121,
        top: -78
      },
      '04': {
        left: 116,
        top: -67
      },
      '05': {
        left: 119,
        top: -78
      },
      '06': {
        left: 119,
        top: -78
      },
      '07': {
        left: 121,
        top: -78
      },
      '08': {
        left: 122,
        top: -78
      },
      '09': {
        left: 116,
        top: -68
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Suits',
    required: false,
    clickable: true,
    sortOrder: 41,
    styles: {
      '01': {
        left: 63,
        top: 411
      },
      '02': {
        left: 64,
        top: 406
      }
    },
    menuItem: 'Clothes'
  }
], 'id');
<commit_msg>Add Jackets/Coats replace<commit_after>import keyBy from 'lodash/keyBy';

export default keyBy([
  {
    id: 'Hairstyles',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '10': {
        left: 194,
        top: 57
      },
      '11': {
        left: 197,
        top: 65
      },
      '12': {
        left: 195,
        top: 71
      },
      '13': {
        left: 195,
        top: 71
      },
      '14': {
        left: 194,
        top: 76
      },
      '15': {
        left: 200,
        top: 56
      },
      '16': {
        left: 194,
        top: 54
      },
      '17': {
        left: 195,
        top: 65
      },
      '18': {
        left: 195,
        top: 57
      },
      '19': {
        left: 194,
        top: 46
      },
      '20': {
        left: 195,
        top: 56
      },
      '21': {
        left: 194,
        top: 56
      },
      '22': {
        left: 174,
        top: 46
      },
      '23': {
        left: 193,
        top: 39
      },
      '01': {
        left: 194,
        top: 36
      },
      '02': {
        left: 194,
        top: 48
      },
      '03': {
        left: 194,
        top: 28
      },
      '04': {
        left: 194,
        top: 42
      },
      '05': {
        left: 193,
        top: 38
      },
      '06': {
        left: 194,
        top: 48
      },
      '07': {
        left: 194,
        top: 47
      },
      '08': {
        left: 195,
        top: 42
      },
      '09': {
        left: 196,
        top: 39
      }
    }
  },
  {
    id: 'Beards',
    required: false,
    clickable: true,
    sortOrder: 43,
    styles: {
      '10': {
        left: 212,
        top: 273
      },
      '01': {
        left: 207,
        top: 309
      },
      '02': {
        left: 209,
        top: 312
      },
      '03': {
        left: 208,
        top: 311
      },
      '04': {
        left: 209,
        top: 313
      },
      '05': {
        left: 209,
        top: 310
      },
      '06': {
        left: 209,
        top: 312
      },
      '07': {
        left: 209,
        top: 313
      },
      '08': {
        left: 209,
        top: 312
      },
      '09': {
        left: 211,
        top: 270
      }
    }
  },
  {
    id: 'Body',
    required: true,
    clickable: false,
    sortOrder: 0,
    styles: {
      '01': {
        left: 148,
        top: 165
      }
    }
  },
  {
    id: 'Coats',
    required: false,
    clickable: true,
    sortOrder: 42,
    conflicts: {
      replace: {
        assets: ['Jackets']
      }
    },
    styles: {
      '01': {
        left: 60,
        top: 411
      },
      '02': {
        left: 60,
        top: 400
      },
      '03': {
        left: 59,
        top: 400
      },
      '04': {
        left: 58,
        top: 397
      },
      '05': {
        left: 60,
        top: 403
      },
      '06': {
        left: 60,
        top: 399
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Glasses',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '10': {
        left: 226,
        top: 296
      },
      '01': {
        left: 226,
        top: 299
      },
      '02': {
        left: 225,
        top: 298
      },
      '03': {
        left: 228,
        top: 295
      },
      '04': {
        left: 228,
        top: 295
      },
      '05': {
        left: 225,
        top: 295
      },
      '06': {
        left: 227,
        top: 296
      },
      '07': {
        left: 226,
        top: 296
      },
      '08': {
        left: 224,
        top: 296
      },
      '09': {
        left: 227,
        top: 297
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Scarves',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 155,
        top: 434
      },
      '02': {
        left: 153,
        top: 426
      },
      '03': {
        left: 154,
        top: 426
      },
      '04': {
        left: 155,
        top: 429
      },
      '05': {
        left: 155,
        top: 426
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: ['T-shirts', 'Sweatshirts']
      },
      warning: {
        types: [
          '01',
          '02',
          '03'
        ],
        assets: [
          'Ties'
        ],
        message: 'Sorry but you can\'t use ties with this shirt'
      }
    },
    styles: {
      '01': {
        left: 110,
        top: 465
      },
      '02': {
        left: 111,
        top: 465
      },
      '03': {
        left: 111,
        top: 465
      },
      '04': {
        left: 110,
        top: 465
      },
      '05': {
        left: 109,
        top: 465
      },
      '06': {
        left: 110,
        top: 465
      },
      '07': {
        left: 110,
        top: 465
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Sweatshirts',
    required: false,
    clickable: true,
    sortOrder: 30,
    conflicts: {
      replace: {
        assets: ['T-shirts', 'Shirts']
      }
    },
    styles: {
      '01': {
        left: 114,
        top: 453
      },
      '02': {
        left: 115,
        top: 451
      },
      '03': {
        left: 116,
        top: 447
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'T-shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: ['Shirts', 'Sweatshirts']
      }
    },
    styles: {
      '01': {
        left: 110,
        top: 473
      },
      '02': {
        left: 113,
        top: 473
      },
      '03': {
        left: 110,
        top: 472
      },
      '04': {
        left: 110,
        top: 470
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Ties',
    required: false,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 308,
        top: 536
      },
      '02': {
        left: 309,
        top: 539
      },
      '03': {
        left: 310,
        top: 536
      },
      '04': {
        left: 310,
        top: 539
      },
      '05': {
        left: 310,
        top: 539
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Eyes',
    required: true,
    clickable: true,
    sortOrder: 70,
    styles: {
      '01': {
        left: 286,
        top: 288
      },
      '02': {
        left: 286,
        top: 285
      },
      '03': {
        left: 284,
        top: 294
      }
    }
  },
  {
    id: 'Jackets',
    required: false,
    clickable: true,
    sortOrder: 40,
    conflicts: {
      replace: {
        assets: ['Coats']
      }
    },
    styles: {
      '01': {
        left: 62,
        top: 406
      },
      '02': {
        left: 62,
        top: 411
      },
      '03': {
        left: 62,
        top: 406
      },
      '04': {
        left: 62,
        top: 406
      },
      '05': {
        left: 64,
        top: 406
      },
      '06': {
        left: 62,
        top: 406
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Mouths',
    required: true,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 311,
        top: 405
      },
      '02': {
        left: 311,
        top: 407
      },
      '03': {
        left: 311,
        top: 407
      }
    }
  },
  {
    id: 'Vests',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 61,
        top: 411
      },
      '02': {
        left: 62,
        top: 408
      },
      '03': {
        left: 63,
        top: 404
      },
      '04': {
        left: 63,
        top: 404
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Hats',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '01': {
        left: 113,
        top: -78
      },
      '02': {
        left: 116,
        top: -78
      },
      '03': {
        left: 121,
        top: -78
      },
      '04': {
        left: 116,
        top: -67
      },
      '05': {
        left: 119,
        top: -78
      },
      '06': {
        left: 119,
        top: -78
      },
      '07': {
        left: 121,
        top: -78
      },
      '08': {
        left: 122,
        top: -78
      },
      '09': {
        left: 116,
        top: -68
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Suits',
    required: false,
    clickable: true,
    sortOrder: 41,
    styles: {
      '01': {
        left: 63,
        top: 411
      },
      '02': {
        left: 64,
        top: 406
      }
    },
    menuItem: 'Clothes'
  }
], 'id');
<|endoftext|>


<commit_before>import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

const middlewares = [ thunk ];
if (__DEV__) {
  const logger = require('redux-logger');
  middlewares.push(logger({ level: 'info' }));
}

const finalCreateStore = applyMiddleware(...middlewares)(createStore);

export default function configureStore(initialState) {
  return finalCreateStore(reducer, initialState);
}
<commit_msg>Handle global__DEV__<commit_after>import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

const middlewares = [ thunk ];
if (typeof __DEV__ !== 'undefined' && __DEV__) {
  const logger = require('redux-logger');
  middlewares.push(logger({ level: 'info' }));
}

const finalCreateStore = applyMiddleware(...middlewares)(createStore);

export default function configureStore(initialState) {
  return finalCreateStore(reducer, initialState);
}
<|endoftext|>


<commit_before>const random = require('./random');

describe('getNumberBetween', () => {

  test('is exclusive', () => {
    let number = random.getNumberBetween(0, 1);
    expect(number).toBeGreaterThan(0);
    expect(number).toBeLessThan(1);
  });

});
<commit_msg>Replace some CRLFs with LFs.<commit_after>const random = require('./random');

describe('getNumberBetween', () => {

  test('is exclusive', () => {
    let number = random.getNumberBetween(0, 1);
    expect(number).toBeGreaterThan(0);
    expect(number).toBeLessThan(1);
  });

});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as TracksActions from '../actions/tracks';
import * as TrackActions from '../actions/track';
import * as MeasureActions from '../actions/measure';
import * as PlayingIndexActions from '../actions/playingIndex';
import * as CursorActions from '../actions/cursor';

import Soundfont from 'soundfont-player';
import audioContext from '../util/audioContext';

import TabStaff from '../components/TabStaff';
import EditorArea from '../components/editor/EditorArea';
import TimeSignatureModal from '../components/editor/TimeSignatureModal';
import TuningModal from '../components/editor/TuningModal';
import BpmModal from '../components/editor/BpmModal';
import Playback from '../components/Playback';
import Metronome from '../components/Metronome';

const Actions = Object.assign(TracksActions, TrackActions, MeasureActions, PlayingIndexActions, CursorActions);

class App extends Component {
  constructor(props) {
    super(props);

    if (typeof window !== 'undefined') {
      window.addEventListener('keydown', this.handleKeyPress);
      window.addEventListener('resize', this.handleResize);
    }

    this.state = {
      openModal: null
    };
  }

  componentWillMount() {
    this.loadSoundfont(this.props.instrument);
  }

  componentWillReceiveProps(nextProps) {
    const { playingIndex } = nextProps;

    if(this.props.playingIndex && playingIndex) {
      if(playingIndex.noteIndex !== this.props.playingIndex.noteIndex
          || playingIndex.measureIndex !== this.props.playingIndex.measureIndex) {
        this.updateScrollPosition(nextProps.playingIndex, nextProps.measures);
      }
    } else if(this.props.instrument !== nextProps.instrument) {
      this.loadSoundfont(nextProps.instrument);
    }
  }

  loadSoundfont = (instrument) => {
    Soundfont.loadBuffers(audioContext, instrument).then((buffers) => {
      this.setState({
        buffers: Object.assign({}, this.state.buffers, { [instrument]: buffers })
      });
    });

    Soundfont.loadBuffers(audioContext, 'woodblock').then((woodblockBuffers) => {
      this.setState({ woodblockBuffers });
    });
  };

  loadSoundfont2 = (instrument) => {
    const nonbuffers = localStorage.getItem(instrument);

    if(nonbuffers) {
      Soundfont.decodeArray(audioContext, JSON.parse(nonbuffers)).then((buffers) => {
        this.setState({
          buffers: Object.assign({}, this.state.buffers, { [instrument]: buffers })
        });
      });
    } else {
      Soundfont.loadNonBuffers(audioContext, instrument).then((ns) => {
        localStorage.setItem(instrument, JSON.stringify(ns));
        Soundfont.decodeArray(audioContext, ns).then((buffers) => {
          this.setState({
            buffers: Object.assign({}, this.state.buffers, { [instrument]: buffers })
          });
        });
      });
    }
  };

  handleResize = () => {
    this.props.actions.resize();
  };

  getXOfCurrentNote = (playingIndex, measures) => {
    const { measureIndex, noteIndex } = playingIndex;
    const xOfMeasures = measures.reduce((acc, curr, i) => {
      if(i >= measureIndex) {
        return acc;
      }
      return acc + curr.width;
    }, 0);

    return xOfMeasures + 55 * noteIndex;
  };

  getYOfCurrentNote = (playingIndex, measures) => {
    const position = measures[playingIndex.measureIndex];
    return (position.rowIndex) * (27 * this.props.tuning.length) + 50;
  };

  updateScrollPosition = (playingIndex, measures) => {
    if(this.props.layout === 'linear') {
      const x = this.getXOfCurrentNote(playingIndex, measures);
      const { scrollX, innerWidth } = window;

      if(x > innerWidth + scrollX - 200) {
        window.scroll(x - 200, 0);
      }
    } else {
      const y = this.getYOfCurrentNote(playingIndex, measures);
      const { innerHeight, scrollY } = window;

      if(y > innerHeight + scrollY - 270) {
        window.scroll(0, y - 100);
      }
    }
  };

  handleStop = () => {
    this.props.actions.setCursor({
      measureIndex: this.props.playingIndex.measureIndex,
      noteIndex: this.props.playingIndex.noteIndex,
      stringIndex: this.props.cursor.stringIndex
    });
    this.props.actions.setPlayingIndex(null);
  };

  handlePlay = () => {
    if(this.props.playingIndex || !this.state.buffers) {
      return;
    }

    const { noteIndex, measureIndex } = this.props.cursor;

    this.props.actions.setPlayingIndex({
      measureIndex,
      noteIndex
    });
  };

  getCurrentNote = () => {
    const { measures } = this.props;
    const { measureIndex, noteIndex } = this.props.cursor;

    return measures[measureIndex].notes[noteIndex];
  };

  navigateCursor = (event) => {
    event.preventDefault();
    if(event.keyCode === 39) { // right arrow
      const { measures } = this.props;
      const { measureIndex, noteIndex } = this.props.cursor;
      if(measureIndex === measures.length - 1 &&
        (noteIndex === measures[measureIndex].notes.length - 1 || measures[measureIndex].notes.length === 0)
      ) {
        this.props.actions.insertMeasure();
      }
      this.props.actions.moveCursorRight();
    } else if(event.keyCode === 37) { // left arrow
      this.props.actions.moveCursorLeft();
    } else if(event.keyCode === 38) { // up arrow
      this.props.actions.moveCursorUp();
    } else if(event.keyCode === 40) { // down arrow
      this.props.actions.moveCursorDown();
    }
  };

  editNote = (fret) => {
    this.props.actions.changeNote(this.props.cursor, fret);
  };

  changeNoteLength = (duration) => {
    this.props.actions.changeNoteLength(this.props.cursor, duration);
  };

  deleteNote = () => {
    const { measureIndex } = this.props.cursor;
    let notes = this.props.measures[measureIndex].notes;

    if(notes.length > 0) {
      this.props.actions.deleteNote(this.props.cursor);
    } else if(notes.length === 0) {
      this.props.actions.deleteMeasure(measureIndex);
    }
  };

  insertNote = () => {
    this.props.actions.insertNote(this.props.cursor);
  };

  pasteNote = () => {
    if(this.props.clipboard) {
      event.preventDefault();
      this.props.actions.pasteNote(this.props.cursor, this.props.clipboard);
    }
  };

  handleKeyPress = (event) => {
    if(this.state.openModal || (this.props.playingIndex && event.keyCode !== 32)) {
      return;
    }

    if((event.metaKey || event.ctrlKey) && event.keyCode === 67) { // cmd/ctrl+c
      event.preventDefault();
      return this.props.actions.copyNote(this.getCurrentNote());
    }
    if((event.metaKey || event.ctrlKey) && event.keyCode === 86) { // cmd/ctrl+v
      return this.pasteNote();
    }
    if((event.metaKey || event.ctrlKey) && event.keyCode === 88) { // cmd/ctrl+x
      event.preventDefault();
      this.props.actions.cutNote(this.props.cursor, this.getCurrentNote());
    }

    if(event.keyCode <= 57 && event.keyCode >= 48 && !event.metaKey) {
      return this.editNote(event.keyCode - 48);
    } else if(event.keyCode === 82 && !event.metaKey && !event.ctrlKey) {
      this.props.actions.changeNote(this.props.cursor, 'rest');
    } else if(event.keyCode === 8) { // delete
      event.preventDefault();
      this.deleteNote();
    } else if(event.keyCode === 69) { // e
      return this.changeNoteLength('e');
    } else if(event.keyCode === 83) { // s
      return this.changeNoteLength('s');
    } else if(event.keyCode === 81) { // q
      return this.changeNoteLength('q');
    } else if(event.keyCode === 87) { // w
      return this.changeNoteLength('w');
    } else if(event.keyCode === 72 && !event.ctrlKey) { // h
      return this.changeNoteLength('h');
    } else if(event.keyCode === 73 && !event.metaKey) { // i
      return this.insertNote();
    } else if(event.keyCode === 32) { // spacebar
      event.preventDefault();
      return this.props.playingIndex ? this.handleStop() : this.handlePlay();
    } else if(event.keyCode === 190) { // period
      this.props.actions.toggleNoteDotted(this.props.cursor);
    } else if(event.shiftKey && event.keyCode === 187) { // plus
      return this.props.actions.increaseNoteLength(this.props.cursor);
    } else if(event.keyCode === 189) { // minus
      return this.props.actions.decreaseNoteLength(this.props.cursor);
    } else if(event.shiftKey && event.keyCode === 222) { // "
      this.props.actions.toggleNoteTremolo(this.props.cursor);
    } else if(event.keyCode === 86) { // v
      this.props.actions.toggleNoteVibrato(this.props.cursor);
    } else if(event.shiftKey && event.keyCode === 78) { // N
      this.props.actions.toggleNoteTrill(this.props.cursor);
    } else {
      return this.navigateCursor(event);
    }
  };

  openModal = (openModal) => {
    this.setState({ openModal });
  };

  closeModal = () => {
    this.setState({ openModal: null });
  };

  render() {
    const { openModal, buffers, woodblockBuffers } = this.state;

    return (

        { this.props.playingIndex ? <Playback buffers={buffers} /> : null}
        { this.props.playingIndex && this.props.metronome ? <Metronome buffers={woodblockBuffers} /> : null}
        <EditorArea handlePlay={this.handlePlay} openModal={this.openModal} />
        <TabStaff />
        <TimeSignatureModal isOpen={openModal === 'timeSig'} closeModal={this.closeModal} />
        <TuningModal isOpen={openModal === 'tuning'} closeModal={this.closeModal} />
        <BpmModal isOpen={openModal === 'bpm'} closeModal={this.closeModal} />

    );
  }
}

function mapStateToProps(state) {
  return {
    measures: state.tracks[state.currentTrackIndex].measures,
    clipboard: state.clipboard,
    layout: state.layout,
    playingIndex: state.playingIndex,
    cursor: state.cursor,
    tuning: state.tracks[state.currentTrackIndex].tuning,
    instrument: state.tracks[state.currentTrackIndex].instrument,
    metronome: state.metronome
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(App);
<commit_msg>last commit accidentally overrode every keyboard shortcut that wasnt specified in the handler, which breaks a lot of default browser shortcuts<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as TracksActions from '../actions/tracks';
import * as TrackActions from '../actions/track';
import * as MeasureActions from '../actions/measure';
import * as PlayingIndexActions from '../actions/playingIndex';
import * as CursorActions from '../actions/cursor';

import Soundfont from 'soundfont-player';
import audioContext from '../util/audioContext';

import TabStaff from '../components/TabStaff';
import EditorArea from '../components/editor/EditorArea';
import TimeSignatureModal from '../components/editor/TimeSignatureModal';
import TuningModal from '../components/editor/TuningModal';
import BpmModal from '../components/editor/BpmModal';
import Playback from '../components/Playback';
import Metronome from '../components/Metronome';

const Actions = Object.assign(TracksActions, TrackActions, MeasureActions, PlayingIndexActions, CursorActions);

class App extends Component {
  constructor(props) {
    super(props);

    if (typeof window !== 'undefined') {
      window.addEventListener('keydown', this.handleKeyPress);
      window.addEventListener('resize', this.handleResize);
    }

    this.state = {
      openModal: null
    };
  }

  componentWillMount() {
    this.loadSoundfont(this.props.instrument);
  }

  componentWillReceiveProps(nextProps) {
    const { playingIndex } = nextProps;

    if(this.props.playingIndex && playingIndex) {
      if(playingIndex.noteIndex !== this.props.playingIndex.noteIndex
          || playingIndex.measureIndex !== this.props.playingIndex.measureIndex) {
        this.updateScrollPosition(nextProps.playingIndex, nextProps.measures);
      }
    } else if(this.props.instrument !== nextProps.instrument) {
      this.loadSoundfont(nextProps.instrument);
    }
  }

  loadSoundfont = (instrument) => {
    Soundfont.loadBuffers(audioContext, instrument).then((buffers) => {
      this.setState({
        buffers: Object.assign({}, this.state.buffers, { [instrument]: buffers })
      });
    });

    Soundfont.loadBuffers(audioContext, 'woodblock').then((woodblockBuffers) => {
      this.setState({ woodblockBuffers });
    });
  };

  loadSoundfont2 = (instrument) => {
    const nonbuffers = localStorage.getItem(instrument);

    if(nonbuffers) {
      Soundfont.decodeArray(audioContext, JSON.parse(nonbuffers)).then((buffers) => {
        this.setState({
          buffers: Object.assign({}, this.state.buffers, { [instrument]: buffers })
        });
      });
    } else {
      Soundfont.loadNonBuffers(audioContext, instrument).then((ns) => {
        localStorage.setItem(instrument, JSON.stringify(ns));
        Soundfont.decodeArray(audioContext, ns).then((buffers) => {
          this.setState({
            buffers: Object.assign({}, this.state.buffers, { [instrument]: buffers })
          });
        });
      });
    }
  };

  handleResize = () => {
    this.props.actions.resize();
  };

  getXOfCurrentNote = (playingIndex, measures) => {
    const { measureIndex, noteIndex } = playingIndex;
    const xOfMeasures = measures.reduce((acc, curr, i) => {
      if(i >= measureIndex) {
        return acc;
      }
      return acc + curr.width;
    }, 0);

    return xOfMeasures + 55 * noteIndex;
  };

  getYOfCurrentNote = (playingIndex, measures) => {
    const position = measures[playingIndex.measureIndex];
    return (position.rowIndex) * (27 * this.props.tuning.length) + 50;
  };

  updateScrollPosition = (playingIndex, measures) => {
    if(this.props.layout === 'linear') {
      const x = this.getXOfCurrentNote(playingIndex, measures);
      const { scrollX, innerWidth } = window;

      if(x > innerWidth + scrollX - 200) {
        window.scroll(x - 200, 0);
      }
    } else {
      const y = this.getYOfCurrentNote(playingIndex, measures);
      const { innerHeight, scrollY } = window;

      if(y > innerHeight + scrollY - 270) {
        window.scroll(0, y - 100);
      }
    }
  };

  handleStop = () => {
    this.props.actions.setCursor({
      measureIndex: this.props.playingIndex.measureIndex,
      noteIndex: this.props.playingIndex.noteIndex,
      stringIndex: this.props.cursor.stringIndex
    });
    this.props.actions.setPlayingIndex(null);
  };

  handlePlay = () => {
    if(this.props.playingIndex || !this.state.buffers) {
      return;
    }

    const { noteIndex, measureIndex } = this.props.cursor;

    this.props.actions.setPlayingIndex({
      measureIndex,
      noteIndex
    });
  };

  getCurrentNote = () => {
    const { measures } = this.props;
    const { measureIndex, noteIndex } = this.props.cursor;

    return measures[measureIndex].notes[noteIndex];
  };

  navigateCursor = (event) => {
    event.preventDefault();
    if(event.keyCode === 39) { // right arrow
      const { measures } = this.props;
      const { measureIndex, noteIndex } = this.props.cursor;
      if(measureIndex === measures.length - 1 &&
        (noteIndex === measures[measureIndex].notes.length - 1 || measures[measureIndex].notes.length === 0)
      ) {
        this.props.actions.insertMeasure();
      }
      this.props.actions.moveCursorRight();
    } else if(event.keyCode === 37) { // left arrow
      this.props.actions.moveCursorLeft();
    } else if(event.keyCode === 38) { // up arrow
      this.props.actions.moveCursorUp();
    } else if(event.keyCode === 40) { // down arrow
      this.props.actions.moveCursorDown();
    }
  };

  editNote = (fret) => {
    this.props.actions.changeNote(this.props.cursor, fret);
  };

  changeNoteLength = (duration) => {
    this.props.actions.changeNoteLength(this.props.cursor, duration);
  };

  deleteNote = () => {
    const { measureIndex } = this.props.cursor;
    let notes = this.props.measures[measureIndex].notes;

    if(notes.length > 0) {
      this.props.actions.deleteNote(this.props.cursor);
    } else if(notes.length === 0) {
      this.props.actions.deleteMeasure(measureIndex);
    }
  };

  insertNote = () => {
    this.props.actions.insertNote(this.props.cursor);
  };

  pasteNote = () => {
    if(this.props.clipboard) {
      event.preventDefault();
      this.props.actions.pasteNote(this.props.cursor, this.props.clipboard);
    }
  };

  handleKeyPress = (event) => {
    if(this.state.openModal || (this.props.playingIndex && event.keyCode !== 32)) {
      return;
    }

    if((event.metaKey || event.ctrlKey) && event.keyCode === 67) { // cmd/ctrl+c
      event.preventDefault();
      return this.props.actions.copyNote(this.getCurrentNote());
    }
    if((event.metaKey || event.ctrlKey) && event.keyCode === 86) { // cmd/ctrl+v
      return this.pasteNote();
    }
    if((event.metaKey || event.ctrlKey) && event.keyCode === 88) { // cmd/ctrl+x
      event.preventDefault();
      this.props.actions.cutNote(this.props.cursor, this.getCurrentNote());
    }

    if(event.keyCode <= 57 && event.keyCode >= 48 && !event.metaKey) {
      return this.editNote(event.keyCode - 48);
    } else if(event.keyCode === 82 && !event.metaKey && !event.ctrlKey) {
      this.props.actions.changeNote(this.props.cursor, 'rest');
    } else if(event.keyCode === 8) { // delete
      event.preventDefault();
      this.deleteNote();
    } else if(event.keyCode === 69) { // e
      return this.changeNoteLength('e');
    } else if(event.keyCode === 83) { // s
      return this.changeNoteLength('s');
    } else if(event.keyCode === 81) { // q
      return this.changeNoteLength('q');
    } else if(event.keyCode === 87) { // w
      return this.changeNoteLength('w');
    } else if(event.keyCode === 72 && !event.ctrlKey) { // h
      return this.changeNoteLength('h');
    } else if(event.keyCode === 73 && !event.metaKey) { // i
      return this.insertNote();
    } else if(event.keyCode === 32) { // spacebar
      event.preventDefault();
      return this.props.playingIndex ? this.handleStop() : this.handlePlay();
    } else if(event.keyCode === 190) { // period
      this.props.actions.toggleNoteDotted(this.props.cursor);
    } else if(event.shiftKey && event.keyCode === 187) { // plus
      return this.props.actions.increaseNoteLength(this.props.cursor);
    } else if(event.keyCode === 189) { // minus
      return this.props.actions.decreaseNoteLength(this.props.cursor);
    } else if(event.shiftKey && event.keyCode === 222) { // "
      this.props.actions.toggleNoteTremolo(this.props.cursor);
    } else if(event.keyCode === 86) { // v
      this.props.actions.toggleNoteVibrato(this.props.cursor);
    } else if(event.shiftKey && event.keyCode === 78) { // N
      this.props.actions.toggleNoteTrill(this.props.cursor);
    } else if(event.keyCode >= 37 && event.keyCode <= 40) {
      return this.navigateCursor(event);
    }
  };

  openModal = (openModal) => {
    this.setState({ openModal });
  };

  closeModal = () => {
    this.setState({ openModal: null });
  };

  render() {
    const { openModal, buffers, woodblockBuffers } = this.state;

    return (

        { this.props.playingIndex ? <Playback buffers={buffers} /> : null}
        { this.props.playingIndex && this.props.metronome ? <Metronome buffers={woodblockBuffers} /> : null}
        <EditorArea handlePlay={this.handlePlay} openModal={this.openModal} />
        <TabStaff />
        <TimeSignatureModal isOpen={openModal === 'timeSig'} closeModal={this.closeModal} />
        <TuningModal isOpen={openModal === 'tuning'} closeModal={this.closeModal} />
        <BpmModal isOpen={openModal === 'bpm'} closeModal={this.closeModal} />

    );
  }
}

function mapStateToProps(state) {
  return {
    measures: state.tracks[state.currentTrackIndex].measures,
    clipboard: state.clipboard,
    layout: state.layout,
    playingIndex: state.playingIndex,
    cursor: state.cursor,
    tuning: state.tracks[state.currentTrackIndex].tuning,
    instrument: state.tracks[state.currentTrackIndex].instrument,
    metronome: state.metronome
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(App);
<|endoftext|>


<commit_before>'use strict'

const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const TerminalWindow = require('electron-terminal-window')

const Grid = require('grid')
const uuid = require('uuid')

let grids = []

function testWindow (bounds) {
  const ipcMain = electron.ipcMain
  return new Promise((resolve, reject) => {
    const eventId = uuid.v4()
    const win = new BrowserWindow({
      y: bounds.y,
      x: bounds.x,
      frame: false
    })
    win.loadURL(`file://${__dirname}/index.html`)
    win.maximize()
    const changedBounds = win.getContentBounds()
    ipcMain.once(eventId, (e, winSize) => {
      win.close()
      resolve(Object.assign({}, winSize, {x: changedBounds.x, y: changedBounds.y}))
    })
    setTimeout(() => win.webContents.executeJavaScript(`
      var ipcRenderer = require('electron').ipcRenderer
      ipcRenderer.send('${eventId}', {width: window.innerWidth, height: window.innerHeight})
    `), 500)
  })
  .catch(e => console.error('e:', e))
}

function findBars (bounds, workArea) {
  if (workArea.y > bounds.y) {
    return ({
      id: 'topBar',
      width: workArea.width,
      height: workArea.y - bounds.y,
      x: 0,
      y: 0
    })
  }
  if (bounds.y - workArea.y + bounds.height - workArea.height > 0) {
    return ({
      id: 'bottomBar',
      width: workArea.width,
      height: bounds.y - workArea.y + bounds.height - workArea.height,
      y: workArea.y,
      x: 0
    })
  }
  if (workArea.x > bounds.x) {
    return ({
      id: 'leftBar',
      width: workArea.x - bounds.x,
      height: workArea.height,
      x: 0,
      y: 0
    })
  }
  if (bounds.x - workArea.x + bounds.width - workArea.width > 0) {
    return ({
      id: 'rightBar',
      width: bounds.x - workArea.x + bounds.width - workArea.width,
      height: workArea.height,
      x: workArea.x + workArea.width,
      y: 0
    })
  }
}

function detectPanel (bounds) {
  const screen = electron.screen
  const displays = screen.getAllDisplays()
  return testWindow(bounds)
  .then((workArea) => findBars(bounds, workArea))
  .catch(e => console.error(e))
}

function getGrid (winId) {
  if (winId === undefined) {
    winId = BrowserWindow.getFocusedWindow()
    if (winId === null) return false
  }
  const gridIndex = Object.keys(grids)
    .filter(gridId => {
      const grid = grids[gridId]
      return grid.panes.some(p => {
        return p.wrapped.id === winId
      })
    })[0]
  return grids[gridIndex || 0]
}

function getPane (winId) {
  const grid = getGrid(winId)
  return grid.getPane(winId)
}

module.exports = {
  init: function init () {
    const screen = electron.screen
    const displays = screen.getAllDisplays()
    return Promise.all(
      displays.map(display => {
        return detectPanel(display.bounds)
        .then(panel => {
          const bounds = display.bounds
          const gridOffset = {x: bounds.x, y: bounds.y}
          const grid = new Grid(bounds.width, bounds.height, gridOffset)
          if (panel) grid.add(null, panel)
          grids.push(grid)
        })
        .catch(e => console.error('e:', e))
      })
    )
  },
  createWindow: function createWindow (gridId) {
    try {
      const grid = typeof gridId === 'number' && typeof grids[gridId] === 'object'
        ? grids[gridId] : getGrid() || grids[0]
      grid.add(TerminalWindow, {
        width: 400,
        height: 300,
        frame: false,
        skipTaskbar: true
      })
    } catch (e) {
      console.error(e)
    }
  },
  changeCurWindow: function changeCurWindow (params) {
    const focusedWindow = BrowserWindow.getFocusedWindow()
    if (!focusedWindow) return // only change focused window
    const pane = getPane(focusedWindow.id)
    if (params.x || params.y) {
      try {
        pane.changeLocation(
          params.x ? pane.x + parseInt(params.x) : pane.x,
          params.y ? pane.y + parseInt(params.y) : pane.y
        )
      } catch(e) {
        console.error(e)
        // TODO: some visual indication (flash border red?)
      }
    } else { // TODO: support both
      try {
        pane.changeSize(
          params.width ? pane.width + parseInt(params.width) : pane.width,
          params.height ? pane.height + parseInt(params.height) : pane.height
        )
      } catch(e) {
        console.error(e)
        // TODO: some visual indication (flash border red?)
      }
    }
  },
  maxSize: function maxSize (params) {
    const focusedWindow = BrowserWindow.getFocusedWindow()
    if (!focusedWindow) return // only change focused window
    const pane = getPane(focusedWindow.id)
    pane.maxSize(params)
  },
  maxLoc: function maxLoc (params) {
    const focusedWindow = BrowserWindow.getFocusedWindow()
    if (!focusedWindow) return // only change focused window
    const pane = getPane(focusedWindow.id)
    pane.maxLoc(params)
  }
}
<commit_msg>fix: taskbar position<commit_after>'use strict'

const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const TerminalWindow = require('electron-terminal-window')

const Grid = require('grid')
const uuid = require('uuid')

let grids = []

function testWindow (bounds) {
  const ipcMain = electron.ipcMain
  return new Promise((resolve, reject) => {
    const eventId = uuid.v4()
    const win = new BrowserWindow({
      y: bounds.y,
      x: bounds.x,
      frame: false
    })
    win.loadURL(`file://${__dirname}/index.html`)
    win.maximize()
    const changedBounds = win.getContentBounds()
    ipcMain.once(eventId, (e, winSize) => {
      win.close()
      resolve(Object.assign({}, winSize, {x: changedBounds.x, y: changedBounds.y}))
    })
    setTimeout(() => win.webContents.executeJavaScript(`
      var ipcRenderer = require('electron').ipcRenderer
      ipcRenderer.send('${eventId}', {width: window.innerWidth, height: window.innerHeight})
    `), 500)
  })
  .catch(e => console.error('e:', e))
}

function findBars (bounds, workArea) {
  if (workArea.y > bounds.y) {
    return ({
      id: 'topBar',
      width: workArea.width,
      height: workArea.y - bounds.y,
      x: 0,
      y: 0
    })
  }
  if (bounds.y - workArea.y + bounds.height - workArea.height > 0) {
    return ({
      id: 'bottomBar',
      width: workArea.width,
      height: bounds.height - workArea.height,
      y: workArea.height,
      x: 0
    })
  }
  if (workArea.x > bounds.x) {
    return ({
      id: 'leftBar',
      width: workArea.x - bounds.x,
      height: workArea.height,
      x: 0,
      y: 0
    })
  }
  if (bounds.x - workArea.x + bounds.width - workArea.width > 0) {
    return ({
      id: 'rightBar',
      width: bounds.x - workArea.x + bounds.width - workArea.width,
      height: workArea.height,
      x: workArea.width,
      y: 0
    })
  }
}

function detectPanel (bounds) {
  const screen = electron.screen
  const displays = screen.getAllDisplays()
  return testWindow(bounds)
  .then((workArea) => findBars(bounds, workArea))
  .catch(e => console.error(e))
}

function getGrid (winId) {
  if (winId === undefined) {
    winId = BrowserWindow.getFocusedWindow()
    if (winId === null) return false
  }
  const gridIndex = Object.keys(grids)
    .filter(gridId => {
      const grid = grids[gridId]
      return grid.panes.some(p => {
        return p.wrapped.id === winId
      })
    })[0]
  return grids[gridIndex || 0]
}

function getPane (winId) {
  const grid = getGrid(winId)
  return grid.getPane(winId)
}

module.exports = {
  init: function init () {
    const screen = electron.screen
    const displays = screen.getAllDisplays()
    return Promise.all(
      displays.map(display => {
        return detectPanel(display.bounds)
        .then(panel => {
          const bounds = display.bounds
          const gridOffset = {x: bounds.x, y: bounds.y}
          const grid = new Grid(bounds.width, bounds.height, gridOffset)
          if (panel) grid.add(null, panel)
          grids.push(grid)
        })
        .catch(e => console.error('e:', e))
      })
    )
  },
  createWindow: function createWindow (gridId) {
    try {
      const grid = typeof gridId === 'number' && typeof grids[gridId] === 'object'
        ? grids[gridId] : getGrid() || grids[0]
      grid.add(TerminalWindow, {
        width: 400,
        height: 300,
        frame: false,
        skipTaskbar: true
      })
    } catch (e) {
      console.error(e)
    }
  },
  changeCurWindow: function changeCurWindow (params) {
    const focusedWindow = BrowserWindow.getFocusedWindow()
    if (!focusedWindow) return // only change focused window
    const pane = getPane(focusedWindow.id)
    if (params.x || params.y) {
      try {
        pane.changeLocation(
          params.x ? pane.x + parseInt(params.x) : pane.x,
          params.y ? pane.y + parseInt(params.y) : pane.y
        )
      } catch(e) {
        console.error(e)
        // TODO: some visual indication (flash border red?)
      }
    } else { // TODO: support both
      try {
        pane.changeSize(
          params.width ? pane.width + parseInt(params.width) : pane.width,
          params.height ? pane.height + parseInt(params.height) : pane.height
        )
      } catch(e) {
        console.error(e)
        // TODO: some visual indication (flash border red?)
      }
    }
  },
  maxSize: function maxSize (params) {
    const focusedWindow = BrowserWindow.getFocusedWindow()
    if (!focusedWindow) return // only change focused window
    const pane = getPane(focusedWindow.id)
    pane.maxSize(params)
  },
  maxLoc: function maxLoc (params) {
    const focusedWindow = BrowserWindow.getFocusedWindow()
    if (!focusedWindow) return // only change focused window
    const pane = getPane(focusedWindow.id)
    pane.maxLoc(params)
  }
}
<|endoftext|>