reorder sources

This commit is contained in:
Bruce Liu 2020-06-02 19:13:46 +08:00
parent 22f528d3e0
commit c92b195cb8
4 changed files with 165 additions and 57 deletions

View File

@ -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<GroupsTabProps, GroupsTabState> {
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<GroupsTabProps, GroupsTabState> {
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<GroupsTabProps, GroupsTabState> {
}
]
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<GroupsTabProps, GroupsTabState> {
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<GroupsTabProps, GroupsTabState> {
render = () => (
<div className="tab-body">
{this.state.manageGroup
?<>
<Stack horizontal horizontalAlign="space-between" style={{height: 44}}>
{this.state.manageGroup && this.state.selectedGroup &&
<>
<Stack horizontal horizontalAlign="space-between" style={{height: 40}}>
<CommandBarButton
text="退出分组"
iconProps={{iconName: "BackToWindow"}}
@ -187,41 +261,49 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />}
</Stack>
<DetailsList
compact={true}
items={this.state.selectedGroup.sids.map(sid => this.props.sources[sid])}
columns={this.sourceColumns}
setKey="multiple"
selection={this.sourcesSelection}
selectionMode={SelectionMode.multiple} />
<MarqueeSelection selection={this.sourcesSelection}>
<DetailsList
compact={true}
items={this.state.selectedGroup.sids.map(sid => this.props.sources[sid])}
columns={this.sourceColumns}
dragDropEvents={this.sourcesDragDropEvents}
setKey="multiple"
selection={this.sourcesSelection}
selectionMode={SelectionMode.multiple} />
</MarqueeSelection>
</>
:<>
<Label></Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={v => v.trim().length == 0 ? "名称不得为空" : ""}
validateOnLoad={false}
placeholder="输入名称"
value={this.state.newGroupName}
name="newGroupName"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<PrimaryButton
disabled={this.state.newGroupName.length == 0}
onClick={this.createGroup}
text="新建" />
</Stack.Item>
</Stack>
</>}
{(!this.state.manageGroup || !this.state.selectedGroup)
?<>
<form onSubmit={this.createGroup}>
<Label htmlFor="newGroupName"></Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={v => v.trim().length == 0 ? "名称不得为空" : ""}
validateOnLoad={false}
placeholder="输入名称"
value={this.state.newGroupName}
id="newGroupName"
name="newGroupName"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<PrimaryButton
disabled={this.state.newGroupName.length == 0}
type="sumbit"
text="新建" />
</Stack.Item>
</Stack>
</form>
<DetailsList
compact={true}
items={Object.values(this.props.groups)}
items={this.props.groups}
columns={this.groupColumns}
setKey="selected"
onItemInvoked={this.manageGroup}
dragDropEvents={this.groupDragDropEvents}
selection={this.groupSelection}
selectionMode={SelectionMode.single} />
@ -272,8 +354,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
</Stack>
</>
)}
</>}
</> : null}
</div>
)
}

View File

@ -76,6 +76,11 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
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 = () => (
<div className="tab-body">
<Label>OPML文件</Label>
@ -88,24 +93,27 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
</Stack.Item>
</Stack>
<Label></Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={v => urlTest(v.trim()) ? "" : "请正确输入URL"}
validateOnLoad={false}
placeholder="输入URL"
value={this.state.newUrl}
name="newUrl"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<PrimaryButton
disabled={!urlTest(this.state.newUrl)}
onClick={() => this.props.addSource(this.state.newUrl)}
text="添加" />
</Stack.Item>
</Stack>
<form onSubmit={this.addSource}>
<Label htmlFor="newUrl"></Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={v => urlTest(v.trim()) ? "" : "请正确输入URL"}
validateOnLoad={false}
placeholder="输入URL"
value={this.state.newUrl}
id="newUrl"
name="newUrl"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<PrimaryButton
disabled={!urlTest(this.state.newUrl)}
type="submit"
text="添加" />
</Stack.Item>
</Stack>
</form>
<DetailsList
items={Object.values(this.props.sources)}

View File

@ -3,7 +3,7 @@ import { connect } from "react-redux"
import { createSelector } from "reselect"
import { RootState } from "../../scripts/reducer"
import GroupsTab from "../../components/settings/groups"
import { createSourceGroup, SourceGroup, updateSourceGroup, addSourceToGroup, deleteSourceGroup, removeSourceFromGroup } from "../../scripts/models/page"
import { createSourceGroup, SourceGroup, updateSourceGroup, addSourceToGroup, deleteSourceGroup, removeSourceFromGroup, reorderSourceGroups } from "../../scripts/models/page"
const getSources = (state: RootState) => 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)

View File

@ -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<number> {
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: [