<commit_before>import * as os from 'os';
import * as moment from 'moment';

import Tyr from '../tyr';
import Collection from './collection';

import Query from './query';

const Subscription = new Collection({
  id: '_t3',
  name: 'tyrSubscription',
  client: false,
  internal: true,
  fields: {
    _id: { is: 'mongoid' },
    u: { link: 'user', label: 'User' },
    c: { is: 'string', label: 'Collection' },
    q: { is: 'string', label: 'Query', note: 'Stringified MongoDB query.' },
    on: { is: 'date' },

    // TODO:  this is temporary, long-term would like to hook up tyranid to session table and use that to
    //        determine user -> instance bindings
    i: { is: 'string', label: 'Instance' }
  }
});

/*
interface LocalListener {
  [collectionId: string]: {
    changeHandlerDereg: () => void,
    removeHandlerDereg: () => void,
    queries: {
      [queryStr: string]: {
        queryObj: MongoDBQuery,
        instances: {
          [instanceId: string]: boolean
        },
        users: {
          [userId: string]: boolean
        }
      }
    }
  }
}
*/

let localListeners /*: LocalListener*/ = {};

async function fireEvent(colId, queryDef, refinedDocument, refinedQuery, type) {
  //con sole.log('fireEvent', colId, queryDef, refinedDocument, refinedQuery);
  if (Tyr.logging.trace)
    Tyr.trace({
      e: 'subscription',
      c: colId,
      m: 'matched',
      instances: queryDef.instances
    });
  const promises = [];

  for (const instanceId in queryDef.instances) {
    const event = new Tyr.Event({
      collection: Subscription,
      dataCollectionId: colId,
      query: refinedQuery,
      document: refinedDocument,
      type: 'subscriptionEvent',
      when: 'pre',
      instanceId: instanceId,
      subType: type
    });

    if (instanceId === Tyr.instanceId) {
      handleSubscriptionEvent(event);
    } else {
      promises.push(Tyr.Event.fire(event));
    }
  }

  return Promise.all(promises);
}

async function parseSubscriptions(subscription, userId) {
  /*con sole.log(
    'parseSubscriptions(), Tyr.instanceId=',
    Tyr.instanceId,
    'userId=',
    userId
  );*/
  let subs;

  if (subscription) {
    subs = [subscription];
  } else if (userId) {
    // clear out existing data for this userId
    subs = await Subscription.findAll({ query: { u: userId } });

    for (const colId in localListeners) {
      const queries = localListeners[colId].queries;

      for (const queryStr in queries) {
        delete queries[queryStr].users[userId];

        //if (queryDef.users is empty) then clear out queryDef.instances ?
      }
    }
  } else {
    subs = await Subscription.findAll({});

    // if we're reparsing all subs, clear out existing data

    for (const colId in localListeners) {
      const listener = localListeners[colId];

      if (listener) {
        listener.changeHandlerDereg && listener.changeHandlerDereg();
        listener.removeHandlerDereg && listener.removeHandlerDereg();
      }
    }

    localListeners = {};
  }

  //con sole.log(Tyr.instanceId + ' *** parseSubscriptions, ' + subs.length + ' subs');

  for (const sub of subs) {
    const colId = sub.c,
      col = Tyr.byId[colId];

    if (!col) {
      console.warn(
        `Existing subscription to collection id ${colId} which no longer exists -- skipping`
      );
      continue;
    }

    let listener = localListeners[colId];

    if (!localListeners[colId]) {
      const changeHandlerDereg = col.on({
        type: 'change',
        handler: async event => {
          //con sole.log(Tyr.instanceId + ' *** ' + col.def.name + ' change:'); //, event);
          const { document, query, _documents } = event;
          const promises = [];

          for (const queryStr in listener.queries) {
            const queryDef = listener.queries[queryStr];

            if (document) {
              if (Query.matches(queryDef.queryObj, document)) {
                promises.push(
                  fireEvent(colId, queryDef, document, undefined, 'change')
                );
              }
            } else if (_documents) {
              promises.push(
                ..._documents
                  .filter(doc => Query.matches(queryDef.queryObj, doc))
                  .map(doc =>
                    fireEvent(colId, queryDef, doc, undefined, 'change')
                  )
              );
            } else {
              /*if (query)*/ const refinedQuery = Query.intersection(
                queryDef.queryObj,
                query
              );
              if (refinedQuery) {
                promises.push(
                  fireEvent(colId, queryDef, null, refinedQuery, 'change')
                );
              }
            }
          }

          await Promise.all(promises);
        }
      });

      const removeHandlerDereg = col.on({
        type: 'remove',
        handler: async event => {
          //con sole.log(Tyr.instanceId + ' *** ' + col.def.name + ' remove:'); //, event);
          const { document, query, _documents } = event;
          const promises = [];

          for (const queryStr in listener.queries) {
            const queryDef = listener.queries[queryStr];

            if (document) {
              if (Query.matches(queryDef.queryObj, document)) {
                promises.push(
                  fireEvent(colId, queryDef, document, undefined, 'remove')
                );
              }
            } else if (_documents) {
              promises.push(
                ..._documents
                  .filter(doc => Query.matches(queryDef.queryObj, doc))
                  .map(doc =>
                    fireEvent(colId, queryDef, doc, undefined, 'remove')
                  )
              );
            } else {
              /*if (query)*/ const refinedQuery = Query.intersection(
                queryDef.queryObj,
                query
              );
              if (refinedQuery) {
                promises.push(
                  fireEvent(colId, queryDef, null, refinedQuery, 'remove')
                );
              }
            }
          }

          await Promise.all(promises);
        }
      });

      listener = localListeners[colId] = {
        changeHandlerDereg,
        removeHandlerDereg,
        queries: {}
      };
    }

    const queryStr = sub.q;

    let queryDef = listener.queries[queryStr];
    if (!queryDef) {
      queryDef = listener.queries[queryStr] = {
        queryObj: col.fromClientQuery(JSON.parse(queryStr)),
        instances: {},
        users: {}
      };
    }

    queryDef.instances[sub.i] = true;
    queryDef.users[sub.u] = true;
  }
}

Subscription.on({
  type: 'subscribe',
  when: 'post',
  async handler(event) {
    await parseSubscriptions(event.subscription);
  }
});

Subscription.on({
  type: 'unsubscribe',
  when: 'post',
  async handler(event) {
    await parseSubscriptions(undefined, event.user);
  }
});

//let bootNeeded = 'Subscription needs to be booted';
Subscription.boot = async function(stage, pass) {
  if (stage === 'link' && Tyr.db) {
    //if (bootNeeded) {
    await parseSubscriptions();

    //bootNeeded = undefined;
    //}

    return undefined; //bootNeeded;
  }
};

Collection.prototype.subscribe = async function(query, user, cancel) {
  if (Tyr.logging.trace) {
    Tyr.trace({
      c: this._id,
      e: 'subscription',
      m: 'subscribe:' + user._id,
      q: query,
      cancel
    });
  }

  if (!user) {
    console.warn(
      `${this.name}.subscribe() called without a query or user -- ignoring`
    );
    return;
  }

  const queryStr = JSON.stringify(query);

  if (!query) {
    if (cancel) {
      await Subscription.remove({ query: { u: user._id, c: this.id } });

      await Tyr.Event.fire({
        collection: Subscription,
        type: 'unsubscribe',
        when: 'post',
        broadcast: true,
        user: user._id
      });

      return;
    } else {
      throw new Error('missing query');
    }
  }

  const subscription = await Subscription.findOne({
    query: {
      u: user._id,
      c: this.id,
      q: queryStr
    }
  });

  if (cancel) {
    if (subscription) {
      await subscription.$remove();

      await Tyr.Event.fire({
        collection: Subscription,
        type: 'unsubscribe',
        when: 'post',
        broadcast: true,
        user: user._id
      });
    }

    return;
  }

  if (!subscription || subscription.i !== Tyr.instanceId) {
    let s = subscription;

    if (s) {
      delete s._id;
      s.on = new Date();
      s.i = Tyr.instanceId;
    } else {
      s = new Subscription({
        u: user._id,
        c: this.id,
        q: queryStr,
        on: new Date(),
        i: Tyr.instanceId
      });
    }

    await Tyr.Event.fire({
      collection: this,
      type: 'subscribe',
      when: 'pre',
      query,
      opts: {
        query,
        auth: user
      },
      subscription: s
    });

    await s.$save();

    await Tyr.Event.fire({
      collection: Subscription,
      type: 'subscribe',
      when: 'post',
      broadcast: true,
      subscription: s
    });
  }
};

Subscription.unsubscribe = async function(userId) {
  const rslts = await Subscription.remove({
    query: {
      u: userId
    }
  });

  if (rslts.result.n) {
    await Tyr.Event.fire({
      collection: Subscription,
      type: 'unsubscribe',
      when: 'post',
      broadcast: true,
      user: userId
    });
  }
};

async function handleSubscriptionEvent(event) {
  //con sole.log(Tyr.instanceId + ' *** handleSubscriptionEvent:'); //, event);
  const col = event.dataCollection,
    listener = localListeners[col.id],
    mQuery = event.query,
    mDoc = event.document;

  if (listener) {
    const userIds = {};

    for (const queryStr in listener.queries) {
      const queryDef = listener.queries[queryStr];

      if (mQuery) {
        if (Query.intersection(queryDef.queryObj, mQuery)) {
          for (const userId in queryDef.users) {
            userIds[userId] = true;
          }
        }
      } else {
        // if mDoc
        if (Query.matches(queryDef.queryObj, mDoc)) {
          for (const userId in queryDef.users) {
            userIds[userId] = true;
          }
        }
      }
    }

    const documents = await event.documents;
    const sockets = Tyr.io.sockets.sockets;
    for (const socketId in sockets) {
      const socket = sockets[socketId];
      if (userIds[socket.userId]) {
        socket.emit('subscriptionEvent', {
          colId: event.dataCollectionId,
          type: event.subType,
          docs: documents.map(doc => doc.$toClient())
        });
      }
    }
  }
}

Subscription.on({
  type: 'subscriptionEvent',
  handler: handleSubscriptionEvent
});

export default Subscription;
<commit_msg>tyranid subscription fix<commit_after>import * as os from 'os';
import * as moment from 'moment';

import Tyr from '../tyr';
import Collection from './collection';

