<commit_before>var fx = require('fs-extra');
var npmi = require('npmi');
var path = require('path');
/*
 * TODO: It should install 4 things:
 *     - command/plugins
 *     - blueprints for sites (blog, spa)
 *     - generator templates (post, article, page)
 *     - themes <= This requires a whole lot of thought.
 */
module.exports = function install(config, nikko){

    var packages = config.packages;

    packages.forEach(function(packageId){
        console.log('  install : %s', packageId);
        installPackage({
            name: packageId,
            path: nikko.basePath,
            npmLoad: {
                loglevel: 'silent'
            }
        });
    });
};

function doInstall(pluginId){

    var plugSource = path.resolve(path.join(nikko.basePath, 'node_modules', pluginId));
    var plugTarget = path.join(nikko.pluginPath, pluginId);

    var binSource = path.join(plugSource, 'bin', pluginId);
    var binTarget = path.join(nikko.binPath, pluginId);

    fx.move(binSource, binTarget, function (err) {
        if (err){
            if(err.code === 'ENOENT'){
                nikko.logger.error('nikko-install Error: the plugin was not found on the node_modules. Are you sure npm was successful?');
                return
            }
            return console.error(err);
        }

        fx.chmodSync(binTarget, 0755);

        fx.move(plugSource, plugTarget, function (err) {
            if (err) return console.error(err);
            console.log("success!");
            //We should get data from plugin... before we go!
            nikko.registerPlugin(pluginId);
        });
    });
}

function installPackage(options){
    console.log('WARNING! NPM INSTALL DISABLED DEBUGGING ======')
    doInstall(options.name);
    console.log('WARNING! NPM INSTALL DISABLED DEBUGGING ======')
    return

    npmi(options, function (err, result) {
        if (err) {
            if (err.code === npmi.LOAD_ERR) console.log('npm load error');
            else if (err.code === npmi.INSTALL_ERR) console.log('npm install error');
            return console.log(err.message);
        }

        // installed
        var pluginPath = path.resolve(options.path);
        console.log(options.name+'@'+options.version+' installed successfully in '+ pluginPath);

        doInstall(options.name);
    });
}<commit_msg>PLUGIN: nikko-install refactoring, copy/delete<commit_after>var fx = require('fs-extra');
var npmi = require('npmi');
var path = require('path');
/*
 * TODO: It should install 4 things:
 *     - command/plugins
 *     - blueprints for sites (blog, spa)
 *     - generator templates (post, article, page)
 *     - themes <= This requires a whole lot of thought.
 */
module.exports = function install(config, nikko){

    var manager = new Installer(nikko);

    var packages = config.packages;

    packages.forEach(function(packageId){
        console.log('  install : %s', packageId);
        manager.install({
            name: packageId,
            path: nikko.basePath,
            npmLoad: {
                loglevel: 'silent'
            }
        });
    });
};

function Installer(nikko){
    this.nikko = nikko;
}

Installer.prototype.register = function(pluginId){

    var nikko = this.nikko;

    var plugSource = path.resolve(path.join(nikko.basePath, 'node_modules', pluginId));
    var plugTarget = path.join(nikko.pluginPath, pluginId);

    var binSource = path.join(plugSource, 'bin', pluginId);
    var binTarget = path.join(nikko.binPath, pluginId);

    //TODO: fx.copy and on pluginRegistered fx.delete
    fx.copy(binSource, binTarget, function (err) {
        if (err){
            if(err.code === 'ENOENT'){
                nikko.logger.error('nikko-install Error: the plugin was not found on the node_modules. Are you sure npm was successful?');
                return
            }
            return console.error(err);
        }

        fx.chmodSync(binTarget, 0755);

        //TODO: fx.copy and on pluginRegistered fx.delete
        fx.copy(plugSource, plugTarget, function (err) {
            if (err) return console.error(err);
            console.log("success!");
            //We should get data from plugin... before we go!
            nikko.on('plugin.registered.'+pluginId, function(){
                console.log('WE SHOULD NOW DELETE FOLDERS', arguments);
                fx.removeSync(binSource);
                fx.removeSync(plugSource);
            });
            nikko.registerPlugin(pluginId);
        });
    });
}

Installer.prototype.install = function(options){
    console.log('WARNING! NPM INSTALL DISABLED DEBUGGING ======')
    this.register(options.name);
    console.log('WARNING! NPM INSTALL DISABLED DEBUGGING ======')
    return

    npmi(options, function (err, result) {
        if (err) {
            if (err.code === npmi.LOAD_ERR) console.log('npm load error');
            else if (err.code === npmi.INSTALL_ERR) console.log('npm install error');
            return console.log(err.message);
        }

        // installed
        var pluginPath = path.resolve(options.path);
        console.log(options.name+'@'+options.version+' installed successfully in '+ pluginPath);

        this.register(options.name);
    }.bind(this));
};<|endoftext|>


<commit_before>export default class DepthShader {
  static pack() {
    return `
      vec4 pack (float depth) {
        const vec4 bitSh = vec4(256 * 256 * 256, 256 * 256, 256, 1.0);
        const vec4 bitMsk = vec4(0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
        vec4 comp = fract(depth * bitSh);
        comp -= comp.xxyz * bitMsk;
        return comp;
      }
    `;
  }

  static unpack() {
    return `
      float unpack (vec4 packedDepth) {
        const vec4 bitShifts = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1);
        return dot(packedDepth, bitShifts);
      }
    `;
  }

  static bumpFromDepth({
    getDepth = 'return 0.;',
  } = {}) {
    return `
      float getDepth(vec2 position) {
        ${getDepth}
      }

      vec4 bumpFromDepth(sampler2D depthTexture, vec2 uv, vec2 resolution, float scale) {
        vec2 step = 1. / resolution;

        float depth = texture2D(depthTexture, uv).r;

        vec2 dxy = depth - vec2(
            texture2D(depthTexture, uv + vec2(step.x, 0.)).r,
            texture2D(depthTexture, uv + vec2(0., step.y)).r
        );

        return vec4(depth, normalize(vec3(dxy * scale / step, 1.)));
      }

      vec4 bumpFromDepth(vec2 uv, vec2 resolution, float scale) {
        vec2 step = 1. / resolution;

        float depth = getDepth(uv);

        vec2 dxy = depth - vec2(
            getDepth(uv + vec2(step.x, 0.)),
            getDepth(uv + vec2(0., step.y))
        );

        return vec4(depth, normalize(vec3(dxy * scale / step, 1.)));
      }
    `;
  }
}
<commit_msg>fix DepthShader for webgl2<commit_after>export default class DepthShader {
  static pack() {
    return `
      vec4 pack (float depth) {
        const vec4 bitSh = vec4(256 * 256 * 256, 256 * 256, 256, 1.0);
        const vec4 bitMsk = vec4(0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
        vec4 comp = fract(depth * bitSh);
        comp -= comp.xxyz * bitMsk;
        return comp;
      }
    `;
  }

  static unpack() {
    return `
      float unpack (vec4 packedDepth) {
        const vec4 bitShifts = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1);
        return dot(packedDepth, bitShifts);
      }
    `;
  }

  static bumpFromDepth({
    getDepth = 'return 0.;',
  } = {}) {
    return `
      float getDepth(vec2 position) {
        ${getDepth}
      }

      vec4 bumpFromDepth(sampler2D depthTexture, vec2 uv, vec2 resolution, float scale) {
        vec2 step = 1. / resolution;

        float depth = texture(depthTexture, uv).r;

        vec2 dxy = depth - vec2(
            texture(depthTexture, uv + vec2(step.x, 0.)).r,
            texture(depthTexture, uv + vec2(0., step.y)).r
        );

        return vec4(depth, normalize(vec3(dxy * scale / step, 1.)));
      }

      vec4 bumpFromDepth(vec2 uv, vec2 resolution, float scale) {
        vec2 step = 1. / resolution;

        float depth = getDepth(uv);

        vec2 dxy = depth - vec2(
            getDepth(uv + vec2(step.x, 0.)),
            getDepth(uv + vec2(0., step.y))
        );

        return vec4(depth, normalize(vec3(dxy * scale / step, 1.)));
      }
    `;
  }
}
<|endoftext|>


<commit_before>require('dotenv').config()
const express = require('express')
const app = require('express')()
const port = 1337

const MongoClient = require("mongodb").MongoClient
const MONGODB_URI = process.env.MONGODB_URI
console.log(MONGODB_URI)

MongoClient.connect(MONGODB_URI, (err, database) => {
	if (err) return console.log(err)
	db = database
})

app
	.listen(process.env.PORT || port, () => {
		console.log('api server on http://localhost:' + port)
	})

app.get('/api/v1/init/generator/:generator', (req, res) => {
	const generator = req.params.generator
	initGenerator(generator)
})

app.get('/api/v1/init/generator/:generator/stand/:stand', (req, res) => {
	const generator = req.params.generator
	const stand = req.params.stand
	initStand(generator, stand)
})

app.get('/api/v1/grid/:id', (req, res) => {

})

app.get('/api/v1/generator/:id', (req, res) => {

})

app.get('/api/v1/stand/:stand', (req, res) => {
	const standCollection = db.collection('stands')
	const stand = req.params.stand

	standCollection.findOne({
		name: stand
	}, function(err, stand) {
		if (err) return console.log(err)
			res.json(JSON.stringify(stand))

	})
})

