refactor context menu as function component

This commit is contained in:
Bruce Liu 2025-03-02 21:41:11 -08:00
parent ba9edbd65b
commit 5ecf189ed6
5 changed files with 576 additions and 694 deletions

View File

@ -13,38 +13,25 @@ import {
ContextualMenuItemType,
DirectionalHint,
} from "office-ui-fabric-react/lib/ContextualMenu"
import { ContextMenuType } from "../scripts/models/app"
import { RSSItem } from "../scripts/models/item"
import { ContextReduxProps } from "../containers/context-menu-container"
import { closeContextMenu, ContextMenuType } from "../scripts/models/app"
import {
markAllRead,
markRead,
markUnread,
RSSItem,
toggleHidden,
toggleStarred,
} from "../scripts/models/item"
import { ViewType, ImageCallbackTypes, ViewConfigs } from "../schema-types"
import { FilterType } from "../scripts/models/feed"
export type ContextMenuProps = ContextReduxProps & {
type: ContextMenuType
event?: MouseEvent | string
position?: [number, number]
item?: RSSItem
feedId?: string
text?: string
url?: string
viewType?: ViewType
viewConfigs?: ViewConfigs
filter?: FilterType
sids?: number[]
showItem: (feedId: string, item: RSSItem) => void
markRead: (item: RSSItem) => void
markUnread: (item: RSSItem) => void
toggleStarred: (item: RSSItem) => void
toggleHidden: (item: RSSItem) => void
switchView: (viewType: ViewType) => void
setViewConfigs: (configs: ViewConfigs) => void
switchFilter: (filter: FilterType) => void
toggleFilter: (filter: FilterType) => void
markAllRead: (sids?: number[], date?: Date, before?: boolean) => void
fetchItems: (sids: number[]) => void
settings: (sids: number[]) => void
close: () => void
}
import { useAppDispatch, useAppSelector } from "../scripts/reducer"
import {
setViewConfigs,
showItem,
switchFilter,
switchView,
toggleFilter,
} from "../scripts/models/page"
export const shareSubmenu = (item: RSSItem): IContextualMenuItem[] => [
{ key: "qr", url: item.link, onRender: renderShareQR },
@ -69,21 +56,42 @@ function getSearchItem(text: string): IContextualMenuItem {
}
}
export class ContextMenu extends React.Component<ContextMenuProps> {
getItems = (): IContextualMenuItem[] => {
switch (this.props.type) {
export function ContextMenu() {
const { type } = useAppSelector(state => state.app.contextMenu)
switch (type) {
case ContextMenuType.Hidden:
return null
case ContextMenuType.Item:
return [
return <ItemContextMenu />
case ContextMenuType.Text:
return <TextContextMenu />
case ContextMenuType.Image:
return <ImageContextMenu />
case ContextMenuType.View:
return <ViewContextMenu />
case ContextMenuType.Group:
return <GroupContextMenu />
case ContextMenuType.MarkRead:
return <MarkReadContextMenu />
}
}
function ItemContextMenu() {
const dispatch = useAppDispatch()
const viewConfigs = useAppSelector(state => state.page.viewConfigs)
const target = useAppSelector(state => state.app.contextMenu.target)
const item = target[0] as RSSItem
const feedId = target[1] as string
const menuItems: IContextualMenuItem[] = [
{
key: "showItem",
text: intl.get("context.read"),
iconProps: { iconName: "TextDocument" },
onClick: () => {
this.props.markRead(this.props.item)
this.props.showItem(
this.props.feedId,
this.props.item
)
dispatch(markRead(item))
dispatch(showItem(feedId, item))
},
},
{
@ -91,28 +99,27 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("openExternal"),
iconProps: { iconName: "NavigateExternalInline" },
onClick: e => {
this.props.markRead(this.props.item)
window.utils.openExternal(
this.props.item.link,
platformCtrl(e)
)
dispatch(markRead(item))
window.utils.openExternal(item.link, platformCtrl(e))
},
},
{
key: "markAsRead",
text: this.props.item.hasRead
text: item.hasRead
? intl.get("article.markUnread")
: intl.get("article.markRead"),
iconProps: this.props.item.hasRead
iconProps: item.hasRead
? {
iconName: "RadioBtnOn",
style: { fontSize: 14, textAlign: "center" },
}
: { iconName: "StatusCircleRing" },
onClick: () => {
if (this.props.item.hasRead)
this.props.markUnread(this.props.item)
else this.props.markRead(this.props.item)
if (item.hasRead) {
dispatch(markUnread(item))
} else {
dispatch(markRead(item))
}
},
split: true,
subMenuProps: {
@ -124,11 +131,9 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
iconName: "Down",
style: { fontSize: 14 },
},
onClick: () =>
this.props.markAllRead(
null,
this.props.item.date
),
onClick: () => {
dispatch(markAllRead(null, item.date))
},
},
{
key: "markAbove",
@ -137,40 +142,35 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
iconName: "Up",
style: { fontSize: 14 },
},
onClick: () =>
this.props.markAllRead(
null,
this.props.item.date,
false
),
onClick: () => {
dispatch(markAllRead(null, item.date, false))
},
},
],
},
},
{
key: "toggleStarred",
text: this.props.item.starred
text: item.starred
? intl.get("article.unstar")
: intl.get("article.star"),
iconProps: {
iconName: this.props.item.starred
? "FavoriteStar"
: "FavoriteStarFill",
iconName: item.starred ? "FavoriteStar" : "FavoriteStarFill",
},
onClick: () => {
this.props.toggleStarred(this.props.item)
dispatch(toggleStarred(item))
},
},
{
key: "toggleHidden",
text: this.props.item.hidden
text: item.hidden
? intl.get("article.unhide")
: intl.get("article.hide"),
iconProps: {
iconName: this.props.item.hidden ? "View" : "Hide3",
iconName: item.hidden ? "View" : "Hide3",
},
onClick: () => {
this.props.toggleHidden(this.props.item)
dispatch(toggleHidden(item))
},
},
{
@ -182,24 +182,24 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("context.share"),
iconProps: { iconName: "Share" },
subMenuProps: {
items: shareSubmenu(this.props.item),
items: shareSubmenu(item),
},
},
{
key: "copyTitle",
text: intl.get("context.copyTitle"),
onClick: () => {
window.utils.writeClipboard(this.props.item.title)
window.utils.writeClipboard(item.title)
},
},
{
key: "copyURL",
text: intl.get("context.copyURL"),
onClick: () => {
window.utils.writeClipboard(this.props.item.link)
window.utils.writeClipboard(item.link)
},
},
...(this.props.viewConfigs !== undefined
...(viewConfigs !== undefined
? [
{
key: "divider_2",
@ -212,50 +212,46 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
items: [
{
key: "showCover",
text: intl.get(
"context.showCover"
),
text: intl.get("context.showCover"),
canCheck: true,
checked: Boolean(
this.props.viewConfigs &
ViewConfigs.ShowCover
viewConfigs & ViewConfigs.ShowCover
),
onClick: () =>
this.props.setViewConfigs(
this.props.viewConfigs ^
dispatch(
setViewConfigs(
viewConfigs ^
ViewConfigs.ShowCover
)
),
},
{
key: "showSnippet",
text: intl.get(
"context.showSnippet"
),
text: intl.get("context.showSnippet"),
canCheck: true,
checked: Boolean(
this.props.viewConfigs &
ViewConfigs.ShowSnippet
viewConfigs & ViewConfigs.ShowSnippet
),
onClick: () =>
this.props.setViewConfigs(
this.props.viewConfigs ^
dispatch(
setViewConfigs(
viewConfigs ^
ViewConfigs.ShowSnippet
)
),
},
{
key: "fadeRead",
text: intl.get(
"context.fadeRead"
),
text: intl.get("context.fadeRead"),
canCheck: true,
checked: Boolean(
this.props.viewConfigs &
ViewConfigs.FadeRead
viewConfigs & ViewConfigs.FadeRead
),
onClick: () =>
this.props.setViewConfigs(
this.props.viewConfigs ^
ViewConfigs.FadeRead
dispatch(
setViewConfigs(
viewConfigs ^ ViewConfigs.FadeRead
)
),
},
],
@ -264,26 +260,35 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
]
: []),
]
case ContextMenuType.Text: {
const items: IContextualMenuItem[] = this.props.text
return <ContextMenuBase menuItems={menuItems} />
}
function TextContextMenu() {
const target = useAppSelector(state => state.app.contextMenu.target) as [
string,
string
]
const text = target[0]
const url = target[1]
const menuItems: IContextualMenuItem[] = text
? [
{
key: "copyText",
text: intl.get("context.copy"),
iconProps: { iconName: "Copy" },
onClick: () => {
window.utils.writeClipboard(this.props.text)
window.utils.writeClipboard(text)
},
},
getSearchItem(this.props.text),
getSearchItem(text),
]
: []
if (this.props.url) {
items.push({
if (url) {
menuItems.push({
key: "urlSection",
itemType: ContextualMenuItemType.Section,
sectionProps: {
topDivider: items.length > 0,
topDivider: menuItems.length > 0,
items: [
{
key: "openInBrowser",
@ -292,10 +297,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
iconName: "NavigateExternalInline",
},
onClick: e => {
window.utils.openExternal(
this.props.url,
platformCtrl(e)
)
window.utils.openExternal(url, platformCtrl(e))
},
},
{
@ -303,19 +305,18 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("context.copyURL"),
iconProps: { iconName: "Link" },
onClick: () => {
window.utils.writeClipboard(
this.props.url
)
window.utils.writeClipboard(url)
},
},
],
},
})
}
return items
}
case ContextMenuType.Image:
return [
return <ContextMenuBase menuItems={menuItems} />
}
function ImageContextMenu() {
const menuItems: IContextualMenuItem[] = [
{
key: "openInBrowser",
text: intl.get("openExternal"),
@ -326,9 +327,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
ImageCallbackTypes.OpenExternalBg
)
} else {
window.utils.imageCallback(
ImageCallbackTypes.OpenExternal
)
window.utils.imageCallback(ImageCallbackTypes.OpenExternal)
}
},
},
@ -337,9 +336,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("context.saveImageAs"),
iconProps: { iconName: "SaveTemplate" },
onClick: () => {
window.utils.imageCallback(
ImageCallbackTypes.SaveAs
)
window.utils.imageCallback(ImageCallbackTypes.SaveAs)
},
},
{
@ -355,14 +352,19 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("context.copyImageURL"),
iconProps: { iconName: "Link" },
onClick: () => {
window.utils.imageCallback(
ImageCallbackTypes.CopyLink
)
window.utils.imageCallback(ImageCallbackTypes.CopyLink)
},
},
]
case ContextMenuType.View:
return [
return <ContextMenuBase menuItems={menuItems} />
}
function ViewContextMenu() {
const dispatch = useAppDispatch()
const viewType = useAppSelector(state => state.page.viewType)
const filter = useAppSelector(state => state.page.filter.type)
const menuItems: IContextualMenuItem[] = [
{
key: "section_1",
itemType: ContextualMenuItemType.Section,
@ -375,44 +377,32 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("context.cardView"),
iconProps: { iconName: "GridViewMedium" },
canCheck: true,
checked:
this.props.viewType === ViewType.Cards,
onClick: () =>
this.props.switchView(ViewType.Cards),
checked: viewType === ViewType.Cards,
onClick: () => dispatch(switchView(ViewType.Cards)),
},
{
key: "listView",
text: intl.get("context.listView"),
iconProps: { iconName: "BacklogList" },
canCheck: true,
checked:
this.props.viewType === ViewType.List,
onClick: () =>
this.props.switchView(ViewType.List),
checked: viewType === ViewType.List,
onClick: () => dispatch(switchView(ViewType.List)),
},
{
key: "magazineView",
text: intl.get("context.magazineView"),
iconProps: { iconName: "Articles" },
canCheck: true,
checked:
this.props.viewType ===
ViewType.Magazine,
onClick: () =>
this.props.switchView(
ViewType.Magazine
),
checked: viewType === ViewType.Magazine,
onClick: () => dispatch(switchView(ViewType.Magazine)),
},
{
key: "compactView",
text: intl.get("context.compactView"),
iconProps: { iconName: "BulletedList" },
canCheck: true,
checked:
this.props.viewType ===
ViewType.Compact,
onClick: () =>
this.props.switchView(ViewType.Compact),
checked: viewType === ViewType.Compact,
onClick: () => dispatch(switchView(ViewType.Compact)),
},
],
},
@ -430,13 +420,10 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
iconProps: { iconName: "ClearFilter" },
canCheck: true,
checked:
(this.props.filter &
~FilterType.Toggles) ==
(filter & ~FilterType.Toggles) ==
FilterType.Default,
onClick: () =>
this.props.switchFilter(
FilterType.Default
),
dispatch(switchFilter(FilterType.Default)),
},
{
key: "unreadOnly",
@ -450,13 +437,10 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
},
canCheck: true,
checked:
(this.props.filter &
~FilterType.Toggles) ==
(filter & ~FilterType.Toggles) ==
FilterType.UnreadOnly,
onClick: () =>
this.props.switchFilter(
FilterType.UnreadOnly
),
dispatch(switchFilter(FilterType.UnreadOnly)),
},
{
key: "starredOnly",
@ -464,13 +448,10 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
iconProps: { iconName: "FavoriteStarFill" },
canCheck: true,
checked:
(this.props.filter &
~FilterType.Toggles) ==
(filter & ~FilterType.Toggles) ==
FilterType.StarredOnly,
onClick: () =>
this.props.switchFilter(
FilterType.StarredOnly
),
dispatch(switchFilter(FilterType.StarredOnly)),
},
],
},
@ -493,28 +474,18 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
children: "Aa",
},
canCheck: true,
checked: !(
this.props.filter &
FilterType.CaseInsensitive
),
checked: !(filter & FilterType.CaseInsensitive),
onClick: () =>
this.props.toggleFilter(
FilterType.CaseInsensitive
),
dispatch(toggleFilter(FilterType.CaseInsensitive)),
},
{
key: "fullSearch",
text: intl.get("context.fullSearch"),
iconProps: { iconName: "Breadcrumb" },
canCheck: true,
checked: Boolean(
this.props.filter &
FilterType.FullSearch
),
checked: Boolean(filter & FilterType.FullSearch),
onClick: () =>
this.props.toggleFilter(
FilterType.FullSearch
),
dispatch(toggleFilter(FilterType.FullSearch)),
},
],
},
@ -523,36 +494,52 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
key: "showHidden",
text: intl.get("context.showHidden"),
canCheck: true,
checked: Boolean(
this.props.filter & FilterType.ShowHidden
),
onClick: () =>
this.props.toggleFilter(FilterType.ShowHidden),
checked: Boolean(filter & FilterType.ShowHidden),
onClick: () => dispatch(toggleFilter(FilterType.ShowHidden)),
},
]
case ContextMenuType.Group:
return [
return <ContextMenuBase menuItems={menuItems} />
}
function GroupContextMenu() {
const dispatch = useAppDispatch()
const sids = useAppSelector(
state => state.app.contextMenu.target
) as number[]
const menuItems: IContextualMenuItem[] = [
{
key: "markAllRead",
text: intl.get("nav.markAllRead"),
iconProps: { iconName: "CheckMark" },
onClick: () => this.props.markAllRead(this.props.sids),
onClick: () => {
dispatch(markAllRead(sids))
},
},
{
key: "refresh",
text: intl.get("nav.refresh"),
iconProps: { iconName: "Sync" },
onClick: () => this.props.fetchItems(this.props.sids),
onClick: () => {
dispatch(markAllRead(sids))
},
},
{
key: "manage",
text: intl.get("context.manageSources"),
iconProps: { iconName: "Settings" },
onClick: () => this.props.settings(this.props.sids),
onClick: () => {
dispatch(markAllRead(sids))
},
},
]
case ContextMenuType.MarkRead:
return [
return <ContextMenuBase menuItems={menuItems} />
}
function MarkReadContextMenu() {
const dispatch = useAppDispatch()
const menuItems: IContextualMenuItem[] = [
{
key: "section_1",
itemType: ContextualMenuItemType.Section,
@ -563,7 +550,9 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
key: "all",
text: intl.get("allArticles"),
iconProps: { iconName: "ReceiptCheck" },
onClick: () => this.props.markAllRead(),
onClick: () => {
dispatch(markAllRead())
},
},
{
key: "1d",
@ -571,7 +560,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
onClick: () => {
let date = new Date()
date.setTime(date.getTime() - 86400000)
this.props.markAllRead(null, date)
dispatch(markAllRead(null, date))
},
},
{
@ -579,10 +568,8 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("app.daysAgo", { days: 3 }),
onClick: () => {
let date = new Date()
date.setTime(
date.getTime() - 3 * 86400000
)
this.props.markAllRead(null, date)
date.setTime(date.getTime() - 3 * 86400000)
dispatch(markAllRead(null, date))
},
},
{
@ -590,35 +577,35 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("app.daysAgo", { days: 7 }),
onClick: () => {
let date = new Date()
date.setTime(
date.getTime() - 7 * 86400000
)
this.props.markAllRead(null, date)
date.setTime(date.getTime() - 7 * 86400000)
dispatch(markAllRead(null, date))
},
},
],
},
},
]
default:
return []
}
}
return <ContextMenuBase menuItems={menuItems} />
}
render() {
return this.props.type == ContextMenuType.Hidden ? null : (
function ContextMenuBase({
menuItems,
}: Readonly<{ menuItems: IContextualMenuItem[] }>) {
const { event, position } = useAppSelector(state => state.app.contextMenu)
const dispatch = useAppDispatch()
return (
<ContextualMenu
directionalHint={DirectionalHint.bottomLeftEdge}
items={this.getItems()}
items={menuItems}
target={
this.props.event ||
(this.props.position && {
left: this.props.position[0],
top: this.props.position[1],
event ||
(position && {
left: position[0],
top: position[1],
})
}
onDismiss={this.props.close}
onDismiss={() => dispatch(closeContextMenu())}
/>
)
}
}

View File

@ -1,6 +1,5 @@
import * as React from "react"
import { connect } from "react-redux"
import { ContextMenuContainer } from "../containers/context-menu-container"
import { closeContextMenu } from "../scripts/models/app"
import PageContainer from "../containers/page-container"
import MenuContainer from "../containers/menu-container"
@ -8,6 +7,7 @@ import NavContainer from "../containers/nav-container"
import LogMenuContainer from "../containers/log-menu-container"
import SettingsContainer from "../containers/settings-container"
import { RootState } from "../scripts/reducer"
import { ContextMenu } from "./context-menu"
const Root = ({ locale, dispatch }) =>
locale && (
@ -20,7 +20,7 @@ const Root = ({ locale, dispatch }) =>
<LogMenuContainer />
<MenuContainer />
<SettingsContainer />
<ContextMenuContainer />
<ContextMenu />
</div>
)

View File

@ -1,115 +0,0 @@
import { connect } from "react-redux"
import { createSelector } from "reselect"
import { RootState } from "../scripts/reducer"
import {
ContextMenuType,
closeContextMenu,
toggleSettings,
} from "../scripts/models/app"
import { ContextMenu } from "../components/context-menu"
import {
RSSItem,
markRead,
markUnread,
toggleStarred,
toggleHidden,
markAllRead,
fetchItems,
} from "../scripts/models/item"
import {
showItem,
switchView,
switchFilter,
toggleFilter,
setViewConfigs,
} from "../scripts/models/page"
import { ViewType, ViewConfigs } from "../schema-types"
import { FilterType } from "../scripts/models/feed"
const getContext = (state: RootState) => state.app.contextMenu
const getViewType = (state: RootState) => state.page.viewType
const getFilter = (state: RootState) => state.page.filter
const getViewConfigs = (state: RootState) => state.page.viewConfigs
const mapStateToProps = createSelector(
[getContext, getViewType, getFilter, getViewConfigs],
(context, viewType, filter, viewConfigs) => {
switch (context.type) {
case ContextMenuType.Item:
return {
type: context.type,
event: context.event,
viewConfigs: viewConfigs,
item: context.target[0],
feedId: context.target[1],
}
case ContextMenuType.Text:
return {
type: context.type,
position: context.position,
text: context.target[0],
url: context.target[1],
}
case ContextMenuType.View:
return {
type: context.type,
event: context.event,
viewType: viewType,
filter: filter.type,
}
case ContextMenuType.Group:
return {
type: context.type,
event: context.event,
sids: context.target,
}
case ContextMenuType.Image:
return {
type: context.type,
position: context.position,
}
case ContextMenuType.MarkRead:
return {
type: context.type,
event: context.event,
}
default:
return { type: ContextMenuType.Hidden }
}
}
)
const mapDispatchToProps = dispatch => {
return {
showItem: (feedId: string, item: RSSItem) =>
dispatch(showItem(feedId, item)),
markRead: (item: RSSItem) => dispatch(markRead(item)),
markUnread: (item: RSSItem) => dispatch(markUnread(item)),
toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)),
toggleHidden: (item: RSSItem) => {
if (!item.hasRead) {
dispatch(markRead(item))
item.hasRead = true // get around chaining error
}
dispatch(toggleHidden(item))
},
switchView: (viewType: ViewType) => {
window.settings.setDefaultView(viewType)
dispatch(switchView(viewType))
},
setViewConfigs: (configs: ViewConfigs) =>
dispatch(setViewConfigs(configs)),
switchFilter: (filter: FilterType) => dispatch(switchFilter(filter)),
toggleFilter: (filter: FilterType) => dispatch(toggleFilter(filter)),
markAllRead: (sids?: number[], date?: Date, before?: boolean) => {
dispatch(markAllRead(sids, date, before))
},
fetchItems: (sids: number[]) => dispatch(fetchItems(false, sids)),
settings: (sids: number[]) => dispatch(toggleSettings(true, sids)),
close: () => dispatch(closeContextMenu()),
}
}
const connector = connect(mapStateToProps, mapDispatchToProps)
export type ContextReduxProps = typeof connector
export const ContextMenuContainer = connector(ContextMenu)

View File

@ -1,29 +1,21 @@
import * as React from "react"
import * as ReactDOM from "react-dom"
import { Provider } from "react-redux"
import { createStore, applyMiddleware } from "redux"
import thunkMiddleware from "redux-thunk"
import { initializeIcons } from "@fluentui/react/lib/Icons"
import { rootReducer, RootState } from "./scripts/reducer"
import Root from "./components/root"
import { AppDispatch } from "./scripts/utils"
import { applyThemeSettings } from "./scripts/settings"
import { initApp, openTextMenu } from "./scripts/models/app"
import { rootStore } from "./scripts/reducer"
window.settings.setProxy()
applyThemeSettings()
initializeIcons("icons/")
const store = createStore(
rootReducer,
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
)
store.dispatch(initApp())
rootStore.dispatch(initApp())
window.utils.addMainContextListener((pos, text) => {
store.dispatch(openTextMenu(pos, text))
rootStore.dispatch(openTextMenu(pos, text))
})
window.fontList = [""]
@ -32,7 +24,7 @@ window.utils.initFontList().then(fonts => {
})
ReactDOM.render(
<Provider store={store}>
<Provider store={rootStore}>
<Root />
</Provider>,
document.getElementById("app")

View File

@ -1,4 +1,5 @@
import { combineReducers } from "redux"
import { applyMiddleware, combineReducers, createStore } from "redux"
import thunkMiddleware from "redux-thunk"
import { sourceReducer } from "./models/source"
import { itemReducer } from "./models/item"
@ -7,6 +8,13 @@ import { appReducer } from "./models/app"
import { groupReducer } from "./models/group"
import { pageReducer } from "./models/page"
import { serviceReducer } from "./models/service"
import { AppDispatch } from "./utils"
import {
TypedUseSelectorHook,
useDispatch,
useSelector,
useStore,
} from "react-redux"
export const rootReducer = combineReducers({
sources: sourceReducer,
@ -18,4 +26,14 @@ export const rootReducer = combineReducers({
app: appReducer,
})
export const rootStore = createStore(
rootReducer,
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
)
export type AppStore = typeof rootStore
export type RootState = ReturnType<typeof rootReducer>
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppStore: () => AppStore = useStore