<commit_before>
/* global FileTransferManager, TestUtils */

exports.defineAutoTests = function () {
  describe('Uploader', function () {
    // increase the timeout since android emulators run without acceleration on Travis and are very slow
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000

    var sampleFile = 'tree.jpg'
    var path = ''
    var serverHost = window.cordova.platformId === 'android' ? '10.0.2.2' : 'localhost'
    var serverUrl = 'http://' + serverHost + ':3000/upload'

    beforeEach(function (done) {
      TestUtils.copyFileToDataDirectory(sampleFile).then(function (newPath) {
        path = newPath
        done()
      })
    })

    afterEach(function (done) {
      TestUtils.deleteFile(sampleFile).then(done)
    })
    describe('Add upload', function () {
      it('exposes FileTransferManager globally', function () {
        expect(FileTransferManager).toBeDefined()
      })

      it('should have init function', function () {
        expect(FileTransferManager.init).toBeDefined()
      })

      it('should have startUpload function', function () {
        var nativeUploader = FileTransferManager.init()
        expect(nativeUploader.startUpload).toBeDefined()
      })

      it('returns an error if no argument is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.error).toBe('upload settings object is missing or invalid argument')
          done()
        })
        nativeUploader.startUpload(null)
      })

      it('returns an error if upload id is missing', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.error).toBe('upload id is required')
          done()
        })
        nativeUploader.startUpload({ })
      })

      it('returns an error if serverUrl is missing', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.id).toBe('test_id')
          expect(result.error).toBe('server url is required')
          done()
        })
        nativeUploader.startUpload({ id: 'test_id', filePath: path })
      })

      it('returns an error if serverUrl is invalid', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result).toBeDefined()
          expect(result.id).toBe('123_456')
          expect(result.error).toBe('invalid server url')
          done()
        })
        nativeUploader.startUpload({ id: '123_456', serverUrl: '  ' })
      })

      it('returns an error if filePath is missing', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.id).toBe('some_id')
          expect(result.error).toBe('filePath is required')
          done()
        })
        nativeUploader.startUpload({ id: 'some_id', serverUrl: serverUrl })
      })

      it('sends upload progress events', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          } else if (upload.state === 'UPLOADING') {
            expect(upload.id).toBe('a_file_id')
            expect(typeof upload.progress).toBe('number')
            expect(upload.eventId).toBeUndefined()
            expect(upload.error).toBeUndefined()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'a_file_id', serverUrl: serverUrl, filePath: path })
      })

      it('sends success callback when upload is completed', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('abc')
            expect(upload.eventId).toBeDefined()
            expect(upload.error).toBeUndefined()
            var response = JSON.parse(upload.serverResponse)
            delete response.receivedInfo.headers
            expect(response.receivedInfo).toEqual({
              originalFilename: sampleFile,
              accessMode: 'public',
              height: 4032,
              grayscale: false,
              width: 3024,
              parameters: {}
            })
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'abc', serverUrl: serverUrl, filePath: path })
      })

      it('returns server status code in event', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('pkl')
            expect(upload.statusCode).toBe(210)
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'pkl', serverUrl: serverUrl, filePath: path })
      })

      it('sends headers during upload', function (done) {
        var nativeUploader = FileTransferManager.init()
        var headers = { signature: 'secret_hash', source: 'test' }
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('plop')
            var response = JSON.parse(upload.serverResponse)
            expect(response.receivedInfo.headers.signature).toBe('secret_hash')
            expect(response.receivedInfo.headers.source).toBe('test')
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'plop', serverUrl: serverUrl, filePath: path, headers: headers })
      })

      it('sends parameters during upload', function (done) {
        var nativeUploader = FileTransferManager.init()
        var params = {
          role: 'tester',
          type: 'authenticated'
        }
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('xeon')
            var response = JSON.parse(upload.serverResponse)
            expect(response.receivedInfo.parameters).toEqual(params)
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'xeon', serverUrl: serverUrl, filePath: path, parameters: params })
      })

      it('can upload in parallel', function (done) {
        var nativeUploader = FileTransferManager.init({ parallelUploadsLimit: 2 })
        var ids = new Set()
        var uploadCount = 0
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            nativeUploader.acknowledgeEvent(upload.eventId)
            uploadCount++
            if (uploadCount === 1) {
              expect(ids.has('file_1')).toBeTruthy()
              expect(ids.has('file_2')).toBeTruthy()
            } else
            if (uploadCount === 2) {
              nativeUploader.off('event', cb)
              done()
            }
          } else if (upload.state === 'UPLOADING') {
            ids.add(upload.id)
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'file_1', serverUrl: serverUrl, filePath: path })
        nativeUploader.startUpload({ id: 'file_2', serverUrl: serverUrl, filePath: path })
      })

      it('sends a FAILED event if upload fails', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'FAILED') {
            expect(upload.id).toBe('err_id')
            expect(upload.error).toBeDefined()
            expect(upload.errorCode).toBeDefined()
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'err_id', serverUrl: 'dummy_url', filePath: path })
      })

      it('sends a FAILED callback if file does not exist', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'FAILED') {
            expect(upload.id).toBe('nox')
            expect(upload.eventId).toBeUndefined()
            expect(upload.error).toContain('file does not exist')
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'nox', serverUrl: serverUrl, filePath: '/path/fake.jpg' })
      })
    })

    describe('Remove upload', function () {
      it('should have removeUpload function', function () {
        var nativeUploader = FileTransferManager.init()
        expect(nativeUploader.removeUpload).toBeDefined()
      })

      it('returns an error if no uploadId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.removeUpload(null, null, function (result) {
          expect(result.error).toBe('upload id is required')
          done()
        })
      })

      it('returns an error if undefined uploadId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.removeUpload(undefined, null, function (result) {
          expect(result.error).toBe('upload id is required')
          done()
        })
      })

      it('does not return error if uploadId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.removeUpload('blob', done, null)
      })

      it('sends a FAILED callback when upload is removed', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'FAILED') {
            expect(upload.id).toBe('xyz')
            expect(upload.eventId).toBeDefined()
            expect(upload.error).toContain('cancel')
            expect(upload.errorCode).toBe(-999)
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          } else if (upload.state === 'UPLOADING') {
            nativeUploader.removeUpload('xyz', null, null)
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'xyz', serverUrl: serverUrl, filePath: path })
      })
    })

    describe('Acknowledge event', function () {
      it('should have acknowledgeEvent function', function () {
        var nativeUploader = FileTransferManager.init()
        expect(nativeUploader.acknowledgeEvent).toBeDefined()
      })

      it('returns an error if no eventId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.acknowledgeEvent(null, null, function (result) {
          expect(result.error).toBe('event id is required')
          done()
        })
      })

      it('returns an error if undefined eventId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.acknowledgeEvent(undefined, null, function (result) {
          expect(result.error).toBe('event id is required')
          done()
        })
      })

      it('does not return error if eventId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.acknowledgeEvent('x-coredata://123/UploadEvent/p1', done, null)
      })
    })
  })
}
<commit_msg>simplify parallel uploads<commit_after>/* global FileTransferManager, TestUtils */

exports.defineAutoTests = function () {
  describe('Uploader', function () {
    var sampleFile = 'tree.jpg'
    var path = ''
    var serverHost = window.cordova.platformId === 'android' ? '10.0.2.2' : 'localhost'
    var serverUrl = 'http://' + serverHost + ':3000/upload'

    beforeEach(function (done) {
      TestUtils.copyFileToDataDirectory(sampleFile).then(function (newPath) {
        path = newPath
        done()
      })
    })

    afterEach(function (done) {
      TestUtils.deleteFile(sampleFile).then(done)
    })
    describe('Add upload', function () {
      it('exposes FileTransferManager globally', function () {
        expect(FileTransferManager).toBeDefined()
      })

      it('should have init function', function () {
        expect(FileTransferManager.init).toBeDefined()
      })

      it('should have startUpload function', function () {
        var nativeUploader = FileTransferManager.init()
        expect(nativeUploader.startUpload).toBeDefined()
      })

      it('returns an error if no argument is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.error).toBe('upload settings object is missing or invalid argument')
          done()
        })
        nativeUploader.startUpload(null)
      })

      it('returns an error if upload id is missing', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.error).toBe('upload id is required')
          done()
        })
        nativeUploader.startUpload({ })
      })

      it('returns an error if serverUrl is missing', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.id).toBe('test_id')
          expect(result.error).toBe('server url is required')
          done()
        })
        nativeUploader.startUpload({ id: 'test_id', filePath: path })
      })

      it('returns an error if serverUrl is invalid', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result).toBeDefined()
          expect(result.id).toBe('123_456')
          expect(result.error).toBe('invalid server url')
          done()
        })
        nativeUploader.startUpload({ id: '123_456', serverUrl: '  ' })
      })

      it('returns an error if filePath is missing', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.on('event', function (result) {
          expect(result.id).toBe('some_id')
          expect(result.error).toBe('filePath is required')
          done()
        })
        nativeUploader.startUpload({ id: 'some_id', serverUrl: serverUrl })
      })

      it('sends upload progress events', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          } else if (upload.state === 'UPLOADING') {
            expect(upload.id).toBe('a_file_id')
            expect(typeof upload.progress).toBe('number')
            expect(upload.eventId).toBeUndefined()
            expect(upload.error).toBeUndefined()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'a_file_id', serverUrl: serverUrl, filePath: path })
      })

      it('sends success callback when upload is completed', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('abc')
            expect(upload.eventId).toBeDefined()
            expect(upload.error).toBeUndefined()
            var response = JSON.parse(upload.serverResponse)
            delete response.receivedInfo.headers
            expect(response.receivedInfo).toEqual({
              originalFilename: sampleFile,
              accessMode: 'public',
              height: 4032,
              grayscale: false,
              width: 3024,
              parameters: {}
            })
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'abc', serverUrl: serverUrl, filePath: path })
      })

      it('returns server status code in event', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('pkl')
            expect(upload.statusCode).toBe(210)
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'pkl', serverUrl: serverUrl, filePath: path })
      })

      it('sends headers during upload', function (done) {
        var nativeUploader = FileTransferManager.init()
        var headers = { signature: 'secret_hash', source: 'test' }
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('plop')
            var response = JSON.parse(upload.serverResponse)
            expect(response.receivedInfo.headers.signature).toBe('secret_hash')
            expect(response.receivedInfo.headers.source).toBe('test')
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'plop', serverUrl: serverUrl, filePath: path, headers: headers })
      })

      it('sends parameters during upload', function (done) {
        var nativeUploader = FileTransferManager.init()
        var params = {
          role: 'tester',
          type: 'authenticated'
        }
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            expect(upload.id).toBe('xeon')
            var response = JSON.parse(upload.serverResponse)
            expect(response.receivedInfo.parameters).toEqual(params)
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'xeon', serverUrl: serverUrl, filePath: path, parameters: params })
      })

      it('can upload in parallel', function (done) {
        var nativeUploader = FileTransferManager.init({ parallelUploadsLimit: 2 })
        var ids = new Set()
        var cb = function (upload) {
          if (upload.state === 'UPLOADED') {
            nativeUploader.acknowledgeEvent(upload.eventId)
            if (ids.size === 1) {
              console.log('parallel ids:', ids)
              expect(ids).toEqual(new Set(['file_1', 'file_2']))
            } else
            if (ids.size === 2) {
              nativeUploader.off('event', cb)
              done()
            }
          } else if (upload.state === 'UPLOADING') {
            ids.add(upload.id)
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'file_1', serverUrl: serverUrl, filePath: path })
        nativeUploader.startUpload({ id: 'file_2', serverUrl: serverUrl, filePath: path })
      })

      it('sends a FAILED event if upload fails', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'FAILED') {
            expect(upload.id).toBe('err_id')
            expect(upload.error).toBeDefined()
            expect(upload.errorCode).toBeDefined()
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'err_id', serverUrl: 'dummy_url', filePath: path })
      })

      it('sends a FAILED callback if file does not exist', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'FAILED') {
            expect(upload.id).toBe('nox')
            expect(upload.eventId).toBeUndefined()
            expect(upload.error).toContain('file does not exist')
            nativeUploader.off('event', cb)
            done()
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'nox', serverUrl: serverUrl, filePath: '/path/fake.jpg' })
      })
    })

    describe('Remove upload', function () {
      it('should have removeUpload function', function () {
        var nativeUploader = FileTransferManager.init()
        expect(nativeUploader.removeUpload).toBeDefined()
      })

      it('returns an error if no uploadId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.removeUpload(null, null, function (result) {
          expect(result.error).toBe('upload id is required')
          done()
        })
      })

      it('returns an error if undefined uploadId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.removeUpload(undefined, null, function (result) {
          expect(result.error).toBe('upload id is required')
          done()
        })
      })

      it('does not return error if uploadId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.removeUpload('blob', done, null)
      })

      it('sends a FAILED callback when upload is removed', function (done) {
        var nativeUploader = FileTransferManager.init()
        var cb = function (upload) {
          if (upload.state === 'FAILED') {
            expect(upload.id).toBe('xyz')
            expect(upload.eventId).toBeDefined()
            expect(upload.error).toContain('cancel')
            expect(upload.errorCode).toBe(-999)
            nativeUploader.acknowledgeEvent(upload.eventId)
            nativeUploader.off('event', cb)
            done()
          } else if (upload.state === 'UPLOADING') {
            nativeUploader.removeUpload('xyz', null, null)
          }
        }
        nativeUploader.on('event', cb)
        nativeUploader.startUpload({ id: 'xyz', serverUrl: serverUrl, filePath: path })
      })
    })

    describe('Acknowledge event', function () {
      it('should have acknowledgeEvent function', function () {
        var nativeUploader = FileTransferManager.init()
        expect(nativeUploader.acknowledgeEvent).toBeDefined()
      })

      it('returns an error if no eventId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.acknowledgeEvent(null, null, function (result) {
          expect(result.error).toBe('event id is required')
          done()
        })
      })

      it('returns an error if undefined eventId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.acknowledgeEvent(undefined, null, function (result) {
          expect(result.error).toBe('event id is required')
          done()
        })
      })

      it('does not return error if eventId is given', function (done) {
        var nativeUploader = FileTransferManager.init()
        nativeUploader.acknowledgeEvent('x-coredata://123/UploadEvent/p1', done, null)
      })
    })
  })
}
<|endoftext|>


