<commit_before>import Utils from '../../utils/utils';
import Texture from '../../gl/texture';
import FontManager from './font_manager';
import debugSettings from '../../utils/debug_settings';

export default class CanvasText {

    constructor () {
        this.canvas = document.createElement('canvas');
        this.canvas.style.backgroundColor = 'transparent'; // render text on transparent background
        this.context = this.canvas.getContext('2d');
        this.text_buffer = 8; // pixel padding around text
    }

    resize (width, height) {
        this.canvas.width = width;
        this.canvas.height = height;
        this.context.clearRect(0, 0, width, height);
    }

    // Set font style params for canvas drawing
    setFont ({ font_css, fill, stroke, stroke_width, px_size }) {
        this.px_size = px_size;
        let ctx = this.context;

        if (stroke && stroke_width > 0) {
            ctx.strokeStyle = stroke;
            ctx.lineWidth = stroke_width;
        }
        ctx.fillStyle = fill;

        ctx.font = font_css;
        ctx.miterLimit = 2;
    }

    textSizes (texts) {
        return FontManager.loadFonts().then(() => {
            for (let style in texts) {
                let text_infos = texts[style];
                let first = true;

                for (let text in text_infos) {
                    // Use cached size, or compute via canvas
                    if (!CanvasText.text_cache[style] || !CanvasText.text_cache[style][text]) {
                        let text_settings = text_infos[text].text_settings;
                        if (first) {
                            this.setFont(text_settings);
                            first = false;
                        }

                        CanvasText.text_cache[style] = CanvasText.text_cache[style] || {};
                        CanvasText.text_cache[style][text] = this.textSize(text, text_settings);
                        CanvasText.cache_stats.misses++;
                    }
                    else {
                        CanvasText.cache_stats.hits++;
                    }

                    // Only send text sizes back to worker (keep computed text line info
                    // on main thread, for future rendering)
                    text_infos[text].size = CanvasText.text_cache[style][text].size;
                }
            }

            return texts;
        });
    }

    // Computes width and height of text based on current font style
    // Includes word wrapping, returns size info for whole text block and individual lines
    textSize (text, {transform, text_wrap, max_lines}) {
        let str = this.applyTextTransform(text, transform);
        let ctx = this.context;
        let buffer = this.text_buffer * Utils.device_pixel_ratio;
        let leading = 2 * Utils.device_pixel_ratio; // make configurable and/or use Canvas TextMetrics when available
        let line_height = this.px_size + leading; // px_size already in device pixels

        let multiline = MultiLine.parse(str, text_wrap, max_lines, line_height, ctx);

        // Final dimensions of text
        let height = multiline.height;
        let width = multiline.width;
        let lines = multiline.lines;

        let collision_size = [
            width / Utils.device_pixel_ratio,
            height / Utils.device_pixel_ratio
        ];

        let texture_size = [
            width + buffer * 2,
            height + buffer * 2
        ];

        let logical_size = texture_size.map(v => v / Utils.device_pixel_ratio);

        // Returns lines (w/per-line info for drawing) and text's overall bounding box + canvas size
        return {
            lines,
            size: { collision_size, texture_size, logical_size, line_height }
        };
    }

    // Draw one or more lines of text at specified location, adjusting for buffer and baseline
    drawText (lines, [x, y], size, { stroke, stroke_width, transform, align }) {
        align = align || 'center';

        for (let line_num=0; line_num < lines.length; line_num++) {
            let line = lines[line_num];
            let str = this.applyTextTransform(line.text, transform);
            let buffer = this.text_buffer * Utils.device_pixel_ratio;
            let texture_size = size.texture_size;
            let line_height = size.line_height;

            // Text alignment
            let tx;
            if (align === 'left') {
                tx = x + buffer;
            }
            else if (align === 'center') {
                tx = x + texture_size[0]/2 - line.width/2;
            }
            else if (align === 'right') {
                tx = x + texture_size[0] - line.width - buffer;
            }

            // In the absence of better Canvas TextMetrics (not supported by browsers yet),
            // 0.75 buffer produces a better approximate vertical centering of text
            let ty = y + buffer * 0.75 + (line_num + 1) * line_height;

            if (stroke && stroke_width > 0) {
                this.context.strokeText(str, tx, ty);
            }
            this.context.fillText(str, tx, ty);
        }

        // Draw bounding boxes for debugging
        if (debugSettings.draw_label_collision_boxes) {
            this.context.save();

            let dpr = Utils.device_pixel_ratio;
            let buffer = dpr * this.text_buffer;
            let collision_size = size.collision_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'blue';
            this.context.lineWidth = lineWidth;
            this.context.strokeRect(x + buffer, y + buffer, dpr * collision_size[0], dpr * collision_size[1]);

            this.context.restore();
        }

        if (debugSettings.draw_label_texture_boxes) {
            this.context.save();

            let texture_size = size.texture_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'green';
            this.context.lineWidth = lineWidth;
            // stroke is applied internally, so the outer border is the edge of the texture
            this.context.strokeRect(x + lineWidth, y + lineWidth, texture_size[0] - 2 * lineWidth, texture_size[1] - 2 * lineWidth);

            this.context.restore();
        }
    }

    rasterize (texts, texture_size) {
        for (let style in texts) {
            let text_infos = texts[style];
            let first = true;

            for (let text in text_infos) {
                let info = text_infos[text];
                let text_settings = info.text_settings;
                let lines = CanvasText.text_cache[style][text].lines; // get previously computed lines of text

                if (first) {
                    this.setFont(text_settings);
                    first = false;
                }

                // each alignment needs to be rendered separately
                for (let align in info.align) {
                    this.drawText(lines, info.align[align].texture_position, info.size, {
                        stroke: text_settings.stroke,
                        stroke_width: text_settings.stroke_width,
                        transform: text_settings.transform,
                        align: align
                    });

                    info.align[align].texcoords = Texture.getTexcoordsForSprite(
                        info.align[align].texture_position,
                        info.size.texture_size,
                        texture_size
                    );
                }
            }
        }
    }

    // Place text labels within an atlas of the given max size
    setTextureTextPositions (texts, max_texture_size) {
        // Find widest label
        let widest = 0;
        for (let style in texts) {
            let text_infos = texts[style];
            for (let text in text_infos) {
                let size = text_infos[text].size.texture_size;
                if (size[0] > widest) {
                    widest = size[0];
                }
            }
        }

        // Layout labels, stacked in columns
        let cx = 0, cy = 0; // current x/y position in atlas
        let height = 0;     // overall atlas height
        for (let style in texts) {
            let text_infos = texts[style];

            for (let text in text_infos) {
                let text_info = text_infos[text];
                // rendered size is same for all alignments
                let size = text_info.size.texture_size;

                // but each alignment needs to be rendered separately
                for (let align in text_info.align) {
                    if (cy + size[1] < max_texture_size) {
                        text_info.align[align].texture_position = [cx, cy]; // add label to current column
                        cy += size[1];
                        if (cy > height) {
                            height = cy;
                        }
                    }
                    else { // start new column if taller than texture
                        cx += widest;
                        cy = 0;
                        text_info.align[align].texture_position = [cx, cy];
                    }
                }
            }
        }

        return [cx + widest, height]; // overall atlas size
    }

    // Called before rasterization
    applyTextTransform (text, transform) {
        if (transform === 'capitalize') {
            return text.replace(/\w\S*/g, function (txt) {
                return txt.charAt(0).toUpperCase() + txt.substr(1);
            });
        }
        else if (transform === 'uppercase') {
            return text.toUpperCase();
        }
        else if (transform === 'lowercase') {
            return text.toLowerCase();
        }
        return text;
    }

    // Convert font CSS-style size ('12px', '14pt', '1.5em', etc.) to pixel size (adjusted for device pixel ratio)
    // Defaults units to pixels if not specified
    static fontPixelSize (size) {
        if (size == null) {
            return;
        }
        size = (typeof size === 'string') ? size : String(size); // need a string for regex

        let [, px_size, units] = size.match(CanvasText.font_size_re) || [];
        units = units || 'px';

        if (units === "em") {
            px_size *= 16;
        } else if (units === "pt") {
            px_size /= 0.75;
        } else if (units === "%") {
            px_size /= 6.25;
        }

        px_size = parseFloat(px_size);
        px_size *= Utils.device_pixel_ratio;
        return px_size;
    }

}

// Extract font size and units
CanvasText.font_size_re = /((?:[0-9]*\.)?[0-9]+)\s*(px|pt|em|%)?/;

// Cache sizes of rendered text
CanvasText.text_cache = {}; // by text style, then text string
CanvasText.cache_stats = { hits: 0, misses: 0 };

// Private class to arrange text labels into multiple lines based on
// "text wrap" and "max line" values
class MultiLine {
    constructor (context, max_lines = Infinity, text_wrap = Infinity) {
        this.width = 0;
        this.height = 0;
        this.lines = [];

        this.ellipsis = '...';
        this.ellipsis_width = context.measureText(this.ellipsis).width;

        this.max_lines = max_lines;
        this.text_wrap = text_wrap;
        this.context = context;
    }

    createLine (line_height){
        if (this.lines.length < this.max_lines){
            return new Line(line_height, this.text_wrap);
        }
        else {
            return false;
        }
    }

    push (line){
        if (this.lines.length < this.max_lines){
            // measure line width
            let line_width = this.context.measureText(line.text).width;
            line.width = line_width;

            if (line_width > this.width){
                this.width = Math.ceil(line_width);
            }

            // add to lines and increment height
            this.lines.push(line);
            this.height += line.height;
            return true;
        }
        else {
            this.addEllipsis();
            return false;
        }
    }

    // pushes to the lines array and returns a new line if possible (false otherwise)
    advance (line, line_height) {
        let can_push = this.push(line);
        if (can_push){
            return this.createLine(line_height);
        }
        else {
            return false;
        }
    }

