refactor(web): migrate local storage hook usage (#36890)

This commit is contained in:
yyh 2026-06-01 16:20:13 +08:00 committed by GitHub
parent 21711bebeb
commit 055d9b9f0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 223 additions and 20 deletions

View File

@ -109,6 +109,9 @@
}
},
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx": {
"no-restricted-globals": {
"count": 1
},
"react/set-state-in-effect": {
"count": 2
},
@ -116,6 +119,11 @@
"count": 1
}
},
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/svg-attribute-error-reproduction.spec.tsx": {
"no-console": {
"count": 19
@ -163,6 +171,9 @@
}
},
"web/app/(shareLayout)/webapp-reset-password/page.tsx": {
"no-restricted-globals": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
@ -178,6 +189,9 @@
}
},
"web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx": {
"no-restricted-globals": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
@ -213,6 +227,11 @@
"count": 1
}
},
"web/app/account/(commonLayout)/delete-account/index.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/account/oauth/authorize/layout.tsx": {
"ts/no-explicit-any": {
"count": 1
@ -233,7 +252,15 @@
"count": 4
}
},
"web/app/components/app-sidebar/app-info/use-app-info-actions.ts": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/app-sidebar/index.tsx": {
"no-restricted-globals": {
"count": 2
},
"ts/no-explicit-any": {
"count": 1
}
@ -390,6 +417,9 @@
}
},
"web/app/components/app/configuration/config/automatic/get-automatic-res.tsx": {
"no-restricted-globals": {
"count": 6
},
"react/set-state-in-effect": {
"count": 4
},
@ -418,6 +448,9 @@
}
},
"web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx": {
"no-restricted-globals": {
"count": 6
},
"react/set-state-in-effect": {
"count": 4
},
@ -487,6 +520,9 @@
}
},
"web/app/components/app/configuration/debug/hooks.tsx": {
"no-restricted-globals": {
"count": 2
},
"ts/no-explicit-any": {
"count": 3
}
@ -515,6 +551,9 @@
}
},
"web/app/components/app/create-app-dialog/app-list/index.tsx": {
"no-restricted-globals": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
@ -528,6 +567,9 @@
}
},
"web/app/components/app/create-app-modal/index.tsx": {
"no-restricted-globals": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
@ -536,6 +578,9 @@
"erasable-syntax-only/enums": {
"count": 1
},
"no-restricted-globals": {
"count": 2
},
"no-restricted-imports": {
"count": 1
},
@ -601,6 +646,9 @@
}
},
"web/app/components/app/switch-app-modal/index.tsx": {
"no-restricted-globals": {
"count": 1
},
"no-restricted-imports": {
"count": 1
},
@ -636,7 +684,15 @@
"count": 1
}
},
"web/app/components/apps/app-card.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/apps/list.tsx": {
"no-restricted-globals": {
"count": 2
},
"no-restricted-imports": {
"count": 1
}
@ -788,6 +844,9 @@
}
},
"web/app/components/base/chat/chat-with-history/hooks.tsx": {
"no-restricted-globals": {
"count": 2
},
"react/set-state-in-effect": {
"count": 4
},
@ -1835,6 +1894,11 @@
"count": 4
}
},
"web/app/components/billing/plan/index.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/billing/pricing/assets/index.tsx": {
"no-barrel-files/no-barrel-files": {
"count": 12
@ -2293,6 +2357,9 @@
}
},
"web/app/components/datasets/metadata/hooks/use-edit-dataset-metadata.ts": {
"no-restricted-globals": {
"count": 2
},
"react/set-state-in-effect": {
"count": 1
}
@ -2315,6 +2382,11 @@
"count": 1
}
},
"web/app/components/datasets/metadata/metadata-document/info-group.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/datasets/metadata/types.ts": {
"erasable-syntax-only/enums": {
"count": 2
@ -2358,6 +2430,11 @@
"count": 2
}
},
"web/app/components/education-verify-action-recorder.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/explore/app-list/index.tsx": {
"no-restricted-imports": {
"count": 1
@ -2430,6 +2507,11 @@
"count": 1
}
},
"web/app/components/goto-anything/actions/recent-store.ts": {
"no-restricted-globals": {
"count": 2
}
},
"web/app/components/goto-anything/actions/types.ts": {
"ts/no-explicit-any": {
"count": 2
@ -2473,6 +2555,11 @@
"count": 1
}
},
"web/app/components/header/account-dropdown/index.tsx": {
"no-restricted-globals": {
"count": 3
}
},
"web/app/components/header/account-setting/data-source-page-new/card.tsx": {
"ts/no-explicit-any": {
"count": 2
@ -3151,6 +3238,11 @@
"count": 2
}
},
"web/app/components/signin/countdown.tsx": {
"no-restricted-globals": {
"count": 4
}
},
"web/app/components/tools/edit-custom-collection-modal/get-schema.tsx": {
"no-restricted-imports": {
"count": 1
@ -3321,6 +3413,16 @@
"count": 1
}
},
"web/app/components/workflow/block-selector/featured-tools.tsx": {
"no-restricted-properties": {
"count": 2
}
},
"web/app/components/workflow/block-selector/featured-triggers.tsx": {
"no-restricted-properties": {
"count": 2
}
},
"web/app/components/workflow/block-selector/hooks.ts": {
"react/set-state-in-effect": {
"count": 1
@ -3342,6 +3444,9 @@
}
},
"web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx": {
"no-restricted-properties": {
"count": 3
},
"react/set-state-in-effect": {
"count": 1
}
@ -3463,6 +3568,11 @@
"count": 1
}
},
"web/app/components/workflow/hooks/use-workflow-canvas-maximize.ts": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/workflow/hooks/use-workflow-interactions.ts": {
"no-barrel-files/no-barrel-files": {
"count": 5
@ -3693,6 +3803,9 @@
}
},
"web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx": {
"no-restricted-globals": {
"count": 1
},
"react/set-state-in-effect": {
"count": 2
},
@ -4193,6 +4306,11 @@
"count": 2
}
},
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx": {
"no-restricted-properties": {
"count": 3
}
},
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx": {
"react-refresh/only-export-components": {
"count": 2
@ -4324,6 +4442,11 @@
"count": 9
}
},
"web/app/components/workflow/nodes/question-classifier/components/class-list.tsx": {
"no-restricted-properties": {
"count": 2
}
},
"web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts": {
"ts/no-explicit-any": {
"count": 8
@ -4508,6 +4631,11 @@
"count": 5
}
},
"web/app/components/workflow/note-node/hooks.ts": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/workflow/note-node/note-editor/index.tsx": {
"no-barrel-files/no-barrel-files": {
"count": 3
@ -4535,6 +4663,9 @@
}
},
"web/app/components/workflow/operator/hooks.ts": {
"no-restricted-globals": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@ -4616,6 +4747,11 @@
"count": 12
}
},
"web/app/components/workflow/panel/debug-and-preview/index.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/workflow/panel/env-panel/variable-modal.tsx": {
"no-restricted-imports": {
"count": 1
@ -4750,6 +4886,11 @@
"count": 2
}
},
"web/app/components/workflow/store/workflow/layout-slice.ts": {
"no-restricted-properties": {
"count": 1
}
},
"web/app/components/workflow/store/workflow/workflow-draft-slice.ts": {
"ts/no-explicit-any": {
"count": 1
@ -4826,6 +4967,11 @@
"count": 2
}
},
"web/app/components/workflow/variable-inspect/index.tsx": {
"no-restricted-globals": {
"count": 1
}
},
"web/app/components/workflow/variable-inspect/left.tsx": {
"ts/no-explicit-any": {
"count": 1
@ -4926,6 +5072,9 @@
}
},
"web/app/reset-password/page.tsx": {
"no-restricted-globals": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
@ -4941,6 +5090,9 @@
}
},
"web/app/signin/components/mail-and-code-auth.tsx": {
"no-restricted-globals": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
@ -4999,11 +5151,22 @@
}
},
"web/context/hooks/use-trigger-events-limit-modal.ts": {
"no-restricted-globals": {
"count": 2
},
"react/set-state-in-effect": {
"count": 3
}
},
"web/context/modal-context-provider.tsx": {
"no-restricted-globals": {
"count": 2
}
},
"web/context/provider-context-provider.tsx": {
"no-restricted-globals": {
"count": 2
},
"ts/no-explicit-any": {
"count": 1
}
@ -5026,6 +5189,11 @@
"count": 1
}
},
"web/hooks/use-import-dsl.ts": {
"no-restricted-globals": {
"count": 2
}
},
"web/hooks/use-metadata.ts": {
"ts/no-explicit-any": {
"count": 1
@ -5318,6 +5486,11 @@
"count": 1
}
},
"web/service/refresh-token.ts": {
"no-restricted-properties": {
"count": 7
}
},
"web/service/share.ts": {
"erasable-syntax-only/enums": {
"count": 1
@ -5476,6 +5649,9 @@
}
},
"web/service/webapp-auth.ts": {
"no-restricted-globals": {
"count": 6
},
"no-restricted-imports": {
"count": 1
}

View File

@ -4,7 +4,6 @@ import type { InstalledApp } from '@/models/explore'
import type { AppData, ConversationItem } from '@/models/share'
import type { HumanInputFilledFormData, HumanInputFormData } from '@/types/workflow'
import { toast } from '@langgenius/dify-ui/toast'
import { useLocalStorageState } from 'ahooks'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -13,6 +12,7 @@ import { getProcessedFilesFromResponse } from '@/app/components/base/file-upload
import { InputVarType } from '@/app/components/workflow/types'
import { useWebAppStore } from '@/context/web-app-context'
import { useAppFavicon } from '@/hooks/use-app-favicon'
import { useLocalStorage } from '@/hooks/use-local-storage'
import { changeLanguage } from '@/i18n-config/client'
import { AppSourceType, delConversation, pinConversation, renameConversation, unpinConversation, updateFeedback } from '@/service/share'
import { useInvalidateShareConversations, useShareChatList, useShareConversationName, useShareConversations } from '@/service/use-share'
@ -141,9 +141,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
}
}
}, [appId, setSidebarCollapseState])
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {
defaultValue: {},
})
const [conversationIdInfo, setConversationIdInfo] = useLocalStorage<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {})
const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId])
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
if (appId) {

View File

@ -4,7 +4,6 @@ import type { InputValueTypes } from '@/app/components/share/text-generation/typ
import type { Locale } from '@/i18n-config'
import type { AppData, ConversationItem } from '@/models/share'
import { toast } from '@langgenius/dify-ui/toast'
import { useLocalStorageState } from 'ahooks'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -12,6 +11,7 @@ import { useTranslation } from 'react-i18next'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import { InputVarType } from '@/app/components/workflow/types'
import { useWebAppStore } from '@/context/web-app-context'
import { useLocalStorage } from '@/hooks/use-local-storage'
import { changeLanguage } from '@/i18n-config/client'
import { AppSourceType, updateFeedback } from '@/service/share'
import { useInvalidateShareConversations, useShareChatList, useShareConversationName, useShareConversations } from '@/service/use-share'
@ -102,9 +102,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
}
setLanguageFromParams()
}, [appInfo])
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {
defaultValue: {},
})
const [conversationIdInfo, setConversationIdInfo] = useLocalStorage<Record<string, Record<string, string>>>(CONVERSATION_ID_INFO, {})
const removeConversationIdInfo = useCallback((appId: string) => {
setConversationIdInfo((prev) => {
const newInfo = { ...prev }

View File

@ -1,5 +1,5 @@
import type { SearchParams } from './types'
import { useDebounceFn, useLocalStorageState } from 'ahooks'
import { useDebounceFn } from 'ahooks'
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
@ -85,17 +85,10 @@ const useEducationReverifyNotice = ({
// const [educationInfo, setEducationInfo] = useState<{ is_student: boolean, allow_refresh: boolean, expire_at: number | null } | null>(null)
// const isLoading = !educationInfo
const { educationAccountExpireAt, allowRefreshEducationVerify, isLoadingEducationAccountInfo: isLoading } = useProviderContext()
const [prevExpireAt, setPrevExpireAt] = useLocalStorageState<number | undefined>('education-reverify-prev-expire-at', {
defaultValue: 0,
})
const [reverifyHasNoticed, setReverifyHasNoticed] = useLocalStorageState<boolean | undefined>('education-reverify-has-noticed', {
defaultValue: false,
})
const [expiredHasNoticed, setExpiredHasNoticed] = useLocalStorageState<boolean | undefined>('education-expired-has-noticed', {
defaultValue: false,
})
const [prevExpireAt, setPrevExpireAt] = useLocalStorage<number>('education-reverify-prev-expire-at', 0)
const [reverifyHasNoticed, setReverifyHasNoticed] = useLocalStorage<boolean>('education-reverify-has-noticed', false)
const [expiredHasNoticed, setExpiredHasNoticed] = useLocalStorage<boolean>('education-expired-has-noticed', false)
/* eslint-disable react/set-state-in-effect -- this persists education notice acknowledgement after provider metadata changes. */
useEffect(() => {
if (isLoading || !timezone)
return
@ -124,7 +117,6 @@ const useEducationReverifyNotice = ({
}
}
}, [allowRefreshEducationVerify, timezone])
/* eslint-enable react/set-state-in-effect */
return {
isLoading,

View File

@ -179,4 +179,43 @@ export default antfu(
}],
},
},
{
name: 'dify/restricted-local-storage-access',
files: [GLOB_TS, GLOB_TSX],
ignores: [
...GLOB_TESTS,
'vitest.setup.ts',
'instrumentation-client.ts',
'hooks/use-local-storage/index.ts',
],
rules: {
'no-restricted-globals': [
'error',
{
name: 'localStorage',
message: 'Do not use localStorage directly. Use @/hooks/use-local-storage instead.',
},
],
'no-restricted-properties': [
'error',
{
object: 'window',
property: 'localStorage',
message: 'Do not use window.localStorage directly. Use @/hooks/use-local-storage instead.',
},
{
object: 'globalThis',
property: 'localStorage',
message: 'Do not use globalThis.localStorage directly. Use @/hooks/use-local-storage instead.',
},
],
'no-restricted-syntax': [
'error',
{
selector: 'ImportDeclaration[source.value="ahooks"] ImportSpecifier[imported.name="useLocalStorageState"]',
message: 'Do not use ahooks useLocalStorageState. Use @/hooks/use-local-storage instead.',
},
],
},
},
)