import Query from './query';

const Subscription = new Collection({
  id: '_t3',
  name: 'tyrSubscription',
  client: false,
  internal: true,
  fields: {
    _id: { is: 'mongoid' },
    u: { link: 'user', label: 'User' },
    c: { is: 'string', label: 'Collection' },
    q: { is: 'string', label: 'Query', note: 'Stringified MongoDB query.' },
    on: { is: 'date' },

    // TODO:  this is temporary, long-term would like to hook up tyranid to session table and use that to
    //        determine user -> instance bindings
    i: { is: 'string', label: 'Instance' }
  }
});

/*
interface LocalListener {
  [collectionId: string]: {
    changeHandlerDereg: () => void,
    removeHandlerDereg: () => void,
    queries: {
      [queryStr: string]: {
        queryObj: MongoDBQuery,
        instances: {
          [instanceId: string]: boolean
        },
        users: {
          [userId: string]: boolean
        }
      }
    }
  }
}
*/

let localListeners /*: LocalListener*/ = {};

async function fireEvent(colId, queryDef, refinedDocument, refinedQuery, type) {
  //con sole.log('fireEvent', colId, queryDef, refinedDocument, refinedQuery);
  if (Tyr.logging.trace)
    Tyr.trace({
      e: 'subscription',
      c: colId,
      m: 'matched',
      instances: queryDef.instances
    });
  const promises = [];

  for (const instanceId in queryDef.instances) {
    const event = new Tyr.Event({
      collection: Subscription,
      dataCollectionId: colId,
      query: refinedQuery,
      document: refinedDocument,
      type: 'subscriptionEvent',
      when: 'pre',
      instanceId: instanceId,
      subType: type
    });

    if (instanceId === Tyr.instanceId) {
      handleSubscriptionEvent(event);
    } else {
      promises.push(Tyr.Event.fire(event));
    }
  }

  return Promise.all(promises);
}

async function parseSubscriptions(subscription, userId) {
  /*con sole.log(
    'parseSubscriptions(), Tyr.instanceId=',
    Tyr.instanceId,
    'userId=',
    userId
  );*/
  let subs;

  if (subscription) {
    subs = [subscription];
  } else if (userId) {
    // clear out existing data for this userId
    subs = await Subscription.findAll({ query: { u: userId } });

    for (const colId in localListeners) {
      const queries = localListeners[colId].queries;

      for (const queryStr in queries) {
        delete queries[queryStr].users[userId];

        //if (queryDef.users is empty) then clear out queryDef.instances ?
      }
    }
  } else {
    subs = await Subscription.findAll({});

    // if we're reparsing all subs, clear out existing data

    for (const colId in localListeners) {
      const listener = localListeners[colId];

      if (listener) {
        listener.changeHandlerDereg && listener.changeHandlerDereg();
        listener.removeHandlerDereg && listener.removeHandlerDereg();
      }
    }

    localListeners = {};
  }

  //con sole.log(Tyr.instanceId + ' *** parseSubscriptions, ' + subs.length + ' subs');

  for (const sub of subs) {
    const colId = sub.c,
      col = Tyr.byId[colId];

    if (!col) {
      console.warn(
        `Existing subscription to collection id ${colId} which no longer exists -- skipping`
      );
      continue;
    }

    let listener = localListeners[colId];

    if (!localListeners[colId]) {
      const changeHandlerDereg = col.on({
        type: 'change',
        when: 'post',
        handler: async event => {
          //con sole.log(Tyr.instanceId + ' *** ' + col.def.name + ' change:'); //, event);
          const { document, query, _documents } = event;
          const promises = [];

          for (const queryStr in listener.queries) {
            const queryDef = listener.queries[queryStr];

            if (document) {
              if (Query.matches(queryDef.queryObj, document)) {
                promises.push(
                  fireEvent(colId, queryDef, document, undefined, 'change')
                );
              }
            } else if (_documents) {
              promises.push(
                ..._documents
                  .filter(doc => Query.matches(queryDef.queryObj, doc))
                  .map(doc =>
                    fireEvent(colId, queryDef, doc, undefined, 'change')
                  )
              );
            } else {
              /*if (query)*/ const refinedQuery = Query.intersection(
                queryDef.queryObj,
                query
              );
              if (refinedQuery) {
                promises.push(
                  fireEvent(colId, queryDef, null, refinedQuery, 'change')
                );
              }
            }
          }

          await Promise.all(promises);
        }
      });

      const removeHandlerDereg = col.on({
        type: 'remove',
        when: 'post',
        handler: async event => {
          //con sole.log(Tyr.instanceId + ' *** ' + col.def.name + ' remove:'); //, event);
          const { document, query, _documents } = event;
          const promises = [];

          for (const queryStr in listener.queries) {
            const queryDef = listener.queries[queryStr];

            if (document) {
              if (Query.matches(queryDef.queryObj, document)) {
                promises.push(
                  fireEvent(colId, queryDef, document, undefined, 'remove')
                );
              }
            } else if (_documents) {
              promises.push(
                ..._documents
                  .filter(doc => Query.matches(queryDef.queryObj, doc))
                  .map(doc =>
                    fireEvent(colId, queryDef, doc, undefined, 'remove')
                  )
              );
            } else {
              /*if (query)*/ const refinedQuery = Query.intersection(
                queryDef.queryObj,
                query
              );
              if (refinedQuery) {
                promises.push(
                  fireEvent(colId, queryDef, null, refinedQuery, 'remove')
                );
              }
            }
          }

          await Promise.all(promises);
        }
      });

      listener = localListeners[colId] = {
        changeHandlerDereg,
        removeHandlerDereg,
        queries: {}
      };
    }

    const queryStr = sub.q;

    let queryDef = listener.queries[queryStr];
    if (!queryDef) {
      queryDef = listener.queries[queryStr] = {
        queryObj: col.fromClientQuery(JSON.parse(queryStr)),
        instances: {},
        users: {}
      };
    }

    queryDef.instances[sub.i] = true;
    queryDef.users[sub.u] = true;
  }
}

Subscription.on({
  type: 'subscribe',
  when: 'post',
  async handler(event) {
    await parseSubscriptions(event.subscription);
  }
});

Subscription.on({
  type: 'unsubscribe',
  when: 'post',
  async handler(event) {
    await parseSubscriptions(undefined, event.user);
  }
});

//let bootNeeded = 'Subscription needs to be booted';
Subscription.boot = async function(stage, pass) {
  if (stage === 'link' && Tyr.db) {
    //if (bootNeeded) {
    await parseSubscriptions();

    //bootNeeded = undefined;
    //}

    return undefined; //bootNeeded;
  }
};

Collection.prototype.subscribe = async function(query, user, cancel) {
  if (Tyr.logging.trace) {
    Tyr.trace({
      c: this._id,
      e: 'subscription',
      m: 'subscribe:' + user._id,
      q: query,
      cancel
    });
  }

  if (!user) {
    console.warn(
      `${this.name}.subscribe() called without a query or user -- ignoring`
    );
    return;
  }

  const queryStr = JSON.stringify(query);

  if (!query) {
    if (cancel) {
      await Subscription.remove({ query: { u: user._id, c: this.id } });

      await Tyr.Event.fire({
        collection: Subscription,
        type: 'unsubscribe',
        when: 'post',
        broadcast: true,
        user: user._id
      });

      return;
    } else {
      throw new Error('missing query');
    }
  }

  const subscription = await Subscription.findOne({
    query: {
      u: user._id,
      c: this.id,
      q: queryStr
    }
  });

  if (cancel) {
    if (subscription) {
      await subscription.$remove();

      await Tyr.Event.fire({
        collection: Subscription,
        type: 'unsubscribe',
        when: 'post',
        broadcast: true,
        user: user._id
      });
    }

    return;
  }

  if (!subscription || subscription.i !== Tyr.instanceId) {
    let s = subscription;

    if (s) {
      delete s._id;
      s.on = new Date();
      s.i = Tyr.instanceId;
    } else {
      s = new Subscription({
        u: user._id,
        c: this.id,
        q: queryStr,
        on: new Date(),
        i: Tyr.instanceId
      });
    }

    await Tyr.Event.fire({
      collection: this,
      type: 'subscribe',
      when: 'pre',
      query,
      opts: {
        query,
        auth: user
      },
      subscription: s
    });

    await s.$save();

    await Tyr.Event.fire({
      collection: Subscription,
      type: 'subscribe',
      when: 'post',
      broadcast: true,
      subscription: s
    });
  }
};

Subscription.unsubscribe = async function(userId) {
  const rslts = await Subscription.remove({
    query: {
      u: userId
    }
  });

  if (rslts.result.n) {
    await Tyr.Event.fire({
      collection: Subscription,
      type: 'unsubscribe',
      when: 'post',
      broadcast: true,
      user: userId
    });
  }
};

async function handleSubscriptionEvent(event) {
  //con sole.log(Tyr.instanceId + ' *** handleSubscriptionEvent:'); //, event);
  const col = event.dataCollection,
    listener = localListeners[col.id],
    mQuery = event.query,
    mDoc = event.document;

  if (listener) {
    const userIds = {};

    for (const queryStr in listener.queries) {
      const queryDef = listener.queries[queryStr];

      if (mQuery) {
        if (Query.intersection(queryDef.queryObj, mQuery)) {
          for (const userId in queryDef.users) {
            userIds[userId] = true;
          }
        }
      } else {
        // if mDoc
        if (Query.matches(queryDef.queryObj, mDoc)) {
          for (const userId in queryDef.users) {
            userIds[userId] = true;
          }
        }
      }
    }

    const documents = await event.documents;
    const sockets = Tyr.io.sockets.sockets;
    for (const socketId in sockets) {
      const socket = sockets[socketId];
      if (userIds[socket.userId]) {
        socket.emit('subscriptionEvent', {
          colId: event.dataCollectionId,
          type: event.subType,
          docs: documents.map(doc => doc.$toClient())
        });
      }
    }
  }
}

Subscription.on({
  type: 'subscriptionEvent',
  handler: handleSubscriptionEvent
});

export default Subscription;
<|endoftext|>


<commit_before>import { GPUTextureFormat, GPUAddressMode, GPUFilterMode } from './constants.js';
import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, RepeatWrapping, MirroredRepeatWrapping, FloatType, HalfFloatType } from '../../../../build/three.module.js';

