@bento-core/query-bar 1.0.0-c3dc.0 → 1.0.0-c3dc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ # DQB copy URL button Configuration
2
+
3
+ ```
4
+ DashTemplateController.js
5
+
6
+ export const setActiveFilterByPathQuery = (match) => {
7
+ const query = decodeURI(match.params.filterQuery || '');
8
+ const filterObject = JSON.parse(query);
9
+ const { autocomplete = [], upload = [], uploadMetadata } = filterObject;
10
+
11
+ const activeFilterValues = Object.keys(filterObject).reduce((curr, key) => {
12
+ if (Array.isArray(filterObject[key])) {
13
+ const activeFilters = filterObject[key].reduce((value, item) => ({
14
+ ...value,
15
+ [item]: true,
16
+ }), {});
17
+ return {
18
+ ...curr,
19
+ [key]: activeFilters,
20
+ };
21
+ }
22
+ return curr;
23
+ }, {});
24
+ store.dispatch(clearAllAndSelectFacet(activeFilterValues));
25
+ store.dispatch(updateAutocompleteData(autocomplete));
26
+ store.dispatch(updateUploadData(upload));
27
+ store.dispatch(updateUploadMetadata(uploadMetadata));
28
+ };
29
+
30
+
31
+ // redirect
32
+ if (match.params.filterQuery) {
33
+ setActiveFilterByPathQuery(match);
34
+ const redirectUrl = '/explore';
35
+ history.push(redirectUrl);
36
+ }
37
+
38
+ ```
39
+
40
+ # DQB copy URL button theme style
41
+
42
+ ``` https://github.com/CBIIT/bento-icdc-frontend/tree/v4.0.1/src/pages/dashboard/filterQueryBar```
package/README.md CHANGED
@@ -60,6 +60,17 @@ const CONFIG = {
60
60
  maxItems: 2,
61
61
  },
62
62
 
63
+ /**
64
+ * set root path for copy url (COPYURL_README)
65
+ * @var {boolean}
66
+ */
67
+ rootPath: `${window.location.href}/`,
68
+ /**
69
+ * display copy url button (COPYURL_README)
70
+ * @var {boolean}
71
+ */
72
+ viewQueryURL: true,
73
+
63
74
  /* Component Helper Functions */