    addEllipsis (){
        let last_line = this.lines[this.lines.length - 1];

        last_line.append(this.ellipsis);
        last_line.width += this.ellipsis_width;

        if (last_line.width > this.width) {
            this.width = last_line.width;
        }
    }

    finish (line){
        if (line){
            this.push(line);
        }
        else {
            this.addEllipsis();
        }
    }

    static parse (str, text_wrap, max_lines, line_height, ctx) {
        // Word wrapping
        // Line breaks can be caused by:
        //  - implicit line break when a maximum character threshold is exceeded per line (text_wrap)
        //  - explicit line break in the label text (\n)
        let words;
        if (typeof text_wrap === 'number') {
            words = str.split(' '); // split words on spaces
        }
        else {
            words = [str]; // no max line word wrapping (but new lines will still be in effect)
        }

        let multiline = new MultiLine(ctx, max_lines, text_wrap);
        let line = multiline.createLine(line_height);

        // First iterate on space-break groups (will be one if max line length off), then iterate on line-break groups
        for (let i = 0; i < words.length; i++) {
            let breaks = words[i].split('\n'); // split on line breaks
            let new_line = (i === 0) ? true : false;

            for (let n=0; n < breaks.length; n++) {
                if (!line){
                    break;
                }

                let word = breaks[n].trim();

                if (!word) {
                    continue;
                }

                let spaced_word = (new_line) ? word : ' ' + word;

                // if adding current word would overflow, add a new line instead
                // first word (i === 0) always appends
                if (text_wrap && i > 0 && line.exceedsTextwrap(spaced_word)) {
                    line = multiline.advance(line, line_height);
                    if (!line){
                        break;
                    }
                    line.append(word);
                    new_line = true;
                }
                else {
                    line.append(spaced_word);
                }

                // if line breaks present, add new line (unless on last line)
                if (n < breaks.length - 1) {
                    line = multiline.advance(line, line_height);
                    new_line = true;
                }
            }

            if (i === words.length - 1){
                multiline.finish(line);
            }
        }
        return multiline;
    }
}

// A Private class used by MultiLine to contain the logic for a single line
// including character count, width, height and text
class Line {
    constructor (height = 0, text_wrap = 0){
        this.chars = 0;
        this.text = '';

        this.height = height;
        this.text_wrap = text_wrap;
    }

    append (text){
        this.chars += text.length;
        this.text += text;
    }

    exceedsTextwrap (text){
        return text.length + this.chars > this.text_wrap;
    }
}
<commit_msg>Add more documentation<commit_after>import Utils from '../../utils/utils';
import Texture from '../../gl/texture';
import FontManager from './font_manager';
import debugSettings from '../../utils/debug_settings';

export default class CanvasText {

    constructor () {
        this.canvas = document.createElement('canvas');
        this.canvas.style.backgroundColor = 'transparent'; // render text on transparent background
        this.context = this.canvas.getContext('2d');
        this.text_buffer = 8; // pixel padding around text
    }

    resize (width, height) {
        this.canvas.width = width;
        this.canvas.height = height;
        this.context.clearRect(0, 0, width, height);
    }

    // Set font style params for canvas drawing
    setFont ({ font_css, fill, stroke, stroke_width, px_size }) {
        this.px_size = px_size;
        let ctx = this.context;

        if (stroke && stroke_width > 0) {
            ctx.strokeStyle = stroke;
            ctx.lineWidth = stroke_width;
        }
        ctx.fillStyle = fill;

        ctx.font = font_css;
        ctx.miterLimit = 2;
    }

    textSizes (texts) {
        return FontManager.loadFonts().then(() => {
            for (let style in texts) {
                let text_infos = texts[style];
                let first = true;

                for (let text in text_infos) {
                    // Use cached size, or compute via canvas
                    if (!CanvasText.text_cache[style] || !CanvasText.text_cache[style][text]) {
                        let text_settings = text_infos[text].text_settings;
                        if (first) {
                            this.setFont(text_settings);
                            first = false;
                        }

                        CanvasText.text_cache[style] = CanvasText.text_cache[style] || {};
                        CanvasText.text_cache[style][text] = this.textSize(text, text_settings);
                        CanvasText.cache_stats.misses++;
                    }
                    else {
                        CanvasText.cache_stats.hits++;
                    }

                    // Only send text sizes back to worker (keep computed text line info
                    // on main thread, for future rendering)
                    text_infos[text].size = CanvasText.text_cache[style][text].size;
                }
            }

            return texts;
        });
    }

    // Computes width and height of text based on current font style
    // Includes word wrapping, returns size info for whole text block and individual lines
    textSize (text, {transform, text_wrap, max_lines}) {
        let str = this.applyTextTransform(text, transform);
        let ctx = this.context;
        let buffer = this.text_buffer * Utils.device_pixel_ratio;
        let leading = 2 * Utils.device_pixel_ratio; // make configurable and/or use Canvas TextMetrics when available
        let line_height = this.px_size + leading; // px_size already in device pixels

        // Parse string into series of lines if it exceeds the text wrapping value or contains line breaks
        let multiline = MultiLine.parse(str, text_wrap, max_lines, line_height, ctx);

        // Final dimensions of text
        let height = multiline.height;
        let width = multiline.width;
        let lines = multiline.lines;

        let collision_size = [
            width / Utils.device_pixel_ratio,
            height / Utils.device_pixel_ratio
        ];

        let texture_size = [
            width + buffer * 2,
            height + buffer * 2
        ];

        let logical_size = texture_size.map(v => v / Utils.device_pixel_ratio);

        // Returns lines (w/per-line info for drawing) and text's overall bounding box + canvas size
        return {
            lines,
            size: { collision_size, texture_size, logical_size, line_height }
        };
    }

    // Draw one or more lines of text at specified location, adjusting for buffer and baseline
    drawText (lines, [x, y], size, { stroke, stroke_width, transform, align }) {
        align = align || 'center';

        for (let line_num=0; line_num < lines.length; line_num++) {
            let line = lines[line_num];
            let str = this.applyTextTransform(line.text, transform);
            let buffer = this.text_buffer * Utils.device_pixel_ratio;
            let texture_size = size.texture_size;
            let line_height = size.line_height;

            // Text alignment
            let tx;
            if (align === 'left') {
                tx = x + buffer;
            }
            else if (align === 'center') {
                tx = x + texture_size[0]/2 - line.width/2;
            }
            else if (align === 'right') {
                tx = x + texture_size[0] - line.width - buffer;
            }

            // In the absence of better Canvas TextMetrics (not supported by browsers yet),
            // 0.75 buffer produces a better approximate vertical centering of text
            let ty = y + buffer * 0.75 + (line_num + 1) * line_height;

            if (stroke && stroke_width > 0) {
                this.context.strokeText(str, tx, ty);
            }
            this.context.fillText(str, tx, ty);
        }

        // Draw bounding boxes for debugging
        if (debugSettings.draw_label_collision_boxes) {
            this.context.save();

            let dpr = Utils.device_pixel_ratio;
            let buffer = dpr * this.text_buffer;
            let collision_size = size.collision_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'blue';
            this.context.lineWidth = lineWidth;
            this.context.strokeRect(x + buffer, y + buffer, dpr * collision_size[0], dpr * collision_size[1]);

            this.context.restore();
        }

        if (debugSettings.draw_label_texture_boxes) {
            this.context.save();

            let texture_size = size.texture_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'green';
            this.context.lineWidth = lineWidth;
            // stroke is applied internally, so the outer border is the edge of the texture
            this.context.strokeRect(x + lineWidth, y + lineWidth, texture_size[0] - 2 * lineWidth, texture_size[1] - 2 * lineWidth);

            this.context.restore();
        }
    }

    rasterize (texts, texture_size) {
        for (let style in texts) {
            let text_infos = texts[style];
            let first = true;

            for (let text in text_infos) {
                let info = text_infos[text];
                let text_settings = info.text_settings;
                let lines = CanvasText.text_cache[style][text].lines; // get previously computed lines of text

                if (first) {
                    this.setFont(text_settings);
                    first = false;
                }

                // each alignment needs to be rendered separately
                for (let align in info.align) {
                    this.drawText(lines, info.align[align].texture_position, info.size, {
                        stroke: text_settings.stroke,
                        stroke_width: text_settings.stroke_width,
                        transform: text_settings.transform,
                        align: align
                    });

                    info.align[align].texcoords = Texture.getTexcoordsForSprite(
                        info.align[align].texture_position,
                        info.size.texture_size,
                        texture_size
                    );
                }
            }
        }
    }

    // Place text labels within an atlas of the given max size
    setTextureTextPositions (texts, max_texture_size) {
        // Find widest label
        let widest = 0;
        for (let style in texts) {
            let text_infos = texts[style];
            for (let text in text_infos) {
                let size = text_infos[text].size.texture_size;
                if (size[0] > widest) {
                    widest = size[0];
                }
            }
        }

        // Layout labels, stacked in columns
        let cx = 0, cy = 0; // current x/y position in atlas
        let height = 0;     // overall atlas height
        for (let style in texts) {
            let text_infos = texts[style];

            for (let text in text_infos) {
                let text_info = text_infos[text];
                // rendered size is same for all alignments
                let size = text_info.size.texture_size;

                // but each alignment needs to be rendered separately
                for (let align in text_info.align) {
                    if (cy + size[1] < max_texture_size) {
                        text_info.align[align].texture_position = [cx, cy]; // add label to current column
                        cy += size[1];
                        if (cy > height) {
                            height = cy;
                        }
                    }
                    else { // start new column if taller than texture
                        cx += widest;
                        cy = 0;
                        text_info.align[align].texture_position = [cx, cy];
                    }
                }
            }
        }

        return [cx + widest, height]; // overall atlas size
    }

    // Called before rasterization
    applyTextTransform (text, transform) {
        if (transform === 'capitalize') {
            return text.replace(/\w\S*/g, function (txt) {
                return txt.charAt(0).toUpperCase() + txt.substr(1);
            });
        }
        else if (transform === 'uppercase') {
            return text.toUpperCase();
        }
        else if (transform === 'lowercase') {
            return text.toLowerCase();
        }
        return text;
    }