<commit_before>export class Extra {
  constructor () {
    this.blockTypeSwitcher()
    this.saveBlock()
    this.newBlock()

    // bind events
    $(document).on('click', '[data-role="page-block-visibility"]', this.toggleVisibilityBlock)
  }

  newBlock () {
    $('[data-role="page-content-tab"]').on('collection-field-added', '[data-addfield="collection"]', (event, formCollectionItem) => {
      const $formCollectionItem = $(formCollectionItem)
      $formCollectionItem.find('[data-role="select-block-type"]').trigger('change')
      $formCollectionItem.find('[data-role="page-block-edit"]').trigger('click')
    })
  }

  saveBlock () {
    $('[data-role="page-content-tab"]').on('click', '[data-role="page-block-save"]', (e) => {
      const $modal = $(e.currentTarget).closest('.modal')
      const separator = ' › '
      const $pageBlockWrapper = $modal.closest('[data-role="page-block-wrapper"]')
      const $pageBlockTitleWrapper = $pageBlockWrapper.find('[data-role="page-block-title-wrapper"]')
      const $pageBlockTitle = $pageBlockWrapper.find('[data-role="page-block-title"]')
      const $pageBlockType = $pageBlockWrapper.find('[data-role="page-block-type"]')
      const $pageBlockPreview = $pageBlockWrapper.find('[data-role="page-block-preview"]')
      const selectedBlockType = $modal.find('[data-role="select-block-type"]').val()

      if (selectedBlockType === 'block' || selectedBlockType === 'widget') {
        const title = jsBackend.pages.extras.extractExtraTitle(
          separator,
          $modal.find('[data-role="page-block-content-type-wrapper"][data-type="' + selectedBlockType + '"] select option:selected')
        )
        $pageBlockTitleWrapper.removeClass('d-none')
        $pageBlockTitle.text(title)
        $pageBlockType.text(selectedBlockType)
      } else {
        $pageBlockTitleWrapper.addClass('d-none')
      }

      if (selectedBlockType === 'rich_text') {
        $pageBlockPreview.removeClass('d-none')

        const text = jsBackend.pages.extras.getBlockPreview($.makeArray($modal.find('.ce-block [contenteditable]')))
        if (text !== '') {
          $pageBlockPreview.html(text)
        } else {
          $pageBlockPreview.html(jsBackend.locale.lbl('NoContentToShow'))
        }
      } else {
        $pageBlockPreview.addClass('d-none')
      }

      $modal.modal('hide')
    })
  }

  getBlockPreview ($elements) {
    let previewText = ''
    const prefix = '
'
    let affix = '
'
    let length = 75
    const addHellip = (string) => {
      if (string.length === 0) {
        return string
      }

      if (string.length > length) {
        --length
        affix = '…' + affix
      }

      return prefix + string.substring(0, length) + affix
    }

    do {
      previewText += ' ' + $elements.shift().innerText
      previewText = previewText.trim()
    } while (previewText.length < length && $elements.length > 0)

    return addHellip(previewText)
  }

  extractExtraTitle (separator, $selectedOption) {
    return $selectedOption.closest('optgroup').attr('label') + separator + $selectedOption.text()
  }

  blockTypeSwitcher () {
    $('[data-role="page-content-tab"]').on('change', '[data-role="select-block-type"]', (e) => {
      const $this = $(e.currentTarget)
      const $wrapper = $this.closest('[data-role="page-block-form-wrapper"]')
      $wrapper.find('[data-role="page-block-content-type-wrapper"]').hide()
      $wrapper.find('[data-role="page-block-content-type-wrapper"][data-type=' + $this.val() + ']').show()

      this.validateOnlyOneModuleIsConnected()
    })
    $('[data-role="page-content-tab"] [data-role="select-block-type"]').trigger('change') // set the initial state
  }

  validateOnlyOneModuleIsConnected () {
    const $selectedBlockOption = $('[data-role="select-block-type"] option:selected[value="block"]:first')
    const $allBlockOptions = $('[data-role="select-block-type"] option[value="block"]')

    if ($selectedBlockOption.length === 0) {
      $allBlockOptions.removeAttr('disabled')

      return
    }

    $allBlockOptions.not($selectedBlockOption).attr('disabled', 'disabled')
  }

  toggleVisibilityBlock (e) {
    // get checkbox
    const $checkbox = $(e.currentTarget)
    const $blackWrapper = $checkbox.parents('[data-role="page-block-wrapper"]')
    const $label = $blackWrapper.find('label[for="' + $checkbox.attr('id') + '"]')

    if ($blackWrapper.hasClass('block-not-visible')) {
      // make visible
      $blackWrapper.removeClass('block-not-visible')
      $label.find('[data-fa-i2svg]').attr('data-icon', 'eye')
      $label.find('.sr-only').html(jsBackend.locale.lbl('Hide'))
    } else {
      // make invisible
      $blackWrapper.addClass('block-not-visible')
      $label.find('[data-fa-i2svg]').attr('data-icon', 'eye-slash')
      $label.find('.sr-only').html(jsBackend.locale.lbl('Show'))
    }
  }
}
<commit_msg>Remove jsBackend that came back from merge conflicts<commit_after>export class Extra {
  constructor () {
    this.blockTypeSwitcher()
    this.saveBlock()
    this.newBlock()

    // bind events
    $(document).on('click', '[data-role="page-block-visibility"]', this.toggleVisibilityBlock)
  }

  newBlock () {
    $('[data-role="page-content-tab"]').on('collection-field-added', '[data-addfield="collection"]', (event, formCollectionItem) => {
      const $formCollectionItem = $(formCollectionItem)
      $formCollectionItem.find('[data-role="select-block-type"]').trigger('change')
      $formCollectionItem.find('[data-role="page-block-edit"]').trigger('click')
    })
  }

  saveBlock () {
    $('[data-role="page-content-tab"]').on('click', '[data-role="page-block-save"]', (e) => {
      const $modal = $(e.currentTarget).closest('.modal')
      const separator = ' › '
      const $pageBlockWrapper = $modal.closest('[data-role="page-block-wrapper"]')
      const $pageBlockTitleWrapper = $pageBlockWrapper.find('[data-role="page-block-title-wrapper"]')
      const $pageBlockTitle = $pageBlockWrapper.find('[data-role="page-block-title"]')
      const $pageBlockType = $pageBlockWrapper.find('[data-role="page-block-type"]')
      const $pageBlockPreview = $pageBlockWrapper.find('[data-role="page-block-preview"]')
      const selectedBlockType = $modal.find('[data-role="select-block-type"]').val()

      if (selectedBlockType === 'block' || selectedBlockType === 'widget') {
        const title = this.extractExtraTitle(
          separator,
          $modal.find('[data-role="page-block-content-type-wrapper"][data-type="' + selectedBlockType + '"] select option:selected')
        )
        $pageBlockTitleWrapper.removeClass('d-none')
        $pageBlockTitle.text(title)
        $pageBlockType.text(selectedBlockType)
      } else {
        $pageBlockTitleWrapper.addClass('d-none')
      }

      if (selectedBlockType === 'rich_text') {
        $pageBlockPreview.removeClass('d-none')

        const text = this.getBlockPreview($.makeArray($modal.find('.ce-block [contenteditable]')))
        if (text !== '') {
          $pageBlockPreview.html(text)
        } else {
          $pageBlockPreview.html(window.backend.locale.lbl('NoContentToShow'))
        }
      } else {
        $pageBlockPreview.addClass('d-none')
      }

      $modal.modal('hide')
    })
  }

  getBlockPreview ($elements) {
    let previewText = ''
    const prefix = '
'
    let affix = '
'
    let length = 75
    const addHellip = (string) => {
      if (string.length === 0) {
        return string
      }

      if (string.length > length) {
        --length
        affix = '…' + affix
      }

      return prefix + string.substring(0, length) + affix
    }

    do {
      previewText += ' ' + $elements.shift().innerText
      previewText = previewText.trim()
    } while (previewText.length < length && $elements.length > 0)

    return addHellip(previewText)
  }

  extractExtraTitle (separator, $selectedOption) {
    return $selectedOption.closest('optgroup').attr('label') + separator + $selectedOption.text()
  }

  blockTypeSwitcher () {
    $('[data-role="page-content-tab"]').on('change', '[data-role="select-block-type"]', (e) => {
      const $this = $(e.currentTarget)
      const $wrapper = $this.closest('[data-role="page-block-form-wrapper"]')
      $wrapper.find('[data-role="page-block-content-type-wrapper"]').hide()
      $wrapper.find('[data-role="page-block-content-type-wrapper"][data-type=' + $this.val() + ']').show()

      this.validateOnlyOneModuleIsConnected()
    })
    $('[data-role="page-content-tab"] [data-role="select-block-type"]').trigger('change') // set the initial state
  }

  validateOnlyOneModuleIsConnected () {
    const $selectedBlockOption = $('[data-role="select-block-type"] option:selected[value="block"]:first')
    const $allBlockOptions = $('[data-role="select-block-type"] option[value="block"]')

    if ($selectedBlockOption.length === 0) {
      $allBlockOptions.removeAttr('disabled')

      return
    }

    $allBlockOptions.not($selectedBlockOption).attr('disabled', 'disabled')
  }

  toggleVisibilityBlock (e) {
    // get checkbox
    const $checkbox = $(e.currentTarget)
    const $blackWrapper = $checkbox.parents('[data-role="page-block-wrapper"]')
    const $label = $blackWrapper.find('label[for="' + $checkbox.attr('id') + '"]')

    if ($blackWrapper.hasClass('block-not-visible')) {
      // make visible
      $blackWrapper.removeClass('block-not-visible')
      $label.find('[data-fa-i2svg]').attr('data-icon', 'eye')
      $label.find('.sr-only').html(window.backend.locale.lbl('Hide'))
    } else {
      // make invisible
      $blackWrapper.addClass('block-not-visible')
      $label.find('[data-fa-i2svg]').attr('data-icon', 'eye-slash')
      $label.find('.sr-only').html(window.backend.locale.lbl('Show'))
    }
  }
}
<|endoftext|>