64
75
  functions: {
65
76
  /**
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="19" viewBox="0 0 16 19" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M13 9.75715C13 10.0452 12.78 10.2714 12.5 10.2714H7.5C7.22 10.2714 7 10.0452 7 9.75715C7 9.46915 7.22 9.24287 7.5 9.24287H12.5C12.78 9.24287 13 9.46915 13 9.75715ZM12.5 11.3H7.5C7.22 11.3 7 11.5263 7 11.8143C7 12.1023 7.22 12.3286 7.5 12.3286H12.5C12.78 12.3286 13 12.1023 13 11.8143C13 11.5263 12.78 11.3 12.5 11.3ZM12.5 13.3572H7.5C7.22 13.3572 7 13.5834 7 13.8714C7 14.1594 7.22 14.3857 7.5 14.3857H12.5C12.78 14.3857 13 14.1594 13 13.8714C13 13.5834 12.78 13.3572 12.5 13.3572ZM16 8.21429V16.4429C16 17.5743 15.1 18.5 14 18.5H6C4.9 18.5 4 17.5743 4 16.4429V15.4143H2C0.9 15.4143 0 14.4886 0 13.3572V4.10001C0 2.96858 0.9 2.04286 2 2.04286H4.5C4.5 1.18915 5.17 0.500008 6 0.500008C6.83 0.500008 7.5 1.18915 7.5 2.04286H10C11.1 2.04286 12 2.96858 12 4.10001V6.15715H14C15.1 6.15715 16 7.08287 16 8.21429ZM6 6.15715H11V4.10001C11 3.53429 10.55 3.07144 10 3.07144H9V4.10001H3V3.07144H2C1.45 3.07144 1 3.53429 1 4.10001V13.3572C1 13.9229 1.45 14.3857 2 14.3857H4V8.21429C4 7.08287 4.9 6.15715 6 6.15715ZM15 8.21429C15 7.64858 14.55 7.18572 14 7.18572H6C5.45 7.18572 5 7.64858 5 8.21429V16.4429C5 17.0086 5.45 17.4714 6 17.4714H14C14.55 17.4714 15 17.0086 15 16.4429V8.21429Z" fill="#142D64"/>
3
+ </svg>
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _clsx = _interopRequireDefault(require("clsx"));
9
+ var _core = require("@material-ui/core");
10
+ var _CopyIcon = _interopRequireDefault(require("../assets/CopyIcon.svg"));
11
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
13
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
14
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
15
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
16
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
17
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
18
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
19
+ const QueryUrl = _ref => {
20
+ let {
21
+ classes,
22
+ filterItems,
23
+ localFind = {},
24
+ rootPath,
25
+ queryUrlCharacterLimit = 70,
26
+ generateUrl
27
+ } = _ref;
28
+ const [display, setDisplay] = (0, _react.useState)(false);
29
+ const toggleDisplay = () => setDisplay(prevDisplay => !prevDisplay);
30
+ const [open, setOpen] = (0, _react.useState)(false);
31
+ const toggleOpen = () => setOpen(prevOpen => !prevOpen);
32
+ const pathFilterParams = filterItems.reduce((acc, item) => {
33
+ const {
34
+ datafield,
35
+ items = []
36
+ } = item;
37
+ acc[datafield] = items;
38
+ return acc;
39
+ }, {});
40
+ const queryString = JSON.stringify(_objectSpread(_objectSpread({}, pathFilterParams), localFind));
41
+ const [url, setUrl] = (0, _react.useState)('');
42
+ const copyUrl = async () => {
43
+ toggleOpen();
44
+ await navigator.clipboard.writeText(url);
45
+ };
46
+ const queryRef = (0, _react.useRef)(null);
47
+ (0, _react.useEffect)(() => {
48
+ if (display && generateUrl) {
49
+ generateUrl(queryString, rootPath, setUrl);
50
+ }
51
+ }, [display]);
52
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
53
+ ref: queryRef,
54
+ className: classes.urlContainer
55
+ }, /*#__PURE__*/_react.default.createElement(_core.Button, {
56
+ onClick: toggleDisplay,
57
+ className: classes.viewLinkToggleBtn
58
+ }, display ? 'Hide Query URL' : 'Show Query URL'), display && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
59
+ type: "button",
60
+ className: (0, _clsx.default)(classes.viewLink)
61
+ }, url.length > queryUrlCharacterLimit ? "".concat(url.substring(0, queryUrlCharacterLimit), "...") : url), /*#__PURE__*/_react.default.createElement(_core.Tooltip, {
62
+ arrow: true,
63
+ title: "Copy to Clipboard"
64
+ }, /*#__PURE__*/_react.default.createElement(_core.IconButton, {
65
+ onClick: copyUrl,
66
+ className: classes.copyIconBtn
67
+ }, /*#__PURE__*/_react.default.createElement("img", {
68
+ src: _CopyIcon.default,
69
+ alt: "copy icon"
70
+ }))))), /*#__PURE__*/_react.default.createElement(_core.Dialog, {
71
+ open: open,
72
+ onClose: () => toggleOpen(),
73
+ "aria-labelledby": "alert-dialog-title",
74
+ "aria-describedby": "alert-dialog-description",
75
+ className: (0, _clsx.default)(classes.dialogBox, 'dialogBox')
76
+ }, /*#__PURE__*/_react.default.createElement(_core.DialogContent, {
77
+ className: classes.okText
78
+ }, /*#__PURE__*/_react.default.createElement(_core.DialogContentText, {
79
+ id: "alert-dialog-description"
80
+ }, "Your query URL has been copied!")), /*#__PURE__*/_react.default.createElement(_core.DialogActions, null, /*#__PURE__*/_react.default.createElement(_core.Button, {
81
+ onClick: () => toggleOpen()
82
+ }, "OK"))));
83
+ };
84
+ const styles = () => ({
85
+ urlContainer: {
86
+ display: 'flex',
87
+ marginTop: '3px',
88
+ minHeight: '10px'
89
+ },
90
+ viewLink: {
91
+ overflow: 'hidden',
92
+ textOverflow: 'ellipsis',
93
+ fontFamily: 'Nunito',
94
+ fontSize: '12px',
95
+ fontWeight: '500',
96
+ lineHeight: '16px',
97
+ letterSpacing: '0em',
98
+ padding: '2px 5px',
99
+ borderRadius: '5px',
100
+ float: 'left',
101
+ color: '#1D79A8',
102
+ backgroundColor: '#fff',
103
+ margin: '0',
104
+ whiteSpace: 'nowrap',
105
+ wordBreak: 'break-all',
106
+ '@media (max-width: 2560px)': {
107
+ maxWidth: '1800px'
108
+ },
109
+ '@media (max-width: 2000px)': {
110
+ maxWidth: '1500px'
111
+ },
112
+ '@media (max-width: 1600px)': {
113
+ maxWidth: '1100px'
114
+ },
115
+ '@media (max-width: 1300px)': {
116
+ maxWidth: '900px'
117
+ }
118
+ },
119
+ urlViewBtn: {
120
+ cursor: 'pointer'
121
+ },
122
+ viewLinkToggleBtn: {
123
+ padding: '5px 10px 5px 10px',
124
+ height: '20px',
125
+ fontFamily: 'Nunito',
126
+ fontSize: '12px',
127
+ fontWeight: '500',
128
+ lineHeight: '16px',
129
+ letterSpacing: '0em',
130
+ textAlign: 'left',
131
+ backgroundColor: '#1D79A8',
132
+ textTransform: 'none',
133
+ color: '#fff',
134
+ float: 'left',
135
+ margin: '0px 10px 0px 0px',
136
+ whiteSpace: 'nowrap',
137
+ '&:hover': {
138
+ backgroundColor: '#1D79A8',
139
+ color: '#fff'
140
+ }
141
+ },
142
+ copyIconBtn: {
143
+ padding: '0px',
144
+ height: '20px',
145
+ marginLeft: '10px',
146
+ float: 'left'
147
+ }
148
+ });
149
+ var _default = (0, _core.withStyles)(styles)(QueryUrl);
150
+ exports.default = _default;
@@ -17,18 +17,49 @@ var _default = _ref => {
17
17
  } = _ref;
18
18
  const {
19
19
  items,
20
- section
20
+ section,
21
+ unknownAges
21
22
  } = data;
23
+ const getDisplayContent = () => {
24
+ const baseLabel = data.label;
25
+ const hasRange = items && items.length >= 2;
26
+
27
+ // If unknownAges is 'only', we're showing only unknown ages
28
+ if (unknownAges === 'only') {
29
+ return {
30
+ label: "".concat(baseLabel, " (Unknown Ages Only)"),
31
+ showRange: false,
32
+ rangeText: ''
33
+ };
34
+ }
35
+
36
+ // If unknownAges is 'exclude', we're excluding unknown ages from the range
37
+ if (unknownAges === 'exclude') {
38
+ return {
39
+ label: "".concat(baseLabel, " (Unknown Ages Excluded)"),
40
+ showRange: hasRange,
41
+ rangeText: hasRange ? "".concat(items[0], " \u2013 ").concat(items[1]) : ''
42
+ };
43
+ }
44
+
45
+ // Default: unknownAges is 'include' or not set
46
+ return {
47
+ label: baseLabel,
48
+ showRange: hasRange,
49
+ rangeText: hasRange ? "".concat(items[0], " \u2013 ").concat(items[1]) : ''
50
+ };
51
+ };
52
+ const displayContent = getDisplayContent();
22
53
  return /*#__PURE__*/_react.default.createElement("span", null, /*#__PURE__*/_react.default.createElement("span", null, ' ', index !== 0 ? /*#__PURE__*/_react.default.createElement("span", {
23
54
  className: classes.operators
24
55
  }, " AND ") : '', /*#__PURE__*/_react.default.createElement("span", {
25
56
  className: (0, _clsx.default)(classes.filterName, classes["facetSection".concat(section, "Background")]),
26
57
  onClick: () => onSectionClick(data)
27
- }, data.label), ' '), /*#__PURE__*/_react.default.createElement("span", null, ' ', /*#__PURE__*/_react.default.createElement("span", {
58
+ }, displayContent.label), ' '), displayContent.showRange && /*#__PURE__*/_react.default.createElement("span", null, ' ', /*#__PURE__*/_react.default.createElement("span", {
28
59
  className: classes.operators
29
60
  }, "IS BETWEEN", ' '), /*#__PURE__*/_react.default.createElement("span", {
30
61
  className: (0, _clsx.default)(classes.filterCheckboxes, classes["facetSection".concat(section)]),
31
62
  onClick: () => onItemClick(data, items[0])
32
- }, "".concat(items[0], " \u2013 ").concat(items[1]))));
63
+ }, displayContent.rangeText)));
33
64
  };
