Skip to content

Commit

Permalink
feat: update UI allow user change folder (janhq#1738)
Browse files Browse the repository at this point in the history
* feat: wip ui jan folder setting

* change input disabled

* finished change directory jan folder

* fix overlap value input current path folder

* make app reload to latest page

* fix: add experimental feature toggle til the next release

---------

Co-authored-by: Louis <[email protected]>
  • Loading branch information
urmauur and louis-jan authored Jan 24, 2024
1 parent 1b794b5 commit 6ba48bc
Show file tree
Hide file tree
Showing 13 changed files with 398 additions and 145 deletions.
1 change: 1 addition & 0 deletions uikit/src/button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const buttonVariants = cva('btn', {
outline: 'btn-outline',
secondary: 'btn-secondary',
secondaryBlue: 'btn-secondary-blue',
secondaryDanger: 'btn-secondary-danger',
ghost: 'btn-ghost',
success: 'btn-success',
},
Expand Down
6 changes: 5 additions & 1 deletion uikit/src/button/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
}

&-secondary-blue {
@apply bg-blue-200 text-blue-600 hover:bg-blue-500/80;
@apply bg-blue-200 text-blue-600 hover:bg-blue-500/50;
}

&-danger {
@apply bg-danger text-danger-foreground hover:bg-danger/90;
}

&-secondary-danger {
@apply bg-red-200 text-red-600 hover:bg-red-500/50;
}

&-outline {
@apply border-input border bg-transparent;
}
Expand Down
2 changes: 1 addition & 1 deletion uikit/src/input/styles.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.input {
@apply border-border placeholder:text-muted-foreground flex h-9 w-full rounded-lg border bg-transparent px-3 py-1 transition-colors;
@apply disabled:cursor-not-allowed disabled:opacity-50;
@apply disabled:cursor-not-allowed disabled:bg-zinc-100;
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
@apply file:border-0 file:bg-transparent file:font-medium;
}
11 changes: 10 additions & 1 deletion web/containers/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,27 @@ import RibbonNav from '@/containers/Layout/Ribbon'

import TopBar from '@/containers/Layout/TopBar'

import { MainViewState } from '@/constants/screens'

import { useMainViewState } from '@/hooks/useMainViewState'
import { SUCCESS_SET_NEW_DESTINATION } from '@/hooks/useVaultDirectory'

const BaseLayout = (props: PropsWithChildren) => {
const { children } = props
const { mainViewState } = useMainViewState()
const { mainViewState, setMainViewState } = useMainViewState()

const { theme, setTheme } = useTheme()

useEffect(() => {
setTheme(theme as string)
}, [setTheme, theme])

useEffect(() => {
if (localStorage.getItem(SUCCESS_SET_NEW_DESTINATION) === 'true') {
setMainViewState(MainViewState.Settings)
}
}, [])

return (
<div className="flex h-screen w-screen flex-1 overflow-hidden">
<RibbonNav />
Expand Down
10 changes: 6 additions & 4 deletions web/containers/ShortcutModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ const availableShortcuts = [

const ShortcutModal: React.FC = () => (
<Modal>
<ModalTrigger asChild>
<Button size="sm" themes="secondary">
Show
</Button>
<ModalTrigger>
<div>
<Button size="sm" themes="secondaryBlue">
Show
</Button>
</div>
</ModalTrigger>
<ModalContent className="max-w-2xl">
<ModalHeader>
Expand Down
105 changes: 105 additions & 0 deletions web/hooks/useVaultDirectory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useEffect } from 'react'

import { fs, AppConfiguration } from '@janhq/core'

import { atom, useAtom } from 'jotai'

import { useMainViewState } from './useMainViewState'

const isSameDirectoryAtom = atom(false)
const isDirectoryConfirmAtom = atom(false)
const isErrorSetNewDestAtom = atom(false)
const currentPathAtom = atom('')
const newDestinationPathAtom = atom('')

export const SUCCESS_SET_NEW_DESTINATION = 'successSetNewDestination'

export function useVaultDirectory() {
const [isSameDirectory, setIsSameDirectory] = useAtom(isSameDirectoryAtom)
const { setMainViewState } = useMainViewState()
const [isDirectoryConfirm, setIsDirectoryConfirm] = useAtom(
isDirectoryConfirmAtom
)
const [isErrorSetNewDest, setIsErrorSetNewDest] = useAtom(
isErrorSetNewDestAtom
)
const [currentPath, setCurrentPath] = useAtom(currentPathAtom)
const [newDestinationPath, setNewDestinationPath] = useAtom(
newDestinationPathAtom
)

useEffect(() => {
window.core?.api
?.getAppConfigurations()
?.then((appConfig: AppConfiguration) => {
setCurrentPath(appConfig.data_folder)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const setNewDestination = async () => {
const destFolder = await window.core?.api?.selectDirectory()
setNewDestinationPath(destFolder)

if (destFolder) {
console.debug(`Destination folder selected: ${destFolder}`)
try {
const appConfiguration: AppConfiguration =
await window.core?.api?.getAppConfigurations()
const currentJanDataFolder = appConfiguration.data_folder

if (currentJanDataFolder === destFolder) {
console.debug(
`Destination folder is the same as current folder. Ignore..`
)
setIsSameDirectory(true)
setIsDirectoryConfirm(false)
return
} else {
setIsSameDirectory(false)
setIsDirectoryConfirm(true)
}
setIsErrorSetNewDest(false)
} catch (e) {
console.error(`Error: ${e}`)
setIsErrorSetNewDest(true)
}
}
}

const applyNewDestination = async () => {
try {
const appConfiguration: AppConfiguration =
await window.core?.api?.getAppConfigurations()
const currentJanDataFolder = appConfiguration.data_folder

appConfiguration.data_folder = newDestinationPath

await fs.syncFile(currentJanDataFolder, newDestinationPath)
await window.core?.api?.updateAppConfiguration(appConfiguration)
console.debug(
`File sync finished from ${currentPath} to ${newDestinationPath}`
)

setIsErrorSetNewDest(false)
localStorage.setItem(SUCCESS_SET_NEW_DESTINATION, 'true')
await window.core?.api?.relaunch()
} catch (e) {
console.error(`Error: ${e}`)
setIsErrorSetNewDest(true)
}
}

return {
setNewDestination,
newDestinationPath,
applyNewDestination,
isSameDirectory,
setIsDirectoryConfirm,
isDirectoryConfirm,
setIsSameDirectory,
currentPath,
isErrorSetNewDest,
setIsErrorSetNewDest,
}
}
57 changes: 57 additions & 0 deletions web/screens/Settings/Advanced/DataFolder/ModalChangeDirectory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'

import {
Modal,
ModalPortal,
ModalContent,
ModalHeader,
ModalTitle,
ModalFooter,
ModalClose,
Button,
} from '@janhq/uikit'

import { useVaultDirectory } from '@/hooks/useVaultDirectory'

const ModalChangeDirectory = () => {
const {
isDirectoryConfirm,
setIsDirectoryConfirm,
applyNewDestination,
newDestinationPath,
} = useVaultDirectory()
return (
<Modal
open={isDirectoryConfirm}
onOpenChange={() => setIsDirectoryConfirm(false)}
>
<ModalPortal />
<ModalContent>
<ModalHeader>
<ModalTitle>Relocate Jan Data Folder</ModalTitle>
</ModalHeader>
<p className="text-muted-foreground">
Are you sure you want to relocate Jan data folder to{' '}
<span className="font-medium text-foreground">
{newDestinationPath}
</span>
? A restart will be required afterward.
</p>
<ModalFooter>
<div className="flex gap-x-2">
<ModalClose asChild onClick={() => setIsDirectoryConfirm(false)}>
<Button themes="ghost">Cancel</Button>
</ModalClose>
<ModalClose asChild>
<Button onClick={applyNewDestination} autoFocus>
Yes, Proceed
</Button>
</ModalClose>
</div>
</ModalFooter>
</ModalContent>
</Modal>
)
}

export default ModalChangeDirectory
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'

import {
Modal,
ModalPortal,
ModalContent,
ModalHeader,
ModalTitle,
ModalFooter,
ModalClose,
Button,
} from '@janhq/uikit'

import { useVaultDirectory } from '@/hooks/useVaultDirectory'

const ModalErrorSetDestGlobal = () => {
const { isErrorSetNewDest, setIsErrorSetNewDest } = useVaultDirectory()
return (
<Modal
open={isErrorSetNewDest}
onOpenChange={() => setIsErrorSetNewDest(false)}
>
<ModalPortal />
<ModalContent>
<ModalHeader>
<ModalTitle>Error Occurred</ModalTitle>
</ModalHeader>
<p className="text-muted-foreground">
Oops! Something went wrong. Jan data folder remains the same. Please
try again.
</p>
<ModalFooter>
<div className="flex gap-x-2">
<ModalClose asChild onClick={() => setIsErrorSetNewDest(false)}>
<Button themes="danger">Got it</Button>
</ModalClose>
</div>
</ModalFooter>
</ModalContent>
</Modal>
)
}

export default ModalErrorSetDestGlobal
49 changes: 49 additions & 0 deletions web/screens/Settings/Advanced/DataFolder/ModalSameDirectory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react'

import {
Modal,
ModalPortal,
ModalContent,
ModalHeader,
ModalTitle,
ModalFooter,
ModalClose,
Button,
} from '@janhq/uikit'

import { useVaultDirectory } from '@/hooks/useVaultDirectory'

const ModalSameDirectory = () => {
const { isSameDirectory, setIsSameDirectory, setNewDestination } =
useVaultDirectory()
return (
<Modal
open={isSameDirectory}
onOpenChange={() => setIsSameDirectory(false)}
>
<ModalPortal />
<ModalContent>
<ModalHeader>
<ModalTitle>Unable to move files</ModalTitle>
</ModalHeader>
<p className="text-muted-foreground">
{`It seems like the folder you've chosen same with current directory`}
</p>
<ModalFooter>
<div className="flex gap-x-2">
<ModalClose asChild onClick={() => setIsSameDirectory(false)}>
<Button themes="ghost">Cancel</Button>
</ModalClose>
<ModalClose asChild>
<Button themes="danger" onClick={setNewDestination} autoFocus>
Choose a different folder
</Button>
</ModalClose>
</div>
</ModalFooter>
</ModalContent>
</Modal>
)
}

export default ModalSameDirectory
52 changes: 52 additions & 0 deletions web/screens/Settings/Advanced/DataFolder/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Button, Input } from '@janhq/uikit'
import { PencilIcon, FolderOpenIcon } from 'lucide-react'

import { useVaultDirectory } from '@/hooks/useVaultDirectory'

import ModalChangeDirectory from './ModalChangeDirectory'
import ModalErrorSetDestGlobal from './ModalErrorSetDestGlobal'
import ModalSameDirectory from './ModalSameDirectory'

const DataFolder = () => {
const { currentPath, setNewDestination } = useVaultDirectory()

return (
<>
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
<div className="flex-shrink-0 space-y-1.5">
<div className="flex gap-x-2">
<h6 className="text-sm font-semibold capitalize">
Jan Data Folder
</h6>
</div>
<p className="leading-relaxed">
Where messages, model configurations, and other user data are
placed.
</p>
</div>
<div className="flex items-center gap-x-3">
<div className="relative">
<Input value={currentPath} className="w-[240px] pr-8" disabled />
<FolderOpenIcon
size={16}
className="absolute right-2 top-1/2 -translate-y-1/2"
/>
</div>
<Button
size="sm"
themes="outline"
className="h-9 w-9 p-0"
onClick={setNewDestination}
>
<PencilIcon size={16} />
</Button>
</div>
</div>
<ModalSameDirectory />
<ModalChangeDirectory />
<ModalErrorSetDestGlobal />
</>
)
}

export default DataFolder
Loading

0 comments on commit 6ba48bc

Please sign in to comment.