<commit_before>Lizard.Views.Timeseries = Backbone.Marionette.ItemView.extend({
    initialize: function(options) {
        this.graphCollection = options.graphCollection;
        //this.model.on('change', this.render, this);
    },
    tagName: 'li',
    events: {
        'click .info': 'showInfoModal',
        'click .add': 'drawGraph',
        'click .add-tablet' : 'tabletGraph'
        // 'scroll': 'loadMore'
    },
    // loadMore: function() {
        // var totalHeight = this.$('> div').height(), scrollTop = this.$el.scrollTop() + this.$el.height(), margin = 100;
        // console.log('scroll')
        // if (scrollTop + margin >= totalHeight) {
            // this.model.collection
        // }
    // },
    showInfoModal: function() {
        var infoModalView = new Lizard.Views.InfoModal({
            model: this.model
        });
        window.graphsView.infomodal.show(infoModalView);
        window.graphsView.infomodal.$el.modal();
    },
    drawGraph: function(e) {
        // add to first graph in the graphs view
        var url = $(e.target).data('url');
        this.graphCollection.models[0].get('graphItems').addTimeseriesByUrl(url);
    },
    tabletGraph: function(e) {
        // add to first graph in the graphs view
        var url = $(e.target).data('url');
        var index = parseInt($(e.target).html()) - 1
        this.graphCollection.models[index].get('graphItems').addTimeseriesByUrl(url);
    },
    template: function(model) {
        return _.template($('#timeserie-item-template').html(), {
            url: model.url,
            name: model.name,
            parameter: model.parameter,
            unit: model.unit,
            uuid: model.uuid,
            events: model.events,
            favorite: model.favorite
        }, {
            variable: 'timeserie'
        });
    }
});

//
// Lizard.Views.TimeseriesCollection = Backbone.Marionette.CollectionView.extend({
    // initialize: function(options) {
        // this.collection = options.collection;
        // this.graphCollection = options.graphCollection;
        // this.collection.fetch()
    // },
    // tagName: 'ul',
    // itemView: Lizard.Views.Timeseries,
    // itemViewOptions: function(model) {
        // return {
            // graphCollection: this.graphCollection
        // };
    // }
// });

Lizard.Views.InfiniteTimeseries = Backbone.Marionette.CollectionView.extend({
    itemView: Lizard.Views.Timeseries,
    className: 'infinite-timeseries',
    isLoading: false,
    itemViewOptions: function (model) {
        return {
            graphCollection: this.graphCollection
        };
    },
    initialize: function(options) {
        this.graphCollection = options.graphCollection;
    },
    // events: {
        // 'click .info': 'showInfoModal'
    // },
    // showInfoModal: function(event) {
        // var tsuuid = $(event.target).parent().parent().data().uuid;
        // model = this.timeseriesCollection.where({uuid: tsuuid})[0];
        // infoModalView = new Lizard.Views.InfoModal({
            // model: model
        // });
        // window.graphsView.infomodal.show(infoModalView.render());
        // $('#info-modal').modal();
    // },
    handleScroll: function(e) {
        this.checkScroll();
    },
    onShow: function(e) {
        this.$el.parent().on('scroll.timeseries', this.handleScroll.bind(this));
        this.collection.fetch();
    },
    onClose: function(e) {
        this.$el.parent().off('scroll.timeseries');
    },
    checkScroll: function() {
        var self = this;
        var triggerPoint = 200;
        // console.log('this.el.scrollTop', this.el.parentNode.scrollTop);
        // console.log('this.el.clientHeight', this.el.clientHeight);
        // console.log('triggerPoint', triggerPoint);
        if (!this.isLoading && this.el.parentNode.scrollTop + this.el.parentNode.clientHeight + triggerPoint > this.el.scrollHeight) {
            $('.loading-indicator').show();
            this.isLoading = true;
            this.collection.page += 1;
            // Load next page
            this.collection.fetch()
            .always(function () {
                $('.loading-indicator').hide();
                setTimeout(function () {
                    self.isLoading = false;
                }, 500);
            });
        }
    }
});

Lizard.Views.TimeseriesSearch = Backbone.Marionette.View.extend({
    searchTimeout: null,
    initialize: function(options) {
        this.timeseriesCollection = options.timeseriesCollection;
    },
    render: function() {
        tpl = '
<input type="text" class="span12 search-query" placeholder="Zoeken" id="searchTimeseries" name="searchTimeseries">
';
        this.$el.html(tpl);
        return this;
    },
    events: {
        'keypress #searchTimeseries': 'search'
    },
    search: function(e) {
        var self = this;

        if (this.searchTimeout !== null) {
            window.clearTimeout(this.searchTimeout);
            this.searchTimeout = null;
        }

        this.searchTimeout = window.setTimeout(function () {
            self.timeseriesCollection.reset();
            self.timeseriesCollection.page = 1;
            self.timeseriesCollection.name = $('#searchTimeseries').val();
            self.timeseriesCollection.fetch();
        }, 700);
    }
});
<commit_msg>fix click on timeseries being handled by the  element in Chrome, but by the  element in Firefox<commit_after>Lizard.Views.Timeseries = Backbone.Marionette.ItemView.extend({
    initialize: function(options) {
        this.graphCollection = options.graphCollection;
        //this.model.on('change', this.render, this);
    },
    tagName: 'li',
    events: {
        'click .info': 'showInfoModal',
        'click .add': 'drawGraph',
        'click .add-tablet' : 'tabletGraph'
        // 'scroll': 'loadMore'
    },
    // loadMore: function() {
        // var totalHeight = this.$('> div').height(), scrollTop = this.$el.scrollTop() + this.$el.height(), margin = 100;
        // console.log('scroll')
        // if (scrollTop + margin >= totalHeight) {
            // this.model.collection
        // }
    // },
    showInfoModal: function() {
        var infoModalView = new Lizard.Views.InfoModal({
            model: this.model
        });
        window.graphsView.infomodal.show(infoModalView);
        window.graphsView.infomodal.$el.modal();
    },
    drawGraph: function(e) {
        // add to first graph in the graphs view
        var url = $(e.target).data('url');
        if (!url) {
            url = $(e.target).parents('.add[data-url]').data('url');
        }
        this.graphCollection.models[0].get('graphItems').addTimeseriesByUrl(url);
    },
    tabletGraph: function(e) {
        // add to first graph in the graphs view
        var url = $(e.target).data('url');
        if (!url) {
            url = $(e.target).parents('.add[data-url]').data('url');
        }
        var index = parseInt($(e.target).html()) - 1
        this.graphCollection.models[index].get('graphItems').addTimeseriesByUrl(url);
    },
    template: function(model) {
        return _.template($('#timeserie-item-template').html(), {
            url: model.url,
            name: model.name,
            parameter: model.parameter,
            unit: model.unit,
            uuid: model.uuid,
            events: model.events,
            favorite: model.favorite
        }, {
            variable: 'timeserie'
        });
    }
});

//
// Lizard.Views.TimeseriesCollection = Backbone.Marionette.CollectionView.extend({
    // initialize: function(options) {
        // this.collection = options.collection;
        // this.graphCollection = options.graphCollection;
        // this.collection.fetch()
    // },
    // tagName: 'ul',
    // itemView: Lizard.Views.Timeseries,
    // itemViewOptions: function(model) {
        // return {
            // graphCollection: this.graphCollection
        // };
    // }
// });

Lizard.Views.InfiniteTimeseries = Backbone.Marionette.CollectionView.extend({
    itemView: Lizard.Views.Timeseries,
    className: 'infinite-timeseries',
    isLoading: false,
    itemViewOptions: function (model) {
        return {
            graphCollection: this.graphCollection
        };
    },
    initialize: function(options) {
        this.graphCollection = options.graphCollection;
    },
    // events: {
        // 'click .info': 'showInfoModal'
    // },
    // showInfoModal: function(event) {
        // var tsuuid = $(event.target).parent().parent().data().uuid;
        // model = this.timeseriesCollection.where({uuid: tsuuid})[0];
        // infoModalView = new Lizard.Views.InfoModal({
            // model: model
        // });
        // window.graphsView.infomodal.show(infoModalView.render());
        // $('#info-modal').modal();
    // },
    handleScroll: function(e) {
        this.checkScroll();
    },
    onShow: function(e) {
        this.$el.parent().on('scroll.timeseries', this.handleScroll.bind(this));
        this.collection.fetch();
    },
    onClose: function(e) {
        this.$el.parent().off('scroll.timeseries');
    },
    checkScroll: function() {
        var self = this;
        var triggerPoint = 200;
        // console.log('this.el.scrollTop', this.el.parentNode.scrollTop);
        // console.log('this.el.clientHeight', this.el.clientHeight);
        // console.log('triggerPoint', triggerPoint);
        if (!this.isLoading && this.el.parentNode.scrollTop + this.el.parentNode.clientHeight + triggerPoint > this.el.scrollHeight) {
            $('.loading-indicator').show();
            this.isLoading = true;
            this.collection.page += 1;
            // Load next page
            this.collection.fetch()
            .always(function () {
                $('.loading-indicator').hide();
                setTimeout(function () {
                    self.isLoading = false;
                }, 500);
            });
        }
    }
});

