mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
feat: row detail options for hosts and credentials, open manage from sidebar header
This commit is contained in:
1 parent
08db1fcdde
commit
a72aa5b57b
18 files changed
+415
-45
No files matched your search
@@ -15,9 +15,11 @@ import { toast } from "sonner";
|
||||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { ComponentSlot } from "@/shell/ActionSlot";
|
||||
import type { Credential } from "@/types/ui-types";
|
||||
import type {
|
||||
CredentialDensity,
|
||||
CredentialTrayTrigger,
|
||||
import {
|
||||
defaultCredentialRowFields,
|
||||
type CredentialDensity,
|
||||
type CredentialRowFields,
|
||||
type CredentialTrayTrigger,
|
||||
} from "@/types/credential-sidebar-preferences";
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,8 @@ const CREDENTIAL_ITEM_DENSITY_TOKENS = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
const DEFAULT_ROW_FIELDS = defaultCredentialRowFields();
|
||||
|
||||
export function CredentialItem({
|
||||
cred,
|
||||
usedByCount = 0,
|
||||
@@ -53,6 +57,7 @@ export function CredentialItem({
|
||||
density = "comfortable",
|
||||
trayTrigger = "hover",
|
||||
showTags = true,
|
||||
rowFields = DEFAULT_ROW_FIELDS,
|
||||
arrangeMode = false,
|
||||
isDragging = false,
|
||||
onReorderDrop,
|
||||
@@ -80,6 +85,7 @@ export function CredentialItem({
|
||||
density?: CredentialDensity;
|
||||
trayTrigger?: CredentialTrayTrigger;
|
||||
showTags?: boolean;
|
||||
rowFields?: CredentialRowFields;
|
||||
/** When true (rearranging unlocked), the row can be dragged to reorder or
|
||||
* onto a folder header to move. */
|
||||
arrangeMode?: boolean;
|
||||
@@ -103,6 +109,8 @@ export function CredentialItem({
|
||||
const isKey = cred.type === "key";
|
||||
const tokens = CREDENTIAL_ITEM_DENSITY_TOKENS[density];
|
||||
const isCompact = density === "compact";
|
||||
const showUsername = rowFields.showUsername && !!cred.username;
|
||||
const showUsage = rowFields.showUsageCount && usedByCount > 0;
|
||||
const isTouchOnly =
|
||||
typeof window !== "undefined" && window.matchMedia("(hover: none)").matches;
|
||||
|
||||
@@ -281,7 +289,7 @@ export function CredentialItem({
|
||||
className={`${tokens.nameTextSize} font-semibold truncate text-foreground leading-none tracking-tight`}
|
||||
>
|
||||
{cred.name}
|
||||
{cred.isShared && (
|
||||
{rowFields.showSharedBadge && cred.isShared && (
|
||||
<span
|
||||
className="ml-1 inline-flex items-center gap-0.5 text-[9px] uppercase text-accent-brand/80"
|
||||
title={t("credentials.sharedBy", {
|
||||
@@ -293,21 +301,27 @@ export function CredentialItem({
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={`text-[9px] px-1 py-px font-bold border leading-none shrink-0 ${isKey ? "border-accent-brand/30 text-accent-brand" : "border-border/60 text-muted-foreground/60"}`}
|
||||
>
|
||||
{isKey ? t("credentials.keyBadge") : t("credentials.passwordBadge")}
|
||||
</span>
|
||||
{rowFields.showTypeBadge && (
|
||||
<span
|
||||
className={`text-[9px] px-1 py-px font-bold border leading-none shrink-0 ${isKey ? "border-accent-brand/30 text-accent-brand" : "border-border/60 text-muted-foreground/60"}`}
|
||||
>
|
||||
{isKey
|
||||
? t("credentials.keyBadge")
|
||||
: t("credentials.passwordBadge")}
|
||||
</span>
|
||||
)}
|
||||
<ComponentSlot
|
||||
slotId="credentials.badges"
|
||||
props={{ credentialId: Number(cred.id) }}
|
||||
/>
|
||||
{cred.pin && (
|
||||
{rowFields.showPinIcon && cred.pin && (
|
||||
<Pin className="size-2.5 text-accent-brand/50 shrink-0" />
|
||||
)}
|
||||
{!shouldUseClickTray && !actionsOnly && (
|
||||
<span className="text-[11px] text-muted-foreground/45 truncate leading-none ml-auto shrink-0 group-hover:hidden">
|
||||
{usedByCount > 0 ? `${usedByCount}h` : ""}
|
||||
{rowFields.showUsageCount && usedByCount > 0
|
||||
? `${usedByCount}h`
|
||||
: ""}
|
||||
</span>
|
||||
)}
|
||||
{(shouldUseClickTray || actionsOnly) && (
|
||||
@@ -331,12 +345,12 @@ export function CredentialItem({
|
||||
</div>
|
||||
|
||||
{/* Username row */}
|
||||
{tokens.showUsernameRow && (cred.username || usedByCount > 0) && (
|
||||
{tokens.showUsernameRow && (showUsername || showUsage) && (
|
||||
<span className="text-[11px] text-muted-foreground/60 truncate leading-none font-mono">
|
||||
{cred.username}
|
||||
{usedByCount > 0 && (
|
||||
{showUsername && cred.username}
|
||||
{showUsage && (
|
||||
<span
|
||||
className={`text-muted-foreground/40${cred.username ? " ml-2" : ""}`}
|
||||
className={`text-muted-foreground/40${showUsername ? " ml-2" : ""}`}
|
||||
>
|
||||
{usedByCount}h
|
||||
</span>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ReorderIndicator } from "@/sidebar/ReorderIndicator";
|
||||
import type { Credential } from "@/types/ui-types";
|
||||
import type {
|
||||
CredentialDensity,
|
||||
CredentialRowFields,
|
||||
CredentialTrayTrigger,
|
||||
} from "@/types/credential-sidebar-preferences";
|
||||
import { CredentialItem } from "./CredentialItem/CredentialItem";
|
||||
@@ -41,6 +42,7 @@ export function CredentialSidebarTree({
|
||||
density = "comfortable",
|
||||
trayTrigger = "hover",
|
||||
showTags = true,
|
||||
rowFields,
|
||||
editingFolderName,
|
||||
editingFolderValue,
|
||||
onEditingFolderNameChange,
|
||||
@@ -63,6 +65,7 @@ export function CredentialSidebarTree({
|
||||
density?: CredentialDensity;
|
||||
trayTrigger?: CredentialTrayTrigger;
|
||||
showTags?: boolean;
|
||||
rowFields?: CredentialRowFields;
|
||||
editingFolderName: string | null;
|
||||
editingFolderValue: string;
|
||||
onEditingFolderNameChange: (name: string | null) => void;
|
||||
@@ -165,6 +168,11 @@ export function CredentialSidebarTree({
|
||||
isOpen,
|
||||
showTags,
|
||||
tagCount: row.item.tags?.length ?? 0,
|
||||
hasDetailRow:
|
||||
!rowFields ||
|
||||
(rowFields.showUsername && !!row.item.username) ||
|
||||
(rowFields.showUsageCount &&
|
||||
(usedByCounts?.get(row.item.id) ?? 0) > 0),
|
||||
});
|
||||
},
|
||||
[
|
||||
@@ -176,6 +184,8 @@ export function CredentialSidebarTree({
|
||||
actionsOnly,
|
||||
density,
|
||||
showTags,
|
||||
rowFields,
|
||||
usedByCounts,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -223,6 +233,7 @@ export function CredentialSidebarTree({
|
||||
density,
|
||||
trayTrigger,
|
||||
showTags,
|
||||
rowFields,
|
||||
]);
|
||||
|
||||
function toggleFolder(name: string) {
|
||||
@@ -422,6 +433,7 @@ export function CredentialSidebarTree({
|
||||
density={density}
|
||||
trayTrigger={trayTrigger}
|
||||
showTags={showTags}
|
||||
rowFields={rowFields}
|
||||
arrangeMode={arrangeMode}
|
||||
isDragging={draggedReorderKey === `cred:${item.id}`}
|
||||
onReorderDrop={(position) =>
|
||||
|
||||
@@ -10,6 +10,7 @@ const HEIGHTS = {
|
||||
alwaysKey: 100.25,
|
||||
alwaysPassword: 74,
|
||||
tags: 18.5,
|
||||
detail: 15,
|
||||
},
|
||||
compact: {
|
||||
base: 23,
|
||||
@@ -20,6 +21,7 @@ const HEIGHTS = {
|
||||
alwaysKey: 74.75,
|
||||
alwaysPassword: 52,
|
||||
tags: 12.5,
|
||||
detail: 0,
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -31,6 +33,7 @@ export function getCredentialRowHeight({
|
||||
isOpen,
|
||||
showTags,
|
||||
tagCount,
|
||||
hasDetailRow = true,
|
||||
}: {
|
||||
density: CredentialDensity;
|
||||
isKey: boolean;
|
||||
@@ -39,6 +42,8 @@ export function getCredentialRowHeight({
|
||||
isOpen: boolean;
|
||||
showTags: boolean;
|
||||
tagCount: number;
|
||||
/** False when the username and usage line has nothing to show. */
|
||||
hasDetailRow?: boolean;
|
||||
}): number {
|
||||
const heights = HEIGHTS[density];
|
||||
let height: number = heights.base;
|
||||
@@ -51,5 +56,9 @@ export function getCredentialRowHeight({
|
||||
height = isKey ? heights.actionsOnlyKey : heights.actionsOnlyPassword;
|
||||
}
|
||||
|
||||
return height + (showTags && tagCount > 0 ? heights.tags : 0);
|
||||
return (
|
||||
height +
|
||||
(showTags && tagCount > 0 ? heights.tags : 0) -
|
||||
(hasDetailRow ? 0 : heights.detail)
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user