    // Convert font CSS-style size ('12px', '14pt', '1.5em', etc.) to pixel size (adjusted for device pixel ratio)
    // Defaults units to pixels if not specified
    static fontPixelSize (size) {
        if (size == null) {
            return;
        }
        size = (typeof size === 'string') ? size : String(size); // need a string for regex

        let [, px_size, units] = size.match(CanvasText.font_size_re) || [];
        units = units || 'px';

        if (units === "em") {
            px_size *= 16;
        } else if (units === "pt") {
            px_size /= 0.75;
        } else if (units === "%") {
            px_size /= 6.25;
        }

        px_size = parseFloat(px_size);
        px_size *= Utils.device_pixel_ratio;
        return px_size;
    }

}

// Extract font size and units
CanvasText.font_size_re = /((?:[0-9]*\.)?[0-9]+)\s*(px|pt|em|%)?/;

// Cache sizes of rendered text
CanvasText.text_cache = {}; // by text style, then text string
CanvasText.cache_stats = { hits: 0, misses: 0 };

// Private class to arrange text labels into multiple lines based on
// "text wrap" and "max line" values
class MultiLine {
    constructor (context, max_lines = Infinity, text_wrap = Infinity) {
        this.width = 0;
        this.height = 0;
        this.lines = [];

        this.ellipsis = '...';
        this.ellipsis_width = context.measureText(this.ellipsis).width;

        this.max_lines = max_lines;
        this.text_wrap = text_wrap;
        this.context = context;
    }

    createLine (line_height){
        if (this.lines.length < this.max_lines){
            return new Line(line_height, this.text_wrap);
        }
        else {
            return false;
        }
    }

    push (line){
        if (this.lines.length < this.max_lines){
            // measure line width
            let line_width = this.context.measureText(line.text).width;
            line.width = line_width;

            if (line_width > this.width){
                this.width = Math.ceil(line_width);
            }

            // add to lines and increment height
            this.lines.push(line);
            this.height += line.height;
            return true;
        }
        else {
            this.addEllipsis();
            return false;
        }
    }

    // pushes to the lines array and returns a new line if possible (false otherwise)
    advance (line, line_height) {
        let can_push = this.push(line);
        if (can_push){
            return this.createLine(line_height);
        }
        else {
            return false;
        }
    }

    addEllipsis (){
        let last_line = this.lines[this.lines.length - 1];

        last_line.append(this.ellipsis);
        last_line.width += this.ellipsis_width;

        if (last_line.width > this.width) {
            this.width = last_line.width;
        }
    }

    finish (line){
        if (line){
            this.push(line);
        }
        else {
            this.addEllipsis();
        }
    }

    static parse (str, text_wrap, max_lines, line_height, ctx) {
        // Word wrapping
        // Line breaks can be caused by:
        //  - implicit line break when a maximum character threshold is exceeded per line (text_wrap)
        //  - explicit line break in the label text (\n)
        let words;
        if (typeof text_wrap === 'number') {
            words = str.split(' '); // split words on spaces
        }
        else {
            words = [str]; // no max line word wrapping (but new lines will still be in effect)
        }

        let multiline = new MultiLine(ctx, max_lines, text_wrap);
        let line = multiline.createLine(line_height);

        // First iterate on space-break groups (will be one if max line length off), then iterate on line-break groups
        for (let i = 0; i < words.length; i++) {
            let breaks = words[i].split('\n'); // split on line breaks
            let new_line = (i === 0) ? true : false;

            for (let n=0; n < breaks.length; n++) {
                if (!line){
                    break;
                }

                let word = breaks[n].trim();

                if (!word) {
                    continue;
                }

                let spaced_word = (new_line) ? word : ' ' + word;

                // if adding current word would overflow, add a new line instead
                // first word (i === 0) always appends
                if (text_wrap && i > 0 && line.exceedsTextwrap(spaced_word)) {
                    line = multiline.advance(line, line_height);
                    if (!line){
                        break;
                    }
                    line.append(word);
                    new_line = true;
                }
                else {
                    line.append(spaced_word);
                }

                // if line breaks present, add new line (unless on last line)
                if (n < breaks.length - 1) {
                    line = multiline.advance(line, line_height);
                    new_line = true;
                }
            }

            if (i === words.length - 1){
                multiline.finish(line);
            }
        }
        return multiline;
    }
}

// A Private class used by MultiLine to contain the logic for a single line
// including character count, width, height and text
class Line {
    constructor (height = 0, text_wrap = 0){
        this.chars = 0;
        this.text = '';

        this.height = height;
        this.text_wrap = text_wrap;
    }

    append (text){
        this.chars += text.length;
        this.text += text;
    }

    exceedsTextwrap (text){
        return text.length + this.chars > this.text_wrap;
    }
}
<|endoftext|>


<commit_before>import Utils from '../../utils/utils';
import Texture from '../../gl/texture';
import FontManager from './font_manager';
import debugSettings from '../../utils/debug_settings';

export default class CanvasText {

    constructor () {
        this.canvas = document.createElement('canvas');
        this.canvas.style.backgroundColor = 'transparent'; // render text on transparent background
        this.context = this.canvas.getContext('2d');
        this.text_buffer = 8; // pixel padding around text
    }

    resize (width, height) {
        this.canvas.width = width;
        this.canvas.height = height;
        this.context.clearRect(0, 0, width, height);
    }

    // Set font style params for canvas drawing
    setFont ({ font_css, fill, stroke, stroke_width, px_size }) {
        this.px_size = px_size;
        let ctx = this.context;

        if (stroke && stroke_width > 0) {
            ctx.strokeStyle = stroke;
            ctx.lineWidth = stroke_width;
        }
        ctx.fillStyle = fill;

        ctx.font = font_css;
        ctx.miterLimit = 2;
    }

    textSizes (texts) {
        return FontManager.loadFonts().then(() => {
            for (let style in texts) {
                let text_infos = texts[style];
                let first = true;

                for (let text in text_infos) {
                    // Use cached size, or compute via canvas
                    if (!CanvasText.text_cache[style] || !CanvasText.text_cache[style][text]) {
                        let text_settings = text_infos[text].text_settings;
                        if (first) {
                            this.setFont(text_settings);
                            first = false;
                        }

                        CanvasText.text_cache[style] = CanvasText.text_cache[style] || {};
                        CanvasText.text_cache[style][text] = this.textSize(text, text_settings);
                        CanvasText.cache_stats.misses++;
                    }
                    else {
                        CanvasText.cache_stats.hits++;
                    }

                    // Only send text sizes back to worker (keep computed text line info
                    // on main thread, for future rendering)
                    text_infos[text].size = CanvasText.text_cache[style][text].size;
                }
            }

            return texts;
        });
    }

    // Computes width and height of text based on current font style
    // Includes word wrapping, returns size info for whole text block and individual lines
    textSize (text, {transform, text_wrap, max_lines}) {
        let str = this.applyTextTransform(text, transform);
        let ctx = this.context;
        let buffer = this.text_buffer * Utils.device_pixel_ratio;
        let leading = 2 * Utils.device_pixel_ratio; // make configurable and/or use Canvas TextMetrics when available
        let line_height = this.px_size + leading; // px_size already in device pixels

        // Word wrapping
        // Line breaks can be caused by:
        //  - implicit line break when a maximum character threshold is exceeded per line (text_wrap)
        //  - explicit line break in the label text (\n)
        let words;
        if (typeof text_wrap === 'number') {
            words = str.split(' '); // split words on spaces
        }
        else {
            words = [str]; // no max line word wrapping (but new lines will still be in effect)
        }

        let multiline = new MultiLine(ctx, max_lines, text_wrap);
        let line = multiline.createLine(line_height);

        // First iterate on space-break groups (will be one if max line length off), then iterate on line-break groups
        for (let w=0; w < words.length; w++) {
            if (!line) {
                break;
            }

            let breaks = words[w].split('\n'); // split on line breaks

            for (let n=0; n < breaks.length; n++) {
                let word = breaks[n];

                if (!word) {
                    continue;
                }

                // if adding current word would overflow, add a new line instead
                if (line.exceedsTextwrap(word)) {
                    line = multiline.createLine(line_height);
                    if (!line){
                        break;
                    }
                }

                multiline.appendToLastLine(word);

                // if line breaks present, add new line (unless on last line)
                if (n < breaks.length - 1) {
                    line = multiline.createLine(line_height);
                    if (!line){
                        break;
                    }
                }
            }
        }

        // Final dimensions of text
        let height = multiline.height;
        let width = multiline.width;
        let lines = multiline.lines;

        let collision_size = [
            width / Utils.device_pixel_ratio,
            height / Utils.device_pixel_ratio
        ];

        let texture_size = [
            width + buffer * 2,
            height + buffer * 2
        ];

        let logical_size = texture_size.map(v => v / Utils.device_pixel_ratio);

        // Returns lines (w/per-line info for drawing) and text's overall bounding box + canvas size
        return {
            lines,
            size: { collision_size, texture_size, logical_size, line_height }
        };
    }

