<commit_before>const _ = require('lodash');
const logging = require('../../../shared/logging');
const labsService = require('../labs');
const membersService = require('./index');
const urlUtils = require('../../../shared/url-utils');
const ghostVersion = require('../../lib/ghost-version');
const settingsCache = require('../settings/cache');
const {formattedMemberResponse} = require('./utils');

// @TODO: This piece of middleware actually belongs to the frontend, not to the member app
// Need to figure a way to separate these things (e.g. frontend actually talks to members API)
const loadMemberSession = async function (req, res, next) {
    if (!labsService.isSet('members')) {
        req.member = null;
        return next();
    }
    try {
        const member = await membersService.ssr.getMemberDataFromSession(req, res);
        Object.assign(req, {member});
        res.locals.member = req.member;
        next();
    } catch (err) {
        logging.warn(err.message);
        Object.assign(req, {member: null});
        next();
    }
};

const getIdentityToken = async function (req, res) {
    try {
        const token = await membersService.ssr.getIdentityTokenForMemberFromSession(req, res);
        res.writeHead(200);
        res.end(token);
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const deleteSession = async function (req, res) {
    try {
        await membersService.ssr.deleteSession(req, res);
        res.writeHead(204);
        res.end();
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const getMemberData = async function (req, res) {
    try {
        const member = await membersService.ssr.getMemberDataFromSession(req, res);
        if (member) {
            res.json(formattedMemberResponse(member));
        } else {
            res.json(null);
        }
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const updateMemberData = async function (req, res) {
    try {
        const data = _.pick(req.body, 'name', 'subscribed');
        const member = await membersService.ssr.getMemberDataFromSession(req, res);
        if (member) {
            const options = {
                id: member.id,
                withRelated: ['stripeSubscriptions', 'stripeSubscriptions.customer']
            };
            const updatedMember = await membersService.api.members.update(data, options);

            res.json(formattedMemberResponse(updatedMember.toJSON()));
        } else {
            res.json(null);
        }
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const getMemberSiteData = async function (req, res) {
    const isStripeConfigured = membersService.config.isStripeConnected();
    const domain = urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
    const blogDomain = domain && domain[1];
    let supportAddress = settingsCache.get('members_support_address') || 'noreply';
    if (!supportAddress.includes('@')) {
        supportAddress = `${supportAddress}@${blogDomain}`;
    }
    const response = {
        title: settingsCache.get('title'),
        description: settingsCache.get('description'),
        logo: settingsCache.get('logo'),
        icon: settingsCache.get('icon'),
        accent_color: settingsCache.get('accent_color'),
        url: urlUtils.urlFor('home', true),
        version: ghostVersion.safe,
        plans: membersService.config.getPublicPlans(),
        allow_self_signup: membersService.config.getAllowSelfSignup(),
        is_stripe_configured: isStripeConfigured,
        portal_button: settingsCache.get('portal_button'),
        portal_name: settingsCache.get('portal_name'),
        portal_plans: settingsCache.get('portal_plans'),
        portal_button_icon: settingsCache.get('portal_button_icon'),
        portal_button_signup_text: settingsCache.get('portal_button_signup_text'),
        portal_button_style: settingsCache.get('portal_button_style'),
        members_support_address: supportAddress
    };

    res.json({site: response});
};

const createSessionFromMagicLink = async function (req, res, next) {
    if (!req.url.includes('token=')) {
        return next();
    }

    // req.query is a plain object, copy it to a URLSearchParams object so we can call toString()
    const searchParams = new URLSearchParams('');
    Object.keys(req.query).forEach((param) => {
        // don't copy the token param
        if (param !== 'token') {
            searchParams.set(param, req.query[param]);
        }
    });

    try {
        const member = await membersService.ssr.exchangeTokenForSession(req, res);
        const subscriptions = member && member.stripe && member.stripe.subscriptions || [];

        const action = req.query.action || req.query['portal-action'];

        if (action === 'signup') {
            let customRedirect = '';
            if (subscriptions.find(sub => ['active', 'trialing'].includes(sub.status))) {
                customRedirect = settingsCache.get('members_paid_signup_redirect') || '';
            } else {
                customRedirect = settingsCache.get('members_free_signup_redirect') || '';
            }

            if (customRedirect && customRedirect !== '/') {
                const baseUrl = urlUtils.getSiteUrl();
                const ensureEndsWith = (string, endsWith) => (string.endsWith(endsWith) ? string : string + endsWith);
                const removeLeadingSlash = string => string.replace(/^\//, '');

                const redirectUrl = new URL(removeLeadingSlash(ensureEndsWith(customRedirect, '/')), ensureEndsWith(baseUrl, '/'));

                return res.redirect(redirectUrl.href);
            }
        }

        // Do a standard 302 redirect to the homepage, with success=true
        searchParams.set('success', true);
        res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);
    } catch (err) {
        logging.warn(err.message);

        // Do a standard 302 redirect to the homepage, with success=false
        searchParams.set('success', false);
        res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);
    }
};

// Set req.member & res.locals.member if a cookie is set
module.exports = {
    loadMemberSession,
    createSessionFromMagicLink,
    getIdentityToken,
    getMemberData,
    updateMemberData,
    getMemberSiteData,
    deleteSession,
    stripeWebhooks: (req, res, next) => membersService.api.middleware.handleStripeWebhook(req, res, next)
};
<commit_msg>Added firstpromoter id to member site data<commit_after>const _ = require('lodash');
const logging = require('../../../shared/logging');
const labsService = require('../labs');
const membersService = require('./index');
const urlUtils = require('../../../shared/url-utils');
const ghostVersion = require('../../lib/ghost-version');
const settingsCache = require('../settings/cache');
const {formattedMemberResponse} = require('./utils');

// @TODO: This piece of middleware actually belongs to the frontend, not to the member app
// Need to figure a way to separate these things (e.g. frontend actually talks to members API)
const loadMemberSession = async function (req, res, next) {
    if (!labsService.isSet('members')) {
        req.member = null;
        return next();
    }
    try {
        const member = await membersService.ssr.getMemberDataFromSession(req, res);
        Object.assign(req, {member});
        res.locals.member = req.member;
        next();
    } catch (err) {
        logging.warn(err.message);
        Object.assign(req, {member: null});
        next();
    }
};

const getIdentityToken = async function (req, res) {
    try {
        const token = await membersService.ssr.getIdentityTokenForMemberFromSession(req, res);
        res.writeHead(200);
        res.end(token);
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const deleteSession = async function (req, res) {
    try {
        await membersService.ssr.deleteSession(req, res);
        res.writeHead(204);
        res.end();
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const getMemberData = async function (req, res) {
    try {
        const member = await membersService.ssr.getMemberDataFromSession(req, res);
        if (member) {
            res.json(formattedMemberResponse(member));
        } else {
            res.json(null);
        }
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const updateMemberData = async function (req, res) {
    try {
        const data = _.pick(req.body, 'name', 'subscribed');
        const member = await membersService.ssr.getMemberDataFromSession(req, res);
        if (member) {
            const options = {
                id: member.id,
                withRelated: ['stripeSubscriptions', 'stripeSubscriptions.customer']
            };
            const updatedMember = await membersService.api.members.update(data, options);

            res.json(formattedMemberResponse(updatedMember.toJSON()));
        } else {
            res.json(null);
        }
    } catch (err) {
        logging.warn(err.message);
        res.writeHead(err.statusCode);
        res.end(err.message);
    }
};

const getMemberSiteData = async function (req, res) {
    const isStripeConfigured = membersService.config.isStripeConnected();
    const domain = urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
    const firstpromoterId = settingsCache.get('firstpromoter') ? settingsCache.get('firstpromoter_id') : '';
    const blogDomain = domain && domain[1];
    let supportAddress = settingsCache.get('members_support_address') || 'noreply';
    if (!supportAddress.includes('@')) {
        supportAddress = `${supportAddress}@${blogDomain}`;
    }
    const response = {
        title: settingsCache.get('title'),
        description: settingsCache.get('description'),
        logo: settingsCache.get('logo'),
        icon: settingsCache.get('icon'),
        accent_color: settingsCache.get('accent_color'),
        url: urlUtils.urlFor('home', true),
        version: ghostVersion.safe,
        plans: membersService.config.getPublicPlans(),
        allow_self_signup: membersService.config.getAllowSelfSignup(),
        is_stripe_configured: isStripeConfigured,
        portal_button: settingsCache.get('portal_button'),
        portal_name: settingsCache.get('portal_name'),
        portal_plans: settingsCache.get('portal_plans'),
        portal_button_icon: settingsCache.get('portal_button_icon'),
        portal_button_signup_text: settingsCache.get('portal_button_signup_text'),
        portal_button_style: settingsCache.get('portal_button_style'),
        firstpromoter_id: firstpromoterId,
        members_support_address: supportAddress
    };

    res.json({site: response});
};

const createSessionFromMagicLink = async function (req, res, next) {
    if (!req.url.includes('token=')) {
        return next();
    }

    // req.query is a plain object, copy it to a URLSearchParams object so we can call toString()
    const searchParams = new URLSearchParams('');
    Object.keys(req.query).forEach((param) => {
        // don't copy the token param
        if (param !== 'token') {
            searchParams.set(param, req.query[param]);
        }
    });

    try {
        const member = await membersService.ssr.exchangeTokenForSession(req, res);
        const subscriptions = member && member.stripe && member.stripe.subscriptions || [];

        const action = req.query.action || req.query['portal-action'];

        if (action === 'signup') {
            let customRedirect = '';
            if (subscriptions.find(sub => ['active', 'trialing'].includes(sub.status))) {
                customRedirect = settingsCache.get('members_paid_signup_redirect') || '';
            } else {
                customRedirect = settingsCache.get('members_free_signup_redirect') || '';
            }

            if (customRedirect && customRedirect !== '/') {
                const baseUrl = urlUtils.getSiteUrl();
                const ensureEndsWith = (string, endsWith) => (string.endsWith(endsWith) ? string : string + endsWith);
                const removeLeadingSlash = string => string.replace(/^\//, '');

                const redirectUrl = new URL(removeLeadingSlash(ensureEndsWith(customRedirect, '/')), ensureEndsWith(baseUrl, '/'));

                return res.redirect(redirectUrl.href);
            }
        }

        // Do a standard 302 redirect to the homepage, with success=true
        searchParams.set('success', true);
        res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);
    } catch (err) {
        logging.warn(err.message);

        // Do a standard 302 redirect to the homepage, with success=false
        searchParams.set('success', false);
        res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`);
    }
};

// Set req.member & res.locals.member if a cookie is set
module.exports = {
    loadMemberSession,
    createSessionFromMagicLink,
    getIdentityToken,
    getMemberData,
    updateMemberData,
    getMemberSiteData,
    deleteSession,
    stripeWebhooks: (req, res, next) => membersService.api.middleware.handleStripeWebhook(req, res, next)
};
<|endoftext|>


<commit_before>import * as ShuffleNetV2_MobileNetV2_Block from "../Layer/ShuffleNetV2_MobileNetV2_Block.js";

export { Base };

/**
 * A neural network composes of multiple blocks.
 *
 * A special recommended configuration is 3x3 ShuffleNetV2 without (explicit) bias:
 *   - bChannelShuffler: true
 *   - pointwise1ChannelCountRate: 1
 *   - strAvgMaxConv: "Conv"
 *   - depthwiseFilterHeight: 3
 *   - depthwiseChannelMultiplierBlock0Step0: 1
 *   - bBias: false
 *   - strActivationName: "cos"
 *
 * The cosine activation function can achieve impilcit bias. This is because depthwise and pointwise convolution can easily achieve zero
 * and ( cos( 0 ) == 1 ). A constant value (e.g. 1) becomes an bias of the next convolution. So there is not necessary to use explicit bias
 * which has worse performance (than without it).
 *
 *
 *
 * @member {string} structure
 *   This neural network's structure.
 *
 * @member {number} totalChannelExpansionFactor
 *   The final output of this neural network will have ( totalChannelExpansionFactor * sourceChannelCount ) channel count.
 *
 * @member {number[]} sourceImageHeightWidth
 *   The size (i.e. [ height, width ]) of the source image. When apply_and_destroy_or_keep() is called, the input tensor should be this size.
 */
class Base {

  /**
   * @param {number} depthwiseChannelMultiplierBlock0Step0
   *   The depthwise convolution of the first step (Step 0) of the first block (Block 0) will expand input channel by this factor.
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep).
   *
   * @see ShuffleNetV2_MobileNetV2_Block.init
   */
  init(
    sourceHeight, sourceWidth, sourceChannelCount,
    stepCountPerBlock,
    bChannelShuffler,
    pointwise1ChannelCountRate,
    strAvgMaxConv, depthwiseFilterHeight, depthwiseChannelMultiplierBlock0Step0, bBias, strActivationName,
    bKeepInputTensor
  ) {

    this.disposeTensors();

    this.sourceChannelCount = sourceChannelCount;
    this.stepCountPerBlock = stepCountPerBlock;
    this.bChannelShuffler = bChannelShuffler;
    this.pointwise1ChannelCountRate = pointwise1ChannelCountRate;
    this.bKeepInputTensor = bKeepInputTensor;

    let targetHeight = 1; // The final output always has ( height x width ) = ( 1 x 1 ), i.e. only one pixel per channel.

    let differenceHeight = sourceHeight - targetHeight;
    let filterWidth = depthwiseFilterHeight;

    this.depthwiseChannelMultiplierBlock0Step0 = depthwiseChannelMultiplierBlock0Step0;
    this.channelCountBlock0 = sourceChannelCount * depthwiseChannelMultiplierBlock0Step0;  // the channel count of the first block (Block 0).

    if ( stepCountPerBlock <= 0 ) { // Not ShuffleNetV2, Not MobileNetV2.

      // The height of processed image will be reduced a little for any depthwise filter larger than 1x1.
      let heightReducedPerBlock = depthwiseFilterHeight - 1;

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 1, pad = "valid" ).
      this.blockCount = Math.floor( differenceHeight / heightReducedPerBlock );

      // Channel count only be expanded by channel multiplier of depthwise convolution of step 0 of block 0.
      this.totalChannelExpansionFactor = depthwiseChannelMultiplierBlock0Step0;

    } else {  // ShuffleNetV2 or MobileNetV2. Halven per block.

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 2, pad = "same" ).
//      this.blockCount = Math.floor( Math.log2( sourceHeight ) );
      this.blockCount = Math.ceil( Math.log2( sourceHeight ) );

//!!! wrong
//       // Channel count is expanded both by channel multiplier of depthwise convolution of step 0 of block 0
//       // and by every block (half height x width and double channel count).
//       this.totalChannelExpansionFactor = depthwiseChannelMultiplierBlock0Step0 * Math.pow( 2, this.blockCount );

      // Channel count is expanded by every block (half height x width and double channel count).
      this.totalChannelExpansionFactor = Math.pow( 2, this.blockCount );
    }

    let nextBlockInputChannelCount = sourceChannelCount;
    let nextBlockDepthwiseChannelMultiplier = depthwiseChannelMultiplierBlock0Step0; // Only block 0 can have ( depthwise channel multiplier > 1 ).

    let nextKeepInputTensor = bKeepInputTensor; // Only step 0 may or may not keep the input tensor according to caller's necessary.

    this.blocks = new Array( this.blockCount );
    for ( let i = 0; i < this.blockCount; ++i )
    {
      let block = new ShuffleNetV2_MobileNetV2_Block.Base();
      block.init(
        sourceHeight, sourceWidth, nextBlockInputChannelCount,
        stepCountPerBlock,
        bChannelShuffler,
        pointwise1ChannelCountRate,
        strAvgMaxConv, depthwiseFilterHeight, nextBlockDepthwiseChannelMultiplier, bBias, strActivationName,
        nextKeepInputTensor
      );

      this.blocks[ i ] = block;
      nextBlockInputChannelCount = block.outputChannelCount; // Using previous block's output channel count as next block's input channel count.
      nextBlockDepthwiseChannelMultiplier = 1;               // Except block 0, all other blocks' depthwise channel multiplier should be 1.
      nextKeepInputTensor = false;                           // All blocks (except block 0) should not keep (and should dispose) the input tensor.
    }

    let block0 = this.blocks[ 0 ];

    // e.g. "C24_24__DConv_101x101_DBias_RELU__PConv_PBias_RELU__Block_1__Step_1"
    this.structure = `C${sourceChannelCount}_${this.channelCountBlock0}`

      + `${ ( block0.step0.bPointwise1 ) ? "__PConv1(x" + pointwise1ChannelCountRate + ")" : "" }`
      + `${ ( block0.step0.bPointwise1 && block0.step0.bPointwise1Bias ) ? ( "_PBias1" ) : "" }`
      + `${ ( block0.step0.bPointwise1 && block0.step0.pointwise1ActivationFunction ) ? ( "_" + block0.step0.pointwise1ActivationName ) : "" }`

      + `${ ( block0.step0.bDepthwise ) ? `__D${strAvgMaxConv}_${depthwiseFilterHeight}x${depthwiseFilterHeight}` : "" }`
      + `${ ( block0.step0.bDepthwise && block0.step0.bDepthwiseBias ) ? ( "_DBias" ) : "" }`
      + `${ ( block0.step0.bDepthwise && block0.step0.depthwiseActivationFunction ) ? ( "_" + strActivationName ) : "" }`

      + `${ ( block0.step0.bPointwise2 ) ? "__PConv2" : "" }`
      + `${ ( block0.step0.bPointwise2 && block0.step0.bPointwise2Bias ) ? ( "_PBias2" ) : "" }`
      + `${ ( block0.step0.bPointwise2 && block0.step0.pointwise2ActivationFunction ) ? ( "_" + block0.step0.pointwise2ActivationName ) : "" }`

      + `__Block_${this.blockCount}`
      + `__Step_${stepCountPerBlock}`
      + `${ ( bChannelShuffler ) ? "__Shuffle" : ( ( stepCountPerBlock > 0 ) ? "__AddInput" : "" ) }`
      //+ `${ ( bKeepInputTensor ) ? "__KeepInput" : "" }`
    ;

    this.sourceImageHeightWidth = [ block0.sourceHeight, block0.sourceWidth ];
  }

  disposeTensors() {
    if ( this.blocks ) {
      for ( let i = 0; i < this.blocks.length; ++i ) {
        let block = this.blocks[ i ];
        block.disposeTensors();
      }
      this.blocks = null;
    }
  }

  /**
   *
   * @param {tf.tensor4d} inputTensor
   *   The image which will be processed. This inputTensor may or may not be disposed according to the init()'s bKeepInputTensor.
   * Its size should be this.sourceImageHeightWidth.
   *
   * @param {boolean} bReturn
   *   If true, the result tensor will be returned. Otherwise, the result tensor will be disposed.
   *
   * @return {tf.tensor4d}
   *   If ( bReturn == true ), return the result (new) tensor. Otheriwse, the result tensor will be disposed and nothing will be returned.
   */
  apply_and_destroy_or_keep( inputTensor, bReturn ) {
    let t = inputTensor;

    let block;
    for ( let i = 0; i < this.blocks.length; ++i ) {
      block = this.blocks[ i ];
      t = block.apply_and_destroy_or_keep( t );
    }

    if ( bReturn )
      return t;
    else
      t.dispose();
  }

}
<commit_msg>Update NeuralNetwork.js<commit_after>import * as ShuffleNetV2_MobileNetV2_Block from "../Layer/ShuffleNetV2_MobileNetV2_Block.js";

export { Base };

/**
 * A neural network composes of multiple blocks.
 *
 * A special recommended configuration is 3x3 ShuffleNetV2 without (explicit) bias:
 *   - bChannelShuffler: true
 *   - pointwise1ChannelCountRate: 1
 *   - strAvgMaxConv: "Conv"
 *   - depthwiseFilterHeight: 3
 *   - depthwiseChannelMultiplierBlock0Step0: 1
 *   - bBias: false
 *   - strActivationName: "cos"
 *
 * The cosine activation function can achieve impilcit bias. This is because depthwise and pointwise convolution can easily achieve zero
 * and ( cos( 0 ) == 1 ). A constant value (e.g. 1) becomes an bias of the next convolution. So there is not necessary to use explicit bias
 * which has worse performance (than without it).
 *
 *
 *
 * @member {string} structure
 *   This neural network's structure.
 *
 * @member {number} totalChannelExpansionFactor
 *   The final output of this neural network will have ( totalChannelExpansionFactor * sourceChannelCount ) channel count.
 *
 * @member {number[]} sourceImageHeightWidth
 *   The size (i.e. [ height, width ]) of the source image. When apply_and_destroy_or_keep() is called, the input tensor should be this size.
 */
class Base {

  /**
   * @param {number} depthwiseChannelMultiplierBlock0Step0
   *   The depthwise convolution of the first step (Step 0) of the first block (Block 0) will expand input channel by this factor.
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep).
   *
   * @see ShuffleNetV2_MobileNetV2_Block.init
   */
  init(
    sourceHeight, sourceWidth, sourceChannelCount,
    stepCountPerBlock,
    bChannelShuffler,
    pointwise1ChannelCountRate,
    strAvgMaxConv, depthwiseFilterHeight, depthwiseChannelMultiplierBlock0Step0, bBias, strActivationName,
    bKeepInputTensor
  ) {

    this.disposeTensors();

    this.sourceChannelCount = sourceChannelCount;
    this.stepCountPerBlock = stepCountPerBlock;
    this.bChannelShuffler = bChannelShuffler;
    this.pointwise1ChannelCountRate = pointwise1ChannelCountRate;
    this.bKeepInputTensor = bKeepInputTensor;

    let targetHeight = 1; // The final output always has ( height x width ) = ( 1 x 1 ), i.e. only one pixel per channel.

    let differenceHeight = sourceHeight - targetHeight;
    let filterWidth = depthwiseFilterHeight;

    this.depthwiseChannelMultiplierBlock0Step0 = depthwiseChannelMultiplierBlock0Step0;
    this.channelCountBlock0 = sourceChannelCount * depthwiseChannelMultiplierBlock0Step0;  // the channel count of the first block (Block 0).

    if ( stepCountPerBlock <= 0 ) { // Not ShuffleNetV2, Not MobileNetV2.

      // The height of processed image will be reduced a little for any depthwise filter larger than 1x1.
      let heightReducedPerBlock = depthwiseFilterHeight - 1;

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 1, pad = "valid" ).
      this.blockCount = Math.floor( differenceHeight / heightReducedPerBlock );

      // Channel count only be expanded by channel multiplier of depthwise convolution of step 0 of block 0.
      this.totalChannelExpansionFactor = depthwiseChannelMultiplierBlock0Step0;

    } else {  // ShuffleNetV2 or MobileNetV2. Halven per block.

      // The block count for reducing sourceHeight to targetHeight by tf.depthwiseConv2d( strides = 2, pad = "same" ).
//      this.blockCount = Math.floor( Math.log2( sourceHeight ) );
      this.blockCount = Math.ceil( Math.log2( sourceHeight ) );

//!!! wrong
//       // Channel count is expanded both by channel multiplier of depthwise convolution of step 0 of block 0
//       // and by every block (half height x width and double channel count).
//       this.totalChannelExpansionFactor = depthwiseChannelMultiplierBlock0Step0 * Math.pow( 2, this.blockCount );

      // Channel count is expanded by every block (half height x width and double channel count).
      this.totalChannelExpansionFactor = Math.pow( 2, this.blockCount );
    }

    let nextBlockInputChannelCount = sourceChannelCount;
    let nextBlockDepthwiseChannelMultiplier = depthwiseChannelMultiplierBlock0Step0; // Only block 0 can have ( depthwise channel multiplier > 1 ).

    let nextKeepInputTensor = bKeepInputTensor; // Only step 0 may or may not keep the input tensor according to caller's necessary.

    this.blocks = new Array( this.blockCount );
    for ( let i = 0; i < this.blockCount; ++i )
    {
      let block = new ShuffleNetV2_MobileNetV2_Block.Base();
      block.init(
        sourceHeight, sourceWidth, nextBlockInputChannelCount,
        stepCountPerBlock,
        bChannelShuffler,
        pointwise1ChannelCountRate,
        strAvgMaxConv, depthwiseFilterHeight, nextBlockDepthwiseChannelMultiplier, bBias, strActivationName,
        nextKeepInputTensor
      );

      this.blocks[ i ] = block;
      nextBlockInputChannelCount = block.outputChannelCount; // Using previous block's output channel count as next block's input channel count.
      nextBlockDepthwiseChannelMultiplier = 1;               // Except block 0, all other blocks' depthwise channel multiplier should be 1.
      nextKeepInputTensor = false;                           // All blocks (except block 0) should not keep (and should dispose) the input tensor.
    }

    let block0 = this.blocks[ 0 ];

    // e.g. "C24_24__DConv_101x101_DBias_RELU__PConv_PBias_RELU__Block_1__Step_1"
    this.structure = `C${sourceChannelCount}_${this.channelCountBlock0}`

      + `${ ( block0.step0.bPointwise1 ) ? "__PConv1(x" + pointwise1ChannelCountRate + ")" : "" }`
      + `${ ( block0.step0.bPointwise1 && block0.step0.bPointwise1Bias ) ? ( "_PBias1" ) : "" }`
      + `${ ( block0.step0.bPointwise1 && block0.step0.pointwise1ActivationFunction ) ? ( "_" + block0.step0.pointwise1ActivationName ) : "" }`

      + `${ ( block0.step0.bDepthwise ) ? `__D${strAvgMaxConv}_${depthwiseFilterHeight}x${depthwiseFilterHeight}` : "" }`
      + `${ ( block0.step0.bDepthwise && block0.step0.bDepthwiseBias ) ? ( "_DBias" ) : "" }`
      + `${ ( block0.step0.bDepthwise && block0.step0.depthwiseActivationFunction ) ? ( "_" + strActivationName ) : "" }`

      + `${ ( block0.step0.bPointwise2 ) ? "__PConv2" : "" }`
      + `${ ( block0.step0.bPointwise2 && block0.step0.bPointwise2Bias ) ? ( "_PBias2" ) : "" }`
      + `${ ( block0.step0.bPointwise2 && block0.step0.pointwise2ActivationFunction ) ? ( "_" + block0.step0.pointwise2ActivationName ) : "" }`

      + `__Block_${this.blockCount}`
      + `__Step_${stepCountPerBlock}`
      + `${ ( bChannelShuffler ) ? "__Shuffle" : ( ( stepCountPerBlock > 0 ) ? "__AddInput" : "" ) }`
      //+ `${ ( bKeepInputTensor ) ? "__KeepInput" : "" }`
    ;

    this.sourceImageHeightWidth = [ block0.sourceHeight, block0.sourceWidth ];
  }

  disposeTensors() {
    if ( this.blocks ) {
      for ( let i = 0; i < this.blocks.length; ++i ) {
        let block = this.blocks[ i ];
        block.disposeTensors();
      }
      this.blocks = null;
    }
  }

  /**
   *
   * @param {tf.tensor3d} inputTensor
   *   The image which will be processed. This inputTensor may or may not be disposed according to init()'s bKeepInputTensor.
   * Its size should be this.sourceImageHeightWidth.
   *
   * @param {boolean} bReturn
   *   If true, the result tensor will be returned. Otherwise, the result tensor will be disposed.
   *
   * @return {tf.tensor3d}
   *   If ( bReturn == true ), return the result (new) tensor. If ( bReturn == false ), the result tensor will be disposed and nothing
   * will be returned. No matter in which case, all other intermediate tensors were disposed.
   */
  apply_and_destroy_or_keep( inputTensor, bReturn ) {
    let t = inputTensor;

    let block;
    for ( let i = 0; i < this.blocks.length; ++i ) {
      block = this.blocks[ i ];
      t = block.apply_and_destroy_or_keep( t );
    }

    if ( bReturn )
      return t;
    else
      t.dispose();
  }

}
<|endoftext|>


<commit_before>/**
 * @file This file is the main (i.e. body) javascript file of neural network web worker. It is not an importable module.
 *
 */

// In module (non-classic) web worker, static import is available. But at the same time, importScripts() will not be avbailable.
// For solving this problem, using classic (non-module) web worker so that tensorflow.js can be loaded by importScripts().
// At the same time, using dynamic import() to load ourselves module because import() can be used in classic (non-module) script.
//
//import * as Net from "./Net.js";

/**
 * The implementation of a neural network web worker.
 *
 */
class WorkerBody {

  /**
   *
   * @param {number} workerId
   *   A non-negative integer represents this worker's id. The id of the first worker should be 0.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   */
  init( workerId = 0, tensorflowJsURL, neuralNetConfig, weightsURL ) {

    if ( workerId < 0 )
      workerId = 0;

    this.workerId = workerId;
    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;

    let bKeepInputTensor = false; // Because every web worker will copy the input, there is not necessary to keep input.

//!!! ...unfinished... global scope ? report progress ?

    // If a specific library module does not existed, all libraries might have not yet been loaded. (i.e. this is the first time WorkerBody.init() is called.)
    if ( !globalThis.NeuralNetProgress ) {

      globalThis.NeuralNetProgress = await import( "./NetProgress.js" ); // Load progress library in globalThis.ValueMax scope dynamically.

      this.initProgress = new NeuralNetProgress.InitProgress(); // The progress object could be created only after the progress library has been loaded.
      this.initProgress.libraryDownload.total = 3; // There are 3 libraries should be loaded: NetProgress, tensorflow.js, NeuralNet.

      this.initProgress.weightsDownload.total = 1; // There are ??? 1 weights file will be loaded.

//!!! How to know this? According to neural net architecture? It will be known only after it has been downloaded.
      this.initProgress.weightsParse.total = 10000; // There are ??? weights will be parsed.

//!!! It seems that the libraryDownload and weightsDownload should not be included.
// Because the weightsParse.total is unknown when downloading library and weights, it not possible to set total of every progress at one time.

      ++this.initProgress.libraryDownload.accumulation; // The library NetProgress has been loaded.

//!!! ...unfinished... inform WorkerProxy progress changed.

      importScripts( tensorflowJsURL ); // Load tensorflow javascript library in global scope.
      ++this.initProgress.libraryDownload.accumulation; // The library tensorflow.js has been loaded.

      globalThis.NeuralNet = await import( "./Net.js" ); // Load neural network library in globalThis.NeuralNet scope dynamically.
      ++this.initProgress.libraryDownload.accumulation; // The library NeuralNet has been loaded.
    }

//!!! ...unfinished... the neuralNetConfig is still class NeuralNet.Config? Otherwise, the create_ScaledSourceTensor_from_ImageData_or_Canvas() will be lost.

    this.neuralNet = new NeuralNet.Base();
    this.neuralNet.init( neuralNetConfig, bKeepInputTensor );

//!!! ...unfinished...
    // Download and parse neural network weights. Also report downloading and parsing progress.

//       this.initProgress.weightsDownload;
//       this.initProgress.weightsParse;

  }

  disposeWorker() {
    if ( this.neuralNet ) {
      this.neuralNet.disposeTensors();
      this.neuralNet = null;
    }

//!!!??? calling close() when the next worker disposed ?
    close();
  }

//!!! Regular Expression for get text inside html table markup:
//
//  /(?<=
]*>.*)(?<=>)[^<]+(?=<)(?=.*
)/g
//
// It can be used to parsing weights data from Google Sheets published html. Problem: safari browser does not support lookbehind regular expression.

//!!! Regular Expression for get text inside html table data (with or without div) markup:
//
//  ]*>(?:
]*>)?([^<]*)(?:
)?
//
// The capture group 1 is one cell of one column of google sheet.

  /**
   * Convert source image data to tensor3d, scale it, transfer scaled source typed-array back to WorkerProxy, compute neural network,
   * pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {ImageData} sourceImageData
   *   The image data to be processed. The size should be [ sourceImageData.height, sourceImageData.width ]
   * = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ] ]. And it should RGBA 4 channels.
   */
  imageData_transferBack_processTensor( processingId, sourceImageData ) {

    // Create (scaled) source image so that then neural network can process it.
    //
    // Usually, only the first web worker ( workerId == 0 ) is responsible for scaling the source image data to default size.
    // After that, all other web worker received the already scaled typed-array.
    //
    // The reason why not use source ImageData directly is that the next web worker could re-create tensor3d more effficiently.
    //
    // ImageData uses Uint8ClampedArray internally. If it is past directly, the next web worker needs create tensor3d by fromPixel()
    // which internally converts twice: from Uint8ClampedArray to Int32Array and from Int32Array to tensor3d.
    //
    // If passing typed-array (Float32Array), the next web worker could use it to re-create tensord3d directly.
    let scaledSourceTensor = this.neuralNetConfig.create_ScaledSourceTensor_from_ImageData_or_Canvas( sourceImageData );

//!!! ...unfinished... If the this.neuralNetConfig (which is past through web worker message) is not real NeuralNet.Config, the following should be used.
//    let scaledSourceTensor = NeuralNet.Config.create_ScaledSourceTensor_from_ImageData_or_Canvas.call( this.neuralNetConfig, sourceImageData );

    // Download the scaledSourceTensor as typed-array (asynchronously), and transfer it back to WorkerProxy (and inform WorkerController).
    //
    // The reason why it is done asynchronously is for not blocking the following computation of neural network.
    this.transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor );

    // At the same time (the scaled source typed-array data is transferring back to WorkerProxy and then WorkerController), this worker is
    // still computing the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Transfer scaled source typed-array data back to WorkerProxy, re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_transferBack_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];

    // Re-create (scaled) source tensor.
    //
    // This should be done before calling transferBackSourceTypedArray() which will transfer (not copy) the sourceTypedArray and invalid it.
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape );

    // Transfer (not copy) the sourceTypedArray back to WorkerProxy). This will invalid sourceTypedArray.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );

    // At the same time (the sourceTypedArray is transferring back to WorkerProxy and then WorkerController), this worker is still computing
    // the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * It will not transfer source back to WorkerProxy. This is different from typedArray_TransferBack_processTensor().
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape ); // Re-create (scaled) source tensor.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Compute neural network, and pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The source tensor3d to be processed. The scaledSourceTensor.shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   * This tensor will be disposed when processing is done.
   */
  processTensorAndDispose( processingId, scaledSourceTensor ) {

    // Note: scaledSourceTensor will be dispose because this.neuralNet is initialized with ( bKeepInputTensor == false ).
    let resultTensor3d = this.neuralNet.apply_and_destroy_or_keep( scaledSourceTensor, true );

    let resultTypedArray = resultTensor3d.dataSync(); // Download synchronously (because here is web worker).
    resultTensor3d.dispose(); // The result tensor should also be disposed.

    // Pass the output of neural network to WorkerProxy (and inform WorkerController).
    let message = { command: "processTensorResult", workerId: this.workerId, processingId: processingId, resultTypedArray: resultTypedArray };
    postMessage( message, [ message.resultTypedArray.buffer ] );
  }

  /**
   * Download the scaledSourceTensor as typed-array (Float32Array) asynchronously. Transfer the typed-array back to WorkerProxy
   * (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The scaled source tensor3d generated by processImageData().
   */
  async transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor ) {
    let sourceTypedArray = await scaledSourceTensor.data(); // Download scaled source tensor3d as typed-array (Float32Array) asynchronously.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );
  }

  /**
   * Transfer source typed-array back to WorkerProxy (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-array past to processTypedArray(). This will become invalid after this call because it will be transferred (not
   * copied) back to WorkerProxy.
   */
  transferBackSourceTypedArray( processingId, sourceTypedArray ) {
    let message = { command: "transferBackSourceTypedArray", workerId: this.workerId, processingId: processingId, sourceTypedArray: sourceTypedArray };
    postMessage( message, [ message.sourceTypedArray.buffer ] );
  }

//!!! ...unfinished... How to know and set total of the following:
//       this.initProgress.libraryDownload;
//       this.initProgress.weightsDownload;
//       this.initProgress.weightsParse;

//       this.initProgress.libraryDownload.total = 3; // There are 3 libraries should be loaded: NetProgress, tensorflow.js, NeuralNet.
//       this.initProgress.libraryDownload.accumulation = 0;

  /** Inform WorkerProxy to set total of every progress of initProgress. */
  reportInitProgress_setTotal() {
    let message = { command: "reportInitProgress", subCommand: "setTotal", workerId: this.workerId,
      libraryDownload: this.initProgress.libraryDownload.total,
      weightsDownload: this.initProgress.weightsDownload.total,
      weightsParse: this.initProgress.weightsParse.total,
    };
    postMessage( message );
  }

  /** Inform WorkerProxy to reset initProgress. */
  reportInitProgress_restAccumulation() {
    let message = { command: "reportInitProgress", subCommand: "restAccumulation", workerId: this.workerId };
    postMessage( message );
  }

  /** Inform WorkerProxy to advance initProgress.libraryDownload. */
  reportInitProgress_libraryDownload() {
//    let message = { command: "initProgress_libraryDownload", workerId: this.workerId, total: this.initProgress.libraryDownload.total, accumulation: this.initProgress.libraryDownload.accumulation };
//    let message = { command: "initProgress_libraryDownload", workerId: this.workerId, initProgress: this.initProgress.libraryDownload };
    let message = { command: "reportInitProgress", subCommand: "libraryDownload", workerId: this.workerId, total: this.initProgress.libraryDownload.total, accumulation: this.initProgress.libraryDownload.accumulation };
    postMessage( message );
  }
}

if ( globalThis.document ) {
  return; // In main document context (Not in worker context). Do nothing. (Should not happen)

// In worker context. Register message handler.

globalThis.onmessage = function( e ) {
  let message = e.data;

  switch ( message.command ) {
    case "init": //{ command: "init", workerId, tensorflowJsURL, neuralNetConfig, weightsURL };
      globalThis.workerBody = new WorkerBody();
      globalThis.workerBody.init( message.workerId, message.tensorflowJsURL, message.neuralNetConfig, message.weightsURL );
      break;

    case "disposeWorker": //{ command: "disposeWorker" };
      globalThis.workerBody.disposeWorker();
      break;

    case "imageData_transferBack_processTensor": //{ command: "imageData_transferBack_processTensor", processingId, sourceImageData };
      globalThis.workerBody.imageData_transferBack_processTensor( message.processingId, message.sourceImageData );
      break;

    case "typedArray_transferBack_processTensor": //{ command: "typedArray_transferBack_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_transferBack_processTensor( message.processingId, message.sourceTypedArray );
      break;

    case "typedArray_processTensor": //{ command: "typedArray_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_processTensor( message.processingId, message.sourceTypedArray );
      break;
  }
}
<commit_msg>Update WorkerBody.js<commit_after>/**
 * @file This file is the main (i.e. body) javascript file of neural network web worker. It is not an importable module.
 *
 */

// In module (non-classic) web worker, static import is available. But at the same time, importScripts() will not be avbailable.
// For solving this problem, using classic (non-module) web worker so that tensorflow.js can be loaded by importScripts().
// At the same time, using dynamic import() to load ourselves module because import() can be used in classic (non-module) script.
//
//import * as Net from "./Net.js";

/**
 * The implementation of a neural network web worker.
 *
 */
class WorkerBody {

  /**
   *
   * @param {number} workerId
   *   A non-negative integer represents this worker's id. The id of the first worker should be 0.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   */
  init( workerId = 0, tensorflowJsURL, neuralNetConfig, weightsURL ) {

    if ( workerId < 0 )
      workerId = 0;

    this.workerId = workerId;
    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;

    let bKeepInputTensor = false; // Because every web worker will copy the input, there is not necessary to keep input.

//!!! ...unfinished... global scope ? report progress ?

    // If a specific library module does not existed, all libraries might have not yet been loaded. (i.e. this is the first time WorkerBody.init() is called.)
    if ( !globalThis.NeuralNetProgress ) {

      globalThis.NeuralNetProgress = await import( "./NetProgress.js" ); // Load progress library in globalThis.ValueMax scope dynamically.

      this.initProgress = new NeuralNetProgress.InitProgress(); // The progress object could be created only after the progress library has been loaded.
      this.initProgress.libraryDownload.total = 3; // There are 3 libraries should be loaded: NetProgress, tensorflow.js, NeuralNet.

      this.initProgress.weightsDownload.total = 1; // There are ??? 1 weights file will be loaded.

//!!! How to know this? According to neural net architecture? It will be known only after it has been downloaded.
      this.initProgress.weightsParse.total = 10000; // There are ??? weights will be parsed.

//!!! It seems that the libraryDownload and weightsDownload should not be included.
// Because the weightsParse.total is unknown when downloading library and weights, it not possible to set total of every progress at one time.

      ++this.initProgress.libraryDownload.accumulation; // The library NetProgress has been loaded.

//!!! ...unfinished... inform WorkerProxy progress changed.

      importScripts( tensorflowJsURL ); // Load tensorflow javascript library in global scope.
      ++this.initProgress.libraryDownload.accumulation; // The library tensorflow.js has been loaded.

      globalThis.NeuralNet = await import( "./Net.js" ); // Load neural network library in globalThis.NeuralNet scope dynamically.
      ++this.initProgress.libraryDownload.accumulation; // The library NeuralNet has been loaded.
    }

//!!! ...unfinished... the neuralNetConfig is still class NeuralNet.Config? Otherwise, the create_ScaledSourceTensor_from_ImageData_or_Canvas() will be lost.

    this.neuralNet = new NeuralNet.Base();
    this.neuralNet.init( neuralNetConfig, bKeepInputTensor );

//!!! ...unfinished...
    // Download and parse neural network weights. Also report downloading and parsing progress.

//       this.initProgress.weightsDownload;
//       this.initProgress.weightsParse;

  }

  disposeWorker() {
    if ( this.neuralNet ) {
      this.neuralNet.disposeTensors();
      this.neuralNet = null;
    }

//!!!??? calling close() when the next worker disposed ?
    close();
  }

//!!! Regular Expression for get text inside html table markup:
//
//  /(?<=
]*>.*)(?<=>)[^<]+(?=<)(?=.*
)/g
//
// It can be used to parsing weights data from Google Sheets published html. Problem: safari browser does not support lookbehind regular expression.

//!!! Regular Expression for get text inside html table data (with or without div) markup:
//
//  let r = RegExp( "]*>(?:
]*>)?([^<]*)(?:
)?	", "g" );
//  let extractedLinesByCells = String( sourceHTMLText ).replace( r, "$1\n" );
//
// The capture group 1 is one cell of one column of google sheet.

  /**
   * Convert source image data to tensor3d, scale it, transfer scaled source typed-array back to WorkerProxy, compute neural network,
   * pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {ImageData} sourceImageData
   *   The image data to be processed. The size should be [ sourceImageData.height, sourceImageData.width ]
   * = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ] ]. And it should RGBA 4 channels.
   */
  imageData_transferBack_processTensor( processingId, sourceImageData ) {

    // Create (scaled) source image so that then neural network can process it.
    //
    // Usually, only the first web worker ( workerId == 0 ) is responsible for scaling the source image data to default size.
    // After that, all other web worker received the already scaled typed-array.
    //
    // The reason why not use source ImageData directly is that the next web worker could re-create tensor3d more effficiently.
    //
    // ImageData uses Uint8ClampedArray internally. If it is past directly, the next web worker needs create tensor3d by fromPixel()
    // which internally converts twice: from Uint8ClampedArray to Int32Array and from Int32Array to tensor3d.
    //
    // If passing typed-array (Float32Array), the next web worker could use it to re-create tensord3d directly.
    let scaledSourceTensor = this.neuralNetConfig.create_ScaledSourceTensor_from_ImageData_or_Canvas( sourceImageData );

//!!! ...unfinished... If the this.neuralNetConfig (which is past through web worker message) is not real NeuralNet.Config, the following should be used.
//    let scaledSourceTensor = NeuralNet.Config.create_ScaledSourceTensor_from_ImageData_or_Canvas.call( this.neuralNetConfig, sourceImageData );

    // Download the scaledSourceTensor as typed-array (asynchronously), and transfer it back to WorkerProxy (and inform WorkerController).
    //
    // The reason why it is done asynchronously is for not blocking the following computation of neural network.
    this.transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor );

    // At the same time (the scaled source typed-array data is transferring back to WorkerProxy and then WorkerController), this worker is
    // still computing the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Transfer scaled source typed-array data back to WorkerProxy, re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_transferBack_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];

    // Re-create (scaled) source tensor.
    //
    // This should be done before calling transferBackSourceTypedArray() which will transfer (not copy) the sourceTypedArray and invalid it.
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape );

    // Transfer (not copy) the sourceTypedArray back to WorkerProxy). This will invalid sourceTypedArray.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );

    // At the same time (the sourceTypedArray is transferring back to WorkerProxy and then WorkerController), this worker is still computing
    // the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * It will not transfer source back to WorkerProxy. This is different from typedArray_TransferBack_processTensor().
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape ); // Re-create (scaled) source tensor.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Compute neural network, and pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The source tensor3d to be processed. The scaledSourceTensor.shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   * This tensor will be disposed when processing is done.
   */
  processTensorAndDispose( processingId, scaledSourceTensor ) {

    // Note: scaledSourceTensor will be dispose because this.neuralNet is initialized with ( bKeepInputTensor == false ).
    let resultTensor3d = this.neuralNet.apply_and_destroy_or_keep( scaledSourceTensor, true );

    let resultTypedArray = resultTensor3d.dataSync(); // Download synchronously (because here is web worker).
    resultTensor3d.dispose(); // The result tensor should also be disposed.

    // Pass the output of neural network to WorkerProxy (and inform WorkerController).
    let message = { command: "processTensorResult", workerId: this.workerId, processingId: processingId, resultTypedArray: resultTypedArray };
    postMessage( message, [ message.resultTypedArray.buffer ] );
  }

  /**
   * Download the scaledSourceTensor as typed-array (Float32Array) asynchronously. Transfer the typed-array back to WorkerProxy
   * (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The scaled source tensor3d generated by processImageData().
   */
  async transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor ) {
    let sourceTypedArray = await scaledSourceTensor.data(); // Download scaled source tensor3d as typed-array (Float32Array) asynchronously.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );
  }

  /**
   * Transfer source typed-array back to WorkerProxy (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-array past to processTypedArray(). This will become invalid after this call because it will be transferred (not
   * copied) back to WorkerProxy.
   */
  transferBackSourceTypedArray( processingId, sourceTypedArray ) {
    let message = { command: "transferBackSourceTypedArray", workerId: this.workerId, processingId: processingId, sourceTypedArray: sourceTypedArray };
    postMessage( message, [ message.sourceTypedArray.buffer ] );
  }

//!!! ...unfinished... How to know and set total of the following:
//       this.initProgress.libraryDownload;
//       this.initProgress.weightsDownload;
//       this.initProgress.weightsParse;

//       this.initProgress.libraryDownload.total = 3; // There are 3 libraries should be loaded: NetProgress, tensorflow.js, NeuralNet.
//       this.initProgress.libraryDownload.accumulation = 0;

  /** Inform WorkerProxy to set total of every progress of initProgress. */
  reportInitProgress_setTotal() {
    let message = { command: "reportInitProgress", subCommand: "setTotal", workerId: this.workerId,
      libraryDownload: this.initProgress.libraryDownload.total,
      weightsDownload: this.initProgress.weightsDownload.total,
      weightsParse: this.initProgress.weightsParse.total,
    };
    postMessage( message );
  }

  /** Inform WorkerProxy to reset initProgress. */
  reportInitProgress_restAccumulation() {
    let message = { command: "reportInitProgress", subCommand: "restAccumulation", workerId: this.workerId };
    postMessage( message );
  }

  /** Inform WorkerProxy to advance initProgress.libraryDownload. */
  reportInitProgress_libraryDownload() {
//    let message = { command: "initProgress_libraryDownload", workerId: this.workerId, total: this.initProgress.libraryDownload.total, accumulation: this.initProgress.libraryDownload.accumulation };
//    let message = { command: "initProgress_libraryDownload", workerId: this.workerId, initProgress: this.initProgress.libraryDownload };
    let message = { command: "reportInitProgress", subCommand: "libraryDownload", workerId: this.workerId, total: this.initProgress.libraryDownload.total, accumulation: this.initProgress.libraryDownload.accumulation };
    postMessage( message );
  }
}

if ( globalThis.document ) {
  return; // In main document context (Not in worker context). Do nothing. (Should not happen)

// In worker context. Register message handler.

globalThis.onmessage = function( e ) {
  let message = e.data;

  switch ( message.command ) {
    case "init": //{ command: "init", workerId, tensorflowJsURL, neuralNetConfig, weightsURL };
      globalThis.workerBody = new WorkerBody();
      globalThis.workerBody.init( message.workerId, message.tensorflowJsURL, message.neuralNetConfig, message.weightsURL );
      break;

    case "disposeWorker": //{ command: "disposeWorker" };
      globalThis.workerBody.disposeWorker();
      break;

    case "imageData_transferBack_processTensor": //{ command: "imageData_transferBack_processTensor", processingId, sourceImageData };
      globalThis.workerBody.imageData_transferBack_processTensor( message.processingId, message.sourceImageData );
      break;

    case "typedArray_transferBack_processTensor": //{ command: "typedArray_transferBack_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_transferBack_processTensor( message.processingId, message.sourceTypedArray );
      break;

    case "typedArray_processTensor": //{ command: "typedArray_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_processTensor( message.processingId, message.sourceTypedArray );
      break;
  }
}
<|endoftext|>


<commit_before>/* init functions */
$(function(){
	$('#resizewin').click(resizelayout);

	initTooltips();

	$('table.checkboxtbl').each(function(){
		var btns = $('tfoot input.submit,tfoot input.reset', this);
		/* when admin sync is active, an extra table is added*/
		if (btns.length==0)
		{
			btns = $(this).next('h4.rsync').next('table.rsync').find('tfoot input.submit,tfoot input.reset');

		}
		var cbs = $('tbody input.checkbox', this);
		if (btns.length && cbs.length)
		{
			enableBtnsWhenChecked(btns, cbs);
		}
	});

	scrollToEl('div.error,div.warning:not(.nofocus)');
});

function scrollToEl(selector)
{
	var intoview = $(selector).eq(0).css('opacity',.1);
	if (intoview.length)
	{
		$('html,body').animate({scrollTop: intoview.offset().top-50}, 500, function(){ intoview.animate({opacity:1}, 1000) });
	}
}

/* older functions */
function initMenu() {
	// $('#menu ul').show();
	$('#menu > li > a').click(
		function(e) {
			var $this = $(this);
			var ul = $this.next();
			if (ul.length)
			{
				ul.slideToggle('normal');
				var li = $this.parent();
				li.toggleClass('collapsed');
				var id = li.prop('id');
				var collapsed = li.hasClass('collapsed');
				$.get('?action=internal.savedata&action2=setdata&key=collapsed_'+id+'&data='+collapsed);
				e=e||event;
				e.preventDefault();
			}
		}
	);
}

function initMenu2() {
	$('#menu ul').hide();
	$('#menu ul:first').show();
	$('#menu li a').click(
	function() {
		var checkElement = $(this).next();
		if((checkElement.is('ul')) && (checkElement.is(':visible'))) {
		return false;
		}
		if((checkElement.is('ul')) && (!checkElement.is(':visible'))) {
		$('#menu ul:visible').slideUp('normal');
		checkElement.slideDown('normal');
		return false;
		}
		}
	);
}

var disableBlockUI=false;
// {form:_form,name:_input.name,value:v,error:err.error};
function customError(errors){
	if(!errors || errors.length==0) return;
	var err;
	var form=errors[0].form;
	var el;
	var clazz;
	var input;

	// remove error from last round
	try{
		for(var i=0;i<form.elements.length;i++){
			input=form.elements[i];
			el=$(input);
			clazz=el.attr("class");
			if(clazz && clazz=="InputError") {
				el.removeClass();
				el=$("#msg_"+input.name);
				el.remove();
			}
		}
	}
	catch(err){
		alert(err)
	}

	// create new error
	for(var i=0;i<errors.length;i++){
		err=errors[i];
		var input=form[err.name];
		var _input=$(input);
		if(i==0) _input.focus();
		_input.addClass("InputError");
		_input.after('
'+err.error+'');
	}
	disableBlockUI=true;
}

function createWaitBlockUI(msg)
{
	var _blockUI=function() {
		if(!disableBlockUI)
		{
			$.blockUI({
				message:msg,
				css: {
					border: 'none',
					padding: '15px',
					backgroundColor: '#000',
					'-webkit-border-radius': '10px',
					'-moz-border-radius': '10px',
					opacity: .5,
					color: '#fff' ,
					fontSize : "18pt"
				},
				fadeIn: 100
			});
		}
	}
	return _blockUI;
}

/* form helpers */
function selectAll(field)
{
	$(field).parents('table:first').find('tbody tr td:first-child input:checkbox').prop('checked', field.checked)
		.filter(':first').triggerHandler('change');
}

function checkTheBox(field) {
	var apendix=field.name.split('_')[1];
	var box = $(field.form['row_'+apendix]);
	if (box.filter(':checked').length==0)
	{
		// calls the click handlers as well
		$(box).click();
	}
}

function enableBtnsWhenChecked(btns, checkboxes)
{
	checkboxes.change(function(){
		var chkd = checkboxes.filter(':checked').length > 0;
		btns.prop('disabled', chkd ? '':'disabled').css('opacity', (chkd ? 1:.5));
	})
		.filter(':first').triggerHandler('change');
}

/* tooltips */
function createTooltip(element, text, x, y, mouseAction )
{
	element.bind(mouseAction, function (event) {
		// remove title from element, so we won't see the default tooltip as well
		element.data('title', element.prop('title')).prop('title', '');
		// detect max x position
		containerRight = $('#layout').offset().left + $('#layout').width() - 20;
		// if you remove() an element it is deleted from the DOM, but the element.tooltip var stays where it is.
		// When an tooltip has been shown before, just re-add the tooltip DOM element.
		// If the tooltip is never created before, create it and add it to the DOM
		if (typeof element.tooltip.data == 'undefined') {
			element.tooltip = $('
'+ text +'

').data('parent', element);
			$('body').append( element.tooltip );
		} else if (typeof element.tooltip == 'object') {
			$('body').append( element.tooltip );
			element.tooltip.removeClass('stayput');
		}
		// reference to the parent
		element.tooltip.data('parent', element);

		// Recalculate the position every time the tooltip is added to a page.
		// This is needed due to the clicked/hovered elements keep changing position when rows ar folded and unfolded
		if (x == 0) {
			var elWidth = element.width();
			if (elWidth > 40) {
				var xPos = element.offset().left;
			} else {
				var xPos = element.offset().left - 20 + (elWidth / 2);
			}
		} else {
			var xPos = x;
		}
		if (y == 0) {
			var yPos = element.offset().top - element.tooltip.outerHeight() - 4;
		} else {
			var yPos = y - 4;
		}
		// if rightside is out of the sitecontainer, shift it left
		var outerRight = xPos + element.tooltip.width();

		if (outerRight > containerRight)
		{
			oldXPos = xPos;
			xPos = 	containerRight - $(element.tooltip).width();
			offset = oldXPos - xPos + 20;
			$(element.tooltip).find('.arrow').css({
				left: offset
			});
		}
		// Set the tooltip position
		$(element.tooltip).css({
			left : xPos,
			top: yPos
		});
		if (mouseAction == 'mouseover') {
			$(this)
				.mouseout(function(){
					var tt = element.tooltip;
					if (!tt.hasClass('stayput'))
					{
						tt.remove();
						// re-add title to element
						element.prop('title', element.data('title'));
					}
				})
				.click(function(e){ $(element.tooltip).toggleClass('stayput'); e.stopPropagation(); });
		} else if (mouseAction == 'click') {
			var overlay = $('

');
			$('body').prepend(overlay);
			$(overlay).click(function(){ $(element.tooltip).remove(); $(overlay).remove(); });
		}
		return false;
	})
}

disableBlockUI=false;
function validatePass() {
	var passDefault = document.getElementById("passEmpty").value;
	if(passDefault.length == 0) {
		$("#passEmpty").focus();
		$("#passEmpty").style.backgroundColor = "#ffeeee";
		$("#messagePass").innerHTML = "This field not to be empty";
		$("#messagePass").style.color = "#bf4f36";
		disableBlockUI=true;
		return false;
	}
}

function initTooltips()
{
	// lookup all elements with a class "tooltipMe" and add a tooltip to them.
	// Use the title attribute when available over the alt atribute
	// images most likely will only have alt
	$('.tooltipMe,abbr').each(function(){
		var $this = $(this);
		var tooltipText = '';
		var title = $this.prop('title');
		var alt = $this.prop('alt');
		if (typeof title !== 'undefined' && title !== false && title !== '') {
			tooltipText = title;
		} else if (typeof alt !== 'undefined' && alt !== false && alt !== '') {
			tooltipText = alt;
		}
		if (tooltipText !== '')
		{
			createTooltip( $this, tooltipText, 0, 0, 'mouseover');
		}
	});
	$('a.btn-mini').each(function(){
		var $this = $(this);
		var tooltipText = $this.find('span').html();
		if (tooltipText !== '')
		{
			createTooltip($this, tooltipText, 0, 0, 'mouseover');
		}
	});

	/*$('table.maintbl div.comment:not(.inline)').each(function(){
		var $this = $(this).addClass('helptextimage').removeClass('comment');
		var parent = $this.parent('td');
		if (parent.length && parent.prev('th').length)
		{
			parent.prev().append($this);
		}
		var html = $this.html();
		$this.html('
' + html + "
");
		createTooltip($this, html, 0, 0, 'mouseover');
	});*/
	$('body').on('click', function(){
		$('div.tooltip.stayput').removeClass('stayput').each(function(){ $(this).data('parent').triggerHandler('mouseout') });
	});
}

function resizelayout(e)
{
	var isfull = $('body').hasClass('full') == 0;
	$('body').toggleClass('full');
	e.preventDefault();
	var contentwidth = parseInt($('#innercontent').width(), 10);
	$.get('?action=internal.savedata&action2=setdata&key=fullscreen&data='+isfull);
	$.get('?action=internal.savedata&action2=setdata&key=contentwidth&data='+contentwidth);
	return false;
};
<commit_msg>disable animations altogether<commit_after>/* init functions */
$(function(){
	$('#resizewin').click(resizelayout);

	initTooltips();

	$('table.checkboxtbl').each(function(){
		var btns = $('tfoot input.submit,tfoot input.reset', this);
		/* when admin sync is active, an extra table is added*/
		if (btns.length==0)
		{
			btns = $(this).next('h4.rsync').next('table.rsync').find('tfoot input.submit,tfoot input.reset');

		}
		var cbs = $('tbody input.checkbox', this);
		if (btns.length && cbs.length)
		{
			enableBtnsWhenChecked(btns, cbs);
		}
	});

	scrollToEl('div.error,div.warning:not(.nofocus)');
});

function scrollToEl(selector)
{
	var intoview = $(selector).eq(0).css('opacity',.1);
	if (intoview.length)
	{
		$('html,body').animate({scrollTop: intoview.offset().top-50}, 500, function(){ intoview.animate({opacity:1}, 1000) });
	}
}

/* older functions */
function initMenu() {
	// $('#menu ul').show();
	$('#menu > li > a').click(
		function(e) {
			var $this = $(this);
			var ul = $this.next();
			if (ul.length)
			{
				ul.slideToggle('normal');
				var li = $this.parent();
				li.toggleClass('collapsed');
				var id = li.prop('id');
				var collapsed = li.hasClass('collapsed');
				$.get('?action=internal.savedata&action2=setdata&key=collapsed_'+id+'&data='+collapsed);
				e=e||event;
				e.preventDefault();
			}
		}
	);
}

function initMenu2() {
	$('#menu ul').hide();
	$('#menu ul:first').show();
	$('#menu li a').click(
	function() {
		var checkElement = $(this).next();
		if((checkElement.is('ul')) && (checkElement.is(':visible'))) {
		return false;
		}
		if((checkElement.is('ul')) && (!checkElement.is(':visible'))) {
		$('#menu ul:visible').slideUp('normal');
		checkElement.slideDown('normal');
		return false;
		}
		}
	);
}

var disableBlockUI=false;
// {form:_form,name:_input.name,value:v,error:err.error};
function customError(errors){
	if(!errors || errors.length==0) return;
	var err;
	var form=errors[0].form;
	var el;
	var clazz;
	var input;

	// remove error from last round
	try{
		for(var i=0;i<form.elements.length;i++){
			input=form.elements[i];
			el=$(input);
			clazz=el.attr("class");
			if(clazz && clazz=="InputError") {
				el.removeClass();
				el=$("#msg_"+input.name);
				el.remove();
			}
		}
	}
	catch(err){
		alert(err)
	}

	// create new error
	for(var i=0;i<errors.length;i++){
		err=errors[i];
		var input=form[err.name];
		var _input=$(input);
		if(i==0) _input.focus();
		_input.addClass("InputError");
		_input.after('
'+err.error+'');
	}
	disableBlockUI=true;
}

function createWaitBlockUI(msg)
{
	var _blockUI=function() {
		if(!disableBlockUI)
		{
			$.blockUI({
				message:msg,
				css: {
					border: 'none',
					padding: '15px',
					backgroundColor: '#000',
					'-webkit-border-radius': '10px',
					'-moz-border-radius': '10px',
					opacity: .5,
					color: '#fff' ,
					fontSize : "18pt"
				},
				fadeIn: 0,
				fadeOut: 0
			});
		}
	}
	return _blockUI;
}

/* form helpers */
function selectAll(field)
{
	$(field).parents('table:first').find('tbody tr td:first-child input:checkbox').prop('checked', field.checked)
		.filter(':first').triggerHandler('change');
}

function checkTheBox(field) {
	var apendix=field.name.split('_')[1];
	var box = $(field.form['row_'+apendix]);
	if (box.filter(':checked').length==0)
	{
		// calls the click handlers as well
		$(box).click();
	}
}

function enableBtnsWhenChecked(btns, checkboxes)
{
	checkboxes.change(function(){
		var chkd = checkboxes.filter(':checked').length > 0;
		btns.prop('disabled', chkd ? '':'disabled').css('opacity', (chkd ? 1:.5));
	})
		.filter(':first').triggerHandler('change');
}

/* tooltips */
function createTooltip(element, text, x, y, mouseAction )
{
	element.bind(mouseAction, function (event) {
		// remove title from element, so we won't see the default tooltip as well
		element.data('title', element.prop('title')).prop('title', '');
		// detect max x position
		containerRight = $('#layout').offset().left + $('#layout').width() - 20;
		// if you remove() an element it is deleted from the DOM, but the element.tooltip var stays where it is.
		// When an tooltip has been shown before, just re-add the tooltip DOM element.
		// If the tooltip is never created before, create it and add it to the DOM
		if (typeof element.tooltip.data == 'undefined') {
			element.tooltip = $('
'+ text +'

').data('parent', element);
			$('body').append( element.tooltip );
		} else if (typeof element.tooltip == 'object') {
			$('body').append( element.tooltip );
			element.tooltip.removeClass('stayput');
		}
		// reference to the parent
		element.tooltip.data('parent', element);

		// Recalculate the position every time the tooltip is added to a page.
		// This is needed due to the clicked/hovered elements keep changing position when rows ar folded and unfolded
		if (x == 0) {
			var elWidth = element.width();
			if (elWidth > 40) {
				var xPos = element.offset().left;
			} else {
				var xPos = element.offset().left - 20 + (elWidth / 2);
			}
		} else {
			var xPos = x;
		}
		if (y == 0) {
			var yPos = element.offset().top - element.tooltip.outerHeight() - 4;
		} else {
			var yPos = y - 4;
		}
		// if rightside is out of the sitecontainer, shift it left
		var outerRight = xPos + element.tooltip.width();

		if (outerRight > containerRight)
		{
			oldXPos = xPos;
			xPos = 	containerRight - $(element.tooltip).width();
			offset = oldXPos - xPos + 20;
			$(element.tooltip).find('.arrow').css({
				left: offset
			});
		}
		// Set the tooltip position
		$(element.tooltip).css({
			left : xPos,
			top: yPos
		});
		if (mouseAction == 'mouseover') {
			$(this)
				.mouseout(function(){
					var tt = element.tooltip;
					if (!tt.hasClass('stayput'))
					{
						tt.remove();
						// re-add title to element
						element.prop('title', element.data('title'));
					}
				})
				.click(function(e){ $(element.tooltip).toggleClass('stayput'); e.stopPropagation(); });
		} else if (mouseAction == 'click') {
			var overlay = $('

');
			$('body').prepend(overlay);
			$(overlay).click(function(){ $(element.tooltip).remove(); $(overlay).remove(); });
		}
		return false;
	})
}

disableBlockUI=false;
function validatePass() {
	var passDefault = document.getElementById("passEmpty").value;
	if(passDefault.length == 0) {
		$("#passEmpty").focus();
		$("#passEmpty").style.backgroundColor = "#ffeeee";
		$("#messagePass").innerHTML = "This field not to be empty";
		$("#messagePass").style.color = "#bf4f36";
		disableBlockUI=true;
		return false;
	}
}

function initTooltips()
{
	// lookup all elements with a class "tooltipMe" and add a tooltip to them.
	// Use the title attribute when available over the alt atribute
	// images most likely will only have alt
	$('.tooltipMe,abbr').each(function(){
		var $this = $(this);
		var tooltipText = '';
		var title = $this.prop('title');
		var alt = $this.prop('alt');
		if (typeof title !== 'undefined' && title !== false && title !== '') {
			tooltipText = title;
		} else if (typeof alt !== 'undefined' && alt !== false && alt !== '') {
			tooltipText = alt;
		}
		if (tooltipText !== '')
		{
			createTooltip( $this, tooltipText, 0, 0, 'mouseover');
		}
	});
	$('a.btn-mini').each(function(){
		var $this = $(this);
		var tooltipText = $this.find('span').html();
		if (tooltipText !== '')
		{
			createTooltip($this, tooltipText, 0, 0, 'mouseover');
		}
	});

	/*$('table.maintbl div.comment:not(.inline)').each(function(){
		var $this = $(this).addClass('helptextimage').removeClass('comment');
		var parent = $this.parent('td');
		if (parent.length && parent.prev('th').length)
		{
			parent.prev().append($this);
		}
		var html = $this.html();
		$this.html('
' + html + "
");
		createTooltip($this, html, 0, 0, 'mouseover');
	});*/
	$('body').on('click', function(){
		$('div.tooltip.stayput').removeClass('stayput').each(function(){ $(this).data('parent').triggerHandler('mouseout') });
	});
}

function resizelayout(e)
{
	var isfull = $('body').hasClass('full') == 0;
	$('body').toggleClass('full');
	e.preventDefault();
	var contentwidth = parseInt($('#innercontent').width(), 10);
	$.get('?action=internal.savedata&action2=setdata&key=fullscreen&data='+isfull);
	$.get('?action=internal.savedata&action2=setdata&key=contentwidth&data='+contentwidth);
	return false;
};
<|endoftext|>


<commit_before>import * as React from "react";

/**
 * Main Navigation Component
 */
export const Header = () => {
  const TITLE = "LLVM Build View";

  return (

      <Wrapper>
        {TITLE}
      </Wrapper>
      <Navigation/>

  );
}
<commit_msg>Import Navigation Component<commit_after>import * as React from "react";
import { Navigation } from "./Navigation";

/**
 * Main Navigation Component
 */
export const Header = () => {
  const TITLE = "LLVM Build View";

  return (

      <Wrapper>
        {TITLE}
      </Wrapper>
      <Navigation/>

  );
}
<|endoftext|>