add service warnings

This commit is contained in:
刘浩远 2020-08-02 14:03:40 +08:00
parent fc0183a80d
commit c35efa04ba
9 changed files with 89 additions and 42 deletions

View File

@ -109,6 +109,10 @@ i.ms-Nav-chevron {
.ms-ActivityItem-timeStamp { .ms-ActivityItem-timeStamp {
color: var(--neutralSecondaryAlt); color: var(--neutralSecondaryAlt);
} }
.ms-MessageBar {
user-select: none;
margin-bottom: 8px;
}
#root > nav { #root > nav {
height: var(--navHeight); height: var(--navHeight);

View File

@ -3,12 +3,13 @@ import intl from "react-intl-universal"
import { SourceGroup } from "../../schema-types" import { SourceGroup } from "../../schema-types"
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, MarqueeSelection, IDragDropEvents } from "@fluentui/react" TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton, MarqueeSelection, IDragDropEvents, MessageBar, MessageBarType } from "@fluentui/react"
import DangerButton from "../utils/danger-button" import DangerButton from "../utils/danger-button"
type GroupsTabProps = { type GroupsTabProps = {
sources: SourceState, sources: SourceState,
groups: SourceGroup[], groups: SourceGroup[],
serviceOn: boolean,
createGroup: (name: string) => void, createGroup: (name: string) => void,
updateGroup: (group: SourceGroup) => void, updateGroup: (group: SourceGroup) => void,
addToGroup: (groupIndex: number, sid: number) => void, addToGroup: (groupIndex: number, sid: number) => void,
@ -263,6 +264,9 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
render = () => ( render = () => (
<div className="tab-body"> <div className="tab-body">
{this.props.serviceOn && (
<MessageBar messageBarType={MessageBarType.info}>{intl.get("service.groupsWarning")}</MessageBar>
)}
{this.state.manageGroup && this.state.selectedGroup && {this.state.manageGroup && this.state.selectedGroup &&
<> <>
<Stack horizontal horizontalAlign="space-between" style={{height: 40}}> <Stack horizontal horizontalAlign="space-between" style={{height: 40}}>

View File

@ -95,14 +95,12 @@ class FeverConfigsTab extends React.Component<ServiceConfigsTabProps, FeverConfi
render() { render() {
return <> return <>
<Stack tokens={{childrenGap: 8}}> {!this.state.existing && (
{!this.state.existing && ( <MessageBar messageBarType={MessageBarType.warning}>{intl.get("service.overwriteWarning")}</MessageBar>
<MessageBar messageBarType={MessageBarType.warning}>{intl.get("service.overwriteWarning")}</MessageBar> )}
)} {!this.state.existing && this.state.importGroups && (
{!this.state.existing && this.state.importGroups && ( <MessageBar messageBarType={MessageBarType.info}>{intl.get("service.groupsWarning")}</MessageBar>
<MessageBar messageBarType={MessageBarType.info}>{intl.get("service.groupsWarning")}</MessageBar> )}
)}
</Stack>
<Stack horizontalAlign="center" style={{marginTop: 48}}> <Stack horizontalAlign="center" style={{marginTop: 48}}>
<Icon iconName="Calories" style={{fontSize: 32, userSelect: "none"}} /> <Icon iconName="Calories" style={{fontSize: 32, userSelect: "none"}} />
<Label style={{margin: "8px 0 36px"}}>Fever API</Label> <Label style={{margin: "8px 0 36px"}}>Fever API</Label>

View File

@ -1,13 +1,15 @@
import * as React from "react" import * as React from "react"
import intl from "react-intl-universal" import intl from "react-intl-universal"
import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList, import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList,
IColumn, SelectionMode, Selection, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown } from "@fluentui/react" IColumn, SelectionMode, Selection, IChoiceGroupOption, ChoiceGroup, IDropdownOption,
Dropdown, MessageBar, MessageBarType } from "@fluentui/react"
import { SourceState, RSSSource, SourceOpenTarget } from "../../scripts/models/source" import { SourceState, RSSSource, SourceOpenTarget } from "../../scripts/models/source"
import { urlTest } from "../../scripts/utils" import { urlTest } from "../../scripts/utils"
import DangerButton from "../utils/danger-button" import DangerButton from "../utils/danger-button"
type SourcesTabProps = { type SourcesTabProps = {
sources: SourceState sources: SourceState
serviceOn: boolean
addSource: (url: string) => void addSource: (url: string) => void
updateSourceName: (source: RSSSource, name: string) => void updateSourceName: (source: RSSSource, name: string) => void
updateSourceIcon: (source: RSSSource, iconUrl: string) => Promise<void> updateSourceIcon: (source: RSSSource, iconUrl: string) => Promise<void>
@ -154,6 +156,9 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
render = () => ( render = () => (
<div className="tab-body"> <div className="tab-body">
{this.props.serviceOn && (
<MessageBar messageBarType={MessageBarType.info}>{intl.get("sources.serviceWarning")}</MessageBar>
)}
<Label>{intl.get("sources.opmlFile")}</Label> <Label>{intl.get("sources.opmlFile")}</Label>
<Stack horizontal> <Stack horizontal>
<Stack.Item> <Stack.Item>
@ -196,6 +201,9 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
selectionMode={SelectionMode.multiple} /> selectionMode={SelectionMode.multiple} />
{this.state.selectedSource && <> {this.state.selectedSource && <>
{this.state.selectedSource.serviceRef && (
<MessageBar messageBarType={MessageBarType.info}>{intl.get("sources.serviceManaged")}</MessageBar>
)}
<Label>{intl.get("sources.selected")}</Label> <Label>{intl.get("sources.selected")}</Label>
<Stack horizontal> <Stack horizontal>
<Stack.Item> <Stack.Item>
@ -251,34 +259,39 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
</>} </>}
</Stack> </Stack>
<Label>{intl.get("sources.fetchFrequency")}</Label> {!this.state.selectedSource.serviceRef && <>
<Stack> <Label>{intl.get("sources.fetchFrequency")}</Label>
<Stack.Item> <Stack>
<Dropdown <Stack.Item>
options={this.fetchFrequencyOptions()} <Dropdown
selectedKey={this.state.selectedSource.fetchFrequency ? String(this.state.selectedSource.fetchFrequency) : "0"} options={this.fetchFrequencyOptions()}
onChange={this.onFetchFrequencyChange} selectedKey={this.state.selectedSource.fetchFrequency ? String(this.state.selectedSource.fetchFrequency) : "0"}
style={{width: 200}} /> onChange={this.onFetchFrequencyChange}
</Stack.Item> style={{width: 200}} />
</Stack> </Stack.Item>
</Stack>
</>}
<ChoiceGroup <ChoiceGroup
label={intl.get("sources.openTarget")} label={intl.get("sources.openTarget")}
options={this.sourceOpenTargetChoices()} options={this.sourceOpenTargetChoices()}
selectedKey={String(this.state.selectedSource.openTarget)} selectedKey={String(this.state.selectedSource.openTarget)}
onChange={this.onOpenTargetChange} /> onChange={this.onOpenTargetChange} />
<Stack horizontal> {!this.state.selectedSource.serviceRef && (
<Stack.Item> <Stack horizontal>
<DangerButton <Stack.Item>
onClick={() => this.props.deleteSource(this.state.selectedSource)} <DangerButton
key={this.state.selectedSource.sid} onClick={() => this.props.deleteSource(this.state.selectedSource)}
text={intl.get("sources.delete")} /> key={this.state.selectedSource.sid}
</Stack.Item> text={intl.get("sources.delete")} />
<Stack.Item> </Stack.Item>
<span className="settings-hint">{intl.get("sources.deleteWarning")}</span> <Stack.Item>
</Stack.Item> <span className="settings-hint">{intl.get("sources.deleteWarning")}</span>
</Stack> </Stack.Item>
</Stack>
)}
</>} </>}
{this.state.selectedSources && <> {this.state.selectedSources && (this.state.selectedSources.filter(s => s.serviceRef).length === 0
? <>
<Label>{intl.get("sources.selectedMulti")}</Label> <Label>{intl.get("sources.selectedMulti")}</Label>
<Stack horizontal> <Stack horizontal>
<Stack.Item> <Stack.Item>
@ -290,7 +303,10 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
<span className="settings-hint">{intl.get("sources.deleteWarning")}</span> <span className="settings-hint">{intl.get("sources.deleteWarning")}</span>
</Stack.Item> </Stack.Item>
</Stack> </Stack>
</>} </>
: (
<MessageBar messageBarType={MessageBarType.info}>{intl.get("sources.serviceManaged")}</MessageBar>
))}
</div> </div>
) )
} }

View File

@ -4,16 +4,18 @@ import { RootState } from "../../scripts/reducer"
import GroupsTab from "../../components/settings/groups" import GroupsTab from "../../components/settings/groups"
import { createSourceGroup, updateSourceGroup, addSourceToGroup, import { createSourceGroup, updateSourceGroup, addSourceToGroup,
deleteSourceGroup, removeSourceFromGroup, reorderSourceGroups } from "../../scripts/models/group" deleteSourceGroup, removeSourceFromGroup, reorderSourceGroups } from "../../scripts/models/group"
import { SourceGroup } from "../../schema-types" import { SourceGroup, SyncService } from "../../schema-types"
const getSources = (state: RootState) => state.sources const getSources = (state: RootState) => state.sources
const getGroups = (state: RootState) => state.groups const getGroups = (state: RootState) => state.groups
const getServiceOn = (state: RootState) => state.service.type !== SyncService.None
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
[getSources, getGroups], [getSources, getGroups, getServiceOn],
(sources, groups) => ({ (sources, groups, serviceOn) => ({
sources: sources, sources: sources,
groups: groups.map((g, i) => ({ ...g, index: i })), groups: groups.map((g, i) => ({ ...g, index: i })),
serviceOn: serviceOn,
key: groups.length key: groups.length
}) })
) )

View File

@ -7,13 +7,16 @@ import { addSource, RSSSource, updateSource, deleteSource, SourceOpenTarget, del
import { importOPML, exportOPML } from "../../scripts/models/group" import { importOPML, exportOPML } from "../../scripts/models/group"
import { AppDispatch, validateFavicon } from "../../scripts/utils" import { AppDispatch, validateFavicon } from "../../scripts/utils"
import { saveSettings } from "../../scripts/models/app" import { saveSettings } from "../../scripts/models/app"
import { SyncService } from "../../schema-types"
const getSources = (state: RootState) => state.sources const getSources = (state: RootState) => state.sources
const getServiceOn = (state: RootState) => state.service.type !== SyncService.None
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
[getSources], [getSources, getServiceOn],
(sources) => ({ (sources, serviceOn) => ({
sources: sources sources: sources,
serviceOn: serviceOn
}) })
) )

View File

@ -32,7 +32,8 @@
"fetchFailure": "Failed to load source \"{name}\".", "fetchFailure": "Failed to load source \"{name}\".",
"fetchSuccess": "Successfully fetched {count, plural, =1 {# article} other {# articles}}.", "fetchSuccess": "Successfully fetched {count, plural, =1 {# article} other {# articles}}.",
"networkError": "A network error has occurred.", "networkError": "A network error has occurred.",
"parseError": "An error has occurred when parsing the XML feed." "parseError": "An error has occurred when parsing the XML feed.",
"syncFailure": "Failed to sync with service"
}, },
"nav": { "nav": {
"menu": "Menu", "menu": "Menu",
@ -112,6 +113,8 @@
"feedback": "Feedback" "feedback": "Feedback"
}, },
"sources": { "sources": {
"serviceWarning": "Sources imported or added here will not be synced with your service.",
"serviceManaged": "This source is managed by your service.",
"untitled": "Source", "untitled": "Source",
"errorAdd": "An error has occured when adding the source.", "errorAdd": "An error has occured when adding the source.",
"errorParse": "An error has occurred when parsing the OPML file.", "errorParse": "An error has occurred when parsing the OPML file.",

View File

@ -32,7 +32,8 @@
"fetchFailure": "无法加载订阅源“{name}”", "fetchFailure": "无法加载订阅源“{name}”",
"fetchSuccess": "成功加载 {count} 篇文章", "fetchSuccess": "成功加载 {count} 篇文章",
"networkError": "连接订阅源时出错", "networkError": "连接订阅源时出错",
"parseError": "解析XML信息流时出错" "parseError": "解析XML信息流时出错",
"syncFailure": "无法与服务同步"
}, },
"nav": { "nav": {
"menu": "菜单", "menu": "菜单",
@ -110,6 +111,8 @@
"feedback": "反馈" "feedback": "反馈"
}, },
"sources": { "sources": {
"serviceWarning": "此处导入或添加的订阅源将不会与服务端同步",
"serviceManaged": "该订阅源由服务端管理",
"untitled": "订阅源", "untitled": "订阅源",
"errorAdd": "添加订阅源时出错", "errorAdd": "添加订阅源时出错",
"errorParse": "解析OPML文件时出错", "errorParse": "解析OPML文件时出错",
@ -187,7 +190,7 @@
"fetchLimit": "同步数量", "fetchLimit": "同步数量",
"fetchLimitNum": "最近 {count} 篇文章", "fetchLimitNum": "最近 {count} 篇文章",
"importGroups": "导入分组", "importGroups": "导入分组",
"failure": "无法连接到服务", "failure": "连接到服务时出错",
"failureHint": "请检查服务配置或网络连接" "failureHint": "请检查服务配置或网络连接"
}, },
"app": { "app": {

View File

@ -356,6 +356,19 @@ export function appReducer(
...state, ...state,
syncing: true syncing: true
} }
case ActionStatus.Failure: return {
...state,
syncing: false,
logMenu: {
...state.logMenu,
notify: true,
logs: [...state.logMenu.logs, new AppLog(
AppLogType.Failure,
intl.get("log.syncFailure"),
String(action.err)
)]
}
}
default: return { default: return {
...state, ...state,
syncing: false syncing: false
@ -467,6 +480,7 @@ export function appReducer(
settings: { settings: {
...state.settings, ...state.settings,
display: true, display: true,
changed: true,
saving: !state.settings.saving saving: !state.settings.saving
} }
} }