diff --git a/src/components/settings/groups.tsx b/src/components/settings/groups.tsx index aa94411..629f0ef 100644 --- a/src/components/settings/groups.tsx +++ b/src/components/settings/groups.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { SourceGroup } from "../../scripts/models/page" import { SourceState, RSSSource } from "../../scripts/models/source" import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack, - TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton } from "@fluentui/react" + TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton, MarqueeSelection, IDragDropEvents, IDragDropContext } from "@fluentui/react" import DangerButton from "../utils/danger-button" type GroupsTabProps = { @@ -12,7 +12,8 @@ type GroupsTabProps = { updateGroup: (group: SourceGroup) => void, addToGroup: (groupIndex: number, sid: number) => void, deleteGroup: (groupIndex: number) => void, - removeFromGroup: (groupIndex: number, sids: number[]) => void + removeFromGroup: (groupIndex: number, sids: number[]) => void, + reorderGroups: (groups: SourceGroup[]) => void } type GroupsTabState = { @@ -25,7 +26,13 @@ type GroupsTabState = { class GroupsTab extends React.Component { groupSelection: Selection + groupDragDropEvents: IDragDropEvents + groupDraggedItem: SourceGroup + groupDraggedIndex = -1 sourcesSelection: Selection + sourcesDragDropEvents: IDragDropEvents + sourcesDraggedItem: RSSSource + sourcesDraggedIndex = -1 constructor(props) { super(props) @@ -37,6 +44,8 @@ class GroupsTab extends React.Component { dropdownIndex: null, manageGroup: false } + this.groupDragDropEvents = this.getGroupDragDropEvents() + this.sourcesDragDropEvents = this.getSourcesDragDropEvents() this.groupSelection = new Selection({ getKey: g => (g as SourceGroup).index, onSelectionChanged: () => { @@ -123,6 +132,70 @@ class GroupsTab extends React.Component { } ] + getGroupDragDropEvents = (): IDragDropEvents => ({ + canDrop: () => true, + canDrag: () => true, + onDrop: (item?: SourceGroup) => { + if (this.groupDraggedItem) { + this.reorderGroups(item) + } + }, + onDragStart: (item?: SourceGroup, itemIndex?: number) => { + this.groupDraggedItem = item + this.groupDraggedIndex = itemIndex! + }, + onDragEnd: () => { + this.groupDraggedItem = undefined + this.groupDraggedIndex = -1 + }, + }) + + reorderGroups = (item: SourceGroup) => { + let draggedItem = this.groupSelection.isIndexSelected(this.groupDraggedIndex) + ? this.groupSelection.getSelection()[0] as SourceGroup + : this.groupDraggedItem! + + let insertIndex = item.index + let groups = this.props.groups.filter(g => g.index != draggedItem.index) + + groups.splice(insertIndex, 0, draggedItem) + + this.props.reorderGroups(groups) + } + + getSourcesDragDropEvents = (): IDragDropEvents => ({ + canDrop: () => true, + canDrag: () => true, + onDrop: (item?: RSSSource) => { + if (this.sourcesDraggedItem) { + this.reorderSources(item) + } + }, + onDragStart: (item?: RSSSource, itemIndex?: number) => { + this.sourcesDraggedItem = item + this.sourcesDraggedIndex = itemIndex! + }, + onDragEnd: () => { + this.sourcesDraggedItem = undefined + this.sourcesDraggedIndex = -1 + }, + }) + + reorderSources = (item: RSSSource) => { + let draggedItems = this.sourcesSelection.isIndexSelected(this.sourcesDraggedIndex) + ? (this.sourcesSelection.getSelection() as RSSSource[]).map(s => s.sid) + : [this.sourcesDraggedItem!.sid] + + let insertIndex = this.state.selectedGroup.sids.indexOf(item.sid) + let items = this.state.selectedGroup.sids.filter(sid => !draggedItems.includes(sid)) + + items.splice(insertIndex, 0, ...draggedItems) + + let group = { ...this.state.selectedGroup, sids: items } + this.props.updateGroup(group) + this.setState({ selectedGroup: group }) + } + manageGroup = (g: SourceGroup) => { if (g.isMultiple) { this.setState({ @@ -143,8 +216,9 @@ class GroupsTab extends React.Component { this.setState({[name]: event.target.value.trim()}) } - createGroup = () => { - this.props.createGroup(this.state.newGroupName) + createGroup = (event: React.FormEvent) => { + event.preventDefault() + if (this.state.newGroupName.length > 0) this.props.createGroup(this.state.newGroupName) } addToGroup = () => { @@ -174,9 +248,9 @@ class GroupsTab extends React.Component { render = () => (
- {this.state.manageGroup - ?<> - + {this.state.manageGroup && this.state.selectedGroup && + <> + { iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />} - this.props.sources[sid])} - columns={this.sourceColumns} - setKey="multiple" - selection={this.sourcesSelection} - selectionMode={SelectionMode.multiple} /> + + this.props.sources[sid])} + columns={this.sourceColumns} + dragDropEvents={this.sourcesDragDropEvents} + setKey="multiple" + selection={this.sourcesSelection} + selectionMode={SelectionMode.multiple} /> + - - :<> - - - - v.trim().length == 0 ? "名称不得为空" : ""} - validateOnLoad={false} - placeholder="输入名称" - value={this.state.newGroupName} - name="newGroupName" - onChange={this.handleInputChange} /> - - - - - + } + {(!this.state.manageGroup || !this.state.selectedGroup) + ?<> +
+ + + + v.trim().length == 0 ? "名称不得为空" : ""} + validateOnLoad={false} + placeholder="输入名称" + value={this.state.newGroupName} + id="newGroupName" + name="newGroupName" + onChange={this.handleInputChange} /> + + + + + +
@@ -272,8 +354,7 @@ class GroupsTab extends React.Component {
)} - } - + : null}
) } diff --git a/src/components/settings/sources.tsx b/src/components/settings/sources.tsx index 087cab3..226a0aa 100644 --- a/src/components/settings/sources.tsx +++ b/src/components/settings/sources.tsx @@ -76,6 +76,11 @@ class SourcesTab extends React.Component { this.setState({[name]: event.target.value.trim()}) } + addSource = (event: React.FormEvent) => { + event.preventDefault() + if (urlTest(this.state.newUrl)) this.props.addSource(this.state.newUrl) + } + render = () => (
@@ -88,24 +93,27 @@ class SourcesTab extends React.Component { - - - - urlTest(v.trim()) ? "" : "请正确输入URL"} - validateOnLoad={false} - placeholder="输入URL" - value={this.state.newUrl} - name="newUrl" - onChange={this.handleInputChange} /> - - - this.props.addSource(this.state.newUrl)} - text="添加" /> - - +
+ + + + urlTest(v.trim()) ? "" : "请正确输入URL"} + validateOnLoad={false} + placeholder="输入URL" + value={this.state.newUrl} + id="newUrl" + name="newUrl" + onChange={this.handleInputChange} /> + + + + + +
state.sources const getGroups = (state: RootState) => state.page.sourceGroups @@ -22,7 +22,8 @@ const mapDispatchToProps = dispatch => ({ updateGroup: (group: SourceGroup) => dispatch(updateSourceGroup(group)), addToGroup: (groupIndex: number, sid: number) => dispatch(addSourceToGroup(groupIndex, sid)), deleteGroup: (groupIndex: number) => dispatch(deleteSourceGroup(groupIndex)), - removeFromGroup: (groupIndex: number, sids: number[]) => dispatch(removeSourceFromGroup(groupIndex, sids)) + removeFromGroup: (groupIndex: number, sids: number[]) => dispatch(removeSourceFromGroup(groupIndex, sids)), + reorderGroups: (groups: SourceGroup[]) => dispatch(reorderSourceGroups(groups)) }) const GroupsTabContainer = connect(mapStateToProps, mapDispatchToProps)(GroupsTab) diff --git a/src/scripts/models/page.ts b/src/scripts/models/page.ts index ccc8785..ba097cc 100644 --- a/src/scripts/models/page.ts +++ b/src/scripts/models/page.ts @@ -189,6 +189,20 @@ export function updateSourceGroup(group: SourceGroup): AppThunk { } } +function reorderSourceGroupsDone(groups: SourceGroup[]): SourceGroupActionTypes { + return { + type: REORDER_SOURCE_GROUPS, + groups: groups + } +} + +export function reorderSourceGroups(groups: SourceGroup[]): AppThunk { + return (dispatch, getState) => { + dispatch(reorderSourceGroupsDone(groups)) + SourceGroup.save(getState().page.sourceGroups) + } +} + async function outlineToSource(dispatch: AppDispatch, outline: Element): Promise { let url = outline.getAttribute("xmlUrl").trim() let name = outline.getAttribute("text") || outline.getAttribute("name") @@ -303,6 +317,10 @@ export function pageReducer( ...state.sourceGroups.slice(action.groupIndex + 1) ] } + case REORDER_SOURCE_GROUPS: return { + ...state, + sourceGroups: action.groups + } case DELETE_SOURCE_GROUP: return { ...state, sourceGroups: [