    // Draw one or more lines of text at specified location, adjusting for buffer and baseline
    drawText (lines, [x, y], size, { stroke, stroke_width, transform, align }) {
        align = align || 'center';

        for (let line_num=0; line_num < lines.length; line_num++) {
            let line = lines[line_num];
            let str = this.applyTextTransform(line.text, transform);
            let buffer = this.text_buffer * Utils.device_pixel_ratio;
            let texture_size = size.texture_size;
            let line_height = size.line_height;

            // Text alignment
            let tx;
            if (align === 'left') {
                tx = x + buffer;
            }
            else if (align === 'center') {
                tx = x + texture_size[0]/2 - line.width/2;
            }
            else if (align === 'right') {
                tx = x + texture_size[0] - line.width - buffer;
            }

            // In the absence of better Canvas TextMetrics (not supported by browsers yet),
            // 0.75 buffer produces a better approximate vertical centering of text
            let ty = y + buffer * 0.75 + (line_num + 1) * line_height;

            if (stroke && stroke_width > 0) {
                this.context.strokeText(str, tx, ty);
            }
            this.context.fillText(str, tx, ty);
        }

        // Draw bounding boxes for debugging
        if (debugSettings.draw_label_collision_boxes) {
            this.context.save();

            let dpr = Utils.device_pixel_ratio;
            let buffer = dpr * this.text_buffer;
            let collision_size = size.collision_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'blue';
            this.context.lineWidth = lineWidth;
            this.context.strokeRect(x + buffer, y + buffer, dpr * collision_size[0], dpr * collision_size[1]);

            this.context.restore();
        }

        if (debugSettings.draw_label_texture_boxes) {
            this.context.save();

            let texture_size = size.texture_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'green';
            this.context.lineWidth = lineWidth;
            // stroke is applied internally, so the outer border is the edge of the texture
            this.context.strokeRect(x + lineWidth, y + lineWidth, texture_size[0] - 2 * lineWidth, texture_size[1] - 2 * lineWidth);

            this.context.restore();
        }
    }

    rasterize (texts, texture_size) {
        for (let style in texts) {
            let text_infos = texts[style];
            let first = true;

            for (let text in text_infos) {
                let info = text_infos[text];
                let text_settings = info.text_settings;
                let lines = CanvasText.text_cache[style][text].lines; // get previously computed lines of text

                if (first) {
                    this.setFont(text_settings);
                    first = false;
                }

                // each alignment needs to be rendered separately
                for (let align in info.align) {
                    this.drawText(lines, info.align[align].texture_position, info.size, {
                        stroke: text_settings.stroke,
                        stroke_width: text_settings.stroke_width,
                        transform: text_settings.transform,
                        align: align
                    });

                    info.align[align].texcoords = Texture.getTexcoordsForSprite(
                        info.align[align].texture_position,
                        info.size.texture_size,
                        texture_size
                    );
                }
            }
        }
    }

    // Place text labels within an atlas of the given max size
    setTextureTextPositions (texts, max_texture_size) {
        // Find widest label
        let widest = 0;
        for (let style in texts) {
            let text_infos = texts[style];
            for (let text in text_infos) {
                let size = text_infos[text].size.texture_size;
                if (size[0] > widest) {
                    widest = size[0];
                }
            }
        }

        // Layout labels, stacked in columns
        let cx = 0, cy = 0; // current x/y position in atlas
        let height = 0;     // overall atlas height
        for (let style in texts) {
            let text_infos = texts[style];

            for (let text in text_infos) {
                let text_info = text_infos[text];
                // rendered size is same for all alignments
                let size = text_info.size.texture_size;

                // but each alignment needs to be rendered separately
                for (let align in text_info.align) {
                    if (cy + size[1] < max_texture_size) {
                        text_info.align[align].texture_position = [cx, cy]; // add label to current column
                        cy += size[1];
                        if (cy > height) {
                            height = cy;
                        }
                    }
                    else { // start new column if taller than texture
                        cx += widest;
                        cy = 0;
                        text_info.align[align].texture_position = [cx, cy];
                    }
                }
            }
        }

        return [cx + widest, height]; // overall atlas size
    }

    // Called before rasterization
    applyTextTransform (text, transform) {
        if (transform === 'capitalize') {
            return text.replace(/\w\S*/g, function (txt) {
                return txt.charAt(0).toUpperCase() + txt.substr(1);
            });
        }
        else if (transform === 'uppercase') {
            return text.toUpperCase();
        }
        else if (transform === 'lowercase') {
            return text.toLowerCase();
        }
        return text;
    }

    // Convert font CSS-style size ('12px', '14pt', '1.5em', etc.) to pixel size (adjusted for device pixel ratio)
    // Defaults units to pixels if not specified
    static fontPixelSize (size) {
        if (size == null) {
            return;
        }
        size = (typeof size === 'string') ? size : String(size); // need a string for regex

        let [, px_size, units] = size.match(CanvasText.font_size_re) || [];
        units = units || 'px';

        if (units === "em") {
            px_size *= 16;
        } else if (units === "pt") {
            px_size /= 0.75;
        } else if (units === "%") {
            px_size /= 6.25;
        }

        px_size = parseFloat(px_size);
        px_size *= Utils.device_pixel_ratio;
        return px_size;
    }

}

// Extract font size and units
CanvasText.font_size_re = /((?:[0-9]*\.)?[0-9]+)\s*(px|pt|em|%)?/;

// Cache sizes of rendered text
CanvasText.text_cache = {}; // by text style, then text string
CanvasText.cache_stats = { hits: 0, misses: 0 };

// Private class to arrange text labels into multiple lines based on
// "text wrap" and "max line" values
class MultiLine {
    constructor (context, max_lines = Infinity, text_wrap = Infinity) {
        this.width = 0;
        this.height = 0;
        this.lines = [];
        this.ellipsis = '...';

        this.max_lines = max_lines;
        this.text_wrap = text_wrap;
        this.context = context;
    }

    createLine (line_height){
        let line = new Line(line_height, this.text_wrap);
        return this.push(line);
    }

    appendToLastLine (text){
        if (!text) {
            return;
        }

        text = text.trim();

        let last = this.lines[this.lines.length - 1];
        let width = this.context.measureText(text).width;

        last.append(text, width);

        if (last.width > this.width){
            this.width = Math.ceil(last.width);
        }
    }

    push (line){
        if (this.lines.length < this.max_lines){
            this.lines.push(line);
            this.height += line.height;
            return line;
        }
        else {
            this.appendToLastLine(this.ellipsis);
            return false;
        }
    }
}

// A Private class used by MultiLine to contain the logic for a single line
// including character count, width, height and text
class Line {
    constructor (height = 0, text_wrap = 0){
        this.chars = 0;
        this.text = '';
        this.width = 0;

        this.height = height;
        this.text_wrap = text_wrap;
    }

    append (text, width){
        this.chars += text.length + 1;
        this.text += text + ' ';
        this.width += width;
    }

    exceedsTextwrap(text){
        return text.length + this.chars > this.text_wrap;
    }
}<commit_msg>Fix spacing issues<commit_after>import Utils from '../../utils/utils';
import Texture from '../../gl/texture';
import FontManager from './font_manager';
import debugSettings from '../../utils/debug_settings';

export default class CanvasText {

    constructor () {
        this.canvas = document.createElement('canvas');
        this.canvas.style.backgroundColor = 'transparent'; // render text on transparent background
        this.context = this.canvas.getContext('2d');
        this.text_buffer = 8; // pixel padding around text
    }

    resize (width, height) {
        this.canvas.width = width;
        this.canvas.height = height;
        this.context.clearRect(0, 0, width, height);
    }

    // Set font style params for canvas drawing
    setFont ({ font_css, fill, stroke, stroke_width, px_size }) {
        this.px_size = px_size;
        let ctx = this.context;

        if (stroke && stroke_width > 0) {
            ctx.strokeStyle = stroke;
            ctx.lineWidth = stroke_width;
        }
        ctx.fillStyle = fill;

        ctx.font = font_css;
        ctx.miterLimit = 2;
    }

    textSizes (texts) {
        return FontManager.loadFonts().then(() => {
            for (let style in texts) {
                let text_infos = texts[style];
                let first = true;

                for (let text in text_infos) {
                    // Use cached size, or compute via canvas
                    if (!CanvasText.text_cache[style] || !CanvasText.text_cache[style][text]) {
                        let text_settings = text_infos[text].text_settings;
                        if (first) {
                            this.setFont(text_settings);
                            first = false;
                        }

                        CanvasText.text_cache[style] = CanvasText.text_cache[style] || {};
                        CanvasText.text_cache[style][text] = this.textSize(text, text_settings);
                        CanvasText.cache_stats.misses++;
                    }
                    else {
                        CanvasText.cache_stats.hits++;
                    }

                    // Only send text sizes back to worker (keep computed text line info
                    // on main thread, for future rendering)
                    text_infos[text].size = CanvasText.text_cache[style][text].size;
                }
            }

            return texts;
        });
    }

    // Computes width and height of text based on current font style
    // Includes word wrapping, returns size info for whole text block and individual lines
    textSize (text, {transform, text_wrap, max_lines}) {
        let str = this.applyTextTransform(text, transform);
        let ctx = this.context;
        let buffer = this.text_buffer * Utils.device_pixel_ratio;
        let leading = 2 * Utils.device_pixel_ratio; // make configurable and/or use Canvas TextMetrics when available
        let line_height = this.px_size + leading; // px_size already in device pixels

        // Word wrapping
        // Line breaks can be caused by:
        //  - implicit line break when a maximum character threshold is exceeded per line (text_wrap)
        //  - explicit line break in the label text (\n)
        let words;
        if (typeof text_wrap === 'number') {
            words = str.split(' '); // split words on spaces
        }
        else {
            words = [str]; // no max line word wrapping (but new lines will still be in effect)
        }

        let multiline = new MultiLine(ctx, max_lines, text_wrap);
        let line = multiline.createLine(line_height);

        // First iterate on space-break groups (will be one if max line length off), then iterate on line-break groups
        for (let w=0; w < words.length; w++) {
            let breaks = words[w].split('\n'); // split on line breaks

            for (let n=0; n < breaks.length; n++) {
                if (!line){
                    break;
                }

                let word = breaks[n].trim();

                if (!word) {
                    continue;
                }

                // if adding current word would overflow, add a new line instead
                if (line.exceedsTextwrap(word)) {
                    line = multiline.advance(line, line_height);
                    if (!line){
                        break;
                    }
                }

                line.append(word);

                // if line breaks present, add new line (unless on last line)
                if (n < breaks.length - 1) {
                    line = multiline.advance(line, line_height);
                }
            }

            if (w === words.length - 1){
                multiline.finish(line);
            }
        }

        // Final dimensions of text
        let height = multiline.height;
        let width = multiline.width;
        let lines = multiline.lines;

        let collision_size = [
            width / Utils.device_pixel_ratio,
            height / Utils.device_pixel_ratio
        ];

        let texture_size = [
            width + buffer * 2,
            height + buffer * 2
        ];

        let logical_size = texture_size.map(v => v / Utils.device_pixel_ratio);

        // Returns lines (w/per-line info for drawing) and text's overall bounding box + canvas size
        return {
            lines,
            size: { collision_size, texture_size, logical_size, line_height }
        };
    }

