go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/frontend/static/common/js/console.js (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Functions for rendering the Milo console.
    16  // Requires: jquery
    17  //
    18  
    19  $(function () {
    20    'use strict';
    21  
    22    // Figure out the number of rows, which is equal to the number of commits.
    23    const numRows = $('.console-commit-item').length;
    24  
    25    /**
    26     * Resizes commit cells when the window resizes.
    27     *
    28     * In the expanded view, the width is set by the browser window size.
    29     * When the window sizes changes, it changes the height of all the cells
    30     * because the cells contains elements that flow left to right.
    31     * Because the commit description on the left size are disjoint from
    32     * the cells, the height of the commit descriptions need to be updated.
    33     */
    34    function resizeHeight() {
    35      if ($('#console-page').hasClass('collapsed')) {
    36        return;  // Don't do anything in collapsed mode.
    37      }
    38      // Find the row height using the first console-cell-container of
    39      // each console-leaf-category.
    40      var rowHeight = 200;  // Minimum height.
    41      // Find the max height of each of the leaf category rows.
    42      $('.console-leaf-category').each(function() {
    43        const thisContainer = $('.console-build-column-stacked .console-cell-container-inner').first();
    44        const thisHeight = thisContainer.height();
    45        if (thisHeight > rowHeight) rowHeight = thisHeight;
    46      });
    47  
    48      // Now set all cells to be the same height, as well as commit descriptions;
    49      $('.console-cell-container').height(rowHeight);
    50      $('.console-commit-item').each(function() {
    51        $(this).height(rowHeight - 1);  // 1px for the border.
    52        const desc = $(this).find('.console-commit-description').first();
    53        const text = desc.find('p').first();
    54        const isClipped = desc.height() < text.height();
    55        // Add a class if the commit description is clipped,
    56        // so that we can render a fadeout.
    57        $(this).toggleClass('bottom-hidden', isClipped);
    58      });
    59  
    60      // Also set the width of the horizontal cell separator.
    61      var width = 0;
    62      $('#console>.console-column').each(function() {width += $(this).width();});
    63      $('.console-commit-item-overlay').width(width);
    64    }
    65  
    66    /**
    67    /* Given a leaf category as a jQuery element, return the matrix of cells.
    68     *
    69     * @param {.console-leaf-category node} category A jQuery node containing
    70     * a leaf ctegory.
    71     *
    72     * @returns {object} A data object, containing 'rows' and 'spaces'.
    73     * rows is a matrix of cells, which are <a> nodes coorisponding to a commit
    74     * and builder.
    75     * spaces is the number of spaces needed to pad the top of the column.
    76     */
    77    function getLeafCategoryData(category) {
    78      // The data source.  Each column represents a single builder.
    79      const builders = category.find('.console-builder-column');
    80  
    81      // Use the first column to find out the number of console spaces.
    82      // This is used to pad empty space in the categories.
    83      const spaces = builders.first().find('.console-space').length;
    84  
    85      // Contains a matrix of cells.
    86      // Used to contain the actual bubbles.
    87      const rows = [];
    88      for (let i = 0; i < numRows; i++) {
    89        rows.push([]);
    90      }
    91  
    92      // Gather the bubbles, populate the matrix.
    93      builders.each(function() {
    94        $(this).find('.console-cell-container').each(function(i) {
    95          rows[i].push($(this).find('a').first());
    96        });
    97      });
    98  
    99      return {
   100        rows: rows,
   101        spaces: spaces
   102      };
   103    }
   104  
   105    /**
   106     * Builds a leaf column from a matrix of rows.
   107     *
   108     * @param {object} data An object containing rows and spaces.
   109     *
   110     * @return {node} A console-builder-column to be appened under the leaf category.
   111     */
   112    function newLeafColumn(data) {
   113      // Create a new column for the leaf category.
   114      const newBuilderColumn = $(document.createElement('div'));
   115      newBuilderColumn.addClass('console-builder-column');
   116      newBuilderColumn.addClass('stacked');
   117      for (let i = 0; i < data.spaces; i++) {
   118        // Pad the header with spaces.
   119        newBuilderColumn.append('<div class="console-space"></div>');
   120      }
   121      const newColumn = $(document.createElement('div'));
   122      newColumn.addClass('console-build-column-stacked')
   123      newBuilderColumn.append(newColumn);
   124  
   125      // Populate each row.
   126      for (const row of data.rows) {
   127        const newContainer = $(document.createElement('div'));
   128        newContainer.addClass('console-cell-container');
   129        newColumn.append(newContainer);
   130        // We need an inner container to calculate height.
   131        const newInnerContainer = $(document.createElement('div'))
   132        newInnerContainer.addClass('console-cell-container-inner');
   133        newContainer.append(newInnerContainer);
   134  
   135        for (const item of row) {
   136          newInnerContainer.append($(item).clone());
   137        }
   138      }
   139      return newBuilderColumn;
   140    }
   141  
   142    /**
   143     * Overrides the default expand/collapse state of the console in the cookie.
   144     *
   145     * @param {bool} overrideDefault whether or not the user wants the default state.
   146     * If default is expand, and the user requested expand, this should be false.
   147     * If default is expand, and the user requested collapse, this should be true.
   148     * If default is collapse, and the user requested expand, this should be true.
   149     * If default is collapse, and the user requested collapse, this should be false.
   150     */
   151    function setCookie(overrideDefault) {
   152      if (overrideDefault) {
   153        Cookies.set('non-default', 1, {path: ''})
   154      } else {
   155        Cookies.remove('non-default', {path: ''})
   156      }
   157    }
   158  
   159    // Collapsed Mode -> Expanded Mode.
   160    $('.control-expand').click(function(e) {
   161      e.preventDefault();
   162      // Switch the top level class so that the expanded view nodes render.
   163      // TODO(hinoka): Refactor CSS so that we only need the expanded class.
   164      $('#console-page').removeClass('collapsed');
   165      $('#console-page').addClass('expanded');
   166  
   167      // Stack the console.
   168      $('.console-leaf-category').each(function() {
   169        const category = $(this);
   170        const data = getLeafCategoryData(category);
   171        const newColumn = newLeafColumn(data);
   172  
   173        // Hide the original columns.
   174        category.find('.console-builder-column').hide();
   175        // Stick the new column in the leaf.
   176        category.append(newColumn);
   177      });
   178  
   179      resizeHeight();
   180      $(window).resize(resizeHeight);
   181  
   182      $('.console-builder-item').hide();  // Hide the builder boxes.
   183  
   184      // Default expand, requested expand - False
   185      // Default collapse, requested expand - True
   186      setCookie(!defaultExpand);
   187    });
   188  
   189    // Expanded Mode -> Collapsed Mode.
   190    $('.control-collapse').click(function(e) {
   191      e.preventDefault();
   192      $('#console-page').addClass('collapsed');
   193      $('#console-page').removeClass('expanded');
   194  
   195      $('.stacked').remove();  // Delete all of the expanded elements.
   196      $('.console-builder-item').show();    // Show the builder boxes.
   197      $('.console-builder-column').show();  // Show the collapsed console.
   198      $('.console-cell-container').height('auto');
   199      $('.console-commit-item').height('auto');
   200  
   201      // Default expand, requested collapse - True
   202      // Default expand, requested expand - False
   203      setCookie(defaultExpand);
   204    });
   205  
   206    // We click on expand if:
   207    // Default is expand, and we don't see a cookie
   208    // Default is collapse, and we do see a cookie
   209    // Essentially we want to XOR the cookie bit with the default bit.
   210    if (Cookies.get('non-default') ? !defaultExpand : defaultExpand) {
   211      $('.control-expand').first().click();
   212    }
   213  });