mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-22 22:02:00 +00:00
feat: add total storage usage label in multiple languages and enhance storage usage component
- Introduced a new "total" label in various language translation files for storage usage. - Updated the StorageUsage component to display the total storage size alongside the progress bar. - Improved layout and styling in the QuickAccessCards component for better visual consistency.
This commit is contained in:
@@ -1401,6 +1401,7 @@
|
||||
"ariaLabel": "شريط تقدم استخدام التخزين",
|
||||
"used": "المستخدمة",
|
||||
"available": "متاح",
|
||||
"total": "الإجمالي",
|
||||
"loading": "جارٍ التحميل...",
|
||||
"retry": "إعادة المحاولة",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Fortschrittsbalken der Speichernutzung",
|
||||
"used": "genutzt",
|
||||
"available": "verfügbar",
|
||||
"total": "Gesamt",
|
||||
"loading": "Wird geladen...",
|
||||
"retry": "Wiederholen",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Storage usage progress bar",
|
||||
"used": "used",
|
||||
"available": "available",
|
||||
"total": "Total",
|
||||
"loading": "Loading...",
|
||||
"retry": "Retry",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Barra de progreso del uso de almacenamiento",
|
||||
"used": "usados",
|
||||
"available": "disponible",
|
||||
"total": "Total",
|
||||
"loading": "Cargando...",
|
||||
"retry": "Reintentar",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Barre de progression de l'utilisation du stockage",
|
||||
"used": "utilisé",
|
||||
"available": "disponible",
|
||||
"total": "Total",
|
||||
"loading": "Chargement...",
|
||||
"retry": "Réessayer",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "स्टोरेज उपयोग प्रगति पट्टी",
|
||||
"used": "उपयोग किया गया",
|
||||
"available": "उपलब्ध",
|
||||
"total": "कुल",
|
||||
"loading": "लोड हो रहा है...",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Barra di progresso utilizzo archiviazione",
|
||||
"used": "utilizzato",
|
||||
"available": "disponibile",
|
||||
"total": "Totale",
|
||||
"loading": "Caricamento...",
|
||||
"retry": "Riprova",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "ストレージ使用状況のプログレスバー",
|
||||
"used": "使用済み",
|
||||
"available": "利用可能",
|
||||
"total": "合計",
|
||||
"loading": "読み込み中...",
|
||||
"retry": "再試行",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "스토리지 사용량 진행 바",
|
||||
"used": "사용됨",
|
||||
"available": "사용 가능",
|
||||
"total": "총계",
|
||||
"loading": "로딩 중...",
|
||||
"retry": "다시 시도",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Opslaggebruik voortgangsbalk",
|
||||
"used": "gebruikt",
|
||||
"available": "beschikbaar",
|
||||
"total": "Totaal",
|
||||
"loading": "Laden...",
|
||||
"retry": "Opnieuw proberen",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Pasek postępu użycia pamięci",
|
||||
"used": "użyte",
|
||||
"available": "dostępne",
|
||||
"total": "Razem",
|
||||
"loading": "Ładowanie...",
|
||||
"retry": "Spróbuj ponownie",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Barra de progresso do uso de armazenamento",
|
||||
"used": "usado",
|
||||
"available": "disponível",
|
||||
"total": "Total",
|
||||
"loading": "Carregando...",
|
||||
"retry": "Tentar novamente",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Индикатор использования хранилища",
|
||||
"used": "Использовано",
|
||||
"available": "Доступно",
|
||||
"total": "Всего",
|
||||
"loading": "Загрузка...",
|
||||
"retry": "Повторить",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "Depolama kullanım ilerleme çubuğu",
|
||||
"used": "kullanıldı",
|
||||
"available": "kullanılabilir",
|
||||
"total": "Toplam",
|
||||
"loading": "Yükleniyor...",
|
||||
"retry": "Tekrar Dene",
|
||||
"errors": {
|
||||
|
@@ -1399,6 +1399,7 @@
|
||||
"ariaLabel": "存储使用进度条",
|
||||
"used": "已使用:",
|
||||
"available": "可用",
|
||||
"total": "总计",
|
||||
"loading": "加载中...",
|
||||
"retry": "重试",
|
||||
"errors": {
|
||||
|
@@ -11,45 +11,42 @@ export function QuickAccessCards() {
|
||||
const QUICK_ACCESS_ITEMS = [
|
||||
{
|
||||
title: t("quickAccess.files.title"),
|
||||
icon: <IconFoldersFilled size={28} />,
|
||||
icon: <IconFoldersFilled size={24} />,
|
||||
description: t("quickAccess.files.description"),
|
||||
path: "/files",
|
||||
color: "bg-primary",
|
||||
},
|
||||
{
|
||||
title: t("quickAccess.shares.title"),
|
||||
icon: <IconShare2 size={28} />,
|
||||
icon: <IconShare2 size={24} />,
|
||||
description: t("quickAccess.shares.description"),
|
||||
path: "/shares",
|
||||
color: "bg-orange-400",
|
||||
},
|
||||
{
|
||||
title: t("quickAccess.reverseShares.title"),
|
||||
icon: <IconDeviceDesktopDown size={28} />,
|
||||
icon: <IconDeviceDesktopDown size={24} />,
|
||||
description: t("quickAccess.reverseShares.description"),
|
||||
path: "/reverse-shares",
|
||||
color: "bg-blue-500",
|
||||
},
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{QUICK_ACCESS_ITEMS.map((card) => (
|
||||
<Card
|
||||
key={card.title}
|
||||
className="cursor-pointer transform transition-all hover:bg-card/80"
|
||||
className="cursor-pointer group transition-all duration-400 border-border/50 backdrop-blur-sm h-full hover:opacity-80 "
|
||||
onClick={() => router.push(card.path)}
|
||||
>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
className={`${card.color} w-12 h-12 rounded-lg flex items-center justify-center text-white shadow-sm`}
|
||||
>
|
||||
<CardContent className="h-full">
|
||||
<div className="flex items-center gap-4 h-full">
|
||||
<div className="dark:group-hover:bg-accent group-hover:bg-primary/10 w-12 h-12 rounded-lg flex items-center justify-center text-primary/80 group-hover:text-primary dark:bg-accent/60 bg-accent/50 border dark:border-none transition-all duration-400 flex-shrink-0">
|
||||
{card.icon}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">{card.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{card.description}</p>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-foreground mb-1 truncate">{card.title}</h3>
|
||||
<p className="text-sm text-muted-foreground group-hover:text-muted-foreground leading-relaxed">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
@@ -26,10 +26,13 @@ export function StorageUsage({ diskSpace, diskSpaceError, onRetry }: StorageUsag
|
||||
<Card className="w-full">
|
||||
<CardContent className="">
|
||||
<div className="flex flex-col gap-4">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconDatabaseCog className="text-gray-500" size={24} />
|
||||
{t("storageUsage.title")}
|
||||
</h2>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconDatabaseCog className="text-gray-500" size={24} />
|
||||
{t("storageUsage.title")}
|
||||
</h2>
|
||||
<span className="text-sm text-muted-foreground">{t("storageUsage.total")}: --</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 py-4">
|
||||
<div className="flex items-center gap-2 text-amber-600 dark:text-amber-400">
|
||||
<IconAlertCircle size={20} />
|
||||
@@ -54,10 +57,13 @@ export function StorageUsage({ diskSpace, diskSpaceError, onRetry }: StorageUsag
|
||||
<Card className="w-full">
|
||||
<CardContent className="">
|
||||
<div className="flex flex-col gap-4">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconDatabaseCog className="text-gray-500" size={24} />
|
||||
{t("storageUsage.title")}
|
||||
</h2>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconDatabaseCog className="text-gray-500" size={24} />
|
||||
{t("storageUsage.title")}
|
||||
</h2>
|
||||
<span className="text-sm text-muted-foreground">{t("storageUsage.total")}: --</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
|
||||
<div className="flex justify-between text-sm text-muted-foreground">
|
||||
@@ -75,15 +81,20 @@ export function StorageUsage({ diskSpace, diskSpaceError, onRetry }: StorageUsag
|
||||
<Card className="w-full">
|
||||
<CardContent className="">
|
||||
<div className="flex flex-col gap-4">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconDatabaseCog className="text-gray-500" size={24} />
|
||||
{t("storageUsage.title")}
|
||||
</h2>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconDatabaseCog className="text-gray-500" size={24} />
|
||||
{t("storageUsage.title")}
|
||||
</h2>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("storageUsage.total")}: {formatStorageSize(diskSpace?.diskSizeGB || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Progress
|
||||
aria-label={t("storageUsage.ariaLabel")}
|
||||
value={((diskSpace?.diskUsedGB || 0) / (diskSpace?.diskSizeGB || 1)) * 100}
|
||||
className="w-full h-3"
|
||||
className="w-full h-2"
|
||||
/>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>
|
||||
|
@@ -126,3 +126,25 @@
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Line clamp utilities */
|
||||
.line-clamp-1 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.line-clamp-3 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
@@ -206,7 +206,7 @@ export function GlobalDropZone({ onSuccess, children }: GlobalDropZoneProps) {
|
||||
if (file) {
|
||||
const timestamp = Date.now();
|
||||
const extension = file.type.split("/")[1] || "png";
|
||||
const fileName = `pasted-image-${timestamp}.${extension}`;
|
||||
const fileName = `${timestamp}.${extension}`;
|
||||
|
||||
const renamedFile = new File([file], fileName, { type: file.type });
|
||||
|
||||
|
@@ -17,6 +17,7 @@ interface FilePreviewModalProps {
|
||||
objectName: string;
|
||||
type?: string;
|
||||
id?: string;
|
||||
description?: string;
|
||||
};
|
||||
isReverseShare?: boolean;
|
||||
}
|
||||
@@ -48,6 +49,8 @@ export function FilePreviewModal({ isOpen, onClose, file, isReverseShare = false
|
||||
pdfAsBlob={previewState.pdfAsBlob}
|
||||
pdfLoadFailed={previewState.pdfLoadFailed}
|
||||
onPdfLoadError={previewState.handlePdfLoadError}
|
||||
description={file.description}
|
||||
onDownload={previewState.handleDownload}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
|
@@ -16,6 +16,8 @@ interface FilePreviewRendererProps {
|
||||
pdfAsBlob: boolean;
|
||||
pdfLoadFailed: boolean;
|
||||
onPdfLoadError: () => void;
|
||||
description?: string;
|
||||
onDownload?: () => void;
|
||||
}
|
||||
|
||||
export function FilePreviewRenderer({
|
||||
@@ -28,6 +30,8 @@ export function FilePreviewRenderer({
|
||||
pdfAsBlob,
|
||||
pdfLoadFailed,
|
||||
onPdfLoadError,
|
||||
description,
|
||||
onDownload,
|
||||
}: FilePreviewRendererProps) {
|
||||
if (isLoading) {
|
||||
return <DefaultPreview fileName={fileName} isLoading />;
|
||||
@@ -63,7 +67,7 @@ export function FilePreviewRenderer({
|
||||
return <TextPreview content={textContent} fileName={fileName} />;
|
||||
|
||||
case "image":
|
||||
return <ImagePreview src={previewUrl!} alt={fileName} />;
|
||||
return <ImagePreview src={previewUrl!} alt={fileName} description={description} onDownload={onDownload} />;
|
||||
|
||||
case "audio":
|
||||
return <AudioPreview src={mediaUrl!} />;
|
||||
|
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconMaximize, IconX } from "@tabler/icons-react";
|
||||
import { IconDownload, IconMaximize, IconX } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||
@@ -10,10 +11,13 @@ import { Button } from "@/components/ui/button";
|
||||
interface ImagePreviewProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
description?: string;
|
||||
onDownload?: () => void;
|
||||
}
|
||||
|
||||
export function ImagePreview({ src, alt }: ImagePreviewProps) {
|
||||
export function ImagePreview({ src, alt, description, onDownload }: ImagePreviewProps) {
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const t = useTranslations();
|
||||
|
||||
const handleExpandClick = () => {
|
||||
setIsFullscreen(true);
|
||||
@@ -29,7 +33,19 @@ export function ImagePreview({ src, alt }: ImagePreviewProps) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle ESC key press
|
||||
const handleDownload = () => {
|
||||
if (onDownload) {
|
||||
onDownload();
|
||||
} else {
|
||||
const link = document.createElement("a");
|
||||
link.href = src;
|
||||
link.download = alt;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape" && isFullscreen) {
|
||||
@@ -39,7 +55,6 @@ export function ImagePreview({ src, alt }: ImagePreviewProps) {
|
||||
|
||||
if (isFullscreen) {
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
// Prevent body scroll when fullscreen is open
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
@@ -76,26 +91,56 @@ export function ImagePreview({ src, alt }: ImagePreviewProps) {
|
||||
typeof window !== "undefined" &&
|
||||
createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-[99999] bg-black/95 backdrop-blur-sm flex items-center justify-center"
|
||||
className="fixed inset-0 z-[99999] bg-black/95 backdrop-blur-sm"
|
||||
onClick={handleBackdropClick}
|
||||
style={{ margin: 0, padding: 0 }}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="cursor-pointer absolute top-6 right-6 z-10 bg-white/10 hover:bg-white/20 text-white border-white/20 h-10 w-10"
|
||||
onClick={handleCloseFullscreen}
|
||||
>
|
||||
<IconX className="h-6 w-6 cursor-pointer" />
|
||||
</Button>
|
||||
<div className="fixed top-0 left-0 right-0 bg-transparent h-24 z-[100000] pointer-events-none">
|
||||
<div className="absolute top-6 right-6 flex gap-2 pointer-events-auto">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="cursor-pointer bg-white/10 hover:bg-white/20 text-white border-white/20 h-10 w-10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDownload();
|
||||
}}
|
||||
>
|
||||
<IconDownload className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="cursor-pointer bg-white/10 hover:bg-white/20 text-white border-white/20 h-10 w-10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCloseFullscreen();
|
||||
}}
|
||||
>
|
||||
<IconX className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="max-w-screen max-h-screen w-auto h-auto object-contain p-4"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ maxWidth: "100vw", maxHeight: "100vh" }}
|
||||
/>
|
||||
<div className="fixed inset-0 flex items-center justify-center pt-6">
|
||||
<div className="relative max-w-full max-h-full">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="max-w-full max-h-screen w-auto h-screen object-contain pb-12"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="fixed bottom-0 left-0 right-0 z-[100000] pointer-events-none">
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6">
|
||||
<div className="text-white/30">
|
||||
<span className=" font-semibold mb-2 truncate">{alt}</span>
|
||||
{description && <p className="text-sm text-gray-200/20 line-clamp-2">{description}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
@@ -18,6 +18,7 @@ interface FileToDelete {
|
||||
interface PreviewFile {
|
||||
name: string;
|
||||
objectName: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface FileToShare {
|
||||
|
Reference in New Issue
Block a user