<commit_before>module.exports = function(sails) {

  /**
   * Module dependencies.
   */

  var _ = require('lodash'),
      util = require('sails-util'),
      pathToRegexp = require('path-to-regexp');

  return function initializeCsrf() {

    // Loop through the configured routes in order, and create a blacklist and a whitelist
    // containing regexes to check routes against.
    var blacklist = [];
    var whitelist = [];
    // Regex to check if the route is...a regex.
    var regExRoute = /^r\|(.*)\|(.*)$/;

    sails.on('router:after', function() {

      var sortedRouteAddresses = sails.router.getSortedRouteAddresses();

      _.each(sortedRouteAddresses, function(address) {

        var routeInfo = sails.util.detectVerb(address);
        var path = routeInfo.original.toLowerCase();
        var verb = routeInfo.verb.toLowerCase();

        // Perform the check
        var matches = path.match(regExRoute);
        var regex;

        // If it *is* a regex, create a RegExp object that Express can bind,
        // pull out the params, and wrap the handler in regexRouteWrapper
        if (matches) {
          regex = new RegExp(matches[1]);
        } else {
          regex =pathToRegexp(address);
        }

        var target = sails.config.routes[address];

        if (target.csrf === false && sails.config.security.csrf === true) {
          blacklist.push({method: verb || '', regex: regex});
        }

        else if (target.csrf === true && sails.config.security.csrf === false) {
          whitelist.push({method: verb || '', regex: regex});
        }

      });

    });

    // Add the csrf token action
    sails.registerAction(csrfToken, 'security/grantcsrftoken');

    sails.on('router:before', function () {

      sails.router.bind('/*', function(req, res, next) {

        var method = req.method.toLowerCase();
        // If global CSRF is disabled check the whitelist.
        if (sails.config.security.csrf === false) {
          // If nothing in the whitelist matches, continue on without checking for a CSRF token.
          if (!_.any(whitelist, function(whitelistedRoute) {
            return req.url.match(whitelistedRoute.regex) && (!whitelistedRoute.method || whitelistedRoute.method === req.method.toLowerCase());
          })) {
            res.locals._csrf = null;
            return next();
          }
        }
        // Otherwise check the blacklist
        else {
          // If anything in the blacklist matches, continue on without checking for a CSRF token.
          if (_.any(blacklist, function(blacklistedRoute) {
            return req.url.match(blacklistedRoute.regex) && (!blacklistedRoute.method || blacklistedRoute.method === req.method.toLowerCase());
          })) {
            res.locals._csrf = null;
            return next();
          }
        }

        // Start with a clear _csrf template token
        res.locals._csrf = null;

        // Disable CSRF protection when no session is present
        if (!req.session) {
          sails.log.silly('Disabling CSRF protection for ' + req.url + ' since request has no session.');
          return next();
        }

        var csrf = require('csurf');

        return csrf()(req, res, function(err) {
          if (err) {
            // Only attempt to handle invalid csrf tokens
            if (err.code !== 'EBADCSRFTOKEN') {
              throw err;
            }
            // Return an Access-Control-Allow-Origin header in case this is a xdomain request
            if (req.headers.origin) {
              res.set('Access-Control-Allow-Origin', req.headers.origin);
              res.set('Access-Control-Allow-Credentials', true);
            }
            return res.forbidden('CSRF mismatch');
          }

          if (util.isSameOrigin(req) || allowCrossOriginCSRF) {
            res.locals._csrf = req.csrfToken();
          } else {
            res.locals._csrf = null;
          }

          next();
        });

      }, null, {_middlewareType: 'CSRF HOOK: CSRF'});

    });

    return;

  };

  function csrfToken (req, res, next) {
    return res.json({
        _csrf: res.locals._csrf
    });
  }

};
<commit_msg>prevent server crash if csurf triggers unexpected error<commit_after>module.exports = function(sails) {

  /**
   * Module dependencies.
   */

  var _ = require('lodash'),
      util = require('sails-util'),
      pathToRegexp = require('path-to-regexp');

  return function initializeCsrf() {

    // Loop through the configured routes in order, and create a blacklist and a whitelist
    // containing regexes to check routes against.
    var blacklist = [];
    var whitelist = [];
    // Regex to check if the route is...a regex.
    var regExRoute = /^r\|(.*)\|(.*)$/;

    sails.on('router:after', function() {

      var sortedRouteAddresses = sails.router.getSortedRouteAddresses();

      _.each(sortedRouteAddresses, function(address) {

        var routeInfo = sails.util.detectVerb(address);
        var path = routeInfo.original.toLowerCase();
        var verb = routeInfo.verb.toLowerCase();

        // Perform the check
        var matches = path.match(regExRoute);
        var regex;

        // If it *is* a regex, create a RegExp object that Express can bind,
        // pull out the params, and wrap the handler in regexRouteWrapper
        if (matches) {
          regex = new RegExp(matches[1]);
        } else {
          regex =pathToRegexp(address);
        }

        var target = sails.config.routes[address];

        if (target.csrf === false && sails.config.security.csrf === true) {
          blacklist.push({method: verb || '', regex: regex});
        }

        else if (target.csrf === true && sails.config.security.csrf === false) {
          whitelist.push({method: verb || '', regex: regex});
        }

      });

    });

    // Add the csrf token action
    sails.registerAction(csrfToken, 'security/grantcsrftoken');

    sails.on('router:before', function () {

      sails.router.bind('/*', function(req, res, next) {

        var method = req.method.toLowerCase();
        // If global CSRF is disabled check the whitelist.
        if (sails.config.security.csrf === false) {
          // If nothing in the whitelist matches, continue on without checking for a CSRF token.
          if (!_.any(whitelist, function(whitelistedRoute) {
            return req.url.match(whitelistedRoute.regex) && (!whitelistedRoute.method || whitelistedRoute.method === req.method.toLowerCase());
          })) {
            res.locals._csrf = null;
            return next();
          }
        }
        // Otherwise check the blacklist
        else {
          // If anything in the blacklist matches, continue on without checking for a CSRF token.
          if (_.any(blacklist, function(blacklistedRoute) {
            return req.url.match(blacklistedRoute.regex) && (!blacklistedRoute.method || blacklistedRoute.method === req.method.toLowerCase());
          })) {
            res.locals._csrf = null;
            return next();
          }
        }

        // Start with a clear _csrf template token
        res.locals._csrf = null;

        // Disable CSRF protection when no session is present
        if (!req.session) {
          sails.log.silly('Disabling CSRF protection for ' + req.url + ' since request has no session.');
          return next();
        }

        var csrf = require('csurf');

        return csrf()(req, res, function(err) {
          if (err) {
            // Only attempt to handle invalid csrf tokens
            if (err.code !== 'EBADCSRFTOKEN') {
              return next(err);
            }
            // Return an Access-Control-Allow-Origin header in case this is a xdomain request
            if (req.headers.origin) {
              res.set('Access-Control-Allow-Origin', req.headers.origin);
              res.set('Access-Control-Allow-Credentials', true);
            }
            return res.forbidden('CSRF mismatch');
          }

          if (util.isSameOrigin(req) || allowCrossOriginCSRF) {
            res.locals._csrf = req.csrfToken();
          } else {
            res.locals._csrf = null;
          }

          next();
        });

      }, null, {_middlewareType: 'CSRF HOOK: CSRF'});

    });

    return;

  };

  function csrfToken (req, res, next) {
    return res.json({
        _csrf: res.locals._csrf
    });
  }

};
<|endoftext|>