Lizard.Views.TimeseriesSearch = Backbone.Marionette.View.extend({
    searchTimeout: null,
    initialize: function(options) {
        this.timeseriesCollection = options.timeseriesCollection;
    },
    render: function() {
        tpl = '
<input type="text" class="span12 search-query" placeholder="Zoeken" id="searchTimeseries" name="searchTimeseries">
';
        this.$el.html(tpl);
        return this;
    },
    events: {
        'keypress #searchTimeseries': 'search'
    },
    search: function(e) {
        var self = this;

        if (this.searchTimeout !== null) {
            window.clearTimeout(this.searchTimeout);
            this.searchTimeout = null;
        }

        this.searchTimeout = window.setTimeout(function () {
            self.timeseriesCollection.reset();
            self.timeseriesCollection.page = 1;
            self.timeseriesCollection.name = $('#searchTimeseries').val();
            self.timeseriesCollection.fetch();
        }, 700);
    }
});
<|endoftext|>


<commit_before>const { remote, ipcRenderer, shell } = require('electron')
const HtmlTranslate = require('./utils/htmlTranslate')
const VersionChecker = require('./utils/versionChecker')
const { setSameWidths } = require('./utils/sameWidths')
const i18next = remote.require('i18next')

const bounds = remote.getCurrentWindow().getBounds()
const htmlTranslate = new HtmlTranslate(document)
const versionChecker = new VersionChecker()
let eventsAttached = false

if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  const imagesWithDarkVersion = document.querySelectorAll('[data-has-dark-version]')
  imagesWithDarkVersion.forEach(image => {
    // replace last occurance https://github.com/electron-userland/electron-builder/issues/5152
    const newSource = image.src.replace(/.([^.]*)$/, '-dark.' + '$1')
    image.src = newSource
  })
}

window.matchMedia('(prefers-color-scheme: dark)').addListener((e) => {
  const imagesWithDarkVersion = document.querySelectorAll('[data-has-dark-version]')
  if (e.matches) {
    imagesWithDarkVersion.forEach(image => {
      const newSource = image.src.replace(/.([^.]*)$/, '-dark.' + '$1')
      image.src = newSource
    })
  } else {
    imagesWithDarkVersion.forEach(image => {
      const newSource = image.src.replace('-dark.', '.')
      image.src = newSource
    })
  }
})

window.onload = (event) => {
  ipcRenderer.send('send-settings')
  htmlTranslate.translate()
  setWindowHeight()
  setTimeout(() => { eventsAttached = true }, 500)
}

document.ondragover = event =>
  event.preventDefault()

document.ondrop = event =>
  event.preventDefault()

document.onkeydown = event => {
  if (event.key === 'd' && (event.ctrlKey || event.metaKey)) {
    ipcRenderer.send('show-debug')
  }
}

ipcRenderer.on('debugInfo', (event, reference, timeleft, breaknumber,
  postponesnumber, settingsfile, logsfile, doNotDisturb) => {
  const debugInfo = document.querySelector('.debug > :first-child')
  if (debugInfo.style.display === 'block') {
    debugInfo.style.display = 'none'
  } else {
    debugInfo.style.display = 'block'
    document.querySelector('#reference').innerHTML = reference
    document.querySelector('#timeleft').innerHTML = timeleft
    document.querySelector('#breakNumber').innerHTML = breaknumber
    document.querySelector('#postponesNumber').innerHTML = postponesnumber
    document.querySelector('#settingsfile').innerHTML = settingsfile
    document.querySelector('#logsfile').innerHTML = logsfile
    document.querySelector('#donotdisturb').innerHTML = doNotDisturb
    document.querySelector('#node').innerHTML = process.versions.node
    document.querySelector('#chrome').innerHTML = process.versions.chrome
    document.querySelector('#electron').innerHTML = process.versions.electron
  }
  setWindowHeight()
})

ipcRenderer.on('enableContributorPreferences', () => {
  showContributorPreferencesButton()
})

const showContributorPreferencesButton = () => {
  document.querySelectorAll('.contributor').forEach((item) => {
    item.classList.remove('hidden')
  })
  document.querySelectorAll('.become').forEach((item) => {
    item.classList.add('hidden')
  })
  document.querySelectorAll('.authenticate').forEach((item) => {
    item.classList.add('hidden')
  })
  setWindowHeight()
}

if (remote.getGlobal('shared').isContributor) {
  showContributorPreferencesButton()
}

document.querySelector('[name="contributorPreferences"]').onclick = (event) => {
  event.preventDefault()
  ipcRenderer.send('open-contributor-preferences')
}

document.querySelector('[name="syncPreferences"]').onclick = (event) => {
  event.preventDefault()
  ipcRenderer.send('open-sync-preferences')
}

// TODO refactor out?
const copyToClipBoard = (str) => {
  const el = document.createElement('textarea')
  el.value = str
  document.body.appendChild(el)
  el.select()
  document.execCommand('copy')
  document.body.removeChild(el)
}

document.querySelector('.debug button').onclick = (event) => {
  event.preventDefault()
  const toCopy = document.querySelector('#to-copy')
  copyToClipBoard(toCopy.textContent)
  const copiedEl = document.createElement('span')
  copiedEl.innerHTML = ' copied!'
  event.target.parentNode.appendChild(copiedEl)
  setTimeout(() => copiedEl.remove(), 1275)
}

document.querySelectorAll('.navigation a').forEach(element => {
  element.onclick = event => {
    event.preventDefault()
    event.target.closest('.navigation').childNodes.forEach(link => {
      if (link.classList) {
        link.classList.remove('active')
      }
    })
    event.target.closest('a').classList.add('active')

    document.querySelectorAll('body > div').forEach(section => {
      const toBeDisplayed =
        document.querySelector(`.${event.target.closest('[data-section]').getAttribute('data-section')}`)
      if (section !== toBeDisplayed) {
        section.classList.add('hidden')
      }
      toBeDisplayed.classList.remove('hidden')
    })

    setSameWidths()
    setWindowHeight()
  }
})

ipcRenderer.on('renderSettings', (event, settings) => {
  document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
    const isNegative = checkbox.classList.contains('negative')
    checkbox.checked = isNegative ? !settings[checkbox.value] : settings[checkbox.value]
    if (!eventsAttached) {
      checkbox.onchange = (event) =>
        ipcRenderer.send('save-setting', checkbox.value,
          isNegative ? !checkbox.checked : checkbox.checked)
    }
  })

  document.querySelectorAll('input[type="radio"]').forEach(radio => {
    let value
    switch (radio.value) {
      case 'true':
        value = true
        break
      case 'false':
        value = false
        break
      default:
        value = radio.value
    }
    radio.checked = settings[radio.name] === value
    if (!eventsAttached) {
      radio.onchange = (event) => {
        ipcRenderer.send('save-setting', radio.name, value)
      }
    }
  })

  document.querySelector('#language').value = settings.language
  if (!eventsAttached) {
    document.querySelector('#language').onchange = (event) => {
      ipcRenderer.send('save-setting', 'language', event.target.value)
      htmlTranslate.translate()
      setWindowHeight()
    }
  }

  document.querySelectorAll('input[type="range"]').forEach(range => {
    const divisor = range.dataset.divisor
    const output = range.closest('div').querySelector('output')
    range.value = settings[range.name] / divisor
    const unit = output.dataset.unit
    output.innerHTML = formatUnitAndValue(unit, range.value)
    document.querySelector('#longBreakEvery').closest('div').querySelector('output')
      .innerHTML = i18next.t('utils.minutes', { count: parseInt(realBreakInterval()) })
    if (!eventsAttached) {
      range.onchange = event => {
        output.innerHTML = formatUnitAndValue(unit, range.value)
        document.querySelector('#longBreakEvery').closest('div').querySelector('output')
          .innerHTML = i18next.t('utils.minutes', { count: parseInt(realBreakInterval()) })
        ipcRenderer.send('save-setting', range.name, range.value * divisor)
      }
      range.oninput = event => {
        output.innerHTML = formatUnitAndValue(unit, range.value)
        document.querySelector('#longBreakEvery').closest('div').querySelector('output')
          .innerHTML = i18next.t('utils.minutes', { count: parseInt(realBreakInterval()) })
      }
    }
  })

  document.querySelectorAll('.sounds img').forEach(preview => {
    if (!eventsAttached) {
      preview.onclick = (event) =>
        ipcRenderer.send('play-sound', preview.closest('div').querySelector('input').value)
    }
  })

  if (!settings.checkNewVersion) {
    document.querySelector('#notifyNewVersion').closest('div').style.display = 'none'
  }
  htmlTranslate.translate()
  setWindowHeight()
})

