diff --git a/src/bridges/utils.ts b/src/bridges/utils.ts index d042022..5f504d2 100644 --- a/src/bridges/utils.ts +++ b/src/bridges/utils.ts @@ -181,6 +181,8 @@ declare global { utils: typeof utilsBridge fontList: Array } + var utils: typeof utilsBridge + var fontList: Array } export default utilsBridge diff --git a/src/components/feeds/cards-feed.tsx b/src/components/feeds/cards-feed.tsx index 9e9241f..5efb2bf 100644 --- a/src/components/feeds/cards-feed.tsx +++ b/src/components/feeds/cards-feed.tsx @@ -1,4 +1,5 @@ import * as React from "react" +import { useState, useEffect, useCallback, useRef } from "react" import intl from "react-intl-universal" import { FeedProps } from "./feed" import DefaultCard from "../cards/default-card" @@ -6,64 +7,63 @@ import { PrimaryButton, FocusZone } from "office-ui-fabric-react" import { RSSItem } from "../../scripts/models/item" import { List, AnimationClassNames } from "@fluentui/react" -class CardsFeed extends React.Component { - observer: ResizeObserver - state = { width: window.innerWidth, height: window.innerHeight } +const CardsFeed: React.FC = props => { + const [width, setWidth] = useState(window.innerWidth) + const [height, setHeight] = useState(window.innerHeight) + const observerRef = useRef(null) - updateWindowSize = (entries: ResizeObserverEntry[]) => { - if (entries) { - this.setState({ - width: entries[0].contentRect.width - 40, - height: window.innerHeight, - }) + useEffect(() => { + setWidth(document.querySelector(".main").clientWidth - 40) + observerRef.current = new ResizeObserver( + (entries: ResizeObserverEntry[]) => { + if (entries) { + setWidth(entries[0].contentRect.width - 40) + setHeight(window.innerHeight) + } + } + ) + observerRef.current.observe(document.querySelector(".main")) + return () => { + observerRef.current.disconnect() } - } + }, []) - componentDidMount() { - this.setState({ - width: document.querySelector(".main").clientWidth - 40, - }) - this.observer = new ResizeObserver(this.updateWindowSize) - this.observer.observe(document.querySelector(".main")) - } - componentWillUnmount() { - this.observer.disconnect() - } - - getItemCountForPage = () => { - let elemPerRow = Math.floor(this.state.width / 280) - let rows = Math.ceil(this.state.height / 304) + const getItemCountForPage = useCallback(() => { + let elemPerRow = Math.floor(width / 280) + let rows = Math.ceil(height / 304) return elemPerRow * rows - } - getPageHeight = () => { - return this.state.height + (304 - (this.state.height % 304)) - } + }, [width, height]) - flexFixItems = () => { - let elemPerRow = Math.floor(this.state.width / 280) - let elemLastRow = this.props.items.length % elemPerRow - let items = [...this.props.items] + const getPageHeight = useCallback(() => { + return height + (304 - (height % 304)) + }, [height]) + + const flexFixItems = () => { + let elemPerRow = Math.floor(width / 280) + let elemLastRow = props.items.length % elemPerRow + let items = [...props.items] for (let i = 0; i < elemPerRow - elemLastRow; i += 1) items.push(null) return items } - onRenderItem = (item: RSSItem, index: number) => + + const onRenderItem = (item: RSSItem, index: number) => item ? ( ) : (
) - canFocusChild = (el: HTMLElement) => { + const canFocusChild = (el: HTMLElement) => { if (el.id === "load-more") { const container = document.getElementById("refocus") const result = @@ -76,43 +76,39 @@ class CardsFeed extends React.Component { } } - render() { - return ( - this.props.feed.loaded && ( - - - {this.props.feed.loaded && !this.props.feed.allLoaded ? ( -
- - this.props.loadMore(this.props.feed) - } - /> -
- ) : null} - {this.props.items.length === 0 && ( -
{intl.get("article.empty")}
- )} -
- ) + return ( + props.feed.loaded && ( + + + {props.feed.loaded && !props.feed.allLoaded ? ( +
+ props.loadMore(props.feed)} + /> +
+ ) : null} + {props.items.length === 0 && ( +
{intl.get("article.empty")}
+ )} +
) - } + ) } export default CardsFeed diff --git a/src/components/feeds/feed.tsx b/src/components/feeds/feed.tsx index 5fd8fa0..bc631c4 100644 --- a/src/components/feeds/feed.tsx +++ b/src/components/feeds/feed.tsx @@ -1,12 +1,15 @@ import * as React from "react" -import { RSSItem } from "../../scripts/models/item" -import { FeedReduxProps } from "../../containers/feed-container" -import { RSSFeed, FeedFilter } from "../../scripts/models/feed" +import { useCallback } from "react" +import { RSSItem, markRead, itemShortcuts } from "../../scripts/models/item" +import { openItemMenu } from "../../scripts/models/app" +import { RSSFeed, FeedFilter, loadMore } from "../../scripts/models/feed" +import { showItem } from "../../scripts/models/page" +import { useAppSelector, useAppDispatch } from "../../scripts/reducer" import { ViewType, ViewConfigs } from "../../schema-types" import CardsFeed from "./cards-feed" import ListFeed from "./list-feed" -export type FeedProps = FeedReduxProps & { +export type FeedProps = { feed: RSSFeed viewType: ViewType viewConfigs?: ViewConfigs @@ -21,15 +24,64 @@ export type FeedProps = FeedReduxProps & { showItem: (fid: string, item: RSSItem) => void } -export class Feed extends React.Component { - render() { - switch (this.props.viewType) { - case ViewType.Cards: - return - case ViewType.Magazine: - case ViewType.Compact: - case ViewType.List: - return - } +interface FeedOwnProps { + feedId: string + viewType: ViewType +} + +export const Feed: React.FC = ({ feedId, viewType }) => { + const dispatch = useAppDispatch() + + const feed = useAppSelector(s => s.feeds[feedId]) + const items = useAppSelector(s => + s.feeds[feedId] ? s.feeds[feedId].iids.map(iid => s.items[iid]) : [] + ) + const sourceMap = useAppSelector(s => s.sources) + const filter = useAppSelector(s => s.page.filter) + const viewConfigs = useAppSelector(s => s.page.viewConfigs) + const currentItem = useAppSelector(s => s.page.itemId) + + const handleShortcuts = useCallback( + (item: RSSItem, e: KeyboardEvent) => dispatch(itemShortcuts(item, e)), + [] + ) + const handleMarkRead = useCallback( + (item: RSSItem) => dispatch(markRead(item)), + [] + ) + const handleContextMenu = useCallback( + (fid: string, item: RSSItem, e) => dispatch(openItemMenu(item, fid, e)), + [] + ) + const handleLoadMore = useCallback((f: RSSFeed) => { + dispatch(loadMore(f)) + }, []) + const handleShowItem = useCallback( + (fid: string, item: RSSItem) => dispatch(showItem(fid, item)), + [] + ) + + const feedProps: FeedProps = { + feed, + viewType, + viewConfigs, + items, + currentItem, + sourceMap, + filter, + shortcuts: handleShortcuts, + markRead: handleMarkRead, + contextMenu: handleContextMenu, + loadMore: handleLoadMore, + showItem: handleShowItem, + } + + switch (viewType) { + case ViewType.Cards: + return + case ViewType.Magazine: + case ViewType.Compact: + case ViewType.List: + return } } diff --git a/src/components/feeds/list-feed.tsx b/src/components/feeds/list-feed.tsx index 45e89bc..b1f2083 100644 --- a/src/components/feeds/list-feed.tsx +++ b/src/components/feeds/list-feed.tsx @@ -15,39 +15,39 @@ import MagazineCard from "../cards/magazine-card" import CompactCard from "../cards/compact-card" import { Card } from "../cards/card" -class ListFeed extends React.Component { - onRenderItem = (item: RSSItem) => { - const props = { - feedId: this.props.feed._id, +const ListFeed: React.FC = props => { + const onRenderItem = (item: RSSItem) => { + const cardProps = { + feedId: props.feed._id, key: item._id, item: item, - source: this.props.sourceMap[item.source], - filter: this.props.filter, - viewConfigs: this.props.viewConfigs, - shortcuts: this.props.shortcuts, - markRead: this.props.markRead, - contextMenu: this.props.contextMenu, - showItem: this.props.showItem, + source: props.sourceMap[item.source], + filter: props.filter, + viewConfigs: props.viewConfigs, + shortcuts: props.shortcuts, + markRead: props.markRead, + contextMenu: props.contextMenu, + showItem: props.showItem, } as Card.Props if ( - this.props.viewType === ViewType.List && - this.props.currentItem === item._id + props.viewType === ViewType.List && + props.currentItem === item._id ) { - props.selected = true + cardProps.selected = true } - switch (this.props.viewType) { + switch (props.viewType) { case ViewType.Magazine: - return + return case ViewType.Compact: - return + return default: - return + return } } - getClassName = () => { - switch (this.props.viewType) { + const getClassName = () => { + switch (props.viewType) { case ViewType.Magazine: return "magazine-feed" case ViewType.Compact: @@ -57,7 +57,7 @@ class ListFeed extends React.Component { } } - canFocusChild = (el: HTMLElement) => { + const canFocusChild = (el: HTMLElement) => { if (el.id === "load-more") { const container = document.getElementById("refocus") const result = @@ -70,42 +70,38 @@ class ListFeed extends React.Component { } } - render() { - return ( - this.props.feed.loaded && ( - - - {this.props.feed.loaded && !this.props.feed.allLoaded ? ( -
- - this.props.loadMore(this.props.feed) - } - /> -
- ) : null} - {this.props.items.length === 0 && ( -
{intl.get("article.empty")}
- )} -
- ) + return ( + props.feed.loaded && ( + + + {props.feed.loaded && !props.feed.allLoaded ? ( +
+ props.loadMore(props.feed)} + /> +
+ ) : null} + {props.items.length === 0 && ( +
{intl.get("article.empty")}
+ )} +
) - } + ) } export default ListFeed diff --git a/src/components/nav.tsx b/src/components/nav.tsx index 02184be..b48adf3 100644 --- a/src/components/nav.tsx +++ b/src/components/nav.tsx @@ -14,15 +14,16 @@ import { openMarkAllMenu, } from "../scripts/models/app" import { toggleSearch } from "../scripts/models/page" -import { ViewType , WindowStateListenerType } from "../schema-types" +import { ViewType, WindowStateListenerType } from "../schema-types" const Nav: React.FC = () => { const dispatch = useDispatch() const state = useSelector((state: RootState) => state.app) const itemShown = useSelector( - (state: RootState) => state.page.itemId && state.page.viewType !== ViewType.List + (state: RootState) => + state.page.itemId && state.page.viewType !== ViewType.List ) - const [maximized, setMaximized] = useState(window.utils.isMaximized()) + const [maximized, setMaximized] = useState(globalThis.utils.isMaximized()) const setBodyFocusState = useCallback((focused: boolean) => { if (focused) document.body.classList.remove("blur") @@ -103,13 +104,23 @@ const Nav: React.FC = () => { } } }, - [state.settings.display, itemShown, menu, search, fetch, markAll, logs, views, settings] + [ + state.settings.display, + itemShown, + menu, + search, + fetch, + markAll, + logs, + views, + settings, + ] ) useEffect(() => { - setBodyFocusState(window.utils.isFocused()) - setBodyFullscreenState(window.utils.isFullscreen()) - window.utils.addWindowStateListener(windowStateListener) + setBodyFocusState(globalThis.utils.isFocused()) + setBodyFullscreenState(globalThis.utils.isFullscreen()) + globalThis.utils.addWindowStateListener(windowStateListener) return () => { // Cleanup will be handled by the event listener removal effect @@ -118,8 +129,8 @@ const Nav: React.FC = () => { useEffect(() => { document.addEventListener("keydown", navShortcutsHandler) - if (window.utils.platform === "darwin") - window.utils.addTouchBarEventsListener(navShortcutsHandler) + if (globalThis.utils.platform === "darwin") + globalThis.utils.addTouchBarEventsListener(navShortcutsHandler) return () => { document.removeEventListener("keydown", navShortcutsHandler) @@ -127,19 +138,19 @@ const Nav: React.FC = () => { }, [navShortcutsHandler]) const minimize = () => { - window.utils.minimizeWindow() + globalThis.utils.minimizeWindow() } const maximize = () => { - window.utils.maximizeWindow() + globalThis.utils.maximizeWindow() setMaximized(!maximized) } const close = () => { - window.utils.closeWindow() + globalThis.utils.closeWindow() } - const fetching = () => (!canFetch() ? " fetching" : "") + const fetching = () => (canFetch() ? "" : " fetching") const getClassNames = () => { const classNames = new Array() @@ -158,42 +169,39 @@ const Nav: React.FC = () => { return (