From 07d6492a6f87a005bda13ccfea368e3b8d3fdfb7 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 28 Jun 2023 23:09:21 +0800 Subject: [PATCH] feat: add favicon cache --- web/package.json | 3 ++- web/pnpm-lock.yaml | 19 +++++++++++++ web/src/components/ShortcutView.tsx | 18 ++++++------- web/src/stores/v1/favicon.ts | 41 +++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 web/src/stores/v1/favicon.ts diff --git a/web/package.json b/web/package.json index 9e02016..817cb3e 100644 --- a/web/package.json +++ b/web/package.json @@ -23,7 +23,8 @@ "react-i18next": "^13.0.1", "react-redux": "^8.0.1", "react-router-dom": "^6.13.0", - "tailwindcss": "^3.3.2" + "tailwindcss": "^3.3.2", + "zustand": "^4.3.8" }, "devDependencies": { "@types/lodash-es": "^4.17.5", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 306828b..7d0367a 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -56,6 +56,9 @@ dependencies: tailwindcss: specifier: ^3.3.2 version: 3.3.2 + zustand: + specifier: ^4.3.8 + version: 4.3.8(react@18.2.0) devDependencies: '@types/lodash-es': @@ -3109,3 +3112,19 @@ packages: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} dev: false + + /zustand@4.3.8(react@18.2.0): + resolution: {integrity: sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==} + engines: {node: '>=12.7.0'} + peerDependencies: + immer: '>=9.0' + react: '>=16.8' + peerDependenciesMeta: + immer: + optional: true + react: + optional: true + dependencies: + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false diff --git a/web/src/components/ShortcutView.tsx b/web/src/components/ShortcutView.tsx index c0f3a41..b8aa4d7 100644 --- a/web/src/components/ShortcutView.tsx +++ b/web/src/components/ShortcutView.tsx @@ -1,16 +1,16 @@ import { Tooltip } from "@mui/joy"; import copy from "copy-to-clipboard"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import toast from "react-hot-toast"; import { shortcutService } from "../services"; import { useAppSelector } from "../stores"; +import useFaviconStore from "../stores/v1/favicon"; import { absolutifyLink } from "../helpers/utils"; -import * as api from "../helpers/api"; import { showCommonDialog } from "./Alert"; import Icon from "./Icon"; import Dropdown from "./common/Dropdown"; import VisibilityIcon from "./VisibilityIcon"; -import { useEffect, useState } from "react"; interface Props { shortcut: Shortcut; @@ -21,18 +21,16 @@ const ShortcutView = (props: Props) => { const { shortcut, handleEdit } = props; const { t } = useTranslation(); const user = useAppSelector((state) => state.user.user as User); + const faviconStore = useFaviconStore(); const [favicon, setFavicon] = useState(undefined); const havePermission = user.role === "ADMIN" || shortcut.creatorId === user.id; useEffect(() => { - api - .getUrlFavicon(shortcut.link) - .then(({ data }) => { - setFavicon(data); - }) - .catch(() => { - // do nothing. - }); + faviconStore.getOrFetchUrlFavicon(shortcut.link).then((url) => { + if (url) { + setFavicon(url); + } + }); }, [shortcut.link]); const handleCopyButtonClick = (shortcut: Shortcut) => { diff --git a/web/src/stores/v1/favicon.ts b/web/src/stores/v1/favicon.ts new file mode 100644 index 0000000..70d16dc --- /dev/null +++ b/web/src/stores/v1/favicon.ts @@ -0,0 +1,41 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import * as api from "../../helpers/api"; + +interface FaviconState { + cache: { + [key: string]: string; + }; + getOrFetchUrlFavicon: (url: string) => Promise; +} + +const useFaviconStore = create()( + persist( + (set, get) => ({ + cache: {}, + getOrFetchUrlFavicon: async (url: string) => { + const cache = get().cache; + if (cache[url]) { + return cache[url]; + } + + try { + const { data: favicon } = await api.getUrlFavicon(url); + if (favicon) { + cache[url] = favicon; + set(cache); + return favicon; + } + } catch (error) { + // do nothing + } + return ""; + }, + }), + { + name: "favicon_cache", + } + ) +); + +export default useFaviconStore;