const generate = () => {
	setInterval(function() {
		const generatorCollection = db.collection('generators')
		const standCollection = db.collection('stands')

		standCollection.find({}, {}).toArray(function(err, stands) {
			stands.map(function(stand) {
				generateMessage(stand)
			})

		})
	}, 3000)
}

// generate()

const initGenerator = (generator) => {
	const generatorCollection = db.collection('generators')
	const data = {
		name: generator,
		created_at: Date.now()
	}

	generatorCollection.save(data, (err, result) => {
		if (err) return console.log(err)
	})
}

const initStand = (generator, stand) => {
	const generatorCollection = db.collection('generators')
	const standCollection = db.collection('stands')

	generatorCollection.findOne({
		name: generator
	}, function(err, generator) {
		if (err) return console.log(err)
		const data = {
			name: stand,
			generator: generator._id,
			created_at: Date.now()
		}

		standCollection.save(data, (err, result) => {
			if (err) return console.log(err)
			console.info('Created stand called `' + data.name + '` that is connected to generator `' + generator.name + '`')
		})
	})

}

const generateMessage = (stand) => {
	const generatorCollection = db.collection('generators')
	const messageCollection = db.collection('messages')

	const data = {
		stand: stand._id,
		generator: stand.generator,
		timestamp: Date.now(),
		avr_va: randomNumnodem(90500, 90600),
		min_va: randomNumnodem(86500, 86700),
		max_va: randomNumnodem(95400, 95600),
	}

	messageCollection.save(data, (err, result) => {
		if (err) return console.log(err)
		console.info('Create message for stand `' + stand.name + '` that is connected to generator `' + stand.generator + '`')
	})
}

const randomNumnodem = (min, max) => {
	return Math.floor(Math.random() * (max - min)) + min
}<commit_msg>added json response for getting stands for generator<commit_after>require('dotenv').config()
const express = require('express')
const app = require('express')()
const port = 1337

const MongoClient = require("mongodb").MongoClient
const MONGODB_URI = process.env.MONGODB_URI
console.log(MONGODB_URI)

MongoClient.connect(MONGODB_URI, (err, database) => {
	if (err) return console.log(err)
	db = database
})

app
	.listen(process.env.PORT || port, () => {
		console.log('api server on http://localhost:' + port)
	})

app.get('/api/v1/init/generator/:generator', (req, res) => {
	const generator = req.params.generator
	initGenerator(generator)
})

app.get('/api/v1/init/generator/:generator/stand/:stand', (req, res) => {
	const generator = req.params.generator
	const stand = req.params.stand
	initStand(generator, stand)
})

app.get('/api/v1/generator/:generator/stands', (req, res) => {
	const standCollection = db.collection('stands')
	const generatorCollection = db.collection('generators')
	const generator = req.params.generator

	generatorCollection.findOne({
		name: generator
	}, function(err, generator) {
		if (err) return console.log(err)
		standCollection.find({
			"generator": generator._id
		}, {}).toArray(function(err, stands) {
			const response = {}
			response.generatorId = generator._id
			response.generatorName = generator.name
			response.timestamp = Date.now()
			response.stands = stands

			console.log(response)
			res.json(JSON.stringify(response))
		})
	})

})

app.get('/api/v1/stand/:stand', (req, res) => {
	const standCollection = db.collection('stands')
	const stand = req.params.stand

	standCollection.findOne({
		name: stand
	}, function(err, stand) {
		if (err) return console.log(err)
		res.json(JSON.stringify(stand))
	})
})

const generate = () => {
	setInterval(function() {
		const generatorCollection = db.collection('generators')
		const standCollection = db.collection('stands')

		standCollection.find({}, {}).toArray(function(err, stands) {
			stands.map(function(stand) {
				generateMessage(stand)
			})
		})
	}, 3000)
}

// generate()

const initGenerator = (generator) => {
	const generatorCollection = db.collection('generators')
	const data = {
		name: generator,
		created_at: Date.now()
	}

	generatorCollection.save(data, (err, result) => {
		if (err) return console.log(err)
	})
}

const initStand = (generator, stand) => {
	const generatorCollection = db.collection('generators')
	const standCollection = db.collection('stands')

	generatorCollection.findOne({
		name: generator
	}, function(err, generator) {
		if (err) return console.log(err)
		const data = {
			name: stand,
			generator: generator._id,
			created_at: Date.now()
		}

		standCollection.save(data, (err, result) => {
			if (err) return console.log(err)
			console.info('Created stand called `' + data.name + '` that is connected to generator `' + generator.name + '`')
		})
	})

}

const generateMessage = (stand) => {
	const generatorCollection = db.collection('generators')
	const messageCollection = db.collection('messages')

	const data = {
		stand: stand._id,
		generator: stand.generator,
		timestamp: Date.now(),
		avr_va: randomNumnodem(90500, 90600),
		min_va: randomNumnodem(86500, 86700),
		max_va: randomNumnodem(95400, 95600),
	}

	messageCollection.save(data, (err, result) => {
		if (err) return console.log(err)
		console.info('Create message for stand `' + stand.name + '` that is connected to generator `' + stand.generator + '`')
	})
}

const randomNumnodem = (min, max) => {
	return Math.floor(Math.random() * (max - min)) + min
}<|endoftext|>


<commit_before>'use strict'

import {
  createError,
  generateErrorFromResponse,
  BAD_ARG, BAD_REQUEST, MISSING_DOC, MISSING_STUB, REV_CONFLICT } from 'pouchdb-errors'
import { parseDoc } from 'pouchdb-adapter-utils'
import { merge } from 'pouchdb-merge'
import { binaryStringToBlobOrBuffer } from 'pouchdb-binary-utils'
import { binaryMd5 } from 'pouchdb-md5'

import { forDocument, forBinaryAttachment, forMeta, forSequence } from './keys'

