{tCount("unread_count", { count: unread })}
} ++ + {text} +
++ + {text} +
+ ); +} diff --git a/components/files-sync/files-sync-text.ts b/components/files-sync/files-sync-text.ts new file mode 100644 index 000000000..50c395ba0 --- /dev/null +++ b/components/files-sync/files-sync-text.ts @@ -0,0 +1,46 @@ +import type { FilesRoot } from "@/lib/desktop/types"; +import type { FilesSyncActivityKind, FilesSyncItemStatus } from "@/lib/files-sync/types"; + +/** The engine's message keys, under `files_sync.errors.`. */ +export const FILES_SYNC_ERROR_KEYS = [ + "offline", "server_refused", "too_large", "name_invalid", "disk_full", "denied", "busy", "quota", "not_found", "policy", "failed", +] as const; + +/** + * The key inside the `files_sync` namespace for an engine message key. The engine may + * write `offline`, `errors.offline` or `files_sync.errors.offline`; anything unknown + * reads as `failed`, so a new key never shows raw. + */ +export function filesSyncErrorKey(key: string | undefined | null): string { + const last = (key ?? "").split(".").pop() ?? ""; + return `errors.${(FILES_SYNC_ERROR_KEYS as readonly string[]).includes(last) ? last : "failed"}`; +} + +export const FILES_SYNC_ACTIVITY_KINDS: readonly FilesSyncActivityKind[] = [ + "uploaded", "downloaded", "folder-created", "renamed", "moved", "deleted", "trashed", + "conflict", "renamed-for-disk", "too-large", "waiting", "mass-delete", "error", +]; + +export const FILES_SYNC_ITEM_STATES: readonly FilesSyncItemStatus[] = ["synced", "syncing", "pending", "error", "online-only"]; + +/** "Show in Explorer", "Show in Finder", "Show in file manager". */ +export function showInKey(platform: string | undefined | null): string { + if (platform === "win32") return "show_in.explorer"; + if (platform === "darwin") return "show_in.finder"; + return "show_in.file_manager"; +} + +/** The name of a root to show: the drive's, or the last part of the folder's path. */ +export function rootName(root: FilesRoot, driveName: string): string { + if (root.kind === "drive") return driveName; + const parts = root.path.split(/[\\/]+/).filter(Boolean); + return parts[parts.length - 1] ?? root.path; +} + +/** A time as "14:05" today and "3 Nov, 14:05" before. */ +export function syncTimeOptions(at: number, now = Date.now()): { hour: "numeric"; minute: "2-digit"; day?: "numeric"; month?: "short" } { + const sameDay = new Date(at).toDateString() === new Date(now).toDateString(); + return sameDay + ? { hour: "numeric", minute: "2-digit" } + : { day: "numeric", month: "short", hour: "numeric", minute: "2-digit" }; +} diff --git a/components/files/file-browser.tsx b/components/files/file-browser.tsx index bd1c0d351..24e13cb88 100644 --- a/components/files/file-browser.tsx +++ b/components/files/file-browser.tsx @@ -39,6 +39,7 @@ import { getFilePreviewKind } from "@/lib/file-preview"; import type { FileAppAction } from "@/lib/files-app-actions"; import { useAppSidePanel } from "@/components/plugins/app-side-panel"; import { MAX_SIDE_PANEL_FILES } from "@/lib/plugin-sandbox/assistant/f.types"; +import { ShowInFolderMenuItem } from "@/components/files-sync/files-sync-item"; import { resourceToView, useFileStore, isSharedFilesPage, sharedFilePages, type FileResource } from "@/stores/file-store"; import { TRASH_LOCATION, TRASH_PATH, isTrashLocation } from "@/lib/files-trash"; import { PluginActions, usePluginActions } from "@/components/plugins/plugin-actions"; @@ -1057,6 +1058,7 @@ export function FileBrowser({{t(error)}
} +{t("folders.forced_off")}
+{t("folders.empty")}
+ ) : ( +{t(error)}
} + {folders && ( +{root.path}
+{t("folders.paired_with", { folder: root.remoteLabel })}
+{t("activity.empty")}
+ ) : ( +