enhancement: fit mobile layout
This commit is contained in:
parent
003598204e
commit
f639ec70d4
@ -1,11 +1,24 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="bg-app">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/images/jan-logo.png" />
|
<link
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/images/jan-logo.png" />
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/images/jan-logo.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/images/jan-logo.png"
|
||||||
|
/>
|
||||||
<link rel="apple-touch-icon" href="/images/jan-logo.png" />
|
<link rel="apple-touch-icon" href="/images/jan-logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover, interactive-widget=resizes-visual"
|
||||||
|
/>
|
||||||
<title>Jan</title>
|
<title>Jan</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -122,7 +122,7 @@ const LeftPanel = () => {
|
|||||||
) {
|
) {
|
||||||
if (currentIsSmallScreen && open) {
|
if (currentIsSmallScreen && open) {
|
||||||
setLeftPanel(false)
|
setLeftPanel(false)
|
||||||
} else if(!open) {
|
} else if (!open) {
|
||||||
setLeftPanel(true)
|
setLeftPanel(true)
|
||||||
}
|
}
|
||||||
prevScreenSizeRef.current = currentIsSmallScreen
|
prevScreenSizeRef.current = currentIsSmallScreen
|
||||||
@ -141,7 +141,7 @@ const LeftPanel = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
}
|
}
|
||||||
}, [setLeftPanel])
|
}, [open, setLeftPanel])
|
||||||
|
|
||||||
const currentPath = useRouterState({
|
const currentPath = useRouterState({
|
||||||
select: (state) => state.location.pathname,
|
select: (state) => state.location.pathname,
|
||||||
@ -184,7 +184,7 @@ const LeftPanel = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop overlay for small screens */}
|
{/* Backdrop overlay for small screens */}
|
||||||
{isSmallScreen && open && (
|
{isSmallScreen && open && !IS_IOS && !IS_ANDROID && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black/50 backdrop-blur z-30"
|
className="fixed inset-0 bg-black/50 backdrop-blur z-30"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -207,7 +207,7 @@ const LeftPanel = () => {
|
|||||||
isResizableContext && 'h-full w-full',
|
isResizableContext && 'h-full w-full',
|
||||||
// Small screen context: fixed positioning and styling
|
// Small screen context: fixed positioning and styling
|
||||||
isSmallScreen &&
|
isSmallScreen &&
|
||||||
'fixed h-[calc(100%-16px)] bg-app z-50 rounded-sm border border-left-panel-fg/10 m-2 px-1 w-48',
|
'fixed h-full pb-[calc(env(safe-area-inset-bottom)+env(safe-area-inset-top))] bg-main-view z-50 md:border border-left-panel-fg/10 px-1 w-full md:w-48',
|
||||||
// Default context: original styling
|
// Default context: original styling
|
||||||
!isResizableContext &&
|
!isResizableContext &&
|
||||||
!isSmallScreen &&
|
!isSmallScreen &&
|
||||||
@ -266,7 +266,8 @@ const LeftPanel = () => {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col',
|
'flex flex-col',
|
||||||
Object.keys(downloads).length > 0 || localDownloadingModels.size > 0
|
Object.keys(downloads).length > 0 ||
|
||||||
|
localDownloadingModels.size > 0
|
||||||
? 'h-[calc(100%-200px)]'
|
? 'h-[calc(100%-200px)]'
|
||||||
: 'h-[calc(100%-140px)]'
|
: 'h-[calc(100%-140px)]'
|
||||||
)}
|
)}
|
||||||
@ -379,7 +380,9 @@ const LeftPanel = () => {
|
|||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent side="bottom" align="end">
|
<DropdownMenuContent side="bottom" align="end">
|
||||||
<DeleteAllThreadsDialog onDeleteAll={deleteAllThreads} />
|
<DeleteAllThreadsDialog
|
||||||
|
onDeleteAll={deleteAllThreads}
|
||||||
|
/>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -30,12 +30,15 @@ const SettingsMenu = () => {
|
|||||||
// On web: exclude llamacpp provider as it's not available
|
// On web: exclude llamacpp provider as it's not available
|
||||||
const activeProviders = providers.filter((provider) => {
|
const activeProviders = providers.filter((provider) => {
|
||||||
if (!provider.active) return false
|
if (!provider.active) return false
|
||||||
|
|
||||||
// On web version, hide llamacpp provider
|
// On web version, hide llamacpp provider
|
||||||
if (!PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && provider.provider === 'llama.cpp') {
|
if (
|
||||||
|
!PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] &&
|
||||||
|
provider.provider === 'llama.cpp'
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -92,7 +95,7 @@ const SettingsMenu = () => {
|
|||||||
title: 'common:keyboardShortcuts',
|
title: 'common:keyboardShortcuts',
|
||||||
route: route.settings.shortcuts,
|
route: route.settings.shortcuts,
|
||||||
hasSubMenu: false,
|
hasSubMenu: false,
|
||||||
isEnabled: true,
|
isEnabled: PlatformFeatures[PlatformFeature.SHORTCUT],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'common:hardware',
|
title: 'common:hardware',
|
||||||
@ -137,7 +140,7 @@ const SettingsMenu = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="fixed top-4 right-4 sm:hidden size-5 cursor-pointer items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out data-[state=open]:bg-main-view-fg/10 z-20"
|
className="fixed top-[calc(10px+env(safe-area-inset-top))] right-4 sm:hidden size-5 cursor-pointer items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out data-[state=open]:bg-main-view-fg/10 z-20"
|
||||||
onClick={toggleMenu}
|
onClick={toggleMenu}
|
||||||
aria-label="Toggle settings menu"
|
aria-label="Toggle settings menu"
|
||||||
>
|
>
|
||||||
@ -152,7 +155,7 @@ const SettingsMenu = () => {
|
|||||||
'h-full w-44 shrink-0 px-1.5 pt-3 border-r border-main-view-fg/5 bg-main-view',
|
'h-full w-44 shrink-0 px-1.5 pt-3 border-r border-main-view-fg/5 bg-main-view',
|
||||||
'sm:flex',
|
'sm:flex',
|
||||||
isMenuOpen
|
isMenuOpen
|
||||||
? 'flex fixed sm:hidden top-0 z-10 m-1 h-[calc(100%-8px)] border-r-0 border-l bg-main-view right-0 py-8 rounded-tr-lg rounded-br-lg'
|
? 'flex fixed sm:hidden top-[calc(10px+env(safe-area-inset-top))] z-10 m-1 h-[calc(100%-8px)] border-r-0 border-l bg-main-view right-0 py-8 rounded-tr-lg rounded-br-lg'
|
||||||
: 'hidden'
|
: 'hidden'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -162,77 +165,82 @@ const SettingsMenu = () => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div key={menu.title}>
|
<div key={menu.title}>
|
||||||
<Link
|
<Link
|
||||||
to={menu.route}
|
to={menu.route}
|
||||||
className="block px-2 gap-1.5 cursor-pointer hover:bg-main-view-fg/5 py-1 w-full rounded [&.active]:bg-main-view-fg/5"
|
className="block px-2 gap-1.5 cursor-pointer hover:bg-main-view-fg/5 py-1 w-full rounded [&.active]:bg-main-view-fg/5"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-main-view-fg/80">{t(menu.title)}</span>
|
<span className="text-main-view-fg/80">
|
||||||
{menu.hasSubMenu && (
|
{t(menu.title)}
|
||||||
<button
|
</span>
|
||||||
onClick={(e) => {
|
{menu.hasSubMenu && (
|
||||||
e.preventDefault()
|
<button
|
||||||
e.stopPropagation()
|
onClick={(e) => {
|
||||||
toggleProvidersExpansion()
|
e.preventDefault()
|
||||||
}}
|
e.stopPropagation()
|
||||||
className="text-main-view-fg/60 hover:text-main-view-fg/80"
|
toggleProvidersExpansion()
|
||||||
>
|
}}
|
||||||
{expandedProviders ? (
|
className="text-main-view-fg/60 hover:text-main-view-fg/80"
|
||||||
<IconChevronDown size={16} />
|
>
|
||||||
) : (
|
{expandedProviders ? (
|
||||||
<IconChevronRight size={16} />
|
<IconChevronDown size={16} />
|
||||||
)}
|
) : (
|
||||||
</button>
|
<IconChevronRight size={16} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</button>
|
||||||
</Link>
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Sub-menu for model providers */}
|
{/* Sub-menu for model providers */}
|
||||||
{menu.hasSubMenu && expandedProviders && (
|
{menu.hasSubMenu && expandedProviders && (
|
||||||
<div className="ml-2 mt-1 space-y-1 first-step-setup-remote-provider">
|
<div className="ml-2 mt-1 space-y-1 first-step-setup-remote-provider">
|
||||||
{activeProviders.map((provider) => {
|
{activeProviders.map((provider) => {
|
||||||
const isActive = matches.some(
|
const isActive = matches.some(
|
||||||
(match) =>
|
(match) =>
|
||||||
match.routeId === '/settings/providers/$providerName' &&
|
match.routeId ===
|
||||||
'providerName' in match.params &&
|
'/settings/providers/$providerName' &&
|
||||||
match.params.providerName === provider.provider
|
'providerName' in match.params &&
|
||||||
)
|
match.params.providerName === provider.provider
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={provider.provider}>
|
<div key={provider.provider}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex px-2 items-center gap-1.5 cursor-pointer hover:bg-main-view-fg/5 py-1 w-full rounded [&.active]:bg-main-view-fg/5 text-main-view-fg/80',
|
'flex px-2 items-center gap-1.5 cursor-pointer hover:bg-main-view-fg/5 py-1 w-full rounded [&.active]:bg-main-view-fg/5 text-main-view-fg/80',
|
||||||
isActive && 'bg-main-view-fg/5',
|
isActive && 'bg-main-view-fg/5',
|
||||||
// hidden for llama.cpp provider for setup remote provider
|
// hidden for llama.cpp provider for setup remote provider
|
||||||
provider.provider === 'llama.cpp' &&
|
provider.provider === 'llama.cpp' &&
|
||||||
stepSetupRemoteProvider &&
|
stepSetupRemoteProvider &&
|
||||||
'hidden'
|
'hidden'
|
||||||
)}
|
)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate({
|
navigate({
|
||||||
to: route.settings.providers,
|
to: route.settings.providers,
|
||||||
params: {
|
params: {
|
||||||
providerName: provider.provider,
|
providerName: provider.provider,
|
||||||
},
|
},
|
||||||
...(stepSetupRemoteProvider
|
...(stepSetupRemoteProvider
|
||||||
? { search: { step: 'setup_remote_provider' } }
|
? {
|
||||||
: {}),
|
search: { step: 'setup_remote_provider' },
|
||||||
})
|
}
|
||||||
}
|
: {}),
|
||||||
>
|
})
|
||||||
<ProvidersAvatar provider={provider} />
|
}
|
||||||
<div className="truncate">
|
>
|
||||||
<span>{getProviderTitle(provider.provider)}</span>
|
<ProvidersAvatar provider={provider} />
|
||||||
|
<div className="truncate">
|
||||||
|
<span>{getProviderTitle(provider.provider)}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import HeaderPage from './HeaderPage'
|
|||||||
import { isProd } from '@/lib/version'
|
import { isProd } from '@/lib/version'
|
||||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||||
import { localStorageKey } from '@/constants/localStorage'
|
import { localStorageKey } from '@/constants/localStorage'
|
||||||
|
import { PlatformFeatures } from '@/lib/platform/const'
|
||||||
|
import { PlatformFeature } from '@/lib/platform'
|
||||||
|
|
||||||
function SetupScreen() {
|
function SetupScreen() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -21,7 +23,7 @@ function SetupScreen() {
|
|||||||
<div className="flex h-full flex-col flex-justify-center">
|
<div className="flex h-full flex-col flex-justify-center">
|
||||||
<HeaderPage></HeaderPage>
|
<HeaderPage></HeaderPage>
|
||||||
<div className="h-full px-8 overflow-y-auto flex flex-col gap-2 justify-center ">
|
<div className="h-full px-8 overflow-y-auto flex flex-col gap-2 justify-center ">
|
||||||
<div className="w-4/6 mx-auto">
|
<div className="w-full lg:w-4/6 mx-auto">
|
||||||
<div className="mb-8 text-left">
|
<div className="mb-8 text-left">
|
||||||
<h1 className="font-editorialnew text-main-view-fg text-4xl">
|
<h1 className="font-editorialnew text-main-view-fg text-4xl">
|
||||||
{t('setup:welcome')}
|
{t('setup:welcome')}
|
||||||
@ -31,22 +33,24 @@ function SetupScreen() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 flex-col">
|
<div className="flex gap-4 flex-col">
|
||||||
<Card
|
{PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && (
|
||||||
header={
|
<Card
|
||||||
<Link
|
header={
|
||||||
to={route.hub.index}
|
<Link
|
||||||
search={{
|
to={route.hub.index}
|
||||||
...(!isProd ? { step: 'setup_local_provider' } : {}),
|
search={{
|
||||||
}}
|
...(!isProd ? { step: 'setup_local_provider' } : {}),
|
||||||
>
|
}}
|
||||||
<div>
|
>
|
||||||
<h1 className="text-main-view-fg font-medium text-base">
|
<div>
|
||||||
{t('setup:localModel')}
|
<h1 className="text-main-view-fg font-medium text-base">
|
||||||
</h1>
|
{t('setup:localModel')}
|
||||||
</div>
|
</h1>
|
||||||
</Link>
|
</div>
|
||||||
}
|
</Link>
|
||||||
></Card>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Card
|
<Card
|
||||||
header={
|
header={
|
||||||
<Link
|
<Link
|
||||||
@ -65,7 +69,7 @@ function SetupScreen() {
|
|||||||
</h1>
|
</h1>
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
></Card>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function PromptAnalytic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-4 right-4 z-50 p-4 shadow-lg bg-main-view w-100 border border-main-view-fg/8 rounded-lg">
|
<div className="fixed bottom-4 right-4 z-50 p-4 shadow-lg bg-main-view w-4/5 md:w-100 border border-main-view-fg/8 rounded-lg">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<IconFileTextShield className="text-accent" />
|
<IconFileTextShield className="text-accent" />
|
||||||
<h2 className="font-medium text-main-view-fg/80">
|
<h2 className="font-medium text-main-view-fg/80">
|
||||||
@ -45,7 +45,9 @@ export function PromptAnalytic() {
|
|||||||
>
|
>
|
||||||
{t('deny')}
|
{t('deny')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => handleProductAnalytics(true)}>{t('allow')}</Button>
|
<Button onClick={() => handleProductAnalytics(true)}>
|
||||||
|
{t('allow')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -56,6 +56,13 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
body {
|
body {
|
||||||
@apply overflow-hidden;
|
@apply overflow-hidden;
|
||||||
|
background-color: white;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: -webkit-fill-available;
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
padding-left: env(safe-area-inset-left);
|
||||||
|
padding-right: env(safe-area-inset-right);
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { PlatformFeature } from './types'
|
import { PlatformFeature } from './types'
|
||||||
import { isPlatformTauri } from './utils'
|
import { isPlatformTauri, isPlatformIOS, isPlatformAndroid } from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform Features Configuration
|
* Platform Features Configuration
|
||||||
@ -12,28 +12,35 @@ import { isPlatformTauri } from './utils'
|
|||||||
*/
|
*/
|
||||||
export const PlatformFeatures: Record<PlatformFeature, boolean> = {
|
export const PlatformFeatures: Record<PlatformFeature, boolean> = {
|
||||||
// Hardware monitoring and GPU usage
|
// Hardware monitoring and GPU usage
|
||||||
[PlatformFeature.HARDWARE_MONITORING]: isPlatformTauri(),
|
[PlatformFeature.HARDWARE_MONITORING]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// Local model inference (llama.cpp)
|
// Local model inference (llama.cpp)
|
||||||
[PlatformFeature.LOCAL_INFERENCE]: isPlatformTauri(),
|
[PlatformFeature.LOCAL_INFERENCE]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// Local API server
|
// Local API server
|
||||||
[PlatformFeature.LOCAL_API_SERVER]: isPlatformTauri(),
|
[PlatformFeature.LOCAL_API_SERVER]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// Hub/model downloads
|
// Hub/model downloads
|
||||||
[PlatformFeature.MODEL_HUB]: isPlatformTauri(),
|
[PlatformFeature.MODEL_HUB]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// System integrations (logs, file explorer, etc.)
|
// System integrations (logs, file explorer, etc.)
|
||||||
[PlatformFeature.SYSTEM_INTEGRATIONS]: isPlatformTauri(),
|
[PlatformFeature.SYSTEM_INTEGRATIONS]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// HTTPS proxy
|
// HTTPS proxy
|
||||||
[PlatformFeature.HTTPS_PROXY]: isPlatformTauri(),
|
[PlatformFeature.HTTPS_PROXY]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// Default model providers (OpenAI, Anthropic, etc.) - disabled for web-only Jan builds
|
// Default model providers (OpenAI, Anthropic, etc.) - disabled for web-only Jan builds
|
||||||
[PlatformFeature.DEFAULT_PROVIDERS]: isPlatformTauri(),
|
[PlatformFeature.DEFAULT_PROVIDERS]: isPlatformTauri(),
|
||||||
|
|
||||||
// Analytics and telemetry - disabled for web
|
// Analytics and telemetry - disabled for web
|
||||||
[PlatformFeature.ANALYTICS]: isPlatformTauri(),
|
[PlatformFeature.ANALYTICS]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// Web-specific automatic model selection from jan provider - enabled for web only
|
// Web-specific automatic model selection from jan provider - enabled for web only
|
||||||
[PlatformFeature.WEB_AUTO_MODEL_SELECTION]: !isPlatformTauri(),
|
[PlatformFeature.WEB_AUTO_MODEL_SELECTION]: !isPlatformTauri(),
|
||||||
@ -45,10 +52,12 @@ export const PlatformFeatures: Record<PlatformFeature, boolean> = {
|
|||||||
[PlatformFeature.MCP_AUTO_APPROVE_TOOLS]: !isPlatformTauri(),
|
[PlatformFeature.MCP_AUTO_APPROVE_TOOLS]: !isPlatformTauri(),
|
||||||
|
|
||||||
// MCP servers settings page - disabled for web
|
// MCP servers settings page - disabled for web
|
||||||
[PlatformFeature.MCP_SERVERS_SETTINGS]: isPlatformTauri(),
|
[PlatformFeature.MCP_SERVERS_SETTINGS]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// Extensions settings page - disabled for web
|
// Extensions settings page - disabled for web
|
||||||
[PlatformFeature.EXTENSIONS_SETTINGS]: isPlatformTauri(),
|
[PlatformFeature.EXTENSIONS_SETTINGS]:
|
||||||
|
isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
// Assistant functionality - disabled for web
|
// Assistant functionality - disabled for web
|
||||||
[PlatformFeature.ASSISTANTS]: isPlatformTauri(),
|
[PlatformFeature.ASSISTANTS]: isPlatformTauri(),
|
||||||
@ -60,5 +69,9 @@ export const PlatformFeatures: Record<PlatformFeature, boolean> = {
|
|||||||
[PlatformFeature.GOOGLE_ANALYTICS]: !isPlatformTauri(),
|
[PlatformFeature.GOOGLE_ANALYTICS]: !isPlatformTauri(),
|
||||||
|
|
||||||
// Alternate shortcut bindings - enabled for web only (to avoid browser conflicts)
|
// Alternate shortcut bindings - enabled for web only (to avoid browser conflicts)
|
||||||
[PlatformFeature.ALTERNATE_SHORTCUT_BINDINGS]: !isPlatformTauri(),
|
[PlatformFeature.ALTERNATE_SHORTCUT_BINDINGS]:
|
||||||
}
|
!isPlatformTauri() && !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
|
||||||
|
// Shortcut
|
||||||
|
[PlatformFeature.SHORTCUT]: !isPlatformIOS() && !isPlatformAndroid(),
|
||||||
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
/**
|
/**
|
||||||
* Supported platforms
|
* Supported platforms
|
||||||
*/
|
*/
|
||||||
export type Platform = 'tauri' | 'web'
|
export type Platform = 'tauri' | 'web' | 'ios' | 'android'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform Feature Enum
|
* Platform Feature Enum
|
||||||
@ -16,6 +16,8 @@ export enum PlatformFeature {
|
|||||||
// Hardware monitoring and GPU usage
|
// Hardware monitoring and GPU usage
|
||||||
HARDWARE_MONITORING = 'hardwareMonitoring',
|
HARDWARE_MONITORING = 'hardwareMonitoring',
|
||||||
|
|
||||||
|
SHORTCUT = 'shortcut',
|
||||||
|
|
||||||
// Local model inference (llama.cpp)
|
// Local model inference (llama.cpp)
|
||||||
LOCAL_INFERENCE = 'localInference',
|
LOCAL_INFERENCE = 'localInference',
|
||||||
|
|
||||||
@ -30,16 +32,16 @@ export enum PlatformFeature {
|
|||||||
|
|
||||||
// HTTPS proxy
|
// HTTPS proxy
|
||||||
HTTPS_PROXY = 'httpsProxy',
|
HTTPS_PROXY = 'httpsProxy',
|
||||||
|
|
||||||
// Default model providers (OpenAI, Anthropic, etc.)
|
// Default model providers (OpenAI, Anthropic, etc.)
|
||||||
DEFAULT_PROVIDERS = 'defaultProviders',
|
DEFAULT_PROVIDERS = 'defaultProviders',
|
||||||
|
|
||||||
// Analytics and telemetry
|
// Analytics and telemetry
|
||||||
ANALYTICS = 'analytics',
|
ANALYTICS = 'analytics',
|
||||||
|
|
||||||
// Web-specific automatic model selection from jan provider
|
// Web-specific automatic model selection from jan provider
|
||||||
WEB_AUTO_MODEL_SELECTION = 'webAutoModelSelection',
|
WEB_AUTO_MODEL_SELECTION = 'webAutoModelSelection',
|
||||||
|
|
||||||
// Model provider settings page management
|
// Model provider settings page management
|
||||||
MODEL_PROVIDER_SETTINGS = 'modelProviderSettings',
|
MODEL_PROVIDER_SETTINGS = 'modelProviderSettings',
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Platform, PlatformFeature } from './types'
|
import { Platform, PlatformFeature } from './types'
|
||||||
|
|
||||||
declare const IS_WEB_APP: boolean
|
declare const IS_WEB_APP: boolean
|
||||||
@ -12,7 +13,25 @@ export const isPlatformTauri = (): boolean => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isPlatformIOS = (): boolean => {
|
||||||
|
if (typeof navigator === 'undefined') return false
|
||||||
|
return (
|
||||||
|
/iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPlatformAndroid = (): boolean => {
|
||||||
|
if (typeof navigator === 'undefined') return false
|
||||||
|
return /Android/.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIOS = (): boolean => isPlatformIOS()
|
||||||
|
|
||||||
|
export const isAndroid = (): boolean => isPlatformAndroid()
|
||||||
|
|
||||||
export const getCurrentPlatform = (): Platform => {
|
export const getCurrentPlatform = (): Platform => {
|
||||||
|
if (isPlatformIOS()) return 'ios'
|
||||||
|
if (isPlatformAndroid()) return 'android'
|
||||||
return isPlatformTauri() ? 'tauri' : 'web'
|
return isPlatformTauri() ? 'tauri' : 'web'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -111,13 +111,17 @@ const AppLayout = () => {
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<AnalyticProvider />
|
<AnalyticProvider />
|
||||||
{PlatformFeatures[PlatformFeature.GOOGLE_ANALYTICS] && <GoogleAnalyticsProvider />}
|
{PlatformFeatures[PlatformFeature.GOOGLE_ANALYTICS] && (
|
||||||
|
<GoogleAnalyticsProvider />
|
||||||
|
)}
|
||||||
<KeyboardShortcutsProvider />
|
<KeyboardShortcutsProvider />
|
||||||
<main className="relative h-svh text-sm antialiased select-none bg-app">
|
<main className="relative h-svh text-sm antialiased select-none bg-app">
|
||||||
{/* Fake absolute panel top to enable window drag */}
|
{/* Fake absolute panel top to enable window drag */}
|
||||||
<div className="absolute w-full h-10 z-10" data-tauri-drag-region />
|
<div className="absolute w-full h-10 z-10" data-tauri-drag-region />
|
||||||
<DialogAppUpdater />
|
<DialogAppUpdater />
|
||||||
{PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && <BackendUpdater />}
|
{PlatformFeatures[PlatformFeature.LOCAL_INFERENCE] && (
|
||||||
|
<BackendUpdater />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Use ResizablePanelGroup only on larger screens */}
|
{/* Use ResizablePanelGroup only on larger screens */}
|
||||||
{!isSmallScreen && isLeftPanelOpen ? (
|
{!isSmallScreen && isLeftPanelOpen ? (
|
||||||
@ -158,11 +162,11 @@ const AppLayout = () => {
|
|||||||
{/* Main content panel */}
|
{/* Main content panel */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-full flex w-full p-1 ',
|
'h-svh flex w-full md:p-1',
|
||||||
isLeftPanelOpen && 'w-full md:w-[calc(100%-198px)]'
|
isLeftPanelOpen && 'w-full md:w-[calc(100%-198px)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="bg-main-view text-main-view-fg border border-main-view-fg/5 w-full rounded-lg overflow-hidden">
|
<div className="bg-main-view text-main-view-fg border border-main-view-fg/5 w-full md:rounded-lg overflow-hidden">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ function Appareances() {
|
|||||||
const { resetCodeBlockStyle } = useCodeblock()
|
const { resetCodeBlockStyle } = useCodeblock()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full pb-[calc(env(safe-area-inset-bottom)+env(safe-area-inset-top))]">
|
||||||
<HeaderPage>
|
<HeaderPage>
|
||||||
<h1 className="font-medium">{t('common:settings')}</h1>
|
<h1 className="font-medium">{t('common:settings')}</h1>
|
||||||
</HeaderPage>
|
</HeaderPage>
|
||||||
|
|||||||
@ -161,7 +161,7 @@ function General() {
|
|||||||
}, [t, checkForUpdate])
|
}, [t, checkForUpdate])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full pb-[calc(env(safe-area-inset-bottom)+env(safe-area-inset-top))]">
|
||||||
<HeaderPage>
|
<HeaderPage>
|
||||||
<h1 className="font-medium">{t('common:settings')}</h1>
|
<h1 className="font-medium">{t('common:settings')}</h1>
|
||||||
</HeaderPage>
|
</HeaderPage>
|
||||||
@ -181,28 +181,29 @@ function General() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!AUTO_UPDATER_DISABLED && PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && (
|
{!AUTO_UPDATER_DISABLED &&
|
||||||
<CardItem
|
PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && (
|
||||||
title={t('settings:general.checkForUpdates')}
|
<CardItem
|
||||||
description={t('settings:general.checkForUpdatesDesc')}
|
title={t('settings:general.checkForUpdates')}
|
||||||
className="flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2"
|
description={t('settings:general.checkForUpdatesDesc')}
|
||||||
actions={
|
className="flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2"
|
||||||
<Button
|
actions={
|
||||||
variant="link"
|
<Button
|
||||||
size="sm"
|
variant="link"
|
||||||
className="p-0"
|
size="sm"
|
||||||
onClick={handleCheckForUpdate}
|
className="p-0"
|
||||||
disabled={isCheckingUpdate}
|
onClick={handleCheckForUpdate}
|
||||||
>
|
disabled={isCheckingUpdate}
|
||||||
<div className="cursor-pointer rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
>
|
||||||
{isCheckingUpdate
|
<div className="cursor-pointer rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
||||||
? t('settings:general.checkingForUpdates')
|
{isCheckingUpdate
|
||||||
: t('settings:general.checkForUpdates')}
|
? t('settings:general.checkingForUpdates')
|
||||||
</div>
|
: t('settings:general.checkForUpdates')}
|
||||||
</Button>
|
</div>
|
||||||
}
|
</Button>
|
||||||
/>
|
}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
<CardItem
|
<CardItem
|
||||||
title={t('common:language')}
|
title={t('common:language')}
|
||||||
actions={<LanguageSwitcher />}
|
actions={<LanguageSwitcher />}
|
||||||
@ -211,165 +212,173 @@ function General() {
|
|||||||
|
|
||||||
{/* Data folder - Desktop only */}
|
{/* Data folder - Desktop only */}
|
||||||
{PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && (
|
{PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && (
|
||||||
<Card title={t('common:dataFolder')}>
|
<Card title={t('common:dataFolder')}>
|
||||||
<CardItem
|
<CardItem
|
||||||
title={t('settings:dataFolder.appData', {
|
title={t('settings:dataFolder.appData', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
align="start"
|
align="start"
|
||||||
className="flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2"
|
className="flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2"
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
<span>
|
<span>
|
||||||
{t('settings:dataFolder.appDataDesc', {
|
{t('settings:dataFolder.appDataDesc', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2 mt-1 ">
|
<div className="flex items-center gap-2 mt-1 ">
|
||||||
<div className="">
|
<div className="">
|
||||||
<span
|
<span
|
||||||
title={janDataFolder}
|
title={janDataFolder}
|
||||||
className="bg-main-view-fg/10 text-xs px-1 py-0.5 rounded-sm text-main-view-fg/80 line-clamp-1 w-fit"
|
className="bg-main-view-fg/10 text-xs px-1 py-0.5 rounded-sm text-main-view-fg/80 line-clamp-1 w-fit"
|
||||||
|
>
|
||||||
|
{janDataFolder}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
janDataFolder && copyToClipboard(janDataFolder)
|
||||||
|
}
|
||||||
|
className="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 p-1"
|
||||||
|
title={
|
||||||
|
isCopied
|
||||||
|
? t('settings:general.copied')
|
||||||
|
: t('settings:general.copyPath')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{janDataFolder}
|
{isCopied ? (
|
||||||
</span>
|
<div className="flex items-center gap-1">
|
||||||
|
<IconCopyCheck
|
||||||
|
size={12}
|
||||||
|
className="text-accent"
|
||||||
|
/>
|
||||||
|
<span className="text-xs leading-0">
|
||||||
|
{t('settings:general.copied')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<IconCopy
|
||||||
|
size={12}
|
||||||
|
className="text-main-view-fg/50"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
</>
|
||||||
onClick={() =>
|
}
|
||||||
janDataFolder && copyToClipboard(janDataFolder)
|
actions={
|
||||||
}
|
<>
|
||||||
className="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 p-1"
|
<Button
|
||||||
title={
|
variant="link"
|
||||||
isCopied
|
size="sm"
|
||||||
? t('settings:general.copied')
|
className="p-0"
|
||||||
: t('settings:general.copyPath')
|
title={t('settings:dataFolder.appData')}
|
||||||
}
|
onClick={handleDataFolderChange}
|
||||||
>
|
>
|
||||||
{isCopied ? (
|
<div className="cursor-pointer flex items-center justify-center rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
||||||
<div className="flex items-center gap-1">
|
<IconFolder
|
||||||
<IconCopyCheck size={12} className="text-accent" />
|
|
||||||
<span className="text-xs leading-0">
|
|
||||||
{t('settings:general.copied')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<IconCopy
|
|
||||||
size={12}
|
size={12}
|
||||||
className="text-main-view-fg/50"
|
className="text-main-view-fg/50"
|
||||||
/>
|
/>
|
||||||
)}
|
<span>{t('settings:general.changeLocation')}</span>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</Button>
|
||||||
</>
|
{selectedNewPath && (
|
||||||
}
|
<ChangeDataFolderLocation
|
||||||
actions={
|
currentPath={janDataFolder || ''}
|
||||||
<>
|
newPath={selectedNewPath}
|
||||||
<Button
|
onConfirm={confirmDataFolderChange}
|
||||||
variant="link"
|
open={isDialogOpen}
|
||||||
size="sm"
|
onOpenChange={(open) => {
|
||||||
className="p-0"
|
setIsDialogOpen(open)
|
||||||
title={t('settings:dataFolder.appData')}
|
if (!open) {
|
||||||
onClick={handleDataFolderChange}
|
setSelectedNewPath(null)
|
||||||
>
|
}
|
||||||
<div className="cursor-pointer flex items-center justify-center rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
}}
|
||||||
<IconFolder
|
>
|
||||||
size={12}
|
<div />
|
||||||
className="text-main-view-fg/50"
|
</ChangeDataFolderLocation>
|
||||||
/>
|
)}
|
||||||
<span>{t('settings:general.changeLocation')}</span>
|
</>
|
||||||
</div>
|
}
|
||||||
</Button>
|
/>
|
||||||
{selectedNewPath && (
|
<CardItem
|
||||||
<ChangeDataFolderLocation
|
title={t('settings:dataFolder.appLogs', {
|
||||||
currentPath={janDataFolder || ''}
|
ns: 'settings',
|
||||||
newPath={selectedNewPath}
|
})}
|
||||||
onConfirm={confirmDataFolderChange}
|
description={t('settings:dataFolder.appLogsDesc')}
|
||||||
open={isDialogOpen}
|
className="flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2"
|
||||||
onOpenChange={(open) => {
|
actions={
|
||||||
setIsDialogOpen(open)
|
<div className="flex items-center gap-2">
|
||||||
if (!open) {
|
<Button
|
||||||
setSelectedNewPath(null)
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
className="p-0"
|
||||||
|
onClick={handleOpenLogs}
|
||||||
|
title={t('settings:dataFolder.appLogs')}
|
||||||
|
>
|
||||||
|
<div className="cursor-pointer flex items-center justify-center rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
||||||
|
<IconLogs
|
||||||
|
size={12}
|
||||||
|
className="text-main-view-fg/50"
|
||||||
|
/>
|
||||||
|
<span>{t('settings:general.openLogs')}</span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
className="p-0"
|
||||||
|
onClick={async () => {
|
||||||
|
if (janDataFolder) {
|
||||||
|
try {
|
||||||
|
const logsPath = `${janDataFolder}/logs`
|
||||||
|
await serviceHub
|
||||||
|
.opener()
|
||||||
|
.revealItemInDir(logsPath)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'Failed to reveal logs folder:',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
title={t('settings:general.revealLogs')}
|
||||||
>
|
>
|
||||||
<div />
|
<div className="cursor-pointer flex items-center justify-center rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
||||||
</ChangeDataFolderLocation>
|
<IconFolder
|
||||||
)}
|
size={12}
|
||||||
</>
|
className="text-main-view-fg/50"
|
||||||
}
|
/>
|
||||||
/>
|
<span>{openFileTitle()}</span>
|
||||||
<CardItem
|
</div>
|
||||||
title={t('settings:dataFolder.appLogs', {
|
</Button>
|
||||||
ns: 'settings',
|
</div>
|
||||||
})}
|
}
|
||||||
description={t('settings:dataFolder.appLogsDesc')}
|
/>
|
||||||
className="flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-y-2"
|
</Card>
|
||||||
actions={
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
size="sm"
|
|
||||||
className="p-0"
|
|
||||||
onClick={handleOpenLogs}
|
|
||||||
title={t('settings:dataFolder.appLogs')}
|
|
||||||
>
|
|
||||||
<div className="cursor-pointer flex items-center justify-center rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
|
||||||
<IconLogs size={12} className="text-main-view-fg/50" />
|
|
||||||
<span>{t('settings:general.openLogs')}</span>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
size="sm"
|
|
||||||
className="p-0"
|
|
||||||
onClick={async () => {
|
|
||||||
if (janDataFolder) {
|
|
||||||
try {
|
|
||||||
const logsPath = `${janDataFolder}/logs`
|
|
||||||
await serviceHub.opener().revealItemInDir(logsPath)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
'Failed to reveal logs folder:',
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
title={t('settings:general.revealLogs')}
|
|
||||||
>
|
|
||||||
<div className="cursor-pointer flex items-center justify-center rounded-sm hover:bg-main-view-fg/15 bg-main-view-fg/10 transition-all duration-200 ease-in-out px-2 py-1 gap-1">
|
|
||||||
<IconFolder
|
|
||||||
size={12}
|
|
||||||
className="text-main-view-fg/50"
|
|
||||||
/>
|
|
||||||
<span>{openFileTitle()}</span>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
{/* Advanced - Desktop only */}
|
{/* Advanced - Desktop only */}
|
||||||
{PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && (
|
{PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && (
|
||||||
<Card title="Advanced">
|
<Card title="Advanced">
|
||||||
<CardItem
|
<CardItem
|
||||||
title={t('settings:others.resetFactory', {
|
title={t('settings:others.resetFactory', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
description={t('settings:others.resetFactoryDesc', {
|
description={t('settings:others.resetFactoryDesc', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
actions={
|
actions={
|
||||||
<FactoryResetDialog onReset={resetApp}>
|
<FactoryResetDialog onReset={resetApp}>
|
||||||
<Button variant="destructive" size="sm">
|
<Button variant="destructive" size="sm">
|
||||||
{t('common:reset')}
|
{t('common:reset')}
|
||||||
</Button>
|
</Button>
|
||||||
</FactoryResetDialog>
|
</FactoryResetDialog>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Other */}
|
{/* Other */}
|
||||||
|
|||||||
@ -443,7 +443,7 @@ function ProviderDetail() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Joyride
|
<Joyride
|
||||||
run={isSetup}
|
run={IS_IOS || IS_ANDROID ? false : isSetup}
|
||||||
floaterProps={{
|
floaterProps={{
|
||||||
hideArrow: true,
|
hideArrow: true,
|
||||||
}}
|
}}
|
||||||
@ -465,7 +465,7 @@ function ProviderDetail() {
|
|||||||
skip: t('providers:joyride.skip'),
|
skip: t('providers:joyride.skip'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full pb-[calc(env(safe-area-inset-bottom)+env(safe-area-inset-top))]">
|
||||||
<HeaderPage>
|
<HeaderPage>
|
||||||
<h1 className="font-medium">{t('common:settings')}</h1>
|
<h1 className="font-medium">{t('common:settings')}</h1>
|
||||||
</HeaderPage>
|
</HeaderPage>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user