export default function (db, req, opts, callback) {
  const wasDelete = 'was_delete' in opts
  const newEdits = opts.new_edits
  const revsLimit = db.opts.revs_limit || 1000
  const newMeta = {...db.meta}

  const mapRequestDoc = doc => {
    const parsedDoc = parseDoc(doc, newEdits)
    return {
      id: parsedDoc.metadata.id,
      rev: parsedDoc.metadata.rev,
      rev_tree: parsedDoc.metadata.rev_tree,
      deleted: !!parsedDoc.metadata.deleted,
      data: parsedDoc.data
    }
  }

  const processAllAttachments = data => {
    const parseBase64 = data => {
      try {
        return global.atob(data)
      } catch (error) {
        return {
          error: createError(BAD_ARG, 'Attachment is not a valid base64 string')
        }
      }
    }

    const processAttachment = attachment => {
      if (!attachment.data || attachment.stub) {
        return new Promise((resolve, reject) => {
          if (!attachment.digest) return reject(createError(MISSING_STUB, 'no digest'))

          db.storage.get(forBinaryAttachment(attachment.digest), (error, data) => {
            if (error) return reject(createError(MISSING_STUB, error.message))
            if (!data) return reject(createError(MISSING_STUB, 'can not find attachment'))
            return resolve({attachment, dbAttachment: []})
          })
        })
      }

      let binData
      if (typeof attachment.data === 'string') {
        binData = parseBase64(attachment.data)
        if (binData.error) {
          return Promise.reject(binData.error)
        }
        attachment = binaryStringToBlobOrBuffer(binData, attachment.content_type)
      } else {
        binData = attachment.data
      }

      return new Promise((resolve, reject) => {
        binaryMd5(binData, md5 => {
          attachment.digest = 'md5-' + md5
          attachment.length = binData.size || binData.length || 0
          attachment.content_type = attachment.content_type || attachment.type
          attachment.stub = true

          const dbAttachment = [
            forBinaryAttachment(attachment.digest), binData]
          // delete attachment.data
          delete attachment.size
          delete attachment.type
          delete attachment.encoding
          resolve({attachment, dbAttachment})
        })
      })
    }

    if (!data._attachments) return Promise.resolve(null)

    const promises = Object.keys(data._attachments).map(key => {
      if (key.startsWith('_')) {
        return Promise.reject(
          createError(BAD_REQUEST, 'Attachment name can not start with "_"'))
      }
      return processAttachment(data._attachments[key])
        .then(({attachment, dbAttachment}) => {
          data._attachments[key] = attachment
          return dbAttachment
        })
    })

    return Promise.all(promises)
  }

  const getChange = (oldDoc, newDoc) => {
    // pouchdb magic
    const rootIsMissing = doc => doc.rev_tree[0].ids[1].status === 'missing'
    // const getAttachments = () => {}

    const getUpdate = () => {
      // Ignore updates to existing revisions
      if (newDoc.rev in oldDoc.rev_map) return {}

      const merged = merge(oldDoc.rev_tree, newDoc.rev_tree[0], revsLimit)
      newDoc.rev_tree = merged.tree

      const inConflict = newEdits && (((oldDoc.deleted && newDoc.deleted) ||
         (!oldDoc.deleted && merged.conflicts !== 'new_leaf') ||
         (oldDoc.deleted && !newDoc.deleted && merged.conflicts === 'new_branch')))

      if (inConflict) {
        return {error: createError(REV_CONFLICT)}
      }

      if (oldDoc.deleted && !newDoc.deleted) newMeta.doc_count ++
      else if (!oldDoc.deleted && newDoc.deleted) newMeta.doc_count --

      newDoc.seq = ++newMeta.update_seq
      newDoc.rev_map = oldDoc.rev_map
      newDoc.rev_map[newDoc.rev] = newDoc.seq
      newDoc.winningRev = newDoc.rev

      const data = newDoc.data
      delete newDoc.data
      data._id = newDoc.id
      data._rev = newDoc.rev

      return {
        doc: [forDocument(newDoc.id), newDoc],
        data: [forSequence(newDoc.seq), data],
        result: {
          ok: true,
          id: newDoc.id,
          rev: newDoc.rev
        }
      }
    }
    const getInsert = () => {
      const merged = merge([], newDoc.rev_tree[0], revsLimit)
      newDoc.rev_tree = merged.tree
      newDoc.seq = ++newMeta.update_seq
      newDoc.rev_map = {}
      newDoc.rev_map[newDoc.rev] = newDoc.seq
      newDoc.winningRev = newDoc.rev
      if (!newDoc.deleted) newMeta.doc_count ++

      const data = newDoc.data
      delete newDoc.data
      data._id = newDoc.id
      data._rev = newDoc.rev

      return {
        doc: [forDocument(newDoc.id), newDoc],
        data: [forSequence(newDoc.seq), data],
        result: {
          ok: true,
          id: newDoc.id,
          rev: newDoc.rev
        }
      }
    }

    return new Promise((resolve, reject) => {
      if (wasDelete && !oldDoc) {
        return reject(createError(MISSING_DOC, 'deleted'))
      }
      if (newEdits && !oldDoc && rootIsMissing(newDoc)) {
        return reject(createError(REV_CONFLICT))
      }

      processAllAttachments(newDoc.data)
        .then(attachments => {
          const change = oldDoc ? getUpdate() : getInsert()
          if (change.error) return reject(change.error)
          if (attachments) change.attachments = attachments
          resolve(change)
        })
        .catch(reject)
    })
  }

  let newDocs
  try {
    newDocs = req.docs.map(mapRequestDoc)
  } catch (error) {
    return callback(generateErrorFromResponse(error))
  }

  const docIds = newDocs.map(doc => forDocument(doc.id))
  db.storage.multiGet(docIds, (error, oldDocs) => {
    if (error) return callback(generateErrorFromResponse(error))

    const oldDocsObj = oldDocs.reduce(
      (result, doc) => {
        if (doc && doc.id) result[doc.id] = doc
        return result
      }, {})

    const promises = newDocs.map(newDoc => getChange(oldDocsObj[newDoc.id], newDoc))
    Promise.all(promises)
      .then(changes => {
        if (changes.length === 0) return callback(null, {})

        const dbChanges = []
        dbChanges.push([forMeta('_local_doc_count'), newMeta.doc_count])
        dbChanges.push([forMeta('_local_last_update_seq'), newMeta.update_seq])

        changes.forEach(change => {
          dbChanges.push(change.doc)
          dbChanges.push(change.data)
          change.attachments && change.attachments.forEach(attachment => {
            if (attachment) dbChanges.push(attachment)
          })
        })

        db.storage.multiPut(dbChanges, error => {
          if (error) return callback(generateErrorFromResponse(error))

          db.meta.doc_count = newMeta.doc_count
          db.meta.update_seq = newMeta.update_seq
          db.changes.notify(db.opts.name)

          callback(null, changes.map(change => change.result))
        })
      })
      .catch(callback)
  })
}
<commit_msg>better save attachments<commit_after>'use strict'

import {
  createError,
  generateErrorFromResponse,
  BAD_ARG, BAD_REQUEST, MISSING_DOC, MISSING_STUB, REV_CONFLICT } from 'pouchdb-errors'
import { parseDoc } from 'pouchdb-adapter-utils'
import { merge } from 'pouchdb-merge'
import { binaryStringToBlobOrBuffer } from 'pouchdb-binary-utils'
import { binaryMd5 } from 'pouchdb-md5'

import { forDocument, forBinaryAttachment, forMeta, forSequence } from './keys'

export default function (db, req, opts, callback) {
  const wasDelete = 'was_delete' in opts
  const newEdits = opts.new_edits
  const revsLimit = db.opts.revs_limit || 1000
  const newMeta = {...db.meta}

  const mapRequestDoc = doc => {
    const parsedDoc = parseDoc(doc, newEdits)
    return {
      id: parsedDoc.metadata.id,
      rev: parsedDoc.metadata.rev,
      rev_tree: parsedDoc.metadata.rev_tree,
      deleted: !!parsedDoc.metadata.deleted,
      data: parsedDoc.data
    }
  }

  const processAllAttachments = data => {
    const parseBase64 = data => {
      try {
        return global.atob(data)
      } catch (error) {
        return {
          error: createError(BAD_ARG, 'Attachment is not a valid base64 string')
        }
      }
    }

    const processAttachment = attachment => {
      if (attachment.stub) {
        return new Promise((resolve, reject) => {
          if (!attachment.digest) return reject(createError(MISSING_STUB, 'no digest'))

          db.storage.get(forBinaryAttachment(attachment.digest), (error, data) => {
            if (error) return reject(createError(MISSING_STUB, error.message))
            if (!data) return reject(createError(MISSING_STUB, 'can not find attachment'))
            return resolve({attachment, dbAttachment: []})
          })
        })
      }

      let binData
      if (typeof attachment.data === 'string') {
        binData = parseBase64(attachment.data)
        if (binData.error) {
          return Promise.reject(binData.error)
        }
        attachment = binaryStringToBlobOrBuffer(binData, attachment.content_type)
      } else {
        binData = attachment.data
      }

      return new Promise((resolve, reject) => {
        binaryMd5(binData, md5 => {
          const meta = {
            digest: 'md5-' + md5,
            content_type: attachment.content_type || attachment.type,
            length: binData.size || binData.length || 0,
            stub: true
          }

          const dbAttachment = [
            forBinaryAttachment(attachment.digest), binData]
          resolve({attachment: meta, dbAttachment})
        })
      })
    }

    if (!data._attachments) return Promise.resolve(null)

    const promises = Object.keys(data._attachments).map(key => {
      if (key.startsWith('_')) {
        return Promise.reject(
          createError(BAD_REQUEST, 'Attachment name can not start with "_"'))
      }
      return processAttachment(data._attachments[key])
        .then(({attachment, dbAttachment}) => {
          data._attachments[key] = attachment
          return dbAttachment
        })
    })

    return Promise.all(promises)
  }

  const getChange = (oldDoc, newDoc) => {
    // pouchdb magic
    const rootIsMissing = doc => doc.rev_tree[0].ids[1].status === 'missing'
    // const getAttachments = () => {}

    const getUpdate = () => {
      // Ignore updates to existing revisions
      if (newDoc.rev in oldDoc.rev_map) return {}

      const merged = merge(oldDoc.rev_tree, newDoc.rev_tree[0], revsLimit)
      newDoc.rev_tree = merged.tree

      const inConflict = newEdits && (((oldDoc.deleted && newDoc.deleted) ||
         (!oldDoc.deleted && merged.conflicts !== 'new_leaf') ||
         (oldDoc.deleted && !newDoc.deleted && merged.conflicts === 'new_branch')))

      if (inConflict) {
        return {error: createError(REV_CONFLICT)}
      }

      if (oldDoc.deleted && !newDoc.deleted) newMeta.doc_count ++
      else if (!oldDoc.deleted && newDoc.deleted) newMeta.doc_count --

      newDoc.seq = ++newMeta.update_seq
      newDoc.rev_map = oldDoc.rev_map
      newDoc.rev_map[newDoc.rev] = newDoc.seq
      newDoc.winningRev = newDoc.rev

      const data = newDoc.data
      delete newDoc.data
      data._id = newDoc.id
      data._rev = newDoc.rev

      return {
        doc: [forDocument(newDoc.id), newDoc],
        data: [forSequence(newDoc.seq), data],
        result: {
          ok: true,
          id: newDoc.id,
          rev: newDoc.rev
        }
      }
    }
    const getInsert = () => {
      const merged = merge([], newDoc.rev_tree[0], revsLimit)
      newDoc.rev_tree = merged.tree
      newDoc.seq = ++newMeta.update_seq
      newDoc.rev_map = {}
      newDoc.rev_map[newDoc.rev] = newDoc.seq
      newDoc.winningRev = newDoc.rev
      if (!newDoc.deleted) newMeta.doc_count ++

      const data = newDoc.data
      delete newDoc.data
      data._id = newDoc.id
      data._rev = newDoc.rev

      return {
        doc: [forDocument(newDoc.id), newDoc],
        data: [forSequence(newDoc.seq), data],
        result: {
          ok: true,
          id: newDoc.id,
          rev: newDoc.rev
        }
      }
    }

    return new Promise((resolve, reject) => {
      if (wasDelete && !oldDoc) {
        return reject(createError(MISSING_DOC, 'deleted'))
      }
      if (newEdits && !oldDoc && rootIsMissing(newDoc)) {
        return reject(createError(REV_CONFLICT))
      }

      processAllAttachments(newDoc.data)
        .then(attachments => {
          const change = oldDoc ? getUpdate() : getInsert()
          if (change.error) return reject(change.error)
          if (attachments) change.attachments = attachments
          resolve(change)
        })
        .catch(reject)
    })
  }

  let newDocs
  try {
    newDocs = req.docs.map(mapRequestDoc)
  } catch (error) {
    return callback(generateErrorFromResponse(error))
  }

  const docIds = newDocs.map(doc => forDocument(doc.id))
  db.storage.multiGet(docIds, (error, oldDocs) => {
    if (error) return callback(generateErrorFromResponse(error))

    const oldDocsObj = oldDocs.reduce(
      (result, doc) => {
        if (doc && doc.id) result[doc.id] = doc
        return result
      }, {})

    const promises = newDocs.map(newDoc => getChange(oldDocsObj[newDoc.id], newDoc))
    Promise.all(promises)
      .then(changes => {
        if (changes.length === 0) return callback(null, {})

        const dbChanges = []
        dbChanges.push([forMeta('_local_doc_count'), newMeta.doc_count])
        dbChanges.push([forMeta('_local_last_update_seq'), newMeta.update_seq])

        changes.forEach(change => {
          dbChanges.push(change.doc)
          dbChanges.push(change.data)
          change.attachments && change.attachments.forEach(attachment => {
            if (attachment) dbChanges.push(attachment)
          })
        })

        db.storage.multiPut(dbChanges, error => {
          if (error) return callback(generateErrorFromResponse(error))

          db.meta.doc_count = newMeta.doc_count
          db.meta.update_seq = newMeta.update_seq
          db.changes.notify(db.opts.name)

          callback(null, changes.map(change => change.result))
        })
      })
      .catch(callback)
  })
}
<|endoftext|>


