mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-31 10:58:04 +08:00
feat: support dnd in all sections
This commit is contained in:
parent
c3b6ae1f01
commit
731b31c8ce
@ -1,13 +1,13 @@
|
|||||||
import type { Metadata } from "./types"
|
import type { Metadata } from "./types"
|
||||||
|
|
||||||
export const sectionIds = ["focus", "social", "china", "world", "tech", "code"] as const
|
export const sectionIds = ["focus", "realtime", "china", "world", "tech", "code"] as const
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
focus: {
|
focus: {
|
||||||
name: "关注",
|
name: "关注",
|
||||||
sources: [],
|
sources: [],
|
||||||
},
|
},
|
||||||
social: {
|
realtime: {
|
||||||
name: "实时",
|
name: "实时",
|
||||||
sources: ["weibo", "douyin", "zhihu", "toutiao", "wallstreetcn", "ithome", "36kr"],
|
sources: ["weibo", "douyin", "zhihu", "toutiao", "wallstreetcn", "ithome", "36kr"],
|
||||||
},
|
},
|
||||||
@ -25,6 +25,6 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
tech: {
|
tech: {
|
||||||
name: "科技",
|
name: "科技",
|
||||||
sources: ["ithome", "coolapk"],
|
sources: ["ithome", "coolapk", "36kr-quick"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
44
src/atoms.ts
44
src/atoms.ts
@ -2,10 +2,32 @@ import { atom } from "jotai"
|
|||||||
import type { SectionID, SourceID } from "@shared/types"
|
import type { SectionID, SourceID } from "@shared/types"
|
||||||
import { metadata } from "@shared/data"
|
import { metadata } from "@shared/data"
|
||||||
import { sources } from "@shared/sources"
|
import { sources } from "@shared/sources"
|
||||||
|
import { typeSafeObjectEntries, typeSafeObjectFromEntries } from "@shared/type.util"
|
||||||
import { atomWithLocalStorage } from "./hooks/atomWithLocalStorage"
|
import { atomWithLocalStorage } from "./hooks/atomWithLocalStorage"
|
||||||
|
|
||||||
export const focusSourcesAtom = atomWithLocalStorage<SourceID[]>("focusSources", [], (stored) => {
|
const initialSources = typeSafeObjectFromEntries(typeSafeObjectEntries(metadata).map(([id, val]) => [id, val.sources]))
|
||||||
return stored.filter(item => item in sources)
|
export const localSourcesAtom = atomWithLocalStorage<Record<SectionID, SourceID[]>>("localsources", () => {
|
||||||
|
return initialSources
|
||||||
|
}, (stored) => {
|
||||||
|
return typeSafeObjectFromEntries(typeSafeObjectEntries({
|
||||||
|
...initialSources,
|
||||||
|
...stored,
|
||||||
|
}).filter(([id]) => initialSources[id]).map(([id, val]) => {
|
||||||
|
if (id === "focus") return [id, val]
|
||||||
|
const oldS = val.filter(k => initialSources[id].includes(k))
|
||||||
|
const newS = initialSources[id].filter(k => !oldS.includes(k))
|
||||||
|
return [id, [...oldS, ...newS]]
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const focusSourcesAtom = atom((get) => {
|
||||||
|
return get(localSourcesAtom).focus
|
||||||
|
}, (get, set, update: SourceID[] | ((prev: SourceID[]) => SourceID[])) => {
|
||||||
|
const _ = update instanceof Function ? update(get(focusSourcesAtom)) : update
|
||||||
|
set(localSourcesAtom, {
|
||||||
|
...get(localSourcesAtom),
|
||||||
|
focus: _,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function initRefetchSources() {
|
function initRefetchSources() {
|
||||||
@ -27,15 +49,11 @@ export const currentSectionIDAtom = atom<SectionID>("focus")
|
|||||||
|
|
||||||
export const currentSectionAtom = atom((get) => {
|
export const currentSectionAtom = atom((get) => {
|
||||||
const id = get(currentSectionIDAtom)
|
const id = get(currentSectionIDAtom)
|
||||||
if (id === "focus") {
|
return get(localSourcesAtom)[id]
|
||||||
return {
|
}, (get, set, update: SourceID[] | ((prev: SourceID[]) => SourceID[])) => {
|
||||||
id,
|
const _ = update instanceof Function ? update(get(currentSectionAtom)) : update
|
||||||
...metadata[id],
|
set(localSourcesAtom, {
|
||||||
sources: get(focusSourcesAtom),
|
...get(localSourcesAtom),
|
||||||
}
|
[get(currentSectionIDAtom)]: _,
|
||||||
}
|
})
|
||||||
return {
|
|
||||||
id,
|
|
||||||
...metadata[id],
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
@ -24,7 +24,7 @@ function RefreshButton() {
|
|||||||
const currentSection = useAtomValue(currentSectionAtom)
|
const currentSection = useAtomValue(currentSectionAtom)
|
||||||
const setRefetchSource = useSetAtom(refetchSourcesAtom)
|
const setRefetchSource = useSetAtom(refetchSourcesAtom)
|
||||||
const refreshAll = useCallback(() => {
|
const refreshAll = useCallback(() => {
|
||||||
const obj = Object.fromEntries(currentSection.sources.map(id => [id, Date.now()]))
|
const obj = Object.fromEntries(currentSection.map(id => [id, Date.now()]))
|
||||||
setRefetchSource(prev => ({
|
setRefetchSource(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
...obj,
|
...obj,
|
||||||
@ -33,7 +33,7 @@ function RefreshButton() {
|
|||||||
|
|
||||||
const isFetching = useIsFetching({
|
const isFetching = useIsFetching({
|
||||||
predicate: (query) => {
|
predicate: (query) => {
|
||||||
return currentSection.sources.includes(query.queryKey[0] as SourceID)
|
return currentSection.includes(query.queryKey[0] as SourceID)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -15,10 +15,10 @@ import type { SourceID } from "@shared/types"
|
|||||||
import { CSS } from "@dnd-kit/utilities"
|
import { CSS } from "@dnd-kit/utilities"
|
||||||
import type { ItemsProps } from "./card"
|
import type { ItemsProps } from "./card"
|
||||||
import { CardWrapper } from "./card"
|
import { CardWrapper } from "./card"
|
||||||
import { focusSourcesAtom } from "~/atoms"
|
import { currentSectionAtom } from "~/atoms"
|
||||||
|
|
||||||
export function Dnd() {
|
export function Dnd() {
|
||||||
const [items, setItems] = useAtom(focusSourcesAtom)
|
const [items, setItems] = useAtom(currentSectionAtom)
|
||||||
const [activeId, setActiveId] = useState<string | null>(null)
|
const [activeId, setActiveId] = useState<string | null>(null)
|
||||||
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor))
|
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor))
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import clsx from "clsx"
|
|||||||
import { useSetAtom } from "jotai"
|
import { useSetAtom } from "jotai"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { Dnd } from "./dnd"
|
import { Dnd } from "./dnd"
|
||||||
import { CardWrapper } from "./card"
|
|
||||||
import { currentSectionIDAtom } from "~/atoms"
|
import { currentSectionIDAtom } from "~/atoms"
|
||||||
|
|
||||||
export function Section({ id }: { id: SectionID }) {
|
export function Section({ id }: { id: SectionID }) {
|
||||||
@ -38,17 +37,7 @@ export function Section({ id }: { id: SectionID }) {
|
|||||||
gridTemplateColumns: "repeat(auto-fill, minmax(350px, 1fr))",
|
gridTemplateColumns: "repeat(auto-fill, minmax(350px, 1fr))",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{id === "focus"
|
<Dnd />
|
||||||
? <Dnd />
|
|
||||||
: (
|
|
||||||
<>
|
|
||||||
{
|
|
||||||
metadata[id].sources.map(source => (
|
|
||||||
<CardWrapper key={source} id={source} />
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ import { atom } from "jotai"
|
|||||||
|
|
||||||
export function atomWithLocalStorage<T>(
|
export function atomWithLocalStorage<T>(
|
||||||
key: string,
|
key: string,
|
||||||
initialValue: T,
|
initialValue: T | (() => T),
|
||||||
initFn?: ((stored: T) => T),
|
initFn?: ((stored: T) => T),
|
||||||
): PrimitiveAtom<T> {
|
): PrimitiveAtom<T> {
|
||||||
const getInitialValue = () => {
|
const getInitialValue = () => {
|
||||||
@ -17,7 +17,8 @@ export function atomWithLocalStorage<T>(
|
|||||||
} catch {
|
} catch {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
return initialValue
|
if (initialValue instanceof Function) return initialValue()
|
||||||
|
else return initialValue
|
||||||
}
|
}
|
||||||
const baseAtom = atom(getInitialValue())
|
const baseAtom = atom(getInitialValue())
|
||||||
const derivedAtom = atom(
|
const derivedAtom = atom(
|
||||||
|
@ -16,11 +16,10 @@ export const Route = createRootRouteWithContext<{
|
|||||||
})
|
})
|
||||||
|
|
||||||
function NotFoundComponent() {
|
function NotFoundComponent() {
|
||||||
// const nav = Route.useNavigate()
|
const nav = Route.useNavigate()
|
||||||
// nav({
|
nav({
|
||||||
// to: "/",
|
to: "/",
|
||||||
// })
|
})
|
||||||
// return <div></div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function RootComponent() {
|
function RootComponent() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useAtomValue } from "jotai"
|
import { useAtomValue } from "jotai"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { focusSourcesAtom } from "~/atoms"
|
import { localSourcesAtom } from "~/atoms"
|
||||||
import { Section } from "~/components/section"
|
import { Section } from "~/components/section"
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
@ -9,9 +9,9 @@ export const Route = createFileRoute("/")({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function IndexComponent() {
|
function IndexComponent() {
|
||||||
const focusSources = useAtomValue(focusSourcesAtom)
|
const focusSources = useAtomValue(localSourcesAtom)
|
||||||
const id = useMemo(() => {
|
const id = useMemo(() => {
|
||||||
return focusSources.length ? "focus" : "social"
|
return focusSources.focus.length ? "focus" : "realtime"
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
return <Section id={id} />
|
return <Section id={id} />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user