document.querySelectorAll('.enabletype').forEach((element) => {
  element.onclick = (event) => {
    const enabletypeChecked = document.querySelectorAll('.enabletype:checked')
    if (enabletypeChecked.length === 0) {
      element.checked = true
      ipcRenderer.send('save-setting', element.value, element.checked)
      window.alert(i18next.t('preferences.schedule.cantDisableBoth'))
    }
  }
})

document.querySelector('.settings > div > button').onclick = (event) => {
  ipcRenderer.send('restore-defaults')
}

document.querySelectorAll('.about a').forEach((item) => {
  item.onclick = (event) => {
    event.preventDefault()
    if (event.target.classList.contains('file')) {
      shell.openPath(event.target.innerHTML)
    } else {
      shell.openExternal(event.target.href)
    }
  }
})

document.querySelector('[name="becomePatron"]').onclick = () => {
  shell.openExternal('https://hovancik.net/stretchly/sponsor')
}

document.querySelector('[name="alreadyContributor"]').onclick = () => {
  document.querySelectorAll('.become').forEach((item) => {
    item.classList.add('hidden')
  })
  document.querySelectorAll('.authenticate').forEach((item) => {
    item.classList.remove('hidden')
  })
  setWindowHeight()
}

document.querySelectorAll('.authenticate a').forEach((button) => {
  button.onclick = (event) => {
    event.preventDefault()
    ipcRenderer.send('open-contributor-auth', button.dataset.provider)
  }
})

document.querySelector('.version').innerHTML = remote.app.getVersion()
versionChecker.latest()
  .then(version => {
    document.querySelector('.latestVersion').innerHTML = version.replace('v', '')
  })
  .catch(exception => {
    console.error(exception)
    document.querySelector('.latestVersion').innerHTML = 'N/A'
  })

function setWindowHeight () {
  const classes = document.querySelector('body').classList
  const height = document.querySelector('body').scrollHeight
  if (classes.contains('darwin')) {
    remote.getCurrentWindow().setSize(bounds.width, height + 22)
  } else if (classes.contains('win32')) {
    remote.getCurrentWindow().setSize(bounds.width, height + 40)
  }
  // linux is broken ;/
}

function realBreakInterval () {
  const microbreakInterval = document.querySelector('#miniBreakEvery').value * 1
  const breakInterval = document.querySelector('#longBreakEvery').value * 1
  return microbreakInterval * (breakInterval + 1)
}

// TODO take out and test
function formatUnitAndValue (unit, value) {
  if (unit === 'seconds') {
    if (value < 60) {
      return i18next.t('utils.seconds', { count: parseInt(value) })
    } else {
      const val = parseFloat(value / 60).toFixed(1)
      if (val % 1 === 0) {
        return i18next.t('utils.minutes', { count: parseInt(val) })
      } else {
        return i18next.t('utils.minutes', { count: val })
      }
    }
  } else {
    return i18next.t(`utils.${unit}`, { count: parseInt(value) })
  }
}
<commit_msg>Improve macOS Preferences window size<commit_after>const { remote, ipcRenderer, shell } = require('electron')
const HtmlTranslate = require('./utils/htmlTranslate')
const VersionChecker = require('./utils/versionChecker')
const { setSameWidths } = require('./utils/sameWidths')
const i18next = remote.require('i18next')

const bounds = remote.getCurrentWindow().getBounds()
const htmlTranslate = new HtmlTranslate(document)
const versionChecker = new VersionChecker()
let eventsAttached = false

if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  const imagesWithDarkVersion = document.querySelectorAll('[data-has-dark-version]')
  imagesWithDarkVersion.forEach(image => {
    // replace last occurance https://github.com/electron-userland/electron-builder/issues/5152
    const newSource = image.src.replace(/.([^.]*)$/, '-dark.' + '$1')
    image.src = newSource
  })
}

window.matchMedia('(prefers-color-scheme: dark)').addListener((e) => {
  const imagesWithDarkVersion = document.querySelectorAll('[data-has-dark-version]')
  if (e.matches) {
    imagesWithDarkVersion.forEach(image => {
      const newSource = image.src.replace(/.([^.]*)$/, '-dark.' + '$1')
      image.src = newSource
    })
  } else {
    imagesWithDarkVersion.forEach(image => {
      const newSource = image.src.replace('-dark.', '.')
      image.src = newSource
    })
  }
})

window.onload = (event) => {
  ipcRenderer.send('send-settings')
  htmlTranslate.translate()
  setWindowHeight()
  setTimeout(() => { eventsAttached = true }, 500)
}

document.ondragover = event =>
  event.preventDefault()

document.ondrop = event =>
  event.preventDefault()

document.onkeydown = event => {
  if (event.key === 'd' && (event.ctrlKey || event.metaKey)) {
    ipcRenderer.send('show-debug')
  }
}

ipcRenderer.on('debugInfo', (event, reference, timeleft, breaknumber,
  postponesnumber, settingsfile, logsfile, doNotDisturb) => {
  const debugInfo = document.querySelector('.debug > :first-child')
  if (debugInfo.style.display === 'block') {
    debugInfo.style.display = 'none'
  } else {
    debugInfo.style.display = 'block'
    document.querySelector('#reference').innerHTML = reference
    document.querySelector('#timeleft').innerHTML = timeleft
    document.querySelector('#breakNumber').innerHTML = breaknumber
    document.querySelector('#postponesNumber').innerHTML = postponesnumber
    document.querySelector('#settingsfile').innerHTML = settingsfile
    document.querySelector('#logsfile').innerHTML = logsfile
    document.querySelector('#donotdisturb').innerHTML = doNotDisturb
    document.querySelector('#node').innerHTML = process.versions.node
    document.querySelector('#chrome').innerHTML = process.versions.chrome
    document.querySelector('#electron').innerHTML = process.versions.electron
  }
  setWindowHeight()
})

ipcRenderer.on('enableContributorPreferences', () => {
  showContributorPreferencesButton()
})

const showContributorPreferencesButton = () => {
  document.querySelectorAll('.contributor').forEach((item) => {
    item.classList.remove('hidden')
  })
  document.querySelectorAll('.become').forEach((item) => {
    item.classList.add('hidden')
  })
  document.querySelectorAll('.authenticate').forEach((item) => {
    item.classList.add('hidden')
  })
  setWindowHeight()
}

if (remote.getGlobal('shared').isContributor) {
  showContributorPreferencesButton()
}

document.querySelector('[name="contributorPreferences"]').onclick = (event) => {
  event.preventDefault()
  ipcRenderer.send('open-contributor-preferences')
}

document.querySelector('[name="syncPreferences"]').onclick = (event) => {
  event.preventDefault()
  ipcRenderer.send('open-sync-preferences')
}

// TODO refactor out?
const copyToClipBoard = (str) => {
  const el = document.createElement('textarea')
  el.value = str
  document.body.appendChild(el)
  el.select()
  document.execCommand('copy')
  document.body.removeChild(el)
}

document.querySelector('.debug button').onclick = (event) => {
  event.preventDefault()
  const toCopy = document.querySelector('#to-copy')
  copyToClipBoard(toCopy.textContent)
  const copiedEl = document.createElement('span')
  copiedEl.innerHTML = ' copied!'
  event.target.parentNode.appendChild(copiedEl)
  setTimeout(() => copiedEl.remove(), 1275)
}

