From e43f1c42a3a8bed434a7243e7ea25ff53207eb95 Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Wed, 28 Sep 2022 19:00:34 -0400 Subject: [PATCH 1/6] Add filter select to available lists on lane editor. --- src/components/LaneCustomListsEditor.tsx | 44 +++++++++++++++++++++++- src/components/LaneEditor.tsx | 28 +++++++++++++++ src/stylesheets/lane_editor.scss | 10 ++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/components/LaneCustomListsEditor.tsx b/src/components/LaneCustomListsEditor.tsx index 659a62e34..ff77d2c63 100644 --- a/src/components/LaneCustomListsEditor.tsx +++ b/src/components/LaneCustomListsEditor.tsx @@ -4,12 +4,16 @@ import { CustomListData } from "../interfaces"; import AddIcon from "./icons/AddIcon"; import TrashIcon from "./icons/TrashIcon"; import GrabIcon from "./icons/GrabIcon"; +import EditableInput from "./EditableInput"; import { Button } from "library-simplified-reusable-components"; export interface LaneCustomListsEditorProps extends React.Props { allCustomLists: CustomListData[]; customListIds: number[]; + filter?: string; + filteredCustomLists?: CustomListData[]; + changeFilter?: (value: string) => void; onUpdate?: (customListIds: number[]) => void; } @@ -22,6 +26,11 @@ export default class LaneCustomListsEditor extends React.Component< LaneCustomListsEditorProps, LaneCustomListsEditorState > { + static defaultProps = { + filter: "owned", + filteredCustomLists: [], + }; + constructor(props) { super(props); this.state = { @@ -32,6 +41,38 @@ export default class LaneCustomListsEditor extends React.Component< this.onDragEnd = this.onDragEnd.bind(this); } + renderFilterSelect(): JSX.Element { + const filters = [ + ["All", ""], + ["Owned", "owned"], + ["Subscribed", "shared-in"], + ]; + + return ( +
+ Select filter type + + + {filters.map(([label, value]) => ( + + ))} + +
+ ); + } + render(): JSX.Element { return (

Available Lists ({this.listsNotInLane().length})

+ {this.renderFilterSelect()}
{ } export interface LaneEditorState { + filter: string; name: string; customListIds: number[]; inheritParentRestrictions: boolean; @@ -39,6 +40,7 @@ export default class LaneEditor extends React.Component< constructor(props) { super(props); this.state = { + filter: "owned", name: this.props.lane && this.props.lane.display_name, customListIds: (this.props.lane && this.props.lane.custom_list_ids) || [], inheritParentRestrictions: @@ -46,6 +48,7 @@ export default class LaneEditor extends React.Component< }; this.changeName = this.changeName.bind(this); this.changeCustomLists = this.changeCustomLists.bind(this); + this.changeFilter = this.changeFilter.bind(this); this.changeInheritParentRestrictions = this.changeInheritParentRestrictions.bind( this ); @@ -56,6 +59,24 @@ export default class LaneEditor extends React.Component< this.renderInfo = this.renderInfo.bind(this); } + filterLists(lists?: CustomListData[]) { + lists = lists || this.props.customLists || []; + + const { filter } = this.state; + + let selectedFilter = null; + + if (filter === "owned") { + selectedFilter = (list) => list.is_owner; + } else if (filter === "shared-in") { + selectedFilter = (list) => !list.is_owner; + } else if (filter === "shared-out") { + selectedFilter = (list) => list.is_owner && list.is_shared; + } + + return selectedFilter ? lists.filter(selectedFilter) : lists; + } + render(): JSX.Element { const parent = this.props.findParentOfLane(this.props.lane); return ( @@ -116,6 +137,9 @@ export default class LaneEditor extends React.Component< @@ -205,6 +229,10 @@ export default class LaneEditor extends React.Component< this.setState(Object.assign({}, this.state, { customListIds })); } + changeFilter(filter) { + this.setState({ filter }); + } + changeInheritParentRestrictions() { const inheritParentRestrictions = !this.state.inheritParentRestrictions; this.setState(Object.assign({}, this.state, { inheritParentRestrictions })); diff --git a/src/stylesheets/lane_editor.scss b/src/stylesheets/lane_editor.scss index fbfb589cc..aadbf98ea 100644 --- a/src/stylesheets/lane_editor.scss +++ b/src/stylesheets/lane_editor.scss @@ -93,8 +93,18 @@ } .droppable-header { + display: flex; + justify-content: space-between; flex: 0 0 auto; overflow: hidden; + + + .form-group { + .form-control { + display: inline; + width: auto; + } + } } .droppable { From ab7fc7718b09be1289b284d3d8aff304d95b1a7c Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Wed, 28 Sep 2022 21:34:48 -0400 Subject: [PATCH 2/6] Update tests. --- .../__tests__/LaneCustomListsEditor-test.tsx | 24 +++++++++++++++++++ src/components/__tests__/LaneEditor-test.tsx | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/components/__tests__/LaneCustomListsEditor-test.tsx b/src/components/__tests__/LaneCustomListsEditor-test.tsx index d6497fbe1..515ee5e98 100644 --- a/src/components/__tests__/LaneCustomListsEditor-test.tsx +++ b/src/components/__tests__/LaneCustomListsEditor-test.tsx @@ -26,6 +26,8 @@ describe("LaneCustomListsEditor", () => { ); let container = wrapper.find(".available-lists"); @@ -48,6 +50,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -69,6 +73,8 @@ describe("LaneCustomListsEditor", () => { ); let container = wrapper.find(".current-lists"); @@ -84,6 +90,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -107,6 +115,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -128,6 +138,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -149,6 +161,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -194,6 +208,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -235,6 +251,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -281,6 +299,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -304,6 +324,8 @@ describe("LaneCustomListsEditor", () => { ); @@ -326,6 +348,8 @@ describe("LaneCustomListsEditor", () => { ); diff --git a/src/components/__tests__/LaneEditor-test.tsx b/src/components/__tests__/LaneEditor-test.tsx index ff7fb957a..606efb5d3 100644 --- a/src/components/__tests__/LaneEditor-test.tsx +++ b/src/components/__tests__/LaneEditor-test.tsx @@ -131,14 +131,14 @@ describe("LaneEditor", () => { /> ); - let input = wrapper.find(EditableInput); + let input = wrapper.find(".lane-details").find(EditableInput); expect(input.props().checked).to.be.true; expect(input.props().label).to.contain("restrictions"); const onChange = input.props().onChange; onChange(); wrapper.update(); - input = wrapper.find(EditableInput); + input = wrapper.find(".lane-details").find(EditableInput); expect(input.props().checked).to.be.false; }); From 4d12c84b4586e94f3341991cf9a907b73e3fabba Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Wed, 28 Sep 2022 23:24:35 -0400 Subject: [PATCH 3/6] Add tests. --- .../__tests__/LaneCustomListsEditor-test.tsx | 79 +++++++++++++------ src/components/__tests__/LaneEditor-test.tsx | 37 +++++++++ 2 files changed, 94 insertions(+), 22 deletions(-) diff --git a/src/components/__tests__/LaneCustomListsEditor-test.tsx b/src/components/__tests__/LaneCustomListsEditor-test.tsx index 515ee5e98..b62fe6ad9 100644 --- a/src/components/__tests__/LaneCustomListsEditor-test.tsx +++ b/src/components/__tests__/LaneCustomListsEditor-test.tsx @@ -22,12 +22,17 @@ describe("LaneCustomListsEditor", () => { }); it("renders available lists", () => { + const filteredCustomListsData = [ + allCustomListsData[0], + allCustomListsData[2], + ]; + let wrapper = mount( ); let container = wrapper.find(".available-lists"); @@ -37,21 +42,19 @@ describe("LaneCustomListsEditor", () => { expect(droppable.length).to.equal(1); let lists = droppable.find(Draggable); - expect(lists.length).to.equal(3); + expect(lists.length).to.equal(2); expect(lists.at(0).text()).to.contain("list 1"); expect(lists.at(0).text()).to.contain("Items in list: 0"); - expect(lists.at(1).text()).to.contain("list 2"); - expect(lists.at(1).text()).to.contain("Items in list: 2"); - expect(lists.at(2).text()).to.contain("list 3"); - expect(lists.at(2).text()).to.contain("Items in list: 0"); + expect(lists.at(1).text()).to.contain("list 3"); + expect(lists.at(1).text()).to.contain("Items in list: 0"); wrapper = mount( ); @@ -64,8 +67,40 @@ describe("LaneCustomListsEditor", () => { lists = droppable.find(Draggable); expect(lists.length).to.equal(1); - expect(lists.at(0).text()).to.contain("list 2"); - expect(lists.at(0).text()).to.contain("Items in list: 2"); + expect(lists.at(0).text()).to.contain("list 3"); + expect(lists.at(0).text()).to.contain("Items in list: 0"); + }); + + it("renders filter select", () => { + const changeFilter = stub(); + + const wrapper = mount( + + ); + + const select = wrapper.find('select[name="filter"]'); + + expect(select.prop("value")).to.equal("owned"); + + const options = select.find("option"); + + expect(options.length).to.equal(3); + + expect(options.at(0).prop("value")).to.equal(""); + expect(options.at(1).prop("value")).to.equal("owned"); + expect(options.at(2).prop("value")).to.equal("shared-in"); + + select.getDOMNode().value = "shared-in"; + select.simulate("change"); + + expect(changeFilter.callCount).to.equal(1); + expect(changeFilter.args[0]).to.deep.equal(["shared-in"]); }); it("renders current lists", () => { @@ -73,7 +108,7 @@ describe("LaneCustomListsEditor", () => { ); @@ -90,7 +125,7 @@ describe("LaneCustomListsEditor", () => { ); @@ -115,7 +150,7 @@ describe("LaneCustomListsEditor", () => { ); @@ -138,7 +173,7 @@ describe("LaneCustomListsEditor", () => { ); @@ -161,7 +196,7 @@ describe("LaneCustomListsEditor", () => { @@ -208,7 +243,7 @@ describe("LaneCustomListsEditor", () => { ); @@ -251,7 +286,7 @@ describe("LaneCustomListsEditor", () => { @@ -299,7 +334,7 @@ describe("LaneCustomListsEditor", () => { @@ -324,7 +359,7 @@ describe("LaneCustomListsEditor", () => { @@ -348,7 +383,7 @@ describe("LaneCustomListsEditor", () => { diff --git a/src/components/__tests__/LaneEditor-test.tsx b/src/components/__tests__/LaneEditor-test.tsx index 606efb5d3..4c1dd9b55 100644 --- a/src/components/__tests__/LaneEditor-test.tsx +++ b/src/components/__tests__/LaneEditor-test.tsx @@ -146,9 +146,46 @@ describe("LaneEditor", () => { const listsEditor = wrapper.find(LaneCustomListsEditor); expect(listsEditor.length).to.equal(1); expect(listsEditor.props().allCustomLists).to.deep.equal(customListsData); + expect(listsEditor.props().filteredCustomLists).to.deep.equal( + customListsData + ); expect(listsEditor.props().customListIds).to.deep.equal([1]); }); + it("filters the lists passed to the custom lists editor's filteredCustomLists prop", () => { + const allCustomListsData = [ + { id: 1, name: "list 1", entries: [], is_owner: true, is_shared: false }, + { id: 2, name: "list 2", entries: [], is_owner: false, is_shared: true }, + ]; + + wrapper.setProps({ + customLists: allCustomListsData, + }); + + let listsEditor; + + listsEditor = wrapper.find(LaneCustomListsEditor); + + // The default filter is owned. + expect(listsEditor.props().filteredCustomLists).to.deep.equal([ + allCustomListsData[0], + ]); + + listsEditor.invoke("changeFilter")("shared-in"); + listsEditor = wrapper.find(LaneCustomListsEditor); + + expect(listsEditor.props().filteredCustomLists).to.deep.equal([ + allCustomListsData[1], + ]); + + listsEditor.invoke("changeFilter")(""); + listsEditor = wrapper.find(LaneCustomListsEditor); + + expect(listsEditor.props().filteredCustomLists).to.deep.equal( + allCustomListsData + ); + }); + it("deletes lane", () => { wrapper = mount( Date: Wed, 28 Sep 2022 23:27:12 -0400 Subject: [PATCH 4/6] Remove extra whitespace. --- src/stylesheets/lane_editor.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stylesheets/lane_editor.scss b/src/stylesheets/lane_editor.scss index aadbf98ea..ce38b0a2e 100644 --- a/src/stylesheets/lane_editor.scss +++ b/src/stylesheets/lane_editor.scss @@ -98,7 +98,6 @@ flex: 0 0 auto; overflow: hidden; - .form-group { .form-control { display: inline; From 4bd869358b12913311a3d7e2dc967e90bc3e5936 Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Thu, 29 Sep 2022 13:10:52 -0400 Subject: [PATCH 5/6] Add share icon to lists that are not owned. --- src/components/LaneCustomListsEditor.tsx | 15 ++++- .../__tests__/LaneCustomListsEditor-test.tsx | 61 +++++++++++++++++++ src/stylesheets/lane_editor.scss | 6 +- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/components/LaneCustomListsEditor.tsx b/src/components/LaneCustomListsEditor.tsx index ff77d2c63..ea0c37a78 100644 --- a/src/components/LaneCustomListsEditor.tsx +++ b/src/components/LaneCustomListsEditor.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { CustomListData } from "../interfaces"; import AddIcon from "./icons/AddIcon"; +import ShareIcon from "./icons/ShareIcon"; import TrashIcon from "./icons/TrashIcon"; import GrabIcon from "./icons/GrabIcon"; import EditableInput from "./EditableInput"; @@ -117,7 +118,12 @@ export default class LaneCustomListsEditor extends React.Component< >
-
{list.name}
+
+ {!list.is_owner && ( + + )} + {list.name} +
Items in list: {list.entry_count}
@@ -181,7 +187,12 @@ export default class LaneCustomListsEditor extends React.Component< >
-
{list.name}
+
+ {!list.is_owner && ( + + )} + {list.name} +
Items in list: {list.entry_count}
diff --git a/src/components/__tests__/LaneCustomListsEditor-test.tsx b/src/components/__tests__/LaneCustomListsEditor-test.tsx index b62fe6ad9..dab4e8070 100644 --- a/src/components/__tests__/LaneCustomListsEditor-test.tsx +++ b/src/components/__tests__/LaneCustomListsEditor-test.tsx @@ -6,6 +6,7 @@ import { shallow, mount } from "enzyme"; import { Droppable, Draggable } from "react-beautiful-dnd"; import LaneCustomListsEditor from "../LaneCustomListsEditor"; +import ShareIcon from "../icons/ShareIcon"; describe("LaneCustomListsEditor", () => { let wrapper; @@ -71,6 +72,37 @@ describe("LaneCustomListsEditor", () => { expect(lists.at(0).text()).to.contain("Items in list: 0"); }); + it("renders a share icon on available lists that are not owned by the current library", () => { + const sharedCustomListsData = [ + ...allCustomListsData, + { + id: 4, + name: "list 4", + entry_count: 0, + is_owner: false, + is_shared: true, + }, + ]; + + const wrapper = mount( + + ); + + const lists = wrapper.find(".available-lists").find(Draggable); + + expect(lists.length).to.equal(4); + + expect(lists.at(0).find(ShareIcon).length).to.equal(0); + expect(lists.at(1).find(ShareIcon).length).to.equal(0); + expect(lists.at(2).find(ShareIcon).length).to.equal(0); + expect(lists.at(3).find(ShareIcon).length).to.equal(1); + }); + it("renders filter select", () => { const changeFilter = stub(); @@ -145,6 +177,35 @@ describe("LaneCustomListsEditor", () => { expect(lists.at(1).text()).to.contain("Items in list: 0"); }); + it("renders a share icon on current lists that are not owned by the current library", () => { + const sharedCustomListsData = [ + ...allCustomListsData, + { + id: 4, + name: "list 4", + entry_count: 0, + is_owner: false, + is_shared: true, + }, + ]; + + const wrapper = mount( + + ); + + const lists = wrapper.find(".current-lists").find(Draggable); + + expect(lists.length).to.equal(2); + + expect(lists.at(0).find(ShareIcon).length).to.equal(0); + expect(lists.at(1).find(ShareIcon).length).to.equal(1); + }); + it("prevents dragging within available lists", () => { const wrapper = mount( svg { margin-top: 6px; margin-right: 10px; } @@ -159,6 +159,10 @@ .list-name, .list-id { font-weight: bold; + + svg { + margin-right: 4px; + } } .list-count { From c5d9e932e6f1243a3eabf5a0e4fd1c0fb5a4dc71 Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Mon, 3 Oct 2022 13:33:40 -0400 Subject: [PATCH 6/6] Consolidate duplicate list info rendering code. --- src/components/LaneCustomListsEditor.tsx | 46 +++++++++++------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/components/LaneCustomListsEditor.tsx b/src/components/LaneCustomListsEditor.tsx index ea0c37a78..e7d06da0c 100644 --- a/src/components/LaneCustomListsEditor.tsx +++ b/src/components/LaneCustomListsEditor.tsx @@ -74,6 +74,24 @@ export default class LaneCustomListsEditor extends React.Component< ); } + renderListInfo(list): JSX.Element { + return ( + <> + +
+
+ {!list.is_owner && ( + + )} + {list.name} +
+
Items in list: {list.entry_count}
+
ID-{list.id}
+
+ + ); + } + render(): JSX.Element { return ( - -
-
- {!list.is_owner && ( - - )} - {list.name} -
-
- Items in list: {list.entry_count} -
-
ID-{list.id}
-
+ {this.renderListInfo(list)}