feat: add data limit warning notification (#8866)

* feat: add disk space checking functionality

- Introduced a new IPC channel for retrieving disk information.
- Integrated the 'check-disk-space' package to fetch available and total disk space.
- Updated the preload API to expose the new disk info retrieval method to the renderer.

* feat: implement disk space warning and data limit checks

- Added functionality to check available disk space and display warnings when storage is low.
- Updated IPC methods to pass directory paths for disk info retrieval.
- Introduced periodic checks for app data disk quota and internal storage quota.
- Enhanced user notifications with localized messages for low storage warnings.

* fix: enhance disk space warning logic and improve logging

- Added additional conditions for displaying disk space warnings based on free percentage thresholds.
- Improved logging format for app data disk quota, providing clearer output in GB.
- Refactored the checkDataLimit function to be asynchronous for better performance.

* format code

* update log format

* fix: improve error handling and logging in disk quota checks

- Added try-catch block in checkAppDataDiskQuota to handle potential errors when retrieving disk information.
- Ensured that errors are logged for better debugging and visibility.
- Updated checkDataLimit to await the checkAppDataDiskQuota function for proper asynchronous handling.

* fix comments

* fix: remove redundant appStorageQuota message from localization files

* lint

* fix: enhance disk space warning logic for USB disks

- Added a condition to warn users when free space on USB disks falls below 5% of total capacity.
- Improved the existing logic for displaying disk space warnings based on total disk size thresholds.

* update i18n

* Refactor data limit notification logic and update i18n messages for disk space warnings. Adjusted check intervals and improved toast notifications for low disk space alerts.

* Fix disk quota check logic in useDataLimit hook to correctly compare free space against 1GB threshold.

* refactor: update styles and improve navbar handling

- Removed unnecessary margin-bottom style from bubble markdown.
- Adjusted margin in Prompt component for better layout.
- Enhanced useAppInit hook to include navbar position logic for background styling.
- Added alignment to ErrorBlock alert for improved visual consistency.

* refactor: relocate checkDataLimit function to utils and update import in useAppInit hook

- Moved checkDataLimit function from useDataLimit hook to utils for better organization.
- Updated import path in useAppInit to reflect the new location of checkDataLimit.
- Removed the now obsolete useDataLimit hook file.

* refactor: update getDiskInfo API to specify return type

- Enhanced getDiskInfo function to explicitly define the return type as a Promise containing disk information or null.

* lint err

* fix: handle null response from getDiskInfo in checkAppDataDiskQuota

- Added a check for null response from getDiskInfo to prevent errors.
- Updated the logic to extract the free disk space only if diskInfo is valid.

---------

Co-authored-by: kangfenmao <kangfenmao@qq.com>
This commit is contained in:
beyondkmp 2025-09-11 21:04:20 +08:00 committed by GitHub
parent 6104b7803b
commit 871565c687
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 240 additions and 0 deletions

View File

@ -220,6 +220,7 @@
"axios": "^1.7.3", "axios": "^1.7.3",
"browser-image-compression": "^2.0.2", "browser-image-compression": "^2.0.2",
"chardet": "^2.1.0", "chardet": "^2.1.0",
"check-disk-space": "3.4.0",
"cheerio": "^1.1.2", "cheerio": "^1.1.2",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"cli-progress": "^3.12.0", "cli-progress": "^3.12.0",

View File

@ -35,6 +35,7 @@ export enum IpcChannel {
App_InstallBunBinary = 'app:install-bun-binary', App_InstallBunBinary = 'app:install-bun-binary',
App_LogToMain = 'app:log-to-main', App_LogToMain = 'app:log-to-main',
App_SaveData = 'app:save-data', App_SaveData = 'app:save-data',
App_GetDiskInfo = 'app:get-disk-info',
App_SetFullScreen = 'app:set-full-screen', App_SetFullScreen = 'app:set-full-screen',
App_IsFullScreen = 'app:is-full-screen', App_IsFullScreen = 'app:is-full-screen',

View File

@ -12,6 +12,7 @@ import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core'
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, UpgradeChannel } from '@shared/config/constant' import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, UpgradeChannel } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { FileMetadata, Provider, Shortcut, ThemeMode } from '@types' import { FileMetadata, Provider, Shortcut, ThemeMode } from '@types'
import checkDiskSpace from 'check-disk-space'
import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron' import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron'
import { Notification } from 'src/renderer/src/types/notification' import { Notification } from 'src/renderer/src/types/notification'
@ -783,6 +784,20 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
addStreamMessage(spanId, modelName, context, msg) addStreamMessage(spanId, modelName, context, msg)
) )
ipcMain.handle(IpcChannel.App_GetDiskInfo, async (_, directoryPath: string) => {
try {
const diskSpace = await checkDiskSpace(directoryPath) // { free, size } in bytes
logger.debug('disk space', diskSpace)
const { free, size } = diskSpace
return {
free,
size
}
} catch (error) {
logger.error('check disk space error', error as Error)
return null
}
})
// API Server // API Server
apiServerService.registerIpcHandlers() apiServerService.registerIpcHandlers()