class WebGPUTextures {

	constructor( device, properties, info ) {

		this.device = device;
		this.properties = properties;
		this.info = info;

		this.defaultTexture = null;
		this.defaultSampler = null;

		this.samplerCache = new WeakMap();

	}

	getDefaultSampler() {

		if ( this.defaultSampler === null ) {

			this.defaultSampler = this.device.createSampler( {} );

		}

		return this.defaultSampler;

	}

	getDefaultTexture() {

		if ( this.defaultTexture === null ) {

			this.defaultTexture = this._createTexture( new Texture() );

		}

		return this.defaultTexture;

	}

	getTextureGPU( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.textureGPU;

	}

	getSampler( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.sampler;

	}

	updateTexture( texture ) {

		let updated = false;

		const textureProperties = this.properties.get( texture );

		if ( texture.version > 0 && textureProperties.version !== texture.version ) {

			const image = texture.image;

			if ( image === undefined ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is undefined' );

			} else if ( image.complete === false ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is incomplete' );

			} else {

				// texture init

				if ( textureProperties.initialized === undefined ) {

					textureProperties.initialized = true;

					const disposeCallback = onTextureDispose.bind( this );
					textureProperties.disposeCallback = disposeCallback;

					texture.addEventListener( 'dispose', disposeCallback );

					this.info.memory.textures ++;

				}

				// texture creation

				if ( textureProperties.textureGPU !== undefined ) {

					// TODO: Avoid calling of destroy() in certain scenarios. When only the contents of a texture
					// are updated, a buffer upload should be sufficient. However, if the user changes
					// the dimensions of the texture, format or usage, a new instance of GPUTexture is required.

					textureProperties.textureGPU.destroy();

				}

				textureProperties.textureGPU = this._createTexture( texture );
				textureProperties.version = texture.version;
				updated = true;

			}

		}

		return updated;

	}

	updateSampler( texture ) {

		let updated = false;

		const array = [];

		array.push( texture.wrapS );
		array.push( texture.wrapT );
		array.push( texture.wrapR );
		array.push( texture.magFilter );
		array.push( texture.minFilter );
		array.push( texture.anisotropy );

		const newKey = array.join();
		const key = this.samplerCache.get( texture );

		if ( key !== newKey ) {

			this.samplerCache.set( texture, newKey );

			const sampler = this.device.createSampler( {
				addressModeU: this._convertAddressMode( texture.wrapS ),
				addressModeV: this._convertAddressMode( texture.wrapT ),
				addressModeW: this._convertAddressMode( texture.wrapR ),
				magFilter: this._convertFilterMode( texture.magFilter ),
				minFilter: this._convertFilterMode( texture.minFilter ),
				mipmapFilter: this._convertFilterMode( texture.minFilter ),
				maxAnisotropy: texture.anisotropy
			} );

			const textureProperties = this.properties.get( texture );
			textureProperties.sampler = sampler;

			updated = true;

		}

		return updated;

	}

	_convertAddressMode( value ) {

		let addressMode = GPUAddressMode.ClampToEdge;

		if ( value === RepeatWrapping ) {

			addressMode = GPUAddressMode.Repeat;

		} else if ( value === MirroredRepeatWrapping ) {

			addressMode = GPUAddressMode.MirrorRepeat;

		}

		return addressMode;

	}

	_convertFilterMode( value ) {

		let filterMode = GPUFilterMode.Linear;

		if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) {

			filterMode = GPUFilterMode.Nearest;

		}

		return filterMode;

	}

	_convertFormat( type ) {

		let formatGPU = GPUTextureFormat.RGBA8Unorm;

		if ( type === FloatType ) {

			formatGPU = GPUTextureFormat.RGBA32Float;

		} else if ( type === HalfFloatType ) {

			formatGPU = GPUTextureFormat.RGBA16Float;

		}

		return formatGPU;

	}

	_createTexture( texture ) {

		const device = this.device;
		const image = texture.image;

		const width = ( image !== undefined ) ? image.width : 1;
		const height = ( image !== undefined ) ? image.height : 1;

		const format = this._convertFormat( texture.type );

		const textureGPU = device.createTexture( {
			size: {
				width: width,
				height: height,
				depth: 1,
			},
			format: format,
			usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST,
		} );

		if ( texture.isDataTexture ) {

			this.__copyBufferToTexture( image, format, textureGPU );

		} else {

			// convert HTML iamges and canvas elements to ImageBitmap before copy

			if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
				( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ) {

				const options = {};

				options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none';

				createImageBitmap( image, 0, 0, width, height, options ).then( imageBitmap => {

					this._copyImageBitmapToTexture( imageBitmap, textureGPU );

				} );

			} else {

				if ( image !== undefined ) {

					// assuming ImageBitmap. Directly start copy operation of the contents of ImageBitmap into the destination texture

					this._copyImageBitmapToTexture( image, textureGPU );

				}

			}

		}

		return textureGPU;

	}

	__copyBufferToTexture( image, format, textureGPU ) {

		// this code assumes data textures in RGBA format
		// TODO: Consider to support valid buffer layouts with other formats like RGB

		const device = this.device;
		const data = image.data;

		const bytesPerTexel = this._getBytesPerTexel( format );
		const bytesPerRow = Math.ceil( image.width * bytesPerTexel / 256 ) * 256;

		const textureDataBuffer = device.createBuffer( {
			size: data.byteLength,
			usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
			mappedAtCreation: true,
		} );

		new data.constructor( textureDataBuffer.getMappedRange() ).set( data );
		textureDataBuffer.unmap();

		const commandEncoder = device.createCommandEncoder( {} );
		commandEncoder.copyBufferToTexture(
			{
				buffer: textureDataBuffer,
				bytesPerRow: bytesPerRow
			}, {
				texture: textureGPU
			}, {
				width: image.width,
				height: image.height,
				depth: 1
			} );

		device.defaultQueue.submit( [ commandEncoder.finish() ] );
		textureDataBuffer.destroy();

	}

	_getBytesPerTexel( format ) {

		if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
		if ( format === GPUTextureFormat.RGBA16Float ) return 8;
		if ( format === GPUTextureFormat.RGBA32Float ) return 16;

	}

	_copyImageBitmapToTexture( imageBitmap, textureGPU ) {

		const device = this.device;

		device.defaultQueue.copyImageBitmapToTexture(
			{
				imageBitmap: imageBitmap
			}, {
				texture: textureGPU
			}, {
				width: imageBitmap.width,
				height: imageBitmap.height,
				depth: 1
			}
		);

	}

}

function onTextureDispose( event ) {

	const texture = event.target;

	const textureProperties = this.properties.get( texture );
	textureProperties.textureGPU.destroy();

	texture.removeEventListener( 'dispose', textureProperties.disposeCallback );

	this.properties.remove( texture );

	this.info.memory.textures --;

}

export default WebGPUTextures;
<commit_msg>WebGPUTextures: Honor premultiplyAlpha when creating ImageBitmap.<commit_after>import { GPUTextureFormat, GPUAddressMode, GPUFilterMode } from './constants.js';
import { Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, RepeatWrapping, MirroredRepeatWrapping, FloatType, HalfFloatType } from '../../../../build/three.module.js';

class WebGPUTextures {

	constructor( device, properties, info ) {

		this.device = device;
		this.properties = properties;
		this.info = info;

		this.defaultTexture = null;
		this.defaultSampler = null;

		this.samplerCache = new WeakMap();

	}

	getDefaultSampler() {

		if ( this.defaultSampler === null ) {

			this.defaultSampler = this.device.createSampler( {} );

		}

		return this.defaultSampler;

	}

	getDefaultTexture() {

		if ( this.defaultTexture === null ) {

			this.defaultTexture = this._createTexture( new Texture() );

		}

		return this.defaultTexture;

	}

	getTextureGPU( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.textureGPU;

	}

	getSampler( texture ) {

		const textureProperties = this.properties.get( texture );

		return textureProperties.sampler;

	}

	updateTexture( texture ) {

		let updated = false;

		const textureProperties = this.properties.get( texture );

		if ( texture.version > 0 && textureProperties.version !== texture.version ) {

			const image = texture.image;

			if ( image === undefined ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is undefined' );

			} else if ( image.complete === false ) {

				console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is incomplete' );

			} else {

				// texture init

				if ( textureProperties.initialized === undefined ) {

					textureProperties.initialized = true;

					const disposeCallback = onTextureDispose.bind( this );
					textureProperties.disposeCallback = disposeCallback;

					texture.addEventListener( 'dispose', disposeCallback );

					this.info.memory.textures ++;

				}

				// texture creation

				if ( textureProperties.textureGPU !== undefined ) {

					// TODO: Avoid calling of destroy() in certain scenarios. When only the contents of a texture
					// are updated, a buffer upload should be sufficient. However, if the user changes
					// the dimensions of the texture, format or usage, a new instance of GPUTexture is required.

					textureProperties.textureGPU.destroy();

				}

				textureProperties.textureGPU = this._createTexture( texture );
				textureProperties.version = texture.version;
				updated = true;

			}

		}

		return updated;

	}

	updateSampler( texture ) {

		let updated = false;

		const array = [];

		array.push( texture.wrapS );
		array.push( texture.wrapT );
		array.push( texture.wrapR );
		array.push( texture.magFilter );
		array.push( texture.minFilter );
		array.push( texture.anisotropy );

		const newKey = array.join();
		const key = this.samplerCache.get( texture );

		if ( key !== newKey ) {

			this.samplerCache.set( texture, newKey );

			const sampler = this.device.createSampler( {
				addressModeU: this._convertAddressMode( texture.wrapS ),
				addressModeV: this._convertAddressMode( texture.wrapT ),
				addressModeW: this._convertAddressMode( texture.wrapR ),
				magFilter: this._convertFilterMode( texture.magFilter ),
				minFilter: this._convertFilterMode( texture.minFilter ),
				mipmapFilter: this._convertFilterMode( texture.minFilter ),
				maxAnisotropy: texture.anisotropy
			} );

			const textureProperties = this.properties.get( texture );
			textureProperties.sampler = sampler;

			updated = true;

		}

		return updated;

	}