<commit_before>(function (window, Cryptoloji, undefined) {
  'use strict'

  var linkClipboard = null
  var linkClipboardCta = null
  var keyClipboard = null

  Cryptoloji.states.share = {
    canEnter: function () {
      // prevent user from entering a broken share without message and key
      // informations. Redirect to encrypt state
      if (!Cryptoloji.current.key) {
        Cryptoloji.stateman.go('encrypt')
        return false
      }
      return true
    },
    enter: function () {
      $('.section_main.encryption').addClass('section-show')
      $(".section_share").addClass("section-show")
      // learn more banner
      $(".learnmore_popup_share").height($('#encryptHeader').innerHeight())
      $(".learnmore_popup_share").css('y', -$(".learnmore_popup_share").innerHeight())
      //
      // $(".learnmore_popup_share").addClass("section-show")
      // $(".learnmore_popup_share").transition({delay: 1500, duration:500, y: 0, easing:'easeInOutExpo'})

      $('.section_share').css({
          y: window.innerHeight,
          height: window.innerHeight-$('#encryptHeader').innerHeight()
      })
      setTimeout(function(){
        $('.section_share').transition({duration:750, y:$('#encryptHeader').innerHeight(), easing:'easeInOutExpo', complete: function(){
          $("#share_more_arrow").addClass("shown")
        }})
      }, 0)

      // hide share elements untill they are completely loaded
      // loader should be show instead
      $('#share_elements').hide()
      $('#share_elements_loader').show()

      // prevent href navigation on click n share link
      $('#share_copytoclipboard').click(function (e) {
        e.preventDefault()
        $('#share_copyIosHint').css({scale:1.25, opacity:1})
        $('#share_copyIosHint').transition({scale:1, opacity:.65, duration:900, easing:'easeInOutExpo'})
      })

      // resize font based on screen height
      Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')

      $(window).resize(function () {
        $('.section_share').css({
          y: $('#encryptHeader').innerHeight(),
          height: window.innerHeight-$('#encryptHeader').innerHeight()
        })
        // resize font based on screen height
        Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')
        if (!Cryptoloji.mq.matches) {
          // resize share modal in mobile layout
          setShareModalCoordinates()
        }
      })

      fillLinkForClipboardCopy()
      fillKeyForClipboardCopy()

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        copyLinkToClipboardHandler()
        copyKeyToClipboardHandler()
      }

      setupShareButtons()

      // share modal is fixed in desktop layout
      if (Cryptoloji.mq.matches) {
        $('.share_key').addClass('share_key-open')
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
      } else {
        setShareModalCoordinates()
        handleShareModal()
      }
    },
    leave: function () {

      Cryptoloji.Api.reset()

      $('.section_main.encryption').removeClass('section-show')

      // only in mobile
      if (!Cryptoloji.mq.matches) {
        // close the modal than exit
        if ($('.share_key').hasClass('share_key-open')) {
          $('.share_key').removeClass('share_key-open')
          $('.share_key').transition({duration:500, y:$('.share_key').height(), easing:'easeInOutExpo', complete: function(){
            $('.share_key_emoji-item').attr('id', '')
          }})
        }
      }
      // learn more banner
      $('.learnmore_popup_share').transition({delay: 0, duration:750, y: -$('.learnmore_popup_share').innerHeight(), easing:'easeInOutExpo', complete: function(){
        $('.learnmore_popup_share').removeClass('section-show')
      }})
      $('.section_share').transition({delay: 250, duration:750, y:window.innerHeight, easing:'easeInOutExpo', complete: function(){
        $('.section_share').removeClass('section-show')
      }})

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        linkClipboard.destroy()
        keyClipboard.destroy()
      }

      // sharer cleanup
      Cryptoloji.UI.Sharer('facebook').unbind()
      Cryptoloji.UI.Sharer('twitter').unbind()
      Cryptoloji.UI.Sharer('gplus').unbind()
      Cryptoloji.UI.Sharer('whatsapp').unbind()
      Cryptoloji.UI.Sharer('mail').unbind()
      Cryptoloji.UI.Sharer('sms').unbind()

      $('#share_copytoclipboard').off()
    }
  }

  function copyLinkToClipboardHandler () {
    function _success (e) {
      console.log(e)
      e.clearSelection()

      // give feedback in place
      var oldVal = $('#share_copytoclipboard').text()
      $('#share_copytoclipboard').text('Copied!')
      $('#share_copytoclipboard').css({scale:1.25})
      $('#share_copytoclipboard').transition({scale:1, duration:900, easing:'easeInOutExpo'})
      setTimeout(function () {
        $('#share_copytoclipboard').text(oldVal)
      }, 1000)
    }
    function _error (e) {
      // select as fallback
      var textVal = $('#share_copytoclipboard').text()
      showTooltip(e.trigger, e.action)
    }

    linkClipboard = new Clipboard('#share_copytoclipboard')
    linkClipboardCta = new Clipboard('#share_copytoclipboardcta', {
      text: function(trigger) {
        // make sure to return proper content
        return $('#share_copytoclipboard').attr('data-clipboard-text')
      }
    })

    linkClipboard.on('success', _success)
    linkClipboardCta.on('success', _success)
    linkClipboard.on('error', _error)
    linkClipboardCta.on('error', _error)
  }

  function copyKeyToClipboardHandler () {
    keyClipboard = new Clipboard('#share_copykeytoclipboard')

    keyClipboard.on('success', function(e) {
      console.log('success', e)
      e.clearSelection()

      // give feedback in place
      $('.share_key_emoji-item_feedback').text('COPIED!')
      setTimeout(function () {
        $('.share_key_emoji-item_feedback').text('')
      }, 350)
    });

    keyClipboard.on('error', function(e) {
      // select as fallback
      var keyVal = $(e.trigger).attr('data-clipboard-text')
      showTooltip(e.trigger, e.action)
    });
  }

  var _tooltipTimeout = null
  function showTooltip (elem, action) {
    // get the selected key
    var x = $(elem).offset().left
    var y = $(elem).offset().top

    var actionMsg = ''
    var actionKey = (action === 'cut' ? 'X' : 'C')

    if (iosTest()) {
      // actionMsg = 'No support :('
      return
    }
    if (/Mac/i.test(navigator.userAgent)) {
      actionMsg = 'Press ⌘-' + actionKey + ' to ' + action
    } else {
      actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action
    }

    // fill the tooltip and position on the trigger elem
    $("#tooltip_share").text(actionMsg)
    TweenLite.set($("#tooltip_share"), {display: "block", opacity: 1})
    TweenLite.to($("#tooltip_share"), 0, {x: x, y: y - 165})

    if (_tooltipTimeout) {
      clearTimeout(_tooltipTimeout)
    }
    _tooltipTimeout = setTimeout(function() {
      TweenLite.to($("#tooltip_share"), 1, {opacity: 0, onComplete: function(){
        TweenLite.set($("#tooltip_share"), {display: "none"})
      }})
    }, 2000);
  }

  function fillLinkForClipboardCopy () {
    // fill copy to clipboard link input
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        // fill link
        $('#share_copytoclipboard').attr('data-clipboard-text', shareURI)
        $('#share_copytoclipboard').attr('href', shareURI)
        $('#share_copytoclipboard').text('This is the link to share')

        // show learn more banner
        //
        $(".learnmore_popup_share").addClass("section-show")
        $(".learnmore_popup_share").transition({delay: 1000, duration:2000, y: 0, easing:'easeInOutExpo'})

        // copy link fallback if ios
        if (iosTest()) {
          $('#share_copytoclipboardcta').hide()
          $('#share_copyIosHint').show()
        }

        // when share links are loaded
        // hide the loader, show the share buttons div and recalculate share m0dal height
        setTimeout(function() {
          $('#share_elements_loader').hide()
          $('#share_elements').show()

          if (!Cryptoloji.mq.matches) {
            setShareModalCoordinates()
          }
        }, 500)

      })
  }

  function fillKeyForClipboardCopy () {
    $('.share_key_emoji-item').attr('data-clipboard-text', Cryptoloji.current.key)
    // copy key fallback if ios
    if (iosTest()) {
      $('.share_key_emoji-item').text(Cryptoloji.current.key)
    }
  }

  function iosTest () {
    return /iPhone|iPad/i.test(navigator.userAgent)
  }

  function setupShareButtons () {
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        var partial = ''
        try{
          partial = encodeURIComponent(Cryptoloji.current.output.substring(0, 16))
        } catch(err) {
          partial = encodeURIComponent("🏩📒🗻😁")
        }
        var twitterMessage = partial +
          '... ' +
          'I scrambled a message for you using Codemoji from @Mozilla: Try to unscramble it!'

        var mailMessage = Cryptoloji.current.output +
          '\n\n'+
          'I wrote you a message and scrambled it with Codemoji, a fun tool from Mozilla to learn about ciphers.' +
          '\n\n'+
          'Try to unscramble it on ' + shareURI

        var smsMessage = Cryptoloji.current.output +
          '\n' +
          'Check out ' + shareURI

        console.log(shareURI)

        Cryptoloji.UI.Sharer('facebook', '#share_button_fb')
          .setShareLink('https://www.facebook.com/sharer/sharer.php')
          .addParam('u', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('twitter', '#share_button_tw')
          .setShareLink('https://twitter.com/intent/tweet')
          .addParam('url', shareURI)
          .addParam('text', twitterMessage)
          .bind()
        Cryptoloji.UI.Sharer('gplus', '#share_button_go')
          .setShareLink('https://plus.google.com/share')
          .addParam('url', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('mail', '#share_button_ma')
          .sameWindow()
          .setShareLink('mailto:')
          .addParam('subject', 'Can you read this emoji message?')
          .addParam('body', mailMessage)
          .bind()

        // $('#share_elements').show()

        if (bowser.mobile) {
          // show whatsapp and sms on mobile only
          // see https://github.com/todotoit/cryptoloji/issues/156

          Cryptoloji.UI.Sharer('whatsapp', '#share_button_wa')
          .sameWindow()
          .setShareLink('whatsapp://send')
          .addParam('text', shareURI)
          .bind()

          if (bowser.ios) {
            // see http://stackoverflow.com/a/19126326
            // why differentation for ios is needed
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setSeparator('&')
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          } else {
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          }
        } else {
          $('#share_button_wa, #share_button_sms').hide()
        }
      })
  }

    var share_modal_height = 0
    var share_modal_margin = 0
  function setShareModalCoordinates () {
    // set the height and the top y coordinate to reach when share key modal is open
    // Cryptoloji.utils.remToPx(.5) is for add a margin
    // 23 is the magic number...
    //

    // if share components are still loading calculate loader height and position
    // else calculate share buttons height and position
    if ($('.share_social_wrapper').height() === 0) {
      share_modal_height = $('body').height() - $('#share_elements_loader').position().top - $('#share_elements_loader').height() - Cryptoloji.utils.remToPx(2)
    } else {
      share_modal_height = $('body').height() - $('.share_social_wrapper').position().top - $('.share_social_wrapper').height() + Cryptoloji.utils.remToPx(.5) + 23 -$('#encryptHeader').innerHeight()
    }
    share_modal_margin = Cryptoloji.utils.remToPx(12.5)
    if (Cryptoloji.mq.matches) {
      share_modal_margin = Cryptoloji.utils.remToPx(15)
    }
    $('.share_key').css('height', share_modal_height)
    TweenLite.set($('.share_key'), {y: share_modal_height - share_modal_margin})
  }

  function _closeModal () {
    $('.share_key').removeClass('share_key-open')
    TweenLite.to($('.share_key'), .75, {y: share_modal_height - share_modal_margin, ease:Expo.easeInOut, onComplete: function(){
      $('.share_key_emoji-item').attr('id', '')
    }})
    $('.section_share .share_main_wrap').off()
  }
  function handleShareModal () {
    $('.share_key_emoji-item').on('click', function () {
      // open modal
      $('.share_key').addClass('share_key-open')
      TweenLite.to($('.share_key'), .75, {y: 0, ease:Expo.easeInOut, onComplete: function(){
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
        handleCloseShareModal()
      }})
    })
    $('#share_key_hide').on('click', function () {
      _closeModal()
    })
  }
  function handleCloseShareModal () {
    $('.section_share .share_main_wrap').on("click", function () {
      _closeModal()
    })
  }

})(window, window.Cryptoloji);
<commit_msg>refining fallback for encoding truncated emoji string<commit_after>(function (window, Cryptoloji, undefined) {
  'use strict'

  var linkClipboard = null
  var linkClipboardCta = null
  var keyClipboard = null

  Cryptoloji.states.share = {
    canEnter: function () {
      // prevent user from entering a broken share without message and key
      // informations. Redirect to encrypt state
      if (!Cryptoloji.current.key) {
        Cryptoloji.stateman.go('encrypt')
        return false
      }
      return true
    },
    enter: function () {
      $('.section_main.encryption').addClass('section-show')
      $(".section_share").addClass("section-show")
      // learn more banner
      $(".learnmore_popup_share").height($('#encryptHeader').innerHeight())
      $(".learnmore_popup_share").css('y', -$(".learnmore_popup_share").innerHeight())
      //
      // $(".learnmore_popup_share").addClass("section-show")
      // $(".learnmore_popup_share").transition({delay: 1500, duration:500, y: 0, easing:'easeInOutExpo'})

      $('.section_share').css({
          y: window.innerHeight,
          height: window.innerHeight-$('#encryptHeader').innerHeight()
      })
      setTimeout(function(){
        $('.section_share').transition({duration:750, y:$('#encryptHeader').innerHeight(), easing:'easeInOutExpo', complete: function(){
          $("#share_more_arrow").addClass("shown")
        }})
      }, 0)

      // hide share elements untill they are completely loaded
      // loader should be show instead
      $('#share_elements').hide()
      $('#share_elements_loader').show()

      // prevent href navigation on click n share link
      $('#share_copytoclipboard').click(function (e) {
        e.preventDefault()
        $('#share_copyIosHint').css({scale:1.25, opacity:1})
        $('#share_copyIosHint').transition({scale:1, opacity:.65, duration:900, easing:'easeInOutExpo'})
      })

      // resize font based on screen height
      Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')

      $(window).resize(function () {
        $('.section_share').css({
          y: $('#encryptHeader').innerHeight(),
          height: window.innerHeight-$('#encryptHeader').innerHeight()
        })
        // resize font based on screen height
        Cryptoloji.utils.resizeEmojis(Cryptoloji.current.output, .5, '.share_message_item')
        if (!Cryptoloji.mq.matches) {
          // resize share modal in mobile layout
          setShareModalCoordinates()
        }
      })

      fillLinkForClipboardCopy()
      fillKeyForClipboardCopy()

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        copyLinkToClipboardHandler()
        copyKeyToClipboardHandler()
      }

      setupShareButtons()

      // share modal is fixed in desktop layout
      if (Cryptoloji.mq.matches) {
        $('.share_key').addClass('share_key-open')
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
      } else {
        setShareModalCoordinates()
        handleShareModal()
      }
    },
    leave: function () {

      Cryptoloji.Api.reset()

      $('.section_main.encryption').removeClass('section-show')

      // only in mobile
      if (!Cryptoloji.mq.matches) {
        // close the modal than exit
        if ($('.share_key').hasClass('share_key-open')) {
          $('.share_key').removeClass('share_key-open')
          $('.share_key').transition({duration:500, y:$('.share_key').height(), easing:'easeInOutExpo', complete: function(){
            $('.share_key_emoji-item').attr('id', '')
          }})
        }
      }
      // learn more banner
      $('.learnmore_popup_share').transition({delay: 0, duration:750, y: -$('.learnmore_popup_share').innerHeight(), easing:'easeInOutExpo', complete: function(){
        $('.learnmore_popup_share').removeClass('section-show')
      }})
      $('.section_share').transition({delay: 250, duration:750, y:window.innerHeight, easing:'easeInOutExpo', complete: function(){
        $('.section_share').removeClass('section-show')
      }})

      // don't handle copy to clipboard if ios
      if (!iosTest()) {
        linkClipboard.destroy()
        keyClipboard.destroy()
      }

      // sharer cleanup
      Cryptoloji.UI.Sharer('facebook').unbind()
      Cryptoloji.UI.Sharer('twitter').unbind()
      Cryptoloji.UI.Sharer('gplus').unbind()
      Cryptoloji.UI.Sharer('whatsapp').unbind()
      Cryptoloji.UI.Sharer('mail').unbind()
      Cryptoloji.UI.Sharer('sms').unbind()

      $('#share_copytoclipboard').off()
    }
  }

  function copyLinkToClipboardHandler () {
    function _success (e) {
      console.log(e)
      e.clearSelection()

      // give feedback in place
      var oldVal = $('#share_copytoclipboard').text()
      $('#share_copytoclipboard').text('Copied!')
      $('#share_copytoclipboard').css({scale:1.25})
      $('#share_copytoclipboard').transition({scale:1, duration:900, easing:'easeInOutExpo'})
      setTimeout(function () {
        $('#share_copytoclipboard').text(oldVal)
      }, 1000)
    }
    function _error (e) {
      // select as fallback
      var textVal = $('#share_copytoclipboard').text()
      showTooltip(e.trigger, e.action)
    }

    linkClipboard = new Clipboard('#share_copytoclipboard')
    linkClipboardCta = new Clipboard('#share_copytoclipboardcta', {
      text: function(trigger) {
        // make sure to return proper content
        return $('#share_copytoclipboard').attr('data-clipboard-text')
      }
    })

    linkClipboard.on('success', _success)
    linkClipboardCta.on('success', _success)
    linkClipboard.on('error', _error)
    linkClipboardCta.on('error', _error)
  }

  function copyKeyToClipboardHandler () {
    keyClipboard = new Clipboard('#share_copykeytoclipboard')

    keyClipboard.on('success', function(e) {
      console.log('success', e)
      e.clearSelection()

      // give feedback in place
      $('.share_key_emoji-item_feedback').text('COPIED!')
      setTimeout(function () {
        $('.share_key_emoji-item_feedback').text('')
      }, 350)
    });

    keyClipboard.on('error', function(e) {
      // select as fallback
      var keyVal = $(e.trigger).attr('data-clipboard-text')
      showTooltip(e.trigger, e.action)
    });
  }

  var _tooltipTimeout = null
  function showTooltip (elem, action) {
    // get the selected key
    var x = $(elem).offset().left
    var y = $(elem).offset().top

    var actionMsg = ''
    var actionKey = (action === 'cut' ? 'X' : 'C')

    if (iosTest()) {
      // actionMsg = 'No support :('
      return
    }
    if (/Mac/i.test(navigator.userAgent)) {
      actionMsg = 'Press ⌘-' + actionKey + ' to ' + action
    } else {
      actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action
    }

    // fill the tooltip and position on the trigger elem
    $("#tooltip_share").text(actionMsg)
    TweenLite.set($("#tooltip_share"), {display: "block", opacity: 1})
    TweenLite.to($("#tooltip_share"), 0, {x: x, y: y - 165})

    if (_tooltipTimeout) {
      clearTimeout(_tooltipTimeout)
    }
    _tooltipTimeout = setTimeout(function() {
      TweenLite.to($("#tooltip_share"), 1, {opacity: 0, onComplete: function(){
        TweenLite.set($("#tooltip_share"), {display: "none"})
      }})
    }, 2000);
  }

  function fillLinkForClipboardCopy () {
    // fill copy to clipboard link input
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        // fill link
        $('#share_copytoclipboard').attr('data-clipboard-text', shareURI)
        $('#share_copytoclipboard').attr('href', shareURI)
        $('#share_copytoclipboard').text('This is the link to share')

        // show learn more banner
        //
        $(".learnmore_popup_share").addClass("section-show")
        $(".learnmore_popup_share").transition({delay: 1000, duration:2000, y: 0, easing:'easeInOutExpo'})

        // copy link fallback if ios
        if (iosTest()) {
          $('#share_copytoclipboardcta').hide()
          $('#share_copyIosHint').show()
        }

        // when share links are loaded
        // hide the loader, show the share buttons div and recalculate share m0dal height
        setTimeout(function() {
          $('#share_elements_loader').hide()
          $('#share_elements').show()

          if (!Cryptoloji.mq.matches) {
            setShareModalCoordinates()
          }
        }, 500)

      })
  }

  function fillKeyForClipboardCopy () {
    $('.share_key_emoji-item').attr('data-clipboard-text', Cryptoloji.current.key)
    // copy key fallback if ios
    if (iosTest()) {
      $('.share_key_emoji-item').text(Cryptoloji.current.key)
    }
  }

  function iosTest () {
    return /iPhone|iPad/i.test(navigator.userAgent)
  }

  function setupShareButtons () {
    Cryptoloji.Api.getShortenedLink()
      .then(function (shareURI) {
        var partial = ''
        try{
          var trytoencode = encodeURIComponent(Cryptoloji.current.output.substring(0, 16))
          partial = Cryptoloji.current.output.substring(0, 16)
        } catch(err) {
          partial = "🏩📒🗻😁"
        }
        var twitterMessage = partial +
          '... ' +
          'I scrambled a message for you using Codemoji from @Mozilla: Try to unscramble it!'

        var mailMessage = Cryptoloji.current.output +
          '\n\n'+
          'I wrote you a message and scrambled it with Codemoji, a fun tool from Mozilla to learn about ciphers.' +
          '\n\n'+
          'Try to unscramble it on ' + shareURI

        var smsMessage = Cryptoloji.current.output +
          '\n' +
          'Check out ' + shareURI

        console.log(shareURI)

        Cryptoloji.UI.Sharer('facebook', '#share_button_fb')
          .setShareLink('https://www.facebook.com/sharer/sharer.php')
          .addParam('u', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('twitter', '#share_button_tw')
          .setShareLink('https://twitter.com/intent/tweet')
          .addParam('url', shareURI)
          .addParam('text', twitterMessage)
          .bind()
        Cryptoloji.UI.Sharer('gplus', '#share_button_go')
          .setShareLink('https://plus.google.com/share')
          .addParam('url', shareURI)
          .bind()
        Cryptoloji.UI.Sharer('mail', '#share_button_ma')
          .sameWindow()
          .setShareLink('mailto:')
          .addParam('subject', 'Can you read this emoji message?')
          .addParam('body', mailMessage)
          .bind()

        // $('#share_elements').show()

        if (bowser.mobile) {
          // show whatsapp and sms on mobile only
          // see https://github.com/todotoit/cryptoloji/issues/156

          Cryptoloji.UI.Sharer('whatsapp', '#share_button_wa')
          .sameWindow()
          .setShareLink('whatsapp://send')
          .addParam('text', shareURI)
          .bind()

          if (bowser.ios) {
            // see http://stackoverflow.com/a/19126326
            // why differentation for ios is needed
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setSeparator('&')
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          } else {
            Cryptoloji.UI.Sharer('sms', '#share_button_sms')
              .sameWindow()
              .setShareLink('sms:')
              .addParam('body', smsMessage)
              .bind()
          }
        } else {
          $('#share_button_wa, #share_button_sms').hide()
        }
      })
  }

    var share_modal_height = 0
    var share_modal_margin = 0
  function setShareModalCoordinates () {
    // set the height and the top y coordinate to reach when share key modal is open
    // Cryptoloji.utils.remToPx(.5) is for add a margin
    // 23 is the magic number...
    //

    // if share components are still loading calculate loader height and position
    // else calculate share buttons height and position
    if ($('.share_social_wrapper').height() === 0) {
      share_modal_height = $('body').height() - $('#share_elements_loader').position().top - $('#share_elements_loader').height() - Cryptoloji.utils.remToPx(2)
    } else {
      share_modal_height = $('body').height() - $('.share_social_wrapper').position().top - $('.share_social_wrapper').height() + Cryptoloji.utils.remToPx(.5) + 23 -$('#encryptHeader').innerHeight()
    }
    share_modal_margin = Cryptoloji.utils.remToPx(12.5)
    if (Cryptoloji.mq.matches) {
      share_modal_margin = Cryptoloji.utils.remToPx(15)
    }
    $('.share_key').css('height', share_modal_height)
    TweenLite.set($('.share_key'), {y: share_modal_height - share_modal_margin})
  }

  function _closeModal () {
    $('.share_key').removeClass('share_key-open')
    TweenLite.to($('.share_key'), .75, {y: share_modal_height - share_modal_margin, ease:Expo.easeInOut, onComplete: function(){
      $('.share_key_emoji-item').attr('id', '')
    }})
    $('.section_share .share_main_wrap').off()
  }
  function handleShareModal () {
    $('.share_key_emoji-item').on('click', function () {
      // open modal
      $('.share_key').addClass('share_key-open')
      TweenLite.to($('.share_key'), .75, {y: 0, ease:Expo.easeInOut, onComplete: function(){
        $('.share_key_emoji-item').attr('id', 'share_copykeytoclipboard')
        handleCloseShareModal()
      }})
    })
    $('#share_key_hide').on('click', function () {
      _closeModal()
    })
  }
  function handleCloseShareModal () {
    $('.section_share .share_main_wrap').on("click", function () {
      _closeModal()
    })
  }

})(window, window.Cryptoloji);
<|endoftext|>