    // Draw one or more lines of text at specified location, adjusting for buffer and baseline
    drawText (lines, [x, y], size, { stroke, stroke_width, transform, align }) {
        align = align || 'center';

        for (let line_num=0; line_num < lines.length; line_num++) {
            let line = lines[line_num];
            let str = this.applyTextTransform(line.text, transform);
            let buffer = this.text_buffer * Utils.device_pixel_ratio;
            let texture_size = size.texture_size;
            let line_height = size.line_height;

            // Text alignment
            let tx;
            if (align === 'left') {
                tx = x + buffer;
            }
            else if (align === 'center') {
                tx = x + texture_size[0]/2 - line.width/2;
            }
            else if (align === 'right') {
                tx = x + texture_size[0] - line.width - buffer;
            }

            // In the absence of better Canvas TextMetrics (not supported by browsers yet),
            // 0.75 buffer produces a better approximate vertical centering of text
            let ty = y + buffer * 0.75 + (line_num + 1) * line_height;

            if (stroke && stroke_width > 0) {
                this.context.strokeText(str, tx, ty);
            }
            this.context.fillText(str, tx, ty);
        }

        // Draw bounding boxes for debugging
        if (debugSettings.draw_label_collision_boxes) {
            this.context.save();

            let dpr = Utils.device_pixel_ratio;
            let buffer = dpr * this.text_buffer;
            let collision_size = size.collision_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'blue';
            this.context.lineWidth = lineWidth;
            this.context.strokeRect(x + buffer, y + buffer, dpr * collision_size[0], dpr * collision_size[1]);

            this.context.restore();
        }

        if (debugSettings.draw_label_texture_boxes) {
            this.context.save();

            let texture_size = size.texture_size;
            let lineWidth = 2;

            this.context.strokeStyle = 'green';
            this.context.lineWidth = lineWidth;
            // stroke is applied internally, so the outer border is the edge of the texture
            this.context.strokeRect(x + lineWidth, y + lineWidth, texture_size[0] - 2 * lineWidth, texture_size[1] - 2 * lineWidth);

            this.context.restore();
        }
    }

    rasterize (texts, texture_size) {
        for (let style in texts) {
            let text_infos = texts[style];
            let first = true;

            for (let text in text_infos) {
                let info = text_infos[text];
                let text_settings = info.text_settings;
                let lines = CanvasText.text_cache[style][text].lines; // get previously computed lines of text

                if (first) {
                    this.setFont(text_settings);
                    first = false;
                }

                // each alignment needs to be rendered separately
                for (let align in info.align) {
                    this.drawText(lines, info.align[align].texture_position, info.size, {
                        stroke: text_settings.stroke,
                        stroke_width: text_settings.stroke_width,
                        transform: text_settings.transform,
                        align: align
                    });

                    info.align[align].texcoords = Texture.getTexcoordsForSprite(
                        info.align[align].texture_position,
                        info.size.texture_size,
                        texture_size
                    );
                }
            }
        }
    }

    // Place text labels within an atlas of the given max size
    setTextureTextPositions (texts, max_texture_size) {
        // Find widest label
        let widest = 0;
        for (let style in texts) {
            let text_infos = texts[style];
            for (let text in text_infos) {
                let size = text_infos[text].size.texture_size;
                if (size[0] > widest) {
                    widest = size[0];
                }
            }
        }

        // Layout labels, stacked in columns
        let cx = 0, cy = 0; // current x/y position in atlas
        let height = 0;     // overall atlas height
        for (let style in texts) {
            let text_infos = texts[style];

            for (let text in text_infos) {
                let text_info = text_infos[text];
                // rendered size is same for all alignments
                let size = text_info.size.texture_size;

                // but each alignment needs to be rendered separately
                for (let align in text_info.align) {
                    if (cy + size[1] < max_texture_size) {
                        text_info.align[align].texture_position = [cx, cy]; // add label to current column
                        cy += size[1];
                        if (cy > height) {
                            height = cy;
                        }
                    }
                    else { // start new column if taller than texture
                        cx += widest;
                        cy = 0;
                        text_info.align[align].texture_position = [cx, cy];
                    }
                }
            }
        }

        return [cx + widest, height]; // overall atlas size
    }

    // Called before rasterization
    applyTextTransform (text, transform) {
        if (transform === 'capitalize') {
            return text.replace(/\w\S*/g, function (txt) {
                return txt.charAt(0).toUpperCase() + txt.substr(1);
            });
        }
        else if (transform === 'uppercase') {
            return text.toUpperCase();
        }
        else if (transform === 'lowercase') {
            return text.toLowerCase();
        }
        return text;
    }

    // Convert font CSS-style size ('12px', '14pt', '1.5em', etc.) to pixel size (adjusted for device pixel ratio)
    // Defaults units to pixels if not specified
    static fontPixelSize (size) {
        if (size == null) {
            return;
        }
        size = (typeof size === 'string') ? size : String(size); // need a string for regex

        let [, px_size, units] = size.match(CanvasText.font_size_re) || [];
        units = units || 'px';

        if (units === "em") {
            px_size *= 16;
        } else if (units === "pt") {
            px_size /= 0.75;
        } else if (units === "%") {
            px_size /= 6.25;
        }

        px_size = parseFloat(px_size);
        px_size *= Utils.device_pixel_ratio;
        return px_size;
    }

}

// Extract font size and units
CanvasText.font_size_re = /((?:[0-9]*\.)?[0-9]+)\s*(px|pt|em|%)?/;

// Cache sizes of rendered text
CanvasText.text_cache = {}; // by text style, then text string
CanvasText.cache_stats = { hits: 0, misses: 0 };

// Private class to arrange text labels into multiple lines based on
// "text wrap" and "max line" values
class MultiLine {
    constructor (context, max_lines = Infinity, text_wrap = Infinity) {
        this.width = 0;
        this.height = 0;
        this.lines = [];

        this.ellipsis = '...';
        this.ellipsis_width = context.measureText(this.ellipsis).width;

        this.max_lines = max_lines;
        this.text_wrap = text_wrap;
        this.context = context;
    }

    createLine (line_height){
        if (this.lines.length < this.max_lines){
            return new Line(line_height, this.text_wrap);
        }
        else {
            return false;
        }
    }

    push (line){
        // remove last space from previous line
        if (this.lines.length > 0){
            let last_line = this.lines[this.lines.length - 1];
            last_line.text = last_line.text.slice(0, last_line.text.length - 1);
        }

        if (this.lines.length < this.max_lines){
            // measure line width
            let line_width = this.context.measureText(line.text).width;
            line.width = line_width;

            if (line_width > this.width){
                this.width = Math.ceil(line_width);
            }

            // add to lines and increment height
            this.lines.push(line);
            this.height += line.height;
            return true;
        }
        else {
            this.addEllipsis();
            return false;
        }
    }

    // pushes to the lines array and returns a new line if possible (false otherwise)
    advance (line, line_height) {
        let can_push = this.push(line);
        if (can_push){
            return this.createLine(line_height);
        }
        else {
            return false;
        }
    }

    addEllipsis (){
        let last_line = this.lines[this.lines.length - 1];
        last_line.append(this.ellipsis);
        last_line.width += this.ellipsis_width;
        if (last_line.width > this.width) {
            this.width = last_line.width;
        }
    }

    finish (line){
        if (line){
            this.push(line);
        }

        if (this.max_lines === this.lines.length) {
            this.addEllipsis();
        }
    }
}

// A Private class used by MultiLine to contain the logic for a single line
// including character count, width, height and text
class Line {
    constructor (height = 0, text_wrap = 0){
        this.chars = 0;
        this.text = '';

        this.height = height;
        this.text_wrap = text_wrap;
    }

    append (text){
        text += ' '; // add space (to be removed later if necessary)
        this.chars += text.length;
        this.text += text;
    }

    exceedsTextwrap(text){
        return text.length + this.chars > this.text_wrap;
    }
}<|endoftext|>


