Merge pull request #2969 from beekeeper-studio/fix/status-bar

Add global status bar
This commit is contained in:
Matthew Rathbone
2025-04-11 11:58:11 -05:00
committed by GitHub
20 changed files with 185 additions and 150 deletions

View File

@@ -1,19 +1,29 @@
@use "sass:color";
$button-height: $statusbar-height * 0.72;
.statusbar {
.global-status-bar {
display: flex;
align-items: center;
justify-content: space-between;
background: $statusbar-bg;
color: rgba($statusbar-text, 0.87);
min-height: $statusbar-height;
height: $statusbar-height;
line-height: $statusbar-height;
font-size: 0.8rem;
padding: 0 $gutter-h;
// overflow: hidden;
> .vue-portal-target {
flex-grow: 1;
}
.statusbar {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 $gutter-h;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
/* display: none; <- Crashes Chrome on hover */
@@ -22,7 +32,7 @@ $button-height: $statusbar-height * 0.72;
}
> .col {
.statusbar > .col {
display: flex;
align-items: center;
}
@@ -59,32 +69,20 @@ $button-height: $statusbar-height * 0.72;
}
// Row Actions
// -------------------------
.connection-button > .btn {
&:hover {
background: rgba(white, 0.1);
}
.connection-button-wrapper {
height: 100%;
overflow: hidden;
}
.connection-button {
height: 100%;
> .btn{
padding: 0;
margin: 0;
box-shadow: none!important;
background: transparent;
color: $statusbar-text;
height: $button-height;
line-height: $button-height;
min-width: min-content;
margin: 0 0.15rem;
margin-top: -1px;
padding: 0 0.35rem;
--ripple-background: none;
border-radius: $btn-border-radius-small;
transition: background 0.15s ease-in-out;
.material-icons {
width: 16px;
}
}
}
@@ -307,27 +305,19 @@ $button-height: $statusbar-height * 0.72;
// Connection Status
// ---------------------------
.connection-button,
.connection-button > .btn {
height: $statusbar-height;
line-height: $statusbar-height;
margin: -$gutter-h;
padding: 0 $gutter-h;
flex: 1 1 100%;
justify-content: flex-start;
overflow: hidden;
}
.connection-name {
font-size: 0.85rem;
}
.connection-button {
position: relative;
.material-icons {
font-size: 20px;
min-width: 38px;
text-align: center;
margin: 0;
width: calc(var(--connection-button-icon-width) + 0.2rem);
}
> .btn {
width: 100%;
height: 100%;
text-align: left;
border-radius: 0;
--trigger-effect: none;

View File

@@ -21,9 +21,6 @@
@toggleSidebar="handleToggleOpenPrimarySidebar"
:sidebar-shown="primarySidebarOpen"
/>
<statusbar>
<ConnectionButton />
</statusbar>
</sidebar>
<div
ref="content"
@@ -34,6 +31,10 @@
</div>
<secondary-sidebar ref="secondarySidebar" @close="handleToggleOpenSecondarySidebar(false)" />
</div>
<global-status-bar
:connection-button-width="primarySidebarWidth"
:connection-button-icon-width="$bksConfig.ui.layout.primarySidebarMinWidth"
/>
<quick-search
v-if="quickSearchShown"
@close="quickSearchShown=false"
@@ -50,26 +51,27 @@
import SecondarySidebar from './sidebar/SecondarySidebar.vue'
import CoreTabs from './CoreTabs.vue'
import Split from 'split.js'
import Statusbar from './common/StatusBar.vue'
import ConnectionButton from './sidebar/core/ConnectionButton.vue'
import ExportManager from './export/ExportManager.vue'
import {AppEvent} from '../common/AppEvent'
import QuickSearch from './quicksearch/QuickSearch.vue'
import ProgressBar from './editor/ProgressBar.vue'
import LostConnectionModal from './LostConnectionModal.vue'
import GlobalStatusBar from './GlobalStatusBar.vue'
import Vue from 'vue'
import RenameDatabaseElementModal from './common/modals/RenameDatabaseElementModal.vue'
import { mapGetters, mapActions, mapState } from 'vuex'
import _ from "lodash"
export default Vue.extend({
components: { CoreSidebar, CoreTabs, Sidebar, Statusbar, ConnectionButton, ExportManager, QuickSearch, ProgressBar, LostConnectionModal, RenameDatabaseElementModal, SecondarySidebar },
components: { CoreSidebar, CoreTabs, Sidebar, ExportManager, QuickSearch, ProgressBar, LostConnectionModal, RenameDatabaseElementModal, SecondarySidebar, GlobalStatusBar },
data() {
/* eslint-disable */
return {
split: null,
quickSearchShown: false,
initializing: true,
resizeObserver: null,
primarySidebarWidth: 0,
}
/* eslint-enable */
},
@@ -159,6 +161,17 @@
this.registerHandlers(this.rootBindings)
this.$nextTick(() => {
this.initializing = false
// This is the easiest way to track the width of the primary sidebar
// in real time because sidebar can be resized by dragging or clicking
// the toggle button. An alternative to this would be assigning the
// width on drag and click events.
this.resizeObserver = new ResizeObserver((entries) => {
const primarySidebar = entries[0]
this.primarySidebarWidth = primarySidebar.contentRect.width
})
this.$nextTick(() => {
this.resizeObserver.observe(this.splitElements[0])
})
})
},
beforeDestroy() {
@@ -170,6 +183,9 @@
if(this.split) {
this.split.destroy()
}
if (this.resizeObserver) {
this.resizeObserver.disconnect()
}
},
methods: {
...mapActions({

View File

@@ -50,7 +50,6 @@
<div class="expand layout-center">
<shortcut-hints />
</div>
<statusbar class="tabulator-footer" />
</div>
<div
v-for="(tab, idx) in tabItems"
@@ -109,6 +108,7 @@
v-if="tab.tabType === 'import-export-database'"
:schema="tab.schemaName"
:tab="tab"
:active="activeTab.id === tab.id"
@close="close"
/>
<DatabaseBackup

View File

@@ -0,0 +1,27 @@
<template>
<div class="global-status-bar">
<div
class="connection-button-wrapper"
:style="{
['--connection-button-icon-width']: connectionButtonIconWidth + 'px',
width: connectionButtonWidth + 'px',
}"
>
<connection-button />
</div>
<portal-target name="global-status-bar"></portal-target>
</div>
</template>
<script>
import { PortalTarget } from "portal-vue";
import ConnectionButton from "./sidebar/core/ConnectionButton.vue";
export default {
components: { PortalTarget, ConnectionButton },
props: {
connectionButtonWidth: Number,
connectionButtonIconWidth: Number,
},
};
</script>

View File

@@ -102,7 +102,7 @@
/>
</div>
<div class="expand" />
<status-bar>
<status-bar :active="active">
<div class="statusbar-info col flex expand">
<span
class="statusbar-item"

View File

@@ -90,7 +90,7 @@
</div>
</div>
<status-bar>
<status-bar :active="active">
<div class="statusbar-info col flex expand">
<span
class="statusbar-item"

View File

@@ -206,6 +206,7 @@
@submitCurrentQueryToFile="submitCurrentQueryToFile"
@wrap-text="wrapText = !wrapText"
:execute-time="executeTime"
:active="active"
/>
</div>

View File

@@ -90,6 +90,7 @@
@clipboardJson="clipboardJson"
@clipboardMarkdown="clipboardMarkdown"
:execute-time="executeTime"
:active="active"
/>
</div>
</div>

View File

@@ -44,7 +44,7 @@
</div>
</div>
<div class="expand" />
<status-bar class="tabulator-footer">
<status-bar class="tabulator-footer" :active="active">
<span class="expand" />
<div class="col flex-right statusbar-actions">
<x-buttons class="pending-changes">

View File

@@ -1,13 +1,15 @@
<template>
<footer :class="classes">
<slot />
</footer>
<portal to="global-status-bar">
<footer :class="classes" v-if="active">
<slot />
</footer>
</portal>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: ['mode'],
props: ['mode', 'active'],
computed: {
...mapGetters(['connectionColor']),
classes() {

View File

@@ -1,5 +1,8 @@
<template>
<statusbar :class="{ 'empty': results.length === 0, 'query-meta': true }">
<statusbar
:active="active"
:class="{ 'empty': results.length === 0, 'query-meta': true }"
>
<template v-if="results.length > 0">
<div
class="truncate statusbar-info"
@@ -61,99 +64,95 @@
</span>
</div>
</template>
<template v-else>
<span class="expand" />
<span class="empty">No Data</span>
</template>
<div class="flex flex-right statusbar-right-actions">
<x-button
class="btn btn-flat btn-icon end"
:disabled="results.length === 0"
menu
>
Download <i class="material-icons">arrow_drop_down</i>
<x-menu>
<x-menuitem @click.prevent="download('csv')">
<x-label>Download as CSV</x-label>
</x-menuitem>
<x-menuitem @click.prevent="download('xlsx')">
<x-label>Download as Excel</x-label>
</x-menuitem>
<x-menuitem @click.prevent="download('json')">
<x-label>Download as JSON</x-label>
</x-menuitem>
<x-menuitem @click.prevent="download('md')">
<x-label>Download as Markdown</x-label>
</x-menuitem>
<span
v-if="dialect !== 'mongodb'"
v-tooltip="{
content: downloadFullTooltip
}"
>
<x-menuitem
@click.prevent="$event => submitCurrentQueryToFile()"
:disabled="!(result && result.truncated)"
>
<x-label>Download Full Resultset</x-label>
<i
v-if="$store.getters.isCommunity"
class="material-icons menu-icon"
>stars</i>
</x-menuitem>
</span>
<hr>
<span v-else class="empty">No Data</span>
<span class="expand" />
<x-button
class="btn btn-flat btn-icon end"
:disabled="results.length === 0"
menu
>
Download <i class="material-icons">arrow_drop_down</i>
<x-menu>
<x-menuitem @click.prevent="download('csv')">
<x-label>Download as CSV</x-label>
</x-menuitem>
<x-menuitem @click.prevent="download('xlsx')">
<x-label>Download as Excel</x-label>
</x-menuitem>
<x-menuitem @click.prevent="download('json')">
<x-label>Download as JSON</x-label>
</x-menuitem>
<x-menuitem @click.prevent="download('md')">
<x-label>Download as Markdown</x-label>
</x-menuitem>
<span
v-if="dialect !== 'mongodb'"
v-tooltip="{
content: downloadFullTooltip
}"
>
<x-menuitem
title="Probably don't do this with large results (500+)"
@click.prevent="copyToClipboard"
@click.prevent="$event => submitCurrentQueryToFile()"
:disabled="!(result && result.truncated)"
>
<x-label>Copy to Clipboard (TSV / Excel)</x-label>
<x-label>Download Full Resultset</x-label>
<i
v-if="$store.getters.isCommunity"
class="material-icons menu-icon"
>stars</i>
</x-menuitem>
<x-menuitem
title="Probably don't do this with large results (500+)"
@click.prevent="copyToClipboardJson"
>
<x-label>Copy to Clipboard (JSON)</x-label>
</x-menuitem>
<x-menuitem
title="Probably don't do this with large results (500+)"
@click.prevent="copyToClipboardMarkdown"
>
<x-label>Copy to Clipboard (Markdown)</x-label>
</x-menuitem>
</x-menu>
</x-button>
<x-button
class="actions-btn btn btn-flat settings-btn"
menu
>
<i class="material-icons">settings</i>
<i class="material-icons">arrow_drop_down</i>
<x-menu>
<x-menuitem disabled togglable>
<x-label>Editor keymap</x-label>
</x-menuitem>
<x-menuitem
:key="t.value"
v-for="t in keymapTypes"
togglable
:toggled="t.value === userKeymap"
@click.prevent="userKeymap = t.value"
>
<x-label>{{ t.name }}</x-label>
</x-menuitem>
<x-menuitem
togglable
:toggled="wrapText"
@click.prevent="$emit('wrap-text')"
>
<x-label class="flex-between">
Wrap Text
</x-label>
</x-menuitem>
</x-menu>
</x-button>
</div>
</span>
<hr>
<x-menuitem
title="Probably don't do this with large results (500+)"
@click.prevent="copyToClipboard"
>
<x-label>Copy to Clipboard (TSV / Excel)</x-label>
</x-menuitem>
<x-menuitem
title="Probably don't do this with large results (500+)"
@click.prevent="copyToClipboardJson"
>
<x-label>Copy to Clipboard (JSON)</x-label>
</x-menuitem>
<x-menuitem
title="Probably don't do this with large results (500+)"
@click.prevent="copyToClipboardMarkdown"
>
<x-label>Copy to Clipboard (Markdown)</x-label>
</x-menuitem>
</x-menu>
</x-button>
<x-button
class="actions-btn btn btn-flat settings-btn"
menu
>
<i class="material-icons">settings</i>
<i class="material-icons">arrow_drop_down</i>
<x-menu>
<x-menuitem disabled togglable>
<x-label>Editor keymap</x-label>
</x-menuitem>
<x-menuitem
:key="t.value"
v-for="t in keymapTypes"
togglable
:toggled="t.value === userKeymap"
@click.prevent="userKeymap = t.value"
>
<x-label>{{ t.name }}</x-label>
</x-menuitem>
<x-menuitem
togglable
:toggled="wrapText"
@click.prevent="$emit('wrap-text')"
>
<x-label class="flex-between">
Wrap Text
</x-label>
</x-menuitem>
</x-menu>
</x-button>
</statusbar>
</template>
<script>
@@ -179,7 +178,7 @@ const shortEnglishHumanizer = humanizeDuration.humanizer({
});
export default {
props: ['results', 'running', 'value', 'executeTime', 'wrapText'],
props: ['results', 'running', 'value', 'executeTime', 'wrapText', 'active'],
components: { Statusbar },
data() {
return {

View File

@@ -13,7 +13,7 @@
:button-portal-target="portalName"
/>
</div>
<status-bar class="import-export__footer">
<status-bar class="import-export__footer" :active="active">
<div class="statusbar-info col flex expand">
<span
class="statusbar-item"

View File

@@ -1,6 +1,6 @@
<template>
<div
class="connection-button flex flex-middle"
class="connection-button"
v-if="config"
:title="$bks.buildConnectionString(config)"
>
@@ -40,7 +40,6 @@
</x-menuitem>
</x-menu>
</x-button>
<portal to="modals">
<modal
class="vue-dialog beekeeper-modal save-connection-modal"

View File

@@ -55,7 +55,7 @@
<div class="expand" />
<status-bar class="tabulator-footer">
<status-bar class="tabulator-footer" :active="active">
<div class="flex flex-middle flex-right statusbar-actions">
<slot name="footer" />
<x-button

View File

@@ -35,7 +35,7 @@
</div>
<div class="expand" />
<status-bar class="tablulator-footer">
<status-bar class="tablulator-footer" :active="active">
<div class="flex flex-middle statusbar-actions">
<slot name="footer" />
<x-button

View File

@@ -47,7 +47,7 @@
<div class="expand" />
<status-bar class="tabulator-footer">
<status-bar class="tabulator-footer" :active="active">
<div class="flex flex-middle flex-right statusbar-actions">
<slot name="footer" />
<x-button

View File

@@ -53,7 +53,7 @@
<div class="expand" />
<status-bar class="tabulator-footer">
<status-bar class="tabulator-footer" :active="active">
<div class="flex flex-middle statusbar-actions">
<slot name="footer" />
<x-button

View File

@@ -68,7 +68,7 @@
<div class="expand" />
<status-bar class="tabulator-footer">
<status-bar class="tabulator-footer" :active="active">
<div class="flex flex-middle statusbar-actions">
<slot name="footer" />
<x-button

View File

@@ -28,7 +28,7 @@
<div class="expand" />
<status-bar class="tabulator-footer">
<status-bar class="tabulator-footer" :active="active">
<div class="flex flex-middle flex-right statusbar-actions">
<slot name="footer" />
<slot name="actions" />

View File

@@ -43,7 +43,7 @@
/>
</template>
<statusbar :mode="statusbarMode">
<statusbar :mode="statusbarMode" :active="active">
<div class="truncate statusbar-info">
<x-button
@click.prevent="openProperties"