	_convertAddressMode( value ) {

		let addressMode = GPUAddressMode.ClampToEdge;

		if ( value === RepeatWrapping ) {

			addressMode = GPUAddressMode.Repeat;

		} else if ( value === MirroredRepeatWrapping ) {

			addressMode = GPUAddressMode.MirrorRepeat;

		}

		return addressMode;

	}

	_convertFilterMode( value ) {

		let filterMode = GPUFilterMode.Linear;

		if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) {

			filterMode = GPUFilterMode.Nearest;

		}

		return filterMode;

	}

	_convertFormat( type ) {

		let formatGPU = GPUTextureFormat.RGBA8Unorm;

		if ( type === FloatType ) {

			formatGPU = GPUTextureFormat.RGBA32Float;

		} else if ( type === HalfFloatType ) {

			formatGPU = GPUTextureFormat.RGBA16Float;

		}

		return formatGPU;

	}

	_createTexture( texture ) {

		const device = this.device;
		const image = texture.image;

		const width = ( image !== undefined ) ? image.width : 1;
		const height = ( image !== undefined ) ? image.height : 1;

		const format = this._convertFormat( texture.type );

		const textureGPU = device.createTexture( {
			size: {
				width: width,
				height: height,
				depth: 1,
			},
			format: format,
			usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST,
		} );

		if ( texture.isDataTexture ) {

			this.__copyBufferToTexture( image, format, textureGPU );

		} else {

			// convert HTML iamges and canvas elements to ImageBitmap before copy

			if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
				( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ) {

				const options = {};

				options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none';
				options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default';

				createImageBitmap( image, 0, 0, width, height, options ).then( imageBitmap => {

					this._copyImageBitmapToTexture( imageBitmap, textureGPU );

				} );

			} else {

				if ( image !== undefined ) {

					// assuming ImageBitmap. Directly start copy operation of the contents of ImageBitmap into the destination texture

					this._copyImageBitmapToTexture( image, textureGPU );

				}

			}

		}

		return textureGPU;

	}

	__copyBufferToTexture( image, format, textureGPU ) {

		// this code assumes data textures in RGBA format
		// TODO: Consider to support valid buffer layouts with other formats like RGB

		const device = this.device;
		const data = image.data;

		const bytesPerTexel = this._getBytesPerTexel( format );
		const bytesPerRow = Math.ceil( image.width * bytesPerTexel / 256 ) * 256;

		const textureDataBuffer = device.createBuffer( {
			size: data.byteLength,
			usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
			mappedAtCreation: true,
		} );

		new data.constructor( textureDataBuffer.getMappedRange() ).set( data );
		textureDataBuffer.unmap();

		const commandEncoder = device.createCommandEncoder( {} );
		commandEncoder.copyBufferToTexture(
			{
				buffer: textureDataBuffer,
				bytesPerRow: bytesPerRow
			}, {
				texture: textureGPU
			}, {
				width: image.width,
				height: image.height,
				depth: 1
			} );

		device.defaultQueue.submit( [ commandEncoder.finish() ] );
		textureDataBuffer.destroy();

	}

	_getBytesPerTexel( format ) {

		if ( format === GPUTextureFormat.RGBA8Unorm ) return 4;
		if ( format === GPUTextureFormat.RGBA16Float ) return 8;
		if ( format === GPUTextureFormat.RGBA32Float ) return 16;

	}

	_copyImageBitmapToTexture( imageBitmap, textureGPU ) {

		const device = this.device;

		device.defaultQueue.copyImageBitmapToTexture(
			{
				imageBitmap: imageBitmap
			}, {
				texture: textureGPU
			}, {
				width: imageBitmap.width,
				height: imageBitmap.height,
				depth: 1
			}
		);

	}

}

function onTextureDispose( event ) {

	const texture = event.target;

	const textureProperties = this.properties.get( texture );
	textureProperties.textureGPU.destroy();

	texture.removeEventListener( 'dispose', textureProperties.disposeCallback );

	this.properties.remove( texture );

	this.info.memory.textures --;

}

export default WebGPUTextures;
<|endoftext|>


<commit_before>import {ref, keyFieldRef} from '../util';
import {Collect, Aggregate, MultiExtent, MultiValues, Values} from '../transforms';
import {error, isArray, isObject, isString, toSet} from 'vega-util';

var types = [
  'identity',
  'ordinal', 'band', 'point', 'index',
  'linear', 'pow', 'sqrt', 'log', 'sequential',
  'time', 'utc',
  'quantize', 'quantile', 'threshold'
]

var allTypes = toSet(types),
    ordinalTypes = toSet(types.slice(1, 5));

export function isOrdinal(type) {
  return ordinalTypes.hasOwnProperty(type);
}

export function isQuantile(type) {
  return type === 'quantile';
}

export default function(spec, scope) {
  var type = spec.type || 'linear',
      params, key;

  if (!allTypes.hasOwnProperty(type)) {
    error('Unrecognized scale type: ' + type);
  }

  params = {
    type:   type,
    domain: parseScaleDomain(spec.domain, spec, scope)
  };

  if (spec.range != null) {
    if (spec.rangeStep != null) {
      error('Scale range and rangeStep are mutually exclusive.');
    }
    params.range = parseScaleRange(spec, scope);
  }

  for (key in spec) {
    if (params[key] || key === 'name') continue;
    params[key] = parseLiteral(spec[key], scope);
  }

  scope.addScale(spec.name, params);
}

function parseLiteral(v, scope) {
  return !isObject(v) ? v
    : v.signal ? scope.signalRef(v.signal)
    : error('Unsupported object: ' + v);
}

// -- SCALE DOMAIN ----

function parseScaleDomain(domain, spec, scope) {
  if (!domain) return; // default domain

  if (domain.signal) {
    return scope.signalRef(domain.signal);
  }

  return (isArray(domain) ? explicitDomain
    : domain.fields ? multipleDomain
    : singularDomain)(domain, spec, scope);
}

function explicitDomain(domain, spec, scope) {
  return domain.map(function(v) {
    return parseLiteral(v, scope);
  });
}

function singularDomain(domain, spec, scope) {
  var data = scope.getData(domain.data);
  if (!data) error('Can not find data set: ' + domain.data);

  return isOrdinal(spec.type)
      ? data.valuesRef(scope, domain.field, parseSort(domain.sort, false))
      : isQuantile(spec.type) ? data.domainRef(scope, domain.field)
      : data.extentRef(scope, domain.field);
}

function multipleDomain(domain, spec, scope) {
  var data = domain.data,
      fields = domain.fields.reduce(function(dom, d) {
        return dom.push(isString(d) ? {data: data, field: d} : d), dom;
      }, []);

  return (isOrdinal(spec.type) ? ordinalMultipleDomain
    : isQuantile(spec.type) ? quantileMultipleDomain
    : numericMultipleDomain)(domain, scope, fields);
}

function ordinalMultipleDomain(domain, scope, fields) {
  var counts, a, c, v;

  // get value counts for each domain field
  counts = fields.map(function(f) {
    var data = scope.getData(f.data);
    if (!data) error('Can not find data set: ' + f.data);
    return data.countsRef(scope, f.field);
  });

  // sum counts from all fields
  a = scope.add(Aggregate({
    groupby: keyFieldRef,
    ops:['sum'], fields: [scope.fieldRef('count')], as:['count'],
    pulse: counts
  }));

  // collect aggregate output
  c = scope.add(Collect({pulse: ref(a)}));

  // extract values for combined domain
  v = scope.add(Values({
    field: keyFieldRef,
    sort:  scope.sortRef(parseSort(domain.sort, true)),
    pulse: ref(c)
  }));

  return ref(v);
}

function parseSort(sort, multidomain) {
  if (sort) {
    if (!sort.field && !sort.op) {
      if (isObject(sort)) sort.field = 'key';
      else sort = {field: 'key'};
    } else if (!sort.field && sort.op !== 'count') {
      error('No field provided for sort aggregate op: ' + sort.op);
    } else if (multidomain && sort.field) {
      error('Multiple domain scales can not sort by field.');
    } else if (multidomain && sort.op && sort.op !== 'count') {
      error('Multiple domain scales support op count only.');
    }
  }
  return sort;
}

function quantileMultipleDomain(domain, scope, fields) {
  // get value arrays for each domain field
  var values = fields.map(function(f) {
    var data = scope.getData(f.data);
    if (!data) error('Can not find data set: ' + f.data);
    return data.domainRef(scope, f.field);
  });

  // combine value arrays
  return ref(scope.add(MultiValues({values: values})));
}

function numericMultipleDomain(domain, scope, fields) {
  // get extents for each domain field
  var extents = fields.map(function(f) {
    var data = scope.getData(f.data);
    if (!data) error('Can not find data set: ' + f.data);
    return data.extentRef(scope, f.field);
  });

  // combine extents
  return ref(scope.add(MultiExtent({extents: extents})));
}

// -- SCALE RANGE -----

function parseScaleRange(spec, scope) {
  var range = spec.range,
      config = scope.config.range;

  if (range.signal) {
    return scope.signalRef(range.signal);
  } else if (isString(range)) {
    if (config && config.hasOwnProperty(range)) {
      range = config[range];
    } else if (range === 'width') {
      range = [0, {signal: 'width'}]
    } else if (range === 'height') {
      range = isOrdinal(spec.type)
        ? [0, {signal: 'height'}]
        : [{signal: 'height'}, 0]
    } else {
      error('Unrecognized scale range value: ' + range);
    }
  } else if (isOrdinal(spec.type) && !isArray(range)) {
    return parseScaleDomain(range, spec, scope);
  } else if (!isArray(range)) {
    error('Unsupported range type: ' + range);
  }

  return range.map(function(v) {
    return parseLiteral(v, scope);
  });
}
<commit_msg>Improve scale parser error reporting.<commit_after>import {ref, keyFieldRef} from '../util';
import {Collect, Aggregate, MultiExtent, MultiValues, Values} from '../transforms';
import {error, isArray, isObject, isString, stringValue, toSet} from 'vega-util';

var types = [
  'identity',
  'ordinal', 'band', 'point', 'index',
  'linear', 'pow', 'sqrt', 'log', 'sequential',
  'time', 'utc',
  'quantize', 'quantile', 'threshold'
]

var allTypes = toSet(types),
    ordinalTypes = toSet(types.slice(1, 5));

export function isOrdinal(type) {
  return ordinalTypes.hasOwnProperty(type);
}

export function isQuantile(type) {
  return type === 'quantile';
}

