2020-08-02 14:03:40 +08:00

384 lines
14 KiB
TypeScript

import * as React from "react"
import intl from "react-intl-universal"
import { SourceGroup } from "../../schema-types"
import { SourceState, RSSSource } from "../../scripts/models/source"
import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack,
TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton, MarqueeSelection, IDragDropEvents, MessageBar, MessageBarType } from "@fluentui/react"
import DangerButton from "../utils/danger-button"
type GroupsTabProps = {
sources: SourceState,
groups: SourceGroup[],
serviceOn: boolean,
createGroup: (name: string) => void,
updateGroup: (group: SourceGroup) => void,
addToGroup: (groupIndex: number, sid: number) => void,
deleteGroup: (groupIndex: number) => void,
removeFromGroup: (groupIndex: number, sids: number[]) => void,
reorderGroups: (groups: SourceGroup[]) => void
}
type GroupsTabState = {
[formName: string]: any,
selectedGroup: SourceGroup,
selectedSources: RSSSource[],
dropdownIndex: number,
manageGroup: boolean
}
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)
this.state = {
editGroupName: "",
newGroupName: "",
selectedGroup: null,
selectedSources: null,
dropdownIndex: null,
manageGroup: false
}
this.groupDragDropEvents = this.getGroupDragDropEvents()
this.sourcesDragDropEvents = this.getSourcesDragDropEvents()
this.groupSelection = new Selection({
getKey: g => (g as SourceGroup).index,
onSelectionChanged: () => {
let g = this.groupSelection.getSelectedCount()
? this.groupSelection.getSelection()[0] as SourceGroup : null
this.setState({
selectedGroup: g,
editGroupName: g && g.isMultiple ? g.name : ""
})
}
})
this.sourcesSelection = new Selection({
getKey: s => (s as RSSSource).sid,
onSelectionChanged: () => {
let sources = this.sourcesSelection.getSelectedCount()
? this.sourcesSelection.getSelection() as RSSSource[] : null
this.setState({
selectedSources: sources
})
}
})
}
groupColumns = (): IColumn[] => [
{
key: "type",
name: intl.get("groups.type"),
minWidth: 40,
maxWidth: 40,
data: "string",
onRender: (g: SourceGroup) => <>
{g.isMultiple ? intl.get("groups.group") : intl.get("groups.source")}
</>
},
{
key: "capacity",
name: intl.get("groups.capacity"),
minWidth: 40,
maxWidth: 60,
data: "string",
onRender: (g: SourceGroup) => <>
{g.isMultiple ? g.sids.length : ""}
</>
},
{
key: "name",
name: intl.get("name"),
minWidth: 200,
data: "string",
isRowHeader: true,
onRender: (g: SourceGroup) => <>
{g.isMultiple ? g.name : this.props.sources[g.sids[0]].name}
</>
}
]
sourceColumns: IColumn[] = [
{
key: "favicon",
name: intl.get("icon"),
fieldName: "name",
isIconOnly: true,
iconName: "ImagePixel",
minWidth: 16,
maxWidth: 16,
onRender: (s: RSSSource) => s.iconurl && (
<img src={s.iconurl} className="favicon" />
)
},
{
key: "name",
name: intl.get("name"),
fieldName: "name",
minWidth: 200,
data: 'string',
isRowHeader: true
},
{
key: "url",
name: "URL",
fieldName: "url",
minWidth: 280,
data: 'string'
}
]
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.groupSelection.setAllSelected(false)
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({
selectedGroup: g,
editGroupName: g && g.isMultiple ? g.name : "",
manageGroup: true
})
}
}
dropdownOptions = () => this.props.groups.filter(g => g.isMultiple).map(g => ({
key: g.index,
text: g.name
}))
handleInputChange = (event) => {
const name: string = event.target.name
this.setState({[name]: event.target.value})
}
validateNewGroupName = (v: string) => {
const name = v.trim()
if (name.length == 0) {
return intl.get("emptyName")
}
for (let group of this.props.groups) {
if (group.isMultiple && group.name === name) {
return intl.get("groups.exist")
}
}
return ""
}
createGroup = (event: React.FormEvent) => {
event.preventDefault()
let trimmed = this.state.newGroupName.trim()
if (this.validateNewGroupName(trimmed) === "") this.props.createGroup(trimmed)
}
addToGroup = () => {
this.props.addToGroup(this.state.dropdownIndex, this.state.selectedGroup.sids[0])
}
removeFromGroup = () => {
this.props.removeFromGroup(this.state.selectedGroup.index, this.state.selectedSources.map(s => s.sid))
this.setState({ selectedSources: null })
}
deleteGroup = () => {
this.props.deleteGroup(this.state.selectedGroup.index)
this.groupSelection.setIndexSelected(this.state.selectedGroup.index, false, false)
this.setState({ selectedGroup: null })
}
updateGroupName = () => {
let group = this.state.selectedGroup
group = { ...group, name: this.state.editGroupName.trim() }
this.props.updateGroup(group)
}
dropdownChange = (_, item: IDropdownOption) => {
this.setState({ dropdownIndex: item ? Number(item.key) : null })
}
render = () => (
<div className="tab-body">
{this.props.serviceOn && (
<MessageBar messageBarType={MessageBarType.info}>{intl.get("service.groupsWarning")}</MessageBar>
)}
{this.state.manageGroup && this.state.selectedGroup &&
<>
<Stack horizontal horizontalAlign="space-between" style={{height: 40}}>
<CommandBarButton
text={intl.get("groups.exitGroup")}
iconProps={{iconName: "BackToWindow"}}
onClick={() => this.setState({manageGroup: false})} />
{this.state.selectedSources != null && <CommandBarButton
text={intl.get("groups.deleteSource")}
onClick={this.removeFromGroup}
iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />}
</Stack>
<MarqueeSelection selection={this.sourcesSelection} isDraggingConstrainedToRoot={true}>
<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>
<span className="settings-hint">{intl.get("groups.sourceHint")}</span>
</>}
{(!this.state.manageGroup || !this.state.selectedGroup)
?<>
<form onSubmit={this.createGroup}>
<Label htmlFor="newGroupName">{intl.get("groups.create")}</Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={this.validateNewGroupName}
validateOnLoad={false}
placeholder={intl.get("groups.enterName")}
value={this.state.newGroupName}
id="newGroupName"
name="newGroupName"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<PrimaryButton
disabled={this.validateNewGroupName(this.state.newGroupName) !== ""}
type="sumbit"
text={intl.get("create")} />
</Stack.Item>
</Stack>
</form>
<DetailsList
compact={true}
items={this.props.groups}
columns={this.groupColumns()}
setKey="selected"
onItemInvoked={this.manageGroup}
dragDropEvents={this.groupDragDropEvents}
selection={this.groupSelection}
selectionMode={SelectionMode.single} />
{this.state.selectedGroup
? ( this.state.selectedGroup.isMultiple
?<>
<Label>{intl.get("groups.selectedGroup")}</Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={v => v.trim().length == 0 ? intl.get("emptyName") : ""}
validateOnLoad={false}
placeholder={intl.get("groups.enterName")}
value={this.state.editGroupName}
name="editGroupName"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<DefaultButton
disabled={this.state.editGroupName.trim().length == 0}
onClick={this.updateGroupName}
text={intl.get("groups.editName")} />
</Stack.Item>
<Stack.Item>
<DangerButton
key={this.state.selectedGroup.index}
onClick={this.deleteGroup}
text={intl.get("groups.deleteGroup")} />
</Stack.Item>
</Stack>
</>
:<>
<Label>{intl.get("groups.selectedSource")}</Label>
<Stack horizontal>
<Stack.Item grow>
<Dropdown
placeholder={intl.get("groups.chooseGroup")}
selectedKey={this.state.dropdownIndex}
options={this.dropdownOptions()}
onChange={this.dropdownChange} />
</Stack.Item>
<Stack.Item>
<DefaultButton
disabled={this.state.dropdownIndex === null}
onClick={this.addToGroup}
text={intl.get("groups.addToGroup")} />
</Stack.Item>
</Stack>
</>
)
: <span className="settings-hint">{intl.get("groups.groupHint")}</span>
}
</> : null}
</div>
)
}
export default GroupsTab