diff --git a/packages/web/src/query/useEditorData.ts b/packages/web/src/query/useEditorData.ts index b786abd65..7bf041df0 100644 --- a/packages/web/src/query/useEditorData.ts +++ b/packages/web/src/query/useEditorData.ts @@ -19,6 +19,8 @@ function getParsedLocalStorage(key) { return null; } +const saveHandlersList = []; + export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = null, onInitialData = null }) { const localStorageKey = `tabdata_editor_${tabid}`; let changeCounter = 0; @@ -90,6 +92,11 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n })); }; + const saveToStorageIfNeeded = async () => { + if (savedCounter == changeCounter) return; // all saved + await saveToStorage(); + }; + const saveToStorage = async () => { if (value == null) return; try { @@ -128,11 +135,13 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n onMount(() => { window.addEventListener('beforeunload', saveToStorageSync); initialLoad(); + saveHandlersList.push(saveToStorageIfNeeded); }); onDestroy(() => { saveToStorage(); window.removeEventListener('beforeunload', saveToStorageSync); + _.remove(saveHandlersList, x => x == saveToStorageIfNeeded); }); return { @@ -144,3 +153,9 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n initialLoad, }; } + +export async function saveAllPendingEditorData() { + for (const item of saveHandlersList) { + await item(); + } +} diff --git a/packages/web/src/utility/openNewTab.ts b/packages/web/src/utility/openNewTab.ts index 928afbb9d..ccc47190c 100644 --- a/packages/web/src/utility/openNewTab.ts +++ b/packages/web/src/utility/openNewTab.ts @@ -6,6 +6,7 @@ import tabs from '../tabs'; import { setSelectedTabFunc } from './common'; import localforage from 'localforage'; import stableStringify from 'json-stable-stringify'; +import { saveAllPendingEditorData } from '../query/useEditorData'; function findFreeNumber(numbers: number[]) { if (numbers.length == 0) return 1; @@ -74,9 +75,9 @@ export default async function openNewTab(newTab, initialData = undefined, option openedTabs.update(files => [ ...(files || []).map(x => ({ ...x, selected: false })), { + ...newTab, tabid, selected: true, - ...newTab, }, ]); @@ -91,3 +92,35 @@ export default async function openNewTab(newTab, initialData = undefined, option // }, // ]); } + +export async function duplicateTab(tab) { + await saveAllPendingEditorData(); + + let title = tab.title; + const mtitle = title.match(/^(.*#)[\d]+$/); + if (mtitle) title = mtitle[1]; + + const keyRegex = /^tabdata_([^_]+)_([^_]+)$/; + const initialData = {}; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + const m = key.match(keyRegex); + if (m && m[2] == tab.tabid) { + initialData[m[1]] = JSON.parse(localStorage.getItem(key)); + } + } + for (const key of await localforage.keys()) { + const m = key.match(keyRegex); + if (m && m[2] == tab.tabid) { + initialData[m[1]] = await localforage.getItem(key); + } + } + openNewTab( + { + ..._.omit(tab, ['tabid']), + title, + }, + initialData, + { forceNewTab: true } + ); +} diff --git a/packages/web/src/widgets/TabsPanel.svelte b/packages/web/src/widgets/TabsPanel.svelte index 8a893aeb6..06e4d4b26 100644 --- a/packages/web/src/widgets/TabsPanel.svelte +++ b/packages/web/src/widgets/TabsPanel.svelte @@ -113,6 +113,7 @@ import { setSelectedTab } from '../utility/common'; import contextMenu from '../utility/contextMenu'; import { getConnectionInfo } from '../utility/metadataLoaders'; + import { duplicateTab } from '../utility/openNewTab'; $: currentDbKey = $currentDatabase && $currentDatabase.name && $currentDatabase.connection @@ -162,6 +163,10 @@ text: 'Close others', onClick: () => closeOthers(tabid), }, + { + text: 'Duplicate', + onClick: () => duplicateTab(tab), + }, tabComponent && tabs[tabComponent] && tabs[tabComponent].allowAddToFavorites &&