34
65
  exports.default = _default;
@@ -9,6 +9,7 @@ var _core = require("@material-ui/core");
9
9
  var _facetFilter = require("@bento-core/facet-filter");
10
10
  var _clsx = _interopRequireDefault(require("clsx"));
11
11
  var _FilterMap = require("../components/FilterMap");
12
+ var _QueryUrl = _interopRequireDefault(require("../components/QueryUrl"));
12
13
  var _styles = _interopRequireDefault(require("./styles"));
13
14
  var _config = _interopRequireDefault(require("./config"));
14
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -27,12 +28,16 @@ const QueryBarGenerator = function QueryBarGenerator() {
27
28
  let uiConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _config.default;
28
29
  const {
29
30
  config,
30
- functions
31
+ functions,
32
+ customStyles
31
33
  } = uiConfig;
32
34
  const {
33
35
  CHECKBOX
34
36
  } = _facetFilter.InputTypes;
35
37
  const maxItems = config && typeof config.maxItems === 'number' ? config.maxItems : _config.default.config.maxItems;
38
+ const queryURLRootPath = config && typeof config.rootPath === 'string' ? config.rootPath : _config.default.config.rootPath;
39
+ const viewQueryURL = config && typeof config.viewQueryURL === 'boolean' ? config.viewQueryURL : _config.default.config.viewQueryURL;
40
+ const queryUrlCharacterLimit = config && typeof config.queryUrlCharacterLimit === 'number' ? config.queryUrlCharacterLimit : _config.default.config.queryUrlCharacterLimit;
36
41
  const clearAll = functions && typeof functions.clearAll === 'function' ? functions.clearAll : _config.default.functions.clearAll;
37
42
  const clearUpload = functions && typeof functions.clearUpload === 'function' ? functions.clearUpload : _config.default.functions.clearUpload;
38
43
  const clearAutocomplete = functions && typeof functions.clearAutocomplete === 'function' ? functions.clearAutocomplete : _config.default.functions.clearAutocomplete;
@@ -40,8 +45,9 @@ const QueryBarGenerator = function QueryBarGenerator() {
40
45
  const resetFacetSection = functions && typeof functions.resetFacetSection === 'function' ? functions.resetFacetSection : _config.default.functions.resetFacetSection;
41
46
  const resetFacetCheckbox = functions && typeof functions.resetFacetCheckbox === 'function' ? functions.resetFacetCheckbox : _config.default.functions.resetFacetCheckbox;
42
47
  const resetFacetSlider = functions && typeof functions.resetFacetSlider === 'function' ? functions.resetFacetSlider : _config.default.functions.resetFacetSlider;
48
+ const generateUrl = functions && typeof functions.generateUrl === 'function' ? functions.generateUrl : _config.default.functions.generateUrl;
43
49
  return {
44
- QueryBar: (0, _core.withStyles)(_styles.default, {
50
+ QueryBar: (0, _core.withStyles)(customStyles || _styles.default, {
45
51
  withTheme: true
46
52
  })(props => {
47
53
  const {
@@ -76,7 +82,7 @@ const QueryBarGenerator = function QueryBarGenerator() {
76
82
  return _objectSpread(_objectSpread({}, facet), {}, {
77
83
  items: itemKeys
78
84
  });
79
- }).filter(facet => facet.items.length > 0);
85
+ }).filter(facet => facet.items.length > 0 || facet.unknownAges && facet.unknownAges !== 'include');
80
86
  if ((mappedInputs.length || autocomplete.length || upload.length) <= 0) {
81
87
  return null;
82
88
  }
@@ -91,26 +97,60 @@ const QueryBarGenerator = function QueryBarGenerator() {
91
97
  className: classes.divider
92
98
  }), /*#__PURE__*/_react.default.createElement("span", {
93
99
  className: classes.queryContainer
94
- }, autocomplete.length || upload.length ? /*#__PURE__*/_react.default.createElement("span", null, upload.length && !autocomplete.length ? /*#__PURE__*/_react.default.createElement("span", {
95
- className: (0, _clsx.default)(classes.filterCheckboxes, classes.localFindBackground),
96
- onClick: clearUpload
97
- }, "INPUT PARTICIPANT SET") : null, autocomplete.length ? /*#__PURE__*/_react.default.createElement("span", null, ' ', /*#__PURE__*/_react.default.createElement("span", {
98
- className: (0, _clsx.default)(classes.filterName, classes.localFindBackground),
99
- onClick: clearAutocomplete
100
- }, "Participant ID"), ' ', ' ', /*#__PURE__*/_react.default.createElement("span", {
100
+ }, (autocomplete.length || upload.length) > 0 && /*#__PURE__*/_react.default.createElement("span", null, (() => {
101
+ const participantItems = autocomplete.filter(i => i.type === 'participantIds');
102
+ const participantCount = upload.length + participantItems.length;
103
+ if (upload.length > 0 && participantItems.length === 0) {
104
+ // Only upload, no participant autocomplete
105
+ return /*#__PURE__*/_react.default.createElement("span", {
106
+ className: (0, _clsx.default)(classes.filterCheckboxes, classes.localFindBackground),
107
+ onClick: clearUpload
108
+ }, "INPUT PARTICIPANT SET");
109
+ }
110
+ if (participantCount > 0) {
111
+ const operator = participantCount === 1 ? 'IS' : 'IN';
112
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("span", {
113
+ className: (0, _clsx.default)(classes.filterName, classes.localFindBackground),
114
+ onClick: clearAutocomplete
115
+ }, "Participant ID"), /*#__PURE__*/_react.default.createElement("span", {
116
+ className: classes.operators
117
+ }, operator), operator === 'IN' && /*#__PURE__*/_react.default.createElement("span", {
118
+ className: classes.bracketsOpen
119
+ }, "("), upload.length > 0 && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("span", {
120
+ className: (0, _clsx.default)(classes.filterCheckboxes, classes.localFind),
121
+ onClick: clearUpload
122
+ }, "INPUT PARTICIPANT SET")), participantItems.slice(0, maxItems).map((d, idx, arr) => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
123
+ key: "pid-".concat(idx)
124
+ }, /*#__PURE__*/_react.default.createElement("span", {
125
+ className: (0, _clsx.default)(classes.filterCheckboxes, classes.facetSectionCases),
126
+ onClick: () => deleteAutocompleteItem(d.title)
127
+ }, d.title), idx < arr.length - 1 && ' ')), participantItems.length > maxItems && '...', operator === 'IN' && /*#__PURE__*/_react.default.createElement("span", {
128
+ className: classes.bracketsClose
129
+ }, ")"));
130
+ }
131
+ return null;
132
+ })(), (upload.length > 0 || autocomplete.some(i => i.type === 'participantIds')) && autocomplete.some(i => i.type === 'associatedIds') && /*#__PURE__*/_react.default.createElement("span", {
101
133
  className: classes.operators
102
- }, autocomplete.length === 1 && !upload.length ? 'IS ' : 'IN ')) : null, /*#__PURE__*/_react.default.createElement("span", null, (upload.length > 0 ? 1 : 0) + autocomplete.length > 1 ? /*#__PURE__*/_react.default.createElement("span", {
103
- className: classes.bracketsOpen
104
- }, "(") : null, upload.length && autocomplete.length ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, ' ', /*#__PURE__*/_react.default.createElement("span", {
105
- className: (0, _clsx.default)(classes.filterCheckboxes, classes.localFind),
106
- onClick: clearUpload
107
- }, "INPUT PARTICIPANT SET"), ' ') : null, autocomplete.slice(0, maxItems).map((d, idx) => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("span", {
108
- className: (0, _clsx.default)(classes.filterCheckboxes, classes.facetSectionCases),
109
- key: idx,
110
- onClick: () => deleteAutocompleteItem(d.title)
111
- }, d.title), idx === maxItems - 1 ? null : ' ')), autocomplete.length > maxItems && '...', (upload.length > 0 ? 1 : 0) + autocomplete.length > 1 ? /*#__PURE__*/_react.default.createElement("span", {
112
- className: classes.bracketsClose
113
- }, ")") : null)) : null, (autocomplete.length || upload.length) && mappedInputs.length ? /*#__PURE__*/_react.default.createElement("span", {
134
+ }, "OR"), (() => {
135
+ const associatedItems = autocomplete.filter(i => i.type === 'associatedIds');
136
+ if (associatedItems.length === 0) return null;
137
+ const operator = associatedItems.length === 1 ? 'IS' : 'IN';
138
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("span", {
139
+ className: (0, _clsx.default)(classes.filterName, classes.localFindAssociatedIdsBackground),
140
+ onClick: clearAutocomplete
141
+ }, "Synonym"), /*#__PURE__*/_react.default.createElement("span", {
142
+ className: classes.operators
143
+ }, operator), operator === 'IN' && /*#__PURE__*/_react.default.createElement("span", {
144
+ className: classes.bracketsOpen
145
+ }, "("), associatedItems.slice(0, maxItems).map((d, idx, arr) => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
146
+ key: "aid-".concat(idx)
147
+ }, /*#__PURE__*/_react.default.createElement("span", {
148
+ className: (0, _clsx.default)(classes.filterCheckboxes, classes.localFindAssociatedIdsText),
149
+ onClick: () => deleteAutocompleteItem(d.title)
150
+ }, d.synonym), idx < arr.length - 1 && ' ')), associatedItems.length > maxItems && '...', operator === 'IN' && /*#__PURE__*/_react.default.createElement("span", {
151
+ className: classes.bracketsClose
152
+ }, ")"));
153
+ })()), (autocomplete.length || upload.length) && mappedInputs.length ? /*#__PURE__*/_react.default.createElement("span", {
114
154
  className: classes.operators
115
155
  }, " AND ") : null, mappedInputs.map((filter, index) => /*#__PURE__*/_react.default.createElement(_FilterMap.Filter, {
116
156
  index: index,
@@ -120,7 +160,14 @@ const QueryBarGenerator = function QueryBarGenerator() {
120
160
  classes: classes,
121
161
  onSectionClick: filter.type === CHECKBOX ? resetFacetSection : resetFacetSlider,
122
162
  onItemClick: filter.type === CHECKBOX ? resetFacetCheckbox : resetFacetSlider
123
- }))));
163
+ }))), viewQueryURL && queryURLRootPath && /*#__PURE__*/_react.default.createElement(_QueryUrl.default, {
164
+ classes: classes,
165
+ localFind: localFind,
166
+ filterItems: mappedInputs,
167
+ rootPath: queryURLRootPath,
168
+ queryUrlCharacterLimit: queryUrlCharacterLimit,
169
+ generateUrl: generateUrl
170
+ }));
124
171
  })
