Home Reference Source Repository

magicad/magicad.js

import base64 from '../base64/base64';
import {
    _addAuthToken,
    _extendQModelOptions,
    _setDiagramImages,
    _getResourceQueryUrl,
    _replaceOptionsFromBase64EncodedUrl
} from './magicad-fn';
import OidcAuthService from '../auth/oidcauth';
import UserManagerSettingsFactory from '../auth/oidc-clientsettings-factory';
import SigninService from '../auth/signin';
import X3DViewService from '../x3dviewservice/X3DViewService.js';
import axios from 'axios';
import utils from '../utils';

export default class MagiCAD {
    /**
     * @private
     * @param {*} fingerprintService
     * @param {*} diagramService
     * @param {*} ahuCalculationService
     * @param {*} signinService (optional) Could be created also with init() function
     * @param {*} x3dViewService (optional) Could be created also with init() function
     */
    constructor(fingerprintService, diagramService, ahuCalculationService, signinService, x3dViewService) {
        /**
         * @private
         */
        this.fingerprintService = fingerprintService;
        /**
         * @private
         */
        this.diagramService = diagramService;
        /**
         * @private
         */
        this.ahuCalculationService = ahuCalculationService;
        /**
         * @private
         */
        this.signinService = signinService;
        /**
         * @private
         */
        this.x3dViewService = x3dViewService;
        /**
         * @private
         */
        this.onFindOptimalSpeed = undefined;
        /**
         * @private
         */
        this.onGetDpTot = undefined;
        /**
         * @private
         */
        this.onLoad = undefined;
        /**
         * @private
         */
        this.onLoadError = undefined;
        /**
         * @private
         */
        this.onRenderX3D = undefined;
    }

    /**
     * Initialize MagiCAD Widget SDK
     * 
     * @param {object} options <pre>Example object:
     * { 
     *  client_id: 'your_client_id', 
     *  popup_redirect_uri: 'callback file url when using popup signin method'
     *  redirect_uri: 'callback file url when using redirect signin method' // if given, popup_redirect_uri will be ignored browser redirection is being used instead of popup
     *  post_logout_redirect_uri: 'callback file url to direct the user to after successful sign-out' 
     *  enableX3D: true, // default value is true and x3dom library will be loaded
     *  onLoadUser: function(user) { console.log(user.username); }, // Set event handler to get signed in user if any exists e.g. case when page refreshed
     *  onSignin: function(user) { console.log(user.username); }, // Set event handler to perform some action on user signin
     *  onSignout: function() { console.log('user signed out'); }, // Set event handler to perform some action on user signout
     *  onRenderX3D: function() { console.log('x3d rendered'); }, // Set event handler to perform some action after rendering x3d image
     *  onGetDpTot function(result) { console.log(result); }, // Set event handler to perform some action with calculation result
     *  onFindOptimalSpeed: function(result) { console.log(result); } // Set event handler to perform some action with calculation result
     * }
     * </pre>
     * @param {object} context (optional)
     */
    init(options, context) {
        // If not given already in constructor
        if (!this.signinService) {
            let factory = new UserManagerSettingsFactory(options);
            let oidcAuthService = new OidcAuthService(factory);
            this.signinService = new SigninService(oidcAuthService);
        }
        if (!this.signinService)
            throw "MagiCAD Widget SDK initialization failed!";

        // Set X3DViewService
        if (options.enableX3D === undefined || options.enableX3D === null) {
            options.enableX3D = true; // default value if not given
        }
        // If not given already in contructor
        if (!this.x3dViewService)
            this.x3dViewService = options.enableX3D ? new X3DViewService() : undefined;

        if (this.signinService) {
            if (typeof options.onLoadUser === 'function') {
                this.signinService && this.signinService.getUser()
                    .then(user => {
                        options.onLoadUser(user.profile);
                    })
                    .catch(err => {
                        options.onLoadUser(null);
                    });
            }
            if (typeof options.onSignin === 'function') {
                this.signinService.onSignin(options.onSignin);
            }
            if (typeof options.onSignout === 'function') {
                this.signinService.onSignout(options.onSignout);
            }
        }

        if (typeof options.onLoad === 'function') {
            this.onLoad = options.onLoad;
        }

        if (typeof options.onLoadError === 'function') {
            this.onLoadError = options.onLoadError;
        }
        if (typeof options.onRenderX3D === 'function') {
            this.onRenderX3D = options.onRenderX3D;
        }
        if (typeof options.onGetDpTot === 'function') {
            this.onGetDpTot = options.onGetDpTot;
        }
        if (typeof options.onFindOptimalSpeed === 'function') {
            this.onFindOptimalSpeed = options.onFindOptimalSpeed;
        }

        if (typeof context === 'object') {
            context.MagiCAD = Object.assign(context.MagiCAD, this);
            context.MagiCAD.__proto__ = this.__proto__;
        }
    }