<commit_before>var CatalinSeoHandler = {
    listenersBinded: false,
    isAjaxEnabled: false,
    priceSlider: {
        urlTemplate: '',
        minPrice: 0,
        maxPrice: 0,
        currentMinPrice: 0,
        currentMaxPrice: 0
    },
    handlePriceEvent: function (val) {
        var self = this;
        if (val) {
            var url = self.priceSlider.urlTemplate.replace('__PRICE_VALUE__', val);
            if (self.isAjaxEnabled) {
                self.handleEvent(url);
            } else {
                window.location.href = url;
            }
        }
    },
    handleEvent: function (el, event) {
        var url, fullUrl;
        var self = this;
        if (typeof el === 'string') {
            url = el;
        } else if (el.tagName.toLowerCase() === 'a') {
            url = $(el).readAttribute('href');
        } else if (el.tagName.toLowerCase() === 'select') {
            url = $(el).getValue();
        }

        // Add this to query string for full page caching systems
        if (url.indexOf('?') != -1) {
            fullUrl = url + '&isLayerAjax=1';
        } else {
            fullUrl = url + '?isLayerAjax=1';
        }

        $('loading').show();
        $('ajax-errors').hide();

        self.pushState(null, url, false);

        new Ajax.Request(fullUrl, {
            method: 'get',
            onSuccess: function (transport) {
                if (transport.responseJSON) {
                    $('catalog-listing').update(transport.responseJSON.listing);
                    $('layered-navigation').update(transport.responseJSON.layer);
                    self.pushState({
                        listing: transport.responseJSON.listing,
                        layer: transport.responseJSON.layer
                    }, url, true);
                    self.ajaxListener();
                    self.toggleContent();
                    self.alignProductGridActions();
                    self.blockCollapsing();
                    if (ConfigurableSwatchesList) {
                        ConfigurableSwatchesList.init();
                    }
                } else {
                    $('ajax-errors').show();
                }
                $('loading').hide();
            }
        });

        if (event) {
            event.preventDefault();
        }
    },
    pushState: function (data, link, replace) {
        var History = window.History;
        if (!History.enabled) {
            return false;
        }

        if (replace) {
            History.replaceState(data, document.title, link);
        } else {
            History.pushState(data, document.title, link);
        }
    },
    ajaxListener: function () {
        var self = this;
        var els;
        els = $$('div.pager a').concat(
            $$('div.sorter a'),
            $$('div.pager select'),
            $$('div.sorter select'),
            $$('div.block-layered-nav a')
        );
        els.each(function (el) {
            if (el.tagName.toLowerCase() === 'a') {
                $(el).observe('click', function (event) {
                    self.handleEvent(this, event);
                });
            } else if (el.tagName.toLowerCase() === 'select') {
                $(el).setAttribute('onchange', '');
                $(el).observe('change', function (event) {
                    self.handleEvent(this, event);
                });
            }
        });
    },
    bindPriceSlider: function () {
        var self = this;
        new Control.Slider([$('price-min'), $('price-max')], 'price-range', {
                range: $R(self.priceSlider.minPrice, self.priceSlider.maxPrice),
                sliderValue: [self.priceSlider.currentMinPrice, self.priceSlider.currentMaxPrice],
                values: $R(self.priceSlider.minPrice, self.priceSlider.maxPrice),

                restricted: true,
                onChange: function (val) {
                    if (val[0] != self.priceSlider.currentMinPrice || val[1] != self.priceSlider.currentMaxPrice) {
                        $('button-price-slider').value = val.join('-');
                    }
                },
                onSlide: function (val) {
                    $('price-max-display').innerHTML = val[1];
                    $('price-min-display').innerHTML = val[0];
                }
            }
        );
    },
    bindListeners: function () {
        var self = this;
        if (self.listenersBinded || !self.isAjaxEnabled) {
            return false;
        }
        self.listenersBinded = true;
        document.observe("dom:loaded", function () {
            self.ajaxListener();

            (function (History) {
                if (!History.enabled) {
                    return false;
                }

                self.pushState({
                    listing: $('catalog-listing').innerHTML,
                    layer: $('layered-navigation').innerHTML
                }, document.location.href, true);

                // Bind to StateChange Event
                History.Adapter.bind(window, 'popstate', function (event) {
                    if (event.type == 'popstate') {
                        var State = History.getState();
                        $('catalog-listing').update(State.data.listing);
                        $('layered-navigation').update(State.data.layer);
                        self.ajaxListener();
                        self.toggleContent();
                        self.alignProductGridActions();
                        self.blockCollapsing();
                        if (ConfigurableSwatchesList) {
                            ConfigurableSwatchesList.init();
                        }
                    }
                });
            })(window.History);
        });
    },
    toggleContent: function() {
        // ==============================================
        // UI Pattern - Toggle Content (tabs and accordions in one setup)
        // ==============================================

        $j('.toggle-content').each(function () {
            var wrapper = jQuery(this);

            var hasTabs = wrapper.hasClass('tabs');
            var hasAccordion = wrapper.hasClass('accordion');
            var startOpen = wrapper.hasClass('open');

            var dl = wrapper.children('dl:first');
            var dts = dl.children('dt');
            var panes = dl.children('dd');
            var groups = new Array(dts, panes);

            //Create a ul for tabs if necessary.
            if (hasTabs) {
                var ul = jQuery('

');
                dts.each(function () {
                    var dt = jQuery(this);
                    var li = jQuery('

');
                    li.html(dt.html());
                    ul.append(li);
                });
                ul.insertBefore(dl);
                var lis = ul.children();
                groups.push(lis);
            }

            //Add "last" classes.
            var i;
            for (i = 0; i < groups.length; i++) {
                groups[i].filter(':last').addClass('last');
            }

            function toggleClasses(clickedItem, group) {
                var index = group.index(clickedItem);
                var i;
                for (i = 0; i < groups.length; i++) {
                    groups[i].removeClass('current');
                    groups[i].eq(index).addClass('current');
                }
            }

            //Toggle on tab (dt) click.
            dts.on('click', function (e) {
                //They clicked the current dt to close it. Restore the wrapper to unclicked state.
                if (jQuery(this).hasClass('current') && wrapper.hasClass('accordion-open')) {
                    wrapper.removeClass('accordion-open');
                } else {
                    //They're clicking something new. Reflect the explicit user interaction.
                    wrapper.addClass('accordion-open');
                }
                toggleClasses(jQuery(this), dts);
            });

            //Toggle on tab (li) click.
            if (hasTabs) {
                lis.on('click', function (e) {
                    toggleClasses(jQuery(this), lis);
                });
                //Open the first tab.
                lis.eq(0).trigger('click');
            }

            //Open the first accordion if desired.
            if (startOpen) {
                dts.eq(0).trigger('click');
            }

        });
    },
    alignProductGridActions: function() {
        // ==============================================
        // Product Listing - Align action buttons/links
        // ==============================================

        // Since the number of columns per grid will vary based on the viewport size, the only way to align the action
        // buttons/links is via JS

        if ($j('.products-grid').length) {

            var alignProductGridActions = function () {
                // Loop through each product grid on the page
                $j('.products-grid').each(function(){
                    var gridRows = []; // This will store an array per row
                    var tempRow = [];
                    productGridElements = $j(this).children('li');
                    productGridElements.each(function (index) {
                        // The JS ought to be agnostic of the specific CSS breakpoints, so we are dynamically checking to find
                        // each row by grouping all cells (eg, li elements) up until we find an element that is cleared.
                        // We are ignoring the first cell since it will always be cleared.
                        if ($j(this).css('clear') != 'none' && index != 0) {
                            gridRows.push(tempRow); // Add the previous set of rows to the main array
                            tempRow = []; // Reset the array since we're on a new row
                        }
                        tempRow.push(this);

                        // The last row will not contain any cells that clear that row, so we check to see if this is the last cell
                        // in the grid, and if so, we add its row to the array
                        if (productGridElements.length == index + 1) {
                            gridRows.push(tempRow);
                        }
                    });

                    $j.each(gridRows, function () {
                        var tallestProductInfo = 0;
                        $j.each(this, function () {
                            // Since this function is called every time the page is resized, we need to remove the min-height
                            // and bottom-padding so each cell can return to its natural size before being measured.
                            $j(this).find('.product-info').css({
                                'min-height': '',
                                'padding-bottom': ''
                            });

                            // We are checking the height of .product-info (rather than the entire li), because the images
                            // will not be loaded when this JS is run.
                            var productInfoHeight = $j(this).find('.product-info').height();
                            // Space above .actions element
                            var actionSpacing = 10;
                            // The height of the absolutely positioned .actions element
                            var actionHeight = $j(this).find('.product-info .actions').height();

                            // Add height of two elements. This is necessary since .actions is absolutely positioned and won't
                            // be included in the height of .product-info
                            var totalHeight = productInfoHeight + actionSpacing + actionHeight;
                            if (totalHeight > tallestProductInfo) {
                                tallestProductInfo = totalHeight;
                            }

                            // Set the bottom-padding to accommodate the height of the .actions element. Note: if .actions
                            // elements are of varying heights, they will not be aligned.
                            $j(this).find('.product-info').css('padding-bottom', actionHeight + 'px');
                        });
                        // Set the height of all .product-info elements in a row to the tallest height
                        $j.each(this, function () {
                            $j(this).find('.product-info').css('min-height', tallestProductInfo);
                        });
                    });
                });
            }
            alignProductGridActions();
        }
    },
    blockCollapsing: function() {
        // ==============================================
        // Block collapsing (on smaller viewports)
        // ==============================================

        enquire.register('(max-width: ' + bp.medium + 'px)', {
            setup: function () {
                this.toggleElements = $j(
                    // This selects the menu on the My Account and CMS pages
                    '.col-left-first .block:not(.block-layered-nav) .block-title, ' +
                    '.col-left-first .block-layered-nav .block-subtitle--filter, ' +
                    '.sidebar:not(.col-left-first) .block .block-title'
                );
            },
            match: function () {
                this.toggleElements.toggleSingle();
            },
            unmatch: function () {
                this.toggleElements.toggleSingle({destruct: true});
            }
        });
    }
}<commit_msg>CE updates for bug with CLP product swatch onclick handler:<commit_after>var CatalinSeoHandler = {
    listenersBinded: false,
    isAjaxEnabled: false,
    priceSlider: {
        urlTemplate: '',
        minPrice: 0,
        maxPrice: 0,
        currentMinPrice: 0,
        currentMaxPrice: 0
    },
    handlePriceEvent: function (val) {
        var self = this;
        if (val) {
            var url = self.priceSlider.urlTemplate.replace('__PRICE_VALUE__', val);
            if (self.isAjaxEnabled) {
                self.handleEvent(url);
            } else {
                window.location.href = url;
            }
        }
    },
    handleEvent: function (el, event) {
        var url, fullUrl;
        var self = this;
        if (typeof el === 'string') {
            url = el;
        } else if (el.tagName.toLowerCase() === 'a') {
            url = $(el).readAttribute('href');
        } else if (el.tagName.toLowerCase() === 'select') {
            url = $(el).getValue();
        }

        // Add this to query string for full page caching systems
        if (url.indexOf('?') != -1) {
            fullUrl = url + '&isLayerAjax=1';
        } else {
            fullUrl = url + '?isLayerAjax=1';
        }

        $('loading').show();
        $('ajax-errors').hide();

        self.pushState(null, url, false);

        new Ajax.Request(fullUrl, {
            method: 'get',
            onSuccess: function (transport) {
                if (transport.responseJSON) {
                    $('catalog-listing').update(transport.responseJSON.listing);
                    $('layered-navigation').update(transport.responseJSON.layer);
                    self.pushState({
                        listing: transport.responseJSON.listing,
                        layer: transport.responseJSON.layer
                    }, url, true);
                    self.ajaxListener();
                    self.toggleContent();
                    self.alignProductGridActions();
                    self.blockCollapsing();

                    if (ConfigurableSwatchesList) {
                        setTimeout(function(){
                            $j(document).trigger('product-media-loaded');
                        }, 0);
                    }
                } else {
                    $('ajax-errors').show();
                }
                $('loading').hide();
            }
        });

        if (event) {
            event.preventDefault();
        }
    },
    pushState: function (data, link, replace) {
        var History = window.History;
        if (!History.enabled) {
            return false;
        }

        if (replace) {
            History.replaceState(data, document.title, link);
        } else {
            History.pushState(data, document.title, link);
        }
    },
    ajaxListener: function () {
        var self = this;
        var els;
        els = $$('div.pager a').concat(
            $$('div.sorter a'),
            $$('div.pager select'),
            $$('div.sorter select'),
            $$('div.block-layered-nav a')
        );
        els.each(function (el) {
            if (el.tagName.toLowerCase() === 'a') {
                $(el).observe('click', function (event) {
                    self.handleEvent(this, event);
                });
            } else if (el.tagName.toLowerCase() === 'select') {
                $(el).setAttribute('onchange', '');
                $(el).observe('change', function (event) {
                    self.handleEvent(this, event);
                });
            }
        });
    },
    bindPriceSlider: function () {
        var self = this;
        new Control.Slider([$('price-min'), $('price-max')], 'price-range', {
                range: $R(self.priceSlider.minPrice, self.priceSlider.maxPrice),
                sliderValue: [self.priceSlider.currentMinPrice, self.priceSlider.currentMaxPrice],
                values: $R(self.priceSlider.minPrice, self.priceSlider.maxPrice),

                restricted: true,
                onChange: function (val) {
                    if (val[0] != self.priceSlider.currentMinPrice || val[1] != self.priceSlider.currentMaxPrice) {
                        $('button-price-slider').value = val.join('-');
                    }
                },
                onSlide: function (val) {
                    $('price-max-display').innerHTML = val[1];
                    $('price-min-display').innerHTML = val[0];
                }
            }
        );
    },
    bindListeners: function () {
        var self = this;
        if (self.listenersBinded || !self.isAjaxEnabled) {
            return false;
        }
        self.listenersBinded = true;
        document.observe("dom:loaded", function () {
            self.ajaxListener();

            (function (History) {
                if (!History.enabled) {
                    return false;
                }

                self.pushState({
                    listing: $('catalog-listing').innerHTML,
                    layer: $('layered-navigation').innerHTML
                }, document.location.href, true);

                // Bind to StateChange Event
                History.Adapter.bind(window, 'popstate', function (event) {
                    if (event.type == 'popstate') {
                        var State = History.getState();
                        $('catalog-listing').update(State.data.listing);
                        $('layered-navigation').update(State.data.layer);
                        self.ajaxListener();
                        self.toggleContent();
                        self.alignProductGridActions();
                        self.blockCollapsing();

                        if (ConfigurableSwatchesList) {
                            setTimeout(function(){
                                $j(document).trigger('product-media-loaded');
                            }, 0);
                        }
                    }
                });
            })(window.History);
        });
    },
    toggleContent: function() {
        // ==============================================
        // UI Pattern - Toggle Content (tabs and accordions in one setup)
        // ==============================================

        $j('.toggle-content').each(function () {
            var wrapper = jQuery(this);

            var hasTabs = wrapper.hasClass('tabs');
            var hasAccordion = wrapper.hasClass('accordion');
            var startOpen = wrapper.hasClass('open');

            var dl = wrapper.children('dl:first');
            var dts = dl.children('dt');
            var panes = dl.children('dd');
            var groups = new Array(dts, panes);

            //Create a ul for tabs if necessary.
            if (hasTabs) {
                var ul = jQuery('

');
                dts.each(function () {
                    var dt = jQuery(this);
                    var li = jQuery('

');
                    li.html(dt.html());
                    ul.append(li);
                });
                ul.insertBefore(dl);
                var lis = ul.children();
                groups.push(lis);
            }

            //Add "last" classes.
            var i;
            for (i = 0; i < groups.length; i++) {
                groups[i].filter(':last').addClass('last');
            }

            function toggleClasses(clickedItem, group) {
                var index = group.index(clickedItem);
                var i;
                for (i = 0; i < groups.length; i++) {
                    groups[i].removeClass('current');
                    groups[i].eq(index).addClass('current');
                }
            }

            //Toggle on tab (dt) click.
            dts.on('click', function (e) {
                //They clicked the current dt to close it. Restore the wrapper to unclicked state.
                if (jQuery(this).hasClass('current') && wrapper.hasClass('accordion-open')) {
                    wrapper.removeClass('accordion-open');
                } else {
                    //They're clicking something new. Reflect the explicit user interaction.
                    wrapper.addClass('accordion-open');
                }
                toggleClasses(jQuery(this), dts);
            });

            //Toggle on tab (li) click.
            if (hasTabs) {
                lis.on('click', function (e) {
                    toggleClasses(jQuery(this), lis);
                });
                //Open the first tab.
                lis.eq(0).trigger('click');
            }

            //Open the first accordion if desired.
            if (startOpen) {
                dts.eq(0).trigger('click');
            }

        });
    },
    alignProductGridActions: function() {
        // ==============================================
        // Product Listing - Align action buttons/links
        // ==============================================

        // Since the number of columns per grid will vary based on the viewport size, the only way to align the action
        // buttons/links is via JS

        if ($j('.products-grid').length) {

            var alignProductGridActions = function () {
                // Loop through each product grid on the page
                $j('.products-grid').each(function(){
                    var gridRows = []; // This will store an array per row
                    var tempRow = [];
                    productGridElements = $j(this).children('li');
                    productGridElements.each(function (index) {
                        // The JS ought to be agnostic of the specific CSS breakpoints, so we are dynamically checking to find
                        // each row by grouping all cells (eg, li elements) up until we find an element that is cleared.
                        // We are ignoring the first cell since it will always be cleared.
                        if ($j(this).css('clear') != 'none' && index != 0) {
                            gridRows.push(tempRow); // Add the previous set of rows to the main array
                            tempRow = []; // Reset the array since we're on a new row
                        }
                        tempRow.push(this);

                        // The last row will not contain any cells that clear that row, so we check to see if this is the last cell
                        // in the grid, and if so, we add its row to the array
                        if (productGridElements.length == index + 1) {
                            gridRows.push(tempRow);
                        }
                    });

                    $j.each(gridRows, function () {
                        var tallestProductInfo = 0;
                        $j.each(this, function () {
                            // Since this function is called every time the page is resized, we need to remove the min-height
                            // and bottom-padding so each cell can return to its natural size before being measured.
                            $j(this).find('.product-info').css({
                                'min-height': '',
                                'padding-bottom': ''
                            });

                            // We are checking the height of .product-info (rather than the entire li), because the images
                            // will not be loaded when this JS is run.
                            var productInfoHeight = $j(this).find('.product-info').height();
                            // Space above .actions element
                            var actionSpacing = 10;
                            // The height of the absolutely positioned .actions element
                            var actionHeight = $j(this).find('.product-info .actions').height();

                            // Add height of two elements. This is necessary since .actions is absolutely positioned and won't
                            // be included in the height of .product-info
                            var totalHeight = productInfoHeight + actionSpacing + actionHeight;
                            if (totalHeight > tallestProductInfo) {
                                tallestProductInfo = totalHeight;
                            }

                            // Set the bottom-padding to accommodate the height of the .actions element. Note: if .actions
                            // elements are of varying heights, they will not be aligned.
                            $j(this).find('.product-info').css('padding-bottom', actionHeight + 'px');
                        });
                        // Set the height of all .product-info elements in a row to the tallest height
                        $j.each(this, function () {
                            $j(this).find('.product-info').css('min-height', tallestProductInfo);
                        });
                    });
                });
            }
            alignProductGridActions();
        }
    },
    blockCollapsing: function() {
        // ==============================================
        // Block collapsing (on smaller viewports)
        // ==============================================

        enquire.register('(max-width: ' + bp.medium + 'px)', {
            setup: function () {
                this.toggleElements = $j(
                    // This selects the menu on the My Account and CMS pages
                    '.col-left-first .block:not(.block-layered-nav) .block-title, ' +
                    '.col-left-first .block-layered-nav .block-subtitle--filter, ' +
                    '.sidebar:not(.col-left-first) .block .block-title'
                );
            },
            match: function () {
                this.toggleElements.toggleSingle();
            },
            unmatch: function () {
                this.toggleElements.toggleSingle({destruct: true});
            }
        });
    }
}<|endoftext|>


<commit_before>import _ from 'messages'
import CopyToClipboard from 'react-copy-to-clipboard'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import React from 'react'
import TabButton from 'tab-button'
import { Toggle } from 'form'
import { Size, Text } from 'editable'
import { Container, Row, Col } from 'grid'
import { formatSize, normalizeXenToolsStatus, osFamily } from 'utils'
import {
  cloneVm,
  convertVmToTemplate,
  deleteVm,
  editVm,
  recoveryStartVm,
  restartVm,
  resumeVm,
  stopVm,
  suspendVm
} from 'xo'

const forceReboot = vm => restartVm(vm, true)
const forceShutdown = vm => stopVm(vm, true)
const fullCopy = vm => cloneVm(vm, true)

export default ({
  vm
}) => <Container>
  <Row>
    <Col mediumSize={12} className='text-xs-right'>
      {vm.power_state === 'Running' &&

          <TabButton
            btnStyle='primary'
            handler={suspendVm}
            handlerParam={vm}
            icon='vm-suspend'
            labelId='suspendVmLabel'
          />
          <TabButton
            btnStyle='warning'
            handler={forceReboot}
            handlerParam={vm}
            icon='vm-force-reboot'
            labelId='forceRebootVmLabel'
          />
          <TabButton
            btnStyle='warning'
            handler={forceShutdown}
            handlerParam={vm}
            icon='vm-force-shutdown'
            labelId='forceShutdownVmLabel'
          />

      }
      {vm.power_state === 'Halted' &&

          <TabButton
            btnStyle='primary'
            handler={recoveryStartVm}
            handlerParam={vm}
            icon='vm-recovery-mode'
            labelId='recoveryModeLabel'
          />
          <TabButton
            btnStyle='primary'
            handler={fullCopy}
            icon='vm-clone'
            labelId='cloneVmLabel'
          />
          <TabButton
            btnStyle='danger'
            handler={convertVmToTemplate}
            handlerParam={vm}
            icon='vm-create-template'
            labelId='vmConvertButton'
          />

      }
      {vm.power_state === 'Suspended' &&

          <TabButton
            btnStyle='primary'
            handler={resumeVm}
            handlerParam={vm}
            icon='vm-start'
            labelId='resumeVmLabel'
          />
          <TabButton
            btnStyle='warning'
            handler={forceShutdown}
            handlerParam={vm}
            icon='vm-force-shutdown'
            labelId='forceShutdownVmLabel'
          />

      }
      <TabButton
        btnStyle='danger'
        handler={deleteVm}
        handlerParam={vm}
        icon='vm-delete'
        labelId='vmRemoveButton'
      />
    </Col>
  </Row>
  <Row>
    <Col mediumSize={12}>

{_('xenSettingsLabel')}

            {_('uuid')}

              {vm.uuid}
              {' '}
              <CopyToClipboard text={vm.uuid}>
                <button className='btn btn-sm btn-secondary btn-copy-to-clipboard'>
                  <Icon icon='clipboard' />
                </button>
              </CopyToClipboard>

            {_('virtualizationMode')}

              {vm.virtualizationMode === 'pv'
                ? _('paraVirtualizedMode')
                : _('hardwareVirtualizedMode')
              }

          {vm.PV_args &&

              {_('pvArgsLabel')}

                <Text value={vm.PV_args} onChange={value => editVm(vm, { PV_args: value })} />

          }

            {_('cpuWeightLabel')}
            {vm.cpuWeight
              ? {vm.cpuWeight}
              : {_('defaultCpuWeight')}
            }

            {_('autoPowerOn')}
            <Toggle defaultValue={vm.auto_poweron} onChange={value => editVm(vm, { auto_poweron: value })} />

            {_('ha')}
            <Toggle defaultValue={vm.high_availability} onChange={value => editVm(vm, { high_availability: value })} />

{_('vmLimitsLabel')}

            {_('vmCpuLimitsLabel')}

              {vm.power_state === 'Running'
                ? vm.CPUs.max
                : <Text value={vm.CPUs.max} onChange={value => editVm(vm, { cpusMax: value })} />
              }

            {_('vmMemoryLimitsLabel')}

Static: {formatSize(vm.memory.static[0])}/<Size value={vm.memory.static[1]} onChange={memoryStaticMax => editVm(vm, { memoryStaticMax })} />

Dynamic: <Size value={vm.memory.dynamic[0]} onChange={memoryMin => editVm(vm, { memoryMin })} />/<Size value={vm.memory.dynamic[1]} onChange={memoryMax => editVm(vm, { memoryMax })} />

{_('guestOsLabel')}

            {_('xenToolsStatus')}
            {_('xenToolsStatusValue', { status: normalizeXenToolsStatus(vm.xenTools) })}

            {_('osName')}
            {isEmpty(vm.os_version)
                ? _('unknownOsName')
                : <Icon icon={osFamily(vm.os_version.distro)} /> {vm.os_version.name}
                }

            {_('osKernel')}
            {vm.os_version ? vm.os_version.uname ? vm.os_version.uname : _('unknownOsKernel') : _('unknownOsKernel')}

{_('miscLabel')}

            {_('originalTemplate')}
            {vm.other.base_template_name ? vm.other.base_template_name : _('unknownOriginalTemplate')}

    </Col>
  </Row>
</Container>
<commit_msg>fix(vm): use Number component instead of Text for vCPU max number edition<commit_after>import _ from 'messages'
import CopyToClipboard from 'react-copy-to-clipboard'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import React from 'react'
import TabButton from 'tab-button'
import { Toggle } from 'form'
import { Number, Size, Text } from 'editable'
import { Container, Row, Col } from 'grid'
import { formatSize, normalizeXenToolsStatus, osFamily } from 'utils'
import {
  cloneVm,
  convertVmToTemplate,
  deleteVm,
  editVm,
  recoveryStartVm,
  restartVm,
  resumeVm,
  stopVm,
  suspendVm
} from 'xo'

const forceReboot = vm => restartVm(vm, true)
const forceShutdown = vm => stopVm(vm, true)
const fullCopy = vm => cloneVm(vm, true)

export default ({
  vm
}) => <Container>
  <Row>
    <Col mediumSize={12} className='text-xs-right'>
      {vm.power_state === 'Running' &&

          <TabButton
            btnStyle='primary'
            handler={suspendVm}
            handlerParam={vm}
            icon='vm-suspend'
            labelId='suspendVmLabel'
          />
          <TabButton
            btnStyle='warning'
            handler={forceReboot}
            handlerParam={vm}
            icon='vm-force-reboot'
            labelId='forceRebootVmLabel'
          />
          <TabButton
            btnStyle='warning'
            handler={forceShutdown}
            handlerParam={vm}
            icon='vm-force-shutdown'
            labelId='forceShutdownVmLabel'
          />

      }
      {vm.power_state === 'Halted' &&

          <TabButton
            btnStyle='primary'
            handler={recoveryStartVm}
            handlerParam={vm}
            icon='vm-recovery-mode'
            labelId='recoveryModeLabel'
          />
          <TabButton
            btnStyle='primary'
            handler={fullCopy}
            icon='vm-clone'
            labelId='cloneVmLabel'
          />
          <TabButton
            btnStyle='danger'
            handler={convertVmToTemplate}
            handlerParam={vm}
            icon='vm-create-template'
            labelId='vmConvertButton'
          />

      }
      {vm.power_state === 'Suspended' &&

          <TabButton
            btnStyle='primary'
            handler={resumeVm}
            handlerParam={vm}
            icon='vm-start'
            labelId='resumeVmLabel'
          />
          <TabButton
            btnStyle='warning'
            handler={forceShutdown}
            handlerParam={vm}
            icon='vm-force-shutdown'
            labelId='forceShutdownVmLabel'
          />

      }
      <TabButton
        btnStyle='danger'
        handler={deleteVm}
        handlerParam={vm}
        icon='vm-delete'
        labelId='vmRemoveButton'
      />
    </Col>
  </Row>
  <Row>
    <Col mediumSize={12}>

{_('xenSettingsLabel')}

            {_('uuid')}

              {vm.uuid}
              {' '}
              <CopyToClipboard text={vm.uuid}>
                <button className='btn btn-sm btn-secondary btn-copy-to-clipboard'>
                  <Icon icon='clipboard' />
                </button>
              </CopyToClipboard>

            {_('virtualizationMode')}

              {vm.virtualizationMode === 'pv'
                ? _('paraVirtualizedMode')
                : _('hardwareVirtualizedMode')
              }

          {vm.PV_args &&

              {_('pvArgsLabel')}

                <Text value={vm.PV_args} onChange={value => editVm(vm, { PV_args: value })} />

          }

            {_('cpuWeightLabel')}
            {vm.cpuWeight
              ? {vm.cpuWeight}
              : {_('defaultCpuWeight')}
            }

            {_('autoPowerOn')}
            <Toggle defaultValue={vm.auto_poweron} onChange={value => editVm(vm, { auto_poweron: value })} />

            {_('ha')}
            <Toggle defaultValue={vm.high_availability} onChange={value => editVm(vm, { high_availability: value })} />

{_('vmLimitsLabel')}

            {_('vmCpuLimitsLabel')}

              {vm.power_state === 'Running'
                ? vm.CPUs.max
                : <Number value={vm.CPUs.max} onChange={value => editVm(vm, { cpusMax: value })} />
              }

            {_('vmMemoryLimitsLabel')}

Static: {formatSize(vm.memory.static[0])}/<Size value={vm.memory.static[1]} onChange={memoryStaticMax => editVm(vm, { memoryStaticMax })} />

Dynamic: <Size value={vm.memory.dynamic[0]} onChange={memoryMin => editVm(vm, { memoryMin })} />/<Size value={vm.memory.dynamic[1]} onChange={memoryMax => editVm(vm, { memoryMax })} />

{_('guestOsLabel')}

            {_('xenToolsStatus')}
            {_('xenToolsStatusValue', { status: normalizeXenToolsStatus(vm.xenTools) })}

            {_('osName')}
            {isEmpty(vm.os_version)
                ? _('unknownOsName')
                : <Icon icon={osFamily(vm.os_version.distro)} /> {vm.os_version.name}
                }

            {_('osKernel')}
            {vm.os_version ? vm.os_version.uname ? vm.os_version.uname : _('unknownOsKernel') : _('unknownOsKernel')}

{_('miscLabel')}

            {_('originalTemplate')}
            {vm.other.base_template_name ? vm.other.base_template_name : _('unknownOriginalTemplate')}

    </Col>
  </Row>
</Container>
<|endoftext|>