export default function(spec, scope) {
  var type = spec.type || 'linear',
      params, key;

  if (!allTypes.hasOwnProperty(type)) {
    error('Unrecognized scale type: ' + type);
  }

  params = {
    type:   type,
    domain: parseScaleDomain(spec.domain, spec, scope)
  };

  if (spec.range != null) {
    if (spec.rangeStep != null) {
      error('Scale range and rangeStep are mutually exclusive.');
    }
    params.range = parseScaleRange(spec, scope);
  }

  for (key in spec) {
    if (params[key] || key === 'name') continue;
    params[key] = parseLiteral(spec[key], scope);
  }

  scope.addScale(spec.name, params);
}

function parseLiteral(v, scope) {
  return !isObject(v) ? v
    : v.signal ? scope.signalRef(v.signal)
    : error('Unsupported object: ' + stringValue(v));
}

function dataLookupError(name) {
  error('Can not find data set: ' + name);
}

// -- SCALE DOMAIN ----

function parseScaleDomain(domain, spec, scope) {
  if (!domain) {
    if (spec.domainMin != null || spec.domainMax != null) {
      error('No scale domain defined for domainMin/domainMax to override.');
    }
    return; // default domain
  }

  if (domain.signal) {
    return scope.signalRef(domain.signal);
  }

  return (isArray(domain) ? explicitDomain
    : domain.fields ? multipleDomain
    : singularDomain)(domain, spec, scope);
}

function explicitDomain(domain, spec, scope) {
  return domain.map(function(v) {
    return parseLiteral(v, scope);
  });
}

function singularDomain(domain, spec, scope) {
  var data = scope.getData(domain.data);
  if (!data) dataLookupError(domain.data);

  return isOrdinal(spec.type)
      ? data.valuesRef(scope, domain.field, parseSort(domain.sort, false))
      : isQuantile(spec.type) ? data.domainRef(scope, domain.field)
      : data.extentRef(scope, domain.field);
}

function multipleDomain(domain, spec, scope) {
  var data = domain.data,
      fields = domain.fields.reduce(function(dom, d) {
        return dom.push(isString(d) ? {data: data, field: d} : d), dom;
      }, []);

  return (isOrdinal(spec.type) ? ordinalMultipleDomain
    : isQuantile(spec.type) ? quantileMultipleDomain
    : numericMultipleDomain)(domain, scope, fields);
}

function ordinalMultipleDomain(domain, scope, fields) {
  var counts, a, c, v;

  // get value counts for each domain field
  counts = fields.map(function(f) {
    var data = scope.getData(f.data);
    if (!data) dataLookupError(f.data);
    return data.countsRef(scope, f.field);
  });

  // sum counts from all fields
  a = scope.add(Aggregate({
    groupby: keyFieldRef,
    ops:['sum'], fields: [scope.fieldRef('count')], as:['count'],
    pulse: counts
  }));

  // collect aggregate output
  c = scope.add(Collect({pulse: ref(a)}));

  // extract values for combined domain
  v = scope.add(Values({
    field: keyFieldRef,
    sort:  scope.sortRef(parseSort(domain.sort, true)),
    pulse: ref(c)
  }));

  return ref(v);
}

function parseSort(sort, multidomain) {
  if (sort) {
    if (!sort.field && !sort.op) {
      if (isObject(sort)) sort.field = 'key';
      else sort = {field: 'key'};
    } else if (!sort.field && sort.op !== 'count') {
      error('No field provided for sort aggregate op: ' + sort.op);
    } else if (multidomain && sort.field) {
      error('Multiple domain scales can not sort by field.');
    } else if (multidomain && sort.op && sort.op !== 'count') {
      error('Multiple domain scales support op count only.');
    }
  }
  return sort;
}

function quantileMultipleDomain(domain, scope, fields) {
  // get value arrays for each domain field
  var values = fields.map(function(f) {
    var data = scope.getData(f.data);
    if (!data) dataLookupError(f.data);
    return data.domainRef(scope, f.field);
  });

  // combine value arrays
  return ref(scope.add(MultiValues({values: values})));
}

function numericMultipleDomain(domain, scope, fields) {
  // get extents for each domain field
  var extents = fields.map(function(f) {
    var data = scope.getData(f.data);
    if (!data) dataLookupError(f.data);
    return data.extentRef(scope, f.field);
  });

  // combine extents
  return ref(scope.add(MultiExtent({extents: extents})));
}

// -- SCALE RANGE -----

function parseScaleRange(spec, scope) {
  var range = spec.range,
      config = scope.config.range;

  if (range.signal) {
    return scope.signalRef(range.signal);
  } else if (isString(range)) {
    if (config && config.hasOwnProperty(range)) {
      range = config[range];
    } else if (range === 'width') {
      range = [0, {signal: 'width'}]
    } else if (range === 'height') {
      range = isOrdinal(spec.type)
        ? [0, {signal: 'height'}]
        : [{signal: 'height'}, 0]
    } else {
      error('Unrecognized scale range value: ' + range);
    }
  } else if (isOrdinal(spec.type) && !isArray(range)) {
    return parseScaleDomain(range, spec, scope);
  } else if (!isArray(range)) {
    error('Unsupported range type: ' + range);
  }

  return range.map(function(v) {
    return parseLiteral(v, scope);
  });
}
<|endoftext|>


<commit_before>'use strict';

var gulp         = require('gulp');
var sass         = require('gulp-sass');
var autoprefixer = require('gulp-autoprefixer');
var rename       = require('gulp-rename');
var uglify       = require('gulp-uglify');
var jshint       = require('gulp-jshint');
var stylish      = require('jshint-stylish');
var wpPot        = require('gulp-wp-pot');
var sort         = require('gulp-sort');
var gcmq         = require('gulp-group-css-media-queries');
var del          = require('del');
var zip          = require('gulp-zip');
var browserSync = require('browser-sync');
var runSequence  = require('run-sequence');
var js_files     = ['js/*.js', '!js/*.min.js', '!js/lib/**/*.js'];

var build_files = [
  '**',
  '!node_modules',
  '!node_modules/**',
  '!build',
  '!build/**',
  '!sass',
  '!sass/**',
  '!.git',
  '!.git/**',
  '!package.json',
  '!.gitignore',
  '!gulpfile.js',
  '!.editorconfig',
  '!.jshintrc'
];

gulp.task('sass', function () {
  gulp.src(['sass/style.scss'])
    .pipe(sass({outputStyle: 'expanded'}))
    .pipe(autoprefixer(['last 2 versions']))
    .pipe(gcmq())
    .pipe(gulp.dest('.'))
    .pipe(browserSync.reload({stream:true}));
});

gulp.task('lint', function() {
  return gulp.src(js_files)
    .pipe(jshint())
    .pipe(jshint.reporter(stylish));
});

gulp.task('compress', function() {
  return gulp.src(js_files, {base: '.'})
    .pipe(gulp.dest('.'))
    .pipe(uglify())
    .pipe(rename({extname: '.min.js'}))
    .pipe(gulp.dest('.'));
});

gulp.task('makepot', function () {
  return gulp.src(['**/*.php'])
    .pipe(sort())
    .pipe(wpPot({
      domain: '<%= theme_domain %>',
      destFile: '<%= theme_domain %>.pot',
      package: '<%= package_name %>',
      bugReport: '<%= theme_bugreport %>',
      team: '<%= author %> <<%= author_email %>>'
    }))
    .pipe(gulp.dest('languages'));
});

gulp.task('browserSync', function() {
  browserSync({
    proxy: '<%= proxy_address %>/<%= proxy_domain %>',
    port: 8080,
    open: true,
    notify: false
  });
});

gulp.task('watch', function () {
  gulp.watch(js_files, ['lint']);
  gulp.watch(js_files, ['compress']);
  gulp.watch(['**/*.php'], ['makepot']);
  gulp.watch('sass/**/*.scss', ['sass']);
});

gulp.task('build-clean', function() {
  del(['dist/**/*']);
});

gulp.task('build-copy', function() {
  return gulp.src(build_files)
    .pipe(gulp.dest('dist/<%= theme_domain %>'));
});

gulp.task('build-zip', function() {
  return gulp.src('dist/**/*')
    .pipe(zip('<%= theme_domain %>.zip'))
    .pipe(gulp.dest('dist'));
});

gulp.task('build-delete', function() {
  del(['dist/**/*', '!dist/<%= theme_domain %>.zip']);
});

gulp.task('build', function(callback) {
  runSequence('build-clean', 'build-copy', 'build-zip', 'build-delete');
});

gulp.task('default', ['sass', 'lint', 'compress', 'makepot', 'watch', 'browserSync']);
<commit_msg>add wiredep<commit_after>'use strict';

var gulp         = require('gulp');
var sass         = require('gulp-sass');
var autoprefixer = require('gulp-autoprefixer');
var rename       = require('gulp-rename');
var uglify       = require('gulp-uglify');
var jshint       = require('gulp-jshint');
var stylish      = require('jshint-stylish');
var wpPot        = require('gulp-wp-pot');
var sort         = require('gulp-sort');
var gcmq         = require('gulp-group-css-media-queries');
var del          = require('del');
var zip          = require('gulp-zip');
var browserSync = require('browser-sync');
var runSequence  = require('run-sequence');
var wiredep 		= require('wiredep').stream;
var js_files     = ['js/*.js', '!js/*.min.js', '!js/lib/**/*.js'];

var build_files = [
  '**',
  '!node_modules',
  '!node_modules/**',
  '!build',
  '!build/**',
  '!sass',
  '!sass/**',
  '!.git',
  '!.git/**',
  '!package.json',
  '!.gitignore',
  '!gulpfile.js',
  '!.editorconfig',
  '!.jshintrc'
];

gulp.task('wiredep', function () {
  gulp.src('sass/style.scss')
    .pipe(wiredep({
      optional: 'configuration',
      goes: 'here'
    }))
    .pipe(gulp.dest('sass/'));
});

gulp.task('sass', function () {
  gulp.src(['sass/style.scss'])
    .pipe(sass({outputStyle: 'expanded'}))
    .pipe(autoprefixer(['last 2 versions']))
    .pipe(gcmq())
    .pipe(gulp.dest('.'))
    .pipe(browserSync.reload({stream:true}));
});

