1281 lines
35 KiB
TypeScript
1281 lines
35 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
import type { Dispatch, MutableRefObject, SetStateAction } from 'react'
|
|
import { invoke } from '@tauri-apps/api/core'
|
|
import { isTauri, mockInvoke } from '../mock-tauri'
|
|
import { formatFolderPickerActionError, pickFolder } from '../utils/vault-dialog'
|
|
import { loadVaultList, saveVaultList } from '../utils/vaultListStore'
|
|
import type { VaultOption } from '../components/StatusBar'
|
|
import { trackEvent } from '../lib/telemetry'
|
|
import { buildAllVaults } from '../utils/vaultCollections'
|
|
import { useVaultReorderAction } from './useVaultReorderAction'
|
|
import { useWorkspaceIdentityActions } from './useWorkspaceIdentityActions'
|
|
import { sanitizeDefaultWorkspacePath } from './vaultSwitcherSanitization'
|
|
|
|
export type { PersistedVaultList } from '../utils/vaultListStore'
|
|
|
|
export const GETTING_STARTED_LABEL = 'Getting Started'
|
|
|
|
declare const __DEMO_VAULT_PATH__: string | undefined
|
|
|
|
/** Build-time demo vault path (dev only). In production Tauri builds this is
|
|
* undefined and the real path is resolved at runtime via get_default_vault_path. */
|
|
const STATIC_DEFAULT_PATH = typeof __DEMO_VAULT_PATH__ !== 'undefined' ? __DEMO_VAULT_PATH__ : ''
|
|
|
|
export const DEFAULT_VAULTS: VaultOption[] = [
|
|
{ label: GETTING_STARTED_LABEL, path: STATIC_DEFAULT_PATH },
|
|
]
|
|
|
|
interface UseVaultSwitcherOptions {
|
|
onSwitch: () => void
|
|
onToast: (msg: string) => void
|
|
}
|
|
|
|
interface PersistedVaultState {
|
|
defaultAvailable: boolean
|
|
defaultPath: string
|
|
defaultWorkspacePath: string | null
|
|
extraVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
lastPersistedSnapshotRef: MutableRefObject<string | null>
|
|
loaded: boolean
|
|
selectedVaultPath: string | null
|
|
setDefaultAvailable: Dispatch<SetStateAction<boolean>>
|
|
setDefaultWorkspacePath: Dispatch<SetStateAction<string | null>>
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
vaultPath: string
|
|
}
|
|
|
|
interface VaultCollections {
|
|
allVaults: VaultOption[]
|
|
defaultVaults: VaultOption[]
|
|
isGettingStartedHidden: boolean
|
|
}
|
|
|
|
interface PersistedVaultStore {
|
|
defaultAvailable: boolean
|
|
defaultPath: string
|
|
defaultWorkspacePath: string | null
|
|
extraVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
lastPersistedSnapshotRef: MutableRefObject<string | null>
|
|
loaded: boolean
|
|
selectedVaultPath: string | null
|
|
setDefaultAvailable: Dispatch<SetStateAction<boolean>>
|
|
setDefaultPath: Dispatch<SetStateAction<string>>
|
|
setDefaultWorkspacePath: Dispatch<SetStateAction<string | null>>
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
setLoaded: Dispatch<SetStateAction<boolean>>
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
vaultPath: string
|
|
}
|
|
|
|
interface VaultActionOptions extends PersistedVaultState, VaultCollections {
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
onToastRef: MutableRefObject<(msg: string) => void>
|
|
}
|
|
|
|
interface RegisteredVaultSelection {
|
|
nextDefaultAvailable: boolean
|
|
nextExtraVaults: VaultOption[]
|
|
nextHiddenDefaults: string[]
|
|
nextSelectedVaultPath: string
|
|
}
|
|
|
|
interface RegisterVaultSelectionOptions {
|
|
verifyAvailability?: boolean
|
|
}
|
|
|
|
interface RestoreGettingStartedOptions {
|
|
defaultPath: string
|
|
onToastRef: MutableRefObject<(msg: string) => void>
|
|
setDefaultAvailable: Dispatch<SetStateAction<boolean>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
switchVault: (path: string) => void
|
|
}
|
|
|
|
interface RemainingVaultOptions {
|
|
defaultVaults: VaultOption[]
|
|
extraVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
isDefault: boolean
|
|
removedPath: string
|
|
}
|
|
|
|
interface RemoveVaultStateOptions extends RemainingVaultOptions {
|
|
defaultWorkspacePath: string | null
|
|
selectedVaultPath: string | null
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
setDefaultWorkspacePath: Dispatch<SetStateAction<string | null>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
vaultPath: string
|
|
}
|
|
|
|
interface RemoveVaultActionOptions {
|
|
defaultWorkspacePath: string | null
|
|
defaultVaults: VaultOption[]
|
|
extraVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
onToastRef: MutableRefObject<(msg: string) => void>
|
|
setDefaultWorkspacePath: Dispatch<SetStateAction<string | null>>
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
selectedVaultPath: string | null
|
|
vaultPath: string
|
|
}
|
|
|
|
interface VaultPathInput {
|
|
path: string
|
|
}
|
|
|
|
function labelFromPath({ path }: VaultPathInput): string {
|
|
return path.split('/').pop() || 'Local Vault'
|
|
}
|
|
|
|
function tauriCall<T>(command: string, args: Record<string, unknown>): Promise<T> {
|
|
return isTauri() ? invoke<T>(command, args) : mockInvoke<T>(command, args)
|
|
}
|
|
|
|
function serializePersistedVaultSnapshot(
|
|
vaults: VaultOption[],
|
|
activeVault: string | null,
|
|
hiddenDefaults: string[],
|
|
defaultWorkspacePath: string | null,
|
|
): string {
|
|
return JSON.stringify({
|
|
activeVault,
|
|
defaultWorkspacePath,
|
|
hiddenDefaults,
|
|
vaults: vaults.map(({ label, path, alias, shortLabel, color, icon, mounted }) => ({
|
|
label,
|
|
path,
|
|
alias: alias ?? null,
|
|
shortLabel: shortLabel ?? null,
|
|
color: color ?? null,
|
|
icon: icon ?? null,
|
|
mounted: mounted !== false,
|
|
})),
|
|
})
|
|
}
|
|
|
|
async function resolveDefaultPath(): Promise<string> {
|
|
if (STATIC_DEFAULT_PATH) {
|
|
return STATIC_DEFAULT_PATH
|
|
}
|
|
|
|
try {
|
|
return await tauriCall<string>('get_default_vault_path', {})
|
|
} catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
function syncDefaultVaultExport(path: string) {
|
|
DEFAULT_VAULTS[0] = { label: GETTING_STARTED_LABEL, path }
|
|
}
|
|
|
|
function selectedBridgeVaultPath(selectedVaultPath: string | null): string | null {
|
|
const path = selectedVaultPath?.trim()
|
|
return path ? path : null
|
|
}
|
|
|
|
function isCanonicalGettingStartedPath(path: string, resolvedDefaultPath: string): boolean {
|
|
return path === resolvedDefaultPath
|
|
}
|
|
|
|
function isUnavailableGettingStartedVault(vault: VaultOption): boolean {
|
|
return vault.label === GETTING_STARTED_LABEL && vault.available === false
|
|
}
|
|
|
|
function shouldDropPersistedGettingStartedVault(vault: VaultOption, resolvedDefaultPath: string): boolean {
|
|
return isCanonicalGettingStartedPath(vault.path, resolvedDefaultPath) && isUnavailableGettingStartedVault(vault)
|
|
}
|
|
|
|
async function checkVaultAvailability(path: string): Promise<boolean> {
|
|
if (!path) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
return await tauriCall<boolean>('check_vault_exists', { path })
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function loadInitialVaultState() {
|
|
const [vaultListResult, defaultPathResult] = await Promise.allSettled([
|
|
loadVaultList(),
|
|
resolveDefaultPath(),
|
|
])
|
|
const { vaults, activeVault, defaultWorkspacePath, hiddenDefaults } = vaultListResult.status === 'fulfilled'
|
|
? vaultListResult.value
|
|
: { vaults: [], activeVault: null, defaultWorkspacePath: null, hiddenDefaults: [] }
|
|
const resolvedDefaultPath = defaultPathResult.status === 'fulfilled'
|
|
? defaultPathResult.value
|
|
: ''
|
|
const defaultAvailable = await checkVaultAvailability(resolvedDefaultPath)
|
|
|
|
if (vaultListResult.status === 'rejected') {
|
|
console.warn('Failed to load vault list:', vaultListResult.reason)
|
|
}
|
|
|
|
const sanitizedState = sanitizeCanonicalGettingStartedState({
|
|
activeVault,
|
|
defaultAvailable,
|
|
hiddenDefaults,
|
|
resolvedDefaultPath,
|
|
vaults,
|
|
})
|
|
const persistedSnapshot = serializePersistedVaultSnapshot(vaults, activeVault, hiddenDefaults, defaultWorkspacePath)
|
|
const sanitizedDefaultWorkspacePath = sanitizeDefaultWorkspacePath({
|
|
activeVault: sanitizedState.activeVault,
|
|
defaultAvailable,
|
|
defaultWorkspacePath,
|
|
resolvedDefaultPath,
|
|
vaults: sanitizedState.vaults,
|
|
})
|
|
|
|
return {
|
|
...sanitizedState,
|
|
defaultWorkspacePath: sanitizedDefaultWorkspacePath,
|
|
persistedSnapshot,
|
|
}
|
|
}
|
|
|
|
function sanitizeCanonicalGettingStartedState({
|
|
activeVault,
|
|
defaultAvailable,
|
|
hiddenDefaults,
|
|
resolvedDefaultPath,
|
|
vaults,
|
|
}: {
|
|
activeVault: string | null
|
|
defaultAvailable: boolean
|
|
hiddenDefaults: string[]
|
|
resolvedDefaultPath: string
|
|
vaults: VaultOption[]
|
|
}) {
|
|
if (!resolvedDefaultPath) {
|
|
return { activeVault, defaultAvailable, hiddenDefaults, resolvedDefaultPath, vaults }
|
|
}
|
|
|
|
const filteredVaults = vaults.filter(
|
|
(vault) => !shouldDropPersistedGettingStartedVault(vault, resolvedDefaultPath),
|
|
)
|
|
const removedStarterPaths = new Set(
|
|
vaults
|
|
.filter((vault) => shouldDropPersistedGettingStartedVault(vault, resolvedDefaultPath))
|
|
.map((vault) => vault.path),
|
|
)
|
|
const sanitizedActiveVault = resolveSanitizedGettingStartedSelection({
|
|
activeVault,
|
|
defaultAvailable,
|
|
filteredVaults,
|
|
removedStarterPaths,
|
|
resolvedDefaultPath,
|
|
})
|
|
|
|
return {
|
|
activeVault: sanitizedActiveVault,
|
|
defaultAvailable,
|
|
hiddenDefaults,
|
|
resolvedDefaultPath,
|
|
vaults: filteredVaults,
|
|
}
|
|
}
|
|
|
|
function resolveSanitizedGettingStartedSelection({
|
|
activeVault,
|
|
defaultAvailable,
|
|
filteredVaults,
|
|
removedStarterPaths,
|
|
resolvedDefaultPath,
|
|
}: {
|
|
activeVault: string | null
|
|
defaultAvailable: boolean
|
|
filteredVaults: VaultOption[]
|
|
removedStarterPaths: Set<string>
|
|
resolvedDefaultPath: string
|
|
}): string | null {
|
|
if (!activeVault || !removedStarterPaths.has(activeVault)) {
|
|
return activeVault
|
|
}
|
|
|
|
if (isCanonicalGettingStartedPath(activeVault, resolvedDefaultPath) && defaultAvailable) {
|
|
return activeVault
|
|
}
|
|
|
|
return filteredVaults[0]?.path ?? null
|
|
}
|
|
|
|
function buildDefaultVaults({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
}: {
|
|
defaultAvailable: boolean
|
|
defaultPath: string
|
|
}): VaultOption[] {
|
|
if (!defaultAvailable || !defaultPath) {
|
|
return []
|
|
}
|
|
|
|
return [{ label: GETTING_STARTED_LABEL, path: defaultPath, managedDefault: true }]
|
|
}
|
|
|
|
function buildVisibleDefaultVaults({
|
|
defaultVaults,
|
|
hiddenDefaults,
|
|
}: {
|
|
defaultVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
}): VaultOption[] {
|
|
return defaultVaults.filter(vault => !hiddenDefaults.includes(vault.path))
|
|
}
|
|
|
|
function applyResolvedDefaultPath({
|
|
defaultAvailable,
|
|
resolvedDefaultPath,
|
|
setDefaultAvailable,
|
|
setDefaultPath,
|
|
}: {
|
|
defaultAvailable: boolean
|
|
resolvedDefaultPath: string
|
|
setDefaultAvailable: Dispatch<SetStateAction<boolean>>
|
|
setDefaultPath: Dispatch<SetStateAction<string>>
|
|
}) {
|
|
setDefaultAvailable(defaultAvailable)
|
|
|
|
if (!resolvedDefaultPath) {
|
|
return
|
|
}
|
|
|
|
setDefaultPath(resolvedDefaultPath)
|
|
syncDefaultVaultExport(resolvedDefaultPath)
|
|
}
|
|
|
|
function normalizeInitialSelectedVaultPath(
|
|
activeVault: string | null,
|
|
resolvedDefaultPath: string,
|
|
vaults: VaultOption[],
|
|
): string | null {
|
|
if (!activeVault) {
|
|
return null
|
|
}
|
|
|
|
const isRememberedDefaultOnlySelection = activeVault === resolvedDefaultPath && vaults.length === 0
|
|
return isRememberedDefaultOnlySelection ? null : activeVault
|
|
}
|
|
|
|
function applyInitialVaultTarget({
|
|
activeVault,
|
|
resolvedDefaultPath,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
onSwitchRef,
|
|
}: {
|
|
activeVault: string | null
|
|
resolvedDefaultPath: string
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
}) {
|
|
if (activeVault) {
|
|
setVaultPath(activeVault)
|
|
setSelectedVaultPath(activeVault)
|
|
onSwitchRef.current()
|
|
return
|
|
}
|
|
|
|
if (resolvedDefaultPath) {
|
|
setVaultPath(resolvedDefaultPath)
|
|
}
|
|
}
|
|
|
|
function useVaultCollections(
|
|
defaultAvailable: boolean,
|
|
defaultPath: string,
|
|
hiddenDefaults: string[],
|
|
extraVaults: VaultOption[],
|
|
): VaultCollections {
|
|
const defaultVaults = useMemo(
|
|
() => buildDefaultVaults({ defaultAvailable, defaultPath }),
|
|
[defaultAvailable, defaultPath],
|
|
)
|
|
const visibleDefaults = useMemo(
|
|
() => buildVisibleDefaultVaults({ defaultVaults, hiddenDefaults }),
|
|
[defaultVaults, hiddenDefaults],
|
|
)
|
|
const allVaults = useMemo(
|
|
() => buildAllVaults({ visibleDefaults, extraVaults, hiddenDefaults }),
|
|
[extraVaults, hiddenDefaults, visibleDefaults],
|
|
)
|
|
const isGettingStartedHidden = useMemo(
|
|
() => hiddenDefaults.includes(defaultPath),
|
|
[defaultPath, hiddenDefaults],
|
|
)
|
|
|
|
return { allVaults, defaultVaults, isGettingStartedHidden }
|
|
}
|
|
|
|
function useLoadPersistedVaultState(
|
|
store: PersistedVaultStore,
|
|
onSwitchRef: MutableRefObject<() => void>,
|
|
) {
|
|
const {
|
|
lastPersistedSnapshotRef,
|
|
setDefaultAvailable,
|
|
setDefaultPath,
|
|
setDefaultWorkspacePath,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setLoaded,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
} = store
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
|
|
loadInitialVaultState()
|
|
.then(({ activeVault, defaultAvailable, defaultWorkspacePath, hiddenDefaults: hidden, persistedSnapshot, resolvedDefaultPath, vaults }) => {
|
|
if (cancelled) return
|
|
|
|
lastPersistedSnapshotRef.current = persistedSnapshot
|
|
setExtraVaults(vaults)
|
|
setHiddenDefaults(hidden)
|
|
setDefaultWorkspacePath(defaultWorkspacePath ?? activeVault ?? null)
|
|
applyResolvedDefaultPath({
|
|
defaultAvailable,
|
|
resolvedDefaultPath,
|
|
setDefaultAvailable,
|
|
setDefaultPath,
|
|
})
|
|
applyInitialVaultTarget({
|
|
activeVault: normalizeInitialSelectedVaultPath(activeVault, resolvedDefaultPath, vaults),
|
|
resolvedDefaultPath,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
onSwitchRef,
|
|
})
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) {
|
|
setLoaded(true)
|
|
}
|
|
})
|
|
|
|
return () => { cancelled = true }
|
|
}, [lastPersistedSnapshotRef, onSwitchRef, setDefaultAvailable, setDefaultPath, setDefaultWorkspacePath, setExtraVaults, setHiddenDefaults, setLoaded, setSelectedVaultPath, setVaultPath])
|
|
}
|
|
|
|
function usePersistedVaultStorage(store: PersistedVaultStore) {
|
|
const { defaultWorkspacePath, extraVaults, hiddenDefaults, lastPersistedSnapshotRef, loaded, selectedVaultPath } = store
|
|
|
|
useEffect(() => {
|
|
if (!loaded) return
|
|
|
|
const snapshot = serializePersistedVaultSnapshot(extraVaults, selectedVaultPath, hiddenDefaults, defaultWorkspacePath)
|
|
|
|
if (lastPersistedSnapshotRef.current === snapshot) {
|
|
return
|
|
}
|
|
|
|
saveVaultList(extraVaults, selectedVaultPath, hiddenDefaults, defaultWorkspacePath)
|
|
.then(() => {
|
|
lastPersistedSnapshotRef.current = snapshot
|
|
})
|
|
.catch(err => {
|
|
console.warn('Failed to persist vault list:', err)
|
|
})
|
|
}, [defaultWorkspacePath, extraVaults, hiddenDefaults, lastPersistedSnapshotRef, loaded, selectedVaultPath])
|
|
}
|
|
|
|
function usePersistedVaultState(onSwitchRef: MutableRefObject<() => void>): PersistedVaultState {
|
|
const [vaultPath, setVaultPath] = useState(STATIC_DEFAULT_PATH)
|
|
const [selectedVaultPath, setSelectedVaultPath] = useState<string | null>(null)
|
|
const [defaultWorkspacePath, setDefaultWorkspacePath] = useState<string | null>(null)
|
|
const [extraVaults, setExtraVaults] = useState<VaultOption[]>([])
|
|
const [hiddenDefaults, setHiddenDefaults] = useState<string[]>([])
|
|
const [defaultAvailable, setDefaultAvailable] = useState(false)
|
|
const lastPersistedSnapshotRef = useRef<string | null>(null)
|
|
const [loaded, setLoaded] = useState(false)
|
|
const [defaultPath, setDefaultPath] = useState(STATIC_DEFAULT_PATH)
|
|
|
|
const store: PersistedVaultStore = {
|
|
defaultAvailable,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
loaded,
|
|
selectedVaultPath,
|
|
setDefaultAvailable,
|
|
setDefaultPath,
|
|
setDefaultWorkspacePath,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setLoaded,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
vaultPath,
|
|
}
|
|
|
|
useLoadPersistedVaultState(store, onSwitchRef)
|
|
usePersistedVaultStorage(store)
|
|
|
|
return {
|
|
defaultAvailable,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
loaded,
|
|
selectedVaultPath,
|
|
setDefaultWorkspacePath,
|
|
setDefaultAvailable,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
vaultPath,
|
|
}
|
|
}
|
|
|
|
function useMcpBridgeVaultSync(loaded: boolean, selectedVaultPath: string | null) {
|
|
useEffect(() => {
|
|
if (!loaded) return
|
|
|
|
tauriCall<string>('sync_mcp_bridge_vault', {
|
|
vaultPath: selectedBridgeVaultPath(selectedVaultPath),
|
|
}).catch(err => {
|
|
console.warn('Failed to sync MCP bridge vault:', err)
|
|
})
|
|
}, [loaded, selectedVaultPath])
|
|
}
|
|
|
|
function formatGettingStartedRestoreError(err: unknown): string {
|
|
const message =
|
|
typeof err === 'string'
|
|
? err
|
|
: err instanceof Error
|
|
? err.message
|
|
: `${err}`
|
|
|
|
const networkErrors = [
|
|
'unable to access',
|
|
'Could not resolve host',
|
|
'network',
|
|
'timed out',
|
|
]
|
|
|
|
if (networkErrors.some(fragment => message.includes(fragment))) {
|
|
return 'Getting Started requires internet. Clone it later.'
|
|
}
|
|
|
|
return `Could not prepare Getting Started vault: ${message}`
|
|
}
|
|
|
|
function formatCreateEmptyVaultError(err: unknown): string {
|
|
const message =
|
|
typeof err === 'string'
|
|
? err
|
|
: err instanceof Error
|
|
? err.message
|
|
: `${err}`
|
|
|
|
if (message.includes('Choose an empty folder')) {
|
|
return message
|
|
}
|
|
|
|
return `Could not create empty vault: ${message}`
|
|
}
|
|
|
|
async function ensureGettingStartedVaultReady(path: string): Promise<void> {
|
|
const exists = await tauriCall<boolean>('check_vault_exists', { path })
|
|
if (!exists) {
|
|
await tauriCall<string>('create_getting_started_vault', { targetPath: path })
|
|
}
|
|
}
|
|
|
|
function addVaultToList({
|
|
setExtraVaults,
|
|
path,
|
|
label,
|
|
}: {
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
path: string
|
|
label: string
|
|
}) {
|
|
setExtraVaults(previousVaults => {
|
|
const exists = previousVaults.some(vault => vault.path === path)
|
|
return exists ? previousVaults : [...previousVaults, { label, path, available: true, mounted: true }]
|
|
})
|
|
}
|
|
|
|
function upsertAvailableVaultOption(
|
|
extraVaults: VaultOption[],
|
|
path: string,
|
|
label: string,
|
|
): VaultOption[] {
|
|
const existingVault = extraVaults.find((vault) => vault.path === path)
|
|
if (!existingVault) {
|
|
return [...extraVaults, { label, path, available: true, mounted: true }]
|
|
}
|
|
|
|
return extraVaults.map((vault) => (
|
|
vault.path === path
|
|
? { ...vault, label: vault.label || label, available: true }
|
|
: vault
|
|
))
|
|
}
|
|
|
|
function buildRegisteredVaultSelection({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
label,
|
|
path,
|
|
}: {
|
|
defaultAvailable: boolean
|
|
defaultPath: string
|
|
extraVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
label: string
|
|
path: string
|
|
}): RegisteredVaultSelection {
|
|
const isCanonicalDefaultVault = path === defaultPath && defaultPath.length > 0
|
|
|
|
return {
|
|
nextDefaultAvailable: isCanonicalDefaultVault ? true : defaultAvailable,
|
|
nextExtraVaults: isCanonicalDefaultVault
|
|
? extraVaults.filter((vault) => vault.path !== path)
|
|
: upsertAvailableVaultOption(extraVaults, path, label),
|
|
nextHiddenDefaults: isCanonicalDefaultVault
|
|
? hiddenDefaults.filter((hiddenPath) => hiddenPath !== path)
|
|
: hiddenDefaults,
|
|
nextSelectedVaultPath: path,
|
|
}
|
|
}
|
|
|
|
async function persistRegisteredVaultSelection({
|
|
defaultWorkspacePath,
|
|
hiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
selectedVaultPath,
|
|
vaults,
|
|
}: {
|
|
defaultWorkspacePath: string | null
|
|
hiddenDefaults: string[]
|
|
lastPersistedSnapshotRef: MutableRefObject<string | null>
|
|
selectedVaultPath: string
|
|
vaults: VaultOption[]
|
|
}): Promise<void> {
|
|
const nextSnapshot = serializePersistedVaultSnapshot(
|
|
vaults,
|
|
selectedVaultPath,
|
|
hiddenDefaults,
|
|
defaultWorkspacePath,
|
|
)
|
|
await saveVaultList(vaults, selectedVaultPath, hiddenDefaults, defaultWorkspacePath)
|
|
lastPersistedSnapshotRef.current = nextSnapshot
|
|
}
|
|
|
|
function applyRegisteredVaultSelection({
|
|
nextDefaultAvailable,
|
|
nextExtraVaults,
|
|
nextHiddenDefaults,
|
|
nextSelectedVaultPath,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
}: RegisteredVaultSelection & {
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
setDefaultAvailable: Dispatch<SetStateAction<boolean>>
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
}) {
|
|
setDefaultAvailable(nextDefaultAvailable)
|
|
setExtraVaults(nextExtraVaults)
|
|
setHiddenDefaults(nextHiddenDefaults)
|
|
switchVaultPath({
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
onSwitchRef,
|
|
path: nextSelectedVaultPath,
|
|
})
|
|
}
|
|
|
|
function switchVaultPath({
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
onSwitchRef,
|
|
path,
|
|
}: {
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
path: string
|
|
}) {
|
|
trackEvent('vault_switched')
|
|
setSelectedVaultPath(path)
|
|
setVaultPath(path)
|
|
onSwitchRef.current()
|
|
}
|
|
|
|
async function ensureVaultCanBeRegistered(path: string): Promise<void> {
|
|
const exists = await checkVaultAvailability(path)
|
|
if (!exists) throw new Error('Selected folder is not available')
|
|
}
|
|
|
|
interface RegisterVaultSelectionActionDeps {
|
|
defaultAvailable: boolean
|
|
defaultPath: string
|
|
defaultWorkspacePath: string | null
|
|
extraVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
lastPersistedSnapshotRef: MutableRefObject<string | null>
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
setDefaultAvailable: Dispatch<SetStateAction<boolean>>
|
|
setDefaultWorkspacePath: Dispatch<SetStateAction<string | null>>
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
}
|
|
|
|
function listRemainingVaults({
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
isDefault,
|
|
removedPath,
|
|
}: RemainingVaultOptions) {
|
|
const visibleDefaults = defaultVaults.filter(vault => (
|
|
vault.path !== removedPath
|
|
&& (!isDefault || !hiddenDefaults.includes(vault.path))
|
|
))
|
|
|
|
return [...visibleDefaults, ...extraVaults.filter(vault => vault.path !== removedPath)]
|
|
}
|
|
|
|
function removeVaultFromState({
|
|
defaultWorkspacePath,
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
isDefault,
|
|
onSwitchRef,
|
|
removedPath,
|
|
setExtraVaults,
|
|
setDefaultWorkspacePath,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
selectedVaultPath,
|
|
vaultPath,
|
|
}: RemoveVaultStateOptions) {
|
|
if (isDefault) {
|
|
setHiddenDefaults(previousHidden => previousHidden.includes(removedPath) ? previousHidden : [...previousHidden, removedPath])
|
|
setExtraVaults(previousVaults => previousVaults.filter(vault => vault.path !== removedPath))
|
|
} else {
|
|
setExtraVaults(previousVaults => previousVaults.filter(vault => vault.path !== removedPath))
|
|
}
|
|
|
|
if (vaultPath !== removedPath) {
|
|
if (selectedVaultPath === removedPath) {
|
|
setSelectedVaultPath(null)
|
|
}
|
|
if (defaultWorkspacePath === removedPath) {
|
|
setDefaultWorkspacePath(null)
|
|
}
|
|
return
|
|
}
|
|
|
|
const remainingVaults = listRemainingVaults({
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
isDefault,
|
|
removedPath,
|
|
})
|
|
if (remainingVaults.length === 0) {
|
|
setSelectedVaultPath(null)
|
|
setDefaultWorkspacePath(null)
|
|
return
|
|
}
|
|
|
|
const nextPath = remainingVaults[0].path
|
|
setSelectedVaultPath(nextPath)
|
|
if (defaultWorkspacePath === removedPath) {
|
|
setDefaultWorkspacePath(nextPath)
|
|
}
|
|
setVaultPath(nextPath)
|
|
onSwitchRef.current()
|
|
}
|
|
|
|
function getRemovedVaultLabel({
|
|
path,
|
|
defaultVaults,
|
|
extraVaults,
|
|
}: {
|
|
path: string
|
|
defaultVaults: VaultOption[]
|
|
extraVaults: VaultOption[]
|
|
}): string {
|
|
const removedVault = [...defaultVaults, ...extraVaults].find(vault => vault.path === path)
|
|
return removedVault?.label ?? labelFromPath({ path })
|
|
}
|
|
|
|
function useSwitchVaultAction(
|
|
onSwitchRef: MutableRefObject<() => void>,
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>,
|
|
setVaultPath: Dispatch<SetStateAction<string>>,
|
|
) {
|
|
return useCallback((path: string) => {
|
|
switchVaultPath({ setSelectedVaultPath, setVaultPath, onSwitchRef, path })
|
|
}, [onSwitchRef, setSelectedVaultPath, setVaultPath])
|
|
}
|
|
|
|
function useVaultClonedAction(
|
|
addAndSwitch: (path: string, label: string) => void,
|
|
onToastRef: MutableRefObject<(msg: string) => void>,
|
|
) {
|
|
return useCallback((path: string, label: string) => {
|
|
addAndSwitch(path, label)
|
|
onToastRef.current(`Vault "${label}" cloned and opened`)
|
|
}, [addAndSwitch, onToastRef])
|
|
}
|
|
|
|
function useRegisterVaultSelectionAction({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setDefaultWorkspacePath,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
}: RegisterVaultSelectionActionDeps) {
|
|
return useCallback(async (path: string, label: string, options: RegisterVaultSelectionOptions = {}) => {
|
|
if (options.verifyAvailability !== false) {
|
|
await ensureVaultCanBeRegistered(path)
|
|
}
|
|
|
|
const nextSelection = buildRegisteredVaultSelection({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
label,
|
|
path,
|
|
})
|
|
await persistRegisteredVaultSelection({
|
|
defaultWorkspacePath: defaultWorkspacePath ?? nextSelection.nextSelectedVaultPath,
|
|
hiddenDefaults: nextSelection.nextHiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
selectedVaultPath: nextSelection.nextSelectedVaultPath,
|
|
vaults: nextSelection.nextExtraVaults,
|
|
})
|
|
setDefaultWorkspacePath((currentDefaultPath) => currentDefaultPath ?? nextSelection.nextSelectedVaultPath)
|
|
applyRegisteredVaultSelection({
|
|
...nextSelection,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
})
|
|
}, [
|
|
defaultAvailable,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setDefaultWorkspacePath,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
])
|
|
}
|
|
|
|
function useSyncVaultSelectionAction({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
}: {
|
|
defaultAvailable: boolean
|
|
defaultPath: string
|
|
extraVaults: VaultOption[]
|
|
hiddenDefaults: string[]
|
|
onSwitchRef: MutableRefObject<() => void>
|
|
setDefaultAvailable: Dispatch<SetStateAction<boolean>>
|
|
setExtraVaults: Dispatch<SetStateAction<VaultOption[]>>
|
|
setHiddenDefaults: Dispatch<SetStateAction<string[]>>
|
|
setSelectedVaultPath: Dispatch<SetStateAction<string | null>>
|
|
setVaultPath: Dispatch<SetStateAction<string>>
|
|
}) {
|
|
return useCallback((path: string, label: string) => {
|
|
const nextSelection = buildRegisteredVaultSelection({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
label,
|
|
path,
|
|
})
|
|
applyRegisteredVaultSelection({
|
|
...nextSelection,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
})
|
|
}, [
|
|
defaultAvailable,
|
|
defaultPath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
])
|
|
}
|
|
|
|
function useOpenLocalFolderAction(
|
|
addAndSwitch: (path: string, label: string) => void,
|
|
onToastRef: MutableRefObject<(msg: string) => void>,
|
|
) {
|
|
return useCallback(async () => {
|
|
let path: string | null
|
|
try {
|
|
path = await pickFolder('Open vault folder')
|
|
} catch (err) {
|
|
onToastRef.current(formatFolderPickerActionError('Could not open vault folder', err))
|
|
return
|
|
}
|
|
|
|
if (!path) return
|
|
|
|
const label = labelFromPath({ path })
|
|
addAndSwitch(path, label)
|
|
onToastRef.current(`Vault "${label}" opened`)
|
|
}, [addAndSwitch, onToastRef])
|
|
}
|
|
|
|
function useCreateEmptyVaultAction(
|
|
addAndSwitch: (path: string, label: string) => void,
|
|
onToastRef: MutableRefObject<(msg: string) => void>,
|
|
) {
|
|
return useCallback(async () => {
|
|
let targetPath: string | null
|
|
try {
|
|
targetPath = await pickFolder('Choose where to create your vault')
|
|
} catch (err) {
|
|
onToastRef.current(formatFolderPickerActionError('Could not choose where to create your vault', err))
|
|
return
|
|
}
|
|
|
|
try {
|
|
if (!targetPath) return
|
|
const vaultPath = await tauriCall<string>('create_empty_vault', { targetPath })
|
|
const label = labelFromPath({ path: vaultPath })
|
|
addAndSwitch(vaultPath, label)
|
|
onToastRef.current(`Vault "${label}" created and opened`)
|
|
} catch (err) {
|
|
onToastRef.current(formatCreateEmptyVaultError(err))
|
|
}
|
|
}, [addAndSwitch, onToastRef])
|
|
}
|
|
|
|
function useRemoveVaultAction({
|
|
defaultWorkspacePath,
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
onSwitchRef,
|
|
onToastRef,
|
|
setExtraVaults,
|
|
setDefaultWorkspacePath,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
selectedVaultPath,
|
|
vaultPath,
|
|
}: RemoveVaultActionOptions) {
|
|
return useCallback((path: string) => {
|
|
const isDefault = defaultVaults.some(vault => vault.path === path)
|
|
|
|
removeVaultFromState({
|
|
defaultWorkspacePath,
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
isDefault,
|
|
onSwitchRef,
|
|
removedPath: path,
|
|
setExtraVaults,
|
|
setDefaultWorkspacePath,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
selectedVaultPath,
|
|
vaultPath,
|
|
})
|
|
onToastRef.current(`Vault "${getRemovedVaultLabel({ path, defaultVaults, extraVaults })}" removed from list`)
|
|
}, [
|
|
defaultWorkspacePath,
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
onSwitchRef,
|
|
onToastRef,
|
|
setExtraVaults,
|
|
setDefaultWorkspacePath,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
selectedVaultPath,
|
|
vaultPath,
|
|
])
|
|
}
|
|
|
|
function useRestoreGettingStartedAction(options: RestoreGettingStartedOptions) {
|
|
const { defaultPath, onToastRef, setDefaultAvailable, setHiddenDefaults, switchVault } = options
|
|
|
|
return useCallback(() => {
|
|
return restoreGettingStartedVault({
|
|
defaultPath,
|
|
onToastRef,
|
|
setDefaultAvailable,
|
|
setHiddenDefaults,
|
|
switchVault,
|
|
})
|
|
}, [defaultPath, onToastRef, setDefaultAvailable, setHiddenDefaults, switchVault])
|
|
}
|
|
|
|
function useVaultActions({
|
|
allVaults,
|
|
defaultAvailable,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
onSwitchRef,
|
|
onToastRef,
|
|
setDefaultAvailable,
|
|
setDefaultWorkspacePath,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
selectedVaultPath,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
vaultPath,
|
|
}: VaultActionOptions) {
|
|
const addVault = useCallback((path: string, label: string) => {
|
|
addVaultToList({ setExtraVaults, path, label })
|
|
}, [setExtraVaults])
|
|
|
|
const switchVault = useSwitchVaultAction(onSwitchRef, setSelectedVaultPath, setVaultPath)
|
|
const workspaceIdentityActions = useWorkspaceIdentityActions({ setDefaultWorkspacePath, setExtraVaults })
|
|
const reorderVaults = useVaultReorderAction({ allVaults, setExtraVaults })
|
|
const registerVaultSelection = useRegisterVaultSelectionAction({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
lastPersistedSnapshotRef,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setDefaultWorkspacePath,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
})
|
|
const syncVaultSelection = useSyncVaultSelectionAction({
|
|
defaultAvailable,
|
|
defaultPath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
onSwitchRef,
|
|
setDefaultAvailable,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
})
|
|
const addAndSwitch = useCallback((path: string, label: string) => {
|
|
addVault(path, label)
|
|
switchVault(path)
|
|
}, [addVault, switchVault])
|
|
|
|
return {
|
|
handleCreateEmptyVault: useCreateEmptyVaultAction(addAndSwitch, onToastRef),
|
|
handleOpenLocalFolder: useOpenLocalFolderAction(addAndSwitch, onToastRef),
|
|
handleVaultCloned: useVaultClonedAction(addAndSwitch, onToastRef),
|
|
registerVaultSelection,
|
|
removeVault: useRemoveVaultAction({
|
|
defaultWorkspacePath,
|
|
defaultVaults,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
onSwitchRef,
|
|
onToastRef,
|
|
setDefaultWorkspacePath,
|
|
setExtraVaults,
|
|
setHiddenDefaults,
|
|
setSelectedVaultPath,
|
|
setVaultPath,
|
|
selectedVaultPath,
|
|
vaultPath,
|
|
}),
|
|
reorderVaults,
|
|
restoreGettingStarted: useRestoreGettingStartedAction({
|
|
defaultPath,
|
|
onToastRef,
|
|
setDefaultAvailable,
|
|
setHiddenDefaults,
|
|
switchVault,
|
|
}),
|
|
setDefaultWorkspace: workspaceIdentityActions.setDefaultWorkspace,
|
|
syncVaultSelection,
|
|
switchVault,
|
|
updateWorkspaceIdentity: workspaceIdentityActions.updateWorkspaceIdentity,
|
|
}
|
|
}
|
|
|
|
async function restoreGettingStartedVault({
|
|
defaultPath,
|
|
onToastRef,
|
|
setDefaultAvailable,
|
|
setHiddenDefaults,
|
|
switchVault,
|
|
}: RestoreGettingStartedOptions) {
|
|
if (!defaultPath) {
|
|
onToastRef.current('Could not resolve the Getting Started vault path')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await ensureGettingStartedVaultReady(defaultPath)
|
|
setDefaultAvailable(true)
|
|
setHiddenDefaults(previousHidden => previousHidden.filter(path => path !== defaultPath))
|
|
switchVault(defaultPath)
|
|
onToastRef.current('Getting Started vault ready')
|
|
} catch (err) {
|
|
onToastRef.current(formatGettingStartedRestoreError(err))
|
|
}
|
|
}
|
|
|
|
/** Manages vault path, extra vaults, switching, cloning, and local folder opening.
|
|
* Vault list and active vault are persisted via Tauri backend to survive app updates. */
|
|
export function useVaultSwitcher({ onSwitch, onToast }: UseVaultSwitcherOptions) {
|
|
const onSwitchRef = useRef(onSwitch)
|
|
const onToastRef = useRef(onToast)
|
|
useEffect(() => { onSwitchRef.current = onSwitch; onToastRef.current = onToast })
|
|
|
|
const persistedState = usePersistedVaultState(onSwitchRef)
|
|
const {
|
|
defaultAvailable,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
extraVaults,
|
|
hiddenDefaults,
|
|
loaded,
|
|
selectedVaultPath,
|
|
vaultPath,
|
|
} = persistedState
|
|
const { allVaults, defaultVaults, isGettingStartedHidden } = useVaultCollections(
|
|
defaultAvailable,
|
|
defaultPath,
|
|
hiddenDefaults,
|
|
extraVaults,
|
|
)
|
|
useMcpBridgeVaultSync(loaded, selectedVaultPath)
|
|
const {
|
|
handleCreateEmptyVault,
|
|
handleOpenLocalFolder,
|
|
handleVaultCloned,
|
|
registerVaultSelection,
|
|
removeVault,
|
|
reorderVaults,
|
|
restoreGettingStarted,
|
|
setDefaultWorkspace,
|
|
syncVaultSelection,
|
|
switchVault,
|
|
updateWorkspaceIdentity,
|
|
} = useVaultActions({
|
|
...persistedState,
|
|
allVaults,
|
|
defaultVaults,
|
|
isGettingStartedHidden,
|
|
onSwitchRef,
|
|
onToastRef,
|
|
})
|
|
|
|
return {
|
|
allVaults,
|
|
defaultPath,
|
|
defaultWorkspacePath,
|
|
handleCreateEmptyVault,
|
|
handleOpenLocalFolder,
|
|
handleVaultCloned,
|
|
isGettingStartedHidden,
|
|
loaded,
|
|
registerVaultSelection,
|
|
removeVault,
|
|
reorderVaults,
|
|
restoreGettingStarted,
|
|
setDefaultWorkspace,
|
|
selectedVaultPath,
|
|
syncVaultSelection,
|
|
switchVault,
|
|
updateWorkspaceIdentity,
|
|
vaultPath,
|
|
}
|
|
}
|