document.querySelectorAll('.navigation a').forEach(element => {
  element.onclick = event => {
    event.preventDefault()
    event.target.closest('.navigation').childNodes.forEach(link => {
      if (link.classList) {
        link.classList.remove('active')
      }
    })
    event.target.closest('a').classList.add('active')

    document.querySelectorAll('body > div').forEach(section => {
      const toBeDisplayed =
        document.querySelector(`.${event.target.closest('[data-section]').getAttribute('data-section')}`)
      if (section !== toBeDisplayed) {
        section.classList.add('hidden')
      }
      toBeDisplayed.classList.remove('hidden')
    })

    setSameWidths()
    setWindowHeight()
  }
})

ipcRenderer.on('renderSettings', (event, settings) => {
  document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
    const isNegative = checkbox.classList.contains('negative')
    checkbox.checked = isNegative ? !settings[checkbox.value] : settings[checkbox.value]
    if (!eventsAttached) {
      checkbox.onchange = (event) =>
        ipcRenderer.send('save-setting', checkbox.value,
          isNegative ? !checkbox.checked : checkbox.checked)
    }
  })

  document.querySelectorAll('input[type="radio"]').forEach(radio => {
    let value
    switch (radio.value) {
      case 'true':
        value = true
        break
      case 'false':
        value = false
        break
      default:
        value = radio.value
    }
    radio.checked = settings[radio.name] === value
    if (!eventsAttached) {
      radio.onchange = (event) => {
        ipcRenderer.send('save-setting', radio.name, value)
      }
    }
  })

  document.querySelector('#language').value = settings.language
  if (!eventsAttached) {
    document.querySelector('#language').onchange = (event) => {
      ipcRenderer.send('save-setting', 'language', event.target.value)
      htmlTranslate.translate()
      setWindowHeight()
    }
  }

  document.querySelectorAll('input[type="range"]').forEach(range => {
    const divisor = range.dataset.divisor
    const output = range.closest('div').querySelector('output')
    range.value = settings[range.name] / divisor
    const unit = output.dataset.unit
    output.innerHTML = formatUnitAndValue(unit, range.value)
    document.querySelector('#longBreakEvery').closest('div').querySelector('output')
      .innerHTML = i18next.t('utils.minutes', { count: parseInt(realBreakInterval()) })
    if (!eventsAttached) {
      range.onchange = event => {
        output.innerHTML = formatUnitAndValue(unit, range.value)
        document.querySelector('#longBreakEvery').closest('div').querySelector('output')
          .innerHTML = i18next.t('utils.minutes', { count: parseInt(realBreakInterval()) })
        ipcRenderer.send('save-setting', range.name, range.value * divisor)
      }
      range.oninput = event => {
        output.innerHTML = formatUnitAndValue(unit, range.value)
        document.querySelector('#longBreakEvery').closest('div').querySelector('output')
          .innerHTML = i18next.t('utils.minutes', { count: parseInt(realBreakInterval()) })
      }
    }
  })

  document.querySelectorAll('.sounds img').forEach(preview => {
    if (!eventsAttached) {
      preview.onclick = (event) =>
        ipcRenderer.send('play-sound', preview.closest('div').querySelector('input').value)
    }
  })

  if (!settings.checkNewVersion) {
    document.querySelector('#notifyNewVersion').closest('div').style.display = 'none'
  }
  htmlTranslate.translate()
  setWindowHeight()
})

document.querySelectorAll('.enabletype').forEach((element) => {
  element.onclick = (event) => {
    const enabletypeChecked = document.querySelectorAll('.enabletype:checked')
    if (enabletypeChecked.length === 0) {
      element.checked = true
      ipcRenderer.send('save-setting', element.value, element.checked)
      window.alert(i18next.t('preferences.schedule.cantDisableBoth'))
    }
  }
})

document.querySelector('.settings > div > button').onclick = (event) => {
  ipcRenderer.send('restore-defaults')
}

document.querySelectorAll('.about a').forEach((item) => {
  item.onclick = (event) => {
    event.preventDefault()
    if (event.target.classList.contains('file')) {
      shell.openPath(event.target.innerHTML)
    } else {
      shell.openExternal(event.target.href)
    }
  }
})

document.querySelector('[name="becomePatron"]').onclick = () => {
  shell.openExternal('https://hovancik.net/stretchly/sponsor')
}

document.querySelector('[name="alreadyContributor"]').onclick = () => {
  document.querySelectorAll('.become').forEach((item) => {
    item.classList.add('hidden')
  })
  document.querySelectorAll('.authenticate').forEach((item) => {
    item.classList.remove('hidden')
  })
  setWindowHeight()
}

document.querySelectorAll('.authenticate a').forEach((button) => {
  button.onclick = (event) => {
    event.preventDefault()
    ipcRenderer.send('open-contributor-auth', button.dataset.provider)
  }
})

document.querySelector('.version').innerHTML = remote.app.getVersion()
versionChecker.latest()
  .then(version => {
    document.querySelector('.latestVersion').innerHTML = version.replace('v', '')
  })
  .catch(exception => {
    console.error(exception)
    document.querySelector('.latestVersion').innerHTML = 'N/A'
  })

function setWindowHeight () {
  const classes = document.querySelector('body').classList
  const height = document.querySelector('body').scrollHeight
  if (classes.contains('darwin')) {
    remote.getCurrentWindow().setSize(bounds.width, height + 32)
  } else if (classes.contains('win32')) {
    remote.getCurrentWindow().setSize(bounds.width, height + 40)
  }
  // linux is broken ;/
}

function realBreakInterval () {
  const microbreakInterval = document.querySelector('#miniBreakEvery').value * 1
  const breakInterval = document.querySelector('#longBreakEvery').value * 1
  return microbreakInterval * (breakInterval + 1)
}

// TODO take out and test
function formatUnitAndValue (unit, value) {
  if (unit === 'seconds') {
    if (value < 60) {
      return i18next.t('utils.seconds', { count: parseInt(value) })
    } else {
      const val = parseFloat(value / 60).toFixed(1)
      if (val % 1 === 0) {
        return i18next.t('utils.minutes', { count: parseInt(val) })
      } else {
        return i18next.t('utils.minutes', { count: val })
      }
    }
  } else {
    return i18next.t(`utils.${unit}`, { count: parseInt(value) })
  }
}
<|endoftext|>


<commit_before>var inside = require('point-in-polygon');
var URI = require('uri-js');

