diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index 106f90810d..b121aa644c 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -11,6 +11,7 @@ import { fetchAppList } from '@/service/apps' import { useAppContext } from '@/context/app-context' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { CheckModal } from '@/hooks/use-pay' +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import TabSlider from '@/app/components/base/tab-slider' import { SearchLg } from '@/app/components/base/icons/src/vender/line/general' import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' @@ -35,7 +36,9 @@ const getKey = ( const Apps = () => { const { t } = useTranslation() const { isCurrentWorkspaceManager } = useAppContext() - const [activeTab, setActiveTab] = useState('all') + const [activeTab, setActiveTab] = useTabSearchParams({ + defaultTab: 'all', + }) const [keywords, setKeywords] = useState('') const [searchKeywords, setSearchKeywords] = useState('') diff --git a/web/app/(commonLayout)/datasets/Container.tsx b/web/app/(commonLayout)/datasets/Container.tsx index c3ebef2ea8..d70ed1cb63 100644 --- a/web/app/(commonLayout)/datasets/Container.tsx +++ b/web/app/(commonLayout)/datasets/Container.tsx @@ -1,7 +1,7 @@ 'use client' // Libraries -import { useRef, useState } from 'react' +import { useRef } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' @@ -15,6 +15,9 @@ import TabSlider from '@/app/components/base/tab-slider' // Services import { fetchDatasetApiBaseUrl } from '@/service/datasets' +// Hooks +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' + const Container = () => { const { t } = useTranslation() @@ -23,7 +26,9 @@ const Container = () => { { value: 'api', text: t('dataset.datasetsApi') }, ] - const [activeTab, setActiveTab] = useState('dataset') + const [activeTab, setActiveTab] = useTabSearchParams({ + defaultTab: 'dataset', + }) const containerRef = useRef(null) const { data } = useSWR(activeTab === 'dataset' ? null : '/datasets/api-base-info', fetchDatasetApiBaseUrl) diff --git a/web/app/components/app/annotation/header-opts/index.tsx b/web/app/components/app/annotation/header-opts/index.tsx index 90b1a9672e..aba3b6324c 100644 --- a/web/app/components/app/annotation/header-opts/index.tsx +++ b/web/app/components/app/annotation/header-opts/index.tsx @@ -42,6 +42,7 @@ const HeaderOptions: FC = ({ const { locale } = useContext(I18n) const { CSVDownloader, Type } = useCSVDownloader() const [list, setList] = useState([]) + const annotationUnavailable = list.length === 0 const listTransformer = (list: AnnotationItemBasic[]) => list.map( (item: AnnotationItemBasic) => { @@ -116,11 +117,11 @@ const HeaderOptions: FC = ({ ...list.map(item => [item.question, item.answer]), ]} > - - diff --git a/web/app/components/app/annotation/header-opts/style.module.css b/web/app/components/app/annotation/header-opts/style.module.css index 29d43f449d..68234aed00 100644 --- a/web/app/components/app/annotation/header-opts/style.module.css +++ b/web/app/components/app/annotation/header-opts/style.module.css @@ -19,7 +19,7 @@ } .actionItem { - @apply h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-gray-100 rounded-lg cursor-pointer; + @apply h-9 py-2 px-3 mx-1 flex items-center space-x-2 hover:bg-gray-100 rounded-lg cursor-pointer disabled:opacity-50; width: calc(100% - 0.5rem); } @@ -35,4 +35,4 @@ left: 4px; transform: translateX(-100%); box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); -} \ No newline at end of file +} diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 02a49782ec..6ceabe23bf 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -12,6 +12,7 @@ import Category from '@/app/components/explore/category' import AppCard from '@/app/components/explore/app-card' import { fetchAppDetail, fetchAppList } from '@/service/explore' import { createApp } from '@/service/apps' +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import CreateAppModal from '@/app/components/explore/create-app-modal' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import Loading from '@/app/components/base/loading' @@ -24,7 +25,9 @@ const Apps: FC = () => { const { isCurrentWorkspaceManager } = useAppContext() const router = useRouter() const { hasEditPermission } = useContext(ExploreContext) - const [currCategory, setCurrCategory] = React.useState('') + const [currCategory, setCurrCategory] = useTabSearchParams({ + defaultTab: '', + }) const [allList, setAllList] = React.useState([]) const [isLoaded, setIsLoaded] = React.useState(false) diff --git a/web/app/components/tools/index.tsx b/web/app/components/tools/index.tsx index d540cfb2b4..2f6eeb07e5 100644 --- a/web/app/components/tools/index.tsx +++ b/web/app/components/tools/index.tsx @@ -16,6 +16,7 @@ import EditCustomToolModal from './edit-custom-collection-modal' import NoCustomTool from './info/no-custom-tool' import NoSearchRes from './info/no-search-res' import NoCustomToolPlaceholder from './no-custom-tool-placeholder' +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import TabSlider from '@/app/components/base/tab-slider' import { createCustomCollection, fetchCollectionList as doFetchCollectionList, fetchBuiltInToolList, fetchCustomToolList } from '@/service/tools' import type { AgentTool } from '@/types/app' @@ -68,7 +69,9 @@ const Tools: FC = ({ })() const [query, setQuery] = useState('') - const [collectionType, setCollectionType] = useState(collectionTypeOptions[0].value) + const [collectionType, setCollectionType] = useTabSearchParams({ + defaultTab: collectionTypeOptions[0].value, + }) const showCollectionList = (() => { let typeFilteredList: Collection[] = [] diff --git a/web/hooks/use-tab-searchparams.ts b/web/hooks/use-tab-searchparams.ts new file mode 100644 index 0000000000..f34c7be8c7 --- /dev/null +++ b/web/hooks/use-tab-searchparams.ts @@ -0,0 +1,34 @@ +import { usePathname, useRouter, useSearchParams } from 'next/navigation' + +type UseTabSearchParamsOptions = { + defaultTab: string + routingBehavior?: 'push' | 'replace' + searchParamName?: string +} + +/** + * Custom hook to manage tab state via URL search parameters in a Next.js application. + * This hook allows for syncing the active tab with the browser's URL, enabling bookmarking and sharing of URLs with a specific tab activated. + * + * @param {UseTabSearchParamsOptions} options Configuration options for the hook: + * - `defaultTab`: The tab to default to when no tab is specified in the URL. + * - `routingBehavior`: Optional. Determines how changes to the active tab update the browser's history ('push' or 'replace'). Default is 'push'. + * - `searchParamName`: Optional. The name of the search parameter that holds the tab state in the URL. Default is 'category'. + * @returns A tuple where the first element is the active tab and the second element is a function to set the active tab. + */ +export const useTabSearchParams = ({ + defaultTab, + routingBehavior = 'push', + searchParamName = 'category', +}: UseTabSearchParamsOptions) => { + const router = useRouter() + const pathName = usePathname() + const searchParams = useSearchParams() + const activeTab = searchParams.get(searchParamName) || defaultTab + + const setActiveTab = (newActiveTab: string) => { + router[routingBehavior](`${pathName}?${searchParamName}=${newActiveTab}`) + } + + return [activeTab, setActiveTab] as const +}