gulp.task('lint', function() {
  return gulp.src(js_files)
    .pipe(jshint())
    .pipe(jshint.reporter(stylish));
});

gulp.task('compress', function() {
  return gulp.src(js_files, {base: '.'})
    .pipe(gulp.dest('.'))
    .pipe(uglify())
    .pipe(rename({extname: '.min.js'}))
    .pipe(gulp.dest('.'));
});

gulp.task('makepot', function () {
  return gulp.src(['**/*.php'])
    .pipe(sort())
    .pipe(wpPot({
      domain: '<%= theme_domain %>',
      destFile: '<%= theme_domain %>.pot',
      package: '<%= package_name %>',
      bugReport: '<%= theme_bugreport %>',
      team: '<%= author %> <<%= author_email %>>'
    }))
    .pipe(gulp.dest('languages'));
});

gulp.task('browserSync', function() {
  browserSync({
    proxy: '<%= proxy_address %>/<%= proxy_domain %>',
    port: 8080,
    open: true,
    notify: false
  });
});

gulp.task('watch', function () {
  gulp.watch(js_files, ['lint']);
  gulp.watch(js_files, ['compress']);
  gulp.watch(['**/*.php'], ['makepot']);
  gulp.watch('sass/**/*.scss', ['sass']);
});

gulp.task('build-clean', function() {
  del(['dist/**/*']);
});

gulp.task('build-copy', function() {
  return gulp.src(build_files)
    .pipe(gulp.dest('dist/<%= theme_domain %>'));
});

gulp.task('build-zip', function() {
  return gulp.src('dist/**/*')
    .pipe(zip('<%= theme_domain %>.zip'))
    .pipe(gulp.dest('dist'));
});

gulp.task('build-delete', function() {
  del(['dist/**/*', '!dist/<%= theme_domain %>.zip']);
});

gulp.task('build', function(callback) {
  runSequence('build-clean', 'build-copy', 'build-zip', 'build-delete');
});

gulp.task('default', ['sass', 'lint', 'compress', 'makepot', 'watch', 'browserSync']);
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * Base type for the status bar that appears at the top of games.
 * The base type is primarily responsible for resizing and 'floating' the bar.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var vegas = require( 'VEGAS/vegas' );

  // constants
  var DEFAULT_FONT = new PhetFont( 20 );

  /**
   * @param {Bounds2} layoutBounds
   * @param {Property.<Bounds2>} visibleBoundsProperty - visible bounds of the parent ScreenView
   * @param {Object} [options]
   * @constructor
   */
  function StatusBar( layoutBounds, visibleBoundsProperty, options ) {

    var self = this;

    options = _.extend( {
      barHeight: 50,
      xMargin: 10,
      yMargin: 8,
      barFill: 'lightGray',
      barStroke: null,

      // true: float bar to top of visible bounds; false: bar at top of layoutBounds
      floatToTop: false,

      // true: keeps things on the status bar aligned with left and right edges of window bounds
      // false: align things on status bar with left and right edges of static layoutBounds
      dynamicAlignment: true

    }, options );

    // @private
    this.layoutBounds = layoutBounds;
    this.xMargin = options.xMargin;
    this.yMargin = options.yMargin;
    this.dynamicAlignment = options.dynamicAlignment;

    // @private size will be set by visibleBoundsListener
    this.barNode = new Rectangle( {
      fill: options.barFill,
      stroke: options.barStroke
    } );

    // Support decoration, with the bar behind everything else
    options.children = [ this.barNode ].concat( options.children || [] );

    Node.call( this, options );

    var visibleBoundsListener = function( visibleBounds ) {

      // resize the bar
      var y = ( options.floatToTop ) ? visibleBounds.top : layoutBounds.top;
      self.barNode.setRect( visibleBounds.minX, y, visibleBounds.width, options.barHeight );

      // update layout of things on the bar
      self.updateLayout();
    };
    visibleBoundsProperty.link( visibleBoundsListener );

    // @private
    this.disposeStatusBar = function() {
      if ( visibleBoundsProperty.hasListener( visibleBoundsListener ) ) {
        visibleBoundsProperty.unlink( visibleBoundsListener );
      }
    };
  }

  vegas.register( 'StatusBar', StatusBar );

  return inherit( Node, StatusBar, {

    /**
     * @public
     * @override
     */
    dispose: function() {
      this.disposeStatusBar();
      Node.prototype.dispose.call( this );
    },

    /**
     * Updates the layout of things on the bar.
     * @protected
     */
    updateLayout: function() {
      var leftEdge = ( ( this.dynamicAlignment ) ? this.barNode.left : this.layoutBounds.minX ) + this.xMargin;
      var rightEdge = ( ( this.dynamicAlignment ) ? this.barNode.right : this.layoutBounds.maxX ) - this.xMargin;
      this.updateLayoutProtected( leftEdge, rightEdge, this.barNode.centerY );
    },

    /**
     * Layout that is specific to subtypes.
     * @param {number} leftEdge - the bar's left edge, compensated for xMargin
     * @param {number} rightEdge - the bar's right edge, compensated for xMargin
     * @param {number} centerY - the bar's vertical center
     * @protected
     * @abstract
     */
    updateLayoutProtected: function( leftEdge, rightEdge, centerY ) {
      throw new Error( 'updateLayout must be implemented by subtypes' );
    }
  }, {

    // Default font for things text that appears in the status bar subtypes
    DEFAULT_FONT: DEFAULT_FONT
  } );
} );
 <commit_msg>var -> const in require statements, use arrow function in define require call, https://github.com/phetsims/tasks/issues/1010<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Base type for the status bar that appears at the top of games.
 * The base type is primarily responsible for resizing and 'floating' the bar.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const inherit = require( 'PHET_CORE/inherit' );
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const vegas = require( 'VEGAS/vegas' );

  // constants
  var DEFAULT_FONT = new PhetFont( 20 );

  /**
   * @param {Bounds2} layoutBounds
   * @param {Property.<Bounds2>} visibleBoundsProperty - visible bounds of the parent ScreenView
   * @param {Object} [options]
   * @constructor
   */
  function StatusBar( layoutBounds, visibleBoundsProperty, options ) {

    var self = this;

    options = _.extend( {
      barHeight: 50,
      xMargin: 10,
      yMargin: 8,
      barFill: 'lightGray',
      barStroke: null,

      // true: float bar to top of visible bounds; false: bar at top of layoutBounds
      floatToTop: false,

      // true: keeps things on the status bar aligned with left and right edges of window bounds
      // false: align things on status bar with left and right edges of static layoutBounds
      dynamicAlignment: true

    }, options );

    // @private
    this.layoutBounds = layoutBounds;
    this.xMargin = options.xMargin;
    this.yMargin = options.yMargin;
    this.dynamicAlignment = options.dynamicAlignment;

    // @private size will be set by visibleBoundsListener
    this.barNode = new Rectangle( {
      fill: options.barFill,
      stroke: options.barStroke
    } );

    // Support decoration, with the bar behind everything else
    options.children = [ this.barNode ].concat( options.children || [] );

    Node.call( this, options );

    var visibleBoundsListener = function( visibleBounds ) {

      // resize the bar
      var y = ( options.floatToTop ) ? visibleBounds.top : layoutBounds.top;
      self.barNode.setRect( visibleBounds.minX, y, visibleBounds.width, options.barHeight );

      // update layout of things on the bar
      self.updateLayout();
    };
    visibleBoundsProperty.link( visibleBoundsListener );

    // @private
    this.disposeStatusBar = function() {
      if ( visibleBoundsProperty.hasListener( visibleBoundsListener ) ) {
        visibleBoundsProperty.unlink( visibleBoundsListener );
      }
    };
  }

  vegas.register( 'StatusBar', StatusBar );

  return inherit( Node, StatusBar, {

    /**
     * @public
     * @override
     */
    dispose: function() {
      this.disposeStatusBar();
      Node.prototype.dispose.call( this );
    },

    /**
     * Updates the layout of things on the bar.
     * @protected
     */
    updateLayout: function() {
      var leftEdge = ( ( this.dynamicAlignment ) ? this.barNode.left : this.layoutBounds.minX ) + this.xMargin;
      var rightEdge = ( ( this.dynamicAlignment ) ? this.barNode.right : this.layoutBounds.maxX ) - this.xMargin;
      this.updateLayoutProtected( leftEdge, rightEdge, this.barNode.centerY );
    },

    /**
     * Layout that is specific to subtypes.
     * @param {number} leftEdge - the bar's left edge, compensated for xMargin
     * @param {number} rightEdge - the bar's right edge, compensated for xMargin
     * @param {number} centerY - the bar's vertical center
     * @protected
     * @abstract
     */
    updateLayoutProtected: function( leftEdge, rightEdge, centerY ) {
      throw new Error( 'updateLayout must be implemented by subtypes' );
    }
  }, {

    // Default font for things text that appears in the status bar subtypes
    DEFAULT_FONT: DEFAULT_FONT
  } );
} );
 <|endoftext|>


<commit_before>// Copyright (c) 2015 Walter Bender
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// You should have received a copy of the GNU General Public License
// along with this library; if not, write to the Free Software
// Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA

// Based on ta-stats.py by Walter Bender

// TODO: CLEAN UP THIS LIST

