feat: add tab memory with Remember Tabs setting
This commit is contained in:
@@ -33,6 +33,7 @@ export interface AppConfig {
|
|||||||
game: {
|
game: {
|
||||||
lastServer: string;
|
lastServer: string;
|
||||||
socialTabBehaviour: 'New Window' | 'Same Window';
|
socialTabBehaviour: 'New Window' | 'Same Window';
|
||||||
|
rememberTabs: boolean;
|
||||||
joinAsSpectator: boolean;
|
joinAsSpectator: boolean;
|
||||||
rawInput: boolean;
|
rawInput: boolean;
|
||||||
betterChat: boolean;
|
betterChat: boolean;
|
||||||
@@ -108,6 +109,7 @@ export interface AppConfig {
|
|||||||
y: number | undefined;
|
y: number | undefined;
|
||||||
maximized: boolean;
|
maximized: boolean;
|
||||||
};
|
};
|
||||||
|
savedTabs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_KEYBINDS: AppConfig['keybinds'] = {
|
export const DEFAULT_KEYBINDS: AppConfig['keybinds'] = {
|
||||||
@@ -145,6 +147,7 @@ export const config = new Store<AppConfig>({
|
|||||||
game: {
|
game: {
|
||||||
lastServer: '',
|
lastServer: '',
|
||||||
socialTabBehaviour: 'New Window',
|
socialTabBehaviour: 'New Window',
|
||||||
|
rememberTabs: false,
|
||||||
joinAsSpectator: false,
|
joinAsSpectator: false,
|
||||||
rawInput: true,
|
rawInput: true,
|
||||||
betterChat: true,
|
betterChat: true,
|
||||||
@@ -210,5 +213,6 @@ export const config = new Store<AppConfig>({
|
|||||||
y: undefined,
|
y: undefined,
|
||||||
maximized: true,
|
maximized: true,
|
||||||
},
|
},
|
||||||
|
savedTabs: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -463,6 +463,9 @@ async function launchApp(): Promise<void> {
|
|||||||
win, ses, preloadPath, tabMode, isGameURL,
|
win, ses, preloadPath, tabMode, isGameURL,
|
||||||
() => config.get('tabWindow'),
|
() => config.get('tabWindow'),
|
||||||
(state) => config.set('tabWindow', state),
|
(state) => config.set('tabWindow', state),
|
||||||
|
() => config.get('savedTabs'),
|
||||||
|
(urls) => config.set('savedTabs', urls),
|
||||||
|
() => config.get('game.rememberTabs') ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Intercept in-page navigation (e.g. window.location = '/social.html')
|
// Intercept in-page navigation (e.g. window.location = '/social.html')
|
||||||
|
|||||||
+39
-1
@@ -42,7 +42,11 @@ export class TabManager {
|
|||||||
private recentlyClosed: { url: string; title: string }[] = [];
|
private recentlyClosed: { url: string; title: string }[] = [];
|
||||||
private getTabWindowState: () => TabWindowState;
|
private getTabWindowState: () => TabWindowState;
|
||||||
private saveTabWindowState: (state: TabWindowState) => void;
|
private saveTabWindowState: (state: TabWindowState) => void;
|
||||||
|
private getSavedTabs: () => string[];
|
||||||
|
private saveTabs: (urls: string[]) => void;
|
||||||
|
private isRememberEnabled: () => boolean;
|
||||||
private tabSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
private tabSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private restoredTabs = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
win: BrowserWindow,
|
win: BrowserWindow,
|
||||||
@@ -52,6 +56,9 @@ export class TabManager {
|
|||||||
isGameURL: (url: string) => boolean,
|
isGameURL: (url: string) => boolean,
|
||||||
getTabWindowState: () => TabWindowState,
|
getTabWindowState: () => TabWindowState,
|
||||||
saveTabWindowState: (state: TabWindowState) => void,
|
saveTabWindowState: (state: TabWindowState) => void,
|
||||||
|
getSavedTabs: () => string[],
|
||||||
|
saveTabs: (urls: string[]) => void,
|
||||||
|
isRememberEnabled: () => boolean,
|
||||||
) {
|
) {
|
||||||
this.mainWin = win;
|
this.mainWin = win;
|
||||||
this.ses = ses;
|
this.ses = ses;
|
||||||
@@ -60,6 +67,9 @@ export class TabManager {
|
|||||||
this.isGameURL = isGameURL;
|
this.isGameURL = isGameURL;
|
||||||
this.getTabWindowState = getTabWindowState;
|
this.getTabWindowState = getTabWindowState;
|
||||||
this.saveTabWindowState = saveTabWindowState;
|
this.saveTabWindowState = saveTabWindowState;
|
||||||
|
this.getSavedTabs = getSavedTabs;
|
||||||
|
this.saveTabs = saveTabs;
|
||||||
|
this.isRememberEnabled = isRememberEnabled;
|
||||||
|
|
||||||
// ── Tab bar view (shared between both modes) ──
|
// ── Tab bar view (shared between both modes) ──
|
||||||
this.tabBarView = new WebContentsView({
|
this.tabBarView = new WebContentsView({
|
||||||
@@ -185,8 +195,30 @@ export class TabManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Open a new tab ──
|
// ── Restore saved tabs on first open, then open the requested tab ──
|
||||||
openTab(url: string): number {
|
openTab(url: string): number {
|
||||||
|
if (!this.restoredTabs) {
|
||||||
|
this.restoredTabs = true;
|
||||||
|
const saved = this.isRememberEnabled() ? this.getSavedTabs() : [];
|
||||||
|
this.saveTabs([]);
|
||||||
|
if (saved.length > 0) {
|
||||||
|
for (const savedUrl of saved) {
|
||||||
|
this.openSingleTab(savedUrl);
|
||||||
|
}
|
||||||
|
// If the requested URL is already among the restored tabs, just activate it
|
||||||
|
const existing = this.tabs.find(t => t.url === url);
|
||||||
|
if (existing) {
|
||||||
|
this.switchToTab(existing.id);
|
||||||
|
this.showTabs();
|
||||||
|
return existing.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.openSingleTab(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Open a single new tab ──
|
||||||
|
private openSingleTab(url: string): number {
|
||||||
if (this.tabs.length >= MAX_TABS) {
|
if (this.tabs.length >= MAX_TABS) {
|
||||||
const existing = this.tabs.find(t => t.url === url);
|
const existing = this.tabs.find(t => t.url === url);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
@@ -489,6 +521,12 @@ export class TabManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private destroyAllTabs(): void {
|
private destroyAllTabs(): void {
|
||||||
|
// Persist tab URLs so they can be restored later
|
||||||
|
if (this.tabs.length > 0 && this.isRememberEnabled()) {
|
||||||
|
this.saveTabs(this.tabs.map(t => t.url));
|
||||||
|
this.restoredTabs = false;
|
||||||
|
}
|
||||||
|
|
||||||
for (const tab of this.tabs) {
|
for (const tab of this.tabs) {
|
||||||
this.stopTitleWatcher(tab.id);
|
this.stopTitleWatcher(tab.id);
|
||||||
if (this.activeTabId === tab.id) {
|
if (this.activeTabId === tab.id) {
|
||||||
|
|||||||
@@ -555,7 +555,7 @@ function buildGeneralSection(
|
|||||||
onChange: (v) => { perf.fpsUnlocked = v; ipcRenderer.invoke('set-config', 'performance', perf); },
|
onChange: (v) => { perf.fpsUnlocked = v; ipcRenderer.invoke('set-config', 'performance', perf); },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const gameDefaults = { lastServer: '', socialTabBehaviour: 'New Window' };
|
const gameDefaults = { lastServer: '', socialTabBehaviour: 'New Window', rememberTabs: false };
|
||||||
const game = { ...gameDefaults, ...gameConf };
|
const game = { ...gameDefaults, ...gameConf };
|
||||||
|
|
||||||
body.appendChild(createSelectRow({
|
body.appendChild(createSelectRow({
|
||||||
@@ -566,6 +566,13 @@ function buildGeneralSection(
|
|||||||
onChange: (v) => { game.socialTabBehaviour = v; ipcRenderer.invoke('set-config', 'game', game); },
|
onChange: (v) => { game.socialTabBehaviour = v; ipcRenderer.invoke('set-config', 'game', game); },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
body.appendChild(createToggleRow({
|
||||||
|
label: 'Remember Tabs',
|
||||||
|
desc: 'Restore your open tabs when you reopen the social/hub window',
|
||||||
|
checked: game.rememberTabs, instant: true,
|
||||||
|
onChange: (v) => { game.rememberTabs = v; ipcRenderer.invoke('set-config', 'game', game); },
|
||||||
|
}));
|
||||||
|
|
||||||
const uiDefaults = { showExitButton: true, deathscreenAnimation: false, hideMenuPopups: false };
|
const uiDefaults = { showExitButton: true, deathscreenAnimation: false, hideMenuPopups: false };
|
||||||
const ui = { ...uiDefaults, ...uiConfRaw };
|
const ui = { ...uiDefaults, ...uiConfRaw };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user