feat(sidebar): persist custom pinned item ordering
This commit is contained in:
1 parent
b635ea1466
commit
406b7acdbb
16 files changed
+1019
-82
No files matched your search
@@ -1844,6 +1844,7 @@ async function confirmRenameObject() {
|
||||
const newName = renameObjectName.value.trim();
|
||||
if (!objectType || !newName || newName === node.label || !node.connectionId || !node.database) return;
|
||||
renameObjectError.value = "";
|
||||
let renameApplied = false;
|
||||
try {
|
||||
const dbType = databaseTypeForNode(node);
|
||||
await connectionStore.ensureConnected(node.connectionId);
|
||||
@@ -1871,10 +1872,18 @@ async function confirmRenameObject() {
|
||||
});
|
||||
await executeTreeNodeSqlWithProductionGuard(node, sql, { database: node.database, schema: node.schema });
|
||||
}
|
||||
renameApplied = true;
|
||||
toast(t("contextMenu.renameObjectSuccess", { oldName: node.label, newName }), 3000);
|
||||
showRenameObjectDialog.value = false;
|
||||
const renamedNode: TreeNode = { ...node, label: newName, objectName: newName, tableName: newName };
|
||||
await refreshTableList(node);
|
||||
connectionStore.replacePinnedTreeNode(node, renamedNode);
|
||||
} catch (e: any) {
|
||||
if (renameApplied) {
|
||||
// The database mutation succeeded even when metadata refresh did not;
|
||||
// remove the old pin instead of allowing it to revive later.
|
||||
connectionStore.removePinnedTreeNodes([node]);
|
||||
}
|
||||
renameObjectError.value = e?.message || String(e);
|
||||
}
|
||||
}
|
||||
@@ -1891,6 +1900,9 @@ async function confirmDropObject() {
|
||||
const msgKey = node.type === "view" ? "contextMenu.dropViewSuccess" : node.type === "materialized_view" ? "contextMenu.dropViewSuccess" : node.type === "procedure" ? "contextMenu.dropProcedureSuccess" : "contextMenu.dropFunctionSuccess";
|
||||
toast(t(msgKey, { name: node.label }), 3000);
|
||||
closeDroppedTableObjectTabsForNode(node);
|
||||
// Procedure/function drops refresh their parent instead of removing this
|
||||
// node directly, so clear their pin before the old identity can survive.
|
||||
connectionStore.removePinnedTreeNodes([node]);
|
||||
if (node.type === "view" || node.type === "materialized_view") {
|
||||
connectionStore.removeTreeNode(node.id);
|
||||
releaseActiveNodeReference([node.id]);
|
||||
|
||||
@@ -62,6 +62,7 @@ import { focusSidebarRenameInput } from "@/lib/sidebar/sidebarRenameFocus";
|
||||
// --- Drag and Drop ---
|
||||
import { useDragSort } from "@/composables/useDragSort";
|
||||
import { sidebarTreeRuntimeKey } from "@/lib/sidebar/sidebarTreeRuntime";
|
||||
import { treeNodePinKey } from "@/lib/app/pinnedItems";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -559,7 +560,11 @@ const canExpand = computed(() =>
|
||||
const isPinned = computed(() => activeNode.value.pinned || connectionStore.isTreeNodePinned(activeNode.value));
|
||||
|
||||
const isNodeDefaultDatabase = computed(
|
||||
() => (activeNode.value.type === "database" || activeNode.value.type === "redis-db" || activeNode.value.type === "mongo-db") && !!activeNode.value.connectionId && !!activeNode.value.database && connectionStore.isDefaultDatabase(activeNode.value.connectionId, activeNode.value.database),
|
||||
() =>
|
||||
(activeNode.value.type === "database" || activeNode.value.type === "redis-db" || activeNode.value.type === "mongo-db") &&
|
||||
!!activeNode.value.connectionId &&
|
||||
typeof activeNode.value.database === "string" &&
|
||||
connectionStore.isDefaultDatabase(activeNode.value.connectionId, activeNode.value.database),
|
||||
);
|
||||
|
||||
const trailingComment = computed(() => {
|
||||
@@ -768,12 +773,27 @@ function finishRenameGroup() {
|
||||
connectionStore.renameConnectionGroup(activeNode.value.id, trimmed);
|
||||
}
|
||||
|
||||
const PINNED_TREE_NODE_DRAG_TYPE = "__pinned-tree-node__";
|
||||
|
||||
function pinnedSortKey(): string {
|
||||
return treeNodePinKey(activeNode.value);
|
||||
}
|
||||
|
||||
function canDragPinnedOrder(): boolean {
|
||||
return isPinned.value && !isNodeDefaultDatabase.value && !props.dragDisabled;
|
||||
}
|
||||
|
||||
const {
|
||||
state: dragState,
|
||||
startDrag,
|
||||
updateTarget,
|
||||
clearTarget,
|
||||
} = useDragSort((draggedId, targetId, position) => {
|
||||
if (dragState.draggedType === PINNED_TREE_NODE_DRAG_TYPE) {
|
||||
connectionStore.reorderPinnedTreeNodes(draggedId, targetId, position);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the grabbed row is part of a multi-selection, move all selected rows
|
||||
// together; otherwise just the grabbed one (issue #681).
|
||||
const selected = connectionStore.selectedTreeNodeIds;
|
||||
@@ -786,13 +806,40 @@ const isDraggable = computed(() => {
|
||||
return activeNode.value.type === "connection" || activeNode.value.type === "connection-group";
|
||||
});
|
||||
|
||||
const dragVisual = computed(() => ({
|
||||
isDropTarget: activeNode.value.type === "connection" || activeNode.value.type === "connection-group",
|
||||
showBefore: dragState.active && dragState.targetId === activeNode.value.id && dragState.dropPosition === "before",
|
||||
showAfter: dragState.active && dragState.targetId === activeNode.value.id && dragState.dropPosition === "after",
|
||||
showInside: dragState.active && dragState.targetId === activeNode.value.id && dragState.dropPosition === "inside",
|
||||
dragging: dragState.active && dragState.draggedId === activeNode.value.id,
|
||||
}));
|
||||
function isPinnedOrderDrag(): boolean {
|
||||
return dragState.active && dragState.draggedType === PINNED_TREE_NODE_DRAG_TYPE;
|
||||
}
|
||||
|
||||
const dragVisual = computed(() => {
|
||||
const targetId = isPinnedOrderDrag() ? pinnedSortKey() : activeNode.value.id;
|
||||
const isDropTarget = isPinnedOrderDrag() ? !!dragState.draggedId && connectionStore.canReorderPinnedTreeNodes(dragState.draggedId, pinnedSortKey()) : activeNode.value.type === "connection" || activeNode.value.type === "connection-group";
|
||||
|
||||
return {
|
||||
isDropTarget,
|
||||
showBefore: dragState.active && dragState.targetId === targetId && dragState.dropPosition === "before",
|
||||
showAfter: dragState.active && dragState.targetId === targetId && dragState.dropPosition === "after",
|
||||
showInside: !isPinnedOrderDrag() && dragState.active && dragState.targetId === targetId && dragState.dropPosition === "inside",
|
||||
dragging: dragState.active && dragState.draggedId === targetId,
|
||||
};
|
||||
});
|
||||
|
||||
function startPinnedOrderDrag(event: MouseEvent) {
|
||||
if (!canDragPinnedOrder()) return;
|
||||
startDrag(event, pinnedSortKey(), PINNED_TREE_NODE_DRAG_TYPE);
|
||||
}
|
||||
|
||||
function updateTreeDragTarget(event: MouseEvent) {
|
||||
if (!dragState.active || !dragVisual.value.isDropTarget) return;
|
||||
if (isPinnedOrderDrag()) {
|
||||
updateTarget(event, pinnedSortKey(), PINNED_TREE_NODE_DRAG_TYPE);
|
||||
return;
|
||||
}
|
||||
updateTarget(event, activeNode.value.id, activeNode.value.type);
|
||||
}
|
||||
|
||||
function clearTreeDragTarget() {
|
||||
clearTarget(isPinnedOrderDrag() ? pinnedSortKey() : activeNode.value.id);
|
||||
}
|
||||
|
||||
const TABLE_REFERENCE_DRAG_THRESHOLD = 5;
|
||||
|
||||
@@ -1060,10 +1107,10 @@ function onKeydown(event: KeyboardEvent) {
|
||||
@dblclick="onDoubleClick"
|
||||
@keydown="onKeydown"
|
||||
@mousedown="onRowMouseDown"
|
||||
@mousemove="dragVisual.isDropTarget ? updateTarget($event, node.id, node.type) : undefined"
|
||||
@mousemove="updateTreeDragTarget"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="
|
||||
clearTarget(node.id);
|
||||
clearTreeDragTarget();
|
||||
handleMouseLeave();
|
||||
"
|
||||
>
|
||||
@@ -1124,7 +1171,19 @@ function onKeydown(event: KeyboardEvent) {
|
||||
<span v-if="databaseOpenVisual.showsIndicator" class="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" />
|
||||
<Badge v-if="isConnectionReadonly" variant="secondary" class="h-4 px-1.5 text-[10px] gap-0.5"><Lock class="w-2.5 h-2.5" />{{ t("connection.readOnlyBadge") }}</Badge>
|
||||
<ConnectionErrorIndicator v-if="node.type === 'connection'" :connection-id="node.connectionId" trigger-class="h-4 w-4" />
|
||||
<Pin v-if="isPinned" class="w-3 h-3 shrink-0 text-primary fill-current" aria-hidden="true" />
|
||||
<button
|
||||
v-if="canDragPinnedOrder()"
|
||||
type="button"
|
||||
class="flex h-4 w-4 shrink-0 cursor-grab items-center justify-center rounded-sm text-primary hover:bg-primary/10 active:cursor-grabbing"
|
||||
:aria-label="t('contextMenu.reorderPinned')"
|
||||
:title="t('contextMenu.reorderPinned')"
|
||||
@mousedown.stop="startPinnedOrderDrag"
|
||||
@click.stop.prevent
|
||||
@dblclick.stop.prevent
|
||||
>
|
||||
<Pin class="h-3 w-3 fill-current" aria-hidden="true" />
|
||||
</button>
|
||||
<Pin v-else-if="isPinned" class="w-3 h-3 shrink-0 text-primary fill-current" aria-hidden="true" />
|
||||
<span v-if="formattedObjectStorage()" class="ml-auto shrink-0 text-right text-xs tabular-nums text-muted-foreground">{{ formattedObjectStorage() }}</span>
|
||||
<button
|
||||
v-if="isConnecting"
|
||||
|
||||
Reference in new issue
Block a user