diff --git a/packages/web/src/elements/TabCloseButton.svelte b/packages/web/src/elements/TabCloseButton.svelte new file mode 100644 index 000000000..93355ef61 --- /dev/null +++ b/packages/web/src/elements/TabCloseButton.svelte @@ -0,0 +1,31 @@ + + + { + mousein = true; + }} + on:mouseleave={() => { + mousein = false; + }} +> + + + + diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index 57302b6e8..21f5063f1 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -46,6 +46,7 @@ 'icon file': 'mdi mdi-file', 'icon loading': 'mdi mdi-loading mdi-spin', 'icon close': 'mdi mdi-close', + 'icon unsaved': 'mdi mdi-record', 'icon stop': 'mdi mdi-close-octagon', 'icon filter': 'mdi mdi-filter', 'icon filter-off': 'mdi mdi-filter-off', diff --git a/packages/web/src/query/AceEditor.svelte b/packages/web/src/query/AceEditor.svelte index efdf02d16..2968ec365 100644 --- a/packages/web/src/query/AceEditor.svelte +++ b/packages/web/src/query/AceEditor.svelte @@ -554,6 +554,7 @@ editor.on('focus', () => dispatch('focus')); editor.setReadOnly(readOnly); + editor.on('change', () => { const content = editor.getValue(); value = content; diff --git a/packages/web/src/tabs/QueryTab.svelte b/packages/web/src/tabs/QueryTab.svelte index fefb5adf3..c987fd851 100644 --- a/packages/web/src/tabs/QueryTab.svelte +++ b/packages/web/src/tabs/QueryTab.svelte @@ -52,7 +52,7 @@ import useEditorData from '../query/useEditorData'; import { extensions } from '../stores'; import applyScriptTemplate from '../utility/applyScriptTemplate'; - import { changeTab } from '../utility/common'; + import { changeTab, markTabUnsaved } from '../utility/common'; import { getDatabaseInfo, useConnectionInfo } from '../utility/metadataLoaders'; import SocketMessageView from '../query/SocketMessageView.svelte'; import useEffect from '../utility/useEffect'; @@ -283,6 +283,8 @@ } const quickExportHandlerRef = createQuickExportHandlerRef(); + + let isInitialized = false; @@ -298,11 +300,17 @@ menu={createMenu()} on:input={e => { setEditorData(e.detail); + if (isInitialized) { + markTabUnsaved(tabid); + } errorMessages = []; }} on:focus={() => { activator.activate(); invalidateCommands(); + setTimeout(() => { + isInitialized = true; + }, 100); }} bind:this={domEditor} onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)} diff --git a/packages/web/src/utility/common.ts b/packages/web/src/utility/common.ts index 5fb98c0ff..e5c083cb2 100644 --- a/packages/web/src/utility/common.ts +++ b/packages/web/src/utility/common.ts @@ -1,4 +1,4 @@ -import { openedTabs } from '../stores'; +import { getOpenedTabs, openedTabs } from '../stores'; import _ from 'lodash'; import getElectron from './getElectron'; @@ -18,6 +18,16 @@ export function changeTab(tabid, changeFunc) { openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? changeFunc(tab) : tab))); } +export function markTabUnsaved(tabid) { + const tab = getOpenedTabs().find(x => x.tabid == tabid); + if (tab.unsaved) return; + openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: true } : tab))); +} + +export function markTabSaved(tabid) { + openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: false } : tab))); +} + export function setSelectedTabFunc(files, tabid) { return [ ...(files || []).filter(x => x.tabid != tabid).map(x => ({ ...x, selected: false })), diff --git a/packages/web/src/utility/saveTabFile.ts b/packages/web/src/utility/saveTabFile.ts index 778807fcf..60351ecdb 100644 --- a/packages/web/src/utility/saveTabFile.ts +++ b/packages/web/src/utility/saveTabFile.ts @@ -1,7 +1,7 @@ import { derived, get } from 'svelte/store'; import { showModal } from '../modals/modalTools'; import { openedTabs } from '../stores'; -import { changeTab } from './common'; +import { changeTab, markTabSaved } from './common'; import SaveFileModal from '../modals/SaveFileModal.svelte'; import registerCommand from '../commands/registerCommand'; import { apiCall } from './api'; @@ -24,12 +24,14 @@ export default async function saveTabFile(editor, saveMode, folder, format, file if (savedFilePath) { await apiCall('files/save-as', { filePath: savedFilePath, data, format }); } + markTabSaved(tabid); }; const onSave = (title, newProps) => { changeTab(tabid, tab => ({ ...tab, title, + unsaved: false, props: { ...tab.props, savedFormat: format, diff --git a/packages/web/src/widgets/TabsPanel.svelte b/packages/web/src/widgets/TabsPanel.svelte index 7244a70c9..9eb5edcaf 100644 --- a/packages/web/src/widgets/TabsPanel.svelte +++ b/packages/web/src/widgets/TabsPanel.svelte @@ -212,6 +212,7 @@ import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders'; import { duplicateTab, getTabDbKey, sortTabs, groupTabs } from '../utility/openNewTab'; import { useConnectionColorFactory } from '../utility/useConnectionColor'; + import TabCloseButton from '../elements/TabCloseButton.svelte'; $: connectionList = useConnectionList(); @@ -434,7 +435,7 @@ {/if} - + 22
closeMultipleTabs(tab => tabGroup.tabs.find(x => x.tabid == tab.tabid))} @@ -479,9 +480,7 @@ {tab.title} - closeTab(tab.tabid)}> - - + closeTab(tab.tabid)} />
{/each} @@ -582,19 +581,12 @@ white-space: nowrap; flex-grow: 1; } - .close-button { - margin-left: 5px; - color: var(--theme-font-3); - } .close-button-right { margin-left: 5px; margin-right: 5px; color: var(--theme-font-3); } - .close-button:hover { - color: var(--theme-font-1); - } .close-button-right:hover { color: var(--theme-font-1); }