From 1e44806f42ff29a7b9b0807b2b4073afa6ddf6bd Mon Sep 17 00:00:00 2001 From: miyanokomiya Date: Mon, 21 Oct 2024 20:13:11 +0900 Subject: [PATCH] feat: Handle network error on library panels - `fetch` throws only when network error happens. --- .../molecules/ShapeLibraryGroup.tsx | 118 ++++++++++-------- src/i18n/index.ts | 2 + 2 files changed, 67 insertions(+), 53 deletions(-) diff --git a/src/components/molecules/ShapeLibraryGroup.tsx b/src/components/molecules/ShapeLibraryGroup.tsx index 253eb2f8..f9f193b4 100644 --- a/src/components/molecules/ShapeLibraryGroup.tsx +++ b/src/components/molecules/ShapeLibraryGroup.tsx @@ -5,6 +5,7 @@ import { getAssetSearchTag } from "../../utils/route"; import { newKeywordFilter } from "../../composables/keywordFilter"; import { ClickOrDragHandler } from "../atoms/ClickOrDragHandler"; import { Size } from "../../models"; +import { useTranslation } from "react-i18next"; type IconEventHandler = (url: string, id: string, size: Size) => void; @@ -75,17 +76,26 @@ interface ShapeLibraryGroupProps { const ShapeLibraryGroup: React.FC = ({ name, type, size, onIconDragStart, onIconClick }) => { const [loading, setLoading] = useState(true); + const [errorMessage, setErrorMessage] = useState(); const [indexData, setIndexData] = useState(); const basePath = `${baseURL}${type}/${name}`; + const { t } = useTranslation(); const fetchIndex = useCallback(async () => { if (indexData) return; - const res = await fetch(`${basePath}/index.json`); - const data = await res.json(); - setIndexData(data); - setLoading(false); - }, [indexData, basePath]); + try { + setLoading(true); + const res = await fetch(`${basePath}/index.json`); + const data = await res.json(); + setIndexData(data); + setErrorMessage(undefined); + } catch (e) { + setErrorMessage(t("error.network.maybe_offline")); + } finally { + setLoading(false); + } + }, [indexData, basePath, t]); useEffect(() => { fetchIndex(); @@ -134,57 +144,59 @@ const ShapeLibraryGroup: React.FC = ({ name, type, size, }); }, [filteredIndexData, indexDataForSearch, sortedIndexData]); + if (loading) { + return
Loading...
; + } + + if (errorMessage) { + return
{errorMessage}
; + } + return (
- {loading ? ( -
Loading...
+
+ +
+ {rootItems ? ( + rootItems.length === 0 ? ( +
No result
+ ) : ( +
    + {rootItems.map(({ url, id, name }) => { + return ( + + ); + })} +
+ ) ) : ( -
-
- -
- {rootItems ? ( - rootItems.length === 0 ? ( -
No result
- ) : ( -
    - {rootItems.map(({ url, id, name }) => { - return ( - - ); - })} -
- ) - ) : ( - sortedIndexData.map(([key, item]) => ( - - )) - )} -
+ sortedIndexData.map(([key, item]) => ( + + )) )}
); diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 87680e28..2ca60dfb 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -24,6 +24,7 @@ const en = { "term.workspace": 'A workspace is a folder where a diagram data is saved. Each sheet is saved as a separate file. All asset files are saved in the "assets" folder.', "term.make_polygon": "To make a polygon, source line has to be neither an elbow nor a segment.", + "error.network.maybe_offline": "Network error happened. The device may be offline.", }, }; type TranslationResource = typeof en; @@ -49,6 +50,7 @@ const ja: TranslationResource = { "term.workspace": 'ワークスペースは、ダイアグラムの保存先となるフォルダです。各シートは別々のファイルとして保存され、アセットファイルは"assets"フォルダに格納されます。', "term.make_polygon": "エルボー、あるいは単一線分はポリゴン化できません。", + "error.network.maybe_offline": "通信エラーが発生しました。端末がオフラインの可能性があります。", }, };