mirror of
https://github.com/beekeeper-studio/beekeeper-studio.git
synced 2026-03-13 10:12:54 +08:00
- Add dedicated reorder API endpoint that returns all affected siblings - Fix position collisions by updating all sibling positions from API response - Unify local and cloud workspace reorder actions with same interface - Fix local workspace bug where unsorted state caused incorrect positions - Add pendingSaveIds tracking to prevent poll from overwriting optimistic updates - Add comprehensive unit tests for reorder scenarios Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import { IConnection } from "@/common/interfaces/IConnection";
|
|
import { DataState, DataStore, mutationsFor, utilActionsFor } from "@/store/modules/data/DataModuleBase";
|
|
import _ from "lodash";
|
|
import Vue from "vue";
|
|
|
|
type State = DataState<IConnection>
|
|
|
|
export const UtilConnectionModule: DataStore<IConnection, State> = {
|
|
namespaced: true,
|
|
state: {
|
|
items: [],
|
|
loading: false,
|
|
error: null,
|
|
pollError: null,
|
|
filter: undefined,
|
|
pendingSaveIds: []
|
|
},
|
|
mutations: mutationsFor<IConnection>({
|
|
connectionFilter(state: DataState<IConnection>, str: string) {
|
|
state.filter = str;
|
|
}
|
|
}),
|
|
actions: utilActionsFor<IConnection>('saved', {
|
|
setConnectionFilter: _.debounce(function (context, filter) {
|
|
context.commit('connectionFilter', filter);
|
|
}, 500),
|
|
|
|
// Reorder action for drag/drop - matches cloud module interface
|
|
async reorder(context, { item, position, connectionFolderId }) {
|
|
const existing = context.state.items.find(c => c.id === item.id)
|
|
if (!existing) return
|
|
|
|
const targetFolderId = connectionFolderId ?? existing.connectionFolderId
|
|
|
|
// Get all siblings in the target folder, sorted by position
|
|
const siblings = context.state.items
|
|
.filter(c => c.connectionFolderId === targetFolderId)
|
|
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
|
|
|
// Build the new ordered list
|
|
let newOrder: IConnection[]
|
|
const itemWithoutSelf = siblings.filter(c => c.id !== item.id)
|
|
|
|
if (typeof position === 'object') {
|
|
if (position.after) {
|
|
const afterIndex = itemWithoutSelf.findIndex(c => c.id === position.after)
|
|
newOrder = [
|
|
...itemWithoutSelf.slice(0, afterIndex + 1),
|
|
{ ...existing, connectionFolderId: targetFolderId },
|
|
...itemWithoutSelf.slice(afterIndex + 1)
|
|
]
|
|
} else if (position.before) {
|
|
const beforeIndex = itemWithoutSelf.findIndex(c => c.id === position.before)
|
|
newOrder = [
|
|
...itemWithoutSelf.slice(0, beforeIndex),
|
|
{ ...existing, connectionFolderId: targetFolderId },
|
|
...itemWithoutSelf.slice(beforeIndex)
|
|
]
|
|
} else {
|
|
// { before: null } means first position
|
|
newOrder = [{ ...existing, connectionFolderId: targetFolderId }, ...itemWithoutSelf]
|
|
}
|
|
} else {
|
|
// Numeric position - insert at that index
|
|
newOrder = [...itemWithoutSelf]
|
|
newOrder.splice(position, 0, { ...existing, connectionFolderId: targetFolderId })
|
|
}
|
|
|
|
// Assign sequential positions
|
|
const updates = newOrder.map((c, idx) => ({
|
|
...c,
|
|
position: idx + 1
|
|
}))
|
|
|
|
// Optimistic update
|
|
context.commit('upsert', updates)
|
|
|
|
// Save all items
|
|
const saved = await Promise.all(
|
|
updates.map(c => Vue.prototype.$util.send('appdb/saved/save', { obj: c }))
|
|
)
|
|
context.commit('upsert', saved)
|
|
|
|
return item.id
|
|
}
|
|
}),
|
|
getters: {
|
|
filteredConnections(state) {
|
|
if (!state.filter) {
|
|
return state.items;
|
|
}
|
|
|
|
const startsWithFilter = _(state.items)
|
|
.filter((item) => _.startsWith(item.name.toLowerCase(), state.filter))
|
|
.value();
|
|
|
|
const containsFilter = _(state.items)
|
|
.difference(startsWithFilter)
|
|
.filter((item) => item.name.toLowerCase().includes(state.filter.toLowerCase()))
|
|
.value();
|
|
|
|
return _.concat(startsWithFilter, containsFilter);
|
|
}
|
|
}
|
|
|
|
}
|