mirror of
https://github.com/beekeeper-studio/beekeeper-studio.git
synced 2026-03-13 10:12:54 +08:00
updated with better error handling for bad moves and visual indicator for when a move completes
This commit is contained in:
@@ -197,6 +197,7 @@
|
||||
:show-duplicate="true"
|
||||
:pinned="pinnedConnections.includes(c)"
|
||||
:privacy-mode="privacyMode"
|
||||
:class="{ 'drag-pending': (pendingSaveIds || []).includes(c.id) }"
|
||||
@edit="edit"
|
||||
@remove="remove"
|
||||
@duplicate="duplicate"
|
||||
@@ -229,6 +230,7 @@
|
||||
:show-duplicate="true"
|
||||
:pinned="pinnedConnections.includes(c)"
|
||||
:privacy-mode="privacyMode"
|
||||
:class="{ 'drag-pending': (pendingSaveIds || []).includes(c.id) }"
|
||||
@edit="edit"
|
||||
@remove="remove"
|
||||
@duplicate="duplicate"
|
||||
@@ -254,6 +256,7 @@
|
||||
:show-duplicate="true"
|
||||
:pinned="pinnedConnections.includes(c)"
|
||||
:privacy-mode="privacyMode"
|
||||
:class="{ 'drag-pending': (pendingSaveIds || []).includes(c.id) }"
|
||||
@edit="edit"
|
||||
@remove="remove"
|
||||
@duplicate="duplicate"
|
||||
@@ -398,7 +401,8 @@ export default {
|
||||
...mapState('data/connections', {
|
||||
connectionsLoading: 'loading',
|
||||
connectionsError: 'error',
|
||||
connectionFilter: 'filter'
|
||||
connectionFilter: 'filter',
|
||||
pendingSaveIds: 'pendingSaveIds'
|
||||
}),
|
||||
...mapState('data/connectionFolders', {
|
||||
folders: 'items',
|
||||
@@ -680,7 +684,7 @@ export default {
|
||||
position: { before: null }
|
||||
})
|
||||
} catch (ex) {
|
||||
this.$noty.error(`Move error: ${ex.message}`)
|
||||
this.$noty.error(`Move error: ${ex.userMessage ?? ex.message}`)
|
||||
}
|
||||
},
|
||||
async onConnectionDrop(event, folder, currentList) {
|
||||
@@ -702,7 +706,7 @@ export default {
|
||||
})
|
||||
}
|
||||
} catch (ex) {
|
||||
this.$noty.error(`Move error: ${ex.message}`)
|
||||
this.$noty.error(`Move error: ${ex.userMessage ?? ex.message}`)
|
||||
}
|
||||
},
|
||||
async submitFolderModal() {
|
||||
@@ -726,4 +730,7 @@ export default {
|
||||
.folder-drop-zone {
|
||||
min-height: 8px;
|
||||
}
|
||||
.drag-pending {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
:item="item"
|
||||
:active="isActive(item)"
|
||||
:selected="selected === item"
|
||||
:class="{ 'drag-pending': (pendingSaveIds || []).includes(item.id) }"
|
||||
@remove="remove"
|
||||
@select="select"
|
||||
@open="open"
|
||||
@@ -132,6 +133,7 @@
|
||||
:item="item"
|
||||
:active="isActive(item)"
|
||||
:selected="selected === item"
|
||||
:class="{ 'drag-pending': (pendingSaveIds || []).includes(item.id) }"
|
||||
@remove="remove"
|
||||
@select="select"
|
||||
@open="open"
|
||||
@@ -157,6 +159,7 @@
|
||||
:item="item"
|
||||
:active="isActive(item)"
|
||||
:selected="selected === item"
|
||||
:class="{ 'drag-pending': (pendingSaveIds || []).includes(item.id) }"
|
||||
@remove="remove"
|
||||
@select="select"
|
||||
@open="open"
|
||||
@@ -285,7 +288,7 @@ export default {
|
||||
...mapGetters(['workspace', 'isCloud', 'isUltimate']),
|
||||
...mapGetters('data/queries', {'filteredQueries': 'filteredQueries'}),
|
||||
...mapState('tabs', {'activeTab': 'active'}),
|
||||
...mapState('data/queries', {'savedQueries': 'items', 'queriesLoading': 'loading', 'queriesError': 'error', 'savedQueryFilter': 'filter'}),
|
||||
...mapState('data/queries', {'savedQueries': 'items', 'queriesLoading': 'loading', 'queriesError': 'error', 'savedQueryFilter': 'filter', 'pendingSaveIds': 'pendingSaveIds'}),
|
||||
...mapState('data/queryFolders', {'folders': 'items', 'foldersLoading': 'loading', 'foldersError': 'error'}),
|
||||
filterQuery: {
|
||||
get() {
|
||||
@@ -472,7 +475,7 @@ export default {
|
||||
position: { before: null }
|
||||
})
|
||||
} catch (ex) {
|
||||
this.$noty.error(`Move error: ${ex.message}`)
|
||||
this.$noty.error(`Move error: ${ex.userMessage ?? ex.message}`)
|
||||
}
|
||||
},
|
||||
async onQueryDrop(event, folder, currentList) {
|
||||
@@ -494,7 +497,7 @@ export default {
|
||||
})
|
||||
}
|
||||
} catch (ex) {
|
||||
this.$noty.error(`Move error: ${ex.message}`)
|
||||
this.$noty.error(`Move error: ${ex.userMessage ?? ex.message}`)
|
||||
}
|
||||
},
|
||||
async submitFolderModal() {
|
||||
@@ -515,6 +518,9 @@ export default {
|
||||
.drag-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.drag-pending {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.folder-drop-zone {
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
@@ -6,21 +6,25 @@ export interface CloudResponseBase {
|
||||
code: number,
|
||||
errors: [],
|
||||
message: string | null,
|
||||
friendlyError?: string,
|
||||
}
|
||||
|
||||
export class CloudError extends Error {
|
||||
public status: number
|
||||
public errors: string[]
|
||||
public userMessage: string
|
||||
|
||||
constructor(status: number, message?: string, errors?: any[]) {
|
||||
const result = [`Cloud Error (${status}):`]
|
||||
const errorStrings: string[] = errors ? errors.map((e) => {
|
||||
return _.isString(e) ? e : e.message
|
||||
}) : []
|
||||
}).filter(Boolean) : []
|
||||
if (message) result.push(message)
|
||||
if (errors?.length) result.push(...errorStrings)
|
||||
if (errorStrings.length) result.push(...errorStrings)
|
||||
super(result.join(" "))
|
||||
this.status = status
|
||||
this.errors = errorStrings
|
||||
this.userMessage = message || errorStrings[0] || `Server error (${status})`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +36,6 @@ export function url(...parts: (string | number)[]) {
|
||||
// Accept any 2xx status as success. POST (create) returns 201, GET/PATCH return 200,
|
||||
// so checking for exactly 200 would incorrectly reject successful creates.
|
||||
export function res<T extends CloudResponseBase>(response: AxiosResponse < T >, key: string) {
|
||||
if (response.status < 200 || response.status >= 300) throw new CloudError(response.status, response.data?.message, response.data?.errors)
|
||||
if (response.status < 200 || response.status >= 300) throw new CloudError(response.status, response.data?.friendlyError || response.data?.message, response.data?.errors)
|
||||
return response.data[key]
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ export const CloudConnectionModule: DataStore<ICloudSavedConnection, State> = {
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot before any mutation (upsert mutates existing in-place via Object.assign)
|
||||
const snapshot = { ...existing }
|
||||
|
||||
// Mark as pending to protect from poll overwrites
|
||||
context.commit('addPendingSave', item.id)
|
||||
|
||||
@@ -94,6 +97,10 @@ export const CloudConnectionModule: DataStore<ICloudSavedConnection, State> = {
|
||||
})
|
||||
return item.id
|
||||
})
|
||||
} catch (e) {
|
||||
// Revert optimistic update using pre-mutation snapshot
|
||||
context.commit('upsert', snapshot)
|
||||
throw e
|
||||
} finally {
|
||||
// Clear pending status
|
||||
context.commit('removePendingSave', item.id)
|
||||
|
||||
@@ -65,6 +65,9 @@ export const CloudQueryModule: DataStore<ISavedQuery, State> = {
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot before any mutation (upsert mutates existing in-place via Object.assign)
|
||||
const snapshot = { ...existing }
|
||||
|
||||
// Mark as pending to protect from poll overwrites
|
||||
context.commit('addPendingSave', item.id)
|
||||
|
||||
@@ -96,6 +99,10 @@ export const CloudQueryModule: DataStore<ISavedQuery, State> = {
|
||||
})
|
||||
return item.id
|
||||
})
|
||||
} catch (e) {
|
||||
// Revert optimistic update using pre-mutation snapshot
|
||||
context.commit('upsert', snapshot)
|
||||
throw e
|
||||
} finally {
|
||||
// Clear pending status
|
||||
context.commit('removePendingSave', item.id)
|
||||
|
||||
Reference in New Issue
Block a user