125
172
  };
126
173
  };
@@ -14,6 +14,21 @@ var _default = {
14
14
  */
15
15
  maxItems: 2
16
16
  },
17
+ /**
18
+ * set root path for copy url
19
+ * @var {boolean}
20
+ */
21
+ rootPath: "".concat(window.location.href, "/"),
22
+ /**
23
+ * display copy url button
24
+ * @var {boolean}
25
+ */
26
+ viewQueryURL: false,
27
+ /**
28
+ * display copy url button
29
+ * @var {boolean}
30
+ */
31
+ queryUrlCharacterLimit: 70,
17
32
  /* Component Helper Functions */
18
33
  functions: {
19
34
  /**
@@ -62,7 +77,16 @@ var _default = {
62
77
  * @param {object} section the configuration object for the section
63
78
  * @returns {void}
64
79
  */
65
- resetFacetSlider: section => {}
80
+ resetFacetSlider: section => {},
81
+ /**
82
+ * Generate a query URL by calling the interop service
83
+ *
84
+ * @param {string} queryStr the query string to encode
85
+ * @param {string} root the root path for the URL
86
+ * @param {function} setUrlCallback callback function to set the generated URL
87
+ * @returns {Promise<void>}
88
+ */
89
+ generateUrl: async (queryStr, root, setUrlCallback) => {}
66
90
  }