<commit_before>E2.plugins["or_modulator"] = function(core, node) {
	var self = this;

	this.desc = 'Emit true if both input values are true and false otherwise.';
	this.input_slots = [
		{ name: 'bool', dt: core.datatypes.BOOL },
		{ name: 'bool', dt: core.datatypes.BOOL }
	];

	this.output_slots = [ { name: 'bool', dt: core.datatypes.BOOL } ];

	this.reset = function()
	{
		self.conds = [ false, false ];
		self.state = false;
	};

	this.update_input = function(slot, data)
	{
		self.conds[slot.index] = data;
	};

	this.update_state = function(delta_t)
	{
		self.state = self.conds[0] || self.conds[1];
	};

	this.update_output = function(slot)
	{
		return self.state;
	};
};
<commit_msg>Fixed copy / paste error in OR plugin description: Was still using the description from 'AND'.<commit_after>E2.plugins["or_modulator"] = function(core, node) {
	var self = this;

	this.desc = 'Emit true if either input value is true and false otherwise.';
	this.input_slots = [
		{ name: 'bool', dt: core.datatypes.BOOL },
		{ name: 'bool', dt: core.datatypes.BOOL }
	];

	this.output_slots = [ { name: 'bool', dt: core.datatypes.BOOL } ];

	this.reset = function()
	{
		self.conds = [ false, false ];
		self.state = false;
	};

	this.update_input = function(slot, data)
	{
		self.conds[slot.index] = data;
	};

	this.update_state = function(delta_t)
	{
		self.state = self.conds[0] || self.conds[1];
	};

	this.update_output = function(slot)
	{
		return self.state;
	};
};
<|endoftext|>