<commit_before>(function() {
    'use strict';

    angular.module('chaise.viewer')

    .controller('AnnotationsController', ['annotations', 'anatomies', 'AnnotationsService', '$window', '$scope', function AnnotationsController(annotations, anatomies, AnnotationsService, $window, $scope) {
        var vm = this;
        vm.annotations = annotations;
        vm.anatomies = anatomies;

        vm.filterAnnotations = filterAnnotations;

        vm.createMode = false;
        vm.newAnnotation = null;
        vm.drawAnnotation = drawAnnotation;
        vm.createAnnotation = createAnnotation;
        vm.cancelNewAnnotation = cancelNewAnnotation;

        vm.editedAnnotation = null; // Track which annotation is being edited right now; used to show/hide the right UI elements depending on which one is being edited.
        // vm.originalAnnotation = null; // Holds the original contents of annotation in the event that a user cancels an edit
        vm.editAnnotation = editAnnotation;
        vm.cancelEdit = cancelEdit;
        vm.updateAnnotation = updateAnnotation;

        vm.deleteAnnotation = deleteAnnotation;

        vm.highlightedAnnotation = null;
        vm.setHighlightedAnnotation = setHighlightedAnnotation;

        $window.addEventListener('message', function annotationControllerListener(event) {
            if (event.origin === window.location.origin) {
                var data = event.data;
                if (data.messageType === 'annotationDrawn') {
                    vm.newAnnotation = {
                        description: '',
                        shape: data.content.shape
                    };
                    $scope.$apply(function() {
                        vm.createMode = true;
                    });
                } else if (data.messageType === 'onHighlighted') {
                    var content = JSON.parse(data.content);
                    var annotation = findAnnotation(content.data.shapes[0].geometry);
                    $scope.$apply(function() {
                        vm.highlightedAnnotation = annotation.data.id;
                    });
                } else if (data.messageType ==='onUnHighlighted') {
                    $scope.$apply(function() {
                        vm.highlightedAnnotation = null;
                    });
                }
            }
        });

        // Returns true if at least one of a specified subset of an object's keys contains a value that contains the query
        function filterAnnotations(keys) {
            var query = vm.query;
            return function(annotation) {
                if (!query) {
                    // If query is "" or undefined, then the annotation is considered a match
                    return true;
                } else {
                    console.log(query);
                    annotation = annotation.data;
                    query = query.toLowerCase();
                    // // If the "anatomy" key is null, make it "No Anatomy" so that a query for "No Anatomy" will match this key
                    if (!annotation.anatomy) {
                        annotation.anatomy = 'No Anatomy';
                    }
                    // Loop through the array to find matches
                    var numKeys = keys.length;
                    if (numKeys > 0) {
                        for (var i = 0; i < numKeys; i++) {
                            console.log('Checking | Key: ', keys[i], ' Value: ', annotation[keys[i]]);
                            if (annotation[keys[i]].toLowerCase().indexOf(query) !== -1) {
                                return true;
                            }
                        }
                    }
                    // // Set the "anatomy" key back to null if it was changed to "No Anatomy" earlier
                    if (annotation.anatomy === 'No Anatomy') {
                        annotation.anatomy = null;
                    }
                }
            }
        }

        function drawAnnotation(annotation) {
            return AnnotationsService.drawAnnotation();
        }

        function createAnnotation() {
            vm.createMode = false;
            return AnnotationsService.createAnnotation(vm.newAnnotation);
        }

        function cancelNewAnnotation() {
            vm.createMode = false;
            return AnnotationsService.cancelNewAnnotation();
        }

        function editAnnotation(annotation) {
            vm.editedAnnotation = annotation.data.id;
            // vm.originalAnnotation = annotation;
        };

        function cancelEdit(annotation) {
            vm.editedAnnotation = null;
            // annotation = vm.originalAnnotation;
        };

        function updateAnnotation(annotation) {
            vm.editedAnnotation = null;
            return AnnotationsService.updateAnnotation(annotation);
        }

        function deleteAnnotation(annotation) {
            return AnnotationsService.deleteAnnotation(annotation);
        };

        function setHighlightedAnnotation(annotation) {
            if (vm.highlightedAnnotation == annotation.data.id) {
                vm.highlightedAnnotation = null;
                return;
            }

            vm.highlightedAnnotation = annotation.data.id;
            highlightAnnotation(annotation);
        }

        function highlightAnnotation(annotation) {
            return AnnotationsService.highlightAnnotation(annotation);
        }

        // Return an annotation that matches an object of coordinates
        function findAnnotation(coordinates) {
            for (var i = 0; i < vm.annotations.length; i++) {
                var annotationCoords = vm.annotations[i].data.coords;
                if (coordinates.x == annotationCoords[0] && coordinates.y == annotationCoords[1] && coordinates.width == annotationCoords[2] && coordinates.height == annotationCoords[3]) {
                    return vm.annotations[i];
                }
            }
        }
    }]);
})();
<commit_msg>Scroll annotation into view in the sidebar when user hovers over the annotation in Annotorious<commit_after>(function() {
    'use strict';

    angular.module('chaise.viewer')

    .controller('AnnotationsController', ['annotations', 'anatomies', 'AnnotationsService', '$window', '$document', '$scope', function AnnotationsController(annotations, anatomies, AnnotationsService, $window, $document, $scope) {
        var vm = this;
        vm.annotations = annotations;
        vm.anatomies = anatomies;

        vm.filterAnnotations = filterAnnotations;

        vm.createMode = false;
        vm.newAnnotation = null;
        vm.drawAnnotation = drawAnnotation;
        vm.createAnnotation = createAnnotation;
        vm.cancelNewAnnotation = cancelNewAnnotation;

        vm.editedAnnotation = null; // Track which annotation is being edited right now; used to show/hide the right UI elements depending on which one is being edited.
        // vm.originalAnnotation = null; // Holds the original contents of annotation in the event that a user cancels an edit
        vm.editAnnotation = editAnnotation;
        vm.cancelEdit = cancelEdit;
        vm.updateAnnotation = updateAnnotation;

        vm.deleteAnnotation = deleteAnnotation;

        vm.highlightedAnnotation = null;
        vm.setHighlightedAnnotation = setHighlightedAnnotation;

        $window.addEventListener('message', function annotationControllerListener(event) {
            if (event.origin === window.location.origin) {
                var data = event.data;
                if (data.messageType === 'annotationDrawn') {
                    vm.newAnnotation = {
                        description: '',
                        shape: data.content.shape
                    };
                    $scope.$apply(function() {
                        vm.createMode = true;
                    });
                } else if (data.messageType === 'onHighlighted') {
                    var content = JSON.parse(data.content);
                    var annotation = findAnnotation(content.data.shapes[0].geometry);
                    $scope.$apply(function() {
                        vm.highlightedAnnotation = annotation.data.id;
                        // Scroll the annotation into visible part of browser
                        document.getElementById('annotation-' + vm.highlightedAnnotation).scrollIntoView({
                            block: 'start',
                            behavior: 'smooth'
                        });
                    });
                    var element = document.get
                } else if (data.messageType ==='onUnHighlighted') {
                    $scope.$apply(function() {
                        vm.highlightedAnnotation = null;
                    });
                }
            }
        });

        // Returns true if at least one of a specified subset of an object's keys contains a value that contains the query
        function filterAnnotations(keys) {
            var query = vm.query;
            return function(annotation) {
                if (!query) {
                    // If query is "" or undefined, then the annotation is considered a match
                    return true;
                } else {
                    console.log(query);
                    annotation = annotation.data;
                    query = query.toLowerCase();
                    // // If the "anatomy" key is null, make it "No Anatomy" so that a query for "No Anatomy" will match this key
                    if (!annotation.anatomy) {
                        annotation.anatomy = 'No Anatomy';
                    }
                    // Loop through the array to find matches
                    var numKeys = keys.length;
                    if (numKeys > 0) {
                        for (var i = 0; i < numKeys; i++) {
                            if (annotation[keys[i]].toLowerCase().indexOf(query) !== -1) {
                                return true;
                            }
                        }
                    }
                    // // Set the "anatomy" key back to null if it was changed to "No Anatomy" earlier
                    if (annotation.anatomy === 'No Anatomy') {
                        annotation.anatomy = null;
                    }
                }
            }
        }

        function drawAnnotation(annotation) {
            return AnnotationsService.drawAnnotation();
        }

        function createAnnotation() {
            vm.createMode = false;
            return AnnotationsService.createAnnotation(vm.newAnnotation);
        }

        function cancelNewAnnotation() {
            vm.createMode = false;
            return AnnotationsService.cancelNewAnnotation();
        }

        function editAnnotation(annotation) {
            vm.editedAnnotation = annotation.data.id;
            // vm.originalAnnotation = annotation;
        };

        function cancelEdit(annotation) {
            vm.editedAnnotation = null;
            // annotation = vm.originalAnnotation;
        };

        function updateAnnotation(annotation) {
            vm.editedAnnotation = null;
            return AnnotationsService.updateAnnotation(annotation);
        }

        function deleteAnnotation(annotation) {
            return AnnotationsService.deleteAnnotation(annotation);
        };

        function setHighlightedAnnotation(annotation) {
            if (vm.highlightedAnnotation == annotation.data.id) {
                vm.highlightedAnnotation = null;
                return;
            }

            vm.highlightedAnnotation = annotation.data.id;
            highlightAnnotation(annotation);
        }

        function highlightAnnotation(annotation) {
            return AnnotationsService.highlightAnnotation(annotation);
        }

        // Return an annotation that matches an object of coordinates
        function findAnnotation(coordinates) {
            for (var i = 0; i < vm.annotations.length; i++) {
                var annotationCoords = vm.annotations[i].data.coords;
                if (coordinates.x == annotationCoords[0] && coordinates.y == annotationCoords[1] && coordinates.width == annotationCoords[2] && coordinates.height == annotationCoords[3]) {
                    return vm.annotations[i];
                }
            }
        }
    }]);
})();
<|endoftext|>