    /**
     * Return information is user logged in.
     * 
     * @return boolean value is used logged in     
     */
    isLoggedIn() {
        return this.signinService && this.signinService.isLoggedIn();
    }

    /**
     * Signin. Will open MagiCAD login popup.
     * 
     * @return {Promise<void>}
     */
    signin() {
        return this.signinService && this.signinService.signin();
    }

    /**
     * Signout current logged in user
     * 
     * @return {Promise<void>}
     */
    signout() {
        return this.signinService && this.signinService.signout();
    }

    /**
     * Return release info of MagiCAD Widget SDK in example Production 2.35.0     
     */
    getReleaseInfo() {
        return `${systemconfig.ENVIRONMENT} ${systemconfig.RELEASENUMBER}`;
    }

    /**
     * Load and initialize MagiCAD Cloud Javascript Library     
     * 
     * @param  {object} options <pre>
     * { 
     *      productId: string, 
     *      variantQpdId: string,
     *      articleNumber: string, 
     *      manufacturerId: string 
     *  }
     *  
     * where manufacturerId and either:
     * 
     *      productId and variantQpdId, or
     *      articleNumber
     *      
 *      is defined.
     * </pre>
     * @return {Promise<ProductData>} Successful response will contain product/variant data
     */
    load(options) {
        let userRequest = this.signinService && this.signinService.getUser(),
            fingerprintRequest = this.fingerprintService.getFingerprint();
        let promise = Promise.all([userRequest, fingerprintRequest]).then(([user, sessionId]) => {
            let baseProductUrl = systemconfig.APIURL + systemconfig.PRODUCTPATH;
            let productUrl;
            if (options.productId)
                productUrl = baseProductUrl + options.productId;
            else if (options.articleNumber && options.manufacturerId)
                productUrl = baseProductUrl + options.manufacturerId + '/' + encodeURI(options.articleNumber);
            if (options.logoContainerId)
                this.renderMagiCADLogo(options.logoContainerId);

            return axios.get(productUrl).then((response) => {
                if (!response || response.status !== 200 || !response.data)
                    return null;

                // Finalize Attachments with auth tokens
                response.data.Attachments.forEach((val) => {
                    val.Uri = _addAuthToken(user, sessionId, val.Uri, options.applicationId);
                });
                // Finalize DXF resources with auth tokens
                response.data.DxfUrl = _addAuthToken(user, sessionId, response.data.DxfUrl, options.applicationId);
                return response.data;
            }, (err) => {
                if (this.onLoadError)
                    this.onLoadError(err);
            });
        });
        if (this.onLoad) {
            promise.then(this.onLoad, this.onLoadError);
        }
        return promise;
    }

    /**
     * Return DXF query url
     * 
     * @param {object} product The product object returned from MagiCAD.load() function
     * @param {object} options <pre>
     * { 
     *      QModelParameters: Optional QModelParameters of model, in format "L=100;D=25;W1=130...",
     *      Filename: Optional file name and extension for the object to download, in format "example.dxf"
     * }
     * </pre>
     * @returns DXF url customized with given options. If invalid options object is provided, returns default DXF url, or null if no DXF URL exists
     */
    getCustomizedDxfQueryUrl(product, options){
        try{
            return _replaceOptionsFromBase64EncodedUrl(options, new URL(product.DxfUrl));
        }
        catch (e) {
            return null;
        }
    }

    /**
     * Return dimension image query url
     * 
     * @param {object} options <pre>
     *  { 
     *      QModelId: Optional QModelId of model, 
     *      QModelParameters: Optional QModelParameters of model, 
     *      ViewMode: one value of { 0=Rendered, 1=DimensionLabels, 2=DimensionValues },
     *      ViewPosition: one value of { 0=Default, 1=Front, 2=Right, 3=Left, 4=Top, 5=Bottom, 6=IsometricLeft, 7=IsometricRight } 
     *  }
     * @returns Dimension image url
     */
    getDimensionImageQueryUrl(options) {
        return _getResourceQueryUrl(options, systemconfig.DIMENSIONSIMAGEQUERYPATH);
    }

