feat(redis): add copy key name action
This commit is contained in:
1 parent
97f107caac
commit
8be2136658
8 files changed
+66
-23
No files matched your search
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, onMounted, onUnmounted, onActivated, onDeactivated, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Search, RefreshCw, Loader2, ChevronRight, ChevronDown, FolderClosed, FolderOpen, Trash2, Plus, KeyRound, TerminalSquare, Asterisk, History, Radio, Clock } from "@lucide/vue";
|
||||
import { Search, RefreshCw, Loader2, ChevronRight, ChevronDown, FolderClosed, FolderOpen, Trash2, Plus, KeyRound, TerminalSquare, Asterisk, History, Radio, Clock, Copy } from "@lucide/vue";
|
||||
import { RecycleScroller } from "vue-virtual-scroller";
|
||||
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
@@ -13,6 +13,7 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
|
||||
import RedisValueViewer from "./RedisValueViewer.vue";
|
||||
import RedisPubSubPanel from "./RedisPubSubPanel.vue";
|
||||
@@ -27,6 +28,7 @@ import { isRedisMutatingCommand } from "@/lib/redisCommandTable";
|
||||
import { isRedisClearScreenCommand, nextRedisCommandDb, redisKeyTextToRaw } from "@/lib/redisCommandSession";
|
||||
import { formatRedisConsoleValue, formatRedisStringValue } from "@/lib/redisValuePresentation";
|
||||
import { isCancelSearchShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { useEditorFontFamilyStyle } from "@/composables/useEditorFontFamilyStyle";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { redisKeySearchPattern } from "@/lib/redisKeyPattern";
|
||||
@@ -425,6 +427,32 @@ function requestGroupDelete(node: RedisKeyTreeNode, event: Event) {
|
||||
showDangerConfirm.value = true;
|
||||
}
|
||||
|
||||
async function copyRedisKeyName(keyRaw: string) {
|
||||
try {
|
||||
await copyToClipboard(keyRaw);
|
||||
toast(t("redis.copied"), 2000);
|
||||
} catch (e: any) {
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function redisKeyContextMenuItems(node: RedisKeyTreeNode): ContextMenuItem[] {
|
||||
if (node.kind !== "leaf") return [];
|
||||
return [
|
||||
{
|
||||
label: t("redis.copyKeyName"),
|
||||
icon: Copy,
|
||||
action: () => copyRedisKeyName(node.keyRaw),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function onRedisRowContextMenu(event: MouseEvent, node: RedisKeyTreeNode, openContextMenu: (event: MouseEvent) => void) {
|
||||
if (node.kind !== "leaf") return;
|
||||
selectedKeyRaw.value = node.keyRaw;
|
||||
openContextMenu(event);
|
||||
}
|
||||
|
||||
function resetLoadedKeys() {
|
||||
loadedKeyRaws.clear();
|
||||
flatKeys.value = [];
|
||||
@@ -1069,30 +1097,38 @@ defineExpose({ focusSearch });
|
||||
</div>
|
||||
<RecycleScroller v-else class="redis-key-scroller flex-1" :items="visibleRows" :item-size="30" :buffer="600" :skip-hover="true" key-field="id">
|
||||
<template #default="{ item: row }">
|
||||
<div class="flex items-center gap-2 border-b px-3 text-[13px] cursor-pointer hover:bg-accent/50 group" :class="{ 'bg-accent': row.node.kind === 'leaf' && selectedKeyRaw === row.node.keyRaw }" :style="{ height: '30px' }" @click="onRowClick(row.node)">
|
||||
<div class="min-w-0 flex flex-1 items-center gap-1 overflow-hidden" :style="{ paddingLeft: `${12 + row.depth * 16}px` }">
|
||||
<template v-if="row.node.kind === 'group'">
|
||||
<component :is="expandedGroupIds.has(row.node.id) ? ChevronDown : ChevronRight" class="w-3 h-3 shrink-0 text-muted-foreground" />
|
||||
<component :is="expandedGroupIds.has(row.node.id) ? FolderOpen : FolderClosed" class="w-3 h-3 shrink-0 text-amber-500" />
|
||||
<span class="dbx-editor-font-family truncate">{{ row.node.label }}</span>
|
||||
<span class="text-muted-foreground ml-1">({{ countLeaves(row.node) }})</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="relative flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<KeyRound class="h-3.5 w-3.5 text-muted-foreground/70 transition-opacity group-hover:opacity-0" :class="{ 'opacity-0': checkedKeys.has(row.node.keyRaw) }" />
|
||||
<input type="checkbox" class="absolute h-3.5 w-3.5 accent-primary cursor-pointer opacity-0 group-hover:opacity-100" :class="{ 'opacity-100': checkedKeys.has(row.node.keyRaw) }" :checked="checkedKeys.has(row.node.keyRaw)" @click="toggleCheck(row.node.keyRaw, $event)" />
|
||||
</span>
|
||||
<span class="dbx-editor-font-family truncate">{{ row.node.label }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<CustomContextMenu :items="redisKeyContextMenuItems(row.node)" v-slot="{ onContextMenu }">
|
||||
<div
|
||||
class="flex items-center gap-2 border-b px-3 text-[13px] cursor-pointer hover:bg-accent/50 group"
|
||||
:class="{ 'bg-accent': row.node.kind === 'leaf' && selectedKeyRaw === row.node.keyRaw }"
|
||||
:style="{ height: '30px' }"
|
||||
@click="onRowClick(row.node)"
|
||||
@contextmenu="(event) => onRedisRowContextMenu(event, row.node, onContextMenu)"
|
||||
>
|
||||
<div class="min-w-0 flex flex-1 items-center gap-1 overflow-hidden" :style="{ paddingLeft: `${12 + row.depth * 16}px` }">
|
||||
<template v-if="row.node.kind === 'group'">
|
||||
<component :is="expandedGroupIds.has(row.node.id) ? ChevronDown : ChevronRight" class="w-3 h-3 shrink-0 text-muted-foreground" />
|
||||
<component :is="expandedGroupIds.has(row.node.id) ? FolderOpen : FolderClosed" class="w-3 h-3 shrink-0 text-amber-500" />
|
||||
<span class="dbx-editor-font-family truncate">{{ row.node.label }}</span>
|
||||
<span class="text-muted-foreground ml-1">({{ countLeaves(row.node) }})</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="relative flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<KeyRound class="h-3.5 w-3.5 text-muted-foreground/70 transition-opacity group-hover:opacity-0" :class="{ 'opacity-0': checkedKeys.has(row.node.keyRaw) }" />
|
||||
<input type="checkbox" class="absolute h-3.5 w-3.5 accent-primary cursor-pointer opacity-0 group-hover:opacity-100" :class="{ 'opacity-100': checkedKeys.has(row.node.keyRaw) }" :checked="checkedKeys.has(row.node.keyRaw)" @click="toggleCheck(row.node.keyRaw, $event)" />
|
||||
</span>
|
||||
<span class="dbx-editor-font-family truncate">{{ row.node.label }}</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center justify-end gap-1">
|
||||
<Badge v-if="row.node.kind === 'leaf' && row.node.keyType" variant="outline" class="text-xs px-1.5 py-0" :class="typeColor(row.node.keyType)">{{ row.node.keyType }}</Badge>
|
||||
<Button v-if="row.node.kind === 'group'" variant="ghost" size="icon" class="h-5 w-5 shrink-0 text-destructive opacity-0 group-hover:opacity-100" :title="t('redis.deleteGroup')" @click="requestGroupDelete(row.node, $event)">
|
||||
<Trash2 class="h-3 w-3" />
|
||||
</Button>
|
||||
<div class="flex shrink-0 items-center justify-end gap-1">
|
||||
<Badge v-if="row.node.kind === 'leaf' && row.node.keyType" variant="outline" class="text-xs px-1.5 py-0" :class="typeColor(row.node.keyType)">{{ row.node.keyType }}</Badge>
|
||||
<Button v-if="row.node.kind === 'group'" variant="ghost" size="icon" class="h-5 w-5 shrink-0 text-destructive opacity-0 group-hover:opacity-100" :title="t('redis.deleteGroup')" @click="requestGroupDelete(row.node, $event)">
|
||||
<Trash2 class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CustomContextMenu>
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
<div v-if="hasMore && !isFetchingAll" class="shrink-0 border-t px-2 py-1.5 flex items-center gap-1.5">
|
||||
|
||||
Reference in new issue
Block a user