TACAT = {'clear': 'forward', 'forward': 'forward', 'back': 'forward',
         'left': 'forward', 'right': 'forward', 'arc': 'arc',
         'x': 'coord', 'y': 'coord', 'heading': 'coord',
         'setxy': 'setxy', 'seth': 'setxy', 'penup': 'pen',
         'setpensize': 'pen', 'setcolor': 'pen', 'pensize': 'pen',
         'color': 'pen', 'setshade': 'pen', 'setgray': 'pen',
         'gray': 'pen', 'fillscreen': 'pen', 'startfill': 'fill',
         'stopfill': 'fill', 'plus': 'number', 'minus': 'number',
         'multiply': 'number', 'divide': 'number',
         'pendown': 'pen', 'shade': 'pen', 'mod': 'number',
         'sqrt': 'number', 'identity': 'number', 'and': 'boolean',
         'or': 'boolean', 'not': 'boolean', 'greater': 'boolean',
         'less': 'boolean', 'equal': 'boolean', 'random': 'random',
         'repeat': 'repeat', 'forever': 'repeat', 'if': 'ifthen',
         'ifthenelse': 'ifthen', 'while': 'ifthen', 'until': 'ifthen',
         'action': 'action', 'do': 'action', 'nameddo': 'action',
         'storein': 'box', 'namedbox': 'box', 'incrementOne': 'box',
         'luminance': 'sensor', 'mousex': 'sensor', 'mousey': 'sensor',
         'start': 'action', 'mousebutton': 'sensor', 'keyboard': 'sensor',
         'readpixel': 'sensor', 'see': 'sensor', 'time': 'sensor',
         'sound': 'sensor', 'volume': 'sensor', 'pitch': 'sensor',
         'resistance': 'sensor', 'voltage': 'sensor', 'video': 'media',
         'wait': 'media', 'camera': 'media', 'journal': 'media',
         'audio': 'media', 'show': 'media', 'setscale': 'media',
         'savepix': 'media', 'savesvg': 'media', 'mediawait': 'media',
         'mediapause': 'media', 'mediastop': 'media', 'mediaplay': 'media',
         'speak': 'media', 'sinewave': 'media', 'description': 'media',
         'push': 'extras', 'pop': 'extras', 'printheap': 'extras',
         'clearheap': 'extras', 'isheapempty': 'extras', 'chr': 'extras',
         'int': 'extras', 'myfunction': 'python', 'userdefined': 'python',
         'box': 'box', 'kbinput': 'sensor', 'showHeap': 'extras',
         'loadblock': 'python', 'loadpalette': 'python'};

TAPAL = {'forward': 'turtlep', 'arc': 'turtlep', 'coord': 'turtlep',
         'setxy': 'turtlep', 'pen': 'penp', 'fill': 'penp',
         'random': 'numberp', 'boolean': 'numberp', 'repeat': 'flowp',
         'ifthen': 'flowp', 'action': 'boxp', 'box': 'boxp',
         'sensor': 'sensorp', 'media': 'mediap', 'extras': 'extrasp',
         'number': 'numberp', 'python': 'extrasp'};

TASCORE = {'forward': 3, 'arc': 3, 'setxy': 2.5, 'coord': 4, 'turtlep': 5,
           'pen': 2.5, 'fill': 2.5, 'penp': 5,
           'number': 2.5, 'boolean': 2.5, 'random': 2.5, 'numberp': 0,
           'repeat': 2.5, 'ifthen': 7.5, 'flowp': 10,
           'box': 7.5, 'action': 7.5, 'boxp': 0,
           'media': 5, 'mediap': 0,
           'python': 5, 'extras': 5, 'extrasp': 0,
           'sensor': 5, 'sensorp': 0};

PALS = ['turtlep', 'penp', 'numberp', 'flowp', 'boxp', 'sensorp', 'mediap',
        'extrasp'];

function analyzeProject(blocks) {
    // Parse block data and generate score based on rubric

    var blockList = [];
    for (blk = 0; blk < blocks.blockList.length; blk++) {
        blockList.push(blocks.blockList[blk].name);
    }

    scores = [];
    for (i = 0; i < PALS.length; i++) {
        scores.push(0);
    }
    cats = [];
    pals = [];

    for (b = 0; b < blockList.length; b++) {
        if (blockList[b] in TACAT) {
            if (!(TACAT[blockList[b]] in cats)) {
                cats.push(TACAT[blockList[b]]);
            }
        } else {
           console.log(blockList[b] + ' not in catelog');
        }
    }
    for (c = 0; c < cats.length; c++) {
        if (cats[c] in TAPAL) {
            if (!(TAPAL[cats[c]] in pals)) {
                pals.push(TAPAL[cats[c]]);
            }
        }
    }

    for (c = 0; c < cats.length; c++) {
        if (cats[c] in TASCORE) {
            scores[PALS.indexOf(TAPAL[cats[c]])] += TASCORE[cats[c]];
        }
    }

    for (p = 0; p < pals.length; p++) {
        if (pals[p] in TASCORE) {
            scores[PALS.indexOf(pals[p])] += TASCORE[pals[p]];
        }
    }

    return scores;
}

function scoreToChartData(scores) {
    var data = {
        labels: [_('turtle'), _('pen'), _('number'), _('flow'), _('box'), _('sensors'), _('media'), _('extras')],
        datasets: [
            {
                label: "My First dataset",
                fillColor: "rgba(220,220,220,0.2)",
                strokeColor: "rgba(220,220,220,1)",
                pointColor: "rgba(220,220,220,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(220,220,220,1)",
                data: scores,
            },
        ]
    };

    return data;
}

function getChartOptions(callback) {
    return {
    // Callback for rendering chart into a bitmap
    onAnimationComplete: callback,

    //Boolean - Whether to show lines for each scale point
    scaleShowLine : true,

    //Boolean - Whether we show the angle lines out of the radar
    angleShowLineOut : true,

    //Boolean - Whether to show labels on the scale
    scaleShowLabels : false,

    // Boolean - Whether the scale should begin at zero
    scaleBeginAtZero : true,

    //String - Colour of the angle line
    angleLineColor : "rgba(0,0,0,.1)",

    //Number - Pixel width of the angle line
    angleLineWidth : 10,

    //String - Point label font declaration
    pointLabelFontFamily : "'Arial'",

    //String - Point label font weight
    pointLabelFontStyle : "normal",

    //Number - Point label font size in pixels
    pointLabelFontSize : 30,

    //String - Point label font colour
    pointLabelFontColor : "#666",

    //Boolean - Whether to show a dot for each point
    pointDot : true,

    //Number - Radius of each point dot in pixels
    pointDotRadius : 18,

    //Number - Pixel width of point dot stroke
    pointDotStrokeWidth : 6,

    //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
    pointHitDetectionRadius : 20,

    //Boolean - Whether to show a stroke for datasets
    datasetStroke : true,

    //Number - Pixel width of dataset stroke
    datasetStrokeWidth : 12,

    //Boolean - Whether to fill the dataset with a colour
    datasetFill : true,

    //String - A legend template
    legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%>
<span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"

    }
}
<commit_msg>more tweaks to analytics<commit_after>// Copyright (c) 2015 Walter Bender
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// You should have received a copy of the GNU General Public License
// along with this library; if not, write to the Free Software
// Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA

// Based on ta-stats.py by Walter Bender

// TODO: CLEAN UP THIS LIST

TACAT = {'clear': 'forward', 'forward': 'forward', 'back': 'forward',
         'left': 'forward', 'right': 'forward', 'arc': 'arc',
         'x': 'coord', 'y': 'coord', 'heading': 'coord',
         'setxy': 'setxy', 'seth': 'setxy', 'penup': 'pen',
         'setpensize': 'pen', 'setcolor': 'pen', 'pensize': 'pen',
         'color': 'pen', 'setshade': 'pen', 'setgray': 'pen',
         'gray': 'pen', 'fillscreen': 'pen', 'beginfill': 'fill',
         'endfill': 'fill', 'plus': 'number', 'minus': 'number',
         'multiply': 'number', 'divide': 'number',
         'pendown': 'pen', 'shade': 'pen', 'mod': 'number',
         'sqrt': 'number', 'identity': 'number', 'and': 'boolean',
         'or': 'boolean', 'not': 'boolean', 'greater': 'boolean',
         'less': 'boolean', 'equal': 'boolean', 'random': 'random',
         'repeat': 'repeat', 'forever': 'repeat', 'if': 'ifthen',
         'ifthenelse': 'ifthen', 'while': 'ifthen', 'until': 'ifthen',
         'action': 'action', 'do': 'action', 'nameddo': 'action',
         'storein': 'box', 'namedbox': 'box', 'incrementOne': 'box',
         'luminance': 'sensor', 'mousex': 'sensor', 'mousey': 'sensor',
         'start': 'action', 'mousebutton': 'sensor', 'keyboard': 'sensor',
         'readpixel': 'sensor', 'see': 'sensor', 'time': 'sensor',
         'sound': 'sensor', 'volume': 'sensor', 'pitch': 'sensor',
         'resistance': 'sensor', 'voltage': 'sensor', 'video': 'media',
         'wait': 'media', 'camera': 'media', 'journal': 'media',
         'audio': 'media', 'show': 'media', 'setscale': 'media',
         'savepix': 'media', 'savesvg': 'media', 'mediawait': 'media',
         'mediapause': 'media', 'mediastop': 'media', 'mediaplay': 'media',
         'speak': 'media', 'sinewave': 'media', 'description': 'media',
         'push': 'extras', 'pop': 'extras', 'printheap': 'extras',
         'clearheap': 'extras', 'isheapempty': 'extras', 'chr': 'extras',
         'int': 'number', 'myfunction': 'python', 'userdefined': 'python',
         'box': 'box', 'kbinput': 'sensor', 'showHeap': 'extras',
         'emptyHeap': 'extras',
         'loadblock': 'python', 'loadpalette': 'python',
         'text': 'ignore', 'number': 'ignore', 'vspace': 'ignore'};

TAPAL = {'forward': 'turtlep', 'arc': 'turtlep', 'coord': 'turtlep',
         'setxy': 'turtlep', 'pen': 'penp', 'fill': 'penp',
         'random': 'numberp', 'boolean': 'numberp', 'repeat': 'flowp',
         'ifthen': 'flowp', 'action': 'boxp', 'box': 'boxp',
         'sensor': 'sensorp', 'media': 'mediap', 'extras': 'extrasp',
         'number': 'numberp', 'python': 'extrasp', 'ignore': 'numberp'};

TASCORE = {'forward': 3, 'arc': 3, 'setxy': 2.5, 'coord': 4, 'turtlep': 5,
           'pen': 2.5, 'fill': 2.5, 'penp': 5,
           'number': 2.5, 'boolean': 2.5, 'random': 2.5, 'numberp': 0,
           'repeat': 2.5, 'ifthen': 7.5, 'flowp': 10,
           'box': 7.5, 'action': 7.5, 'boxp': 0,
           'media': 5, 'mediap': 0,
           'python': 5, 'extras': 5, 'extrasp': 0, 'ignore': 0,
           'sensor': 5, 'sensorp': 0};

PALS = ['turtlep', 'penp', 'numberp', 'flowp', 'boxp', 'sensorp', 'mediap',
        'extrasp'];