    /**
     * 
     * Return image query url
     * 
     * @param {object} options <pre>
     * { 
     *      QModelId: Optional QModelId of model, 
     *      QModelParameters: Optional QModelParameters of model,
     *      ViewPosition: one value of { 0=Default, 1=Front, 2=Right, 3=Left, 4=Top, 5=Bottom, 6=IsometricLeft, 7=IsometricRight } 
     * }
     * @returns Image url
     */
    getImageQueryUrl(options) {
        return _getResourceQueryUrl(options, systemconfig.IMAGEQUERYPATH);
    }

    /**
     * 
     * Renders X3D Image container to given container
     * 
     * @param {object} options  <pre>
     *   Basic example:
     * 
     *   {
     *       x3dUri: location of x3d,
     *       imageUri: location of image
     *       containerId: identifier of the container for x3d,
     *   }
     *
     *  Or, to get 3D view with custom model parameters:
     *  { 
     *       QModelId: the identifier of the 3D geometry (GUID format), 
     *       QModelParameters: Optional QModelParameters of model, 
     *       ViewMode: one value of { 0=Rendered, 1=DimensionLabels, 2=DimensionValues,
     *       containerId: identifier of the container for x3d,
     *   }
     *   
     *   Or, to get 3D view with product external reference and variant qpd id:
     *   {
     *       productId: the external reference of the product (GUID format)
     *       variantQpdId: the qpd id of the specific variant,
     *       containerId: identifier of the container for x3d
     *   }
     *
     *
     *  </pre>
     * @param {boolean} update Update existing container with new image url
     * @return {Promise<void>} result as promise
     */
    renderX3D(options, update) {
        if (!this.x3dViewService) {
            return new Promise((resolve, reject) => {
                reject("Enable X3D in MagiCAD.init() function");
            });
        }
        let queryOptions = {
            containerId : (options || {}).containerId,
            imageUri : (options ||{}).imageUri,
            x3dUri : (options || {}).x3dUri
        };
        
        let queryObject = _extendQModelOptions(options);
        
        if (queryObject.QModelId) { // QModelId is set, override image and x3d
            queryOptions.x3dUri = systemconfig.APIURL + systemconfig.X3DQUERYPATH + base64.b64EncodeUnicode(JSON.stringify(queryObject));
            queryOptions.imageUri = systemconfig.APIURL + systemconfig.IMAGEQUERYPATH + base64.b64EncodeUnicode(JSON.stringify(queryObject));
        }
        
        else if (queryObject.variantQpdId && queryObject.productId) { // Product external reference and variant QpdId is set, override image and x3d
            queryOptions.x3dUri = systemconfig.APIURL +
                systemconfig.X3DBYQPDIDPATH
                    .replace('{productId}', queryObject.productId)
                    .replace('{encodedVariantQpdId}', base64.b64EncodeUnicode(queryObject.variantQpdId));
            
            queryOptions.imageUri = systemconfig.APIURL + 
                systemconfig.IMAGEBYQPDIDPATH
                    .replace('{productId}', queryObject.productId)
                    .replace('{encodedVariantQpdId}', base64.b64EncodeUnicode(queryObject.variantQpdId));
        }
        
        return this.x3dViewService.loadX3D(queryOptions, update).then(result => {
            if (this.onRenderX3D) this.onRenderX3D();
            return result;
        });

    }

    /**
     * Render MagiCAD logo to container with specified identified
     * 
     * @param {string} containerId Container identifier
     * @param {boolean} transparent Boolean value should transparent image be used
     */
    renderMagiCADLogo(containerId, transparent) {
        let container = document.getElementById(containerId);
        if (!container)
            return;
        let data = {
            ImageUrl: transparent ?
                systemconfig.MAGICLOUDLOGOTRANSPARENT : systemconfig.MAGICLOUDLOGO,
            Alt: 'Powered by MagiCAD',
            Title: 'Powered by MagiCAD'
        };
        container.innerHTML = `<img src="${data.ImageUrl}" alt="${data.Alt}" title="${data.Title}" />`;
    }

