feat: support dnd in all sections

This commit is contained in:
Ou 2024-10-09 21:36:42 +08:00
parent c3b6ae1f01
commit 731b31c8ce
8 changed files with 49 additions and 42 deletions

View File

@ -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"],
}, },
} }

View File

@ -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],
}
}) })

View File

@ -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)
}, },
}) })

View File

@ -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))

View File

@ -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>
) )

View File

@ -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(

View File

@ -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() {

View File

@ -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} />