<commit_before>import { isNumber, isString } from 'substance'
import { type } from '../value'
import { parseSymbol } from './expressionHelpers'

export function getCellState(cell) {
  // FIXME: we should make sure that cellState is
  // initialized as early as possible
  return cell.state
}

export function isExpression(source) {
  return /^\s*=/.exec(source)
}

export function getCellValue(cell) {
  if (!cell) return undefined
  if (cell.state) {
    return cell.state.value
  } else {
    let sheet = cell.getDocument()
    let type = sheet.getCellType(cell)
    return valueFromText(type, cell.text())
  }
}

export function valueFromText(preferredType, text) {
  const data = _parseText(preferredType, text)
  const type_ = type(data)
  return { type: type_, data }
}

function _parseText(preferredType, text) {
  // guess value
  if (text === 'false') {
    return false
  } else if (text === 'true') {
    return true
  } else if (!isNaN(text)) {
    let _int = Number.parseInt(text, 10)
    if (_int == text) { // eslint-disable-line
      return _int
    } else {
      return Number.parseFloat(text)
    }
  } else {
    return text
  }
}

export const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

export function getColumnLabel(colIdx) {
  if (!isNumber(colIdx)) {
    throw new Error('Illegal argument.')
  }
  var label = ""
  while(true) { // eslint-disable-line
    var mod = colIdx % ALPHABET.length
    colIdx = Math.floor(colIdx/ALPHABET.length)
    label = ALPHABET[mod] + label
    if (colIdx > 0) colIdx--
    else if (colIdx === 0) break
  }
  return label
}

