feat(redis): add sheet UI components, list set API, and member detail utils

- Add shadcn Sheet UI components for member detail drawer
- Add redisListSet API for editing list items by index
- Add member detail presentation helpers (format, highlight, edit, resize)
- Add tests for redis value presentation
This commit is contained in:
t8y2
2026-05-13 13:54:59 +08:00
parent 90e8bbe757
commit 4dc3a1b3c0
14 changed files with 268 additions and 1 deletions
+1
View File
@@ -111,6 +111,7 @@ export const redisDeleteKey = forward("redisDeleteKey");
export const redisHashSet = forward("redisHashSet");
export const redisHashDel = forward("redisHashDel");
export const redisListPush = forward("redisListPush");
export const redisListSet = forward("redisListSet");
export const redisListRemove = forward("redisListRemove");
export const redisSetAdd = forward("redisSetAdd");
export const redisSetRemove = forward("redisSetRemove");
+10
View File
@@ -597,6 +597,16 @@ export async function redisListPush(connectionId: string, db: number, keyRaw: st
return post("/api/redis/list-push", { connectionId, db, keyRaw, value });
}
export async function redisListSet(
connectionId: string,
db: number,
keyRaw: string,
index: number,
value: string,
): Promise<void> {
return post("/api/redis/list-set", { connectionId, db, keyRaw, index, value });
}
export async function redisListRemove(connectionId: string, db: number, keyRaw: string, index: number): Promise<void> {
return post("/api/redis/list-remove", { connectionId, db, keyRaw, index });
}
+36
View File
@@ -5,6 +5,23 @@ export interface RedisMemberDetail {
format: RedisMemberDetailFormat;
}
export type RedisMemberDetailKind = "list" | "set" | "hash" | "zset" | "stream";
export const REDIS_MEMBER_DETAIL_SHEET_MIN_WIDTH = 360;
export const REDIS_MEMBER_DETAIL_SHEET_MAX_WIDTH = 900;
export function canEditRedisMemberDetail(kind: RedisMemberDetailKind): boolean {
return kind !== "stream";
}
export function clampRedisMemberDetailSheetWidth(width: number, viewportWidth: number): number {
const viewportMax = Math.max(REDIS_MEMBER_DETAIL_SHEET_MIN_WIDTH, viewportWidth - 32);
return Math.min(
Math.min(REDIS_MEMBER_DETAIL_SHEET_MAX_WIDTH, viewportMax),
Math.max(REDIS_MEMBER_DETAIL_SHEET_MIN_WIDTH, width),
);
}
export function formatRedisMemberDetail(value: unknown): RedisMemberDetail {
if (typeof value === "string") {
const trimmed = value.trim();
@@ -24,3 +41,22 @@ export function formatRedisMemberDetail(value: unknown): RedisMemberDetail {
return { text: String(value), format: "text" };
}
}
export function getRedisMemberSelectionKey(title: string, value: unknown): string {
return `${title}\n${formatRedisMemberDetail(value).text}`;
}
export function highlightRedisJsonDetail(json: string): string {
const escaped = json.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
return escaped.replace(
/("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g,
(match) => {
let cls = "json-number";
if (match.startsWith('"')) cls = match.endsWith(":") ? "json-key" : "json-string";
else if (match === "true" || match === "false") cls = "json-boolean";
else if (match === "null") cls = "json-null";
return `<span class="${cls}">${match}</span>`;
},
);
}
+10
View File
@@ -429,6 +429,16 @@ export async function redisListPush(connectionId: string, db: number, keyRaw: st
return invoke("redis_list_push", { connectionId, db, keyRaw, value });
}
export async function redisListSet(
connectionId: string,
db: number,
keyRaw: string,
index: number,
value: string,
): Promise<void> {
return invoke("redis_list_set", { connectionId, db, keyRaw, index, value });
}
export async function redisListRemove(connectionId: string, db: number, keyRaw: string, index: number): Promise<void> {
return invoke("redis_list_remove", { connectionId, db, keyRaw, index });
}