67
91
  };
68
92
  exports.default = _default;
@@ -152,6 +152,12 @@ var _default = () => ({
152
152
  facetSectionLibraryBackground: {
153
153
  backgroundColor: '#DDEAE540',
154
154
  border: '1px solid #646464'
155
+ },
156
+ localFindAssociatedIdsBackground: {
157
+ backgroundColor: '#F6A700'
158
+ },
159
+ localFindAssociatedIdsText: {
160
+ color: '#B36B00'
155
161
  }
156
162
  });
157
163
  exports.default = _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bento-core/query-bar",
3
- "version": "1.0.0-c3dc.0",
3
+ "version": "1.0.0-c3dc.10",
4
4
  "description": "This package provides the Query Bar component that displays the current Facet Search and Local Find filters on the Dashboard/Explore page. It also provides the direct ability to reset all or some of the filters with the click of a button. It is designed to be implemented directly with the:",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  "react-redux": "^7.2.1"
24
24
  },
25
25
  "dependencies": {
26
- "@bento-core/facet-filter": "^1.0.0",
26
+ "@bento-core/facet-filter": "^1.0.1-c3dc.10",
27
27
  "lodash": "^4.17.20"
28
28
  },
29
29
  "author": "CTOS Bento Team",
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="19" viewBox="0 0 16 19" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M13 9.75715C13 10.0452 12.78 10.2714 12.5 10.2714H7.5C7.22 10.2714 7 10.0452 7 9.75715C7 9.46915 7.22 9.24287 7.5 9.24287H12.5C12.78 9.24287 13 9.46915 13 9.75715ZM12.5 11.3H7.5C7.22 11.3 7 11.5263 7 11.8143C7 12.1023 7.22 12.3286 7.5 12.3286H12.5C12.78 12.3286 13 12.1023 13 11.8143C13 11.5263 12.78 11.3 12.5 11.3ZM12.5 13.3572H7.5C7.22 13.3572 7 13.5834 7 13.8714C7 14.1594 7.22 14.3857 7.5 14.3857H12.5C12.78 14.3857 13 14.1594 13 13.8714C13 13.5834 12.78 13.3572 12.5 13.3572ZM16 8.21429V16.4429C16 17.5743 15.1 18.5 14 18.5H6C4.9 18.5 4 17.5743 4 16.4429V15.4143H2C0.9 15.4143 0 14.4886 0 13.3572V4.10001C0 2.96858 0.9 2.04286 2 2.04286H4.5C4.5 1.18915 5.17 0.500008 6 0.500008C6.83 0.500008 7.5 1.18915 7.5 2.04286H10C11.1 2.04286 12 2.96858 12 4.10001V6.15715H14C15.1 6.15715 16 7.08287 16 8.21429ZM6 6.15715H11V4.10001C11 3.53429 10.55 3.07144 10 3.07144H9V4.10001H3V3.07144H2C1.45 3.07144 1 3.53429 1 4.10001V13.3572C1 13.9229 1.45 14.3857 2 14.3857H4V8.21429C4 7.08287 4.9 6.15715 6 6.15715ZM15 8.21429C15 7.64858 14.55 7.18572 14 7.18572H6C5.45 7.18572 5 7.64858 5 8.21429V16.4429C5 17.0086 5.45 17.4714 6 17.4714H14C14.55 17.4714 15 17.0086 15 16.4429V8.21429Z" fill="#142D64"/>
3
+ </svg>
@@ -0,0 +1,172 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import clsx from 'clsx';
3
+ import {
4
+ Button,
5
+ IconButton,
6
+ Tooltip,
7
+ Dialog,
8
+ DialogActions,
9
+ DialogContent,
10
+ DialogContentText,
11
+ withStyles,
12
+ } from '@material-ui/core';
13
+ import CopyIcon from '../assets/CopyIcon.svg';
14
+
15
+ const QueryUrl = ({
16
+ classes,
17
+ filterItems,
18
+ localFind = {},
19
+ rootPath,
20
+ queryUrlCharacterLimit = 70,
21
+ generateUrl,
22
+ }) => {
23
+ const [display, setDisplay] = useState(false);
24
+ const toggleDisplay = () => setDisplay((prevDisplay) => !prevDisplay);
25
+
26
+ const [open, setOpen] = useState(false);
27
+ const toggleOpen = () => setOpen((prevOpen) => !prevOpen);
28
+
29
+ const pathFilterParams = filterItems.reduce((acc, item) => {
30
+ const { datafield, items = [] } = item;
31
+ acc[datafield] = items;
32
+ return acc;
33
+ }, {});
34
+
35
+ const queryString = JSON.stringify({
36
+ ...pathFilterParams,
37
+ ...localFind,
38
+ });
39
+ const [url, setUrl] = useState('');
40
+
41
+ const copyUrl = async () => {
42
+ toggleOpen();
43
+ await navigator.clipboard.writeText(url);
44
+ };
45
+
46
+ const queryRef = useRef(null);
47
+
48
+ useEffect(() => {
49
+ if (display && generateUrl) {
50
+ generateUrl(queryString, rootPath, setUrl);
51
+ }
52
+ }, [display]);
53
+
54
+ return (
55
+ <>
56
+ <div ref={queryRef} className={classes.urlContainer}>
57
+ <Button
58
+ onClick={toggleDisplay}
59
+ className={classes.viewLinkToggleBtn}
60
+ >
61
+ { (display) ? 'Hide Query URL' : 'Show Query URL'}
62
+ </Button>
63
+ {
64
+ (display) && (
65
+ <>
66
+ <div
67
+ type="button"
68
+ className={clsx(classes.viewLink)}
69
+ >
70
+ {url.length > queryUrlCharacterLimit ? `${url.substring(0, queryUrlCharacterLimit)}...` : url}
71
+ </div>
72
+ <Tooltip
73
+ arrow
74
+ title="Copy to Clipboard"
75
+ >
76
+ <IconButton onClick={copyUrl} className={classes.copyIconBtn}>
77
+ <img src={CopyIcon} alt="copy icon" />
78
+ </IconButton>
79
+ </Tooltip>
80
+ </>
81
+ )
82
+ }
83
+ </div>
84
+ <Dialog
85
+ open={open}
86
+ onClose={() => toggleOpen()}
87
+ aria-labelledby="alert-dialog-title"
88
+ aria-describedby="alert-dialog-description"
89
+ className={clsx(classes.dialogBox, 'dialogBox')}
90
+ >
91
+ <DialogContent className={classes.okText}>
92
+ <DialogContentText id="alert-dialog-description">
93
+ Your query URL has been copied!
94
+ </DialogContentText>
95
+ </DialogContent>
96
+ <DialogActions>
97
+ <Button onClick={() => toggleOpen()}>
98
+ OK
99
+ </Button>
100
+ </DialogActions>
101
+ </Dialog>
102
+ </>
103
+ );
104
+ };
105
+
106
+ const styles = () => ({
107
+ urlContainer: {
108
+ display: 'flex',
109
+ marginTop: '3px',
110
+ minHeight: '10px',
111
+ },
112
+ viewLink: {
113
+ overflow: 'hidden',
114
+ textOverflow: 'ellipsis',
115
+ fontFamily: 'Nunito',
116
+ fontSize: '12px',
117
+ fontWeight: '500',
118
+ lineHeight: '16px',
119
+ letterSpacing: '0em',
120
+ padding: '2px 5px',
121
+ borderRadius: '5px',
122
+ float: 'left',
123
+ color: '#1D79A8',
124
+ backgroundColor: '#fff',
125
+ margin: '0',
126
+ whiteSpace: 'nowrap',
127
+ wordBreak: 'break-all',
128
+ '@media (max-width: 2560px)': {
129
+ maxWidth: '1800px',
130
+ },
131
+ '@media (max-width: 2000px)': {
132
+ maxWidth: '1500px',
133
+ },
134
+ '@media (max-width: 1600px)': {
135
+ maxWidth: '1100px',
136
+ },
137
+ '@media (max-width: 1300px)': {
138
+ maxWidth: '900px',
139
+ },
140
+ },
141
+ urlViewBtn: {
142
+ cursor: 'pointer',
143
+ },
144
+ viewLinkToggleBtn: {
145
+ padding: '5px 10px 5px 10px',
146
+ height: '20px',
147
+ fontFamily: 'Nunito',
148
+ fontSize: '12px',
149
+ fontWeight: '500',
150
+ lineHeight: '16px',
151
+ letterSpacing: '0em',
152
+ textAlign: 'left',
153
+ backgroundColor: '#1D79A8',
154
+ textTransform: 'none',
155
+ color: '#fff',
156
+ float: 'left',
157
+ margin: '0px 10px 0px 0px',
158
+ whiteSpace: 'nowrap',
159
+ '&:hover': {
160
+ backgroundColor: '#1D79A8',
161
+ color: '#fff',
162
+ },
163
+ },
164
+ copyIconBtn: {
165
+ padding: '0px',
166
+ height: '20px',
167
+ marginLeft: '10px',
168
+ float: 'left',
169
+ },
170
+ });
171
+
172
+ export default withStyles(styles)(QueryUrl);
@@ -5,7 +5,39 @@ export default ({
5
5
  index, data, classes,
6
6
  onSectionClick, onItemClick,
7
7
  }) => {
8
- const { items, section } = data;
8
+ const { items, section, unknownAges } = data;
9
+
10
+ const getDisplayContent = () => {
11
+ const baseLabel = data.label;
12
+ const hasRange = items && items.length >= 2;
13
+
14
+ // If unknownAges is 'only', we're showing only unknown ages
15
+ if (unknownAges === 'only') {
16
+ return {
17
+ label: `${baseLabel} (Unknown Ages Only)`,
18
+ showRange: false,
19
+ rangeText: '',
20
+ };
21
+ }
22
+
23
+ // If unknownAges is 'exclude', we're excluding unknown ages from the range
24
+ if (unknownAges === 'exclude') {
25
+ return {
26
+ label: `${baseLabel} (Unknown Ages Excluded)`,
27
+ showRange: hasRange,
28
+ rangeText: hasRange ? `${items[0]} – ${items[1]}` : '',
29
+ };
30
+ }
31
+
32
+ // Default: unknownAges is 'include' or not set
33
+ return {
34
+ label: baseLabel,
35
+ showRange: hasRange,
36
+ rangeText: hasRange ? `${items[0]} – ${items[1]}` : '',
37
+ };
38
+ };
39
+
40
+ const displayContent = getDisplayContent();
9
41
 
10
42
  return (
11
43
  <span>
@@ -16,23 +48,25 @@ export default ({
16
48
  className={clsx(classes.filterName, classes[`facetSection${section}Background`])}
17
49
  onClick={() => onSectionClick(data)}
18
50
  >
19
- {data.label}
51
+ {displayContent.label}
20
52
  </span>
21
53
  {' '}
22
54
  </span>
23
- <span>
24
- {' '}
25
- <span className={classes.operators}>
26
- IS BETWEEN
55
+ {displayContent.showRange && (
56
+ <span>
27
57
  {' '}
58
+ <span className={classes.operators}>
59
+ IS BETWEEN
60
+ {' '}
61
+ </span>
62
+ <span
63
+ className={clsx(classes.filterCheckboxes, classes[`facetSection${section}`])}
64
+ onClick={() => onItemClick(data, items[0])}
65
+ >
66
+ {displayContent.rangeText}
67
+ </span>
28
68
  </span>
29
- <span
30
- className={clsx(classes.filterCheckboxes, classes[`facetSection${section}`])}
31
- onClick={() => onItemClick(data, items[0])}
32
- >
33
- {`${items[0]} – ${items[1]}`}
34
- </span>
35
- </span>
69
+ )}
36
70
  </span>
37
71
  );
38
72
  };
@@ -3,6 +3,7 @@ import { withStyles, Button } from '@material-ui/core';
3
3
  import { InputTypes } from '@bento-core/facet-filter';
4
4
  import clsx from 'clsx';
5
5
  import { Filter } from '../components/FilterMap';
6
+ import QueryUrl from '../components/QueryUrl';
6
7
  import DEFAULT_STYLES from './styles';
7
8
  import DEFAULT_CONFIG from './config';
8
9
 
@@ -13,13 +14,25 @@ import DEFAULT_CONFIG from './config';
13
14
  * @returns {object} { QueryBar }
14
15
  */
15
16
  export const QueryBarGenerator = (uiConfig = DEFAULT_CONFIG) => {
16
- const { config, functions } = uiConfig;
17
+ const { config, functions, customStyles } = uiConfig;
17
18
  const { CHECKBOX } = InputTypes;
18
19
 
19
20
  const maxItems = config && typeof config.maxItems === 'number'
20
21
  ? config.maxItems
21
22
  : DEFAULT_CONFIG.config.maxItems;
22
23
 
24
+ const queryURLRootPath = config && typeof config.rootPath === 'string'
25
+ ? config.rootPath
26
+ : DEFAULT_CONFIG.config.rootPath;
27
+
28
+ const viewQueryURL = config && typeof config.viewQueryURL === 'boolean'
29
+ ? config.viewQueryURL
30
+ : DEFAULT_CONFIG.config.viewQueryURL;
31
+
32
+ const queryUrlCharacterLimit = config && typeof config.queryUrlCharacterLimit === 'number'
33
+ ? config.queryUrlCharacterLimit
34
+ : DEFAULT_CONFIG.config.queryUrlCharacterLimit;
35
+
23
36
  const clearAll = functions && typeof functions.clearAll === 'function'
24
37
  ? functions.clearAll
25
38
  : DEFAULT_CONFIG.functions.clearAll;
@@ -48,8 +61,13 @@ export const QueryBarGenerator = (uiConfig = DEFAULT_CONFIG) => {
48
61
  ? functions.resetFacetSlider
49
62
  : DEFAULT_CONFIG.functions.resetFacetSlider;
50
63
 
64
+ const generateUrl = functions && typeof functions.generateUrl === 'function'
65
+ ? functions.generateUrl
66
+ : DEFAULT_CONFIG.functions.generateUrl;
67
+
51
68
  return {
52
- QueryBar: withStyles(DEFAULT_STYLES, { withTheme: true })((props) => {
69
+ QueryBar: withStyles(customStyles || DEFAULT_STYLES,
70
+ { withTheme: true })((props) => {
53
71
  const { statusReducer, localFind, classes } = props;
54
72
 
55
73
  const { autocomplete, upload } = localFind;
@@ -73,7 +91,7 @@ export const QueryBarGenerator = (uiConfig = DEFAULT_CONFIG) => {
73
91
 
74
92
  return { ...facet, items: itemKeys };
75
93
  })
76
- .filter((facet) => facet.items.length > 0);
94
+ .filter((facet) => facet.items.length > 0 || (facet.unknownAges && facet.unknownAges !== 'include'));
77
95
 
78
96
  if ((mappedInputs.length || autocomplete.length || upload.length) <= 0) {
79
97
  return null;
@@ -93,70 +111,117 @@ export const QueryBarGenerator = (uiConfig = DEFAULT_CONFIG) => {
93
111
  <span className={classes.queryContainer}>
94
112
  {/* Local Find Selections */}
95
113
  {/* TODO: Refactor this into a separate component */}
96
- {(autocomplete.length || upload.length) ? (
97
- <span>
98
- {/* Standalone case set button */}
99
- {(upload.length && !autocomplete.length)
100
- ? (
114
+ {/* Section: Localfind Results */}
115
+ {(autocomplete.length || upload.length) > 0 && (
116
+ <span>
117
+ {/* Participant ID Section */}
118
+ {(() => {
119
+ const participantItems = autocomplete.filter((i) => i.type === 'participantIds');
120
+ const participantCount = upload.length + participantItems.length;
121
+
122
+ if (upload.length > 0 && participantItems.length === 0) {
123
+ // Only upload, no participant autocomplete
124
+ return (
101
125
  <span
102
126
  className={clsx(classes.filterCheckboxes, classes.localFindBackground)}
103
127
  onClick={clearUpload}
104
128
  >
105
129
  INPUT PARTICIPANT SET
106
130
  </span>
107
- ) : null}
108
- {autocomplete.length
109
- ? (
110
- <span>
111
- {' '}
131
+ );
132
+ }
133
+
134
+ if (participantCount > 0) {
135
+ const operator = participantCount === 1 ? 'IS' : 'IN';
136
+
137
+ return (
138
+ <>
112
139
  <span
113
140
  className={clsx(classes.filterName, classes.localFindBackground)}
114
141
  onClick={clearAutocomplete}
115
142
  >
116
143
  Participant ID
117
144
  </span>
118
- {' '}
119
- {' '}
120
- <span className={classes.operators}>
121
- {(autocomplete.length === 1 && !upload.length) ? 'IS ' : 'IN '}
122
- </span>
123
- </span>
124
- ) : null}
125
- <span>
126
- {(((upload.length > 0 ? 1 : 0) + autocomplete.length) > 1)
127
- ? <span className={classes.bracketsOpen}>(</span>
128
- : null}
129
- {upload.length && autocomplete.length ? (
130
- <>
131
- {' '}
132
- <span
133
- className={clsx(classes.filterCheckboxes, classes.localFind)}
134
- onClick={clearUpload}
135
- >
136
- INPUT PARTICIPANT SET
137
- </span>
138
- {' '}
139
- </>
140
- ) : null}
141
- {autocomplete.slice(0, maxItems).map((d, idx) => (
142
- <>
143
- <span
144
- className={clsx(classes.filterCheckboxes, classes.facetSectionCases)}
145
- key={idx}
146
- onClick={() => deleteAutocompleteItem(d.title)}
147
- >
148
- {d.title}
149
- </span>
150
- {idx === (maxItems - 1) ? null : ' '}
145
+ <span className={classes.operators}>{operator}</span>
146
+
147
+ {operator === 'IN' && <span className={classes.bracketsOpen}>(</span>}
148
+
149
+ {upload.length > 0 && (
150
+ <>
151
+ <span
152
+ className={clsx(classes.filterCheckboxes, classes.localFind)}
153
+ onClick={clearUpload}
154
+ >
155
+ INPUT PARTICIPANT SET
156
+ </span>
157
+ </>
158
+ )}
159
+
160
+ {participantItems.slice(0, maxItems).map((d, idx, arr) => (
161
+ <React.Fragment key={`pid-${idx}`}>
162
+ <span
163
+ className={clsx(classes.filterCheckboxes, classes.facetSectionCases)}
164
+ onClick={() => deleteAutocompleteItem(d.title)}
165
+ >
166
+ {d.title}
167
+ </span>
168
+ {idx < arr.length - 1 && ' '}
169
+ </React.Fragment>
170
+ ))}
171
+ {participantItems.length > maxItems && '...'}
172
+ {operator === 'IN' && <span className={classes.bracketsClose}>)</span>}
151
173
  </>
152
- ))}
153
- {autocomplete.length > maxItems && '...'}
154
- {(((upload.length > 0 ? 1 : 0) + autocomplete.length) > 1)
155
- ? <span className={classes.bracketsClose}>)</span>
156
- : null}
157
- </span>
158
- </span>
159
- ) : null}
174
+ );
175
+ }
176
+
177
+ return null;
178
+ })()}
179
+
180
+ {/* 'or' logic */}
181
+ {(upload.length > 0 || autocomplete.some((i) => i.type === 'participantIds'))
182
+ && autocomplete.some((i) => i.type === 'associatedIds')
183
+ && (<span className={classes.operators}>OR</span>)}
184
+
185
+ {/* Associated ID Section */}
186
+ {(() => {
187
+ const associatedItems = autocomplete.filter((i) => i.type === 'associatedIds');
188
+ if (associatedItems.length === 0) return null;
189
+
190
+ const operator = associatedItems.length === 1 ? 'IS' : 'IN';
191
+
192
+ return (
193
+ <>
194
+ <span
195
+ className={clsx(classes.filterName, classes.localFindAssociatedIdsBackground)}
196
+ onClick={clearAutocomplete}
197
+ >
198
+ Synonym
199
+ </span>
200
+ <span className={classes.operators}>{operator}</span>
201
+ {operator === 'IN' && <span className={classes.bracketsOpen}>(</span>}
202
+
203
+ {associatedItems.slice(0, maxItems).map((d, idx, arr) => (
204
+ <React.Fragment key={`aid-${idx}`}>
205
+ <span
206
+ className={
207
+ clsx(classes.filterCheckboxes, classes.localFindAssociatedIdsText)
208
+ }
209
+ onClick={() => deleteAutocompleteItem(d.title)}
210
+ >
211
+ {d.synonym}
212
+ </span>
213
+ {idx < arr.length - 1 && ' '}
214
+ </React.Fragment>
215
+ ))}
216
+ {associatedItems.length > maxItems && '...'}
217
+ {operator === 'IN' && <span className={classes.bracketsClose}>)</span>}
218
+ </>
219
+ );
220
+ })()}
221
+ </span>
222
+ )}
223
+
224
+ {/* --------------------- */}
160
225
 
161
226
  {/* Facet Sidebar Selections */}
162
227
  {((autocomplete.length || upload.length) && mappedInputs.length)
@@ -178,6 +243,18 @@ export const QueryBarGenerator = (uiConfig = DEFAULT_CONFIG) => {
178
243
  />
179
244
  ))}
180
245
  </span>
246
+ {
247
+ (viewQueryURL && queryURLRootPath) && (
248
+ <QueryUrl
249
+ classes={classes}
250
+ localFind={localFind}
251
+ filterItems={mappedInputs}
252
+ rootPath={queryURLRootPath}
253
+ queryUrlCharacterLimit={queryUrlCharacterLimit}
254
+ generateUrl={generateUrl}
255
+ />
256
+ )
257
+ }
181
258
  </div>
182
259
  );
183
260
  }),
@@ -9,6 +9,22 @@ export default {
9
9
  maxItems: 2,
10
10
  },
11
11
 
12
+ /**
13
+ * set root path for copy url
14
+ * @var {boolean}
15
+ */
16
+ rootPath: `${window.location.href}/`,
17
+ /**
18
+ * display copy url button
19
+ * @var {boolean}
20
+ */
21
+ viewQueryURL: false,
22
+ /**
23
+ * display copy url button
24
+ * @var {boolean}
25
+ */
26
+ queryUrlCharacterLimit: 70,
27
+
12
28
  /* Component Helper Functions */
13
29
  functions: {
14
30
  /**
@@ -64,5 +80,15 @@ export default {
64
80
  * @returns {void}
65
81
  */
66
82
  resetFacetSlider: (section) => {},
83
+
84
+ /**
85
+ * Generate a query URL by calling the interop service
86
+ *
87
+ * @param {string} queryStr the query string to encode
88
+ * @param {string} root the root path for the URL
89
+ * @param {function} setUrlCallback callback function to set the generated URL
90
+ * @returns {Promise<void>}
91
+ */
92
+ generateUrl: async (queryStr, root, setUrlCallback) => {},
67
93
  },
68
94
  };
@@ -147,4 +147,10 @@ export default () => ({
147
147
  backgroundColor: '#DDEAE540',
148
148
  border: '1px solid #646464',
149
149
  },
150
+ localFindAssociatedIdsBackground: {
151
+ backgroundColor: '#F6A700',
152
+ },
153
+ localFindAssociatedIdsText: {
154
+ color: '#B36B00',
155
+ },
150
156
  });