export function getCellLabel(rowIdx, colIdx) {
  let colLabel = getColumnLabel(colIdx)
  let rowLabel = rowIdx + 1
  return colLabel + rowLabel
}

export function getColumnIndex(colStr) {
  let index = 0
  let rank = 1
  for (let i = 0; i < colStr.length; i++) {
    let letter = colStr[i]
    index += rank * ALPHABET.indexOf(letter)
    rank++
  }
  return index
}

export function getRowCol(cellLabel) {
  var match = /^([A-Z]+)([1-9][0-9]*)$/.exec(cellLabel)
  return [
    parseInt(match[2], 10)-1,
    getColumnIndex(match[1])
  ]
}

export function getError(cell) {
  let cellState = getCellState(cell)
  return cellState.errors[0]
}

export function getFrameSize(layout) {
  // WORKAROUND, this should be solved in libcore functions
  const defaultSizes = {
    'width': '400',
    'height': '400'
  }
  const sizes = layout.width ? layout : defaultSizes
  return sizes
}

// This is useful for writing tests, to use queries such as 'A1:A10'
export function queryCells(cells, query) {
  let { type, name } = parseSymbol(query)
  switch (type) {
    case 'cell': {
      const [row, col] = getRowCol(name)
      return cells[row][col]
    }
    case 'range': {
      let [anchor, focus] = name.split(':')
      const [anchorRow, anchorCol] = getRowCol(anchor)
      const [focusRow, focusCol] = getRowCol(focus)
      if (anchorRow === focusRow && anchorCol === focusCol) {
        return cells[anchorCol][focusCol]
      }
      if (anchorRow === focusRow) {
        return cells[anchorRow].slice(anchorCol, focusCol+1)
      }
      if (anchorCol === focusCol) {
        let res = []
        for (let i = anchorRow; i <= focusRow; i++) {
          res.push(cells[i][anchorCol])
        }
        return res
      }
      throw new Error('Unsupported query')
    }
    default:
      throw new Error('Unsupported query')
  }
}

export function qualifiedId(doc, cell) {
  let cellId = isString(cell) ? cell : cell.id
  if (doc) {
    let docId = isString(doc) ? doc : doc.id
    return `${docId}!${cellId}`
  } else {
    return cellId
  }
}

export const BROKEN_REF = '#REF!'

export function transformCellRangeExpression(expr, params) {
  const mode = params.mode
  const idx = params.idx
  const count = params.count
  if(params.length !== 3 || !isNumber(idx) || !isNumber(count) || (mode !== 'col' || mode !== 'row')) {
    throw new Error('Illegal arguments')
  }

  let borders = getCellRangeBorders(expr, mode)

  // If operation applied to col/rows after given borders we shoudn't modify expression
  if(borders.end < idx) {
    return expr
  }

  // If it is removing of cell reference or cell range is inside removed range we should return error
  if(borders.start === borders.end === idx && count < 0 || borders.start > idx && borders.end < idx + count) {
    return '#BROKENREF'
  }

  if(idx <= borders.start) {
    borders.start += count
  }

  if(idx <= borders.end) {
    borders.end += count
  }

  // get labels
  return modifyCellRangeLabel(expr, borders, mode)
}

export function getCellRangeBorders(expr, mode) {
  if(!expr || !mode) {
    throw new Error('Illegal arguments.')
  }

  const range = expr.split(':')
  let borders = {
    start: 0,
    end: 0
  }

  if(mode === 'col') {
    if(range.length === 2) {
      borders.start = getRowCol(range[0])[1]
      borders.end = getRowCol(range[1])[1]
    } else {
      borders.start = borders.end = getRowCol(range[0])[1]
    }
  } else if (mode === 'row') {
    if(range.length === 2) {
      borders.start = getRowCol(range[0])[0]
      borders.end = getRowCol(range[1])[0]
    } else {
      borders.start = borders.end = getRowCol(range[0])[0]
    }
  } else {
    throw new Error('Illegal mode: ' + mode)
  }

  return borders
}

export function modifyCellRangeLabel(expr, borders, mode) {
  if(!expr || !borders || !mode) {
    throw new Error('Illegal arguments.')
  }
  const range = expr.split(':')
  if(range.length !== 2) {
    throw new Error('Illegal cell range.')
  }

  let startRow = getRowCol(range[0])[0]
  let endRow = getRowCol(range[1])[0]
  let startCol = getRowCol(range[0])[1]
  let endCol = getRowCol(range[1])[1]

  if(mode === 'col') {
    startCol += borders.start
    endCol += borders.end
  } else if (mode === 'row') {
    startRow += borders.start
    endRow += borders.end
  } else {
    throw new Error('Illegal mode: ' + mode)
  }

  return getColumnLabel(startCol) + (startRow + 1) + ':' + getColumnLabel(endCol) + (endRow + 1)
}
<commit_msg>Fix cell reference transformation.<commit_after>import { isNumber, isString } from 'substance'
import { type } from '../value'
import { parseSymbol } from './expressionHelpers'

export function getCellState(cell) {
  // FIXME: we should make sure that cellState is
  // initialized as early as possible
  return cell.state
}

export function isExpression(source) {
  return /^\s*=/.exec(source)
}

export function getCellValue(cell) {
  if (!cell) return undefined
  if (cell.state) {
    return cell.state.value
  } else {
    let sheet = cell.getDocument()
    let type = sheet.getCellType(cell)
    return valueFromText(type, cell.text())
  }
}

export function valueFromText(preferredType, text) {
  const data = _parseText(preferredType, text)
  const type_ = type(data)
  return { type: type_, data }
}

function _parseText(preferredType, text) {
  // guess value
  if (text === 'false') {
    return false
  } else if (text === 'true') {
    return true
  } else if (!isNaN(text)) {
    let _int = Number.parseInt(text, 10)
    if (_int == text) { // eslint-disable-line
      return _int
    } else {
      return Number.parseFloat(text)
    }
  } else {
    return text
  }
}

export const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

export function getColumnLabel(colIdx) {
  if (!isNumber(colIdx)) {
    throw new Error('Illegal argument.')
  }
  var label = ""
  while(true) { // eslint-disable-line
    var mod = colIdx % ALPHABET.length
    colIdx = Math.floor(colIdx/ALPHABET.length)
    label = ALPHABET[mod] + label
    if (colIdx > 0) colIdx--
    else if (colIdx === 0) break
  }
  return label
}

export function getCellLabel(rowIdx, colIdx) {
  let colLabel = getColumnLabel(colIdx)
  let rowLabel = rowIdx + 1
  return colLabel + rowLabel
}

export function getColumnIndex(colStr) {
  let index = 0
  let rank = 1
  for (let i = 0; i < colStr.length; i++) {
    let letter = colStr[i]
    index += rank * ALPHABET.indexOf(letter)
    rank++
  }
  return index
}

