264 lines
9.2 KiB
TypeScript
264 lines
9.2 KiB
TypeScript
import { createFileRoute } from '@tanstack/react-router'
|
|
import { route } from '@/constants/routes'
|
|
import SettingsMenu from '@/containers/SettingsMenu'
|
|
import HeaderPage from '@/containers/HeaderPage'
|
|
import { Switch } from '@/components/ui/switch'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardItem } from '@/containers/Card'
|
|
import LanguageSwitcher from '@/containers/LanguageSwitcher'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { useGeneralSetting } from '@/hooks/useGeneralSetting'
|
|
import { useEffect, useState } from 'react'
|
|
import { open } from '@tauri-apps/plugin-dialog'
|
|
|
|
import {
|
|
Dialog,
|
|
DialogClose,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from '@/components/ui/dialog'
|
|
import {
|
|
factoryReset,
|
|
getJanDataFolder,
|
|
relocateJanDataFolder,
|
|
} from '@/services/app'
|
|
import { IconFolder, IconLogs } from '@tabler/icons-react'
|
|
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
|
import { windowKey } from '@/constants/windows'
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export const Route = createFileRoute(route.settings.general as any)({
|
|
component: General,
|
|
})
|
|
|
|
function General() {
|
|
const { t } = useTranslation()
|
|
const { spellCheckChatInput, setSpellCheckChatInput } = useGeneralSetting()
|
|
const [janDataFolder, setJanDataFolder] = useState<string | undefined>()
|
|
|
|
useEffect(() => {
|
|
const fetchDataFolder = async () => {
|
|
const path = await getJanDataFolder()
|
|
setJanDataFolder(path)
|
|
}
|
|
|
|
fetchDataFolder()
|
|
}, [])
|
|
|
|
const resetApp = async () => {
|
|
// TODO: Loading indicator
|
|
await factoryReset()
|
|
}
|
|
|
|
const handleOpenLogs = async () => {
|
|
try {
|
|
// Check if logs window already exists
|
|
const existingWindow = await WebviewWindow.getByLabel(
|
|
windowKey.logsAppWindow
|
|
)
|
|
|
|
if (existingWindow) {
|
|
// If window exists, focus it
|
|
await existingWindow.setFocus()
|
|
console.log('Focused existing logs window')
|
|
} else {
|
|
// Create a new logs window using Tauri v2 WebviewWindow API
|
|
const logsWindow = new WebviewWindow(windowKey.logsAppWindow, {
|
|
url: route.appLogs,
|
|
title: 'App Logs - Jan',
|
|
width: 800,
|
|
height: 600,
|
|
resizable: true,
|
|
center: true,
|
|
})
|
|
|
|
// Listen for window creation
|
|
logsWindow.once('tauri://created', () => {
|
|
console.log('Logs window created')
|
|
})
|
|
|
|
// Listen for window errors
|
|
logsWindow.once('tauri://error', (e) => {
|
|
console.error('Error creating logs window:', e)
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to open logs window:', error)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col h-full">
|
|
<HeaderPage>
|
|
<h1 className="font-medium">{t('common.settings')}</h1>
|
|
</HeaderPage>
|
|
<div className="flex h-full w-full">
|
|
<SettingsMenu />
|
|
<div className="p-4 w-full h-[calc(100%-32px)] overflow-y-auto">
|
|
<div className="flex flex-col justify-between gap-4 gap-y-3 w-full">
|
|
{/* General */}
|
|
<Card title={t('common.general')}>
|
|
<CardItem
|
|
title="App Version"
|
|
actions={
|
|
<span className="text-main-view-fg/80">v{VERSION}</span>
|
|
}
|
|
/>
|
|
<CardItem
|
|
title={t('settings.general.autoDownload', {
|
|
ns: 'settings',
|
|
})}
|
|
actions={<Switch />}
|
|
/>
|
|
<CardItem
|
|
title={t('common.language')}
|
|
actions={<LanguageSwitcher />}
|
|
/>
|
|
</Card>
|
|
|
|
{/* Data folder */}
|
|
<Card title={t('common.dataFolder')}>
|
|
<CardItem
|
|
title={t('settings.dataFolder.appData', {
|
|
ns: 'settings',
|
|
})}
|
|
align="start"
|
|
description={
|
|
<>
|
|
<span>
|
|
{t('settings.dataFolder.appDataDesc', {
|
|
ns: 'settings',
|
|
})}
|
|
</span>
|
|
<span
|
|
title={janDataFolder}
|
|
className="bg-main-view-fg/10 text-xs mt-1 px-1 py-0.5 rounded-sm text-main-view-fg/80 line-clamp-1"
|
|
>
|
|
{janDataFolder}
|
|
</span>
|
|
</>
|
|
}
|
|
actions={
|
|
<Button
|
|
variant="link"
|
|
size="sm"
|
|
className="hover:no-underline"
|
|
title="App Data Folder"
|
|
onClick={async () => {
|
|
const selectedPath = await open({
|
|
multiple: false,
|
|
directory: true,
|
|
defaultPath: janDataFolder,
|
|
})
|
|
if (selectedPath === janDataFolder) return
|
|
if (selectedPath !== null) {
|
|
setJanDataFolder(selectedPath)
|
|
await relocateJanDataFolder(selectedPath)
|
|
window.core?.api?.relaunch()
|
|
// TODO: we need function to move everything into new folder selectedPath
|
|
// eg like this
|
|
// await window.core?.api?.moveDataFolder(selectedPath)
|
|
}
|
|
}}
|
|
>
|
|
<div className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out">
|
|
<IconFolder size={18} className="text-main-view-fg/50" />
|
|
</div>
|
|
</Button>
|
|
}
|
|
/>
|
|
<CardItem
|
|
title={t('settings.dataFolder.appLogs', {
|
|
ns: 'settings',
|
|
})}
|
|
description="View detailed logs of the App"
|
|
actions={
|
|
<Button
|
|
variant="link"
|
|
size="sm"
|
|
onClick={handleOpenLogs}
|
|
title="App Logs"
|
|
>
|
|
{/* Open Logs */}
|
|
<div className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out">
|
|
<IconLogs size={18} className="text-main-view-fg/50" />
|
|
</div>
|
|
</Button>
|
|
}
|
|
/>
|
|
</Card>
|
|
|
|
{/* Other */}
|
|
<Card title={t('common.others')}>
|
|
<CardItem
|
|
title={t('settings.others.spellCheck', {
|
|
ns: 'settings',
|
|
})}
|
|
description={t('settings.others.spellCheckDesc', {
|
|
ns: 'settings',
|
|
})}
|
|
actions={
|
|
<Switch
|
|
checked={spellCheckChatInput}
|
|
onCheckedChange={(e) => setSpellCheckChatInput(e)}
|
|
/>
|
|
}
|
|
/>
|
|
<CardItem
|
|
title={t('settings.others.resetFactory', {
|
|
ns: 'settings',
|
|
})}
|
|
description={t('settings.others.resetFactoryDesc', {
|
|
ns: 'settings',
|
|
})}
|
|
actions={
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button variant="destructive" size="sm">
|
|
{t('common.reset')}
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Factory Reset</DialogTitle>
|
|
<DialogDescription>
|
|
Are you sure you want to reset the app to factory
|
|
settings? This action is irreversible and recommended
|
|
only if the application is corrupted.
|
|
</DialogDescription>
|
|
<DialogFooter className="mt-2 flex items-center">
|
|
<DialogClose asChild>
|
|
<Button
|
|
variant="link"
|
|
size="sm"
|
|
className="hover:no-underline"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</DialogClose>
|
|
<DialogClose asChild>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={() => resetApp()}
|
|
>
|
|
Reset
|
|
</Button>
|
|
</DialogClose>
|
|
</DialogFooter>
|
|
</DialogHeader>
|
|
</DialogContent>
|
|
</Dialog>
|
|
}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|