<commit_before>/* global _ */
/* global Styx */
/* global esprima */
/* global vis */
(function() {
    var network;
    var fontFaces = "Consolas, Menlo, Monaco, monospace";

    window.cfgVisualization = {
        parseProgram: parseProgram,
        renderControlFlowGraph: renderControlFlowGraph
    };

    function parseProgram(code, options) {
        return Styx.parse(esprima.parse(code), options);
    }

    function renderControlFlowGraph(container, controlFlowGraph) {
        var visualizationOptions = {
            width: "100%",
            height: "100%",

            layout: {
                hierarchical: {
                    levelSeparation: 100,
                    direction: "UD",
                    sortMethod: "directed"
                }
            },

            edges: {
                smooth: {
                    type: "curvedCW",
                    roundness: 0.45
                }
            }
        };

        if (network) {
            network.destroy();
        }

        setTimeout(function() {
            var visGraph = generateNodesAndEdges(controlFlowGraph);
            network = new vis.Network(container, visGraph, visualizationOptions);
        }, 0);
    }

    function generateNodesAndEdges(cfg) {
        var nodeSet = { };
        collectNodes(cfg.entry, nodeSet);

        var nodes = [];
        var edges = [];

        _.each(nodeSet, function(node) {
            addNodeAndEdges(node, nodes, edges);
        });

        return {
            nodes: new vis.DataSet(nodes),
            edges: new vis.DataSet(edges)
        };
    }

    function addNodeAndEdges(node, nodes, edges) {
        nodes.push({
            id: node.id,
            label: node.label || node.id,
            color: getNodeColor(node),
            font: {
                face: fontFaces,
                color: getNodeFontColor(node)
            }
        });

        _.each(node.outgoingEdges, function(outgoingEdge) {
            var color = getEdgeColor(outgoingEdge);
            var visEdge = {
                from: node.id,
                to: outgoingEdge.target.id,
                label: outgoingEdge.label,
                color: color,
                font: {
                    background: "white",
                    color: color,
                    face: fontFaces
                },
                arrows: "to"
            };

            edges.push(visEdge);
        });
    }

    function getNodeColor(node) {
        switch (node.type) {
            case Styx.NodeType.Entry:
                return "#FFCC00";
            case Styx.NodeType.SuccessExit:
                return "#4CD964";
            case Styx.NodeType.ErrorExit:
                return "#FF2D55";
            default:
                return "#97C2FC";
        }
    }

    function getNodeFontColor(node) {
        return node.type === Styx.NodeType.ErrorExit ? "white" : "black";
    }

    function getEdgeColor(edge) {
        switch (edge.type) {
            case Styx.EdgeType.Epsilon:
                return "#999999";
            case Styx.EdgeType.Conditional:
                return "#FF9500";
            case Styx.EdgeType.AbruptCompletion:
                return "#FF2D55";
            default:
                return "#2B7CE9";
        }
    }

    function collectNodes(node, nodeSet) {
        if (nodeSet[node.id]) {
            return;
        }

        nodeSet[node.id] = node;

        for (var i = 0; i < node.outgoingEdges.length; i++) {
            var targetNode = node.outgoingEdges[i].target;
            collectNodes(targetNode, nodeSet);
        }
    }
}());
<commit_msg>Forces visualization nodes to be perfect circles<commit_after>/* global _ */
/* global Styx */
/* global esprima */
/* global vis */
(function() {
    var network;
    var fontFaces = "Consolas, Menlo, Monaco, monospace";

    window.cfgVisualization = {
        parseProgram: parseProgram,
        renderControlFlowGraph: renderControlFlowGraph
    };

    function parseProgram(code, options) {
        return Styx.parse(esprima.parse(code), options);
    }

    function renderControlFlowGraph(container, controlFlowGraph) {
        var visualizationOptions = {
            width: "100%",
            height: "100%",

            layout: {
                hierarchical: {
                    levelSeparation: 100,
                    direction: "UD",
                    sortMethod: "directed"
                }
            },

            nodes: {
                shape: "circle"
            },

            edges: {
                smooth: {
                    type: "curvedCW",
                    roundness: 0.45
                }
            }
        };

        if (network) {
            network.destroy();
        }

        setTimeout(function() {
            var visGraph = generateNodesAndEdges(controlFlowGraph);
            network = new vis.Network(container, visGraph, visualizationOptions);
        }, 0);
    }

    function generateNodesAndEdges(cfg) {
        var nodeSet = { };
        collectNodes(cfg.entry, nodeSet);

        var nodes = [];
        var edges = [];

        _.each(nodeSet, function(node) {
            addNodeAndEdges(node, nodes, edges);
        });

        return {
            nodes: new vis.DataSet(nodes),
            edges: new vis.DataSet(edges)
        };
    }

    function addNodeAndEdges(node, nodes, edges) {
        nodes.push({
            id: node.id,
            label: node.label || node.id,
            color: getNodeColor(node),
            font: {
                face: fontFaces,
                color: getNodeFontColor(node)
            }
        });

        _.each(node.outgoingEdges, function(outgoingEdge) {
            var color = getEdgeColor(outgoingEdge);
            var visEdge = {
                from: node.id,
                to: outgoingEdge.target.id,
                label: outgoingEdge.label,
                color: color,
                font: {
                    background: "white",
                    color: color,
                    face: fontFaces
                },
                arrows: "to"
            };

            edges.push(visEdge);
        });
    }

    function getNodeColor(node) {
        switch (node.type) {
            case Styx.NodeType.Entry:
                return "#FFCC00";
            case Styx.NodeType.SuccessExit:
                return "#4CD964";
            case Styx.NodeType.ErrorExit:
                return "#FF2D55";
            default:
                return "#97C2FC";
        }
    }

    function getNodeFontColor(node) {
        return node.type === Styx.NodeType.ErrorExit ? "white" : "black";
    }

    function getEdgeColor(edge) {
        switch (edge.type) {
            case Styx.EdgeType.Epsilon:
                return "#999999";
            case Styx.EdgeType.Conditional:
                return "#FF9500";
            case Styx.EdgeType.AbruptCompletion:
                return "#FF2D55";
            default:
                return "#2B7CE9";
        }
    }

    function collectNodes(node, nodeSet) {
        if (nodeSet[node.id]) {
            return;
        }

        nodeSet[node.id] = node;

        for (var i = 0; i < node.outgoingEdges.length; i++) {
            var targetNode = node.outgoingEdges[i].target;
            collectNodes(targetNode, nodeSet);
        }
    }
}());
<|endoftext|>


<commit_before>module.exports = function(grunt) {

grunt.config('protractor', {

  basic: {
    options: {
      configFile: 'config.protractor.js',
      args: {
        baseUrl: 'http://localhost:<%= project.frontend.port.webserver %>'
      }
    }
  }

});

grunt.loadNpmTasks('grunt-protractor-runner');

};
<commit_msg>tools/ redefined protractor task name #14<commit_after>module.exports = function(grunt) {

grunt.config('protractor', {

  e2e: {
    options: {
      configFile: 'config.protractor.js',
      args: {
        baseUrl: 'http://localhost:<%= project.frontend.port.webserver %>'
      }
    }
  }

});

grunt.loadNpmTasks('grunt-protractor-runner');

};
<|endoftext|>


<commit_before>import { store } from '../persistence/store';

export const LOAD_PLAYLISTS = 'LOAD_PLAYLISTS';
export const ADD_PLAYLIST = 'ADD_PLAYLIST';
export const DELETE_PLAYLIST = 'DELETE_PLAYLIST';

export function addPlaylist(tracks, name) {
  return dispatch => {
    let playlists = store.get('playlists') || {};
    let playlist = {name, tracks};

    if (tracks.length === 0) {
      dispatch({
	      type: null
      });
      return;
    }

    if (playlists) {
      playlists.push(playlist);
    } else {
      playlists = [playlist];
    }

    store.set('playlists', playlists);
    dispatch({
      type: ADD_PLAYLIST,
      payload: playlists
    });
  };
}

export function deletePlaylist(name) {
  return dispatch => {
    let playlists = store.get('playlists') || {};
    if (playlists) {
      let index = _.findIndex(playlists, {'name': name});
      playlists.pop(index);
    } else {
      playlists = [];
    }

    store.set('playlists', playlists);
    dispatch({
      type: DELETE_PLAYLIST,
      payload: playlists
    });
  };
}

export function loadPlaylists() {
  return dispatch => {
    let playlists = store.get('playlists') || {};

    if (playlists) {
      dispatch({
 	type: LOAD_PLAYLISTS,
	payload: playlists
      });
    } else {
      dispatch({
	 type: LOAD_PLAYLISTS,
         payload: []
      });
    }
  };
}
<commit_msg>Update playlists.js<commit_after>import { store } from '../persistence/store';

export const LOAD_PLAYLISTS = 'LOAD_PLAYLISTS';
export const ADD_PLAYLIST = 'ADD_PLAYLIST';
export const DELETE_PLAYLIST = 'DELETE_PLAYLIST';

export function addPlaylist(tracks, name) {
  return dispatch => {
    let playlists = store.get('playlists') || {};
    let playlist = {name, tracks};

    if (tracks.length === 0) {
      dispatch({
	      type: null
      });
      return;
    }

    if (playlists) {
      playlists.push(playlist);
    } else {
      playlists = [playlist];
    }

    store.set('playlists', playlists);
    dispatch({
      type: ADD_PLAYLIST,
      payload: playlists
    });
  };
}

export function deletePlaylist(name) {
  return dispatch => {
    let playlists = store.get('playlists') || {};
    if (playlists) {
      let index = _.findIndex(playlists, {'name': name});
      playlists.pop(index);
    } else {
      playlists = [];
    }

    store.set('playlists', playlists);
    dispatch({
      type: DELETE_PLAYLIST,
      payload: playlists
    });
  };
}

export function loadPlaylists() {
  return dispatch => {
    let playlists = store.get('playlists') || {};
    if (playlists) {
      dispatch({
	      type: LOAD_PLAYLISTS,
	      payload: playlists
      });
    } else {
      dispatch({
	 type: LOAD_PLAYLISTS,
         payload: []
      });
    }
  };
}
<|endoftext|>


<commit_before>// function called only for display purposes on clicks
function getFillColor(mode) {
  if (mode === "empty") {
    return "#f7f7f5";
  } else if (mode === "blue") {
    return "#268bd2";
  } else if (mode === "orange") {
    return "#eb8b16";
  } else if (mode === "purple") {
    return "#6c71c4";
  } else if (mode === "green") {
    return "#859900";
  } else if (mode === "red") {
    return "#dc322f";
  } else if (mode === "bg") {
    return "#f7f7f5";
  } else {  // it is a multi-tile
    return 'url(#' + mode + ')';
  }
}

// handle the driver's game data structures on mouse click
function processSpace(d, i) {
  var coordinates = [d.row, d.col];
  // what happens next depends on global mode

  var curModes = mode.split("-");  // multitiles are "-" delimited
  d3.select("#mode").text(JSON.stringify(curModes));

  if (curModes.length === 1) {
    var thisMode = curModes[0];
    var array = [];
    if (thisMode === "empty") {
      array = empties;
    } else {
      if (!singles.hasOwnProperty(mode)) {
        singles[mode] = [];
      }
      array = singles[mode];
    }

    var idx = array.pIndexOf(coordinates);  // dwelp.js augments prototype

    // toggle existence in array
    if (idx === -1) {
      d3.select("#circle_" + i)
        .attr("stroke", "#777")
        .attr("stroke-width", "2px")
        .attr("fill", getFillColor(mode))
        .attr("opacity", 0.8);
      array.push(coordinates);
      // remove from any other arrays it might have been in
      for (key in singles) {
        if (singles[key] !== array) {
          singles[key].pSplice(coordinates);  // another prototype augment
        }
      }
      for (key in multies) {
        multies[key].pSplice(coordinates);
      }
      if (thisMode !== "empty") {
        empties.pSplice(coordinates);
      }
    } else {
      d3.select("#circle_" + i)
        .attr("stroke", "#eee")
        .attr("stroke-width", "1px")
        .attr("fill", getFillColor("bg"))
        .attr("opacity", 0.2);
      array.pSplice(coordinates);
    }
  } else {
    for (var j = 0; j < curModes.length; j++) {
      var thisMode = curModes[j];
      array = multies[thisMode];
      var idx = array.pIndexOf(coordinates);
      // toggle existence in array
      if (idx === -1) {
        // here we want to select second row
        d3.select("#circle_" + i)
          .attr("stroke", "#000")
          .attr("stroke-width", "2px")
          .attr("fill", getFillColor(mode))
          .attr("opacity", 0.8);
        array.push(coordinates);
        empties.pSplice(coordinates);
        // remove from any other arrays it might have been in
        for (key in singles) {
          if (singles[key] !== array) {
            singles[key].pSplice(coordinates);  // another prototype augment
          }
        }
        for (key in multies) {
          // here we need to check if current color is involved
          if (curModes.pIndexOf(key) === -1) {
            multies[key].pSplice(coordinates);
          }
        }
      } else {
        // TODO this one is complicated. For now multis can overwrite but not erase.
      }
    }
  }

  //d3.select("#empties").text("empties: " + JSON.stringify(empties));
  //d3.select("#singles").text("singles: " + JSON.stringify(singles));
  //d3.select("#multies").text("multies: " + JSON.stringify(multies));
}

