Customize style of the results list (#1103)
* Use dropdown to show options
* Customize toggle for menu dropdown
* Update tests
* Address PR comments
diff --git a/app/addons/components/assets/less/components.less b/app/addons/components/assets/less/components.less
index 9a0df82..480681e 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 1e12e98..44f60f2 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 0000000..2638a29
--- /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 1f4ba9e..4dd3312 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 @@
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 a67e240..8df4c23 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 @@
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 @@
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 @@
}
}
-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 @@
}
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 0000000..62be559
--- /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 09b4c9c..8c6b4d8 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 @@
});
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 5f727f3..266f985 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', () => {
- 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 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 data = {
+ selectedFields: ['vBool', 'vString', 'vFloat', 'vInt', 'vObject']
+ };
+
+ it('all types of value are converted to the appropriate text for display', () => {
const wrapper = shallow(<TableRow
onClick={sinon.stub()}
docChecked={sinon.stub()}
@@ -54,4 +56,34 @@
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 ead6b89..8d05cfc 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 @@
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 @@
.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 0000000..c63f0ea
--- /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 c7dbccc..8c765c2 100644
--- a/app/addons/documents/components/results-toolbar.js
+++ b/app/addons/documents/components/results-toolbar.js
@@ -13,12 +13,15 @@
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 @@
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 @@
<div className="document-result-screen__toolbar">
{bulkAction}
{bulkHeader}
+ {resultOptions}
{createDocumentLink}
</div>
);
@@ -83,5 +94,7 @@
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 3d9f33b..49a3f96 100644
--- a/app/addons/documents/constants.js
+++ b/app/addons/documents/constants.js
@@ -21,5 +21,12 @@
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 6fac0ff..cd9cc88 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 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 ddd17e6..d69c15d 100644
--- a/app/addons/documents/index-results/actiontypes.js
+++ b/app/addons/documents/index-results/actiontypes.js
@@ -29,5 +29,6 @@
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 ac209dc..86a7bb4 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 @@
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 @@
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 5fe2daf..32614fe 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 @@
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 @@
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 550a088..3197839 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 @@
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 @@
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 ca940ec..7914265 100644
--- a/app/addons/documents/index-results/containers/IndexResultsContainer.js
+++ b/app/addons/documents/index-results/containers/IndexResultsContainer.js
@@ -20,7 +20,8 @@
changeLayout,
bulkCheckOrUncheck,
changeTableHeaderAttribute,
- resetState
+ resetState,
+ updateResultsStyle
} from '../actions/base';
import {
getDocs,
@@ -36,7 +37,8 @@
getTextEmptyIndex,
getDocType,
getFetchParams,
- getQueryOptionsParams
+ getQueryOptionsParams,
+ getResultsStyle
} from '../reducers';
@@ -56,7 +58,8 @@
docType: getDocType(indexResults),
fetchParams: getFetchParams(indexResults),
queryOptionsParams: getQueryOptionsParams(indexResults),
- partitionKey: ownProps.partitionKey
+ partitionKey: ownProps.partitionKey,
+ resultsStyle: getResultsStyle(indexResults)
};
};
@@ -91,6 +94,9 @@
},
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 2f90d3a..5d040c3 100644
--- a/app/addons/documents/index-results/reducers.js
+++ b/app/addons/documents/index-results/reducers.js
@@ -29,6 +29,7 @@
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 @@
}
};
+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 @@
}),
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 @@
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 @@
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 @@
// 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 getQueryOptionsPanel = state => state.queryOptionsPanel;
export const getPerPage = state => state.pagination.perPage;
export const getFetchParams = state => state.fetchParams;
+export const getResultsStyle = state => state.resultsStyle;