forked from Archives/Athou_commafeed
cleanup
This commit is contained in:
@@ -3,7 +3,6 @@ import { client } from "app/client"
|
|||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { type EntrySource, type EntrySourceType, entriesSlice, setMarkAllAsReadConfirmationDialogOpen, setSearch } from "app/entries/slice"
|
import { type EntrySource, type EntrySourceType, entriesSlice, setMarkAllAsReadConfirmationDialogOpen, setSearch } from "app/entries/slice"
|
||||||
import type { RootState } from "app/store"
|
import type { RootState } from "app/store"
|
||||||
import { setHasNewEntries } from "app/tree/slice"
|
|
||||||
import { reloadTree } from "app/tree/thunks"
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import type { Entry, MarkRequest, TagRequest } from "app/types"
|
import type { Entry, MarkRequest, TagRequest } from "app/types"
|
||||||
import { reloadTags } from "app/user/thunks"
|
import { reloadTags } from "app/user/thunks"
|
||||||
@@ -27,9 +26,6 @@ export const loadEntries = createAppAsyncThunk(
|
|||||||
const state = thunkApi.getState()
|
const state = thunkApi.getState()
|
||||||
const endpoint = getEndpoint(arg.source.type)
|
const endpoint = getEndpoint(arg.source.type)
|
||||||
const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0))
|
const result = await endpoint(buildGetEntriesPaginatedRequest(state, arg.source, 0))
|
||||||
if (arg.source.type === "feed") {
|
|
||||||
thunkApi.dispatch(setHasNewEntries({ feedId: +arg.source.id, value: false }))
|
|
||||||
}
|
|
||||||
return result.data
|
return result.data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
|
import { type PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||||
import { markEntry } from "app/entries/thunks"
|
import { loadEntries, markEntry } from "app/entries/thunks"
|
||||||
import { redirectTo } from "app/redirect/slice"
|
import { redirectTo } from "app/redirect/slice"
|
||||||
import { collapseTreeCategory, reloadTree } from "app/tree/thunks"
|
import { collapseTreeCategory, reloadTree } from "app/tree/thunks"
|
||||||
import type { Category } from "app/types"
|
import type { Category, Subscription } from "app/types"
|
||||||
import { visitCategoryTree } from "app/utils"
|
import { flattenCategoryTree, visitCategoryTree } from "app/utils"
|
||||||
|
|
||||||
|
export interface TreeSubscription extends Subscription {
|
||||||
|
// client-side only flag
|
||||||
|
hasNewEntries?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TreeCategory extends Category {
|
||||||
|
feeds: TreeSubscription[]
|
||||||
|
children: TreeCategory[]
|
||||||
|
}
|
||||||
|
|
||||||
interface TreeState {
|
interface TreeState {
|
||||||
rootCategory?: Category
|
rootCategory?: TreeCategory
|
||||||
mobileMenuOpen: boolean
|
mobileMenuOpen: boolean
|
||||||
sidebarVisible: boolean
|
sidebarVisible: boolean
|
||||||
}
|
}
|
||||||
@@ -37,21 +47,27 @@ export const treeSlice = createSlice({
|
|||||||
visitCategoryTree(state.rootCategory, c => {
|
visitCategoryTree(state.rootCategory, c => {
|
||||||
for (const f of c.feeds.filter(f => f.id === action.payload.feedId)) {
|
for (const f of c.feeds.filter(f => f.id === action.payload.feedId)) {
|
||||||
f.unread += action.payload.amount
|
f.unread += action.payload.amount
|
||||||
|
f.hasNewEntries = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setHasNewEntries: (state, action: PayloadAction<{ feedId: number; value: boolean }>) => {
|
|
||||||
if (!state.rootCategory) return
|
|
||||||
|
|
||||||
visitCategoryTree(state.rootCategory, category => {
|
|
||||||
category.feeds = category.feeds.map(feed =>
|
|
||||||
feed.id === action.payload.feedId ? { ...feed, hasNewEntries: action.payload.value } : feed
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
builder.addCase(reloadTree.fulfilled, (state, action) => {
|
builder.addCase(reloadTree.fulfilled, (state, action) => {
|
||||||
|
// set hasNewEntries to true if new unread > previous unread
|
||||||
|
if (state.rootCategory) {
|
||||||
|
const oldFeeds = flattenCategoryTree(state.rootCategory).flatMap(c => c.feeds)
|
||||||
|
const oldFeedsById = new Map(oldFeeds.map(f => [f.id, f]))
|
||||||
|
|
||||||
|
const newFeeds = flattenCategoryTree(action.payload).flatMap(c => c.feeds)
|
||||||
|
for (const newFeed of newFeeds) {
|
||||||
|
const oldFeed = oldFeedsById.get(newFeed.id)
|
||||||
|
if (oldFeed && newFeed.unread > oldFeed.unread) {
|
||||||
|
newFeed.hasNewEntries = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.rootCategory = action.payload
|
state.rootCategory = action.payload
|
||||||
})
|
})
|
||||||
builder.addCase(collapseTreeCategory.pending, (state, action) => {
|
builder.addCase(collapseTreeCategory.pending, (state, action) => {
|
||||||
@@ -68,10 +84,29 @@ export const treeSlice = createSlice({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
builder.addCase(loadEntries.pending, (state, action) => {
|
||||||
|
if (!state.rootCategory) return
|
||||||
|
|
||||||
|
const { source } = action.meta.arg
|
||||||
|
if (source.type === "category") {
|
||||||
|
visitCategoryTree(state.rootCategory, c => {
|
||||||
|
if (c.id === source.id) {
|
||||||
|
for (const f of flattenCategoryTree(c).flatMap(c => c.feeds)) {
|
||||||
|
f.hasNewEntries = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (source.type === "feed") {
|
||||||
|
const feeds = flattenCategoryTree(state.rootCategory).flatMap(c => c.feeds)
|
||||||
|
for (const f of feeds.filter(f => f.id === +source.id)) {
|
||||||
|
f.hasNewEntries = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
builder.addCase(redirectTo, state => {
|
builder.addCase(redirectTo, state => {
|
||||||
state.mobileMenuOpen = false
|
state.mobileMenuOpen = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount, setHasNewEntries } = treeSlice.actions
|
export const { setMobileMenuOpen, toggleSidebar, incrementUnreadCount } = treeSlice.actions
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createAppAsyncThunk } from "app/async-thunk"
|
import { createAppAsyncThunk } from "app/async-thunk"
|
||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { redirectToCategory, redirectToFeed } from "app/redirect/thunks"
|
import { redirectToCategory, redirectToFeed } from "app/redirect/thunks"
|
||||||
import { incrementUnreadCount, setHasNewEntries } from "app/tree/slice"
|
import { incrementUnreadCount } from "app/tree/slice"
|
||||||
import type { CollapseRequest, Subscription } from "app/types"
|
import type { CollapseRequest, Subscription } from "app/types"
|
||||||
import { flattenCategoryTree, visitCategoryTree } from "app/utils"
|
import { flattenCategoryTree, visitCategoryTree } from "app/utils"
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ export const collapseTreeCategory = createAppAsyncThunk(
|
|||||||
"tree/category/collapse",
|
"tree/category/collapse",
|
||||||
async (req: CollapseRequest) => await client.category.collapse(req).then(r => r.data)
|
async (req: CollapseRequest) => await client.category.collapse(req).then(r => r.data)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const selectNextUnreadTreeItem = createAppAsyncThunk(
|
export const selectNextUnreadTreeItem = createAppAsyncThunk(
|
||||||
"tree/selectNextUnreadItem",
|
"tree/selectNextUnreadItem",
|
||||||
(
|
(
|
||||||
@@ -74,7 +75,6 @@ export const newFeedEntriesDiscovered = createAppAsyncThunk(
|
|||||||
amount,
|
amount,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
thunkApi.dispatch(setHasNewEntries({ feedId, value: true }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit"
|
import { configureStore } from "@reduxjs/toolkit"
|
||||||
|
import { client } from "app/client"
|
||||||
|
import { loadEntries } from "app/entries/thunks"
|
||||||
import { type RootState, reducers } from "app/store"
|
import { type RootState, reducers } from "app/store"
|
||||||
import { selectNextUnreadTreeItem } from "app/tree/thunks"
|
import { newFeedEntriesDiscovered, selectNextUnreadTreeItem } from "app/tree/thunks"
|
||||||
import type { Category, Subscription } from "app/types"
|
import type { Category, Entries, Entry, Subscription } from "app/types"
|
||||||
import { describe, expect, it } from "vitest"
|
import type { AxiosResponse } from "axios"
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||||
|
|
||||||
|
vi.mock(import("app/client"))
|
||||||
|
|
||||||
const createCategory = (id: string): Category => ({
|
const createCategory = (id: string): Category => ({
|
||||||
id,
|
id,
|
||||||
@@ -117,3 +122,51 @@ describe("selectNextUnreadTreeItem", () => {
|
|||||||
expect(store.getState().redirect.to).toBe("/app/feed/3")
|
expect(store.getState().redirect.to).toBe("/app/feed/3")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("hasNewEntries", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("sets and clear flag for a feed", async () => {
|
||||||
|
vi.mocked(client.feed.getEntries).mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
entries: [{ id: "3" } as Entry],
|
||||||
|
hasMore: false,
|
||||||
|
name: "my-feed",
|
||||||
|
errorCount: 3,
|
||||||
|
feedLink: "https://mysite.com/feed",
|
||||||
|
timestamp: 123,
|
||||||
|
ignoredReadStatus: false,
|
||||||
|
},
|
||||||
|
} as AxiosResponse<Entries>)
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: reducers,
|
||||||
|
preloadedState: {
|
||||||
|
tree: {
|
||||||
|
rootCategory: root,
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
source: {
|
||||||
|
type: "feed",
|
||||||
|
id: "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as RootState,
|
||||||
|
})
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].unread).toBe(0)
|
||||||
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBeFalsy()
|
||||||
|
|
||||||
|
// increments unread count and sets hasNewEntries to true
|
||||||
|
await store.dispatch(newFeedEntriesDiscovered({ feedId: 1, amount: 3 }))
|
||||||
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].unread).toBe(3)
|
||||||
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBe(true)
|
||||||
|
|
||||||
|
// reload entries and sets hasNewEntries to false
|
||||||
|
await store.dispatch(loadEntries({ source: { type: "feed", id: "1" }, clearSearch: true }))
|
||||||
|
expect(store.getState().tree.rootCategory?.children[0].feeds[0].hasNewEntries).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -30,17 +30,13 @@ export interface Subscription {
|
|||||||
filter?: string
|
filter?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TreeSubscription extends Subscription {
|
|
||||||
hasNewEntries?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
id: string
|
id: string
|
||||||
parentId?: string
|
parentId?: string
|
||||||
parentName?: string
|
parentName?: string
|
||||||
name: string
|
name: string
|
||||||
children: Category[]
|
children: Category[]
|
||||||
feeds: TreeSubscription[]
|
feeds: Subscription[]
|
||||||
expanded: boolean
|
expanded: boolean
|
||||||
position: number
|
position: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import type { TreeCategory } from "app/tree/slice"
|
||||||
import { throttle } from "throttle-debounce"
|
import { throttle } from "throttle-debounce"
|
||||||
import type { Category } from "./types"
|
import type { Category } from "./types"
|
||||||
|
|
||||||
export function visitCategoryTree(
|
export function visitCategoryTree(
|
||||||
category: Category,
|
category: TreeCategory,
|
||||||
visitor: (category: Category) => void,
|
visitor: (category: TreeCategory) => void,
|
||||||
options?: {
|
options?: {
|
||||||
childrenFirst?: boolean
|
childrenFirst?: boolean
|
||||||
}
|
}
|
||||||
@@ -19,13 +20,13 @@ export function visitCategoryTree(
|
|||||||
if (childrenFirst) visitor(category)
|
if (childrenFirst) visitor(category)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenCategoryTree(category: Category): Category[] {
|
export function flattenCategoryTree(category: TreeCategory): TreeCategory[] {
|
||||||
const categories: Category[] = []
|
const categories: Category[] = []
|
||||||
visitCategoryTree(category, c => categories.push(c))
|
visitCategoryTree(category, c => categories.push(c))
|
||||||
return categories
|
return categories
|
||||||
}
|
}
|
||||||
|
|
||||||
export function categoryUnreadCount(category?: Category): number {
|
export function categoryUnreadCount(category?: TreeCategory): number {
|
||||||
if (!category) return 0
|
if (!category) return 0
|
||||||
|
|
||||||
return flattenCategoryTree(category)
|
return flattenCategoryTree(category)
|
||||||
@@ -34,6 +35,14 @@ export function categoryUnreadCount(category?: Category): number {
|
|||||||
.reduce((total, current) => total + current, 0)
|
.reduce((total, current) => total + current, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function categoryHasNewEntries(category?: TreeCategory): boolean {
|
||||||
|
if (!category) return false
|
||||||
|
|
||||||
|
return flattenCategoryTree(category)
|
||||||
|
.flatMap(c => c.feeds)
|
||||||
|
.some(f => f.hasNewEntries)
|
||||||
|
}
|
||||||
|
|
||||||
export const calculatePlaceholderSize = ({ width, height, maxWidth }: { width?: number; height?: number; maxWidth: number }) => {
|
export const calculatePlaceholderSize = ({ width, height, maxWidth }: { width?: number; height?: number; maxWidth: number }) => {
|
||||||
const placeholderWidth = width && Math.min(width, maxWidth)
|
const placeholderWidth = width && Math.min(width, maxWidth)
|
||||||
const placeholderHeight = height && width && width > maxWidth ? height * (maxWidth / width) : height
|
const placeholderHeight = height && width && width > maxWidth ? height * (maxWidth / width) : height
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import {
|
|||||||
redirectToTagDetails,
|
redirectToTagDetails,
|
||||||
} from "app/redirect/thunks"
|
} from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import type { TreeSubscription } from "app/tree/slice"
|
||||||
import { collapseTreeCategory } from "app/tree/thunks"
|
import { collapseTreeCategory } from "app/tree/thunks"
|
||||||
import type { Category, Subscription, TreeSubscription } from "app/types"
|
import type { Category, Subscription } from "app/types"
|
||||||
import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
|
import { categoryHasNewEntries, categoryUnreadCount, flattenCategoryTree } from "app/utils"
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
import { OnDesktop } from "components/responsive/OnDesktop"
|
import { OnDesktop } from "components/responsive/OnDesktop"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
@@ -89,6 +90,7 @@ export function Tree() {
|
|||||||
name={<Trans>All</Trans>}
|
name={<Trans>All</Trans>}
|
||||||
icon={allIcon}
|
icon={allIcon}
|
||||||
unread={categoryUnreadCount(root)}
|
unread={categoryUnreadCount(root)}
|
||||||
|
hasNewEntries={categoryHasNewEntries(root)}
|
||||||
selected={source.type === "category" && source.id === Constants.categories.all.id}
|
selected={source.type === "category" && source.id === Constants.categories.all.id}
|
||||||
expanded={false}
|
expanded={false}
|
||||||
level={0}
|
level={0}
|
||||||
@@ -103,6 +105,7 @@ export function Tree() {
|
|||||||
name={<Trans>Starred</Trans>}
|
name={<Trans>Starred</Trans>}
|
||||||
icon={starredIcon}
|
icon={starredIcon}
|
||||||
unread={0}
|
unread={0}
|
||||||
|
hasNewEntries={false}
|
||||||
selected={source.type === "category" && source.id === Constants.categories.starred.id}
|
selected={source.type === "category" && source.id === Constants.categories.starred.id}
|
||||||
expanded={false}
|
expanded={false}
|
||||||
level={0}
|
level={0}
|
||||||
@@ -122,6 +125,7 @@ export function Tree() {
|
|||||||
name={category.name}
|
name={category.name}
|
||||||
icon={category.expanded ? expandedIcon : collapsedIcon}
|
icon={category.expanded ? expandedIcon : collapsedIcon}
|
||||||
unread={categoryUnreadCount(category)}
|
unread={categoryUnreadCount(category)}
|
||||||
|
hasNewEntries={categoryHasNewEntries(category)}
|
||||||
selected={source.type === "category" && source.id === category.id}
|
selected={source.type === "category" && source.id === category.id}
|
||||||
expanded={category.expanded}
|
expanded={category.expanded}
|
||||||
level={level}
|
level={level}
|
||||||
@@ -143,12 +147,12 @@ export function Tree() {
|
|||||||
name={feed.name}
|
name={feed.name}
|
||||||
icon={feed.iconUrl}
|
icon={feed.iconUrl}
|
||||||
unread={feed.unread}
|
unread={feed.unread}
|
||||||
|
hasNewEntries={!!feed.hasNewEntries}
|
||||||
selected={source.type === "feed" && source.id === String(feed.id)}
|
selected={source.type === "feed" && source.id === String(feed.id)}
|
||||||
level={level}
|
level={level}
|
||||||
hasError={feed.errorCount > errorThreshold}
|
hasError={feed.errorCount > errorThreshold}
|
||||||
onClick={feedClicked}
|
onClick={feedClicked}
|
||||||
key={feed.id}
|
key={feed.id}
|
||||||
newMessages={feed.hasNewEntries}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -160,6 +164,7 @@ export function Tree() {
|
|||||||
name={tag}
|
name={tag}
|
||||||
icon={tagIcon}
|
icon={tagIcon}
|
||||||
unread={0}
|
unread={0}
|
||||||
|
hasNewEntries={false}
|
||||||
selected={source.type === "tag" && source.id === tag}
|
selected={source.type === "tag" && source.id === tag}
|
||||||
level={0}
|
level={0}
|
||||||
hasError={false}
|
hasError={false}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface TreeNodeProps {
|
|||||||
expanded?: boolean
|
expanded?: boolean
|
||||||
level: number
|
level: number
|
||||||
hasError: boolean
|
hasError: boolean
|
||||||
newMessages?: boolean
|
hasNewEntries: boolean
|
||||||
onClick: (e: React.MouseEvent, id: string) => void
|
onClick: (e: React.MouseEvent, id: string) => void
|
||||||
onIconClick?: (e: React.MouseEvent, id: string) => void
|
onIconClick?: (e: React.MouseEvent, id: string) => void
|
||||||
}
|
}
|
||||||
@@ -65,12 +65,12 @@ export function TreeNode(props: TreeNodeProps) {
|
|||||||
hasError: props.hasError,
|
hasError: props.hasError,
|
||||||
hasUnread: props.unread > 0,
|
hasUnread: props.unread > 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
py={1}
|
py={1}
|
||||||
pl={props.level * 20}
|
pl={props.level * 20}
|
||||||
className={`${classes.node} cf-treenode cf-treenode-${props.type}`}
|
className={`${classes.node} cf-treenode cf-treenode-${props.type}`}
|
||||||
|
onClick={(e: React.MouseEvent) => props.onClick(e, props.id)}
|
||||||
data-id={props.id}
|
data-id={props.id}
|
||||||
data-type={props.type}
|
data-type={props.type}
|
||||||
data-unread-count={props.unread}
|
data-unread-count={props.unread}
|
||||||
@@ -81,7 +81,7 @@ export function TreeNode(props: TreeNodeProps) {
|
|||||||
<Box className={classes.nodeText}>{props.name}</Box>
|
<Box className={classes.nodeText}>{props.name}</Box>
|
||||||
{!props.expanded && (
|
{!props.expanded && (
|
||||||
<Box className="cf-treenode-unread-count">
|
<Box className="cf-treenode-unread-count">
|
||||||
<UnreadCount unreadCount={props.unread} newMessages={props.id !== "all" ? props.newMessages : false} />
|
<UnreadCount unreadCount={props.unread} showIndicator={props.hasNewEntries} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -5,20 +5,20 @@ import { tss } from "tss"
|
|||||||
const useStyles = tss.create(() => ({
|
const useStyles = tss.create(() => ({
|
||||||
badge: {
|
badge: {
|
||||||
width: "3.2rem",
|
width: "3.2rem",
|
||||||
|
// for some reason, mantine Badge has "cursor: 'default'"
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export function UnreadCount(props: { unreadCount: number; newMessages: boolean | undefined }) {
|
export function UnreadCount(props: { unreadCount: number; showIndicator: boolean }) {
|
||||||
const { classes } = useStyles()
|
const { classes } = useStyles()
|
||||||
|
|
||||||
if (props.unreadCount <= 0) return null
|
if (props.unreadCount <= 0) return null
|
||||||
|
|
||||||
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
|
const count = props.unreadCount >= 10000 ? "10k+" : props.unreadCount
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count} openDelay={Constants.tooltip.delay}>
|
<Tooltip label={props.unreadCount} disabled={props.unreadCount === count} openDelay={Constants.tooltip.delay}>
|
||||||
<Indicator disabled={!props.newMessages} size={4} offset={10} position="top-start" color="orange" withBorder={false} zIndex={5}>
|
<Indicator disabled={!props.showIndicator} size={4} offset={10} position="middle-start">
|
||||||
<Badge className={`${classes.badge} cf-badge`} variant="light" fullWidth>
|
<Badge className={`${classes.badge} cf-badge`} variant="light" fullWidth>
|
||||||
{count}
|
{count}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
Reference in New Issue
Block a user