View File

@ -44,6 +44,8 @@ export function tracedInvoke(channel: string, spanContext: SpanContext | undefin
// Custom APIs for renderer // Custom APIs for renderer
const api = { const api = {
getAppInfo: () => ipcRenderer.invoke(IpcChannel.App_Info), getAppInfo: () => ipcRenderer.invoke(IpcChannel.App_Info),
getDiskInfo: (directoryPath: string): Promise<{ free: number; size: number } | null> =>
ipcRenderer.invoke(IpcChannel.App_GetDiskInfo, directoryPath),
reload: () => ipcRenderer.invoke(IpcChannel.App_Reload), reload: () => ipcRenderer.invoke(IpcChannel.App_Reload),
setProxy: (proxy: string | undefined, bypassRules?: string) => setProxy: (proxy: string | undefined, bypassRules?: string) =>
ipcRenderer.invoke(IpcChannel.App_Proxy, proxy, bypassRules), ipcRenderer.invoke(IpcChannel.App_Proxy, proxy, bypassRules),

View File

@ -12,6 +12,7 @@ import { handleSaveData } from '@renderer/store'
import { selectMemoryConfig } from '@renderer/store/memory' import { selectMemoryConfig } from '@renderer/store/memory'
import { setAvatar, setFilesPath, setResourcesPath, setUpdateState } from '@renderer/store/runtime' import { setAvatar, setFilesPath, setResourcesPath, setUpdateState } from '@renderer/store/runtime'
import { delay, runAsyncFunction } from '@renderer/utils' import { delay, runAsyncFunction } from '@renderer/utils'
import { checkDataLimit } from '@renderer/utils'
import { defaultLanguage } from '@shared/config/constant' import { defaultLanguage } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { useLiveQuery } from 'dexie-react-hooks' import { useLiveQuery } from 'dexie-react-hooks'
@ -159,4 +160,8 @@ export function useAppInit() {
logger.error('Failed to update memory config:', error) logger.error('Failed to update memory config:', error)
}) })
}, [memoryConfig]) }, [memoryConfig])
useEffect(() => {
checkDataLimit()
}, [])
} }

View File

@ -2650,6 +2650,10 @@
"url": "Joplin Web Clipper Service URL", "url": "Joplin Web Clipper Service URL",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "Disk Space Warning",
"appDataDiskQuotaDescription": "Data directory space is almost full, please clear disk space, otherwise data will be lost"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "Auto Backup", "label": "Auto Backup",

View File