function analyzeProject(blocks) {
    // Parse block data and generate score based on rubric

    var blockList = [];
    for (blk = 0; blk < blocks.blockList.length; blk++) {
        blockList.push(blocks.blockList[blk].name);
    }

    scores = [];
    for (i = 0; i < PALS.length; i++) {
        scores.push(0);
    }
    cats = [];
    pals = [];

    for (b = 0; b < blockList.length; b++) {
        if (blockList[b] in TACAT) {
            if (!(TACAT[blockList[b]] in cats)) {
                cats.push(TACAT[blockList[b]]);
            }
        } else {
           console.log(blockList[b] + ' not in catelog');
        }
    }
    for (c = 0; c < cats.length; c++) {
        if (cats[c] in TAPAL) {
            if (!(TAPAL[cats[c]] in pals)) {
                pals.push(TAPAL[cats[c]]);
            }
        }
    }

    for (c = 0; c < cats.length; c++) {
        if (cats[c] in TASCORE) {
            scores[PALS.indexOf(TAPAL[cats[c]])] += TASCORE[cats[c]];
        }
    }

    for (p = 0; p < pals.length; p++) {
        if (pals[p] in TASCORE) {
            scores[PALS.indexOf(pals[p])] += TASCORE[pals[p]];
        }
    }

    return scores;
}

function scoreToChartData(scores) {
    var data = {
        labels: [_('turtle'), _('pen'), _('number'), _('flow'), _('box'), _('sensors'), _('media'), _('extras')],
        datasets: [
            {
                label: "My First dataset",
                fillColor: "rgba(220,220,220,0.2)",
                strokeColor: "rgba(220,220,220,1)",
                pointColor: "rgba(220,220,220,1)",
                pointStrokeColor: "#fff",
                pointHighlightFill: "#fff",
                pointHighlightStroke: "rgba(220,220,220,1)",
                data: scores,
            },
        ]
    };

    return data;
}

function getChartOptions(callback) {
    return {
    // Callback for rendering chart into a bitmap
    onAnimationComplete: callback,

    //Boolean - Whether to show lines for each scale point
    scaleShowLine : true,

    //Boolean - Whether we show the angle lines out of the radar
    angleShowLineOut : true,

    //Boolean - Whether to show labels on the scale
    scaleShowLabels : false,

    // Boolean - Whether the scale should begin at zero
    scaleBeginAtZero : true,

    //String - Colour of the angle line
    angleLineColor : "rgba(0,0,0,.1)",

    //Number - Pixel width of the angle line
    angleLineWidth : 10,

    //String - Point label font declaration
    pointLabelFontFamily : "'Arial'",

    //String - Point label font weight
    pointLabelFontStyle : "normal",

    //Number - Point label font size in pixels
    pointLabelFontSize : 30,

    //String - Point label font colour
    pointLabelFontColor : "#666",

    //Boolean - Whether to show a dot for each point
    pointDot : true,

    //Number - Radius of each point dot in pixels
    pointDotRadius : 18,

    //Number - Pixel width of point dot stroke
    pointDotStrokeWidth : 6,

    //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
    pointHitDetectionRadius : 20,

    //Boolean - Whether to show a stroke for datasets
    datasetStroke : true,

    //Number - Pixel width of dataset stroke
    datasetStrokeWidth : 12,

    //Boolean - Whether to fill the dataset with a colour
    datasetFill : true,

    //String - A legend template
    legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%>
<span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"

    }
}
<|endoftext|>


<commit_before>/*

Jappix - An open social platform
These are the anonymous mode JS script for Jappix

-------------------------------------------------

License: AGPL
Authors: Valérian Saliou, Emmanuel Gil Peyrot
Contact: http://project.jappix.com/contact
Last revision: 21/11/10

*/

// Connected to an anonymous session
function anonymousConnected(con) {
	logThis('Jappix (anonymous) is now connected.', 3);

	// Connected marker
	CURRENT_SESSION = true;

	// Not resumed?
	if(!RESUME) {
		// Create the app
		createTalkPage();

		// Send our first presence
		firstPresence('');

		// If no domain is defined, we assume this must be ours
		if(ANONYMOUS_ROOM.indexOf('@') == -1)
			ANONYMOUS_ROOM = ANONYMOUS_ROOM + '@' + HOST_MUC;

		// Create the new groupchat
		checkChatCreate(ANONYMOUS_ROOM, 'groupchat');

		// Remove some nasty elements for the anonymous mode
		$('.tools-mucadmin, .tools-add').remove();
	}

	// Resumed
	else {
		presenceSend();

		// Change the title
		pageTitle('talk');
	}

	// Remove the waiting icon
	removeGeneralWait();
}

// Disconnected from an anonymous session
function anonymousDisconnected() {
	logThis('Jappix (anonymous) is now disconnected.', 3);

	// Reconnect pane
	if(CURRENT_SESSION)
		createReconnect('anonymous');
}

// Logins to a anonymous account
function anonymousLogin(server) {
	try {
		// We define the http binding parameters
		oArgs = new Object();
		oArgs.httpbase = HOST_BOSH;
		oArgs.timerval = 2000;

		// We create the new http-binding connection
		con = new JSJaCHttpBindingConnection(oArgs);

		// And we handle everything that happen
		con.registerHandler('message', handleMessage);
		con.registerHandler('presence', handlePresence);
		con.registerHandler('iq', handleIQ);
		con.registerHandler('onconnect', anonymousConnected);
		con.registerHandler('onerror', handleError);
		con.registerHandler('ondisconnect', anonymousDisconnected);

		// We set the anonymous connection parameters
		oArgs = new Object();
		oArgs.domain = server;
		oArgs.authtype = 'saslanon';
		oArgs.resource = JAPPIX_RESOURCE + ' Anonymous';
		oArgs.secure = true;

		// We connect !
		con.connect(oArgs);

		// Change the page title
		pageTitle('wait');
	}

	catch(e) {
		// Logs errors
		logThis('Error while anonymous loggin in: ' + e, 1);

		// Reset Jappix
		anonymousDisconnected();

		// Open an unknown error
		openThisError(2);
	}

	finally {
		return false;
	}
}

// Plugin launcher
function launchAnonymous() {
	logThis('Anonymous mode detected, connecting...', 3);

	// We add the login wait div
	showGeneralWait();

	// Fire the login action
	anonymousLogin(HOST_ANONYMOUS);
}

// Launch this plugin!
$(document).ready(launchAnonymous);

var anon_params = (function() {
	var vars = [], hash;
	var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');

	for(var i = 0; i < hashes.length; i++) {
		var hash = hashes[i].split('=');
		vars.push(hash[0]);
		vars[hash[0]] = hash[1];
	}

	return vars;
})();

if(anon_params['r'] && anon_params['n'] !== '')
	ANONYMOUS_ROOM = anon_params['r'];

if(anon_params['n'] && anon_params['n'] !== '')
	ANONYMOUS_NICK = anon_params['n'];
<commit_msg>Unescape for anonymous URI.<commit_after>/*

Jappix - An open social platform
These are the anonymous mode JS script for Jappix

-------------------------------------------------

License: AGPL
Authors: Valérian Saliou, Emmanuel Gil Peyrot
Contact: http://project.jappix.com/contact
Last revision: 22/11/10

*/

// Connected to an anonymous session
function anonymousConnected(con) {
	logThis('Jappix (anonymous) is now connected.', 3);

	// Connected marker
	CURRENT_SESSION = true;

	// Not resumed?
	if(!RESUME) {
		// Create the app
		createTalkPage();

		// Send our first presence
		firstPresence('');

		// If no domain is defined, we assume this must be ours
		if(ANONYMOUS_ROOM.indexOf('@') == -1)
			ANONYMOUS_ROOM = ANONYMOUS_ROOM + '@' + HOST_MUC;

		// Create the new groupchat
		checkChatCreate(ANONYMOUS_ROOM, 'groupchat');

		// Remove some nasty elements for the anonymous mode
		$('.tools-mucadmin, .tools-add').remove();
	}

	// Resumed
	else {
		presenceSend();

		// Change the title
		pageTitle('talk');
	}

	// Remove the waiting icon
	removeGeneralWait();
}

// Disconnected from an anonymous session
function anonymousDisconnected() {
	logThis('Jappix (anonymous) is now disconnected.', 3);

	// Reconnect pane
	if(CURRENT_SESSION)
		createReconnect('anonymous');
}

// Logins to a anonymous account
function anonymousLogin(server) {
	try {
		// We define the http binding parameters
		oArgs = new Object();
		oArgs.httpbase = HOST_BOSH;
		oArgs.timerval = 2000;

		// We create the new http-binding connection
		con = new JSJaCHttpBindingConnection(oArgs);

		// And we handle everything that happen
		con.registerHandler('message', handleMessage);
		con.registerHandler('presence', handlePresence);
		con.registerHandler('iq', handleIQ);
		con.registerHandler('onconnect', anonymousConnected);
		con.registerHandler('onerror', handleError);
		con.registerHandler('ondisconnect', anonymousDisconnected);

		// We set the anonymous connection parameters
		oArgs = new Object();
		oArgs.domain = server;
		oArgs.authtype = 'saslanon';
		oArgs.resource = JAPPIX_RESOURCE + ' Anonymous';
		oArgs.secure = true;

		// We connect !
		con.connect(oArgs);

		// Change the page title
		pageTitle('wait');
	}

	catch(e) {
		// Logs errors
		logThis('Error while anonymous loggin in: ' + e, 1);

		// Reset Jappix
		anonymousDisconnected();

		// Open an unknown error
		openThisError(2);
	}

	finally {
		return false;
	}
}

// Plugin launcher
function launchAnonymous() {
	logThis('Anonymous mode detected, connecting...', 3);

	// We add the login wait div
	showGeneralWait();

	// Fire the login action
	anonymousLogin(HOST_ANONYMOUS);
}

// Launch this plugin!
$(document).ready(launchAnonymous);

var anon_params = (function() {
	var vars = [], hash;
	var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');

	for(var i = 0; i < hashes.length; i++) {
		var hash = hashes[i].split('=');
		vars.push(hash[0]);
		vars[hash[0]] = hash[1];
	}

	return vars;
})();

if(anon_params['r'] && anon_params['n'] !== '')
	ANONYMOUS_ROOM = unescape(anon_params['r']);

if(anon_params['n'] && anon_params['n'] !== '')
	ANONYMOUS_NICK = unescape(anon_params['n']);
<|endoftext|>