mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-04-24 07:07:27 +02: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 { SourceGroup } from "../../scripts/models/page"
|
||||||
import { SourceState, RSSSource } from "../../scripts/models/source"
|
import { SourceState, RSSSource } from "../../scripts/models/source"
|
||||||
import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack,
|
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"
|
import DangerButton from "../utils/danger-button"
|
||||||
|
|
||||||
type GroupsTabProps = {
|
type GroupsTabProps = {
|
||||||
@ -12,7 +12,8 @@ type GroupsTabProps = {
|
|||||||
updateGroup: (group: SourceGroup) => void,
|
updateGroup: (group: SourceGroup) => void,
|
||||||
addToGroup: (groupIndex: number, sid: number) => void,
|
addToGroup: (groupIndex: number, sid: number) => void,
|
||||||
deleteGroup: (groupIndex: number) => void,
|
deleteGroup: (groupIndex: number) => void,
|
||||||
removeFromGroup: (groupIndex: number, sids: number[]) => void
|
removeFromGroup: (groupIndex: number, sids: number[]) => void,
|
||||||
|
reorderGroups: (groups: SourceGroup[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupsTabState = {
|
type GroupsTabState = {
|
||||||
@ -25,7 +26,13 @@ type GroupsTabState = {
|
|||||||
|
|
||||||
class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||||
groupSelection: Selection
|
groupSelection: Selection
|
||||||
|
groupDragDropEvents: IDragDropEvents
|
||||||
|
groupDraggedItem: SourceGroup
|
||||||
|
groupDraggedIndex = -1
|
||||||
sourcesSelection: Selection
|
sourcesSelection: Selection
|
||||||
|
sourcesDragDropEvents: IDragDropEvents
|
||||||
|
sourcesDraggedItem: RSSSource
|
||||||
|
sourcesDraggedIndex = -1
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@ -37,6 +44,8 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
|||||||
dropdownIndex: null,
|
dropdownIndex: null,
|
||||||
manageGroup: false
|
manageGroup: false
|
||||||
}
|
}
|
||||||
|
this.groupDragDropEvents = this.getGroupDragDropEvents()
|
||||||
|
this.sourcesDragDropEvents = this.getSourcesDragDropEvents()
|
||||||
this.groupSelection = new Selection({
|
this.groupSelection = new Selection({
|
||||||
getKey: g => (g as SourceGroup).index,
|
getKey: g => (g as SourceGroup).index,
|
||||||
onSelectionChanged: () => {
|
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) => {
|
manageGroup = (g: SourceGroup) => {
|
||||||
if (g.isMultiple) {
|
if (g.isMultiple) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -143,8 +216,9 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
|||||||
this.setState({[name]: event.target.value.trim()})
|
this.setState({[name]: event.target.value.trim()})
|
||||||
}
|
}
|
||||||
|
|
||||||
createGroup = () => {
|
createGroup = (event: React.FormEvent) => {
|
||||||
this.props.createGroup(this.state.newGroupName)
|
event.preventDefault()
|
||||||
|
if (this.state.newGroupName.length > 0) this.props.createGroup(this.state.newGroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
addToGroup = () => {
|
addToGroup = () => {
|
||||||
@ -174,9 +248,9 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
|||||||
|
|
||||||
render = () => (
|
render = () => (
|
||||||
<div className="tab-body">
|
<div className="tab-body">
|
||||||
{this.state.manageGroup
|
{this.state.manageGroup && this.state.selectedGroup &&
|
||||||
?<>
|
<>
|
||||||
<Stack horizontal horizontalAlign="space-between" style={{height: 44}}>
|
<Stack horizontal horizontalAlign="space-between" style={{height: 40}}>
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
text="退出分组"
|
text="退出分组"
|
||||||
iconProps={{iconName: "BackToWindow"}}
|
iconProps={{iconName: "BackToWindow"}}
|
||||||
@ -187,17 +261,22 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
|||||||
iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />}
|
iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<MarqueeSelection selection={this.sourcesSelection}>
|
||||||
<DetailsList
|
<DetailsList
|
||||||
compact={true}
|
compact={true}
|
||||||
items={this.state.selectedGroup.sids.map(sid => this.props.sources[sid])}
|
items={this.state.selectedGroup.sids.map(sid => this.props.sources[sid])}
|
||||||
columns={this.sourceColumns}
|
columns={this.sourceColumns}
|
||||||
|
dragDropEvents={this.sourcesDragDropEvents}
|
||||||
setKey="multiple"
|
setKey="multiple"
|
||||||
selection={this.sourcesSelection}
|
selection={this.sourcesSelection}
|
||||||
selectionMode={SelectionMode.multiple} />
|
selectionMode={SelectionMode.multiple} />
|
||||||
|
</MarqueeSelection>
|
||||||
|
|
||||||
</>
|
</>}
|
||||||
:<>
|
{(!this.state.manageGroup || !this.state.selectedGroup)
|
||||||
<Label>新建分组</Label>
|
?<>
|
||||||
|
<form onSubmit={this.createGroup}>
|
||||||
|
<Label htmlFor="newGroupName">新建分组</Label>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<TextField
|
<TextField
|
||||||
@ -205,23 +284,26 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
|||||||
validateOnLoad={false}
|
validateOnLoad={false}
|
||||||
placeholder="输入名称"
|
placeholder="输入名称"
|
||||||
value={this.state.newGroupName}
|
value={this.state.newGroupName}
|
||||||
|
id="newGroupName"
|
||||||
name="newGroupName"
|
name="newGroupName"
|
||||||
onChange={this.handleInputChange} />
|
onChange={this.handleInputChange} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
disabled={this.state.newGroupName.length == 0}
|
disabled={this.state.newGroupName.length == 0}
|
||||||
onClick={this.createGroup}
|
type="sumbit"
|
||||||
text="新建" />
|
text="新建" />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</form>
|
||||||
|
|
||||||
<DetailsList
|
<DetailsList
|
||||||
compact={true}
|
compact={true}
|
||||||
items={Object.values(this.props.groups)}
|
items={this.props.groups}
|
||||||
columns={this.groupColumns}
|
columns={this.groupColumns}
|
||||||
setKey="selected"
|
setKey="selected"
|
||||||
onItemInvoked={this.manageGroup}
|
onItemInvoked={this.manageGroup}
|
||||||
|
dragDropEvents={this.groupDragDropEvents}
|
||||||
selection={this.groupSelection}
|
selection={this.groupSelection}
|
||||||
selectionMode={SelectionMode.single} />
|
selectionMode={SelectionMode.single} />
|
||||||
|
|
||||||
@ -272,8 +354,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>}
|
</> : null}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,11 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
this.setState({[name]: event.target.value.trim()})
|
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 = () => (
|
render = () => (
|
||||||
<div className="tab-body">
|
<div className="tab-body">
|
||||||
<Label>OPML文件</Label>
|
<Label>OPML文件</Label>
|
||||||
@ -88,7 +93,8 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Label>添加订阅源</Label>
|
<form onSubmit={this.addSource}>
|
||||||
|
<Label htmlFor="newUrl">添加订阅源</Label>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<TextField
|
<TextField
|
||||||
@ -96,16 +102,18 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
validateOnLoad={false}
|
validateOnLoad={false}
|
||||||
placeholder="输入URL"
|
placeholder="输入URL"
|
||||||
value={this.state.newUrl}
|
value={this.state.newUrl}
|
||||||
|
id="newUrl"
|
||||||
name="newUrl"
|
name="newUrl"
|
||||||
onChange={this.handleInputChange} />
|
onChange={this.handleInputChange} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
disabled={!urlTest(this.state.newUrl)}
|
disabled={!urlTest(this.state.newUrl)}
|
||||||
onClick={() => this.props.addSource(this.state.newUrl)}
|
type="submit"
|
||||||
text="添加" />
|
text="添加" />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</form>
|
||||||
|
|
||||||
<DetailsList
|
<DetailsList
|
||||||
items={Object.values(this.props.sources)}
|
items={Object.values(this.props.sources)}
|
||||||
|
@ -3,7 +3,7 @@ import { connect } from "react-redux"
|
|||||||
import { createSelector } from "reselect"
|
import { createSelector } from "reselect"
|
||||||
import { RootState } from "../../scripts/reducer"
|
import { RootState } from "../../scripts/reducer"
|
||||||
import GroupsTab from "../../components/settings/groups"
|
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 getSources = (state: RootState) => state.sources
|
||||||
const getGroups = (state: RootState) => state.page.sourceGroups
|
const getGroups = (state: RootState) => state.page.sourceGroups
|
||||||
@ -22,7 +22,8 @@ const mapDispatchToProps = dispatch => ({
|
|||||||
updateGroup: (group: SourceGroup) => dispatch(updateSourceGroup(group)),
|
updateGroup: (group: SourceGroup) => dispatch(updateSourceGroup(group)),
|
||||||
addToGroup: (groupIndex: number, sid: number) => dispatch(addSourceToGroup(groupIndex, sid)),
|
addToGroup: (groupIndex: number, sid: number) => dispatch(addSourceToGroup(groupIndex, sid)),
|
||||||
deleteGroup: (groupIndex: number) => dispatch(deleteSourceGroup(groupIndex)),
|
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)
|
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> {
|
async function outlineToSource(dispatch: AppDispatch, outline: Element): Promise<number> {
|
||||||
let url = outline.getAttribute("xmlUrl").trim()
|
let url = outline.getAttribute("xmlUrl").trim()
|
||||||
let name = outline.getAttribute("text") || outline.getAttribute("name")
|
let name = outline.getAttribute("text") || outline.getAttribute("name")
|
||||||
@ -303,6 +317,10 @@ export function pageReducer(
|
|||||||
...state.sourceGroups.slice(action.groupIndex + 1)
|
...state.sourceGroups.slice(action.groupIndex + 1)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
case REORDER_SOURCE_GROUPS: return {
|
||||||
|
...state,
|
||||||
|
sourceGroups: action.groups
|
||||||
|
}
|
||||||
case DELETE_SOURCE_GROUP: return {
|
case DELETE_SOURCE_GROUP: return {
|
||||||
...state,
|
...state,
|
||||||
sourceGroups: [
|
sourceGroups: [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user