mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-09 08:18:38 +01:00
reorder sources
This commit is contained in:
parent
22f528d3e0
commit
c92b195cb8
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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)}
|
||||
|
@ -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)
|
||||
|
@ -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: [
|
||||
|
Loading…
x
Reference in New Issue
Block a user