@ -132,6 +132,7 @@
}, },
"title": "API 服务器" "title": "API 服务器"
}, },
"assistants": { "assistants": {
"abbr": "助手", "abbr": "助手",
"clear": { "clear": {
@ -2650,6 +2651,10 @@
"url": "Joplin 剪裁服务监听 URL", "url": "Joplin 剪裁服务监听 URL",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "磁盘空间警告",
"appDataDiskQuotaDescription": "数据目录空间即将用尽, 请清理磁盘空间, 否则会丢失数据"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "自动备份", "label": "自动备份",

View File

@ -2650,6 +2650,10 @@
"url": "Joplin 剪輯服務 URL", "url": "Joplin 剪輯服務 URL",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "磁碟空間警告",
"appDataDiskQuotaDescription": "資料目錄空間即將用盡, 請清理磁碟空間, 否則會丟失數據"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "自動備份", "label": "自動備份",

View File

@ -538,6 +538,10 @@
"tip": "Στη γραμμή εργαλείων των εκτελέσιμων blocks κώδικα θα εμφανίζεται το κουμπί εκτέλεσης· προσέξτε να μην εκτελέσετε επικίνδυνο κώδικα!", "tip": "Στη γραμμή εργαλείων των εκτελέσιμων blocks κώδικα θα εμφανίζεται το κουμπί εκτέλεσης· προσέξτε να μην εκτελέσετε επικίνδυνο κώδικα!",
"title": "Εκτέλεση Κώδικα" "title": "Εκτέλεση Κώδικα"
}, },
"code_fancy_block": {
"label": "[to be translated]:花式代码块",
"tip": "[to be translated]:使用更美观的代码块样式,例如 HTML 卡片"
},
"code_image_tools": { "code_image_tools": {
"label": "Ενεργοποίηση εργαλείου προεπισκόπησης", "label": "Ενεργοποίηση εργαλείου προεπισκόπησης",
"tip": "Ενεργοποίηση εργαλείου προεπισκόπησης για εικόνες που αποδίδονται από blocks κώδικα όπως το mermaid" "tip": "Ενεργοποίηση εργαλείου προεπισκόπησης για εικόνες που αποδίδονται από blocks κώδικα όπως το mermaid"
@ -1741,8 +1745,15 @@
"compress_content": "μείωση πλάτους στήλης", "compress_content": "μείωση πλάτους στήλης",
"compress_content_description": "Ενεργοποιώντας το, θα περιορίζεται ο αριθμός των χαρακτήρων ανά γραμμή, μειώνοντας την οθόνη που εμφανίζεται", "compress_content_description": "Ενεργοποιώντας το, θα περιορίζεται ο αριθμός των χαρακτήρων ανά γραμμή, μειώνοντας την οθόνη που εμφανίζεται",
"default_font": "προεπιλεγμένη γραμματοσειρά", "default_font": "προεπιλεγμένη γραμματοσειρά",
"font_size": "[to be translated]:字体大小",
"font_size_description": "[to be translated]:调整字体大小以获得更好的阅读体验 (10-30px)",
"font_size_large": "[to be translated]:大",
"font_size_medium": "[to be translated]:中",
"font_size_small": "[to be translated]:小",
"font_title": "ρυθμίσεις γραμματοσειράς", "font_title": "ρυθμίσεις γραμματοσειράς",
"serif_font": "σειρά γραμματοσειρών", "serif_font": "σειρά γραμματοσειρών",
"show_table_of_contents": "[to be translated]:显示目录大纲",
"show_table_of_contents_description": "[to be translated]:显示目录大纲侧边栏,方便文档内导航",
"title": "ρυθμίσεις εμφάνισης" "title": "ρυθμίσεις εμφάνισης"
}, },
"editor": { "editor": {
@ -2639,6 +2650,10 @@
"url": "URL υπηρεσίας περικοπής Joplin", "url": "URL υπηρεσίας περικοπής Joplin",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "Προειδοποίηση χώρου δίσκου",
"appDataDiskQuotaDescription": "Ο κατάλογος δεδομένων της εφαρμογής είναι σχεδόν γεμάτος, παρακαλώ απομακρύνετε τον χώρο δίσκου, αλλιώς θα χαθούν τα δεδομένα"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "Αυτόματο αντίγραφο ασφαλείας", "label": "Αυτόματο αντίγραφο ασφαλείας",

View File

@ -538,6 +538,10 @@
"tip": "En la barra de herramientas de bloques de código ejecutables se mostrará un botón de ejecución. ¡Tenga cuidado en no ejecutar código peligroso!", "tip": "En la barra de herramientas de bloques de código ejecutables se mostrará un botón de ejecución. ¡Tenga cuidado en no ejecutar código peligroso!",
"title": "Ejecución de Código" "title": "Ejecución de Código"
}, },
"code_fancy_block": {
"label": "[to be translated]:花式代码块",
"tip": "[to be translated]:使用更美观的代码块样式,例如 HTML 卡片"
},
"code_image_tools": { "code_image_tools": {
"label": "Habilitar herramienta de vista previa", "label": "Habilitar herramienta de vista previa",
"tip": "Habilitar herramientas de vista previa para imágenes renderizadas de bloques de código como mermaid" "tip": "Habilitar herramientas de vista previa para imágenes renderizadas de bloques de código como mermaid"
@ -1741,8 +1745,15 @@
"compress_content": "reducir el ancho de la columna", "compress_content": "reducir el ancho de la columna",
"compress_content_description": "Al activarlo, se limitará el número de caracteres por línea, reduciendo el contenido mostrado en pantalla.", "compress_content_description": "Al activarlo, se limitará el número de caracteres por línea, reduciendo el contenido mostrado en pantalla.",
"default_font": "fuente predeterminada", "default_font": "fuente predeterminada",
"font_size": "[to be translated]:字体大小",
"font_size_description": "[to be translated]:调整字体大小以获得更好的阅读体验 (10-30px)",
"font_size_large": "[to be translated]:大",
"font_size_medium": "[to be translated]:中",
"font_size_small": "[to be translated]:小",
"font_title": "Configuración de fuente", "font_title": "Configuración de fuente",
"serif_font": "fuente serif", "serif_font": "fuente serif",
"show_table_of_contents": "[to be translated]:显示目录大纲",
"show_table_of_contents_description": "[to be translated]:显示目录大纲侧边栏,方便文档内导航",
"title": "configuración de visualización" "title": "configuración de visualización"
}, },
"editor": { "editor": {
@ -2639,6 +2650,10 @@
"url": "URL a la que escucha el servicio de recorte de Joplin", "url": "URL a la que escucha el servicio de recorte de Joplin",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "Advertencia de espacio en disco",
"appDataDiskQuotaDescription": "El espacio de almacenamiento de datos está casi lleno, por favor, limpie el espacio en disco, de lo contrario, se perderán los datos"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "Copia de seguridad automática", "label": "Copia de seguridad automática",

View File

@ -538,6 +538,10 @@
"tip": "Une bouton d'exécution s'affichera dans la barre d'outils des blocs de code exécutables. Attention à ne pas exécuter de code dangereux !", "tip": "Une bouton d'exécution s'affichera dans la barre d'outils des blocs de code exécutables. Attention à ne pas exécuter de code dangereux !",
"title": "Exécution de code" "title": "Exécution de code"
}, },
"code_fancy_block": {
"label": "[to be translated]:花式代码块",
"tip": "[to be translated]:使用更美观的代码块样式,例如 HTML 卡片"
},
"code_image_tools": { "code_image_tools": {
"label": "Activer l'outil d'aperçu", "label": "Activer l'outil d'aperçu",
"tip": "Activer les outils de prévisualisation pour les images rendues des blocs de code tels que mermaid" "tip": "Activer les outils de prévisualisation pour les images rendues des blocs de code tels que mermaid"
@ -1741,8 +1745,15 @@
"compress_content": "réduire la largeur des colonnes", "compress_content": "réduire la largeur des colonnes",
"compress_content_description": "L'activation limitera le nombre de caractères par ligne, réduisant ainsi le contenu affiché à l'écran.", "compress_content_description": "L'activation limitera le nombre de caractères par ligne, réduisant ainsi le contenu affiché à l'écran.",
"default_font": "police par défaut", "default_font": "police par défaut",
"font_size": "[to be translated]:字体大小",
"font_size_description": "[to be translated]:调整字体大小以获得更好的阅读体验 (10-30px)",
"font_size_large": "[to be translated]:大",
"font_size_medium": "[to be translated]:中",
"font_size_small": "[to be translated]:小",
"font_title": "paramétrage des polices", "font_title": "paramétrage des polices",
"serif_font": "police à empattements", "serif_font": "police à empattements",
"show_table_of_contents": "[to be translated]:显示目录大纲",
"show_table_of_contents_description": "[to be translated]:显示目录大纲侧边栏,方便文档内导航",
"title": "Paramètres d'affichage" "title": "Paramètres d'affichage"
}, },
"editor": { "editor": {
@ -2639,6 +2650,10 @@
"url": "URL surveillée par le service de découpage de Joplin", "url": "URL surveillée par le service de découpage de Joplin",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "Avertissement d'espace sur le disque",
"appDataDiskQuotaDescription": "L'espace de stockage des données est presque plein, veuillez nettoyer l'espace sur le disque, sinon les données seront perdues"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "Sauvegarde automatique", "label": "Sauvegarde automatique",

View File

@ -538,6 +538,10 @@
"tip": "実行可能なコードブロックのツールバーには実行ボタンが表示されます。危険なコードを実行しないでください!", "tip": "実行可能なコードブロックのツールバーには実行ボタンが表示されます。危険なコードを実行しないでください!",
"title": "コード実行" "title": "コード実行"
}, },
"code_fancy_block": {
"label": "[to be translated]:花式代码块",
"tip": "[to be translated]:使用更美观的代码块样式,例如 HTML 卡片"
},
"code_image_tools": { "code_image_tools": {
"label": "プレビューツールを有効にする", "label": "プレビューツールを有効にする",
"tip": "mermaid などのコードブロックから生成された画像に対してプレビューツールを有効にする" "tip": "mermaid などのコードブロックから生成された画像に対してプレビューツールを有効にする"
@ -1741,8 +1745,15 @@
"compress_content": "バーの幅を減らします", "compress_content": "バーの幅を減らします",
"compress_content_description": "有効にすると、1行あたりの単語数が制限され、画面に表示されるコンテンツが減少します。", "compress_content_description": "有効にすると、1行あたりの単語数が制限され、画面に表示されるコンテンツが減少します。",
"default_font": "デフォルトフォント", "default_font": "デフォルトフォント",
"font_size": "[to be translated]:字体大小",
"font_size_description": "[to be translated]:调整字体大小以获得更好的阅读体验 (10-30px)",
"font_size_large": "[to be translated]:大",
"font_size_medium": "[to be translated]:中",
"font_size_small": "[to be translated]:小",
"font_title": "フォント設定", "font_title": "フォント設定",
"serif_font": "セリフフォント", "serif_font": "セリフフォント",
"show_table_of_contents": "[to be translated]:显示目录大纲",
"show_table_of_contents_description": "[to be translated]:显示目录大纲侧边栏,方便文档内导航",
"title": "見せる" "title": "見せる"
}, },
"editor": { "editor": {
@ -2639,6 +2650,10 @@
"url": "Joplin 剪輯服務 URL", "url": "Joplin 剪輯服務 URL",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "ディスク容量警告",
"appDataDiskQuotaDescription": "データディレクトリの容量がほぼ満杯になっており、新しいデータの保存ができなくなる可能性があります。まずデータをバックアップしてから、ディスク容量を整理してください。"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "自動バックアップ", "label": "自動バックアップ",

View File

@ -538,6 +538,10 @@
"tip": "A barra de ferramentas de blocos de código executáveis exibirá um botão de execução; atenção para não executar códigos perigosos!", "tip": "A barra de ferramentas de blocos de código executáveis exibirá um botão de execução; atenção para não executar códigos perigosos!",
"title": "Execução de Código" "title": "Execução de Código"
}, },
"code_fancy_block": {
"label": "[to be translated]:花式代码块",
"tip": "[to be translated]:使用更美观的代码块样式,例如 HTML 卡片"
},
"code_image_tools": { "code_image_tools": {
"label": "Habilitar ferramenta de visualização", "label": "Habilitar ferramenta de visualização",
"tip": "Ativar ferramentas de visualização para imagens renderizadas de blocos de código como mermaid" "tip": "Ativar ferramentas de visualização para imagens renderizadas de blocos de código como mermaid"
@ -1741,8 +1745,15 @@
"compress_content": "reduzir a largura da coluna", "compress_content": "reduzir a largura da coluna",
"compress_content_description": "Ativando isso limitará o número de caracteres por linha, reduzindo o conteúdo exibido na tela.", "compress_content_description": "Ativando isso limitará o número de caracteres por linha, reduzindo o conteúdo exibido na tela.",
"default_font": "fonte padrão", "default_font": "fonte padrão",
"font_size": "[to be translated]:字体大小",
"font_size_description": "[to be translated]:调整字体大小以获得更好的阅读体验 (10-30px)",
"font_size_large": "[to be translated]:大",
"font_size_medium": "[to be translated]:中",
"font_size_small": "[to be translated]:小",
"font_title": "configuração de fonte", "font_title": "configuração de fonte",
"serif_font": "fonte com serifa", "serif_font": "fonte com serifa",
"show_table_of_contents": "[to be translated]:显示目录大纲",
"show_table_of_contents_description": "[to be translated]:显示目录大纲侧边栏,方便文档内导航",
"title": "configurações de exibição" "title": "configurações de exibição"
}, },
"editor": { "editor": {
@ -2639,6 +2650,10 @@
"url": "URL para o qual o serviço de recorte do Joplin está escutando", "url": "URL para o qual o serviço de recorte do Joplin está escutando",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "Aviso de espaço em disco",
"appDataDiskQuotaDescription": "O espaço de armazenamento de dados está quase cheio, por favor, limpe o espaço em disco, caso contrário, os dados serão perdidos"
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "Backup automático", "label": "Backup automático",

View File

@ -538,6 +538,10 @@
"tip": "Выполнение кода в блоке кода возможно, но не рекомендуется выполнять опасный код!", "tip": "Выполнение кода в блоке кода возможно, но не рекомендуется выполнять опасный код!",
"title": "Выполнение кода" "title": "Выполнение кода"
}, },
"code_fancy_block": {
"label": "[to be translated]:花式代码块",
"tip": "[to be translated]:使用更美观的代码块样式,例如 HTML 卡片"
},
"code_image_tools": { "code_image_tools": {
"label": "Включить инструменты предпросмотра", "label": "Включить инструменты предпросмотра",
"tip": "Включить инструменты предпросмотра для изображений, сгенерированных из блоков кода (например mermaid)" "tip": "Включить инструменты предпросмотра для изображений, сгенерированных из блоков кода (например mermaid)"
@ -1741,8 +1745,15 @@
"compress_content": "Уменьшить ширину стержня", "compress_content": "Уменьшить ширину стержня",
"compress_content_description": "При включении он ограничит количество слов на строку, уменьшая содержимое, отображаемое на экране.", "compress_content_description": "При включении он ограничит количество слов на строку, уменьшая содержимое, отображаемое на экране.",
"default_font": "По умолчанию шрифт", "default_font": "По умолчанию шрифт",
"font_size": "[to be translated]:字体大小",
"font_size_description": "[to be translated]:调整字体大小以获得更好的阅读体验 (10-30px)",
"font_size_large": "[to be translated]:大",
"font_size_medium": "[to be translated]:中",
"font_size_small": "[to be translated]:小",
"font_title": "Настройки шрифта", "font_title": "Настройки шрифта",
"serif_font": "Serif Font", "serif_font": "Serif Font",
"show_table_of_contents": "[to be translated]:显示目录大纲",
"show_table_of_contents_description": "[to be translated]:显示目录大纲侧边栏,方便文档内导航",
"title": "показывать" "title": "показывать"
}, },
"editor": { "editor": {
@ -2639,6 +2650,10 @@
"url": "URL Joplin", "url": "URL Joplin",
"url_placeholder": "http://127.0.0.1:41184/" "url_placeholder": "http://127.0.0.1:41184/"
}, },
"limit": {
"appDataDiskQuota": "Предупреждение о пространстве на диске",
"appDataDiskQuotaDescription": "Каталог данных почти заполнен, что может привести к невозможности сохранения новых данных. Сначала создайте резервную копию данных, затем освободите дисковое пространство."
},
"local": { "local": {
"autoSync": { "autoSync": {
"label": "Автоматическое резервное копирование", "label": "Автоматическое резервное копирование",

View File

@ -0,0 +1,104 @@
import { loggerService } from '@logger'
import { AppInfo } from '@renderer/types'
import { GB, MB } from '@shared/config/constant'
import { t } from 'i18next'
const logger = loggerService.withContext('useDataLimit')
const CHECK_INTERVAL_NORMAL = 1000 * 60 * 10 // 10 minutes
const CHECK_INTERVAL_WARNING = 1000 * 60 * 1 // 1 minute when warning is active
let currentInterval: NodeJS.Timeout | null = null
let currentToastId: string | null = null
async function checkAppStorageQuota() {
try {
const { usage, quota } = await navigator.storage.estimate()
if (usage && quota) {
const usageInMB = (usage / MB).toFixed(2)
const quotaInMB = (quota / MB).toFixed(2)
const usagePercentage = (usage / quota) * 100
logger.info(`App storage quota: Used ${usageInMB} MB / Total ${quotaInMB} MB (${usagePercentage.toFixed(2)}%)`)
// if usage percentage is greater than 95%,
// warn user to clean up app internal data
if (usagePercentage >= 95) {
return true
}
}
} catch (error) {
logger.error('Failed to get storage quota:', error as Error)
}
return false
}
async function checkAppDataDiskQuota(appDataPath: string) {
try {
const diskInfo = await window.api.getDiskInfo(appDataPath)
if (!diskInfo) {
return false
}
const { free } = diskInfo
logger.info(`App data disk quota: Free ${free} GB`)
// if free is less than 1GB, return true
return free < 1 * GB
} catch (error) {
logger.error('Failed to get app data disk quota:', error as Error)
}
return false
}
export async function checkDataLimit() {
const check = async () => {
let isStorageQuotaLow = false
let isAppDataDiskQuotaLow = false
isStorageQuotaLow = await checkAppStorageQuota()
const appInfo: AppInfo = await window.api.getAppInfo()
if (appInfo?.appDataPath) {
isAppDataDiskQuotaLow = await checkAppDataDiskQuota(appInfo.appDataPath)
}
const shouldShowWarning = isStorageQuotaLow || isAppDataDiskQuotaLow
// Show or hide toast based on warning state
if (shouldShowWarning && !currentToastId) {
// Show persistent toast without close button
const toastId = window.toast.warning({
title: t('settings.data.limit.appDataDiskQuota'),
description: t('settings.data.limit.appDataDiskQuotaDescription'),
timeout: 0, // Never auto-dismiss
hideCloseButton: true // Hide close button so user cannot dismiss
})
currentToastId = toastId
// Switch to warning mode with shorter interval
logger.info('Disk space low, switching to 1-minute check interval')
if (currentInterval) {
clearInterval(currentInterval)
}
currentInterval = setInterval(check, CHECK_INTERVAL_WARNING)
} else if (!shouldShowWarning && currentToastId) {
// Dismiss toast when space is recovered
window.toast.closeToast(currentToastId)
currentToastId = null
// Switch back to normal mode
logger.info('Disk space recovered, switching back to 10-minute check interval')
if (currentInterval) {
clearInterval(currentInterval)
}
currentInterval = setInterval(check, CHECK_INTERVAL_NORMAL)
}
}
// Initial check
check()
// Set up initial interval (normal mode)
if (!currentInterval) {
currentInterval = setInterval(check, CHECK_INTERVAL_NORMAL)
}
}

View File

@ -222,6 +222,7 @@ export function uniqueObjectArray<T>(array: T[]): T[] {
export * from './api' export * from './api'
export * from './collection' export * from './collection'
export * from './dataLimit'
export * from './file' export * from './file'
export * from './image' export * from './image'
export * from './json' export * from './json'

View File

@ -13150,6 +13150,7 @@ __metadata:
axios: "npm:^1.7.3" axios: "npm:^1.7.3"
browser-image-compression: "npm:^2.0.2" browser-image-compression: "npm:^2.0.2"
chardet: "npm:^2.1.0" chardet: "npm:^2.1.0"
check-disk-space: "npm:3.4.0"
cheerio: "npm:^1.1.2" cheerio: "npm:^1.1.2"
chokidar: "npm:^4.0.3" chokidar: "npm:^4.0.3"
cli-progress: "npm:^3.12.0" cli-progress: "npm:^3.12.0"
@ -14612,6 +14613,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"check-disk-space@npm:3.4.0":
version: 3.4.0
resolution: "check-disk-space@npm:3.4.0"
checksum: 10c0/cc39c91e1337e974fb5069c2fbd9eb92aceca6e35f3da6863a4eada58f15c1bf6970055bffed1e41c15cde1fd0ad2580bb99bef8275791ed56d69947f8657aa5
languageName: node
linkType: hard
"check-error@npm:^2.1.1": "check-error@npm:^2.1.1":
version: 2.1.1 version: 2.1.1
resolution: "check-error@npm:2.1.1" resolution: "check-error@npm:2.1.1"