// hold driver's game data structures while we build
var mode = "empty";

var empties = [];
var singles = {'blue':[], 'orange':[], 'purple':[], 'red': [], 'green': []};
var multies = {'blue':[], 'orange':[], 'purple':[], 'green': [], 'red': []};

// set up grid on screen
var gridDim = 9;
var canvSize = 500;

var spaceType = 'empty';

var svg = d3.select("#svg")
  .append("svg")
    .attr("width", canvSize)
    .attr("height", canvSize)
  .append("g");

// need n-1 lines for n grid spaces
var xs = d3.range(canvSize/gridDim, canvSize + 0.1, canvSize/gridDim);

// for circles
var cr = 0.48 * canvSize / gridDim;
var cxs = [];
for (var i = 0; i < xs.length; i++) {
  for (var j = 0; j < xs.length; j++) {
    cxs.push({row: i, col: j, cy: xs[i] - cr, cx: xs[j] - cr});
  }
}

// horizontal lines of grid
svg.selectAll(".horizontalGrid")
    .data(xs)
  .enter()
    .append("line")
    .attr({"x1" : 0,
           "x2" : canvSize,
           "y1" : function(d){return d;},
           "y2" : function(d){return d;},
           "fill" : "none",
           "shape-rendering" : "crispEdges",
           "stroke" : "#bbb",
           "opacity" : 0.4,
           "stroke-width" : "2px"});

// vertical line
svg.selectAll(".verticalGrid")
    .data(xs)
  .enter()
    .append("line")
    .attr({"x1" : function(d){return d;},
           "x2" : function(d){return d;},
           "y1" : 0,
           "y2" : canvSize,
           "fill" : "none",
           "shape-rendering" : "crispEdges",
           "stroke" : "#bbb",
           "opacity" : 0.4,
           "stroke-width" : "2px"});

svg.selectAll(".boardcircles")
    .data(cxs)
  .enter()
    .append("circle")
    .attr({"id" : function(d, i) { return "circle_" + i; },
           "class" : "boardcircle",
           "cx" : function(d){return d.cx;},
           "cy" : function(d){return d.cy;},
           "r" : cr,
           "fill": "#eee",
           "stroke" : "#eee",
           "opacity" : 0.2,
           "stroke-width" : "1px"})
    .on("click", processSpace);

// MENU TO CHANGE MODE

// function to fill in multi-Tile circles
var buildTripleGradient = function(id, col1, col2, col3) {
  var gradient = svg.append("svg:defs")
      .append("svg:linearGradient")
      .attr("id", id)
      .attr("x1", "0%")
      .attr("y1", "0%")
      .attr("x2", "100%")
      .attr("y2", "100%")
      .attr("spreadMethod", "pad");
  gradient.append("svg:stop")
      .attr("offset", "0%")
      .attr("stop-color", col1)
      .attr("stop-opacity", 1);
  gradient.append("svg:stop")
      .attr("offset", "50%")
      .attr("stop-color", col2)
      .attr("stop-opacity", 0.75);
  gradient.append("svg:stop")
      .attr("offset", "100%")
      .attr("stop-color", col3)
      .attr("stop-opacity", 1);
}

buildTripleGradient("blue-orange", getFillColor("blue"), getFillColor("bg"), getFillColor("orange"));
buildTripleGradient("blue-purple", getFillColor("blue"), getFillColor("bg"), getFillColor("purple"));
buildTripleGradient("blue-green", getFillColor("blue"), getFillColor("bg"), getFillColor("green"));
buildTripleGradient("orange-green", getFillColor("orange"), getFillColor("bg"), getFillColor("green"));
buildTripleGradient("blue-orange-purple", getFillColor("blue"), getFillColor("orange"), getFillColor("purple"));

var nMenuCol = 6;
var nMenuRow = 2;

function changeMode(d, i) {
  mode = d.mode;
  d3.selectAll(".menucircle").attr("opacity", 0.8);
  d3.select("#menucircle_" + i).attr("opacity", 1);
}

menuItems = [];
itemNames = ["empty", "blue", "orange", "purple", "green", "red"];
multiNames = ["blue-orange", "blue-purple", "blue-green", "orange-green", "blue-orange-purple"];
for (var j = 0; j < itemNames.length; j++) {
  menuItems.push({type: "single", mode: itemNames[j], cy: canvSize / gridDim - cr, cx: xs[j] - cr});
}
for (var j = 0; j < multiNames.length; j++) {
  menuItems.push({type: "multi", mode: multiNames[j], cy: 2 * canvSize / gridDim - cr, cx: xs[j] - cr});
}

var svgMenu = d3.select("#svg-menu")
  .append("svg")
    .attr("width", nMenuCol * canvSize / gridDim)
    .attr("height", nMenuRow * canvSize / gridDim)
  .append("g");

svgMenu.selectAll(".menucircles")
    .data(menuItems)
  .enter()
    .append("circle")
    .attr({"id" : function(d, i) { return "menucircle_" + i; },
           "class" : "menucircle",
           "cx" : function(d){return d.cx;},
           "cy" : function(d){return d.cy;},
           "r" : 0.8 * cr,
           "fill": function(d){return getFillColor(d.mode);},
           "opacity" : 1,
           "stroke" : "#aaa",
           "stroke-width" : "2px"})
    .on("click", changeMode);

// reset the whole plot situation
var resetCurrentSetup = function() {
  empties = [];
  singles = {};
  multies = {'blue':[], 'orange':[], 'purple':[], 'green': [], 'red': []};

  d3.select("#movecount").property("value", "");

  d3.selectAll(".menucircle")
    .attr("opacity", 1);
  d3.selectAll(".boardcircle")
    .attr({"fill" : "#eee",
           "stroke" : "#eee",
           "opacity" : 0.2});

  clearResults();
}

// ****************************************************************************
// GAME HANDLING AND SOLVER MAGIC HAPPENS HERE

var checkForAlternatingMode = function() {
  if (!(singles.hasOwnProperty('red') && singles.hasOwnProperty('green'))) {
    //console.log("false:  needs red/green");
    return false;
  }

  for (key in singles) {
    if ((key === 'red' || key === 'green')) {
      if(singles[key].length === 0) {
        //console.log("false: red/green empty");
        return false;
      }
    } else {
      if(singles[key].length > 0) {
        //console.log("false: key " + key + " exists");
        return false;
      }
    }
  }
  return true;
}

// quickie to clear results lines + text
var clearResults = function() {
  svg.selectAll(".resultslines").data([]).exit().remove();
  d3.select("#solution").text("");
}

// move to the next element with wrap around
var toggle = function(array, el) {
  var idx = array.indexOf(el);
  return (idx === array.length - 1) ? array[0] : array[idx + 1];
}

// solution looks like [3,2]->[3,1]:[0,0]->[0,1]
var plotSolution = function(solution) {
  if (solution.length === 0) {
    return;
  }

  var colors = ["#dc322f", "#268bd2", "#859900"];
  var offsets = [-10, 0, 10, 5];
  var color = "#859900";
  var offset = -10;
  // populate move list
  var moves = solution.split(":");
  var prevEnd = "",
      spaces = [],
      start = [],
      end = [],
      moveList  = [];
  for (var i = 0; i < moves.length; i++) {
    spaces = moves[i].split("->");
    if (spaces[0] !== prevEnd) {
      color = toggle(colors, color);
      offset = toggle(offsets, offset);
    }

    start = JSON.parse(spaces[0]);
    end = JSON.parse(spaces[1]);
    moveList.push({x1: xs[start[1]] - cr, y1: xs[start[0]] - cr,
                   x2: xs[end[1]] - cr, y2: xs[end[0]] - cr,
                   color: color, offset: offset});

    prevEnd = spaces[1];
  }

  // now plot it
  var g = svg.selectAll(".resultslines")
      .data(moveList)
    .enter()
      .append("g")
      .attr("class", "resultslines");

  g.append("line")
      .attr({"x1" : function(d){return d.x1;},
             "x2" : function(d){return d.x2;},
             "y1" : function(d){return d.y1;},
             "y2" : function(d){return d.y2;},
             "fill" : "none",
             "shape-rendering" : "crispEdges",
             "stroke" : function(d){return d.color;},
             "opacity" : 0.9,
             "stroke-width" : "2px"});

  g.append("text")
      .text(function(d, i) {return (i + 1) + "."})
      .attr({"x" : function(d){return 0.5 * d.x1 + 0.5 * d.x2 + d.offset;},
             "y" : function(d){return 0.5 * d.y1 + 0.5 * d.y2 + d.offset;},
             "fill" : function(d){return d.color;},
             "font-size" : 18,
             "stroke" : function(d){return d.color;}});

}

var solveCurrentSetup = function() {
  // super rudimentary, non-robust error checking
  if (empties.length === 0) {
    d3.select("#solution").text("Invalid game: no empty tiles");
    return 0;
  }
  var hasSingles = false;
  for (key in singles) {
    if (singles[key].length > 1 || (singles[key].length == 1 && multies[key].length >= 1)) {
      hasSingles = true;
      break;
    }
  }
  if (!hasSingles) {
    d3.select("#solution").text("Invalid game: fewer than two elements of a color");
    return 0;
  }

  // create a Game
  var game = dwelp.Game();

  // set parameters based on driver variables (rest are set in constructor)
  game.empties = empties.slice();
  game.singles = game.copyGameInput(singles);
  game.multies = game.copyGameInput(multies);
  game.alternatingColors = checkForAlternatingMode();
  game.maxMoves = +d3.select("#movecount").property("value");

  //console.log(JSON.stringify(game));

  // solve and post
  game.solvegame();
  d3.select("#solution").text("Solution: " + JSON.stringify(game.solution));
  plotSolution(game.solution);
}

