Browse Source

Feat/use searchparams as state (#2554)

Co-authored-by: crazywoola <427733928@qq.com>
Rozstone 1 năm trước cách đây
mục cha
commit
d93288f711

+ 4 - 1
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('')
 

+ 7 - 2
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<HTMLDivElement>(null)
   const { data } = useSWR(activeTab === 'dataset' ? null : '/datasets/api-base-info', fetchDatasetApiBaseUrl)
 

+ 3 - 2
web/app/components/app/annotation/header-opts/index.tsx

@@ -42,6 +42,7 @@ const HeaderOptions: FC<Props> = ({
   const { locale } = useContext(I18n)
   const { CSVDownloader, Type } = useCSVDownloader()
   const [list, setList] = useState<AnnotationItemBasic[]>([])
+  const annotationUnavailable = list.length === 0
 
   const listTransformer = (list: AnnotationItemBasic[]) => list.map(
     (item: AnnotationItemBasic) => {
@@ -116,11 +117,11 @@ const HeaderOptions: FC<Props> = ({
                   ...list.map(item => [item.question, item.answer]),
                 ]}
               >
-                <button className={s.actionItem}>
+                <button disabled={annotationUnavailable} className={s.actionItem}>
                   <span className={s.actionName}>CSV</span>
                 </button>
               </CSVDownloader>
-              <button className={cn(s.actionItem, '!border-0')} onClick={JSONLOutput}>
+              <button disabled={annotationUnavailable} className={cn(s.actionItem, '!border-0')} onClick={JSONLOutput}>
                 <span className={s.actionName}>JSONL</span>
               </button>
             </Menu.Items>

+ 2 - 2
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);
-}
+}

+ 4 - 1
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<AppCategory | ''>('')
+  const [currCategory, setCurrCategory] = useTabSearchParams({
+    defaultTab: '',
+  })
   const [allList, setAllList] = React.useState<App[]>([])
   const [isLoaded, setIsLoaded] = React.useState(false)
 

+ 4 - 1
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<Props> = ({
   })()
 
   const [query, setQuery] = useState('')
-  const [collectionType, setCollectionType] = useState<CollectionType>(collectionTypeOptions[0].value)
+  const [collectionType, setCollectionType] = useTabSearchParams({
+    defaultTab: collectionTypeOptions[0].value,
+  })
 
   const showCollectionList = (() => {
     let typeFilteredList: Collection[] = []

+ 34 - 0
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
+}