var GeoJSON = module.exports = {

///////////////////////////////////////////////////////////////////// VALIDATION

    isGeoJSON: function ( x ) {
        // Validate a GeoJSON object.
        return (
            GeoJSON.isGeometry(x) ||
            GeoJSON.isFeature(x)  ||
            GeoJSON.isFeatureCollection(x)
        );
    },

    isGeometry: function ( x ) {
        // Validate a GeoJSON Geometry.
        return (
            GeoJSON.isPoint(x) ||
            GeoJSON.isMultiPoint(x) ||
            GeoJSON.isLineString(x) ||
            GeoJSON.isMultiLineString(x) ||
            GeoJSON.isPolygon(x) ||
            GeoJSON.isMultiPolygon(x) ||
            GeoJSON.isGeometryCollection(x)
        );
    },

    isPosition: function ( x ) {
        // Validate a GeoJSON Position.
        return (
            Array.isArray(x) &&
            x.length > 1 &&
            x.every(
                function ( element ) {
                    return (typeof element === 'number');
                }
            )
        );
    },

    isPointPosition: function ( x ) {
        // Validate the coordinates of a GeoJSON Point.
        return GeoJSON.isPosition(x);
    },

    isMultiPointPosition: function ( x ) {
        // Validate the coordinates of a GeoJSON MultiPoint.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isPosition)
        );
    },

    isLineStringPosition: function ( x ) {
        // Validate the coordinates of a GeoJSON LineString.
        return (
            Array.isArray(x) &&
            x.length > 1 &&
            GeoJSON.isMultiPointPosition(x)
        );
    },

    isLinearRingCoordinates: function ( x ) {
        // Validate the coordinates of a GeoJSON LinearRing.
        return (
            Array.isArray(x) &&
            x.length > 3 &&
            GeoJSON.isLineStringPosition(x) &&
            GeoJSON.equalPositions(x[0],x[x.length-1])
        );
    },

    isMultiLineStringCoordinates: function ( x ) {
        // Validate the coordinates of a GeoJSON MultiLineString.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isLineStringPosition)
        );
    },

    isPolygonCoordinates: function ( x ) {
        // Validate the coordinates of a GeoJSON Polygon.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isLinearRingCoordinates) &&
            x.every(
                function ( LinearRingCoordinates , i , PolygonCoordinates ) {
                    if (i == 0) return true;
                    else return GeoJSON.containedPolygon(
                        LinearRingCoordinates,
                        PolygonCoordinates[0]
                    );
                }
            )
        );
    },

    isMultiPolygonCoordinates: function ( x ) {
        // Validate the coordinates of a GeoJSON MultiPolygon.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isPolygonCoordinates)
        );
    },

    isPoint: function ( x ) {
        // Validate a GeoJSON Point.
        return (
            x != null &&
            x.type === 'Point' &&
            GeoJSON.isPointPosition(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isMultiPoint: function ( x ) {
        // Validate a GeoJSON MultiPoint.
        return (
            x != null &&
            x.type === 'MultiPoint' &&
            GeoJSON.isMultiPointPosition(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isLineString: function ( x ) {
        // Validate a GeoJSON LineString.
        return (
            x != null &&
            x.type === 'LineString' &&
            GeoJSON.isLineStringPosition(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isMultiLineString: function ( x ) {
        // Validate a GeoJSON MultiLineString.
        return (
            x != null &&
            x.type === 'MultiLineString' &&
            GeoJSON.isMultiLineStringCoordinates(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isPolygon: function ( x ) {
        // Validate a GeoJSON Polygon.
        return (
            x != null &&
            x.type === 'Polygon' &&
            GeoJSON.isPolygonCoordinates(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isMultiPolygon: function ( x ) {
        // Validate a GeoJSON MultiPolygon.
        return (
            x != null &&
            x.type === 'MultiPolygon' &&
            GeoJSON.isMultiPolygonCoordinates(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isGeometryCollection: function ( x ) {
        // Validate a GeoJSON GeometryCollection.
        return (
            x != null &&
            x.type === 'GeometryCollection' &&
            Array.isArray(x.geometries) &&
            x.geometries.every(GeoJSON.isGeometry) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isFeature: function ( x ) {
        // Validate a GeoJSON Feature.
        return (
            x != null &&
            x.type === 'Feature' &&
            typeof x.properties === 'object' &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x) &&
            (
                x.geometry === null ||
                GeoJSON.isGeometry(x.geometry)
            )
        );
    },

    isFeatureCollection: function ( x ) {
        // Validate a GeoJSON FeatureCollection.
        return (
            x != null &&
            x.type === 'FeatureCollection' &&
            Array.isArray(x.features) &&
            x.features.every(GeoJSON.isFeature) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isCRS: function ( x ) {
        // Validate a GeoJSON Coordinate Reference System.
        return (
            x != null &&
            (
                (
                    x.type === 'name' &&
                    x.properties != null &&
                    typeof x.properties.name === 'string' &&
                    x.properties.name.length > 0
                ) ||
                (
                    x.type === 'link' &&
                    GeoJSON.isLink(x.properties)
                )
            )
        );
    },

    validCRS: function ( x ) {
        // Validate the CRS property of a GeoJSON object.
        return (
            x != null &&
            (
                typeof x.crs === 'undefined' ||
                x.crs === null ||
                GeoJSON.isCRS(x.crs)
            )
        )
    },

    isBbox: function ( x ) {
        throw 'Not Implemented'
    },

    validBbox: function ( x ) {
        // Validate the bbox property of a GeoJSON object.
        return (
            x != null &&
            (
                typeof x.bbox === 'undefined' ||
                (
                    Array.isArray(x.bbox) &&
                    x.bbox.length%2 === 0 &&
                    GeoJSON.isBbox()
                )
            )
        );
    },

    isLink: function ( x ) {
        // Validate a GeoJSON Link.
        try {
            return (
                x != null &&
                x.properties != null &&
                typeof x.properties.href === 'string' &&
                URI.parse(x.properties.href) &&
                (
                    typeof x.properties.type === 'undefined' ||
                    (
                        typeof x.properties.type === 'string' &&
                        x.properties.type.length > 0
                    )
                )
            );
        }
        catch (e) {
            return false;
        }
    },

//////////////////////////////////////////////////////////////////////// UTILITY

    equalPositions: function ( a , b ) {
        // Compare two GeoJSON Positions for equality.
        return (
            GeoJSON.isPosition(a) && GeoJSON.isPosition(b) &&
            a.length == b.length &&
            a.every(
                function ( element , index ) {
                    return (a[index] == b[index]);
                }
            )
        );
    },

    containedPolygon: function ( inner , outer ) {
        // Determine whether one GeoJSON LinearRing contains another.
        return inner.every(
            function ( Position ) {
                return inside(Position,outer);
            }
        );
    },

    Point: function ( latitude , longitude , altitude ) {
        // Create a valid GeoJSON Point geometry.
        var Point = null;
        if (typeof longitude === 'number' && typeof latitude === 'number') {
            Point = {
                type: 'Point',
                coordinates: [longitude,latitude]
            };
            if (typeof altitude === 'number') Point.coordinates.push(altitude);
        }
        return Point;
    },

    Feature: function ( Geometry , properties ) {
        // Create a valid GeoJSON Feature.
        var Feature = {
            type: 'Feature',
            geometry: null,
            properties: (typeof properties === 'object') ? properties : {}
        };
        if (GeoJSON.isGeometry(Geometry)) Feature.geometry = Geometry;
        return Feature;
    },

    FeatureCollection: function ( Features ) {
        // Create a valid GeoJSON FeatureCollection.
        var FeatureCollection = {
            type: 'FeatureCollection',
            features: []
        };
        if (GeoJSON.isFeature(Features)) {
            FeatureCollection.features.push(Features);
        }
        else if (Array.isArray(Features)) Features.forEach(
            function ( Feature ) {
                if (GeoJSON.isFeature(Feature)) {
                    FeatureCollection.features.push(Feature);
                }
            }
        );
        return FeatureCollection;
    },

    GeometryCollection: function ( Geometries ) {
        // Create a valid GeoJSON GeometryCollection.
        var GeometryCollection = {
            type: 'GeometryCollection',
            geometries: []
        };
        if (GeoJSON.isGeometry(Geometries)) {
            GeometryCollection.geometries.push(Geometries);
        }
        else if (Array.isArray(Geometries)) Geometries.forEach(
            function ( Geometry ) {
                if (GeoJSON.isGeometry(Geometry)) {
                    GeometryCollection.geometries.push(Geometry);
                }
            }
        );
        return GeometryCollection;
    },

    positionsOf: function ( geojson ) {
        // Find all the Positions in a valid GeoJSON object.
        var positions = [];
        var extractLineStringPositions = function ( LineStringCoordinates ) {
            LineStringCoordinates.forEach(
                function ( Position ) {
                    positions.push(Position);
                }
            );
        };
        var extractPolygonPositions = function ( PolygonCoordinates ) {
            PolygonCoordinates.forEach(extractLineStringPositions);
        };
        switch (geojson.type) {
            case 'Point':
                positions.push(geojson.coordinates);
                break;
            case 'MultiPoint':
            case 'LineString':
                extractLineStringPositions(geojson.coordinates);
                break;
            case 'MultiLineString':
            case 'Polygon':
                extractPolygonPositions(geojson.coordinates);
                break;
            case 'MultiPolygon':
                geojson.coordinates.forEach(extractPolygonPositions);
                break;
            case 'GeometryCollection':
                extractLineStringPositions(
                    GeoJSON.positionsOf(geojson.geometries)
                );
                break;
            case 'Feature':
                extractLineStringPositions(
                    GeoJSON.positionsOf(geojson.geometry)
                );
                break;
            case 'FeatureCollection':
                geojson.features.forEach(
                    function ( Feature ) {
                        extractLineStringPositions(
                            GeoJSON.positionsOf(Feature.geometry)
                        );
                    }
                );
                break;
        }
        return positions;
    },

    featuresOf: function ( geojson ) {
        // Find all Features in a valid GeoJSON object.
        var features = [];
        switch (geojson.type) {
            case 'Feature':
                features.push(geojson);
                break;
            case 'FeatureCollection':
                geojson.features.forEach(
                    function ( Feature ) {
                        GeoJSON.featuresOf(Feature).forEach(
                            function ( Feature ) {
                                features.push(Feature);
                            }
                        );
                    }
                );
                break;
        }
        return features;
    },

    geometriesOf: function ( geojson ) {
        // Find all Geometries in a valid GeoJSON object.
        var geometries = [];
        var extractFeatureGeometries = function ( Feature ) {
            GeoJSON.geometriesOf(Feature.geometry).forEach(
                function ( Geometry ) {
                    geometries.push(Geometry);
                }
            );
        };
        switch (geojson.type) {
            case 'Point':
            case 'MultiPoint':
            case 'LineString':
            case 'MultiLineString':
            case 'Polygon':
            case 'MultiPolygon':
                geometries.push(geojson);
                break;
            case 'GeometryCollection':
                geojson.geometries.forEach(
                    function ( Geometry ) {
                        GeoJSON.geometriesOf(Geometry).forEach(
                            function ( Geometry ) {
                                geometries.push(Geometry);
                            }
                        );
                    }
                );
                break;
            case 'Feature':
                extractFeatureGeometries(geojson);
                break;
            case 'FeatureCollection':
                geojson.features.forEach(extractFeatureGeometries);
                break;
        }
        return geometries;
    },

///////////////////////////////////////////////////////////////////// DEPRECATED

    isPointCoordinates: function ( x ) {
        // DEPRECATED: Use isPointPosition.
        return GeoJSON.isPointPosition(x);
    },

    isMultiPointCoordinates: function ( x ) {
        // DEPRECATED: Use isMultiPointPosition.
        return GeoJSON.isMultiPointPosition(x);
    },

    isLineStringCoordinates: function ( x ) {
        // DEPRECATED: Use isLineStringPosition.
        return GeoJSON.isLineStringPosition(x);
    }

};
<commit_msg>Deprecate isLinearRingCoordinates. #5<commit_after>var inside = require('point-in-polygon');
var URI = require('uri-js');

var GeoJSON = module.exports = {

///////////////////////////////////////////////////////////////////// VALIDATION

    isGeoJSON: function ( x ) {
        // Validate a GeoJSON object.
        return (
            GeoJSON.isGeometry(x) ||
            GeoJSON.isFeature(x)  ||
            GeoJSON.isFeatureCollection(x)
        );
    },

    isGeometry: function ( x ) {
        // Validate a GeoJSON Geometry.
        return (
            GeoJSON.isPoint(x) ||
            GeoJSON.isMultiPoint(x) ||
            GeoJSON.isLineString(x) ||
            GeoJSON.isMultiLineString(x) ||
            GeoJSON.isPolygon(x) ||
            GeoJSON.isMultiPolygon(x) ||
            GeoJSON.isGeometryCollection(x)
        );
    },

    isPosition: function ( x ) {
        // Validate a GeoJSON Position.
        return (
            Array.isArray(x) &&
            x.length > 1 &&
            x.every(
                function ( element ) {
                    return (typeof element === 'number');
                }
            )
        );
    },

    isPointPosition: function ( x ) {
        // Validate the coordinates of a GeoJSON Point.
        return GeoJSON.isPosition(x);
    },

    isMultiPointPosition: function ( x ) {
        // Validate the coordinates of a GeoJSON MultiPoint.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isPosition)
        );
    },

    isLineStringPosition: function ( x ) {
        // Validate the coordinates of a GeoJSON LineString.
        return (
            Array.isArray(x) &&
            x.length > 1 &&
            GeoJSON.isMultiPointPosition(x)
        );
    },

    isLinearRingPosition: function ( x ) {
        // Validate the coordinates of a GeoJSON LinearRing.
        return (
            Array.isArray(x) &&
            x.length > 3 &&
            GeoJSON.isLineStringPosition(x) &&
            GeoJSON.equalPositions(x[0],x[x.length-1])
        );
    },

    isMultiLineStringCoordinates: function ( x ) {
        // Validate the coordinates of a GeoJSON MultiLineString.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isLineStringPosition)
        );
    },

    isPolygonCoordinates: function ( x ) {
        // Validate the coordinates of a GeoJSON Polygon.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isLinearRingPosition) &&
            x.every(
                function ( LinearRingCoordinates , i , PolygonCoordinates ) {
                    if (i == 0) return true;
                    else return GeoJSON.containedPolygon(
                        LinearRingCoordinates,
                        PolygonCoordinates[0]
                    );
                }
            )
        );
    },

    isMultiPolygonCoordinates: function ( x ) {
        // Validate the coordinates of a GeoJSON MultiPolygon.
        return (
            Array.isArray(x) &&
            x.every(GeoJSON.isPolygonCoordinates)
        );
    },

    isPoint: function ( x ) {
        // Validate a GeoJSON Point.
        return (
            x != null &&
            x.type === 'Point' &&
            GeoJSON.isPointPosition(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isMultiPoint: function ( x ) {
        // Validate a GeoJSON MultiPoint.
        return (
            x != null &&
            x.type === 'MultiPoint' &&
            GeoJSON.isMultiPointPosition(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isLineString: function ( x ) {
        // Validate a GeoJSON LineString.
        return (
            x != null &&
            x.type === 'LineString' &&
            GeoJSON.isLineStringPosition(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isMultiLineString: function ( x ) {
        // Validate a GeoJSON MultiLineString.
        return (
            x != null &&
            x.type === 'MultiLineString' &&
            GeoJSON.isMultiLineStringCoordinates(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isPolygon: function ( x ) {
        // Validate a GeoJSON Polygon.
        return (
            x != null &&
            x.type === 'Polygon' &&
            GeoJSON.isPolygonCoordinates(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isMultiPolygon: function ( x ) {
        // Validate a GeoJSON MultiPolygon.
        return (
            x != null &&
            x.type === 'MultiPolygon' &&
            GeoJSON.isMultiPolygonCoordinates(x.coordinates) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isGeometryCollection: function ( x ) {
        // Validate a GeoJSON GeometryCollection.
        return (
            x != null &&
            x.type === 'GeometryCollection' &&
            Array.isArray(x.geometries) &&
            x.geometries.every(GeoJSON.isGeometry) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isFeature: function ( x ) {
        // Validate a GeoJSON Feature.
        return (
            x != null &&
            x.type === 'Feature' &&
            typeof x.properties === 'object' &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x) &&
            (
                x.geometry === null ||
                GeoJSON.isGeometry(x.geometry)
            )
        );
    },

    isFeatureCollection: function ( x ) {
        // Validate a GeoJSON FeatureCollection.
        return (
            x != null &&
            x.type === 'FeatureCollection' &&
            Array.isArray(x.features) &&
            x.features.every(GeoJSON.isFeature) &&
            GeoJSON.validCRS(x) &&
            GeoJSON.validBbox(x)
        );
    },

    isCRS: function ( x ) {
        // Validate a GeoJSON Coordinate Reference System.
        return (
            x != null &&
            (
                (
                    x.type === 'name' &&
                    x.properties != null &&
                    typeof x.properties.name === 'string' &&
                    x.properties.name.length > 0
                ) ||
                (
                    x.type === 'link' &&
                    GeoJSON.isLink(x.properties)
                )
            )
        );
    },

    validCRS: function ( x ) {
        // Validate the CRS property of a GeoJSON object.
        return (
            x != null &&
            (
                typeof x.crs === 'undefined' ||
                x.crs === null ||
                GeoJSON.isCRS(x.crs)
            )
        )
    },

    isBbox: function ( x ) {
        throw 'Not Implemented'
    },

    validBbox: function ( x ) {
        // Validate the bbox property of a GeoJSON object.
        return (
            x != null &&
            (
                typeof x.bbox === 'undefined' ||
                (
                    Array.isArray(x.bbox) &&
                    x.bbox.length%2 === 0 &&
                    GeoJSON.isBbox()
                )
            )
        );
    },

    isLink: function ( x ) {
        // Validate a GeoJSON Link.
        try {
            return (
                x != null &&
                x.properties != null &&
                typeof x.properties.href === 'string' &&
                URI.parse(x.properties.href) &&
                (
                    typeof x.properties.type === 'undefined' ||
                    (
                        typeof x.properties.type === 'string' &&
                        x.properties.type.length > 0
                    )
                )
            );
        }
        catch (e) {
            return false;
        }
    },

//////////////////////////////////////////////////////////////////////// UTILITY

    equalPositions: function ( a , b ) {
        // Compare two GeoJSON Positions for equality.
        return (
            GeoJSON.isPosition(a) && GeoJSON.isPosition(b) &&
            a.length == b.length &&
            a.every(
                function ( element , index ) {
                    return (a[index] == b[index]);
                }
            )
        );
    },

    containedPolygon: function ( inner , outer ) {
        // Determine whether one GeoJSON LinearRing contains another.
        return inner.every(
            function ( Position ) {
                return inside(Position,outer);
            }
        );
    },

    Point: function ( latitude , longitude , altitude ) {
        // Create a valid GeoJSON Point geometry.
        var Point = null;
        if (typeof longitude === 'number' && typeof latitude === 'number') {
            Point = {
                type: 'Point',
                coordinates: [longitude,latitude]
            };
            if (typeof altitude === 'number') Point.coordinates.push(altitude);
        }
        return Point;
    },

    Feature: function ( Geometry , properties ) {
        // Create a valid GeoJSON Feature.
        var Feature = {
            type: 'Feature',
            geometry: null,
            properties: (typeof properties === 'object') ? properties : {}
        };
        if (GeoJSON.isGeometry(Geometry)) Feature.geometry = Geometry;
        return Feature;
    },

    FeatureCollection: function ( Features ) {
        // Create a valid GeoJSON FeatureCollection.
        var FeatureCollection = {
            type: 'FeatureCollection',
            features: []
        };
        if (GeoJSON.isFeature(Features)) {
            FeatureCollection.features.push(Features);
        }
        else if (Array.isArray(Features)) Features.forEach(
            function ( Feature ) {
                if (GeoJSON.isFeature(Feature)) {
                    FeatureCollection.features.push(Feature);
                }
            }
        );
        return FeatureCollection;
    },

    GeometryCollection: function ( Geometries ) {
        // Create a valid GeoJSON GeometryCollection.
        var GeometryCollection = {
            type: 'GeometryCollection',
            geometries: []
        };
        if (GeoJSON.isGeometry(Geometries)) {
            GeometryCollection.geometries.push(Geometries);
        }
        else if (Array.isArray(Geometries)) Geometries.forEach(
            function ( Geometry ) {
                if (GeoJSON.isGeometry(Geometry)) {
                    GeometryCollection.geometries.push(Geometry);
                }
            }
        );
        return GeometryCollection;
    },

    positionsOf: function ( geojson ) {
        // Find all the Positions in a valid GeoJSON object.
        var positions = [];
        var extractLineStringPositions = function ( LineStringCoordinates ) {
            LineStringCoordinates.forEach(
                function ( Position ) {
                    positions.push(Position);
                }
            );
        };
        var extractPolygonPositions = function ( PolygonCoordinates ) {
            PolygonCoordinates.forEach(extractLineStringPositions);
        };
        switch (geojson.type) {
            case 'Point':
                positions.push(geojson.coordinates);
                break;
            case 'MultiPoint':
            case 'LineString':
                extractLineStringPositions(geojson.coordinates);
                break;
            case 'MultiLineString':
            case 'Polygon':
                extractPolygonPositions(geojson.coordinates);
                break;
            case 'MultiPolygon':
                geojson.coordinates.forEach(extractPolygonPositions);
                break;
            case 'GeometryCollection':
                extractLineStringPositions(
                    GeoJSON.positionsOf(geojson.geometries)
                );
                break;
            case 'Feature':
                extractLineStringPositions(
                    GeoJSON.positionsOf(geojson.geometry)
                );
                break;
            case 'FeatureCollection':
                geojson.features.forEach(
                    function ( Feature ) {
                        extractLineStringPositions(
                            GeoJSON.positionsOf(Feature.geometry)
                        );
                    }
                );
                break;
        }
        return positions;
    },

    featuresOf: function ( geojson ) {
        // Find all Features in a valid GeoJSON object.
        var features = [];
        switch (geojson.type) {
            case 'Feature':
                features.push(geojson);
                break;
            case 'FeatureCollection':
                geojson.features.forEach(
                    function ( Feature ) {
                        GeoJSON.featuresOf(Feature).forEach(
                            function ( Feature ) {
                                features.push(Feature);
                            }
                        );
                    }
                );
                break;
        }
        return features;
    },

    geometriesOf: function ( geojson ) {
        // Find all Geometries in a valid GeoJSON object.
        var geometries = [];
        var extractFeatureGeometries = function ( Feature ) {
            GeoJSON.geometriesOf(Feature.geometry).forEach(
                function ( Geometry ) {
                    geometries.push(Geometry);
                }
            );
        };
        switch (geojson.type) {
            case 'Point':
            case 'MultiPoint':
            case 'LineString':
            case 'MultiLineString':
            case 'Polygon':
            case 'MultiPolygon':
                geometries.push(geojson);
                break;
            case 'GeometryCollection':
                geojson.geometries.forEach(
                    function ( Geometry ) {
                        GeoJSON.geometriesOf(Geometry).forEach(
                            function ( Geometry ) {
                                geometries.push(Geometry);
                            }
                        );
                    }
                );
                break;
            case 'Feature':
                extractFeatureGeometries(geojson);
                break;
            case 'FeatureCollection':
                geojson.features.forEach(extractFeatureGeometries);
                break;
        }
        return geometries;
    },

///////////////////////////////////////////////////////////////////// DEPRECATED

    isPointCoordinates: function ( x ) {
        // DEPRECATED: Use isPointPosition.
        return GeoJSON.isPointPosition(x);
    },

    isMultiPointCoordinates: function ( x ) {
        // DEPRECATED: Use isMultiPointPosition.
        return GeoJSON.isMultiPointPosition(x);
    },

    isLineStringCoordinates: function ( x ) {
        // DEPRECATED: Use isLineStringPosition.
        return GeoJSON.isLineStringPosition(x);
    },

    isLinearRingCoordinates: function ( x ) {
        // DEPRECATED: Use isLinearRingPosition.
        return GeoJSON.isLinearRingPosition(x);
    }

};
<|endoftext|>


<commit_before>'use strict';

/**
 * ButtressJS - Realtime datastore for business software
 *
 * @file call.js
 * @description Call model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

const mongoose = require('mongoose');
const ObjectId = require('mongodb').ObjectId;
const Model = require('../');
const Logging = require('../../logging');
const Shared = require('../shared');
const Sugar = require('sugar');

/* ********************************************************************************
 *
 * LOCALS
 *
 **********************************************************************************/

let schema = new mongoose.Schema();
let ModelDef = null;
const collectionName = 'contactlists';
const collection = Model.mongoDb.collection(collectionName);

/* ********************************************************************************
 *
 * LOCALS
 *
 **********************************************************************************/

/* ********************************************************************************
 *
 * CONSTANTS
 *
 **********************************************************************************/
let constants = {};

/* ********************************************************************************
 *
 * SCHEMA
 *
 **********************************************************************************/

schema.add({
  name: String,
  _app: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'App'
  },
  campaignId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Campaign'
  },
  companyIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Company'
  }],
  personIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Person'
  }],
  callIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Call'
  }],
  emailIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Email'
  }],
  assignedToUserId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  metadata: [{key: String, value: String}],
  notes: [{
    userId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    },
    text: String,
    timestamp: {
      type: Date,
      default: Sugar.Date.create
    }
  }]
});

/*
 VIRTUALS
 */
schema.virtual('details').get(function() {
  return {
    id: this._id,
    name: this.name,
    campaignId: this.campaignId,
    companyIds: this.companyIds,
    assignedToUserId: this.assignedToUserId,
    notes: this.notes.map(n => ({text: n.text, timestamp: n.timestamp, userId: n.userId}))
  };
});

/* ********************************************************************************
 *
 * STATIC METHODS
 *
 **********************************************************************************/

/**
 * @param {Object} body - body passed through from a POST request to be validated
 * @return {Object} - returns an object with validation context
 */
const __doValidation = body => {
  let res = {
    isValid: true,
    missing: [],
    invalid: []
  };

  if (!body.campaignId) {
    res.isValid = false;
    res.missing.push('campaign');
  }
  if (!body.companyIds && !body.personIds) {
    res.isValid = false;
    res.missing.push('data');
  }
  if (!body.assignedToUserId) {
    res.isValid = false;
    res.missing.push('user');
  }

  let app = Shared.validateAppProperties(collectionName, body);
  if (app.isValid === false) {
    res.isValid = false;
    res.invalid = res.invalid.concat(app.invalid);
    res.missing = res.missing.concat(app.missing);
  }

  return res;
};

schema.statics.validate = body => {
  if (body instanceof Array === false) {
    body = [body];
  }
  let validation = body.map(__doValidation).filter(v => v.isValid === false);

  return validation.length >= 1 ? validation[0] : {isValid: true};
};

/*
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
const __add = body => {
  return prev => {
    const md = {
      name: body.name,
      _app: Model.authApp._id,
      campaignId: body.campaignId,
      companyIds: body.companyIds,
      personIds: body.personIds,
      assignedToUserId: body.assignedToUserId,
      notes: body.notes ? body.notes : [],
      metadata: []
    };

    if (body.id) {
      md._id = new ObjectId(body.id);
    }

    const validated = Shared.applyAppProperties(collectionName, body);
    return prev.concat([Object.assign(md, validated)]);
  };
};

schema.statics.add = Shared.add(collection, __add);
// schema.statics.addFromCampaign = (campaign, body) => {
//   body.campaignId = campaign._id;
//   return ModelDef.add(body);
// };

/* ********************************************************************************
 *
 * SCHEMA DB FUNCTIONS
 *
 **********************************************************************************/

schema.statics.exists = id => {
  return collection.find({_id: new ObjectId(id)})
    .limit(1)
    .count()
    .then(count => count > 0);
};

schema.statics.getAll = () => {
  return collection.find({_app: Model.authApp._id}, {metadata: 0});
};

schema.statics.rm = contactList => {
  Logging.log(`DELETING: ${contactList._id}`, Logging.Constants.LogLevel.DEBUG);
  return ModelDef.remove({_id: contactList._id});
};

schema.statics.rmAll = () => {
  return ModelDef.remove({});
};

/* ********************************************************************************
 *
 * METHODS
 *
 **********************************************************************************/
const PATH_CONTEXT = {
  '^name$': {type: 'scalar', values: []},
  '^companyIds$': {type: 'vector-add', values: []},
  '^assignedToUserId$': {type: 'scalar', values: []},
  '^companyIds.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^companyIds.([0-9]{1,3}).(__remove__)$': {type: 'vector-rm', values: []},
  '^notes$': {type: 'vector-add', values: []},
  '^notes.([0-9]{1,3}).__remove__$': {type: 'vector-rm', values: []},
  '^notes.([0-9]{1,3}).text$': {type: 'scalar', values: []}
};

schema.statics.validateUpdate = Shared.validateUpdate(PATH_CONTEXT, collectionName);
schema.statics.updateByPath = Shared.updateByPath(PATH_CONTEXT, collectionName, collection);

/* ********************************************************************************
 *
 * METADATA
 *
 **********************************************************************************/

schema.methods.addOrUpdateMetadata = Shared.addOrUpdateMetadata;
schema.methods.findMetadata = Shared.findMetadata;
schema.methods.rmMetadata = Shared.rmMetadata;
schema.statics.getAllMetadata = Shared.getAllMetadata(collection);

ModelDef = mongoose.model('Contactlist', schema);

/* ********************************************************************************
 *
 * EXPORTS
 *
 **********************************************************************************/
module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;

<commit_msg>- FIX: ContactList remove should take id not contactList object<commit_after>'use strict';

/**
 * ButtressJS - Realtime datastore for business software
 *
 * @file call.js
 * @description Call model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

const mongoose = require('mongoose');
const ObjectId = require('mongodb').ObjectId;
const Model = require('../');
const Logging = require('../../logging');
const Shared = require('../shared');
const Sugar = require('sugar');

/* ********************************************************************************
 *
 * LOCALS
 *
 **********************************************************************************/

let schema = new mongoose.Schema();
let ModelDef = null;
const collectionName = 'contactlists';
const collection = Model.mongoDb.collection(collectionName);

/* ********************************************************************************
 *
 * LOCALS
 *
 **********************************************************************************/

/* ********************************************************************************
 *
 * CONSTANTS
 *
 **********************************************************************************/
let constants = {};

/* ********************************************************************************
 *
 * SCHEMA
 *
 **********************************************************************************/

schema.add({
  name: String,
  _app: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'App'
  },
  campaignId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Campaign'
  },
  companyIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Company'
  }],
  personIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Person'
  }],
  callIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Call'
  }],
  emailIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Email'
  }],
  assignedToUserId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  metadata: [{key: String, value: String}],
  notes: [{
    userId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    },
    text: String,
    timestamp: {
      type: Date,
      default: Sugar.Date.create
    }
  }]
});

/*
 VIRTUALS
 */
schema.virtual('details').get(function() {
  return {
    id: this._id,
    name: this.name,
    campaignId: this.campaignId,
    companyIds: this.companyIds,
    assignedToUserId: this.assignedToUserId,
    notes: this.notes.map(n => ({text: n.text, timestamp: n.timestamp, userId: n.userId}))
  };
});

/* ********************************************************************************
 *
 * STATIC METHODS
 *
 **********************************************************************************/

/**
 * @param {Object} body - body passed through from a POST request to be validated
 * @return {Object} - returns an object with validation context
 */
const __doValidation = body => {
  let res = {
    isValid: true,
    missing: [],
    invalid: []
  };

  if (!body.campaignId) {
    res.isValid = false;
    res.missing.push('campaign');
  }
  if (!body.companyIds && !body.personIds) {
    res.isValid = false;
    res.missing.push('data');
  }
  if (!body.assignedToUserId) {
    res.isValid = false;
    res.missing.push('user');
  }

  let app = Shared.validateAppProperties(collectionName, body);
  if (app.isValid === false) {
    res.isValid = false;
    res.invalid = res.invalid.concat(app.invalid);
    res.missing = res.missing.concat(app.missing);
  }

  return res;
};

schema.statics.validate = body => {
  if (body instanceof Array === false) {
    body = [body];
  }
  let validation = body.map(__doValidation).filter(v => v.isValid === false);

  return validation.length >= 1 ? validation[0] : {isValid: true};
};

/*
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
const __add = body => {
  return prev => {
    const md = {
      name: body.name,
      _app: Model.authApp._id,
      campaignId: body.campaignId,
      companyIds: body.companyIds,
      personIds: body.personIds,
      assignedToUserId: body.assignedToUserId,
      notes: body.notes ? body.notes : [],
      metadata: []
    };

    if (body.id) {
      md._id = new ObjectId(body.id);
    }

    const validated = Shared.applyAppProperties(collectionName, body);
    return prev.concat([Object.assign(md, validated)]);
  };
};

schema.statics.add = Shared.add(collection, __add);
// schema.statics.addFromCampaign = (campaign, body) => {
//   body.campaignId = campaign._id;
//   return ModelDef.add(body);
// };

/* ********************************************************************************
 *
 * SCHEMA DB FUNCTIONS
 *
 **********************************************************************************/

schema.statics.exists = id => {
  return collection.find({_id: new ObjectId(id)})
    .limit(1)
    .count()
    .then(count => count > 0);
};

schema.statics.getAll = () => {
  return collection.find({_app: Model.authApp._id}, {metadata: 0});
};

schema.statics.rm = id => {
  Logging.log(`DELETING: ${id}`, Logging.Constants.LogLevel.DEBUG);
  return ModelDef.remove({_id: id});
};

schema.statics.rmAll = () => {
  return ModelDef.remove({});
};

/* ********************************************************************************
 *
 * METHODS
 *
 **********************************************************************************/
const PATH_CONTEXT = {
  '^name$': {type: 'scalar', values: []},
  '^companyIds$': {type: 'vector-add', values: []},
  '^assignedToUserId$': {type: 'scalar', values: []},
  '^companyIds.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^companyIds.([0-9]{1,3}).(__remove__)$': {type: 'vector-rm', values: []},
  '^notes$': {type: 'vector-add', values: []},
  '^notes.([0-9]{1,3}).__remove__$': {type: 'vector-rm', values: []},
  '^notes.([0-9]{1,3}).text$': {type: 'scalar', values: []}
};

schema.statics.validateUpdate = Shared.validateUpdate(PATH_CONTEXT, collectionName);
schema.statics.updateByPath = Shared.updateByPath(PATH_CONTEXT, collectionName, collection);

/* ********************************************************************************
 *
 * METADATA
 *
 **********************************************************************************/

schema.methods.addOrUpdateMetadata = Shared.addOrUpdateMetadata;
schema.methods.findMetadata = Shared.findMetadata;
schema.methods.rmMetadata = Shared.rmMetadata;
schema.statics.getAllMetadata = Shared.getAllMetadata(collection);

ModelDef = mongoose.model('Contactlist', schema);

/* ********************************************************************************
 *
 * EXPORTS
 *
 **********************************************************************************/
module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;

<|endoftext|>