;
RightAllDocsHeader.propTypes = {
diff --git a/app/addons/documents/components/results-toolbar.js b/app/addons/documents/components/results-toolbar.js
new file mode 100644
index 000000000..e5c5a2a01
--- /dev/null
+++ b/app/addons/documents/components/results-toolbar.js
@@ -0,0 +1,77 @@
+// 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 React from 'react';
+import BulkDocumentHeaderController from "../header/header";
+import Stores from "../sidebar/stores";
+import Components from "../../components/react-components";
+
+const {BulkActionComponent} = Components;
+const store = Stores.sidebarStore;
+
+export class ResultsToolBar extends React.Component {
+ shouldComponentUpdate (nextProps) {
+ return nextProps.isListDeletable != undefined;
+ }
+
+ render () {
+ const dbName = store.getDatabase().id;
+ const {
+ hasResults,
+ isListDeletable,
+ removeItem,
+ allDocumentsSelected,
+ hasSelectedItem,
+ toggleSelectAll,
+ isLoading
+ } = this.props;
+
+ // Determine if we need to display the bulk action selector
+ let bulkAction = null;
+ if ((isListDeletable && hasResults) || isLoading) {
+ bulkAction = ;
+ }
+
+ // Determine if we need to display the bulk doc header
+ let bulkHeader = null;
+ if (hasResults || isLoading) {
+ bulkHeader = ;
+ }
+
+ return (
+
+ );
+ }
+};
+
+ResultsToolBar.propTypes = {
+ removeItem: React.PropTypes.func.isRequired,
+ allDocumentsSelected: React.PropTypes.bool.isRequired,
+ hasSelectedItem: React.PropTypes.bool.isRequired,
+ toggleSelectAll: React.PropTypes.func.isRequired,
+ isLoading: React.PropTypes.bool.isRequired,
+ hasResults: React.PropTypes.bool.isRequired,
+ isListDeletable: React.PropTypes.bool
+};
diff --git a/app/addons/documents/constants.js b/app/addons/documents/constants.js
new file mode 100644
index 000000000..a7063d3e9
--- /dev/null
+++ b/app/addons/documents/constants.js
@@ -0,0 +1,19 @@
+// 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.
+
+export default {
+ LAYOUT_ORIENTATION: {
+ TABLE: 'LAYOUT_TABLE',
+ METADATA: 'LAYOUT_METADATA',
+ JSON: 'LAYOUT_JSON'
+ }
+};
diff --git a/app/addons/documents/header/header.actions.js b/app/addons/documents/header/header.actions.js
index 3dc26b3ed..3baf9430d 100644
--- a/app/addons/documents/header/header.actions.js
+++ b/app/addons/documents/header/header.actions.js
@@ -33,11 +33,11 @@ export default {
ActionsQueryOptions.runQuery(params);
},
- toggleTableView: function (state) {
+ toggleLayout: function (layout) {
FauxtonAPI.dispatch({
- type: ActionTypes.TOGGLE_TABLEVIEW,
+ type: ActionTypes.TOGGLE_LAYOUT,
options: {
- enable: state
+ layout: layout
}
});
}
diff --git a/app/addons/documents/header/header.actiontypes.js b/app/addons/documents/header/header.actiontypes.js
index b7fc6a69a..7d5d9b7e8 100644
--- a/app/addons/documents/header/header.actiontypes.js
+++ b/app/addons/documents/header/header.actiontypes.js
@@ -11,5 +11,5 @@
// the License.
export default {
- TOGGLE_TABLEVIEW: 'TOGGLE_TABLEVIEW',
+ TOGGLE_LAYOUT: 'TOGGLE_LAYOUT',
};
diff --git a/app/addons/documents/header/header.js b/app/addons/documents/header/header.js
index 0e76d2ec9..046470975 100644
--- a/app/addons/documents/header/header.js
+++ b/app/addons/documents/header/header.js
@@ -12,83 +12,126 @@
import React from 'react';
import Actions from './header.actions';
-import Components from '../../components/react-components';
+import Constants from '../constants';
import IndexResultStores from '../index-results/stores';
import QueryOptionsStore from '../queryoptions/stores';
import { Button, ButtonGroup } from 'react-bootstrap';
const { indexResultsStore } = IndexResultStores;
const { queryOptionsStore } = QueryOptionsStore;
-const { ToggleHeaderButton } = Components;
-var BulkDocumentHeaderController = React.createClass({
+export default class BulkDocumentHeaderController extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = this.getStoreState();
+ }
+
getStoreState () {
return {
- selectedView: indexResultsStore.getCurrentViewType(),
- isTableView: indexResultsStore.getIsTableView(),
- includeDocs: queryOptionsStore.getIncludeDocsEnabled(),
- bulkDocCollection: indexResultsStore.getBulkDocCollection()
+ selectedLayout: indexResultsStore.getSelectedLayout(),
+ bulkDocCollection: indexResultsStore.getBulkDocCollection(),
+ isMango: indexResultsStore.getIsMangoResults()
};
- },
-
- getInitialState () {
- return this.getStoreState();
- },
+ }
componentDidMount () {
indexResultsStore.on('change', this.onChange, this);
queryOptionsStore.on('change', this.onChange, this);
- },
+ }
componentWillUnmount () {
indexResultsStore.off('change', this.onChange);
queryOptionsStore.off('change', this.onChange);
- },
+ }
onChange () {
this.setState(this.getStoreState());
- },
+ }
render () {
- var isTableViewSelected = this.state.isTableView;
+ const {
+ changeLayout,
+ selectedLayout,
+ typeOfIndex
+ } = this.props;
+
+ // If the changeLayout function is not undefined, default to using prop values
+ // because we're using our new redux store.
+ // TODO: migrate completely to redux and eliminate this check.
+ const layout = changeLayout ? selectedLayout : this.state.selectedLayout;
+ let metadata, json, table;
+ if ((typeOfIndex && typeOfIndex === 'view') || !this.state.isMango) {
+ metadata = ;
+ }
+
+ // reduce doesn't allow for include_docs=true, so we'll prevent JSON and table
+ // views since they force include_docs=true when reduce is checked in the
+ // query options panel.
+ if (!queryOptionsStore.reduce()) {
+ table = ;
+
+ json = ;
+ }
return (
-
-
+ {table}
+ {metadata}
+ {json}
- {this.props.showIncludeAllDocs ? : null} { /* text is set via responsive css */}
);
- },
+ }
- toggleIncludeDocs () {
- Actions.toggleIncludeDocs(this.state.includeDocs, this.state.bulkDocCollection);
- },
+ toggleLayout (newLayout) {
+ // this will be present when using redux stores
+ const {
+ changeLayout,
+ selectedLayout,
+ fetchAllDocs,
+ fetchParams,
+ queryOptionsParams,
+ queryOptionsToggleIncludeDocs
+ } = this.props;
- toggleTableView: function (enable) {
- Actions.toggleTableView(enable);
- }
-});
+ if (changeLayout && newLayout !== selectedLayout) {
+ // change our layout to JSON, Table, or Metadata
+ changeLayout(newLayout);
-export default {
- BulkDocumentHeaderController: BulkDocumentHeaderController
+ queryOptionsParams.include_docs = newLayout !== Constants.LAYOUT_ORIENTATION.METADATA;
+ if (newLayout === Constants.LAYOUT_ORIENTATION.TABLE) {
+ fetchParams.conflicts = true;
+ } else {
+ delete fetchParams.conflicts;
+ }
+
+ // tell the query options panel we're updating include_docs
+ queryOptionsToggleIncludeDocs(!queryOptionsParams.include_docs);
+ fetchAllDocs(fetchParams, queryOptionsParams);
+ return;
+ }
+
+ // fall back to old backbone style logic
+ Actions.toggleLayout(newLayout);
+ if (!this.state.isMango) {
+ Actions.toggleIncludeDocs(newLayout === Constants.LAYOUT_ORIENTATION.METADATA, this.state.bulkDocCollection);
+ }
+ }
};
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
index 12938439e..1134e0797 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -17,15 +17,15 @@ import ReactComponentsActions from "../components/actions";
// sequence info is an array in couchdb2 with two indexes. On couch 1.x, it's just a string / number
-function getSeqNum (val) {
+const getSeqNum = (val) => {
return _.isArray(val) ? val[1] : val;
-}
+};
-function getNewButtonLinks (databaseName) {
- var addLinks = FauxtonAPI.getExtensions('sidebar:links');
- var newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', FauxtonAPI.url.encode(databaseName));
+const getNewButtonLinks = (databaseName) => {
+ const addLinks = FauxtonAPI.getExtensions('sidebar:links');
+ const newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', FauxtonAPI.url.encode(databaseName));
- var addNewLinks = _.reduce(addLinks, function (menuLinks, link) {
+ const addNewLinks = _.reduce(addLinks, function (menuLinks, link) {
menuLinks.push({
title: link.title,
url: newUrlPrefix + '/' + link.url,
@@ -47,23 +47,23 @@ function getNewButtonLinks (databaseName) {
title: 'Add New',
links: addNewLinks
}];
-}
+};
-function getMangoLink (databaseName) {
- var newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', FauxtonAPI.url.encode(databaseName));
+const getMangoLink = (databaseName) => {
+ const newUrlPrefix = '#' + FauxtonAPI.urls('databaseBaseURL', 'app', FauxtonAPI.url.encode(databaseName));
return {
title: app.i18n.en_US['new-mango-index'],
url: newUrlPrefix + '/_index',
icon: 'fonticon-plus-circled'
};
-}
+};
-function parseJSON (str) {
+const parseJSON = (str) => {
return JSON.parse('"' + str + '"'); // this ensures newlines are converted
-}
+};
-function getModifyDatabaseLinks (databaseName) {
+const getModifyDatabaseLinks = (databaseName) => {
return [{
title: 'Replicate Database',
icon: 'fonticon-replicate',
@@ -73,11 +73,11 @@ function getModifyDatabaseLinks (databaseName) {
icon: 'fonticon-trash',
onClick: ReactComponentsActions.showDeleteDatabaseModal.bind(this, {showDeleteModal: true, dbId: databaseName})
}];
-}
+};
-function truncateDoc (docString, maxRows) {
- var lines = docString.split('\n');
- var isTruncated = false;
+const truncateDoc = (docString, maxRows) => {
+ let lines = docString.split('\n');
+ let isTruncated = false;
if (lines.length > maxRows) {
isTruncated = true;
lines = lines.slice(0, maxRows);
@@ -87,13 +87,12 @@ function truncateDoc (docString, maxRows) {
isTruncated: isTruncated,
content: docString
};
-}
-
+};
export default {
- getSeqNum: getSeqNum,
- getNewButtonLinks: getNewButtonLinks,
- getModifyDatabaseLinks: getModifyDatabaseLinks,
- parseJSON: parseJSON,
- truncateDoc: truncateDoc
+ getSeqNum,
+ getNewButtonLinks,
+ getModifyDatabaseLinks,
+ parseJSON,
+ truncateDoc
};
diff --git a/app/addons/documents/index-results/actions.js b/app/addons/documents/index-results/actions.js
index b484fad22..619cfe33e 100644
--- a/app/addons/documents/index-results/actions.js
+++ b/app/addons/documents/index-results/actions.js
@@ -51,8 +51,8 @@ export default {
if (!options.collection.fetch) { return; }
return options.collection.fetch({reset: true}).then(() => {
- this.resultsListReset();
this.sendMessageNewResultList(options);
+ this.resultsListReset();
}, (collection, _xhr) => {
//Make this more robust as sometimes the colection is passed through here.
var xhr = collection.responseText ? collection : _xhr;
diff --git a/app/addons/documents/index-results/actiontypes.js b/app/addons/documents/index-results/actiontypes.js
index e39c17bba..ddd17e680 100644
--- a/app/addons/documents/index-results/actiontypes.js
+++ b/app/addons/documents/index-results/actiontypes.js
@@ -19,5 +19,15 @@ export default {
INDEX_RESULTS_SELECT_NEW_FIELD_IN_TABLE: 'INDEX_RESULTS_SELECT_NEW_FIELD_IN_TABLE',
INDEX_RESULTS_CLEAR_SELECTED_ITEMS: 'INDEX_RESULTS_CLEAR_SELECTED_ITEMS',
INDEX_RESULTS_TOGGLE_PRIORITIZED_TABLE_VIEW: 'INDEX_RESULTS_TOGGLE_PRIORITIZED_TABLE_VIEW',
-
+ INDEX_RESULTS_REDUX_NEW_RESULTS: 'INDEX_RESULTS_REDUX_NEW_RESULTS',
+ INDEX_RESULTS_REDUX_IS_LOADING: 'INDEX_RESULTS_REDUX_IS_LOADING',
+ INDEX_RESULTS_REDUX_CHANGE_LAYOUT: 'INDEX_RESULTS_REDUX_CHANGE_LAYOUT',
+ INDEX_RESULTS_REDUX_TOGGLE_SHOW_ALL_COLUMNS: 'INDEX_RESULTS_REDUX_TOGGLE_SHOW_ALL_COLUMNS',
+ INDEX_RESULTS_REDUX_SET_PER_PAGE: 'INDEX_RESULTS_REDUX_SET_PER_PAGE',
+ INDEX_RESULTS_REDUX_PAGINATE_NEXT: 'INDEX_RESULTS_REDUX_PAGINATE_NEXT',
+ INDEX_RESULTS_REDUX_PAGINATE_PREVIOUS: 'INDEX_RESULTS_REDUX_PAGINATE_PREVIOUS',
+ 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'
};
diff --git a/app/addons/documents/index-results/apis/base.js b/app/addons/documents/index-results/apis/base.js
new file mode 100644
index 000000000..c0ce4c5d8
--- /dev/null
+++ b/app/addons/documents/index-results/apis/base.js
@@ -0,0 +1,100 @@
+// 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 ActionTypes from '../actiontypes';
+
+export const nowLoading = () => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_IS_LOADING
+ };
+};
+
+export const resetState = () => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_RESET_STATE
+ };
+};
+
+export const newResultsAvailable = (docs, params, canShowNext) => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_NEW_RESULTS,
+ docs: docs,
+ params: params,
+ canShowNext: canShowNext
+ };
+};
+
+export const newSelectedDocs = (selectedDocs = []) => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS,
+ selectedDocs: selectedDocs
+ };
+};
+
+export const selectDoc = (doc, selectedDocs) => {
+ // locate the doc in the selected docs array if it exists
+ const indexInSelectedDocs = selectedDocs.findIndex((selectedDoc) => {
+ return selectedDoc._id === doc._id;
+ });
+
+ // if the doc exists in the selectedDocs array, remove it. This occurs
+ // when a user has deselected or unchecked a doc from the list of results.
+ if (indexInSelectedDocs > -1) {
+ selectedDocs.splice(indexInSelectedDocs, 1);
+
+ // otherwise, add the _deleted: true flag and push it on to the array.
+ } else {
+ doc._deleted = true;
+ selectedDocs.push(doc);
+ }
+
+ return newSelectedDocs(selectedDocs);
+};
+
+export const bulkCheckOrUncheck = (docs, selectedDocs, allDocumentsSelected) => {
+ docs.forEach((doc) => {
+ // find the index of the doc in the selectedDocs array
+ const indexInSelectedDocs = selectedDocs.findIndex((selectedDoc) => {
+ return (doc._id || doc.id) === selectedDoc._id;
+ });
+
+ // remove the doc if we know all the documents are currently selected
+ if (allDocumentsSelected) {
+ selectedDocs.splice(indexInSelectedDocs, 1);
+ // otherwise, add the doc if it doesn't exist in the selectedDocs array
+ } else if (indexInSelectedDocs === -1) {
+ selectedDocs.push({
+ _id: doc._id || doc.id,
+ _rev: doc._rev || doc.rev || doc.value.rev,
+ _deleted: true
+ });
+ }
+ });
+
+ return newSelectedDocs(selectedDocs);
+};
+
+export const changeLayout = (newLayout) => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_CHANGE_LAYOUT,
+ layout: newLayout
+ };
+};
+
+export const changeTableHeaderAttribute = (newField, selectedFields) => {
+ selectedFields[newField.index] = newField.newSelectedRow;
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_CHANGE_TABLE_HEADER_ATTRIBUTE,
+ selectedFieldsTableView: selectedFields
+ };
+};
+
diff --git a/app/addons/documents/index-results/apis/fetch.js b/app/addons/documents/index-results/apis/fetch.js
new file mode 100644
index 000000000..1b580a390
--- /dev/null
+++ b/app/addons/documents/index-results/apis/fetch.js
@@ -0,0 +1,200 @@
+// 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 'url-polyfill';
+import 'whatwg-fetch';
+import FauxtonAPI from '../../../../core/api';
+import queryString from 'query-string';
+import SidebarActions from '../../sidebar/actions';
+import { nowLoading, newResultsAvailable, newSelectedDocs } from './base';
+
+const maxDocLimit = 10000;
+
+// This is a helper function to determine what params need to be sent to couch based
+// on what the user entered (i.e. queryOptionsParams) and what fauxton is using to
+// emulate pagination (i.e. fetchParams).
+export const mergeParams = (fetchParams, queryOptionsParams) => {
+ const params = {};
+
+ // determine the final "index" or "position" in the total result list based on the
+ // user's skip and limit inputs. If queryOptionsParams.limit is empty,
+ // finalDocPosition will be NaN. That's ok.
+ const finalDocPosition = (queryOptionsParams.skip || 0) + queryOptionsParams.limit;
+
+ // The skip value sent to couch will be the max of our current pagination skip
+ // (i.e. fetchParams.skip) and the user's original skip input (i.e. queryOptionsParams.skip).
+ // The limit will continue to be our pagination limit.
+ params.skip = Math.max(fetchParams.skip, queryOptionsParams.skip || 0);
+ params.limit = fetchParams.limit;
+ if (fetchParams.conflicts) {
+ params.conflicts = true;
+ }
+
+ // Determine the total number of documents remaining based on the user's skip and
+ // limit inputs. Again, note that this will be NaN if queryOptionsParams.limit is
+ // empty. That's ok.
+ const totalDocsRemaining = finalDocPosition - params.skip;
+
+ // return the merged params to send to couch and the num docs remaining.
+ return {
+ params: Object.assign({}, queryOptionsParams, params),
+ totalDocsRemaining: totalDocsRemaining
+ };
+};
+
+export const removeOverflowDocsAndCalculateHasNext = (docs, totalDocsRemaining, fetchLimit) => {
+ // Now is the time to determine if we have another page of results
+ // after this set of documents. We also want to manipulate the array
+ // of docs because we always search with a limit larger than the desired
+ // number of results. This is necessaary to emulate pagination.
+ let canShowNext = false;
+ if (totalDocsRemaining && docs.length > totalDocsRemaining) {
+ // We know the user manually entered a limit and we've reached the
+ // end of their desired results. We need to remove any extra results
+ // that were returned because of our pagination emulation logic.
+ docs.splice(totalDocsRemaining);
+ } else if (docs.length === fetchLimit) {
+ // The number of docs returned is equal to our params.limit, which is
+ // one more than our perPage size. We know that there is another
+ // page of results after this.
+ docs.splice(fetchLimit - 1);
+ canShowNext = true;
+ }
+
+ return {
+ finalDocList: docs,
+ canShowNext
+ };
+};
+
+// All the business logic for fetching docs from couch.
+// Arguments:
+// - fetchUrl -> the endpoint to fetch from
+// - fetchParams -> the internal params fauxton uses to emulate pagination
+// - queryOptionsParams -> manual query params entered by user
+export const fetchAllDocs = (fetchUrl, fetchParams, queryOptionsParams) => {
+ const { params, totalDocsRemaining } = mergeParams(fetchParams, queryOptionsParams);
+ params.limit = Math.min(params.limit, maxDocLimit);
+
+ return (dispatch) => {
+ // first, tell app state that we're loading
+ dispatch(nowLoading());
+
+ // now fetch the results
+ return queryEndpoint(fetchUrl, params).then((docs) => {
+ const {
+ finalDocList,
+ canShowNext
+ } = removeOverflowDocsAndCalculateHasNext(docs, totalDocsRemaining, params.limit);
+
+ // dispatch that we're all done
+ dispatch(newResultsAvailable(finalDocList, params, canShowNext));
+ });
+ };
+};
+
+export const queryEndpoint = (fetchUrl, params) => {
+ const query = queryString.stringify(params);
+ return fetch(`${fetchUrl}?${query}`, {
+ credentials: 'include',
+ headers: {
+ 'Accept': 'application/json; charset=utf-8'
+ }
+ })
+ .then(res => res.json())
+ .then(res => res.error ? [] : res.rows);
+};
+
+export const errorMessage = (ids) => {
+ let msg = 'Failed to delete your document!';
+
+ if (ids) {
+ msg = 'Failed to delete: ' + ids.join(', ');
+ }
+
+ FauxtonAPI.addNotification({
+ msg: msg,
+ type: 'error',
+ clear: true
+ });
+};
+
+export const validateBulkDelete = (docs) => {
+ const itemsLength = docs.length;
+
+ const msg = (itemsLength === 1) ? 'Are you sure you want to delete this doc?' :
+ 'Are you sure you want to delete these ' + itemsLength + ' docs?';
+
+ if (itemsLength === 0) {
+ window.alert('Please select the document rows you want to delete.');
+ return false;
+ }
+
+ if (!window.confirm(msg)) {
+ return false;
+ }
+
+ return true;
+};
+
+export const bulkDeleteDocs = (databaseName, fetchUrl, docs, designDocs, fetchParams, queryOptionsParams) => {
+ if (!validateBulkDelete(docs)) {
+ return false;
+ }
+
+ return (dispatch) => {
+ const payload = {
+ docs: docs
+ };
+
+ return postToBulkDocs(databaseName, payload).then((res) => {
+ if (res.error) {
+ errorMessage();
+ return;
+ }
+ processBulkDeleteResponse(res, docs, designDocs);
+ dispatch(newSelectedDocs());
+ dispatch(fetchAllDocs(fetchUrl, fetchParams, queryOptionsParams));
+ });
+ };
+};
+
+export const postToBulkDocs = (databaseName, payload) => {
+ return fetch(`/${databaseName}/_bulk_docs`, {
+ method: 'POST',
+ credentials: 'include',
+ body: JSON.stringify(payload),
+ headers: {
+ 'Accept': 'application/json; charset=utf-8',
+ 'Content-Type': 'application/json'
+ }
+ })
+ .then(res => res.json());
+};
+
+export const processBulkDeleteResponse = (res, originalDocs, designDocs) => {
+ FauxtonAPI.addNotification({
+ msg: 'Successfully deleted your docs',
+ clear: true
+ });
+
+ const failedDocs = res.filter(doc => !!doc.error).map(doc => doc.id);
+ const hasDesignDocs = !!originalDocs.map(d => d._id).find((_id) => /_design/.test(_id));
+
+ if (failedDocs.length > 0) {
+ errorMessage(failedDocs);
+ }
+
+ if (designDocs && hasDesignDocs) {
+ SidebarActions.updateDesignDocs(designDocs);
+ }
+};
diff --git a/app/addons/documents/index-results/apis/pagination.js b/app/addons/documents/index-results/apis/pagination.js
new file mode 100644
index 000000000..366f9e726
--- /dev/null
+++ b/app/addons/documents/index-results/apis/pagination.js
@@ -0,0 +1,88 @@
+// 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 FauxtonAPI from '../../../../core/api';
+import { fetchAllDocs } from './fetch';
+import ActionTypes from '../actiontypes';
+
+export const toggleShowAllColumns = () => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_TOGGLE_SHOW_ALL_COLUMNS
+ };
+};
+
+export const setPerPage = (amount) => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_SET_PER_PAGE,
+ perPage: amount
+ };
+};
+
+export const resetFetchParamsBeforePerPageChange = (fetchParams, queryOptionsParams, amount) => {
+ return Object.assign({}, fetchParams, {
+ limit: amount + 1,
+ skip: queryOptionsParams.skip || 0
+ });
+};
+
+export const updatePerPageResults = (databaseName, fetchParams, queryOptionsParams, amount) => {
+ // Set the query limit to the perPage + 1 so we know if there is
+ // a next page. We also need to reset to the beginning of all
+ // possible pages since our logic to paginate backwards can't handle
+ // changing perPage amounts.
+ fetchParams = resetFetchParamsBeforePerPageChange(fetchParams, queryOptionsParams, amount);
+
+ return (dispatch) => {
+ dispatch(setPerPage(amount));
+ dispatch(fetchAllDocs(databaseName, fetchParams, queryOptionsParams));
+ };
+};
+
+export const incrementSkipForPageNext = (fetchParams, perPage) => {
+ return Object.assign({}, fetchParams, {
+ skip: fetchParams.skip + perPage
+ });
+};
+
+export const paginateNext = (databaseName, fetchParams, queryOptionsParams, perPage) => {
+ // add the perPage to the previous skip.
+ fetchParams = incrementSkipForPageNext(fetchParams, perPage);
+
+ return (dispatch) => {
+ dispatch({
+ type: ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_NEXT
+ });
+ dispatch(fetchAllDocs(databaseName, fetchParams, queryOptionsParams));
+ };
+};
+
+export const decrementSkipForPagePrevious = (fetchParams, perPage) => {
+ return Object.assign({}, fetchParams, {
+ skip: Math.max(fetchParams.skip - perPage, 0)
+ });
+};
+
+export const paginatePrevious = (databaseName, fetchParams, queryOptionsParams, perPage) => {
+ // subtract the perPage to the previous skip.
+ fetchParams = decrementSkipForPagePrevious(fetchParams, perPage);
+
+ return (dispatch) => {
+ dispatch({
+ type: ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_PREVIOUS
+ });
+ dispatch(fetchAllDocs(databaseName, fetchParams, queryOptionsParams));
+ };
+};
+
+export const resetPagination = (perPage = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE) => {
+ return setPerPage(perPage);
+};
diff --git a/app/addons/documents/index-results/apis/queryoptions.js b/app/addons/documents/index-results/apis/queryoptions.js
new file mode 100644
index 000000000..94e16205f
--- /dev/null
+++ b/app/addons/documents/index-results/apis/queryoptions.js
@@ -0,0 +1,113 @@
+// 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 ActionTypes from '../actiontypes';
+import { fetchAllDocs } from './fetch';
+
+const updateQueryOptions = (queryOptions) => {
+ return {
+ type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
+ options: queryOptions
+ };
+};
+
+export const resetFetchParamsBeforeExecute = (perPage) => {
+ return {
+ limit: perPage + 1,
+ skip: 0
+ };
+};
+
+export const queryOptionsExecute = (fetchUrl, queryOptionsParams, perPage) => {
+ const fetchParams = resetFetchParamsBeforeExecute(perPage);
+ return fetchAllDocs(fetchUrl, fetchParams, queryOptionsParams);
+};
+
+export const queryOptionsToggleVisibility = (newVisibility) => {
+ return updateQueryOptions({
+ isVisible: newVisibility
+ });
+};
+
+export const queryOptionsToggleReduce = (previousReduce) => {
+ return updateQueryOptions({
+ reduce: !previousReduce
+ });
+};
+
+export const queryOptionsUpdateGroupLevel = (newGroupLevel) => {
+ return updateQueryOptions({
+ groupLevel: newGroupLevel
+ });
+};
+
+export const queryOptionsToggleByKeys = (previousShowByKeys) => {
+ return updateQueryOptions({
+ showByKeys: !previousShowByKeys,
+ showBetweenKeys: !!previousShowByKeys
+ });
+};
+
+export const queryOptionsToggleBetweenKeys = (previousShowBetweenKeys) => {
+ return updateQueryOptions({
+ showBetweenKeys: !previousShowBetweenKeys,
+ showByKeys: !!previousShowBetweenKeys
+ });
+};
+
+export const queryOptionsUpdateBetweenKeys = (newBetweenKeys) => {
+ return updateQueryOptions({
+ betweenKeys: newBetweenKeys
+ });
+};
+
+export const queryOptionsUpdateByKeys = (newByKeys) => {
+ return updateQueryOptions({
+ byKeys: newByKeys
+ });
+};
+
+export const queryOptionsToggleDescending = (previousDescending) => {
+ return updateQueryOptions({
+ descending: !previousDescending
+ });
+};
+
+export const queryOptionsUpdateSkip = (newSkip) => {
+ return updateQueryOptions({
+ skip: newSkip
+ });
+};
+
+export const queryOptionsUpdateLimit = (newLimit) => {
+ return updateQueryOptions({
+ limit: newLimit
+ });
+};
+
+export const queryOptionsToggleIncludeDocs = (previousIncludeDocs) => {
+ return updateQueryOptions({
+ includeDocs: !previousIncludeDocs
+ });
+};
+
+export const queryOptionsFilterOnlyDdocs = () => {
+ return updateQueryOptions({
+ betweenKeys: {
+ include: false,
+ startkey: '\"_design\"',
+ endkey: '\"_design0\"'
+ },
+ showBetweenKeys: true,
+ showByKeys: false
+ });
+};
diff --git a/app/addons/documents/index-results/components/pagination/PaginationFooter.js b/app/addons/documents/index-results/components/pagination/PaginationFooter.js
new file mode 100644
index 000000000..9d006747d
--- /dev/null
+++ b/app/addons/documents/index-results/components/pagination/PaginationFooter.js
@@ -0,0 +1,92 @@
+// 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 React from 'react';
+import PagingControls from './PagingControls.js';
+import PerPageSelector from './PerPageSelector.js';
+import TableControls from './TableControls';
+
+export default class PaginationFooter extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ getPageNumberText () {
+ const { docs, pageStart, pageEnd } = this.props;
+
+ if (docs.length === 0) {
+ return Showing 0 documents.;
+ }
+
+ return Showing document {pageStart} - {pageEnd}.;
+ }
+
+ perPageChange (amount) {
+ const { updatePerPageResults, fetchParams, queryOptionsParams } = this.props;
+ updatePerPageResults(amount, fetchParams, queryOptionsParams);
+ }
+
+ nextClicked (event) {
+ event.preventDefault();
+
+ const { canShowNext, fetchParams, queryOptionsParams, paginateNext, perPage } = this.props;
+ if (canShowNext) {
+ paginateNext(fetchParams, queryOptionsParams, perPage);
+ }
+ }
+
+ previousClicked (event) {
+ event.preventDefault();
+
+ const { canShowPrevious, fetchParams, queryOptionsParams, paginatePrevious, perPage } = this.props;
+ if (canShowPrevious) {
+ paginatePrevious(fetchParams, queryOptionsParams, perPage);
+ }
+ }
+
+ render () {
+ const {
+ showPrioritizedEnabled,
+ hasResults,
+ prioritizedEnabled,
+ displayedFields,
+ perPage,
+ canShowNext,
+ canShowPrevious,
+ toggleShowAllColumns
+ } = this.props;
+
+ return (
+
+ );
+ }
+};
diff --git a/app/addons/documents/index-results/components/pagination/PagingControls.js b/app/addons/documents/index-results/components/pagination/PagingControls.js
new file mode 100644
index 000000000..dd02c400f
--- /dev/null
+++ b/app/addons/documents/index-results/components/pagination/PagingControls.js
@@ -0,0 +1,46 @@
+// 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 React from 'react';
+
+export default function PagingControls ({ nextClicked, previousClicked, canShowPrevious, canShowNext }) {
+ let canShowPreviousClassName = '';
+ let canShowNextClassName = '';
+
+ if (!canShowPrevious) {
+ canShowPreviousClassName = 'disabled';
+ }
+
+ if (!canShowNext) {
+ canShowNextClassName = 'disabled';
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+PagingControls.propTypes = {
+ nextClicked: React.PropTypes.func.isRequired,
+ previousClicked: React.PropTypes.func.isRequired,
+ canShowPrevious: React.PropTypes.bool.isRequired,
+ canShowNext: React.PropTypes.bool.isRequired
+};
diff --git a/app/addons/documents/index-results/components/pagination/PerPageSelector.js b/app/addons/documents/index-results/components/pagination/PerPageSelector.js
new file mode 100644
index 000000000..ba9657c8d
--- /dev/null
+++ b/app/addons/documents/index-results/components/pagination/PerPageSelector.js
@@ -0,0 +1,56 @@
+// 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 React from 'react';
+
+export default class PerPageSelector extends React.Component {
+ constructor (props) {
+ super(props);
+ }
+
+ perPageChange (e) {
+ const perPage = parseInt(e.target.value, 10);
+ this.props.perPageChange(perPage);
+ }
+
+ getOptions () {
+ return _.map(this.props.options, (i) => {
+ return ();
+ });
+ }
+
+ render () {
+ return (
+
+
+
+ );
+ }
+
+};
+
+PerPageSelector.defaultProps = {
+ label: 'Documents per page: ',
+ options: [5, 10, 20, 30, 50, 100]
+};
+
+PerPageSelector.propTypes = {
+ perPage: React.PropTypes.number.isRequired,
+ perPageChange: React.PropTypes.func.isRequired,
+ label: React.PropTypes.string,
+ options: React.PropTypes.array
+};
diff --git a/app/addons/documents/index-results/components/pagination/TableControls.js b/app/addons/documents/index-results/components/pagination/TableControls.js
new file mode 100644
index 000000000..0023c2076
--- /dev/null
+++ b/app/addons/documents/index-results/components/pagination/TableControls.js
@@ -0,0 +1,64 @@
+// 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 React from 'react';
+
+export default class TableControls extends React.Component {
+ constructor (props) {
+ super(props);
+ }
+
+ getAmountShownFields () {
+ const fields = this.props.displayedFields;
+
+ if (fields.shown === fields.allFieldCount) {
+ return (
+
+ Showing {fields.shown} columns.
+
+ );
+ }
+
+ return (
+
+ Showing {fields.shown} of {fields.allFieldCount} columns.
+
+ );
+ }
+};
+
+TableControls.propTypes = {
+ prioritizedEnabled: React.PropTypes.bool.isRequired,
+ displayedFields: React.PropTypes.object.isRequired,
+ toggleShowAllColumns: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-results/components/queryoptions/AdditionalParams.js b/app/addons/documents/index-results/components/queryoptions/AdditionalParams.js
new file mode 100644
index 000000000..f3be5f0f9
--- /dev/null
+++ b/app/addons/documents/index-results/components/queryoptions/AdditionalParams.js
@@ -0,0 +1,78 @@
+// 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 React from 'react';
+import FauxtonAPI from '../../../../../core/api';
+
+export default class AdditionalParams extends React.Component {
+ updateSkip (e) {
+ e.preventDefault();
+ let val = e.target.value;
+
+ //check skip is only numbers
+ if (!/^\d*$/.test(val)) {
+ FauxtonAPI.addNotification({
+ msg: 'Skip can only be a number',
+ type: 'error'
+ });
+ val = this.props.skip;
+ }
+
+ this.props.updateSkip(val);
+ }
+
+ updateLimit (e) {
+ e.preventDefault();
+ this.props.updateLimit(e.target.value);
+ }
+
+ toggleDescending () {
+ this.props.toggleDescending(this.props.descending);
+ }
+
+ render () {
+ return (
+
+
Additional Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+};
diff --git a/app/addons/documents/index-results/components/queryoptions/KeySearchFields.js b/app/addons/documents/index-results/components/queryoptions/KeySearchFields.js
new file mode 100644
index 000000000..dddb63e38
--- /dev/null
+++ b/app/addons/documents/index-results/components/queryoptions/KeySearchFields.js
@@ -0,0 +1,111 @@
+// 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 React from 'react';
+import ReactDOM from 'react-dom';
+
+export default class KeySearchFields extends React.Component {
+ constructor (props) {
+ super(props);
+ }
+
+ toggleByKeys () {
+ this.props.toggleByKeys();
+ }
+
+ toggleBetweenKeys () {
+ this.props.toggleBetweenKeys();
+ }
+
+ updateBetweenKeys () {
+ this.props.updateBetweenKeys({
+ startkey: ReactDOM.findDOMNode(this.refs.startkey).value,
+ endkey: ReactDOM.findDOMNode(this.refs.endkey).value,
+ include: this.props.betweenKeys.include
+ });
+ }
+
+ updateInclusiveEnd () {
+ this.props.updateBetweenKeys({
+ include: !this.props.betweenKeys.include,
+ startkey: this.props.betweenKeys.startkey,
+ endkey: this.props.betweenKeys.endkey
+ });
+ }
+
+ updateByKeys (e) {
+ this.props.updateByKeys(e.target.value);
+ }
+
+ render () {
+ let keysGroupClass = 'controls-group well js-query-keys-wrapper ';
+ let byKeysClass = 'row-fluid js-keys-section ';
+ let betweenKeysClass = byKeysClass;
+ let byKeysButtonClass = 'drop-down btn ';
+ let betweenKeysButtonClass = byKeysButtonClass;
+
+ if (!this.props.showByKeys && !this.props.showBetweenKeys) {
+ keysGroupClass += 'hide';
+ }
+
+ if (!this.props.showByKeys) {
+ byKeysClass += 'hide';
+ } else {
+ byKeysButtonClass += 'active';
+ }
+
+ if (!this.props.showBetweenKeys) {
+ betweenKeysClass += 'hide';
+ } else {
+ betweenKeysButtonClass += 'active';
+ }
+
+ return (
+
+
Keys
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+};
diff --git a/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js b/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js
new file mode 100644
index 000000000..735dd4680
--- /dev/null
+++ b/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js
@@ -0,0 +1,102 @@
+// 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 React from 'react';
+
+export default class MainFieldsView extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ toggleIncludeDocs () {
+ this.props.toggleIncludeDocs(this.props.includeDocs);
+ }
+
+ groupLevelChange (e) {
+ this.props.updateGroupLevel(e.target.value);
+ }
+
+ groupLevel () {
+ if (!this.props.reduce) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+
+ reduce () {
+ if (!this.props.showReduce) {
+ return null;
+ }
+
+ return (
+
+
+ );
+ }
+
+};
+
+MainFieldsView.propTypes = {
+ toggleIncludeDocs: React.PropTypes.func.isRequired,
+ includeDocs: React.PropTypes.bool.isRequired,
+ reduce: React.PropTypes.bool.isRequired,
+ toggleReduce: React.PropTypes.func,
+ updateGroupLevel: React.PropTypes.func,
+ docURL: React.PropTypes.string.isRequired
+};
diff --git a/app/addons/documents/index-results/components/queryoptions/QueryButtons.js b/app/addons/documents/index-results/components/queryoptions/QueryButtons.js
new file mode 100644
index 000000000..2251c1179
--- /dev/null
+++ b/app/addons/documents/index-results/components/queryoptions/QueryButtons.js
@@ -0,0 +1,38 @@
+// 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 React from 'react';
+
+export default class QueryButtons extends React.Component {
+ constructor (props) {
+ super(props);
+ }
+
+ hideTray () {
+ this.props.onCancel();
+ }
+
+ render () {
+ return (
+
+ );
+ }
+};
+
+QueryButtons.propTypes = {
+ onCancel: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-results/components/queryoptions/QueryOptions.js b/app/addons/documents/index-results/components/queryoptions/QueryOptions.js
new file mode 100644
index 000000000..7d8b9630b
--- /dev/null
+++ b/app/addons/documents/index-results/components/queryoptions/QueryOptions.js
@@ -0,0 +1,147 @@
+// 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 React from 'react';
+import FauxtonAPI from '../../../../../core/api';
+import GeneralComponents from '../../../../components/react-components';
+import Constants from '../../../constants';
+import MainFieldsView from './MainFieldsView';
+import KeySearchFields from './KeySearchFields';
+import AdditionalParams from './AdditionalParams';
+import QueryButtons from './QueryButtons';
+
+const { ToggleHeaderButton, TrayContents } = GeneralComponents;
+
+export default class QueryOptions extends React.Component {
+ constructor(props) {
+ super(props);
+ const {
+ ddocsOnly,
+ queryOptionsFilterOnlyDdocs
+ } = props;
+
+ if (ddocsOnly) {
+ queryOptionsFilterOnlyDdocs();
+ }
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const {
+ ddocsOnly,
+ queryOptionsFilterOnlyDdocs,
+ resetState
+ } = this.props;
+
+ if (!ddocsOnly && nextProps.ddocsOnly) {
+ queryOptionsFilterOnlyDdocs();
+ } else if (ddocsOnly && !nextProps.ddocsOnly) {
+ resetState();
+ }
+ }
+
+ executeQuery (e) {
+ if (e) { e.preventDefault(); }
+ this.closeTray();
+
+ const {
+ queryOptionsExecute,
+ queryOptionsParams,
+ perPage,
+ resetPagination,
+ selectedLayout,
+ changeLayout
+ } = this.props;
+
+ // reset pagination back to the beginning but hold on to the current perPage
+ resetPagination(perPage);
+
+ // We may have to change the layout based on include_docs.
+ const isMetadata = selectedLayout === Constants.LAYOUT_ORIENTATION.METADATA;
+ if (isMetadata && queryOptionsParams.include_docs) {
+ changeLayout(Constants.LAYOUT_ORIENTATION.TABLE);
+ } else if (!isMetadata && !queryOptionsParams.include_docs) {
+ changeLayout(Constants.LAYOUT_ORIENTATION.METADATA);
+ }
+
+ // finally, run the query
+ queryOptionsExecute(queryOptionsParams, perPage);
+ }
+
+ toggleTrayVisibility () {
+ this.props.queryOptionsToggleVisibility(!this.props.contentVisible);
+ }
+
+ closeTray () {
+ this.props.queryOptionsToggleVisibility(false);
+ }
+
+ getTray () {
+ return (
+
+
+
+
+ );
+ }
+
+ render () {
+ return (
+
+
+
+
+ {this.getTray()}
+
+
+
+ );
+ }
+};
+
+QueryOptions.propTypes = {
+ contentVisible: React.PropTypes.bool.isRequired
+};
diff --git a/app/addons/documents/index-results/components/results/IndexResults.js b/app/addons/documents/index-results/components/results/IndexResults.js
new file mode 100644
index 000000000..58f17eb7b
--- /dev/null
+++ b/app/addons/documents/index-results/components/results/IndexResults.js
@@ -0,0 +1,100 @@
+// 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 React from 'react';
+import ResultsScreen from './ResultsScreen';
+
+export default class IndexResults extends React.Component {
+ constructor (props) {
+ super(props);
+ }
+
+ componentDidMount () {
+ const {
+ fetchAllDocs,
+ fetchParams,
+ queryOptionsParams,
+ } = this.props;
+
+ // now get the docs!
+ fetchAllDocs(fetchParams, queryOptionsParams);
+ }
+
+ componentWillUpdate (nextProps) {
+ const {
+ fetchAllDocs,
+ fetchParams,
+ queryOptionsParams,
+ ddocsOnly
+ } = nextProps;
+
+ if (this.props.ddocsOnly !== ddocsOnly) {
+ fetchAllDocs(fetchParams, queryOptionsParams);
+ }
+ }
+
+ componentWillUnmount () {
+ const { resetState } = this.props;
+ resetState();
+ }
+
+ deleteSelectedDocs () {
+ const { bulkDeleteDocs, fetchParams, selectedDocs, queryOptionsParams } = this.props;
+ bulkDeleteDocs(selectedDocs, fetchParams, queryOptionsParams);
+ }
+
+ isSelected (id) {
+ const { selectedDocs } = this.props;
+
+ // check whether this id exists in our array of selected docs
+ return selectedDocs.findIndex((doc) => {
+ return id === doc._id;
+ }) > -1;
+ }
+
+ docChecked (_id, _rev) {
+ const { selectDoc, selectedDocs } = this.props;
+
+ // dispatch an action to push this doc on to the array of selected docs
+ const doc = {
+ _id: _id,
+ _rev: _rev
+ };
+
+ selectDoc(doc, selectedDocs);
+ }
+
+ toggleSelectAll () {
+ const {
+ docs,
+ selectedDocs,
+ allDocumentsSelected,
+ bulkCheckOrUncheck
+ } = this.props;
+
+ bulkCheckOrUncheck(docs, selectedDocs, allDocumentsSelected);
+ }
+
+ render () {
+ const { results } = this.props;
+
+ return (
+
+ );
+ }
+};
diff --git a/app/addons/documents/index-results/components/results/NoResultsScreen.js b/app/addons/documents/index-results/components/results/NoResultsScreen.js
new file mode 100644
index 000000000..2279b28d8
--- /dev/null
+++ b/app/addons/documents/index-results/components/results/NoResultsScreen.js
@@ -0,0 +1,26 @@
+// 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 React from 'react';
+
+export default function NoResultsScreen ({ text }) {
+ return (
+
+
+
{text}
+
+ );
+};
+
+NoResultsScreen.propTypes = {
+ text: React.PropTypes.string.isRequired
+};
diff --git a/app/addons/documents/index-results/components/results/ResultsScreen.js b/app/addons/documents/index-results/components/results/ResultsScreen.js
new file mode 100644
index 000000000..797536870
--- /dev/null
+++ b/app/addons/documents/index-results/components/results/ResultsScreen.js
@@ -0,0 +1,132 @@
+// 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 React from 'react';
+import FauxtonAPI from '../../../../../core/api';
+import Constants from '../../../constants';
+import Components from "../../../../components/react-components";
+import {ResultsToolBar} from "../../../components/results-toolbar";
+import NoResultsScreen from './NoResultsScreen';
+import TableView from './TableView';
+
+const { LoadLines, Document } = Components;
+
+export default class ResultsScreen extends React.Component {
+ constructor (props) {
+ super(props);
+ }
+
+ componentDidMount () {
+ prettyPrint();
+ }
+
+ componentDidUpdate () {
+ prettyPrint();
+ }
+
+ onClick (id, doc) {
+ FauxtonAPI.navigate(doc.url);
+ }
+
+ getUrlFragment (url) {
+ if (!this.props.isEditable) {
+ return null;
+ }
+
+ return (
+
+
+ );
+ }
+
+ getDocumentList () {
+ let noop = () => {};
+ let data = this.props.results.results;
+
+ return _.map(data, function (doc, i) {
+ return (
+
+ {doc.url ? this.getUrlFragment('#' + doc.url) : doc.url}
+
+ );
+ }, this);
+ }
+
+ getDocumentStyleView () {
+ let classNames = 'view';
+
+ if (this.props.isListDeletable) {
+ classNames += ' show-select';
+ }
+
+ return (
+
+ );
+ }
+
+};
diff --git a/app/addons/documents/index-results/components/results/TableRow.js b/app/addons/documents/index-results/components/results/TableRow.js
new file mode 100644
index 000000000..4db7d7cda
--- /dev/null
+++ b/app/addons/documents/index-results/components/results/TableRow.js
@@ -0,0 +1,148 @@
+// 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 React from 'react';
+import FauxtonAPI from '../../../../../core/api';
+import Components from '../../../../components/react-components';
+import uuid from 'uuid';
+
+const { Copy } = Components;
+
+export default class TableRow extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ checked: this.props.isSelected
+ };
+ }
+
+ onChange () {
+ this.props.docChecked(this.props.el.id, this.props.el._rev);
+ }
+
+ getRowContents (element, rowNumber) {
+ const el = element.content;
+
+ const row = this.props.data.selectedFields.map(function (k, i) {
+
+ const key = 'tableview-data-cell-' + rowNumber + k + i + el[k];
+ const stringified = typeof el[k] === 'object' ? JSON.stringify(el[k], null, ' ') : el[k];
+
+ return (
+
+ );
+ }
+};
+
+TableRow.propTypes = {
+ docIdentifier: React.PropTypes.string.isRequired,
+ docChecked: React.PropTypes.func.isRequired,
+ isSelected: React.PropTypes.bool.isRequired,
+ index: React.PropTypes.number.isRequired,
+ data: React.PropTypes.object.isRequired,
+ onClick: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-results/components/results/TableView.js b/app/addons/documents/index-results/components/results/TableView.js
new file mode 100644
index 000000000..299ce7118
--- /dev/null
+++ b/app/addons/documents/index-results/components/results/TableView.js
@@ -0,0 +1,105 @@
+// 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 React from 'react';
+import TableRow from './TableRow';
+import WrappedAutocomplete from './WrappedAutocomplete';
+
+export default class TableView extends React.Component {
+ constructor (props) {
+ super(props);
+ }
+
+ getContentRows () {
+ const data = this.props.data.results;
+
+ return data.map(function (el, i) {
+
+ return (
+
+ );
+ }.bind(this));
+ }
+
+ getOptionFieldRows (filtered) {
+ const notSelectedFields = this.props.data.notSelectedFields;
+
+ if (!notSelectedFields) {
+ return filtered.map(function (el, i) {
+ return
+ );
+ }
+};
diff --git a/app/addons/documents/index-results/components/results/WrappedAutocomplete.js b/app/addons/documents/index-results/components/results/WrappedAutocomplete.js
new file mode 100644
index 000000000..5b1c52aad
--- /dev/null
+++ b/app/addons/documents/index-results/components/results/WrappedAutocomplete.js
@@ -0,0 +1,41 @@
+// 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 React from 'react';
+import ReactSelect from "react-select";
+
+export default function WrappedAutocomplete ({
+ selectedField,
+ notSelectedFields,
+ index,
+ changeField,
+ selectedFields
+}) {
+ const options = notSelectedFields.map((el) => {
+ return {value: el, label: el};
+ });
+
+ return (
+