Skip to content

Commit

Permalink
Implement list sharing. (#42)
Browse files Browse the repository at this point in the history
* Implement UI for list sharing.

* Update/add tests.

* Update changelog.

* Tweak code formatting.

* Update wording.
  • Loading branch information
ray-lee authored Sep 20, 2022
1 parent eda1994 commit 0035819
Show file tree
Hide file tree
Showing 40 changed files with 1,668 additions and 347 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## Changelog

### v0.4.0

#### Updated

- Added support for list sharing.

### v0.3.0

#### Fixed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@
"colors-cli": "^1.0.27",
"copy-webpack-plugin": "6.4.0",
"css-loader": "^5.2.4",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"eslint": "^7.0.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
Expand Down
56 changes: 56 additions & 0 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export default class ActionCreator extends BaseActionCreator {
static readonly CUSTOM_LIST_DETAILS_MORE = "CUSTOM_LIST_DETAILS_MORE";
static readonly EDIT_CUSTOM_LIST = "EDIT_CUSTOM_LIST";
static readonly DELETE_CUSTOM_LIST = "DELETE_CUSTOM_LIST";
static readonly CUSTOM_LIST_SHARE = "CUSTOM_LIST_SHARE";
static readonly EDIT_CUSTOM_LIST_SHARE = "EDIT_CUSTOM_LIST_SHARE";
static readonly CUSTOM_LISTS_AFTER_SHARE = "CUSTOM_LISTS_AFTER_SHARE";
static readonly OPEN_CUSTOM_LIST_EDITOR = "OPEN_CUSTOM_LIST_EDITOR";
static readonly UPDATE_CUSTOM_LIST_EDITOR_PROPERTY =
"UPDATE_CUSTOM_LIST_EDITOR_PROPERTY";
Expand Down Expand Up @@ -899,6 +902,59 @@ export default class ActionCreator extends BaseActionCreator {
).bind(this);
}

shareCustomList(library: string, listId: string) {
const shareUrl = "/" + library + "/admin/custom_list/" + listId + "/share";

const shareAction = this.postForm(
ActionCreator.EDIT_CUSTOM_LIST_SHARE,
shareUrl,
null
).bind(this);

const loadUrl = "/" + library + "/admin/custom_lists";

const loadAction = this.fetchJSON<CustomListsData>(
ActionCreator.CUSTOM_LISTS_AFTER_SHARE,
loadUrl
).bind(this);

return ((dispatch) => {
dispatch({
type: `${ActionCreator.CUSTOM_LIST_SHARE}_${ActionCreator.REQUEST}`,
listId,
});

return dispatch(shareAction)
.then(() => dispatch(loadAction))
.then((data) =>
Promise.all([
// Dispatch CUSTOM_LISTS_LOAD to update the sidebar.

dispatch({
type: `${ActionCreator.CUSTOM_LISTS}_${ActionCreator.LOAD}`,
data,
isAfterShare: true,
}),

// Dispatch CUSTOM_LIST_SHARE_SUCCESS to update the custom list editor.

dispatch({
type: `${ActionCreator.CUSTOM_LIST_SHARE}_${ActionCreator.SUCCESS}`,
listId,
data,
}),
])
)
.catch((error) =>
dispatch({
type: `${ActionCreator.CUSTOM_LIST_SHARE}_${ActionCreator.FAILURE}`,
listId,
error,
})
);
}).bind(this);
}

openCustomListEditor(listId: string) {
return (dispatch, getState) =>
dispatch({
Expand Down
14 changes: 10 additions & 4 deletions src/components/AdvancedSearchBooleanFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import AdvancedSearchFilter from "./AdvancedSearchFilter";

export interface AdvancedSearchBooleanFilterProps {
query: AdvancedSearchQuery;
readOnly?: boolean;
selectedQueryId?: string;
onBooleanChange: (id: string, bool: string) => void;
onMove: (id: string, targetId: string) => void;
Expand All @@ -23,6 +24,7 @@ const renderSeparator = (query, index) => {

export default function AdvancedSearchBooleanFilter({
query,
readOnly,
selectedQueryId,
onBooleanChange,
onMove,
Expand Down Expand Up @@ -92,25 +94,28 @@ export default function AdvancedSearchBooleanFilter({
[query.id, onMove]
);

const isSelected = !readOnly && selectedQueryId === query.id;

const className = classNames({
"advanced-search-boolean-filter": true,
"drag-drop": dropProps.isOver && dropProps.canDrop,
selected: selectedQueryId === query.id,
selected: isSelected,
});

return (
<div
aria-selected={selectedQueryId === query.id}
aria-selected={isSelected}
className={className}
onClick={handleClick}
onKeyDown={handleKeyDown}
ref={drop}
ref={readOnly ? undefined : drop}
role="treeitem"
tabIndex={0}
>
<header>
<div>
<select
disabled={readOnly}
onBlur={handleBooleanChange}
onChange={handleBooleanChange}
value={query.and ? "and" : "or"}
Expand All @@ -124,7 +129,7 @@ export default function AdvancedSearchBooleanFilter({
</select>
</div>

<button onClick={handleRemoveButtonClick}>×</button>
{!readOnly && <button onClick={handleRemoveButtonClick}>×</button>}
</header>

<ul>
Expand All @@ -133,6 +138,7 @@ export default function AdvancedSearchBooleanFilter({
{renderSeparator(query, index)}{" "}
<AdvancedSearchFilter
query={child}
readOnly={readOnly}
selectedQueryId={selectedQueryId}
onBooleanChange={onBooleanChange}
onMove={onMove}
Expand Down
15 changes: 11 additions & 4 deletions src/components/AdvancedSearchBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import AdvancedSearchFilterInput from "./AdvancedSearchFilterInput";
import AdvancedSearchFilterViewer from "./AdvancedSearchFilterViewer";

export interface AdvancedSearchBuilderProps {
isOwner?: boolean;
name: string;
query: AdvancedSearchQuery;
selectedQueryId: string;
Expand Down Expand Up @@ -40,6 +41,7 @@ export const operators = [
];

export default function AdvancedSearchBuilder({
isOwner,
name,
query,
selectedQueryId,
Expand All @@ -49,16 +51,21 @@ export default function AdvancedSearchBuilder({
removeQuery,
selectQuery,
}: AdvancedSearchBuilderProps) {
const readOnly = !isOwner;

return (
<DndProvider backend={HTML5Backend}>
<div className="advanced-search">
<AdvancedSearchFilterInput
name={name}
onAdd={(query) => addQuery?.(name, query)}
/>
{!readOnly && (
<AdvancedSearchFilterInput
name={name}
onAdd={(query) => addQuery?.(name, query)}
/>
)}

<AdvancedSearchFilterViewer
query={query}
readOnly={readOnly}
selectedQueryId={selectedQueryId}
onBooleanChange={(id, bool) => updateQueryBoolean?.(name, id, bool)}
onMove={(id, targetId) => moveQuery?.(name, id, targetId)}
Expand Down
4 changes: 4 additions & 0 deletions src/components/AdvancedSearchFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ export interface AdvancedSearchFilterProps {
onSelect: (id: string) => void;
onRemove: (id: string) => void;
query: AdvancedSearchQuery;
readOnly?: boolean;
selectedQueryId?: string;
}

export default function AdvancedSearchFilter({
query,
readOnly,
selectedQueryId,
onBooleanChange,
onMove,
Expand All @@ -25,6 +27,7 @@ export default function AdvancedSearchFilter({
return (
<AdvancedSearchBooleanFilter
query={query}
readOnly={readOnly}
selectedQueryId={selectedQueryId}
onBooleanChange={onBooleanChange}
onMove={onMove}
Expand All @@ -37,6 +40,7 @@ export default function AdvancedSearchFilter({
return (
<AdvancedSearchValueFilter
query={query}
readOnly={readOnly}
selected={query.id === selectedQueryId}
onMove={onMove}
onSelect={onSelect}
Expand Down
3 changes: 3 additions & 0 deletions src/components/AdvancedSearchFilterViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AdvancedSearchFilter from "./AdvancedSearchFilter";

export interface AdvancedSearchFilterViewerProps {
query: AdvancedSearchQuery;
readOnly?: boolean;
selectedQueryId?: string;
onBooleanChange: (id: string, bool: string) => void;
onMove: (id: string, targetId: string) => void;
Expand All @@ -13,6 +14,7 @@ export interface AdvancedSearchFilterViewerProps {

export default function AdvancedSearchFilterViewer({
query,
readOnly,
selectedQueryId,
onBooleanChange,
onMove,
Expand All @@ -23,6 +25,7 @@ export default function AdvancedSearchFilterViewer({
<div className="advanced-search-filter-tree" role="tree">
<AdvancedSearchFilter
query={query}
readOnly={readOnly}
selectedQueryId={selectedQueryId}
onBooleanChange={onBooleanChange}
onMove={onMove}
Expand Down
11 changes: 7 additions & 4 deletions src/components/AdvancedSearchValueFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { fields, operators } from "./AdvancedSearchBuilder";

export interface AdvancedSearchValueFilterProps {
query: AdvancedSearchQuery;
readOnly?: boolean;
selected?: boolean;
onMove: (id: string, targetId: string) => void;
onRemove: (id: string) => void;
Expand All @@ -32,6 +33,7 @@ function getOpSymbol(value) {

export default ({
query,
readOnly,
selected,
onMove,
onRemove,
Expand Down Expand Up @@ -98,29 +100,30 @@ export default ({
[query.id, onMove]
);

const isSelected = !readOnly && selected;
const { key, op, value } = query;

const className = classNames({
"advanced-search-value-filter": true,
"drag-drop": dropProps.isOver && dropProps.canDrop,
selected,
selected: isSelected,
});

return (
<div
aria-selected={selected}
aria-selected={isSelected}
className={className}
onClick={handleClick}
onKeyDown={handleKeyDown}
ref={(node) => drag(drop(node))}
ref={readOnly ? undefined : (node) => drag(drop(node))}
role="treeitem"
tabIndex={0}
>
<span>
{getFieldLabel(key)} {getOpSymbol(op)} {value}
</span>

<button onClick={handleRemoveButtonClick}>×</button>
{!readOnly && <button onClick={handleRemoveButtonClick}>×</button>}
</div>
);
};
Loading

0 comments on commit 0035819

Please sign in to comment.