<commit_msg>changed empty tiles back to thin border<commit_after>// function called only for display purposes on clicks
function getFillColor(mode) {
  if (mode === "empty") {
    return "#f7f7f5";
  } else if (mode === "blue") {
    return "#268bd2";
  } else if (mode === "orange") {
    return "#eb8b16";
  } else if (mode === "purple") {
    return "#6c71c4";
  } else if (mode === "green") {
    return "#859900";
  } else if (mode === "red") {
    return "#dc322f";
  } else if (mode === "bg") {
    return "#f7f7f5";
  } else {  // it is a multi-tile
    return 'url(#' + mode + ')';
  }
}

// handle the driver's game data structures on mouse click
function processSpace(d, i) {
  var coordinates = [d.row, d.col];
  // what happens next depends on global mode

  var curModes = mode.split("-");  // multitiles are "-" delimited
  d3.select("#mode").text(JSON.stringify(curModes));

  if (curModes.length === 1) {
    var thisMode = curModes[0];
    var array = [];
    if (thisMode === "empty") {
      array = empties;
    } else {
      if (!singles.hasOwnProperty(mode)) {
        singles[mode] = [];
      }
      array = singles[mode];
    }

    var idx = array.pIndexOf(coordinates);  // dwelp.js augments prototype

    // toggle existence in array
    if (idx === -1) {
      d3.select("#circle_" + i)
        .attr("stroke", "#777")
        .attr("stroke-width", function(){return (thisMode === "empty") ? "1px" : "2px";})
        .attr("fill", getFillColor(mode))
        .attr("opacity", 0.8);
      array.push(coordinates);
      // remove from any other arrays it might have been in
      for (key in singles) {
        if (singles[key] !== array) {
          singles[key].pSplice(coordinates);  // another prototype augment
        }
      }
      for (key in multies) {
        multies[key].pSplice(coordinates);
      }
      if (thisMode !== "empty") {
        empties.pSplice(coordinates);
      }
    } else {
      d3.select("#circle_" + i)
        .attr("stroke", "#eee")
        .attr("stroke-width", "1px")
        .attr("fill", getFillColor("bg"))
        .attr("opacity", 0.2);
      array.pSplice(coordinates);
    }
  } else {
    for (var j = 0; j < curModes.length; j++) {
      var thisMode = curModes[j];
      array = multies[thisMode];
      var idx = array.pIndexOf(coordinates);
      // toggle existence in array
      if (idx === -1) {
        // here we want to select second row
        d3.select("#circle_" + i)
          .attr("stroke", "#000")
          .attr("stroke-width", "2px")
          .attr("fill", getFillColor(mode))
          .attr("opacity", 0.8);
        array.push(coordinates);
        empties.pSplice(coordinates);
        // remove from any other arrays it might have been in
        for (key in singles) {
          if (singles[key] !== array) {
            singles[key].pSplice(coordinates);  // another prototype augment
          }
        }
        for (key in multies) {
          // here we need to check if current color is involved
          if (curModes.pIndexOf(key) === -1) {
            multies[key].pSplice(coordinates);
          }
        }
      } else {
        // TODO this one is complicated. For now multis can overwrite but not erase.
      }
    }
  }

  //d3.select("#empties").text("empties: " + JSON.stringify(empties));
  //d3.select("#singles").text("singles: " + JSON.stringify(singles));
  //d3.select("#multies").text("multies: " + JSON.stringify(multies));
}

// hold driver's game data structures while we build
var mode = "empty";

var empties = [];
var singles = {'blue':[], 'orange':[], 'purple':[], 'red': [], 'green': []};
var multies = {'blue':[], 'orange':[], 'purple':[], 'green': [], 'red': []};

// set up grid on screen
var gridDim = 9;
var canvSize = 500;

var spaceType = 'empty';

var svg = d3.select("#svg")
  .append("svg")
    .attr("width", canvSize)
    .attr("height", canvSize)
  .append("g");

// need n-1 lines for n grid spaces
var xs = d3.range(canvSize/gridDim, canvSize + 0.1, canvSize/gridDim);

// for circles
var cr = 0.48 * canvSize / gridDim;
var cxs = [];
for (var i = 0; i < xs.length; i++) {
  for (var j = 0; j < xs.length; j++) {
    cxs.push({row: i, col: j, cy: xs[i] - cr, cx: xs[j] - cr});
  }
}

// horizontal lines of grid
svg.selectAll(".horizontalGrid")
    .data(xs)
  .enter()
    .append("line")
    .attr({"x1" : 0,
           "x2" : canvSize,
           "y1" : function(d){return d;},
           "y2" : function(d){return d;},
           "fill" : "none",
           "shape-rendering" : "crispEdges",
           "stroke" : "#bbb",
           "opacity" : 0.4,
           "stroke-width" : "2px"});

// vertical line
svg.selectAll(".verticalGrid")
    .data(xs)
  .enter()
    .append("line")
    .attr({"x1" : function(d){return d;},
           "x2" : function(d){return d;},
           "y1" : 0,
           "y2" : canvSize,
           "fill" : "none",
           "shape-rendering" : "crispEdges",
           "stroke" : "#bbb",
           "opacity" : 0.4,
           "stroke-width" : "2px"});

svg.selectAll(".boardcircles")
    .data(cxs)
  .enter()
    .append("circle")
    .attr({"id" : function(d, i) { return "circle_" + i; },
           "class" : "boardcircle",
           "cx" : function(d){return d.cx;},
           "cy" : function(d){return d.cy;},
           "r" : cr,
           "fill": "#eee",
           "stroke" : "#eee",
           "opacity" : 0.2,
           "stroke-width" : "1px"})
    .on("click", processSpace);

// MENU TO CHANGE MODE

// function to fill in multi-Tile circles
var buildTripleGradient = function(id, col1, col2, col3) {
  var gradient = svg.append("svg:defs")
      .append("svg:linearGradient")
      .attr("id", id)
      .attr("x1", "0%")
      .attr("y1", "0%")
      .attr("x2", "100%")
      .attr("y2", "100%")
      .attr("spreadMethod", "pad");
  gradient.append("svg:stop")
      .attr("offset", "0%")
      .attr("stop-color", col1)
      .attr("stop-opacity", 1);
  gradient.append("svg:stop")
      .attr("offset", "50%")
      .attr("stop-color", col2)
      .attr("stop-opacity", 0.75);
  gradient.append("svg:stop")
      .attr("offset", "100%")
      .attr("stop-color", col3)
      .attr("stop-opacity", 1);
}

buildTripleGradient("blue-orange", getFillColor("blue"), getFillColor("bg"), getFillColor("orange"));
buildTripleGradient("blue-purple", getFillColor("blue"), getFillColor("bg"), getFillColor("purple"));
buildTripleGradient("blue-green", getFillColor("blue"), getFillColor("bg"), getFillColor("green"));
buildTripleGradient("orange-green", getFillColor("orange"), getFillColor("bg"), getFillColor("green"));
buildTripleGradient("blue-orange-purple", getFillColor("blue"), getFillColor("orange"), getFillColor("purple"));

var nMenuCol = 6;
var nMenuRow = 2;

function changeMode(d, i) {
  mode = d.mode;
  d3.selectAll(".menucircle").attr("opacity", 0.8);
  d3.select("#menucircle_" + i).attr("opacity", 1);
}

menuItems = [];
itemNames = ["empty", "blue", "orange", "purple", "green", "red"];
multiNames = ["blue-orange", "blue-purple", "blue-green", "orange-green", "blue-orange-purple"];
for (var j = 0; j < itemNames.length; j++) {
  menuItems.push({type: "single", mode: itemNames[j], cy: canvSize / gridDim - cr, cx: xs[j] - cr});
}
for (var j = 0; j < multiNames.length; j++) {
  menuItems.push({type: "multi", mode: multiNames[j], cy: 2 * canvSize / gridDim - cr, cx: xs[j] - cr});
}

var svgMenu = d3.select("#svg-menu")
  .append("svg")
    .attr("width", nMenuCol * canvSize / gridDim)
    .attr("height", nMenuRow * canvSize / gridDim)
  .append("g");

svgMenu.selectAll(".menucircles")
    .data(menuItems)
  .enter()
    .append("circle")
    .attr({"id" : function(d, i) { return "menucircle_" + i; },
           "class" : "menucircle",
           "cx" : function(d){return d.cx;},
           "cy" : function(d){return d.cy;},
           "r" : 0.8 * cr,
           "fill": function(d){return getFillColor(d.mode);},
           "opacity" : 1,
           "stroke" : "#aaa",
           "stroke-width" : "2px"})
    .on("click", changeMode);

// reset the whole plot situation
var resetCurrentSetup = function() {
  empties = [];
  singles = {};
  multies = {'blue':[], 'orange':[], 'purple':[], 'green': [], 'red': []};

  d3.select("#movecount").property("value", "");

  d3.selectAll(".menucircle")
    .attr("opacity", 1);
  d3.selectAll(".boardcircle")
    .attr({"fill" : "#eee",
           "stroke" : "#eee",
           "opacity" : 0.2});

  clearResults();
}

// ****************************************************************************
// GAME HANDLING AND SOLVER MAGIC HAPPENS HERE

var checkForAlternatingMode = function() {
  if (!(singles.hasOwnProperty('red') && singles.hasOwnProperty('green'))) {
    //console.log("false:  needs red/green");
    return false;
  }

  for (key in singles) {
    if ((key === 'red' || key === 'green')) {
      if(singles[key].length === 0) {
        //console.log("false: red/green empty");
        return false;
      }
    } else {
      if(singles[key].length > 0) {
        //console.log("false: key " + key + " exists");
        return false;
      }
    }
  }
  return true;
}

// quickie to clear results lines + text
var clearResults = function() {
  svg.selectAll(".resultslines").data([]).exit().remove();
  d3.select("#solution").text("");
}

// move to the next element with wrap around
var toggle = function(array, el) {
  var idx = array.indexOf(el);
  return (idx === array.length - 1) ? array[0] : array[idx + 1];
}