export function getRowCol(cellLabel) {
  var match = /^([A-Z]+)([1-9][0-9]*)$/.exec(cellLabel)
  return [
    parseInt(match[2], 10)-1,
    getColumnIndex(match[1])
  ]
}

export function getError(cell) {
  let cellState = getCellState(cell)
  return cellState.errors[0]
}

export function getFrameSize(layout) {
  // WORKAROUND, this should be solved in libcore functions
  const defaultSizes = {
    'width': '400',
    'height': '400'
  }
  const sizes = layout.width ? layout : defaultSizes
  return sizes
}

// This is useful for writing tests, to use queries such as 'A1:A10'
export function queryCells(cells, query) {
  let { type, name } = parseSymbol(query)
  switch (type) {
    case 'cell': {
      const [row, col] = getRowCol(name)
      return cells[row][col]
    }
    case 'range': {
      let [anchor, focus] = name.split(':')
      const [anchorRow, anchorCol] = getRowCol(anchor)
      const [focusRow, focusCol] = getRowCol(focus)
      if (anchorRow === focusRow && anchorCol === focusCol) {
        return cells[anchorCol][focusCol]
      }
      if (anchorRow === focusRow) {
        return cells[anchorRow].slice(anchorCol, focusCol+1)
      }
      if (anchorCol === focusCol) {
        let res = []
        for (let i = anchorRow; i <= focusRow; i++) {
          res.push(cells[i][anchorCol])
        }
        return res
      }
      throw new Error('Unsupported query')
    }
    default:
      throw new Error('Unsupported query')
  }
}

export function qualifiedId(doc, cell) {
  let cellId = isString(cell) ? cell : cell.id
  if (doc) {
    let docId = isString(doc) ? doc : doc.id
    return `${docId}!${cellId}`
  } else {
    return cellId
  }
}

export const BROKEN_REF = '#REF!'

export function transformCellRangeExpression(expr, params) {
  const mode = params.mode
  const idx = params.idx
  const count = params.count
  if(!isNumber(idx) || !isNumber(count) || (mode !== 'col' && mode !== 'row')) {
    throw new Error('Illegal arguments')
  }

  let borders = getCellRangeBorders(expr, mode)
  const isCellReference = borders.start === borders.end

  // If operation applied to col/rows after given borders we shoudn't modify expression
  if(borders.end < idx) {
    return expr
  }

  // If it is removing of cell reference or cell range is inside removed range we should return error
  if(isCellReference && borders.start === idx && count < 0 || borders.start > idx && borders.end < idx + count) {
    return '#BROKENREF'
  }

  if(idx <= borders.start) {
    borders.start += count
  }

  if(idx <= borders.end && !isCellReference) {
    borders.end += count
  }

  // get labels
  return modifyCellRangeLabel(expr, borders, mode)
}

export function getCellRangeBorders(expr, mode) {
  if(!expr || !mode) {
    throw new Error('Illegal arguments.')
  }

  const range = expr.split(':')
  let borders = {
    start: 0,
    end: 0
  }

  if(mode === 'col') {
    if(range.length === 2) {
      borders.start = getRowCol(range[0])[1]
      borders.end = getRowCol(range[1])[1]
    } else {
      borders.start = borders.end = getRowCol(range[0])[1]
    }
  } else if (mode === 'row') {
    if(range.length === 2) {
      borders.start = getRowCol(range[0])[0]
      borders.end = getRowCol(range[1])[0]
    } else {
      borders.start = borders.end = getRowCol(range[0])[0]
    }
  } else {
    throw new Error('Illegal mode: ' + mode)
  }

  return borders
}

export function modifyCellRangeLabel(expr, borders, mode) {
  if(!expr || !borders || !mode) {
    throw new Error('Illegal arguments.')
  }
  const range = expr.split(':')

  let startRow = getRowCol(range[0])[0]
  let startCol = getRowCol(range[0])[1]

  if(range.length === 1) {
    if(mode === 'col') {
      startCol = borders.start
    } else if (mode === 'row') {
      startRow = borders.start
    } else {
      throw new Error('Illegal mode: ' + mode)
    }

    return getCellLabel(startRow, startCol)
  }

  let endRow = getRowCol(range[1])[0]
  let endCol = getRowCol(range[1])[1]

  if(mode === 'col') {
    startCol = borders.start
    endCol = borders.end
  } else if (mode === 'row') {
    startRow = borders.start
    endRow = borders.end
  } else {
    throw new Error('Illegal mode: ' + mode)
  }

  return getCellLabel(startRow, startCol) + ':' + getCellLabel(endRow, endColl)
}
<|endoftext|>


<commit_before>import React, { Component, cloneElement } from 'react'
import PropTypes from 'prop-types'
import themed from 'react-themed'
import autobind from 'autobind-decorator'
import classnames from 'classnames'
import { Dropdown, Text } from '@rentpath/react-ui-core'
import RadioGroupFilterCard from './RadioGroupFilterCard'
import DropdownFilterCardWrapper from './DropdownFilterCardWrapper'

const nodeFuncOrObject = PropTypes.oneOfType([
  PropTypes.node,
  PropTypes.func,
  PropTypes.object,
])

@themed(['FilterDropdown'])
export default class RadioGroupDropdown extends Component {
  static propTypes = {
    className: PropTypes.string,
    theme: PropTypes.object,
    anchorText: PropTypes.node,
    fields: PropTypes.arrayOf(PropTypes.shape({
      label: nodeFuncOrObject,
      value: PropTypes.string,
      anchorLabel: nodeFuncOrObject,
    })),
    applyButton: PropTypes.object,
    cancelButton: PropTypes.object,
  }

  static defaultProps = {
    theme: {},
    fields: [],
  }

  constructor(props) {
    super(props)
    this.state = {
      value: null,
    }
  }

  get standardFields() {
    return this.props.fields.map(originalField => {
      const { anchorLabel, ...field } = originalField
      return {
        ...field,
        checked: field.value === this.state.value,
      }
    })
  }

  @autobind
  handleValueChange(value) {
    this.setState({ value })
  }

  renderAnchorButton() {
    const { value } = this.state
    const { fields, anchorText } = this.props

    const { label, anchorLabel } = fields.find(f => f.value === value) || {}
    const text = anchorLabel || label

    if (anchorText) return cloneElement(anchorText, { text })
    return <Text>{text}</Text>
  }

  render() {
    const {
      anchorText,
      fields,
      theme,
      className,
      ...props
    } = this.props

    return (
      <Dropdown
        className={classnames(
          className,
          theme.RadioGroupDropdown,
          theme.FilterDropdown,
        )}
        anchorField={{ children: this.renderAnchorButton() }}
      >
        <DropdownFilterCardWrapper
          {...props}
          FilterCard={RadioGroupFilterCard}
          fields={this.standardFields}
          handleValueChange={this.handleValueChange}
        />
      </Dropdown>
    )
  }
}
<commit_msg>missed one<commit_after>import React, { Component, cloneElement } from 'react'
import PropTypes from 'prop-types'
import themed from 'react-themed'
import autobind from 'autobind-decorator'
import classnames from 'classnames'
import { Dropdown, Text } from '@rentpath/react-ui-core'
import RadioGroupFilterCard from './RadioGroupFilterCard'
import DropdownFilterCardWrapper from './DropdownFilterCardWrapper'

const nodeFuncOrObject = PropTypes.oneOfType([
  PropTypes.node,
  PropTypes.func,
  PropTypes.object,
])

@themed(['FilterDropdown'])
export default class RadioGroupDropdown extends Component {
  static propTypes = {
    className: PropTypes.string,
    theme: PropTypes.object,
    anchorText: PropTypes.node,
    fields: PropTypes.arrayOf(PropTypes.shape({
      label: nodeFuncOrObject,
      value: PropTypes.string,
      anchorLabel: nodeFuncOrObject,
    })),
    applyButton: PropTypes.object,
    cancelButton: PropTypes.object,
  }

  static defaultProps = {
    theme: {},
    fields: [],
  }

  constructor(props) {
    super(props)
    this.state = {
      value: null,
    }
  }

  get standardFields() {
    return this.props.fields.map(originalField => {
      const { anchorLabel, ...field } = originalField
      return {
        ...field,
        checked: field.value === this.state.value,
      }
    })
  }

  @autobind
  handleValueChange(value) {
    this.setState({ value })
  }

  renderAnchorButton() {
    const { value } = this.state
    const { fields, anchorText } = this.props

    const { label, anchorLabel } = fields.find(f => f.value === value) || {}
    const text = anchorLabel || label

    if (anchorText) return cloneElement(anchorText, { text })
    return <Text>{text}</Text>
  }

  render() {
    const {
      anchorText,
      fields,
      theme,
      className,
      ...props
    } = this.props

    return (
      <Dropdown
        className={classnames(
          className,
          theme.FilterDropdown,
        )}
        anchorField={{ children: this.renderAnchorButton() }}
      >
        <DropdownFilterCardWrapper
          {...props}
          FilterCard={RadioGroupFilterCard}
          fields={this.standardFields}
          handleValueChange={this.handleValueChange}
        />
      </Dropdown>
    )
  }
}
<|endoftext|>