can now sync with config files that are updated externally (not imported)
Signed-off-by: FTCHD <144691102+FTCHD@users.noreply.github.com>
This commit is contained in:
+16
-5
@@ -718,7 +718,16 @@ export async function getConfigPath({ id, validate = true }: { id: string; valid
|
|||||||
const appLocalDataDirPath = await appLocalDataDir()
|
const appLocalDataDirPath = await appLocalDataDir()
|
||||||
console.log('[getConfigPath] appLocalDataDirPath', appLocalDataDirPath)
|
console.log('[getConfigPath] appLocalDataDirPath', appLocalDataDirPath)
|
||||||
|
|
||||||
let configPath = `${appLocalDataDirPath}/configs/${id}/rclone.conf`
|
const slashSymbol = platform() === 'windows' ? '\\' : '/'
|
||||||
|
|
||||||
|
let configPath =
|
||||||
|
appLocalDataDirPath +
|
||||||
|
slashSymbol +
|
||||||
|
'configs' +
|
||||||
|
slashSymbol +
|
||||||
|
id +
|
||||||
|
slashSymbol +
|
||||||
|
'rclone.conf'
|
||||||
|
|
||||||
if (id == 'default') {
|
if (id == 'default') {
|
||||||
const defaultPaths = await getDefaultPaths()
|
const defaultPaths = await getDefaultPaths()
|
||||||
@@ -731,10 +740,12 @@ export async function getConfigPath({ id, validate = true }: { id: string; valid
|
|||||||
configPath = defaultPaths.config
|
configPath = defaultPaths.config
|
||||||
}
|
}
|
||||||
|
|
||||||
const configExists = await exists(configPath)
|
if (validate) {
|
||||||
if (validate && !configExists) {
|
const configExists = await exists(configPath)
|
||||||
console.error('[getConfigPath] config file does not exist')
|
if (!configExists) {
|
||||||
throw new Error('Config file does not exist')
|
console.error('[getConfigPath] config file does not exist')
|
||||||
|
throw new Error('Config file does not exist')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return configPath
|
return configPath
|
||||||
|
|||||||
+25
-3
@@ -77,6 +77,7 @@ export async function initRclone(args: string[]) {
|
|||||||
sync: undefined,
|
sync: undefined,
|
||||||
isEncrypted: false,
|
isEncrypted: false,
|
||||||
pass: undefined,
|
pass: undefined,
|
||||||
|
passCommand: undefined,
|
||||||
})
|
})
|
||||||
usePersistedStore.setState({ configFiles })
|
usePersistedStore.setState({ configFiles })
|
||||||
|
|
||||||
@@ -89,6 +90,29 @@ export async function initRclone(args: string[]) {
|
|||||||
usePersistedStore.setState({ activeConfigFile })
|
usePersistedStore.setState({ activeConfigFile })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let configFolderPath = activeConfigFile.sync
|
||||||
|
? activeConfigFile.sync
|
||||||
|
: (await getConfigPath({ id: activeConfigFile.id!, validate: true })).replace(
|
||||||
|
/\/rclone\.conf$/,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
if (activeConfigFile.sync) {
|
||||||
|
const slashSymbol = platform() === 'windows' ? '\\' : '/'
|
||||||
|
if (!(await exists(configFolderPath + slashSymbol + 'rclone.conf'))) {
|
||||||
|
await message('The config file could not be found. Switching to the default config.', {
|
||||||
|
title: 'Invalid synced config',
|
||||||
|
kind: 'error',
|
||||||
|
okLabel: 'OK',
|
||||||
|
})
|
||||||
|
configFolderPath = (await getConfigPath({ id: 'default', validate: true })).replace(
|
||||||
|
/\/rclone\.conf$/,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
usePersistedStore.setState({ activeConfigFile: configFiles[0] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const extraParams =
|
const extraParams =
|
||||||
activeConfigFile.id === 'default'
|
activeConfigFile.id === 'default'
|
||||||
? undefined
|
? undefined
|
||||||
@@ -97,9 +121,7 @@ export async function initRclone(args: string[]) {
|
|||||||
...(activeConfigFile.isEncrypted
|
...(activeConfigFile.isEncrypted
|
||||||
? { RCLONE_CONFIG_PASS: activeConfigFile.pass }
|
? { RCLONE_CONFIG_PASS: activeConfigFile.pass }
|
||||||
: {}),
|
: {}),
|
||||||
RCLONE_CONFIG_DIR: (
|
RCLONE_CONFIG_DIR: configFolderPath,
|
||||||
await getConfigPath({ id: activeConfigFile.id!, validate: true })
|
|
||||||
).replace(/\/rclone\.conf$/, ''),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { Button } from '@heroui/react'
|
import { Button } from '@heroui/react'
|
||||||
import { ask, open } from '@tauri-apps/plugin-dialog'
|
import { message, open } from '@tauri-apps/plugin-dialog'
|
||||||
import { mkdir, readTextFile, writeTextFile } from '@tauri-apps/plugin-fs'
|
import { mkdir, readTextFile, writeTextFile } from '@tauri-apps/plugin-fs'
|
||||||
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { UploadIcon } from 'lucide-react'
|
import { UploadIcon } from 'lucide-react'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { getConfigPath } from '../../lib/rclone/api'
|
import { getConfigPath } from '../../lib/rclone/api'
|
||||||
@@ -58,7 +59,11 @@ export default function ConfigCreateDrawer({
|
|||||||
|
|
||||||
const configPath = await getConfigPath({ id: generatedId, validate: false })
|
const configPath = await getConfigPath({ id: generatedId, validate: false })
|
||||||
|
|
||||||
await mkdir(configPath.replace('/rclone.conf', ''), { recursive: true })
|
const slashSymbol = platform() === 'windows' ? '\\' : '/'
|
||||||
|
|
||||||
|
await mkdir(configPath.replace(slashSymbol + 'rclone.conf', ''), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
await writeTextFile(configPath, content)
|
await writeTextFile(configPath, content)
|
||||||
console.log('[handleCreate] saved config to', configPath)
|
console.log('[handleCreate] saved config to', configPath)
|
||||||
|
|
||||||
@@ -68,17 +73,20 @@ export default function ConfigCreateDrawer({
|
|||||||
pass,
|
pass,
|
||||||
isEncrypted: content.includes('RCLONE_ENCRYPT_V0:'),
|
isEncrypted: content.includes('RCLONE_ENCRYPT_V0:'),
|
||||||
sync: undefined,
|
sync: undefined,
|
||||||
|
passCommand: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[handleCreate] failed to save config', error)
|
console.error('[handleCreate] failed to save config', error)
|
||||||
await ask('Failed to save config', {
|
await message(
|
||||||
title: 'Error',
|
error instanceof Error ? error.message : 'An unknown error occurred',
|
||||||
kind: 'error',
|
{
|
||||||
okLabel: 'OK',
|
title: 'Failed to save config',
|
||||||
cancelLabel: '',
|
kind: 'error',
|
||||||
})
|
okLabel: 'OK',
|
||||||
|
}
|
||||||
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
}
|
}
|
||||||
@@ -194,12 +202,15 @@ export default function ConfigCreateDrawer({
|
|||||||
}, '')
|
}, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slashSymbol =
|
||||||
|
platform() === 'windows' ? '\\' : '/'
|
||||||
|
|
||||||
setConfigContent(content)
|
setConfigContent(content)
|
||||||
setConfig({
|
setConfig({
|
||||||
...config,
|
...config,
|
||||||
label:
|
label:
|
||||||
config.label ||
|
config.label ||
|
||||||
selectedFile.split('/').pop() ||
|
selectedFile.split(slashSymbol).pop() ||
|
||||||
'New Config',
|
'New Config',
|
||||||
isEncrypted:
|
isEncrypted:
|
||||||
content.includes('RCLONE_ENCRYPT_V0:'),
|
content.includes('RCLONE_ENCRYPT_V0:'),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { Button } from '@heroui/react'
|
import { Button } from '@heroui/react'
|
||||||
|
import { message } from '@tauri-apps/plugin-dialog'
|
||||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs'
|
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { getConfigPath } from '../../lib/rclone/api'
|
import { getConfigPath } from '../../lib/rclone/api'
|
||||||
@@ -65,7 +66,15 @@ export default function ConfigEditDrawer({
|
|||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error('[handleUpdate] failed to save config', error)
|
||||||
|
await message(
|
||||||
|
error instanceof Error ? error.message : 'An unknown error occurred',
|
||||||
|
{
|
||||||
|
title: 'Failed to save config',
|
||||||
|
kind: 'error',
|
||||||
|
okLabel: 'OK',
|
||||||
|
}
|
||||||
|
)
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,338 @@
|
|||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerBody,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
Input,
|
||||||
|
Switch,
|
||||||
|
} from '@heroui/react'
|
||||||
|
import { Button } from '@heroui/react'
|
||||||
|
import { message, open } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { exists, readTextFile } from '@tauri-apps/plugin-fs'
|
||||||
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { UploadIcon } from 'lucide-react'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { usePersistedStore } from '../../lib/store'
|
||||||
|
import type { ConfigFile } from '../../types/config'
|
||||||
|
|
||||||
|
export default function ConfigSyncDrawer({
|
||||||
|
onClose,
|
||||||
|
isOpen,
|
||||||
|
}: {
|
||||||
|
onClose: () => void
|
||||||
|
isOpen: boolean
|
||||||
|
}) {
|
||||||
|
const [config, setConfig] = useState<Partial<ConfigFile>>({
|
||||||
|
label: 'New Config',
|
||||||
|
})
|
||||||
|
|
||||||
|
const [isPasswordCommand, setIsPasswordCommand] = useState(false)
|
||||||
|
|
||||||
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
|
||||||
|
const handleCreate = useCallback(
|
||||||
|
async ({
|
||||||
|
label,
|
||||||
|
pass,
|
||||||
|
isEncrypted,
|
||||||
|
passCommand,
|
||||||
|
sync,
|
||||||
|
}: {
|
||||||
|
label?: string
|
||||||
|
sync?: string
|
||||||
|
isEncrypted?: boolean
|
||||||
|
pass?: string
|
||||||
|
passCommand?: string
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
if (!label) {
|
||||||
|
throw new Error('Label is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sync) {
|
||||||
|
throw new Error('Path is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEncrypted) {
|
||||||
|
if (!pass && !passCommand) {
|
||||||
|
throw new Error('Password is required for encrypted configs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSaving(true)
|
||||||
|
|
||||||
|
const generatedId = crypto.randomUUID()
|
||||||
|
|
||||||
|
usePersistedStore.getState().addConfigFile({
|
||||||
|
id: generatedId,
|
||||||
|
label,
|
||||||
|
isEncrypted: isEncrypted || false,
|
||||||
|
pass,
|
||||||
|
passCommand,
|
||||||
|
sync,
|
||||||
|
})
|
||||||
|
|
||||||
|
onClose()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[handleCreate] failed to save config', error)
|
||||||
|
await message(
|
||||||
|
error instanceof Error ? error.message : 'An unknown error occurred',
|
||||||
|
{
|
||||||
|
title: 'Failed to save config',
|
||||||
|
kind: 'error',
|
||||||
|
okLabel: 'OK',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClose]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
isOpen={isOpen}
|
||||||
|
placement={'bottom'}
|
||||||
|
size="full"
|
||||||
|
onClose={onClose}
|
||||||
|
hideCloseButton={true}
|
||||||
|
>
|
||||||
|
<DrawerContent>
|
||||||
|
{(close) => (
|
||||||
|
<>
|
||||||
|
<DrawerHeader className="flex flex-col gap-1">Sync Config</DrawerHeader>
|
||||||
|
<DrawerBody>
|
||||||
|
<form
|
||||||
|
id="config-form"
|
||||||
|
className="flex flex-col gap-5"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleCreate({
|
||||||
|
label: config.label,
|
||||||
|
pass: isPasswordCommand ? undefined : config.pass,
|
||||||
|
isEncrypted: config.isEncrypted,
|
||||||
|
passCommand: isPasswordCommand
|
||||||
|
? config.passCommand
|
||||||
|
: undefined,
|
||||||
|
sync: config.sync,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
size="lg"
|
||||||
|
isSelected={config.isEncrypted}
|
||||||
|
onValueChange={() =>
|
||||||
|
setConfig({ ...config, isEncrypted: !config.isEncrypted })
|
||||||
|
}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Encrypted
|
||||||
|
</Switch>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Name"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder="Enter a name for your config"
|
||||||
|
type="text"
|
||||||
|
value={config.label}
|
||||||
|
autoCapitalize="off"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setConfig({ ...config, label: value })
|
||||||
|
}}
|
||||||
|
isClearable={true}
|
||||||
|
onClear={() => {
|
||||||
|
setConfig({ ...config, label: '' })
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{config.isEncrypted && (
|
||||||
|
<Input
|
||||||
|
// label={
|
||||||
|
// <div className="flex items-center gap-1.5">
|
||||||
|
// <p className="text-medium">Password</p>
|
||||||
|
// <Switch
|
||||||
|
// size="sm"
|
||||||
|
// isSelected={isPasswordCommand}
|
||||||
|
// onValueChange={() =>
|
||||||
|
// setIsPasswordCommand(!isPasswordCommand)
|
||||||
|
// }
|
||||||
|
// color="primary"
|
||||||
|
// >
|
||||||
|
// Command
|
||||||
|
// </Switch>
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
label="Password"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder={
|
||||||
|
isPasswordCommand
|
||||||
|
? 'Enter the password command for your config file'
|
||||||
|
: 'Enter the password for your config file'
|
||||||
|
}
|
||||||
|
type={isPasswordCommand ? 'text' : 'password'}
|
||||||
|
value={isPasswordCommand ? config.passCommand : config.pass}
|
||||||
|
autoCapitalize="off"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
...(isPasswordCommand
|
||||||
|
? { passCommand: value }
|
||||||
|
: { pass: value }),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
isClearable={true}
|
||||||
|
onClear={() => {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
...(isPasswordCommand
|
||||||
|
? { passCommand: '' }
|
||||||
|
: { pass: '' }),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label={
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<p className="text-medium">Config</p>
|
||||||
|
<Button
|
||||||
|
isIconOnly={true}
|
||||||
|
variant="light"
|
||||||
|
size="sm"
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
let selectedFolder = await open({
|
||||||
|
directory: true,
|
||||||
|
multiple: false,
|
||||||
|
title: 'Select the root directory of your config file',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!selectedFolder) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedFolder.endsWith('/') ||
|
||||||
|
selectedFolder.endsWith('\\')
|
||||||
|
) {
|
||||||
|
selectedFolder = selectedFolder.slice(
|
||||||
|
0,
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await exists(selectedFolder))) {
|
||||||
|
throw new Error(
|
||||||
|
'The selected path does not exist'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const slashSymbol =
|
||||||
|
platform() === 'windows' ? '\\' : '/'
|
||||||
|
|
||||||
|
const configPath =
|
||||||
|
selectedFolder +
|
||||||
|
slashSymbol +
|
||||||
|
'rclone.conf'
|
||||||
|
|
||||||
|
let content: string | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
content = await readTextFile(configPath)
|
||||||
|
} catch {
|
||||||
|
throw new Error(
|
||||||
|
'Could not find an rclone.conf file in the selected folder'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
throw new Error(
|
||||||
|
'Empty rclone.conf file'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
label:
|
||||||
|
config.label ||
|
||||||
|
configPath
|
||||||
|
.split(slashSymbol)
|
||||||
|
.pop() ||
|
||||||
|
'New Config',
|
||||||
|
sync: selectedFolder,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
await message(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'An unknown error occurred',
|
||||||
|
{
|
||||||
|
title: 'Failed to sync config',
|
||||||
|
kind: 'error',
|
||||||
|
okLabel: 'OK',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UploadIcon className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder="Select the folder containing your rclone.conf file"
|
||||||
|
value={config.sync || ''}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
console.log(value)
|
||||||
|
setConfig({ ...config, sync: value })
|
||||||
|
}}
|
||||||
|
autoCapitalize="off"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck="false"
|
||||||
|
type="textarea"
|
||||||
|
size="lg"
|
||||||
|
isClearable={true}
|
||||||
|
onClear={() => {
|
||||||
|
setConfig({ ...config, sync: '' })
|
||||||
|
}}
|
||||||
|
data-focus-visible="false"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</DrawerBody>
|
||||||
|
<DrawerFooter>
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
variant="light"
|
||||||
|
onPress={close}
|
||||||
|
data-focus-visible="false"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
form="config-form"
|
||||||
|
isDisabled={isSaving}
|
||||||
|
data-focus-visible="false"
|
||||||
|
>
|
||||||
|
{isSaving ? 'Saving...' : 'Sync'}
|
||||||
|
</Button>
|
||||||
|
</DrawerFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
+69
-47
@@ -1,4 +1,17 @@
|
|||||||
import { Button, Card, CardBody, Checkbox, Chip, Input, Tab, Tabs } from '@heroui/react'
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
Checkbox,
|
||||||
|
Chip,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownTrigger,
|
||||||
|
Input,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
} from '@heroui/react'
|
||||||
import { getVersion as getUiVersion } from '@tauri-apps/api/app'
|
import { getVersion as getUiVersion } from '@tauri-apps/api/app'
|
||||||
import { ask, message } from '@tauri-apps/plugin-dialog'
|
import { ask, message } from '@tauri-apps/plugin-dialog'
|
||||||
import { remove } from '@tauri-apps/plugin-fs'
|
import { remove } from '@tauri-apps/plugin-fs'
|
||||||
@@ -10,6 +23,7 @@ import {
|
|||||||
CodeIcon,
|
CodeIcon,
|
||||||
CogIcon,
|
CogIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
|
ImportIcon,
|
||||||
MedalIcon,
|
MedalIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
@@ -23,6 +37,7 @@ import { usePersistedStore, useStore } from '../../lib/store'
|
|||||||
import { triggerTrayRebuild } from '../../lib/tray'
|
import { triggerTrayRebuild } from '../../lib/tray'
|
||||||
import ConfigCreateDrawer from '../components/ConfigCreateDrawer'
|
import ConfigCreateDrawer from '../components/ConfigCreateDrawer'
|
||||||
import ConfigEditDrawer from '../components/ConfigEditDrawer'
|
import ConfigEditDrawer from '../components/ConfigEditDrawer'
|
||||||
|
import ConfigSyncDrawer from '../components/ConfigSyncDrawer'
|
||||||
import RemoteCreateDrawer from '../components/RemoteCreateDrawer'
|
import RemoteCreateDrawer from '../components/RemoteCreateDrawer'
|
||||||
import RemoteDefaultsDrawer from '../components/RemoteDefaultsDrawer'
|
import RemoteDefaultsDrawer from '../components/RemoteDefaultsDrawer'
|
||||||
import RemoteEditDrawer from '../components/RemoteEditDrawer'
|
import RemoteEditDrawer from '../components/RemoteEditDrawer'
|
||||||
@@ -870,6 +885,7 @@ function ConfigSection() {
|
|||||||
const activeConfigFile = usePersistedStore((state) => state.activeConfigFile)
|
const activeConfigFile = usePersistedStore((state) => state.activeConfigFile)
|
||||||
|
|
||||||
const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false)
|
const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false)
|
||||||
|
const [isSyncDrawerOpen, setIsSyncDrawerOpen] = useState(false)
|
||||||
const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false)
|
const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false)
|
||||||
const [focusedConfigId, setFocusedConfigId] = useState<string | null>(null)
|
const [focusedConfigId, setFocusedConfigId] = useState<string | null>(null)
|
||||||
|
|
||||||
@@ -878,44 +894,40 @@ function ConfigSection() {
|
|||||||
<BaseHeader
|
<BaseHeader
|
||||||
title="Config"
|
title="Config"
|
||||||
endContent={
|
endContent={
|
||||||
<Button
|
<Dropdown>
|
||||||
onPress={async () => {
|
<DropdownTrigger>
|
||||||
// setFocusedConfig({
|
<Button variant="faded" color="primary" data-focus-visible="false">
|
||||||
// id: undefined,
|
Add Config
|
||||||
// label: filePath.split('/').pop() || 'New Config',
|
</Button>
|
||||||
// isEncrypted: configText.includes('RCLONE_ENCRYPT_V0:'),
|
</DropdownTrigger>
|
||||||
// pass: undefined,
|
<DropdownMenu
|
||||||
// sync: undefined,
|
onAction={(key) => {
|
||||||
// })
|
setTimeout(() => {
|
||||||
setIsCreateDrawerOpen(true)
|
if (key === 'import') {
|
||||||
|
setIsCreateDrawerOpen(true)
|
||||||
// await ask(
|
} else {
|
||||||
// 'Config loaded successfully, you can now remove the file.',
|
setIsSyncDrawerOpen(true)
|
||||||
// {
|
}
|
||||||
// title: 'Success',
|
}, 100)
|
||||||
// kind: 'info',
|
}}
|
||||||
// okLabel: 'OK',
|
variant="faded"
|
||||||
// cancelLabel: '',
|
>
|
||||||
// }
|
<DropdownItem
|
||||||
// )
|
key="import"
|
||||||
// } catch (error) {
|
description="Edit using the CLI or UI"
|
||||||
// console.error(error)
|
startContent={<PlusIcon />}
|
||||||
// await ask('Failed to load config file', {
|
>
|
||||||
// title: 'Error',
|
Import Config
|
||||||
// kind: 'error',
|
</DropdownItem>
|
||||||
// okLabel: 'OK',
|
<DropdownItem
|
||||||
// cancelLabel: '',
|
key="sync"
|
||||||
// })
|
description="Update using Git or similar"
|
||||||
// }
|
startContent={<ImportIcon />}
|
||||||
}}
|
>
|
||||||
isIconOnly={true}
|
Sync Config
|
||||||
variant="faded"
|
</DropdownItem>
|
||||||
color="primary"
|
</DropdownMenu>
|
||||||
data-focus-visible="false"
|
</Dropdown>
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<PlusIcon className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-2 p-4">
|
<div className="flex flex-col gap-2 p-4">
|
||||||
@@ -964,7 +976,8 @@ function ConfigSection() {
|
|||||||
}}
|
}}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
configFile.id === 'default' ||
|
configFile.id === 'default' ||
|
||||||
configFile.id === activeConfigFile?.id
|
configFile.id === activeConfigFile?.id ||
|
||||||
|
Boolean(configFile.sync)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PencilIcon className="w-4 h-4" />
|
<PencilIcon className="w-4 h-4" />
|
||||||
@@ -999,14 +1012,16 @@ function ConfigSection() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = await getConfigPath({
|
if (!configFile.sync) {
|
||||||
id: configFile.id!,
|
const path = await getConfigPath({
|
||||||
validate: true,
|
id: configFile.id!,
|
||||||
})
|
validate: true,
|
||||||
|
})
|
||||||
|
|
||||||
await remove(path.replace('rclone.conf', ''), {
|
await remove(path.replace('rclone.conf', ''), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (activeConfigFile?.id === configFile.id) {
|
if (activeConfigFile?.id === configFile.id) {
|
||||||
usePersistedStore
|
usePersistedStore
|
||||||
@@ -1043,6 +1058,13 @@ function ConfigSection() {
|
|||||||
}}
|
}}
|
||||||
id={focusedConfigId}
|
id={focusedConfigId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConfigSyncDrawer
|
||||||
|
isOpen={isSyncDrawerOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsSyncDrawerOpen(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -4,4 +4,5 @@ export interface ConfigFile {
|
|||||||
sync: string | undefined
|
sync: string | undefined
|
||||||
isEncrypted: boolean
|
isEncrypted: boolean
|
||||||
pass: string | undefined
|
pass: string | undefined
|
||||||
|
passCommand: string | undefined
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user