You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@couchdb.apache.org by GitBox <gi...@apache.org> on 2018/11/08 04:15:56 UTC

[GitHub] Antonio-Maranhao closed pull request #1103: Customize style of the results list

Antonio-Maranhao closed pull request #1103: Customize style of the results list
URL: https://github.com/apache/couchdb-fauxton/pull/1103
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/app/addons/components/assets/less/components.less b/app/addons/components/assets/less/components.less
index 9a0df82df..480681e6a 100644
--- a/app/addons/components/assets/less/components.less
+++ b/app/addons/components/assets/less/components.less
@@ -25,3 +25,4 @@
 @import "layouts.less";
 @import "polling.less";
 @import "jsonlink.less";
+@import "menudropdown.less";
diff --git a/app/addons/components/assets/less/docs.less b/app/addons/components/assets/less/docs.less
index 1e12e98ef..44f60f21d 100644
--- a/app/addons/components/assets/less/docs.less
+++ b/app/addons/components/assets/less/docs.less
@@ -30,6 +30,12 @@
         border-left: 1px solid @docHeaderOtherBorders;
         border-right: 1px solid @docHeaderOtherBorders;
         font-family: monospace;
+        .prettyprint--small {
+          font-size: 11px;
+        }
+        .prettyprint--large {
+          font-size: 15px;
+        }
       }
       header {
         font-weight: bold;
diff --git a/app/addons/components/assets/less/menudropdown.less b/app/addons/components/assets/less/menudropdown.less
new file mode 100644
index 000000000..2638a2911
--- /dev/null
+++ b/app/addons/components/assets/less/menudropdown.less
@@ -0,0 +1,17 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+.dropdown-menu > li > a.fonticon-placeholder:before {
+  padding-right: 17px;
+  content: "";
+}
diff --git a/app/addons/components/components/document.js b/app/addons/components/components/document.js
index 1f4ba9e8c..4dd331257 100644
--- a/app/addons/components/components/document.js
+++ b/app/addons/components/components/document.js
@@ -9,24 +9,29 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-import PropTypes from 'prop-types';
 
-import React from "react";
-import ReactDOM from "react-dom";
-import FauxtonAPI from "../../../core/api";
-import Helpers from "../../documents/helpers";
+import classnames from 'classnames';
+import PropTypes from 'prop-types';
+import React from 'react';
+import FauxtonAPI from '../../../core/api';
+import Constants from '../../documents/constants';
+import Helpers from '../../documents/helpers';
 
 export class Document extends React.Component {
   static propTypes = {
     docIdentifier: PropTypes.string.isRequired,
     docChecked: PropTypes.func.isRequired,
     truncate: PropTypes.bool,
-    maxRows: PropTypes.number
+    maxRows: PropTypes.number,
+    resultsStyle: PropTypes.object
   };
 
   static defaultProps = {
     truncate: true,
-    maxRows: 500
+    maxRows: 500,
+    resultsStyle: {
+      fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM
+    }
   };
 
   onChange = (e) => {
@@ -91,10 +96,14 @@ export class Document extends React.Component {
       isTruncated = result.isTruncated;
       content = result.content;
     }
-
+    const { fontSize } = this.props.resultsStyle;
+    const classNames = classnames('prettyprint', {
+      'prettyprint--small': fontSize === Constants.INDEX_RESULTS_STYLE.FONT_SIZE_SMALL,
+      'prettyprint--large': fontSize === Constants.INDEX_RESULTS_STYLE.FONT_SIZE_LARGE
+    });
     return (
       <div className="doc-data">
-        <pre className="prettyprint">{content}</pre>
+        <pre className={classNames}>{content}</pre>
         {isTruncated ? <div className="doc-content-truncated">(truncated)</div> : null}
       </div>
     );
diff --git a/app/addons/components/components/menudropdown.js b/app/addons/components/components/menudropdown.js
index a67e24060..8df4c2320 100644
--- a/app/addons/components/components/menudropdown.js
+++ b/app/addons/components/components/menudropdown.js
@@ -9,14 +9,25 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
+
+import classnames from 'classnames';
+import PropTypes from 'prop-types';
 import React from "react";
-import ReactDOM from "react-dom";
-import { Dropdown } from "react-bootstrap";
+import { Button, Dropdown } from "react-bootstrap";
 import RootCloseWrapper from 'react-overlays/lib/RootCloseWrapper';
 
 export class MenuDropDown extends React.Component {
   static defaultProps = {
-    icon: 'fonticon-plus-circled'
+    icon: 'fonticon-plus-circled',
+    hideArrow: false,
+    toggleType: 'link'
+  };
+
+  static propTypes = {
+    icon: PropTypes.string,
+    hideArrow: PropTypes.bool,
+    links: PropTypes.array.isRequired,
+    toggleType: PropTypes.string.isRequired
   };
 
   createSectionLinks = (links) => {
@@ -30,7 +41,7 @@ export class MenuDropDown extends React.Component {
   createEntry = (link, key) => {
     return (
       <li key={key}>
-        <a className={link.icon ? 'icon ' + link.icon : ''}
+        <a className={classnames('icon', link.icon, { 'fonticon-placeholder': !link.icon})}
           data-bypass={link.external ? 'true' : ''}
           href={link.url}
           onClick={link.onClick}
@@ -65,11 +76,13 @@ export class MenuDropDown extends React.Component {
 
   render() {
     const menuItems = this.createSection();
+    const arrowClass = this.props.hideArrow ? '' : 'arrow';
+    const CustomMenuToggle = this.props.toggleType === 'button' ? CustomMenuButtonToggle : CustomMenuLinkToggle;
     return (
       <Dropdown id="dropdown-menu">
         <CustomMenuToggle bsRole="toggle" icon={this.props.icon}>
         </CustomMenuToggle>
-        <CustomMenu bsRole="menu" className="arrow">
+        <CustomMenu bsRole="menu" className={arrowClass}>
           {menuItems}
         </CustomMenu>
       </Dropdown>
@@ -77,7 +90,31 @@ export class MenuDropDown extends React.Component {
   }
 }
 
-class CustomMenuToggle extends React.Component {
+class CustomMenuButtonToggle extends React.Component {
+  constructor(props, context) {
+    super(props, context);
+    this.handleClick = this.handleClick.bind(this);
+  }
+
+  handleClick(e) {
+    e.preventDefault();
+    this.props.onClick(e);
+  }
+
+  render() {
+    return (
+      <Button
+        onClick={this.handleClick}>
+        <i className={"dropdown-toggle " + this.props.icon}
+          style={{ fontSize: '1rem', boxShadow: '0px 0px 0px' }}>
+        </i>
+        {this.props.children}
+      </Button>
+    );
+  }
+}
+
+class CustomMenuLinkToggle extends React.Component {
   constructor(props, context) {
     super(props, context);
     this.handleClick = this.handleClick.bind(this);
@@ -105,11 +142,10 @@ export class CustomMenu extends React.Component {
   }
 
   render() {
-    const { children, open, onClose } = this.props;
-
+    const { children, open, onClose, className } = this.props;
     return (
       <RootCloseWrapper disabled={!open} onRootClose={onClose}>
-        <ul className="dropdown-menu arrow" role="menu" aria-labelledby="dLabel">
+        <ul className={classnames('dropdown-menu', className)} role="menu" aria-labelledby="dLabel">
           {children}
         </ul>
       </RootCloseWrapper>
diff --git a/app/addons/documents/__tests__/results-options.test.js b/app/addons/documents/__tests__/results-options.test.js
new file mode 100644
index 000000000..62be559b7
--- /dev/null
+++ b/app/addons/documents/__tests__/results-options.test.js
@@ -0,0 +1,63 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { mount } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+import ResultsOptions from '../components/results-options';
+import Constants from '../constants';
+
+describe('Results Options', () => {
+
+  const defaultProps = {
+    resultsStyle: {
+      textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_TRUNCATED,
+      fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM
+    },
+    updateStyle: () => {}
+  };
+
+  it('calls updateStyle when one of the options is clicked', () => {
+    const mockUpdateStyle = sinon.spy();
+    const wrapper = mount(<ResultsOptions
+      {...defaultProps}
+      updateStyle={mockUpdateStyle}/>
+    );
+    wrapper.find('a.icon').at(0).simulate('click');
+    sinon.assert.called(mockUpdateStyle);
+  });
+
+  it('shows the two sections by default', () => {
+    const wrapper = mount(<ResultsOptions
+      {...defaultProps} />
+    );
+    expect(wrapper.find('li.header-label').length).toBe(2);
+  });
+
+  it('hides Display Density when prop is set to false', () => {
+    const wrapper = mount(<ResultsOptions
+      {...defaultProps}
+      showDensity={false} />
+    );
+    expect(wrapper.find('li.header-label').length).toBe(1);
+    expect(wrapper.find('li.header-label').text()).toBe('Font size');
+  });
+
+  it('hides Font Size when prop is set to false', () => {
+    const wrapper = mount(<ResultsOptions
+      {...defaultProps}
+      showFontSize={false} />
+    );
+    expect(wrapper.find('li.header-label').length).toBe(1);
+    expect(wrapper.find('li.header-label').text()).toBe('Display density');
+  });
+});
diff --git a/app/addons/documents/__tests__/results-toolbar.test.js b/app/addons/documents/__tests__/results-toolbar.test.js
index 09b4c9ca6..8c6b4d83c 100644
--- a/app/addons/documents/__tests__/results-toolbar.test.js
+++ b/app/addons/documents/__tests__/results-toolbar.test.js
@@ -10,23 +10,29 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import sinon from "sinon";
-import utils from "../../../../test/mocha/testUtils";
-import FauxtonAPI from "../../../core/api";
-import {ResultsToolBar} from "../components/results-toolbar";
-import React from 'react';
-import ReactDOM from 'react-dom';
 import { mount } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+import utils from '../../../../test/mocha/testUtils';
+import FauxtonAPI from '../../../core/api';
+import {ResultsToolBar} from '../components/results-toolbar';
+import Constants from '../constants';
 
 describe('Results Toolbar', () => {
-  const restProps = {
+
+  const defaultProps = {
     removeItem: () => {},
     allDocumentsSelected: false,
     hasSelectedItem: false,
     toggleSelectAll: () => {},
     isLoading: false,
     queryOptionsParams: {},
-    databaseName: 'mydb'
+    databaseName: 'mydb',
+    resultsStyle: {
+      textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_TRUNCATED,
+      fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM
+    },
+    updateResultsStyle: () => {}
   };
 
   beforeEach(() => {
@@ -38,21 +44,93 @@ describe('Results Toolbar', () => {
   });
 
   it('renders all content when there are results and they are deletable', () => {
-    const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={true} {...restProps}/>);
+    const wrapper = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={true} />
+    );
     expect(wrapper.find('.bulk-action-component').length).toBe(1);
     expect(wrapper.find('div.two-sides-toggle-button').length).toBe(1);
     expect(wrapper.find('.document-result-screen__toolbar-create-btn').length).toBe(1);
   });
 
   it('does not render bulk action component when list is not deletable', () => {
-    const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={false} {...restProps}/>);
+    const wrapper = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={false} />
+    );
     expect(wrapper.find('.bulk-action-component').length).toBe(0);
     expect(wrapper.find('div.two-sides-toggle-button').length).toBe(1);
     expect(wrapper.find('.document-result-screen__toolbar-create-btn').length).toBe(1);
   });
 
   it('includes default partition key when one is selected', () => {
-    const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={false} {...restProps} partitionKey={'partKey1'}/>);
-    expect(wrapper.find('a').prop('href')).toMatch(/\?partitionKey=partKey1$/);
+    const wrapper = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={false}
+      partitionKey={'partKey1'} />);
+    expect(wrapper.find('a.document-result-screen__toolbar-create-btn').prop('href')).toMatch(/\?partitionKey=partKey1$/);
+  });
+
+  it('toggles display density', () => {
+    const mockUpdateStyle = sinon.spy();
+    const wrapper = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={false}
+      updateResultsStyle={mockUpdateStyle}
+      selectedLayout={Constants.LAYOUT_ORIENTATION.METADATA}/>
+    );
+    wrapper.find('a.icon').first().simulate('click');
+    sinon.assert.calledWith(mockUpdateStyle, { textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_FULL});
+  });
+
+  it('switches font size', () => {
+    const mockUpdateStyle = sinon.spy();
+    const wrapper = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={false}
+      updateResultsStyle={mockUpdateStyle}
+      selectedLayout={Constants.LAYOUT_ORIENTATION.METADATA}/>
+    );
+    wrapper.find('a.icon').at(1).simulate('click');
+    sinon.assert.calledWith(mockUpdateStyle, { fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_SMALL});
+
+    wrapper.find('a.icon').at(2).simulate('click');
+    sinon.assert.calledWith(mockUpdateStyle, { fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM});
+
+    wrapper.find('a.icon').at(3).simulate('click');
+    sinon.assert.calledWith(mockUpdateStyle, { fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_LARGE});
+  });
+
+  it.only('does not show Display Density option in JSON layout', () => {
+    const toolbarJson = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={false}
+      selectedLayout={Constants.LAYOUT_ORIENTATION.JSON}/>
+    );
+    expect(toolbarJson.find('li.header-label').text()).toBe('Font size');
+
+    const toolbarMetadata = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={false}
+      selectedLayout={Constants.LAYOUT_ORIENTATION.METADATA}/>
+    );
+    expect(toolbarMetadata.find('li.header-label').at(0).text()).toBe('Display density');
+    expect(toolbarMetadata.find('li.header-label').at(1).text()).toBe('Font size');
+
+    const toolbarTable = mount(<ResultsToolBar
+      {...defaultProps}
+      hasResults={true}
+      isListDeletable={false}
+      selectedLayout={Constants.LAYOUT_ORIENTATION.TABLE}/>
+    );
+    expect(toolbarTable.find('li.header-label').at(0).text()).toBe('Display density');
+    expect(toolbarTable.find('li.header-label').at(1).text()).toBe('Font size');
   });
 });
diff --git a/app/addons/documents/__tests__/table-row.test.js b/app/addons/documents/__tests__/table-row.test.js
index 5f727f379..266f98589 100644
--- a/app/addons/documents/__tests__/table-row.test.js
+++ b/app/addons/documents/__tests__/table-row.test.js
@@ -9,35 +9,37 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-import FauxtonAPI from "../../../core/api";
-import TableRow from "../index-results/components/results/TableRow";
-import utils from "../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
-import sinon from "sinon";
+
+import React from 'react';
+import sinon from 'sinon';
 import { shallow } from 'enzyme';
+import FauxtonAPI from '../../../core/api';
+import Constants from '../constants';
+import TableRow from '../index-results/components/results/TableRow';
+import utils from '../../../../test/mocha/testUtils';
 
 FauxtonAPI.router = new FauxtonAPI.Router([]);
 const { assert } = utils;
 
 describe('Docs Table Row', () => {
 
+  const elem = {
+    content: {
+      _id: "123",
+      vBool: true,
+      vString: 'abc',
+      vFloat: 123.1234,
+      vInt: 123,
+      vObject: { f1: 1, f2: 'b'},
+    },
+    id: "123"
+  };
+
+  const data = {
+    selectedFields: ['vBool', 'vString', 'vFloat', 'vInt', 'vObject']
+  };
+
   it('all types of value are converted to the appropriate text for display', () => {
-    const elem = {
-      content: {
-        _id: "123",
-        vBool: true,
-        vString: 'abc',
-        vFloat: 123.1234,
-        vInt: 123,
-        vObject: { f1: 1, f2: 'b'},
-      },
-      id: "123"
-    };
-
-    const data = {
-      selectedFields: ['vBool', 'vString', 'vFloat', 'vInt', 'vObject']
-    };
     const wrapper = shallow(<TableRow
       onClick={sinon.stub()}
       docChecked={sinon.stub()}
@@ -54,4 +56,34 @@ describe('Docs Table Row', () => {
     assert.equal(wrapper.find('td').at(5).text(), JSON.stringify(elem.content.vInt));
     assert.equal(wrapper.find('td').at(6).text().replace(/[\s]/g, ''), JSON.stringify(elem.content.vObject));
   });
+
+  it('shows full text values', () => {
+    const wrapper = shallow(<TableRow
+      onClick={sinon.stub()}
+      docChecked={sinon.stub()}
+      el={elem}
+      data={data}
+      index={0}
+      docIdentifier={elem.id}
+      isSelected={false}
+      textOverflow={Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_FULL}
+    />);
+
+    expect(wrapper.find('td.showall').exists()).toBe(true);
+  });
+
+  it('truncates text values', () => {
+    const wrapper = shallow(<TableRow
+      onClick={sinon.stub()}
+      docChecked={sinon.stub()}
+      el={elem}
+      data={data}
+      index={0}
+      docIdentifier={elem.id}
+      isSelected={false}
+      textOverflow={Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_TRUNCATED}
+    />);
+
+    expect(wrapper.find('td.showall').exists()).toBe(false);
+  });
 });
diff --git a/app/addons/documents/assets/less/index-results.less b/app/addons/documents/assets/less/index-results.less
index ead6b89c3..8d05cfc2b 100644
--- a/app/addons/documents/assets/less/index-results.less
+++ b/app/addons/documents/assets/less/index-results.less
@@ -34,6 +34,36 @@
     min-height: 26px;
     padding: 8px 0;
   }
+
+  .text-overflow-switch {
+    min-width: 145px;
+    min-height: 26px;
+    padding: 8px 0;
+  }
+
+  .toolbar-dropdown {    
+    
+    .btn {
+      color: #666;
+    }
+    
+    .dropdown-menu.arrow:before {
+      right: 80%;
+    }
+
+    a.dropdown-toggle {
+      padding-left: 16px;
+    }
+
+    .dropdown-menu li a {
+      padding: 10px 15px 10px 12px;
+      &:hover {
+        background-color: @hoverHighlight;
+        color: white;
+      }
+    }
+  }
+  
 }
 
 .document-result-screen__toolbar-flex-container {
@@ -117,14 +147,27 @@ a.document-result-screen__toolbar-create-btn:visited {
   td, th, td a {
     vertical-align: middle;
     line-height: 20px;
-    font-size: 14px;
+    font-size: 0.875rem;
   }
+
   td, th {
     color: @defaultHTag;
     max-width: 160px;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
+    vertical-align: top;
+    &.showall {
+      overflow: visible;
+      white-space: normal;
+      word-wrap: break-word;
+    }
+    &.small-font {
+      font-size: 0.75rem;
+    }
+    &.large-font {
+      font-size: 1rem;
+    }
   }
   td.tableview-checkbox-cell, th.tableview-header-el-checkbox {
     width: 35px;
@@ -220,4 +263,4 @@ a.document-result-screen__toolbar-create-btn:visited {
   .fonticon-attention-circled {
     margin-right: 4px;
   }
-}
\ No newline at end of file
+}
diff --git a/app/addons/documents/components/results-options.js b/app/addons/documents/components/results-options.js
new file mode 100644
index 000000000..c63f0eaa6
--- /dev/null
+++ b/app/addons/documents/components/results-options.js
@@ -0,0 +1,99 @@
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import Components from '../../components/react-components';
+import Constants from '../constants';
+
+const { MenuDropDown } = Components;
+
+export default class ResultsOptions extends React.Component {
+  static defaultProps = {
+    showDensity: true,
+    showFontSize: true
+  };
+  static propTypes = {
+    resultsStyle: PropTypes.object.isRequired,
+    updateStyle: PropTypes.func.isRequired,
+    showDensity: PropTypes.bool,
+    showFontSize: PropTypes.bool
+  };
+
+  constructor(props) {
+    super(props);
+    this.toggleTextOverflow = this.toggleTextOverflow.bind(this);
+    this.setFontSize = this.setFontSize.bind(this);
+  }
+
+  toggleTextOverflow() {
+    if (this.props.resultsStyle.textOverflow === Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_FULL) {
+      this.props.updateStyle({
+        textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_TRUNCATED
+      });
+    } else {
+      this.props.updateStyle({
+        textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_FULL
+      });
+    }
+  }
+
+  setFontSize(size) {
+    this.props.updateStyle({
+      fontSize: size
+    });
+  }
+
+  getDensitySection() {
+    let menuOptionTitle = 'Show full values';
+    if (this.props.resultsStyle.textOverflow === Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_FULL) {
+      menuOptionTitle = 'Truncate values';
+    }
+
+    const densityItems = [{
+      title: menuOptionTitle,
+      onClick: this.toggleTextOverflow
+    }];
+
+    return {
+      title: 'Display density',
+      links: densityItems
+    };
+  }
+
+  getFontSizeSection() {
+    const fontSizeItems = [{
+      title: 'Small',
+      onClick: () => { this.setFontSize(Constants.INDEX_RESULTS_STYLE.FONT_SIZE_SMALL); },
+      icon: this.props.resultsStyle.fontSize === Constants.INDEX_RESULTS_STYLE.FONT_SIZE_SMALL ? 'fonticon-ok' : ''
+    },
+    {
+      title: 'Medium',
+      onClick: () => { this.setFontSize(Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM); },
+      icon: this.props.resultsStyle.fontSize === Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM ? 'fonticon-ok' : ''
+    },
+    {
+      title: 'Large',
+      onClick: () => { this.setFontSize(Constants.INDEX_RESULTS_STYLE.FONT_SIZE_LARGE); },
+      icon: this.props.resultsStyle.fontSize === Constants.INDEX_RESULTS_STYLE.FONT_SIZE_LARGE ? 'fonticon-ok' : ''
+    }];
+    return {
+      title: 'Font size',
+      links: fontSizeItems
+    };
+  }
+
+  render() {
+    const links = [];
+    if (this.props.showDensity) {
+      links.push(this.getDensitySection());
+    }
+    if (this.props.showFontSize) {
+      links.push(this.getFontSizeSection());
+    }
+
+    return (
+      <div className='toolbar-dropdown'>
+        <MenuDropDown id="result-style-menu" links={links} icon='fonticon-mixer' hideArrow={true} toggleType='button'/>
+      </div>
+    );
+  }
+}
diff --git a/app/addons/documents/components/results-toolbar.js b/app/addons/documents/components/results-toolbar.js
index c7dbccc2c..8c765c231 100644
--- a/app/addons/documents/components/results-toolbar.js
+++ b/app/addons/documents/components/results-toolbar.js
@@ -13,12 +13,15 @@ import PropTypes from 'prop-types';
 
 import React from 'react';
 import BulkDocumentHeaderController from "../header/header";
+import ResultsOptions from './results-options';
 import Components from "../../components/react-components";
+import Constants from '../constants';
 import Helpers from '../helpers';
 
 const {BulkActionComponent} = Components;
 
 export class ResultsToolBar extends React.Component {
+
   shouldComponentUpdate (nextProps) {
     return nextProps.isListDeletable != undefined;
   }
@@ -48,10 +51,17 @@ export class ResultsToolBar extends React.Component {
         title="Select all docs that can be..." />;
     }
 
-    // Determine if we need to display the bulk doc header
+    // Determine if we need to display the bulk doc header and result options
     let bulkHeader = null;
+    const showDensityOptions = this.props.selectedLayout !== Constants.LAYOUT_ORIENTATION.JSON;
+    let resultOptions = null;
     if (hasResults || isLoading) {
       bulkHeader = <BulkDocumentHeaderController {...this.props} />;
+      resultOptions = <ResultsOptions
+        updateStyle={this.props.updateResultsStyle}
+        resultsStyle={this.props.resultsStyle}
+        showDensity={showDensityOptions}
+      />;
     }
 
     let createDocumentLink = null;
@@ -69,6 +79,7 @@ export class ResultsToolBar extends React.Component {
       <div className="document-result-screen__toolbar">
         {bulkAction}
         {bulkHeader}
+        {resultOptions}
         {createDocumentLink}
       </div>
     );
@@ -83,5 +94,7 @@ ResultsToolBar.propTypes = {
   isLoading: PropTypes.bool.isRequired,
   hasResults: PropTypes.bool.isRequired,
   isListDeletable: PropTypes.bool,
-  partitionKey: PropTypes.string
+  partitionKey: PropTypes.string,
+  resultsStyle: PropTypes.object.isRequired,
+  updateResultsStyle: PropTypes.func.isRequired
 };
diff --git a/app/addons/documents/constants.js b/app/addons/documents/constants.js
index 3d9f33beb..49a3f96d8 100644
--- a/app/addons/documents/constants.js
+++ b/app/addons/documents/constants.js
@@ -21,5 +21,12 @@ export default {
     MANGO_INDEX: 'MangoIndex',
     MANGO_QUERY: 'MangoQueryResult',
     VIEW: 'view'
+  },
+  INDEX_RESULTS_STYLE: {
+    TEXT_OVERFLOW_TRUNCATED: 'truncated',
+    TEXT_OVERFLOW_FULL: 'full',
+    FONT_SIZE_SMALL: 'small-font',
+    FONT_SIZE_LARGE: 'large-font',
+    FONT_SIZE_MEDIUM: 'medium-font'
   }
 };
diff --git a/app/addons/documents/index-results/actions/base.js b/app/addons/documents/index-results/actions/base.js
index 6fac0ffea..cd9cc881a 100644
--- a/app/addons/documents/index-results/actions/base.js
+++ b/app/addons/documents/index-results/actions/base.js
@@ -105,3 +105,9 @@ export const changeTableHeaderAttribute = (newField, selectedFields) => {
   };
 };
 
+export const updateResultsStyle = (newStyle) => {
+  return {
+    type: ActionTypes.INDEX_RESULTS_SET_STYLE,
+    resultsStyle: newStyle
+  };
+};
diff --git a/app/addons/documents/index-results/actiontypes.js b/app/addons/documents/index-results/actiontypes.js
index ddd17e680..d69c15d44 100644
--- a/app/addons/documents/index-results/actiontypes.js
+++ b/app/addons/documents/index-results/actiontypes.js
@@ -29,5 +29,6 @@ export default {
   INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS: 'INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS',
   INDEX_RESULTS_REDUX_CHANGE_TABLE_HEADER_ATTRIBUTE: 'INDEX_RESULTS_REDUX_CHANGE_TABLE_HEADER_ATTRIBUTE',
   INDEX_RESULTS_REDUX_RESET_STATE: 'INDEX_RESULTS_REDUX_RESET_STATE',
-  INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS: 'INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS'
+  INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS: 'INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS',
+  INDEX_RESULTS_SET_STYLE: 'INDEX_RESULTS_SET_STYLE'
 };
diff --git a/app/addons/documents/index-results/components/results/ResultsScreen.js b/app/addons/documents/index-results/components/results/ResultsScreen.js
index ac209dc0e..86a7bb4aa 100644
--- a/app/addons/documents/index-results/components/results/ResultsScreen.js
+++ b/app/addons/documents/index-results/components/results/ResultsScreen.js
@@ -65,7 +65,8 @@ export default class ResultsScreen extends React.Component {
           header={doc.header}
           docChecked={this.props.docChecked}
           isDeletable={doc.isDeletable}
-          docIdentifier={doc.id} >
+          docIdentifier={doc.id}
+          resultsStyle={this.props.resultsStyle} >
           {doc.url ? this.getUrlFragment('#' + doc.url) : doc.url}
         </Document>
       );
@@ -104,6 +105,7 @@ export default class ResultsScreen extends React.Component {
           hasSelectedItem={this.props.hasSelectedItem}
           toggleSelect={this.toggleSelectAll}
           changeField={this.props.changeTableHeaderAttribute}
+          resultsStyle={this.props.resultsStyle}
           title="Select all docs that can be..." />
       </div>
     );
diff --git a/app/addons/documents/index-results/components/results/TableRow.js b/app/addons/documents/index-results/components/results/TableRow.js
index 5fe2dafd0..32614fe73 100644
--- a/app/addons/documents/index-results/components/results/TableRow.js
+++ b/app/addons/documents/index-results/components/results/TableRow.js
@@ -10,16 +10,21 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+import classnames from 'classnames';
 import PropTypes from 'prop-types';
-
 import React from 'react';
 import FauxtonAPI from '../../../../../core/api';
 import Components from '../../../../components/react-components';
+import Constants from '../../../constants';
 import uuid from 'uuid';
 
 const { Copy } = Components;
 
 export default class TableRow extends React.Component {
+  static defaultProps = {
+    textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_TRUNCATED
+  };
+
   constructor (props) {
     super(props);
     this.state = {
@@ -40,8 +45,14 @@ export default class TableRow extends React.Component {
       const stringified = ['object', 'boolean'].includes(typeof el[k]) ?
         JSON.stringify(el[k], null, '  ') : el[k];
 
+      const classNames = classnames({
+        'showall': this.props.textOverflow === Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_FULL,
+        'small-font': this.props.fontSize === Constants.INDEX_RESULTS_STYLE.FONT_SIZE_SMALL,
+        'large-font': this.props.fontSize === Constants.INDEX_RESULTS_STYLE.FONT_SIZE_LARGE
+      });
+
       return (
-        <td key={key} title={stringified} onClick={this.onClick.bind(this)}>
+        <td key={key} title={stringified} className={classNames} onClick={this.onClick.bind(this)}>
           {stringified}
         </td>
       );
@@ -147,5 +158,6 @@ TableRow.propTypes = {
   isSelected: PropTypes.bool.isRequired,
   index: PropTypes.number.isRequired,
   data: PropTypes.object.isRequired,
-  onClick: PropTypes.func.isRequired
+  onClick: PropTypes.func.isRequired,
+  textOverflow: PropTypes.string
 };
diff --git a/app/addons/documents/index-results/components/results/TableView.js b/app/addons/documents/index-results/components/results/TableView.js
index 550a08828..31978391d 100644
--- a/app/addons/documents/index-results/components/results/TableView.js
+++ b/app/addons/documents/index-results/components/results/TableView.js
@@ -21,7 +21,7 @@ export default class TableView extends React.Component {
 
   getContentRows () {
     const data = this.props.data.results;
-
+    const { textOverflow, fontSize } = this.props.resultsStyle;
     return data.map(function (el, i) {
       return (
         <TableRow
@@ -32,7 +32,9 @@ export default class TableView extends React.Component {
           docIdentifier={el.id || "tableview-row-component-" + i}
           docChecked={this.props.docChecked}
           isSelected={this.props.isSelected(el.id)}
-          data={this.props.data} />
+          data={this.props.data}
+          textOverflow={textOverflow}
+          fontSize={fontSize} />
       );
     }.bind(this));
   }
diff --git a/app/addons/documents/index-results/containers/IndexResultsContainer.js b/app/addons/documents/index-results/containers/IndexResultsContainer.js
index ca940ecc9..7914265ef 100644
--- a/app/addons/documents/index-results/containers/IndexResultsContainer.js
+++ b/app/addons/documents/index-results/containers/IndexResultsContainer.js
@@ -20,7 +20,8 @@ import {
   changeLayout,
   bulkCheckOrUncheck,
   changeTableHeaderAttribute,
-  resetState
+  resetState,
+  updateResultsStyle
 } from '../actions/base';
 import {
   getDocs,
@@ -36,7 +37,8 @@ import {
   getTextEmptyIndex,
   getDocType,
   getFetchParams,
-  getQueryOptionsParams
+  getQueryOptionsParams,
+  getResultsStyle
 } from '../reducers';
 
 
@@ -56,7 +58,8 @@ const mapStateToProps = ({indexResults}, ownProps) => {
     docType: getDocType(indexResults),
     fetchParams: getFetchParams(indexResults),
     queryOptionsParams: getQueryOptionsParams(indexResults),
-    partitionKey: ownProps.partitionKey
+    partitionKey: ownProps.partitionKey,
+    resultsStyle: getResultsStyle(indexResults)
   };
 };
 
@@ -91,6 +94,9 @@ const mapDispatchToProps = (dispatch, ownProps) => {
     },
     queryOptionsToggleIncludeDocs: (previousIncludeDocs) => {
       dispatch(queryOptionsToggleIncludeDocs(previousIncludeDocs));
+    },
+    updateResultsStyle: (newStyle) => {
+      dispatch(updateResultsStyle(newStyle));
     }
   };
 };
diff --git a/app/addons/documents/index-results/reducers.js b/app/addons/documents/index-results/reducers.js
index 2f90d3a1f..5d040c3b9 100644
--- a/app/addons/documents/index-results/reducers.js
+++ b/app/addons/documents/index-results/reducers.js
@@ -29,6 +29,7 @@ const initialState = {
   selectedLayout: Constants.LAYOUT_ORIENTATION.METADATA,
   textEmptyIndex: 'No Documents Found',
   docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
+  resultsStyle: loadStyle(),
   fetchParams: {
     limit: getDefaultPerPage() + 1,
     skip: 0
@@ -61,11 +62,38 @@ const initialState = {
   }
 };
 
+function loadStyle() {
+  let style = app.utils.localStorageGet('fauxton:results_style');
+  if (!style) {
+    style = {
+      textOverflow: Constants.INDEX_RESULTS_STYLE.TEXT_OVERFLOW_TRUNCATED,
+      fontSize: Constants.INDEX_RESULTS_STYLE.FONT_SIZE_MEDIUM
+    };
+  }
+  return style;
+}
+
+function storeStyle(style) {
+  app.utils.localStorageSet('fauxton:results_style', style);
+}
+
 export default function resultsState(state = initialState, action) {
   switch (action.type) {
 
+    case ActionTypes.INDEX_RESULTS_SET_STYLE:
+      const newStyle = {
+        ...state.resultsStyle,
+        ...action.resultsStyle
+      };
+      storeStyle(newStyle);
+      return {
+        ...state,
+        resultsStyle: newStyle
+      };
+
     case ActionTypes.INDEX_RESULTS_REDUX_RESET_STATE:
-      return Object.assign({}, initialState, {
+      return {
+        ...initialState,
         selectedLayout: state.selectedLayout,
         selectedDocs: [],
         fetchParams: {
@@ -77,18 +105,21 @@ export default function resultsState(state = initialState, action) {
         }),
         queryOptionsPanel: Object.assign({}, initialState.queryOptionsPanel,
           state.queryOptionsPanel, {reduce: false, groupLevel: 'exact', showReduce: false}),
-        isLoading: false
-      });
+        isLoading: false,
+        resultsStyle: state.resultsStyle
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_IS_LOADING:
-      return Object.assign({}, state, {
+      return {
+        ...state,
         isLoading: true
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS:
-      return Object.assign({}, state, {
+      return {
+        ...state,
         selectedDocs: action.selectedDocs
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_NEW_RESULTS:
       let selectedLayout = state.selectedLayout;
@@ -98,7 +129,8 @@ export default function resultsState(state = initialState, action) {
           selectedLayout = Constants.LAYOUT_ORIENTATION.TABLE;
         }
       }
-      return Object.assign({}, state, {
+      return {
+        ...state,
         docs: action.docs,
         isLoading: false,
         isEditable: true, //TODO: determine logic for this
@@ -110,51 +142,57 @@ export default function resultsState(state = initialState, action) {
         selectedLayout: selectedLayout,
         executionStats: action.executionStats,
         warning: action.warning
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_CHANGE_LAYOUT:
-      return Object.assign({}, state, {
+      return {
+        ...state,
         selectedLayout: action.layout
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_TOGGLE_SHOW_ALL_COLUMNS:
-      return Object.assign({}, state, {
+      return {
+        ...state,
         tableView: Object.assign({}, state.tableView, {
           showAllFieldsTableView: !state.tableView.showAllFieldsTableView,
           cachedFieldsTableView: state.tableView.selectedFieldsTableView
         })
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_CHANGE_TABLE_HEADER_ATTRIBUTE:
-      return Object.assign({}, state, {
+      return {
+        ...state,
         tableView: Object.assign({}, state.tableView, {
           selectedFieldsTableView: action.selectedFieldsTableView
         })
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_SET_PER_PAGE:
       app.utils.localStorageSet('fauxton:perpageredux', action.perPage);
-      return Object.assign({}, state, {
+      return {
+        ...state,
         pagination: Object.assign({}, initialState.pagination, {
           perPage: action.perPage
         })
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_NEXT:
-      return Object.assign({}, state, {
+      return {
+        ...state,
         pagination: Object.assign({}, state.pagination, {
           pageStart: state.pagination.pageStart + state.pagination.perPage,
           currentPage: state.pagination.currentPage + 1
         })
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_PREVIOUS:
-      return Object.assign({}, state, {
+      return {
+        ...state,
         pagination: Object.assign({}, state.pagination, {
           pageStart: state.pagination.pageStart - state.pagination.perPage,
           currentPage: state.pagination.currentPage - 1
         })
-      });
+      };
 
     case ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS:
       // includeDocs or reduce should be mutually exclusive
@@ -168,9 +206,10 @@ export default function resultsState(state = initialState, action) {
         // Switch off includeDocs when reduce is being set to true
         action.options.includeDocs = false;
       }
-      return Object.assign({}, state, {
+      return {
+        ...state,
         queryOptionsPanel: Object.assign({}, state.queryOptionsPanel, action.options)
-      });
+      };
 
     default:
       return state;
@@ -349,3 +388,4 @@ export const getCanShowNext = state => state.pagination.canShowNext;
 export const getQueryOptionsPanel = state => state.queryOptionsPanel;
 export const getPerPage = state => state.pagination.perPage;
 export const getFetchParams = state => state.fetchParams;
+export const getResultsStyle = state => state.resultsStyle;


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services