// solution looks like [3,2]->[3,1]:[0,0]->[0,1]
var plotSolution = function(solution) {
  if (solution.length === 0) {
    return;
  }

  var colors = ["#dc322f", "#268bd2", "#859900"];
  var offsets = [-10, 0, 10, 5];
  var color = "#859900";
  var offset = -10;
  // populate move list
  var moves = solution.split(":");
  var prevEnd = "",
      spaces = [],
      start = [],
      end = [],
      moveList  = [];
  for (var i = 0; i < moves.length; i++) {
    spaces = moves[i].split("->");
    if (spaces[0] !== prevEnd) {
      color = toggle(colors, color);
      offset = toggle(offsets, offset);
    }

    start = JSON.parse(spaces[0]);
    end = JSON.parse(spaces[1]);
    moveList.push({x1: xs[start[1]] - cr, y1: xs[start[0]] - cr,
                   x2: xs[end[1]] - cr, y2: xs[end[0]] - cr,
                   color: color, offset: offset});

    prevEnd = spaces[1];
  }

  // now plot it
  var g = svg.selectAll(".resultslines")
      .data(moveList)
    .enter()
      .append("g")
      .attr("class", "resultslines");

  g.append("line")
      .attr({"x1" : function(d){return d.x1;},
             "x2" : function(d){return d.x2;},
             "y1" : function(d){return d.y1;},
             "y2" : function(d){return d.y2;},
             "fill" : "none",
             "shape-rendering" : "crispEdges",
             "stroke" : function(d){return d.color;},
             "opacity" : 0.9,
             "stroke-width" : "2px"});

  g.append("text")
      .text(function(d, i) {return (i + 1) + "."})
      .attr({"x" : function(d){return 0.5 * d.x1 + 0.5 * d.x2 + d.offset;},
             "y" : function(d){return 0.5 * d.y1 + 0.5 * d.y2 + d.offset;},
             "fill" : function(d){return d.color;},
             "font-size" : 18,
             "stroke" : function(d){return d.color;}});

}

var solveCurrentSetup = function() {
  // super rudimentary, non-robust error checking
  if (empties.length === 0) {
    d3.select("#solution").text("Invalid game: no empty tiles");
    return 0;
  }
  var hasSingles = false;
  for (key in singles) {
    if (singles[key].length > 1 || (singles[key].length == 1 && multies[key].length >= 1)) {
      hasSingles = true;
      break;
    }
  }
  if (!hasSingles) {
    d3.select("#solution").text("Invalid game: fewer than two elements of a color");
    return 0;
  }

  // create a Game
  var game = dwelp.Game();

  // set parameters based on driver variables (rest are set in constructor)
  game.empties = empties.slice();
  game.singles = game.copyGameInput(singles);
  game.multies = game.copyGameInput(multies);
  game.alternatingColors = checkForAlternatingMode();
  game.maxMoves = +d3.select("#movecount").property("value");

  //console.log(JSON.stringify(game));

  // solve and post
  game.solvegame();
  d3.select("#solution").text("Solution: " + JSON.stringify(game.solution));
  plotSolution(game.solution);
}

<|endoftext|>


<commit_before>/* @flow weak */
import { request } from '../utils'
import config from '../config'

export function gitStatus () {
  return request.get(`/git/${config.spaceKey}`)
}

export function gitTags () {
  return request.get(`/git/${config.spaceKey}/tags`)
}

export function gitCheckout (branch, remoteBranch) {
  return request.post(`/git/${config.spaceKey}/checkout`, {
    name: branch, startPoint: remoteBranch
  })
}

export function gitCommit ({files, message}) {
  const url = config.isPlatform ? `/git/${config.spaceKey}/commits` : `/git/${config.spaceKey}`
  return request.post(url, {files, message})
}

export function gitPull () {
  return request.post(`/git/${config.spaceKey}/pull`)
}

export function gitPushAll () {
  return request.post(`/git/${config.spaceKey}/push?all=true`).then(res => {
    if (res.ok || res.nothingToPush) return true
    if (!res.ok) return false
  })
}
export function gitFetch () {
  return request.post(`/git/${config.spaceKey}/fetch`)
}
// branches
export function gitNewBranch (branchName) {
  return request.post(`/git/${config.spaceKey}/branches`, {branchName})
}
export function gitGetBranches () {
  return request.get(`/git/${config.spaceKey}/branches`)
}

export function gitCurrentBranch (){
  return request.get(`/git/${config.spaceKey}/branch`)
}
export function gitDeleteBranch (branchName) {
  return request.delete(`/git/${config.spaceKey}/branches/${branchName}`)
}

export function gitCreateStash (message){
  return request.post(`/git/${config.spaceKey}/stash`, {message: message})
}

export function gitStashList (){
  return request.get(`/git/${config.spaceKey}/stash`)
}

export function gitDropStash (stashRef, all=false){
  return request.delete(`/git/${config.spaceKey}/stash`, {stashRef, all})
}

export function gitApplyStash ({stashRef, pop, applyIndex}){
  return request.post(`/git/${config.spaceKey}/stash/apply`, {stashRef, pop, applyIndex})
}

export function gitCheckoutStash ({stashRef, branch}){
  return request.post(`/git/${config.spaceKey}/stash/checkout`, {stashRef, branch})
}

export function gitResetHead ({ref, resetType}) {
  return request.post(`/git/${config.spaceKey}/reset`, {ref, resetType})
}

export function gitConflicts ({path}) {
  return request.get(`/git/${config.spaceKey}/conflicts`, {path, base64:false})
}

export function gitResolveConflict ({path, content}) {
  return request.post(`/git/${config.spaceKey}/conflicts`, {path, content, base64:false})
}

export function gitCancelConflict ({path}) {
  return request.delete(`/git/${config.spaceKey}/conflicts`, {path})
}

export function gitRebase ({branch, upstream, interactive, preserve}) {
  return request.post(`/git/${config.spaceKey}/rebase`, {branch, upstream, interactive, preserve})
}

export function gitAddTag ({tagName, ref, message, force}) {
  return request.post(`/git/${config.spaceKey}/tags`, {tagName, ref, message, force})
}

export function gitMerge (branch) {
  return request.post(`/git/${config.spaceKey}/merge`, {name: branch})
}

export function gitRebaseState () {
  return request.get(`/git/${config.spaceKey}?state`)
}

export function gitRebaseOperate ({operation, message}) {
  return request.post(`/git/${config.spaceKey}/rebase/operate`, {operation, message})
}

export function gitRebaseUpdate (lines) {
  return request.post(`/git/${config.spaceKey}/rebase/update`, lines,
    { headers: { 'Content-Type': 'application/json' } }
  )
}

export function gitCommitDiff ({rev}) {
  return config.isPlatform ?
    request.diffFilesList(`/git/${config.spaceKey}/commits`, { ref: rev })
  : request.get(`/git/${config.spaceKey}/diff`, {rev})
}

export function gitFileDiff ({ path, oldRef, newRef }) {
  return config.isPlatform ?
    request.diff(`/git/${config.spaceKey}/commits/${oldRef}...${newRef}`, { path })
  : request.get(`/git/${config.spaceKey}/diff`, { path, oldRef, newRef })
}

export function gitReadFile ({ref, path}) {
  return request.get(`/git/${config.spaceKey}/read`, { path, ref, base64: false })
}
<commit_msg>Fix file diff<commit_after>/* @flow weak */
import { request } from '../utils'
import config from '../config'

export function gitStatus () {
  return request.get(`/git/${config.spaceKey}`)
}

export function gitTags () {
  return request.get(`/git/${config.spaceKey}/tags`)
}

export function gitCheckout (branch, remoteBranch) {
  return request.post(`/git/${config.spaceKey}/checkout`, {
    name: branch, startPoint: remoteBranch
  })
}

export function gitCommit ({files, message}) {
  const url = config.isPlatform ? `/git/${config.spaceKey}/commits` : `/git/${config.spaceKey}`
  return request.post(url, {files, message})
}

export function gitPull () {
  return request.post(`/git/${config.spaceKey}/pull`)
}

export function gitPushAll () {
  return request.post(`/git/${config.spaceKey}/push?all=true`).then(res => {
    if (res.ok || res.nothingToPush) return true
    if (!res.ok) return false
  })
}
export function gitFetch () {
  return request.post(`/git/${config.spaceKey}/fetch`)
}
// branches
export function gitNewBranch (branchName) {
  return request.post(`/git/${config.spaceKey}/branches`, {branchName})
}
export function gitGetBranches () {
  return request.get(`/git/${config.spaceKey}/branches`)
}

export function gitCurrentBranch (){
  return request.get(`/git/${config.spaceKey}/branch`)
}
export function gitDeleteBranch (branchName) {
  return request.delete(`/git/${config.spaceKey}/branches/${branchName}`)
}

export function gitCreateStash (message){
  return request.post(`/git/${config.spaceKey}/stash`, {message: message})
}

export function gitStashList (){
  return request.get(`/git/${config.spaceKey}/stash`)
}

export function gitDropStash (stashRef, all=false){
  return request.delete(`/git/${config.spaceKey}/stash`, {stashRef, all})
}

export function gitApplyStash ({stashRef, pop, applyIndex}){
  return request.post(`/git/${config.spaceKey}/stash/apply`, {stashRef, pop, applyIndex})
}

export function gitCheckoutStash ({stashRef, branch}){
  return request.post(`/git/${config.spaceKey}/stash/checkout`, {stashRef, branch})
}

export function gitResetHead ({ref, resetType}) {
  return request.post(`/git/${config.spaceKey}/reset`, {ref, resetType})
}

export function gitConflicts ({path}) {
  return request.get(`/git/${config.spaceKey}/conflicts`, {path, base64:false})
}

export function gitResolveConflict ({path, content}) {
  return request.post(`/git/${config.spaceKey}/conflicts`, {path, content, base64:false})
}

export function gitCancelConflict ({path}) {
  return request.delete(`/git/${config.spaceKey}/conflicts`, {path})
}

export function gitRebase ({branch, upstream, interactive, preserve}) {
  return request.post(`/git/${config.spaceKey}/rebase`, {branch, upstream, interactive, preserve})
}

export function gitAddTag ({tagName, ref, message, force}) {
  return request.post(`/git/${config.spaceKey}/tags`, {tagName, ref, message, force})
}

export function gitMerge (branch) {
  return request.post(`/git/${config.spaceKey}/merge`, {name: branch})
}

export function gitRebaseState () {
  return request.get(`/git/${config.spaceKey}?state`)
}

export function gitRebaseOperate ({operation, message}) {
  return request.post(`/git/${config.spaceKey}/rebase/operate`, {operation, message})
}

export function gitRebaseUpdate (lines) {
  return request.post(`/git/${config.spaceKey}/rebase/update`, lines,
    { headers: { 'Content-Type': 'application/json' } }
  )
}

export function gitCommitDiff ({rev}) {
  return config.isPlatform ?
    request.diffFilesList(`/git/${config.spaceKey}/commits`, { ref: rev })
  : request.get(`/git/${config.spaceKey}/diff`, {rev})
}

export function gitFileDiff ({ path, oldRef, newRef }) {
  return config.isPlatform ?
    request.diff(`/git/${config.spaceKey}/commits`, { path, oldRef, newRef })
  : request.get(`/git/${config.spaceKey}/diff`, { path, oldRef, newRef })
}

export function gitReadFile ({ref, path}) {
  return request.get(`/git/${config.spaceKey}/read`, { path, ref, base64: false })
}
<|endoftext|>