    /**
     * Render MagiCAD logo to container with specified identified
     * @param {*} containerId Container identifier
     * @param {*} transparent Boolean value whether transparent image should be used
     * @deprecated Use renderMagiCADLogo instead
     */
    renderMagiCloudLogo(containerId, transparent) {
        this.renderMagiCADLogo(containerId, transparent);
    }

    /**
     * Set specified diagram and bind click event to given elementId. Click event will add possible data to given dataElementId
     * @param {object} data Product data
     * @param {object} options <pre>Example:
     *  { 
     *      type: diagramType, 
     *      elementId: 'el', 
     *      dataElementId: 'dataEl' 
     *  } 
     * where diagramType is one of { Ventilation, Heating, Cooling, SupplyAir, ExtractAir, Radiator, Pump }
     * </pre>
     */
    setDiagram(data, options) {
        if (!options || !options.elementId || !options.type ||
            !data.Diagrams || !data.Diagrams[options.type])
            return;

        let element = document.getElementById(options.elementId);
        if (!element)
            return;

        element.innerHTML = '';
        let img = utils.createElement('img', {
            src: data.Diagrams[options.type].BaseUrl,
            alt: options.type
        });
        element.appendChild(img);

        // Set diagram data if exists
        let hasData = data.Diagrams[options.type].HasData;
        if (hasData) {
            let baseUrl = data.Diagrams[options.type].BaseUrl;
            let dataElement = document.getElementById(options.dataElementId);
            if (!dataElement)
                return;
            if (options.operationPoint && options.operationPoint.x && options.operationPoint.y &&
                options.operationPoint.x >= 0 && options.operationPoint.y >= 0) {

                let diagram = {
                    URL: baseUrl,
                    DataURL: hasData ? baseUrl + '/data' : null,
                    operationPoint: options.operationPoint,
                    HasData: hasData
                };
                _setDiagramImages(this.diagramService, img, dataElement, diagram, options.type);
            }
            let that = this;
            img.addEventListener('click', (event) => {
                let diagram = that.diagramService.getDiagram({
                    target: img,
                    data: {
                        BaseURL: baseUrl,
                        HasData: hasData
                    },
                    pageX: event.pageX,
                    pageY: event.pageY
                });
                that.diagramService.getOperatingPoint(diagram).then(() => {
                    _setDiagramImages(that.diagramService, img, dataElement, diagram, options.type);
                }, () => {
                    // Failure when performing getOperatingPoint
                });
            });
        }
    }

    /**
     * Get pressure drop of curve at specific flow.
     * @return Example {Succeed: true, Value: 27.870676}
     * @param {string} productId Product identifier (GUID)
     * @param {string} systemTypeId Value = {Unknown, AnyFluid, SupplyFluid, ReturnFluid, ColdWater, HotWater, FireHydrant, HotSupplyFluid, Sprinkler, Sewer, AnyAir, SupplyAir, ExtractAir, OutdoorSupply, OutdoorExhau}
     * @param {number} qv Specified flow
     * @param {number} fanSpeed Specified Fan Speed
     */
    getDpTot(productId, systemTypeId, qv, fanSpeed) {
        let promise = this.ahuCalculationService.getDpTot(productId, systemTypeId, qv, fanSpeed);
        if (this.onGetDpTot) {
            promise.then(this.onGetDpTot);
        }
        return promise;
    }

    /**
     * Find suitable fanSpeed for given operation point.
     * @private
     * @return Example {PressureDrop: 107.4791, Succeed: true, Value: 6}
     * @param {string} productId Product identifier (GUID)
     * @param {string} systemTypeId Value = {Unknown, AnyFluid, SupplyFluid, ReturnFluid, ColdWater, HotWater, FireHydrant, HotSupplyFluid, Sprinkler, Sewer, AnyAir, SupplyAir, ExtractAir, OutdoorSupply, OutdoorExhau}
     * @param {number} qv Specified flow
     * @param {number} dpTot Pressure drop value
     */
    findOptimalSpeed(productId, systemTypeId, qv, dpTot) {
        let promise = this.ahuCalculationService.findOptimalSpeed(productId, systemTypeId, qv, dpTot);
        if (this.onFindOptimalSpeed) {
            promise.then(this.onFindOptimalSpeed);
        }
        return promise;
    }
}