mirror of
https://github.com/beekeeper-studio/beekeeper-studio.git
synced 2026-03-13 10:12:54 +08:00
Merge pull request #2641 from beekeeper-studio/feature/2379-Tab-History
Feature/2379 tab history
This commit is contained in:
@@ -13,6 +13,7 @@ import { GeneratorHandlers } from '@/handlers/generatorHandlers';
|
||||
import { Handlers } from '../backend/handlers/handlers';
|
||||
import { newState, removeState, state } from '@/handlers/handlerState';
|
||||
import { QueryHandlers } from '@/handlers/queryHandlers';
|
||||
import { TabHistoryHandlers } from '@/handlers/tabHistoryHandlers'
|
||||
import { ExportHandlers } from '@commercial/backend/handlers/exportHandlers';
|
||||
import { BackupHandlers } from '@commercial/backend/handlers/backupHandlers';
|
||||
import { ImportHandlers } from '@commercial/backend/handlers/importHandlers';
|
||||
@@ -50,6 +51,7 @@ export const handlers: Handlers = {
|
||||
...EnumHandlers,
|
||||
...TempHandlers,
|
||||
...LicenseHandlers,
|
||||
...TabHistoryHandlers,
|
||||
...(platformInfo.isDevelopment && DevHandlers),
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { LoggerOptions } from 'typeorm/logger/LoggerOptions'
|
||||
import { PinnedEntity } from "./models/PinnedEntity"
|
||||
import { CloudCredential } from "./models/CloudCredential"
|
||||
import { OpenTab } from "./models/OpenTab"
|
||||
|
||||
import { LicenseKey } from "./models/LicenseKey"
|
||||
import { HiddenEntity } from "./models/HiddenEntity"
|
||||
import { HiddenSchema } from "./models/HiddenSchema"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import _ from 'lodash'
|
||||
import ISavedQuery from "@/common/interfaces/ISavedQuery";
|
||||
import { TableFilter, TableOrView } from "@/lib/db/models";
|
||||
import { Column, Entity } from "typeorm";
|
||||
import { Column, Entity, LessThan, Not, IsNull, DeleteDateColumn } from "typeorm";
|
||||
import { ApplicationEntity } from "./application_entity";
|
||||
import _ from 'lodash'
|
||||
import { TransportOpenTab } from "@/common/transport/TransportOpenTab";
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ type TabType = 'query' | 'table' | 'table-properties' | 'settings' | 'table-buil
|
||||
|
||||
const pickable = ['title', 'tabType', 'unsavedChanges', 'unsavedQueryText', 'tableName', 'schemaName', 'entityType', 'titleScope', 'connectionId', 'workspaceId', 'position']
|
||||
|
||||
interface ConnectionIds {
|
||||
connectionId: number,
|
||||
workspaceId: number
|
||||
}
|
||||
|
||||
@Entity({ name: 'tabs'})
|
||||
export class OpenTab extends ApplicationEntity {
|
||||
@@ -74,6 +78,12 @@ export class OpenTab extends ApplicationEntity {
|
||||
filters?: string
|
||||
isRunning = false;
|
||||
|
||||
@Column({type: 'datetime', nullable: true})
|
||||
lastActive?: Date
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt?: Date
|
||||
|
||||
public setFilters(filters: Nullable<TableFilter[]>) {
|
||||
if (filters && _.isArray(filters)) {
|
||||
this.filters = JSON.stringify(filters)
|
||||
@@ -156,5 +166,45 @@ export class OpenTab extends ApplicationEntity {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
static async getHistory(connectionIds: ConnectionIds, limit = 10): Promise<TransportOpenTab[]> {
|
||||
const { connectionId, workspaceId } = connectionIds
|
||||
return await this.find({
|
||||
where: {
|
||||
connectionId,
|
||||
workspaceId
|
||||
},
|
||||
order: {
|
||||
lastActive: 'DESC'
|
||||
},
|
||||
take: limit,
|
||||
withDeleted: true
|
||||
})
|
||||
}
|
||||
|
||||
static async clearOldDeletedTabs(connectionIds: ConnectionIds, xDays: number): Promise<void> {
|
||||
const { connectionId, workspaceId } = connectionIds
|
||||
const deletedAtThreshold = new Date()
|
||||
deletedAtThreshold.setDate(deletedAtThreshold.getDate() - xDays)
|
||||
|
||||
await this.delete({
|
||||
connectionId,
|
||||
workspaceId,
|
||||
deletedAt: LessThan(deletedAtThreshold)
|
||||
})
|
||||
}
|
||||
|
||||
static async getClosedHistory(connectionIds: ConnectionIds): Promise<TransportOpenTab> {
|
||||
const { connectionId, workspaceId } = connectionIds
|
||||
return await this.findOne({
|
||||
where: {
|
||||
deletedAt: Not(IsNull()),
|
||||
connectionId,
|
||||
workspaceId
|
||||
},
|
||||
order: {
|
||||
deletedAt: 'DESC'
|
||||
},
|
||||
withDeleted: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface TransportOpenTab extends Transport {
|
||||
connectionId: number,
|
||||
workspaceId?: number,
|
||||
filters?: string,
|
||||
lastActive?: Date|null,
|
||||
deletedAt?: Date|null
|
||||
isRunning: boolean, // not on the actual model, but used in frontend
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
:key="tab.id"
|
||||
:tab="tab"
|
||||
:tabs-count="tabItems.length"
|
||||
:selected="activeTab === tab"
|
||||
:selected="activeTab.id === tab.id"
|
||||
@click="click"
|
||||
@close="close"
|
||||
@closeAll="closeAll"
|
||||
@@ -55,12 +55,12 @@
|
||||
class="tab-pane"
|
||||
:id="'tab-' + idx"
|
||||
:key="tab.id"
|
||||
:class="{active: (activeTab === tab)}"
|
||||
v-show="activeTab === tab"
|
||||
:class="{active: (activeTab.id === tab.id)}"
|
||||
v-show="activeTab.id === tab.id"
|
||||
>
|
||||
<QueryEditor
|
||||
v-if="tab.tabType === 'query'"
|
||||
:active="activeTab === tab"
|
||||
:active="activeTab.id === tab.id"
|
||||
:tab="tab"
|
||||
:tab-id="tab.id"
|
||||
/>
|
||||
@@ -72,7 +72,7 @@
|
||||
<template v-slot:default="slotProps">
|
||||
<TableTable
|
||||
:tab="tab"
|
||||
:active="activeTab === tab"
|
||||
:active="activeTab.id === tab.id"
|
||||
:table="slotProps.table"
|
||||
/>
|
||||
</template>
|
||||
@@ -84,7 +84,7 @@
|
||||
>
|
||||
<template v-slot:default="slotProps">
|
||||
<TableProperties
|
||||
:active="activeTab === tab"
|
||||
:active="activeTab.id === tab.id"
|
||||
:tab="tab"
|
||||
:tab-id="tab.id"
|
||||
:table="slotProps.table"
|
||||
@@ -93,7 +93,7 @@
|
||||
</tab-with-table>
|
||||
<TableBuilder
|
||||
v-if="tab.tabType === 'table-builder'"
|
||||
:active="activeTab === tab"
|
||||
:active="activeTab.id === tab.id"
|
||||
:tab="tab"
|
||||
:tab-id="tab.id"
|
||||
/>
|
||||
@@ -107,7 +107,7 @@
|
||||
v-if="tab.tabType === 'backup'"
|
||||
:connection="connection"
|
||||
:is-restore="false"
|
||||
:active="activeTab === tab"
|
||||
:active="activeTab.id === tab.id"
|
||||
:tab="tab"
|
||||
@close="close"
|
||||
/>
|
||||
@@ -115,7 +115,7 @@
|
||||
v-if="tab.tabType === 'restore'"
|
||||
:connection="connection"
|
||||
:is-restore="true"
|
||||
:active="activeTab === tab"
|
||||
:active="activeTab.id === tab.id"
|
||||
:tab="tab"
|
||||
@close="close"
|
||||
/>
|
||||
@@ -265,7 +265,7 @@ import { AppEvent } from '../common/AppEvent'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import Draggable from 'vuedraggable'
|
||||
import ShortcutHints from './editor/ShortcutHints.vue'
|
||||
import { FormatterDialect, DialectTitles } from '@shared/lib/dialects/models'
|
||||
import { FormatterDialect } from '@shared/lib/dialects/models'
|
||||
import Vue from 'vue';
|
||||
import { CloseTabOptions } from '@/common/appdb/models/CloseTab';
|
||||
import TabWithTable from './common/TabWithTable.vue';
|
||||
@@ -277,7 +277,6 @@ import { readWebFile } from '@/common/utils'
|
||||
import Noty from 'noty'
|
||||
import ConfirmationModal from './common/modals/ConfirmationModal.vue'
|
||||
import SqlFilesImportModal from '@/components/common/modals/SqlFilesImportModal.vue'
|
||||
import DetailViewSidebar from '@/components/sidebar/DetailViewSidebar.vue'
|
||||
|
||||
import { safeSqlFormat as safeFormat } from '@/common/utils';
|
||||
import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/transport/TransportOpenTab'
|
||||
@@ -301,7 +300,6 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
PendingChangesButton,
|
||||
ConfirmationModal,
|
||||
SqlFilesImportModal,
|
||||
DetailViewSidebar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -401,7 +399,7 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
},
|
||||
keymap() {
|
||||
const result = {
|
||||
'ctrl+shift+T': this.reopenLastClosedTab,
|
||||
'ctrl+shift+t': this.reopenLastClosedTab,
|
||||
'ctrl+tab': this.nextTab,
|
||||
'ctrl+shift+tab': this.previousTab,
|
||||
'alt+1': this.handleAltNumberKeyPress,
|
||||
@@ -416,8 +414,9 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
}
|
||||
|
||||
if(this.$config.isMac) {
|
||||
result['shift+meta+['] = this.previousTab
|
||||
result['shift+meta+]'] = this.nextTab
|
||||
result['meta+shift+t'] = this.reopenLastClosedTab
|
||||
result['shift+meta+['] = this.previousTab
|
||||
result['shift+meta+]'] = this.nextTab
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -596,25 +595,26 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
handleCreateTab() {
|
||||
this.createQuery()
|
||||
},
|
||||
createQuery(optionalText, queryTitle?) {
|
||||
async createQuery(optionalText, queryTitle?) {
|
||||
// const text = optionalText ? optionalText : ""
|
||||
console.log("Creating tab")
|
||||
let qNum = 0
|
||||
let tabName = "New Query"
|
||||
do {
|
||||
qNum = qNum + 1
|
||||
tabName = `Query #${qNum}`
|
||||
} while (this.tabItems.filter((t) => t.title === tabName).length > 0);
|
||||
if (queryTitle) {
|
||||
tabName = queryTitle
|
||||
} else {
|
||||
do {
|
||||
qNum = qNum + 1
|
||||
tabName = `Query #${qNum}`
|
||||
} while (this.tabItems.filter((t) => t.title === tabName).length > 0);
|
||||
}
|
||||
|
||||
const result = {} as TransportOpenTab;
|
||||
result.tabType = 'query'
|
||||
result.title = tabName,
|
||||
result.unsavedChanges = false
|
||||
result.unsavedQueryText = optionalText
|
||||
this.addTab(result)
|
||||
const result = {} as TransportOpenTab;
|
||||
result.tabType = 'query'
|
||||
result.title = tabName,
|
||||
result.unsavedChanges = false
|
||||
result.unsavedQueryText = optionalText
|
||||
await this.addTab(result)
|
||||
},
|
||||
async loadTableCreate(table) {
|
||||
let method = null
|
||||
@@ -877,9 +877,9 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
openTableProperties({ table }) {
|
||||
const t = {} as TransportOpenTab;
|
||||
t.tabType = 'table-properties';
|
||||
t.tableName = table.name
|
||||
t.schemaName = table.schema
|
||||
t.title = table.name
|
||||
t.tableName = table.name ?? table.tableName
|
||||
t.schemaName = table.schema ?? table.schemaName
|
||||
t.title = table.name ?? table.tableName
|
||||
const existing = this.tabItems.find((tab) => matches(tab, t))
|
||||
if (existing) return this.$store.dispatch('tabs/setActive', existing)
|
||||
this.addTab(t)
|
||||
@@ -887,9 +887,9 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
async openTable({ table, filters, openDetailView }) {
|
||||
let tab = {} as TransportOpenTab;
|
||||
tab.tabType = 'table';
|
||||
tab.title = table.name
|
||||
tab.tableName = table.name
|
||||
tab.schemaName = table.schema
|
||||
tab.title = table.name ?? table.tableName
|
||||
tab.tableName = table.name ?? table.tableName
|
||||
tab.schemaName = table.schema ?? table.schemaName
|
||||
tab.entityType = table.entityType
|
||||
tab = setFilters(tab, filters)
|
||||
tab.titleScope = "all"
|
||||
@@ -1040,7 +1040,7 @@ import { TransportOpenTab, setFilters, matches, duplicate } from '@/common/trans
|
||||
|
||||
},
|
||||
createQueryFromItem(item) {
|
||||
this.createQuery(item.text)
|
||||
this.createQuery(item.text ?? item.unsavedQueryText, item.title ?? null)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
class="shortcut"
|
||||
v-else
|
||||
>
|
||||
<span>Ctrl</span><span>Shift</span><span>N</span>
|
||||
<span>Ctrl</span><span>⇧</span><span>N</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
@@ -81,6 +81,21 @@
|
||||
<span>Ctrl</span><span>T</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div>Reopen Closed Tabs</div>
|
||||
<div
|
||||
class="shortcut"
|
||||
v-if="$config.isMac"
|
||||
>
|
||||
<span>⌘</span><span>⇧</span><span>T</span>
|
||||
</div>
|
||||
<div
|
||||
class="shortcut"
|
||||
v-else
|
||||
>
|
||||
<span>Ctrl</span><span>⇧</span><span>T</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div>Close Tab</div>
|
||||
<div
|
||||
|
||||
@@ -26,6 +26,40 @@
|
||||
>
|
||||
<li>No Results</li>
|
||||
</ul>
|
||||
<ul
|
||||
class="results"
|
||||
v-if="!results.length && !searchTerm && historyResults.length"
|
||||
>
|
||||
<li
|
||||
class="result-item"
|
||||
v-for="(blob, idx) in historyResults"
|
||||
:key="idx"
|
||||
:class="{selected: idx === selectedItem}"
|
||||
@click.prevent="handleHistoryClick($event, blob)"
|
||||
>
|
||||
<table-icon
|
||||
v-if="blob.tabType === 'table' || blob.tabDetails?.tabType === 'table'"
|
||||
:table="blob"
|
||||
/>
|
||||
<i
|
||||
class="material-icons item-icon query"
|
||||
v-else-if="blob.tabType === 'table-properties' || blob.tabDetails?.tabType === 'table-properties'"
|
||||
>construction</i>
|
||||
<i
|
||||
class="material-icons item-icon connection"
|
||||
v-else-if="blob.tabType === 'connection' || blob.tabDetails?.tabType === 'connection'"
|
||||
>power</i>
|
||||
<i
|
||||
class="material-icons item-icon database"
|
||||
v-else-if="blob.tabType === 'database' || blob.tabDetails?.tabType === 'database'"
|
||||
>storage</i>
|
||||
<i
|
||||
class="material-icons item-icon database"
|
||||
v-else
|
||||
>code</i>
|
||||
<span v-html="highlightHistory(blob)" />
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="results empty"
|
||||
v-if="!results.length && !searchTerm"
|
||||
@@ -112,6 +146,7 @@ export default Vue.extend({
|
||||
document.addEventListener('mousedown', this.maybeHide)
|
||||
this.$nextTick(() => {
|
||||
this.$refs.searchBox.focus()
|
||||
this.getTabHistory()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
@@ -122,7 +157,8 @@ export default Vue.extend({
|
||||
active: false,
|
||||
searchTerm: null,
|
||||
results: [],
|
||||
selectedItem: 0
|
||||
selectedItem: 0,
|
||||
historyResults: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -148,8 +184,11 @@ export default Vue.extend({
|
||||
|
||||
},
|
||||
computed: {
|
||||
...mapState(['usedConfig']),
|
||||
...mapState('search', ['searchIndex']),
|
||||
...mapGetters({ database: 'search/database'}),
|
||||
...mapState(['tables']),
|
||||
...mapState('tabs', { 'tabs': 'tabs' }),
|
||||
elements() {
|
||||
if (this.$refs.menu) {
|
||||
return Array.from(this.$refs.menu.getElementsByTagName("*"))
|
||||
@@ -179,6 +218,10 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getTabHistory() {
|
||||
const results = await Vue.prototype.$util.send('appdb/tabhistory/get', { workspaceId: this.usedConfig.workspaceId, connectionId: this.usedConfig.id });
|
||||
this.historyResults = results
|
||||
},
|
||||
highlight(blob) {
|
||||
const dangerous = blob.title
|
||||
const text = escapeHtml(dangerous || "unknown item")
|
||||
@@ -187,7 +230,16 @@ export default Vue.extend({
|
||||
|
||||
return result
|
||||
},
|
||||
highlightHistory(blob) {
|
||||
const dangerous = blob.title
|
||||
let historyText = [escapeHtml(dangerous || 'unknown item')]
|
||||
|
||||
if (blob.deletedAt) {
|
||||
historyText.push('recently closed')
|
||||
}
|
||||
|
||||
return historyText.join(' - ')
|
||||
},
|
||||
openSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.searchBox.focus()
|
||||
@@ -240,6 +292,15 @@ export default Vue.extend({
|
||||
}
|
||||
if (!persistSearch) this.closeSearch()
|
||||
},
|
||||
async handleHistoryClick(_event: MouseEvent, result: any) {
|
||||
this.closeSearch()
|
||||
if (result.deletedAt) {
|
||||
result.deletedAt = null
|
||||
await this.$store.dispatch('tabs/add', { item: result } )
|
||||
}
|
||||
|
||||
return await this.$store.dispatch('tabs/setActive', result)
|
||||
},
|
||||
handleClick(event: MouseEvent, result: any) {
|
||||
if (event.ctrlKey) {
|
||||
this.submitAlt(result)
|
||||
@@ -248,13 +309,22 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
enter() {
|
||||
const result = this.results[this.selectedItem]
|
||||
this.submit(result)
|
||||
let result = this.results[this.selectedItem]
|
||||
if (!this.results.length && !this.searchTerm && this.historyResults.length) {
|
||||
result = this.historyResults[this.selectedItem]
|
||||
this.handleHistoryClick(_, result)
|
||||
} else {
|
||||
this.submit(result)
|
||||
}
|
||||
},
|
||||
metaEnter() {
|
||||
const result = this.results[this.selectedItem]
|
||||
this.submitAlt(result)
|
||||
|
||||
let result = this.results[this.selectedItem]
|
||||
if (!this.results.length && !this.searchTerm && this.historyResults.length) {
|
||||
result = this.historyResults[this.selectedItem]
|
||||
this.handleHistoryClick(_, result)
|
||||
} else {
|
||||
this.submitAlt(result)
|
||||
}
|
||||
},
|
||||
persistentSearchEnter(){
|
||||
const cursorPosition = this.$refs.searchBox.selectionStart
|
||||
|
||||
@@ -32,7 +32,7 @@ class State {
|
||||
connectionAbortController: AbortController = null;
|
||||
|
||||
// enums
|
||||
enumsInitialized: boolean = false;
|
||||
enumsInitialized = false;
|
||||
|
||||
private enumWatcher: FSWatcher = null;
|
||||
|
||||
|
||||
19
apps/studio/src/handlers/tabHistoryHandlers.ts
Normal file
19
apps/studio/src/handlers/tabHistoryHandlers.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { OpenTab } from "@/common/appdb/models/OpenTab";
|
||||
import { TransportOpenTab } from "@/common/transport/TransportOpenTab";
|
||||
|
||||
interface ConnectionIds {
|
||||
connectionId: number,
|
||||
workspaceId: number
|
||||
}
|
||||
|
||||
export const TabHistoryHandlers = {
|
||||
'appdb/tabhistory/get': async (connectionIds, limit = 10): Promise<TransportOpenTab[]> => {
|
||||
return await OpenTab.getHistory(connectionIds, limit)
|
||||
},
|
||||
'appdb/tabhistory/getLastDeletedTab': async (connectionIds): Promise<TransportOpenTab> => {
|
||||
return await OpenTab.getClosedHistory(connectionIds)
|
||||
},
|
||||
'appdb/tabhistory/clearDeletedTabs': async (connectionIds: ConnectionIds, xDays = 7): Promise<void> => {
|
||||
return await OpenTab.clearOldDeletedTabs(connectionIds, xDays)
|
||||
}
|
||||
}
|
||||
12
apps/studio/src/migration/20241116_tab_history_updates.js
Normal file
12
apps/studio/src/migration/20241116_tab_history_updates.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export default {
|
||||
name: '20241116-tab-history-updates',
|
||||
async run(runner) {
|
||||
const queries = [
|
||||
`ALTER TABLE tabs ADD COLUMN lastActive datetime`,
|
||||
`ALTER TABLE tabs ADD COLUMN deletedAt datetime`
|
||||
]
|
||||
for (let index = 0; index < queries.length; index++) {
|
||||
await runner.query(queries[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
12
apps/studio/src/migration/20250211_add_tab_history_index.js
Normal file
12
apps/studio/src/migration/20250211_add_tab_history_index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export default {
|
||||
name: '20250211-add-tab-history-index',
|
||||
async run(runner) {
|
||||
const queries = [
|
||||
`CREATE INDEX idx_tabs_deleted_at_desc ON tabs(deletedAt DESC)`,
|
||||
`CREATE INDEX idx_tabs_last_active_desc ON tabs(lastActive DESC)`
|
||||
]
|
||||
for (let index = 0; index < queries.length; index++) {
|
||||
await runner.query(queries[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,10 @@ import lastUsedWorkspace from './20240923_user_settings_default_workspace'
|
||||
import userSettingKeymap from './20241017_add_user_setting_keymap'
|
||||
import missingUserSettings from './20241017_add_missing_user_settings'
|
||||
import useBeta from './20241009_add_beta_toggle'
|
||||
import tabHistoryUpdates from './20241116_tab_history_updates'
|
||||
import deleteDuplicateConnections from './20241115_delete_duplicate_connections'
|
||||
import addNewUrlField from './20250128_add_new_url_field'
|
||||
import tabHistoryIndex from './20250211_add_tab_history_index'
|
||||
import fixOracleData from './20250225_oracle_default_connection_method'
|
||||
|
||||
import ultimate from './ultimate/index'
|
||||
@@ -77,7 +79,11 @@ const realMigrations = [
|
||||
firebirdConnection, exportPath, UserSettingsWindowPosition,
|
||||
demoSetup, minimalMode, tokenCache, libsqlOptions, nameTokenCache, lastUsedWorkspace,
|
||||
maxAllowedAppRelease, userSettingKeymap, missingUserSettings,
|
||||
useBeta, deleteDuplicateConnections, addNewUrlField,
|
||||
useBeta,
|
||||
deleteDuplicateConnections,
|
||||
tabHistoryUpdates,
|
||||
addNewUrlField,
|
||||
tabHistoryIndex,
|
||||
fixOracleData,
|
||||
]
|
||||
|
||||
|
||||
@@ -451,6 +451,7 @@ const store = new Vuex.Store<State>({
|
||||
await context.dispatch('data/usedconnections/recordUsed', config)
|
||||
context.dispatch('updateWindowTitle', config)
|
||||
|
||||
await Vue.prototype.$util.send('appdb/tabhistory/clearDeletedTabs', { workspaceId: context.state.usedConfig.workspaceId, connectionId: context.state.usedConfig.id })
|
||||
} else {
|
||||
throw "No username provided"
|
||||
}
|
||||
|
||||
@@ -63,7 +63,12 @@ export const TabModule: Module<State, RootState> = {
|
||||
state.lastClosedTabs.push(...tabs.map((tab) => duplicate(tab)))
|
||||
},
|
||||
setActive(state, tab?: TransportOpenTab) {
|
||||
const tabs = state.tabs.map(t => {
|
||||
t.active = t.id === tab.id
|
||||
return t
|
||||
})
|
||||
state.active = tab
|
||||
state.tabs = tabs
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
@@ -78,7 +83,7 @@ export const TabModule: Module<State, RootState> = {
|
||||
workspaceId: context.rootState.workspaceId
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
context.commit('set', tabs || [])
|
||||
if (tabs?.length) {
|
||||
const active = tabs.find((t) => t.active) || tabs[0]
|
||||
@@ -87,25 +92,28 @@ export const TabModule: Module<State, RootState> = {
|
||||
}
|
||||
},
|
||||
async unload(context) {
|
||||
await Vue.prototype.$util.send('appdb/tabs/remove', { obj: context.state.tabs })
|
||||
context.commit('remove', context.state.tabs)
|
||||
context.commit('setActive', null)
|
||||
},
|
||||
async reopenLastClosedTab(context) {
|
||||
// Walk through array in reverse to check if it belongs to the current connection
|
||||
for (let i = context.state.lastClosedTabs.length - 1; i >= 0; i--) {
|
||||
const tab = context.state.lastClosedTabs[i]
|
||||
// Does this tab belong to the current connection?
|
||||
if (tab.connectionId === context.rootState.usedConfig?.id && tab.workspaceId === context.rootState.workspaceId) {
|
||||
const { usedConfig, workspaceId } = context.rootState
|
||||
|
||||
try {
|
||||
const tab = await Vue.prototype.$util.send('appdb/tabhistory/getLastDeletedTab', { workspaceId: workspaceId, connectionId: usedConfig.id });
|
||||
if (tab) {
|
||||
tab.deletedAt = null
|
||||
await context.dispatch('add', { item: tab })
|
||||
await context.dispatch('setActive', tab)
|
||||
break
|
||||
}
|
||||
}catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
async add(context, options: { item: TransportOpenTab, endOfPosition?: boolean }) {
|
||||
const { usedConfig } = context.rootState
|
||||
let { item, endOfPosition } = options
|
||||
const { endOfPosition } = options
|
||||
let { item } = options
|
||||
|
||||
if (endOfPosition) {
|
||||
item.position = (context.getters.sortedTabs.reverse()[0]?.position || 0) + 1
|
||||
}
|
||||
@@ -113,7 +121,9 @@ export const TabModule: Module<State, RootState> = {
|
||||
log.info("saving tab", item)
|
||||
item.workspaceId = context.rootState.workspaceId
|
||||
item.connectionId = usedConfig.id
|
||||
item = await Vue.prototype.$util.send('appdb/tabs/save', { obj: item });
|
||||
item.deletedAt = null
|
||||
item.active = true
|
||||
item = await Vue.prototype.$util.send('appdb/tabs/save', { obj: item })
|
||||
}
|
||||
context.commit('add', item)
|
||||
return item;
|
||||
@@ -122,22 +132,27 @@ export const TabModule: Module<State, RootState> = {
|
||||
items.forEach((p, idx) => p.position = idx)
|
||||
const { usedConfig } = context.rootState
|
||||
context.commit('set', items)
|
||||
if (usedConfig?.id) await Vue.prototype.$util.send('appdb/tabs/save', { obj: items });
|
||||
if (usedConfig?.id) await Vue.prototype.$util.send('appdb/tabs/save', { obj: items })
|
||||
},
|
||||
async remove(context, rawItems: TransportOpenTab | TransportOpenTab[]) {
|
||||
const items = _.isArray(rawItems) ? rawItems : [rawItems]
|
||||
items.forEach((tab) => context.commit('remove', tab))
|
||||
items.forEach((tab) => {
|
||||
tab.deletedAt = new Date()
|
||||
tab.position = 99
|
||||
return context.commit('remove', tab)
|
||||
})
|
||||
const { usedConfig } = context.rootState
|
||||
if (usedConfig?.id) {
|
||||
await Vue.prototype.$util.send('appdb/tabs/remove', { obj: items });
|
||||
await Vue.prototype.$util.send('appdb/tabs/save', { obj: items })
|
||||
}
|
||||
},
|
||||
async save(context, rawTabs: TransportOpenTab[] | TransportOpenTab) {
|
||||
try {
|
||||
if (rawTabs == null) return
|
||||
const tabs = _.isArray(rawTabs) ? rawTabs : [rawTabs]
|
||||
const { usedConfig } = context.rootState
|
||||
if (usedConfig?.id) {
|
||||
await Vue.prototype.$util.send('appdb/tabs/save', { obj: tabs });
|
||||
await Vue.prototype.$util.send('appdb/tabs/save', { obj: tabs })
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error("tab/save", ex)
|
||||
@@ -149,7 +164,6 @@ export const TabModule: Module<State, RootState> = {
|
||||
} catch (ex) {
|
||||
console.error("tab/saveAll", ex)
|
||||
}
|
||||
|
||||
},
|
||||
async setActive(context, tab: TransportOpenTab) {
|
||||
const oldActive = context.state.active
|
||||
@@ -158,8 +172,10 @@ export const TabModule: Module<State, RootState> = {
|
||||
oldActive.active = false
|
||||
}
|
||||
tab.active = true
|
||||
await context.dispatch('save', [tab, oldActive].filter((x) => !!x))
|
||||
tab.lastActive = new Date()
|
||||
tab.deletedAt = null
|
||||
|
||||
await context.dispatch('save', [tab, oldActive].filter((x) => !!x))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user