SYNC: widget configuration saved to storage

This commit is contained in:
SPRINX0\prochazka
2025-12-10 15:57:39 +01:00
committed by Diflow
parent b12c79462e
commit 149611041e
12 changed files with 81 additions and 53 deletions

View File

@@ -141,11 +141,10 @@
/> />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="2"> <svelte:fragment slot="2">
<WidgetColumnBar> <WidgetColumnBar storageName="diagramSettingsWidget">
<WidgetColumnBarItem <WidgetColumnBarItem
title="Settings" title="Settings"
name="diagramSettings" name="diagramSettings"
storageName="diagramSettingsWidget"
onClose={() => { onClose={() => {
styleStore.update(x => ({ ...x, settingsVisible: false })); styleStore.update(x => ({ ...x, settingsVisible: false }));
}} }}

View File

@@ -1,6 +1,7 @@
const cache = {}; const cache = {};
export function getLocalStorage(key, defaultValue = undefined) { export function getLocalStorage(key, defaultValue = undefined) {
if (!key) return defaultValue;
if (key in cache) return cache[key]; if (key in cache) return cache[key];
const item = localStorage.getItem(key); const item = localStorage.getItem(key);
if (item) { if (item) {
@@ -16,11 +17,13 @@ export function getLocalStorage(key, defaultValue = undefined) {
} }
export function setLocalStorage(key, value) { export function setLocalStorage(key, value) {
if (!key) return;
localStorage.setItem(key, JSON.stringify(value)); localStorage.setItem(key, JSON.stringify(value));
delete cache[key]; delete cache[key];
} }
export function removeLocalStorage(key) { export function removeLocalStorage(key) {
if (!key) return;
localStorage.removeItem(key); localStorage.removeItem(key);
delete cache[key]; delete cache[key];
} }

View File

@@ -7,11 +7,11 @@
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte'; import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
</script> </script>
<WidgetColumnBar> <WidgetColumnBar storageName="archiveWidget">
<WidgetColumnBarItem title="Archive folders, DB models" name="folders" height="50%" storageName='archiveFoldersWidget'> <WidgetColumnBarItem title="Archive folders, DB models" name="folders" height="50%">
<ArchiveFolderList /> <ArchiveFolderList />
</WidgetColumnBarItem> </WidgetColumnBarItem>
<WidgetColumnBarItem title="Files, Tables, Views, Functions" name="files" storageName='archiveFilesWidget'> <WidgetColumnBarItem title="Files, Tables, Views, Functions" name="files">
<ArchiveFilesList /> <ArchiveFilesList />
</WidgetColumnBarItem> </WidgetColumnBarItem>
</WidgetColumnBar> </WidgetColumnBar>

View File

@@ -17,14 +17,9 @@
$: cloudContentList = useCloudContentList(); $: cloudContentList = useCloudContentList();
</script> </script>
<WidgetColumnBar {hidden}> <WidgetColumnBar {hidden} storageName="databaseWidget">
{#if $config?.singleConnection} {#if $config?.singleConnection}
<WidgetColumnBarItem <WidgetColumnBarItem title={_t('widget.databases', { defaultMessage: 'Databases' })} name="databases" height="35%">
title={_t('widget.databases', { defaultMessage: 'Databases' })}
name="databases"
height="35%"
storageName="databasesWidget"
>
<SingleConnectionDatabaseList connection={$config?.singleConnection} /> <SingleConnectionDatabaseList connection={$config?.singleConnection} />
</WidgetColumnBarItem> </WidgetColumnBarItem>
{:else if !$config?.singleDbConnection} {:else if !$config?.singleDbConnection}
@@ -32,7 +27,6 @@
title={_t('common.connections', { defaultMessage: 'Connections' })} title={_t('common.connections', { defaultMessage: 'Connections' })}
name="connections" name="connections"
height="35%" height="35%"
storageName="connectionsWidget"
> >
<ConnectionList <ConnectionList
passProps={{ passProps={{

View File

@@ -34,7 +34,6 @@
title={_t('widget.pinned', { defaultMessage: 'Pinned' })} title={_t('widget.pinned', { defaultMessage: 'Pinned' })}
name="pinned" name="pinned"
height="15%" height="15%"
storageName="pinnedItemsWidget"
skip={!_.compact($pinnedDatabases).length && skip={!_.compact($pinnedDatabases).length &&
!$pinnedTables.some(x => x && x.conid == conid && x.database == $currentDatabase?.name)} !$pinnedTables.some(x => x && x.conid == conid && x.database == $currentDatabase?.name)}
positiveCondition={correctCloudStatus} positiveCondition={correctCloudStatus}
@@ -47,7 +46,6 @@
? _t('widget.collectionsContainers', { defaultMessage: 'Collections/containers' }) ? _t('widget.collectionsContainers', { defaultMessage: 'Collections/containers' })
: _t('widget.tablesViewsFunctions', { defaultMessage: 'Tables, views, functions' })} : _t('widget.tablesViewsFunctions', { defaultMessage: 'Tables, views, functions' })}
name="dbObjectsSql" name="dbObjectsSql"
storageName="dbObjectsWidget"
skip={!( skip={!(
conid && conid &&
(database || singleDatabase) && (database || singleDatabase) &&
@@ -61,7 +59,6 @@
<WidgetColumnBarItem <WidgetColumnBarItem
title={_t('widget.keys', { defaultMessage: 'Keys' })} title={_t('widget.keys', { defaultMessage: 'Keys' })}
name="dbObjectsKeyValue" name="dbObjectsKeyValue"
storageName="dbObjectsWidget"
skip={!(conid && (database || singleDatabase) && driver?.databaseEngineTypes?.includes('keyvalue'))} skip={!(conid && (database || singleDatabase) && driver?.databaseEngineTypes?.includes('keyvalue'))}
positiveCondition={correctCloudStatus} positiveCondition={correctCloudStatus}
> >
@@ -71,7 +68,6 @@
<WidgetColumnBarItem <WidgetColumnBarItem
title={_t('widget.databaseContent', { defaultMessage: 'Database content' })} title={_t('widget.databaseContent', { defaultMessage: 'Database content' })}
name="dbObjectsFocused" name="dbObjectsFocused"
storageName="dbObjectsWidget"
skip={conid && (database || singleDatabase)} skip={conid && (database || singleDatabase)}
positiveCondition={correctCloudStatus} positiveCondition={correctCloudStatus}
> >
@@ -85,7 +81,6 @@
<WidgetColumnBarItem <WidgetColumnBarItem
title={_t('widget.databaseContent', { defaultMessage: 'Database content' })} title={_t('widget.databaseContent', { defaultMessage: 'Database content' })}
name="dbObjectsError" name="dbObjectsError"
storageName="dbObjectsWidget"
skip={!(conid && (database || singleDatabase) && !driver)} skip={!(conid && (database || singleDatabase) && !driver)}
positiveCondition={correctCloudStatus} positiveCondition={correctCloudStatus}
> >
@@ -102,7 +97,6 @@
title={_t('widget.databaseContent', { defaultMessage: 'Database content' })} title={_t('widget.databaseContent', { defaultMessage: 'Database content' })}
name="incorrectClaudStatus" name="incorrectClaudStatus"
height="15%" height="15%"
storageName="incorrectClaudStatusWidget"
skip={correctCloudStatus} skip={correctCloudStatus}
> >
<WidgetsInnerContainer> <WidgetsInnerContainer>

View File

@@ -18,13 +18,13 @@
$: favorites = useFavorites(); $: favorites = useFavorites();
</script> </script>
<WidgetColumnBar> <WidgetColumnBar storageName="filesWidget">
<WidgetColumnBarItem title={_t('files.savedFiles', { defaultMessage: "Saved files" })} name="files" height="70%" storageName="savedFilesWidget"> <WidgetColumnBarItem title={_t('files.savedFiles', { defaultMessage: 'Saved files' })} name="files" height="70%">
<SavedFilesList /> <SavedFilesList />
</WidgetColumnBarItem> </WidgetColumnBarItem>
{#if hasPermission('files/favorites/read')} {#if hasPermission('files/favorites/read')}
<WidgetColumnBarItem title={_t('files.favorites', { defaultMessage: "Favorites" })} name="favorites" storageName="favoritesWidget"> <WidgetColumnBarItem title={_t('files.favorites', { defaultMessage: 'Favorites' })} name="favorites">
<WidgetsInnerContainer> <WidgetsInnerContainer>
<AppObjectList list={$favorites || []} module={favoriteFileAppObject} /> <AppObjectList list={$favorites || []} module={favoriteFileAppObject} />
</WidgetsInnerContainer> </WidgetsInnerContainer>

View File

@@ -16,11 +16,13 @@
import { _t } from '../translations'; import { _t } from '../translations';
$: favorites = useFavorites(); $: favorites = useFavorites();
</script> </script>
<WidgetColumnBar> <WidgetColumnBar storageName="historyWidget">
<WidgetColumnBarItem title={_t('history.recentlyClosedTabs', { defaultMessage: "Recently closed tabs" })} name="closedTabs" storageName='closedTabsWidget'> <WidgetColumnBarItem
title={_t('history.recentlyClosedTabs', { defaultMessage: 'Recently closed tabs' })}
name="closedTabs"
>
<WidgetsInnerContainer> <WidgetsInnerContainer>
<AppObjectList <AppObjectList
list={_.sortBy( list={_.sortBy(
@@ -31,7 +33,7 @@
/> />
</WidgetsInnerContainer> </WidgetsInnerContainer>
</WidgetColumnBarItem> </WidgetColumnBarItem>
<WidgetColumnBarItem title={_t('history.queryHistory', { defaultMessage: "Query history" })} name="queryHistory" storageName='queryHistoryWidget'> <WidgetColumnBarItem title={_t('history.queryHistory', { defaultMessage: 'Query history' })} name="queryHistory">
<QueryHistoryList /> <QueryHistoryList />
</WidgetColumnBarItem> </WidgetColumnBarItem>
</WidgetColumnBar> </WidgetColumnBar>

View File

@@ -8,11 +8,15 @@
import { _t } from '../translations'; import { _t } from '../translations';
</script> </script>
<WidgetColumnBar> <WidgetColumnBar storageName="pluginsWidget">
<WidgetColumnBarItem title={_t('widgets.installedExtensions', { defaultMessage: 'Installed extensions' })} name="installed" height="50%" storageName='installedPluginsWidget'> <WidgetColumnBarItem
title={_t('widgets.installedExtensions', { defaultMessage: 'Installed extensions' })}
name="installed"
height="50%"
>
<InstalledPluginsList /> <InstalledPluginsList />
</WidgetColumnBarItem> </WidgetColumnBarItem>
<WidgetColumnBarItem title={_t('widgets.availableExtensions', { defaultMessage: 'Available extensions' })} name="all" storageName='allPluginsWidget'> <WidgetColumnBarItem title={_t('widgets.availableExtensions', { defaultMessage: 'Available extensions' })} name="all">
<AvailablePluginsList /> <AvailablePluginsList />
</WidgetColumnBarItem> </WidgetColumnBarItem>
</WidgetColumnBar> </WidgetColumnBar>

View File

@@ -162,7 +162,9 @@
text: _t('privateCloudWidget.addExistingFolder', { defaultMessage: 'Add existing folder (from link)' }), text: _t('privateCloudWidget.addExistingFolder', { defaultMessage: 'Add existing folder (from link)' }),
onClick: () => { onClick: () => {
showModal(InputTextModal, { showModal(InputTextModal, {
label: _t('privateCloudWidget.yourInviteLink', { defaultMessage: 'Your invite link (in form dbgate://folder/xxx)' }), label: _t('privateCloudWidget.yourInviteLink', {
defaultMessage: 'Your invite link (in form dbgate://folder/xxx)',
}),
header: _t('privateCloudWidget.addExistingSharedFolder', { defaultMessage: 'Add existing shared folder' }), header: _t('privateCloudWidget.addExistingSharedFolder', { defaultMessage: 'Add existing shared folder' }),
onConfirm: async newFolder => { onConfirm: async newFolder => {
apiCall('cloud/grant-folder', { apiCall('cloud/grant-folder', {
@@ -192,7 +194,10 @@
const handleDelete = () => { const handleDelete = () => {
showModal(ConfirmModal, { showModal(ConfirmModal, {
message: _t('privateCloudWidget.deleteFolderConfirm', { defaultMessage: 'Really delete folder {folder}? All folder content will be deleted!', values: { folder: contentGroupMap[folder]?.name } }), message: _t('privateCloudWidget.deleteFolderConfirm', {
defaultMessage: 'Really delete folder {folder}? All folder content will be deleted!',
values: { folder: contentGroupMap[folder]?.name },
}),
header: _t('privateCloudWidget.deleteFolder', { defaultMessage: 'Delete folder' }), header: _t('privateCloudWidget.deleteFolder', { defaultMessage: 'Delete folder' }),
onConfirm: () => { onConfirm: () => {
apiCall('cloud/delete-folder', { apiCall('cloud/delete-folder', {
@@ -240,19 +245,26 @@
} }
</script> </script>
<WidgetColumnBar> <WidgetColumnBar storageName="privateCloudItems">
<WidgetColumnBarItem <WidgetColumnBarItem title="DbGate Cloud" name="privateCloud" height="50%" skip={!$cloudSigninTokenHolder}>
title="DbGate Cloud"
name="privateCloud"
height="50%"
storageName="privateCloudItems"
skip={!$cloudSigninTokenHolder}
>
<SearchBoxWrapper> <SearchBoxWrapper>
<SearchInput placeholder={_t('privateCloudWidget.searchPlaceholder', { defaultMessage: 'Search cloud connections and files' })} bind:value={filter} /> <SearchInput
placeholder={_t('privateCloudWidget.searchPlaceholder', {
defaultMessage: 'Search cloud connections and files',
})}
bind:value={filter}
/>
<CloseSearchButton bind:filter /> <CloseSearchButton bind:filter />
<DropDownButton icon="icon plus-thick" menu={createAddItemMenu} title={_t('privateCloudWidget.addNewConnectionOrFile', { defaultMessage: 'Add new connection or file' })} /> <DropDownButton
<DropDownButton icon="icon add-folder" menu={createAddFolderMenu} title={_t('privateCloudWidget.addNewFolder', { defaultMessage: 'Add new folder' })} /> icon="icon plus-thick"
menu={createAddItemMenu}
title={_t('privateCloudWidget.addNewConnectionOrFile', { defaultMessage: 'Add new connection or file' })}
/>
<DropDownButton
icon="icon add-folder"
menu={createAddFolderMenu}
title={_t('privateCloudWidget.addNewFolder', { defaultMessage: 'Add new folder' })}
/>
<InlineButton <InlineButton
on:click={handleRefreshContent} on:click={handleRefreshContent}
title={_t('privateCloudWidget.refreshFiles', { defaultMessage: 'Refresh files' })} title={_t('privateCloudWidget.refreshFiles', { defaultMessage: 'Refresh files' })}
@@ -289,7 +301,10 @@
/> />
{#if !cloudContentFlat?.length} {#if !cloudContentFlat?.length}
<ErrorInfo message={_t('privateCloudWidget.noContent', { defaultMessage: 'You have no content on DbGate cloud' })} icon="img info" /> <ErrorInfo
message={_t('privateCloudWidget.noContent', { defaultMessage: 'You have no content on DbGate cloud' })}
icon="img info"
/>
<div class="error-info"> <div class="error-info">
<div class="m-1"></div> <div class="m-1"></div>
<FormStyledButton <FormStyledButton

View File

@@ -26,15 +26,21 @@
} }
</script> </script>
<WidgetColumnBar> <WidgetColumnBar storageName="publicCloudItems">
<WidgetColumnBarItem title={_t('publicCloudWidget.publicKnowledgeBase', { defaultMessage: "Public Knowledge Base" })} name="publicCloud" storageName="publicCloudItems"> <WidgetColumnBarItem
title={_t('publicCloudWidget.publicKnowledgeBase', { defaultMessage: 'Public Knowledge Base' })}
name="publicCloud"
>
<WidgetsInnerContainer> <WidgetsInnerContainer>
<SearchBoxWrapper> <SearchBoxWrapper>
<SearchInput placeholder={_t('publicCloudWidget.searchPublicFiles', { defaultMessage: "Search public files" })} bind:value={filter} /> <SearchInput
placeholder={_t('publicCloudWidget.searchPublicFiles', { defaultMessage: 'Search public files' })}
bind:value={filter}
/>
<CloseSearchButton bind:filter /> <CloseSearchButton bind:filter />
<InlineButton <InlineButton
on:click={handleRefreshPublic} on:click={handleRefreshPublic}
title={_t('publicCloudWidget.refreshFiles', { defaultMessage: "Refresh files" })} title={_t('publicCloudWidget.refreshFiles', { defaultMessage: 'Refresh files' })}
data-testid="CloudItemsWidget_buttonRefreshPublic" data-testid="CloudItemsWidget_buttonRefreshPublic"
> >
<FontIcon icon="icon refresh" /> <FontIcon icon="icon refresh" />
@@ -49,12 +55,21 @@
/> />
{#if !$publicFiles?.length} {#if !$publicFiles?.length}
<ErrorInfo message={_t('publicCloudWidget.noFilesFound', { defaultMessage: "No files found for your configuration" })} /> <ErrorInfo
message={_t('publicCloudWidget.noFilesFound', { defaultMessage: 'No files found for your configuration' })}
/>
<div class="error-info"> <div class="error-info">
<div class="m-1"> <div class="m-1">
{_t('publicCloudWidget.onlyRelevantFilesListed', { defaultMessage: "Only files relevant for your connections, platform and DbGate edition are listed. Please define connections at first." })} {_t('publicCloudWidget.onlyRelevantFilesListed', {
defaultMessage:
'Only files relevant for your connections, platform and DbGate edition are listed. Please define connections at first.',
})}
</div> </div>
<FormStyledButton value={_t('publicCloudWidget.refreshList', { defaultMessage: "Refresh list" })} skipWidth on:click={handleRefreshPublic} /> <FormStyledButton
value={_t('publicCloudWidget.refreshList', { defaultMessage: 'Refresh list' })}
skipWidth
on:click={handleRefreshPublic}
/>
</div> </div>
{/if} {/if}
</WidgetsInnerContainer> </WidgetsInnerContainer>

View File

@@ -2,6 +2,7 @@
import { onMount, setContext } from 'svelte'; import { onMount, setContext } from 'svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import _ from 'lodash'; import _ from 'lodash';
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
export let hidden = false; export let hidden = false;
export let storageName = null; export let storageName = null;
@@ -12,7 +13,10 @@
// const widgetColumnBarHeight = writable(0); // const widgetColumnBarHeight = writable(0);
const widgetColumnBarComputed = writable({}); const widgetColumnBarComputed = writable({});
let deltaHeights = {}; const fromStorage = getLocalStorage(storageName);
let deltaHeights = fromStorage?.deltaHeights || {};
$: setLocalStorage(storageName, { deltaHeights });
// setContext('widgetColumnBarHeight', widgetColumnBarHeight); // setContext('widgetColumnBarHeight', widgetColumnBarHeight);
setContext('pushWidgetItemDefinition', (name, item) => { setContext('pushWidgetItemDefinition', (name, item) => {

View File

@@ -3,8 +3,6 @@
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { writable } from 'svelte/store';
import WidgetTitle from './WidgetTitle.svelte'; import WidgetTitle from './WidgetTitle.svelte';
import splitterDrag from '../utility/splitterDrag'; import splitterDrag from '../utility/splitterDrag';
import { getLocalStorage, setLocalStorage } from '../utility/storageCache'; import { getLocalStorage, setLocalStorage } from '../utility/storageCache';