mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-01-27 23:10:50 -08:00
* feat: new config for tidgi mini window * chore: upgrade electron-forge * fix: use 汉语 和 漢語 * feat: shortcut to open mini window * test: TidGiMenubarWindow * feat: allow updateWindowProperties on the fly * fix: wrong icon path * fix: log not showing error message and stack * refactor: directly log error when using logger.error * feat: shortcut to open window * fix: menubar not closed * test: e2e for menubar * test: keyboard shortcut * test: wiki web content, and refactor to files * test: update command * Update Testing.md * test: menubar settings about menubarSyncWorkspaceWithMainWindow, menubarFixedWorkspaceId * test: simplify menubar test and cleanup test config * fix: view missing when execute several test all together * refactor: use hook to cleanup menubar setting * refactor: I clear test ai settings to before hook * Add option to show title bar on menubar window Introduces a new preference 'showMenubarWindowTitleBar' allowing users to toggle the title bar visibility on the menubar window. Updates related services, interfaces, and UI components to support this feature, and adds corresponding localization strings for English and Chinese. * refactor: tidgiminiwindow * refactor: preference keys to right order * Refactor window dimension checks to use constants Replaces hardcoded window dimensions with values from windowDimension and WindowNames constants for improved maintainability and consistency in window identification and checks. * I cleanup test wiki * Update defaultPreferences.ts * test: mini window workspace switch * fix: image broken by ai, and lint * fix: can't switch to mini window * refactor: useless todo * Update index.ts * refactor: reuse serialize-error * Update index.ts * Update testKeyboardShortcuts.ts * refactor: dup logic * Update ui.ts * fix: electron-ipc-cat
627 lines
31 KiB
TypeScript
627 lines
31 KiB
TypeScript
import { mapSeries } from 'bluebird';
|
|
import { app, dialog, session } from 'electron';
|
|
import { inject, injectable } from 'inversify';
|
|
|
|
import { WikiChannel } from '@/constants/channels';
|
|
import { tiddlywikiLanguagesMap } from '@/constants/languages';
|
|
import { WikiCreationMethod } from '@/constants/wikiCreation';
|
|
import type { IAuthenticationService } from '@services/auth/interface';
|
|
import { container } from '@services/container';
|
|
import { i18n } from '@services/libs/i18n';
|
|
import { logger } from '@services/libs/log';
|
|
import type { IMenuService } from '@services/menu/interface';
|
|
import type { IPreferenceService } from '@services/preferences/interface';
|
|
import serviceIdentifier from '@services/serviceIdentifier';
|
|
import { SupportedStorageServices } from '@services/types';
|
|
import type { IViewService } from '@services/view/interface';
|
|
import type { IWikiService } from '@services/wiki/interface';
|
|
import type { IWindowService } from '@services/windows/interface';
|
|
import { WindowNames } from '@services/windows/WindowProperties';
|
|
import type { IWorkspace, IWorkspaceService } from '@services/workspaces/interface';
|
|
import { isWikiWorkspace } from '@services/workspaces/interface';
|
|
|
|
import { DELAY_MENU_REGISTER } from '@/constants/parameters';
|
|
import type { ISyncService } from '@services/sync/interface';
|
|
import type { IInitializeWorkspaceOptions, IWorkspaceViewService } from './interface';
|
|
import { registerMenu } from './registerMenu';
|
|
import { getTidgiMiniWindowTargetWorkspace } from './utilities';
|
|
|
|
@injectable()
|
|
export class WorkspaceView implements IWorkspaceViewService {
|
|
constructor(
|
|
@inject(serviceIdentifier.Authentication) private readonly authService: IAuthenticationService,
|
|
@inject(serviceIdentifier.Preference) private readonly preferenceService: IPreferenceService,
|
|
) {
|
|
setTimeout(() => {
|
|
void registerMenu();
|
|
}, DELAY_MENU_REGISTER);
|
|
}
|
|
|
|
public async initializeAllWorkspaceView(): Promise<void> {
|
|
logger.info('starting', { function: 'initializeAllWorkspaceView' });
|
|
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
|
|
const workspacesList = await workspaceService.getWorkspacesAsList();
|
|
logger.info(`Found ${workspacesList.length} workspaces to initialize`, {
|
|
workspaces: workspacesList.map(w => ({ id: w.id, name: w.name, isSubWiki: isWikiWorkspace(w) ? w.isSubWiki : false, pageType: w.pageType })),
|
|
}, { function: 'initializeAllWorkspaceView' });
|
|
// Only load workspace that is not a subwiki and not a page type
|
|
const wikiService = container.get<IWikiService>(serviceIdentifier.Wiki);
|
|
workspacesList.filter((workspace) => isWikiWorkspace(workspace) && !workspace.isSubWiki && !workspace.pageType).forEach((workspace) => {
|
|
wikiService.setWikiStartLockOn(workspace.id);
|
|
});
|
|
// sorting (-1 will make a in the front, b in the back)
|
|
const sortedList = workspacesList
|
|
.sort((a, b) => a.order - b.order) // sort by order, 1-2<0, so first will be the first
|
|
.sort((a, b) => (a.active && !b.active ? -1 : 0)) // put active wiki first
|
|
.sort((a, b) => (isWikiWorkspace(a) && a.isSubWiki && (!isWikiWorkspace(b) || !b.isSubWiki) ? -1 : 0)); // put subwiki on top, they can't restart wiki, so need to sync them first, then let main wiki restart the wiki // revert this after tw can reload tid from fs
|
|
await mapSeries(sortedList, async (workspace) => {
|
|
await this.initializeWorkspaceView(workspace);
|
|
});
|
|
wikiService.setAllWikiStartLockOff();
|
|
}
|
|
|
|
public async initializeWorkspaceView(workspace: IWorkspace, options: IInitializeWorkspaceOptions = {}): Promise<void> {
|
|
logger.info(i18n.t('Log.InitializeWorkspaceView'));
|
|
|
|
// Skip initialization for page workspaces - they don't need TiddlyWiki setup
|
|
if (workspace.pageType) {
|
|
logger.info('skipping initialization for page workspace', { function: 'initializeWorkspaceView', workspaceId: workspace.id, pageType: workspace.pageType });
|
|
return;
|
|
}
|
|
|
|
const { followHibernateSettingWhenInit = true, syncImmediately = true, isNew = false } = options;
|
|
// skip if workspace don't contains a valid tiddlywiki setup, this allows user to delete workspace later
|
|
const wikiService = container.get<IWikiService>(serviceIdentifier.Wiki);
|
|
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
|
|
const shouldBeMainWiki = isWikiWorkspace(workspace) && !workspace.isSubWiki;
|
|
logger.info('checking wiki existence', {
|
|
workspaceId: workspace.id,
|
|
shouldBeMainWiki,
|
|
wikiFolderLocation: isWikiWorkspace(workspace) ? workspace.wikiFolderLocation : undefined,
|
|
function: 'initializeWorkspaceView',
|
|
});
|
|
const checkResult = await wikiService.checkWikiExist(workspace, { shouldBeMainWiki, showDialog: true });
|
|
if (checkResult !== true) {
|
|
logger.warn('checkWikiExist found invalid wiki', {
|
|
workspaceId: workspace.id,
|
|
checkResult,
|
|
shouldBeMainWiki,
|
|
wikiFolderLocation: isWikiWorkspace(workspace) ? workspace.wikiFolderLocation : undefined,
|
|
function: 'initializeWorkspaceView',
|
|
});
|
|
return;
|
|
}
|
|
logger.info('wiki validation passed', {
|
|
workspaceId: workspace.id,
|
|
function: 'initializeWorkspaceView',
|
|
});
|
|
logger.debug('Initializing workspace', {
|
|
workspaceId: workspace.id,
|
|
options: JSON.stringify(options),
|
|
function: 'initializeWorkspaceView',
|
|
});
|
|
if (followHibernateSettingWhenInit) {
|
|
const hibernateUnusedWorkspacesAtLaunch = await this.preferenceService.get('hibernateUnusedWorkspacesAtLaunch');
|
|
if ((hibernateUnusedWorkspacesAtLaunch || (isWikiWorkspace(workspace) && workspace.hibernateWhenUnused)) && !workspace.active) {
|
|
logger.debug(
|
|
`initializeWorkspaceView() quit because ${
|
|
JSON.stringify({
|
|
followHibernateSettingWhenInit,
|
|
'workspace.hibernateWhenUnused': isWikiWorkspace(workspace) ? workspace.hibernateWhenUnused : false,
|
|
'workspace.active': workspace.active,
|
|
hibernateUnusedWorkspacesAtLaunch,
|
|
})
|
|
}`,
|
|
);
|
|
if (isWikiWorkspace(workspace) && !workspace.hibernated) {
|
|
await workspaceService.update(workspace.id, { hibernated: true });
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
const syncGitWhenInitializeWorkspaceView = async () => {
|
|
if (!isWikiWorkspace(workspace)) return;
|
|
const { wikiFolderLocation, gitUrl: githubRepoUrl, storageService, isSubWiki } = workspace;
|
|
// we are using syncWikiIfNeeded that handles recursive sync for all subwiki, so we only need to pass main wiki to it in this method.
|
|
if (isSubWiki) {
|
|
return;
|
|
}
|
|
// get sync process ready
|
|
try {
|
|
if (workspace.syncOnStartup && storageService !== SupportedStorageServices.local && syncImmediately) {
|
|
// check synced wiki should have githubRepoUrl
|
|
if (typeof githubRepoUrl !== 'string') {
|
|
throw new TypeError(`githubRepoUrl is undefined in initializeAllWorkspaceView when init ${wikiFolderLocation}`);
|
|
}
|
|
const mainWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
|
|
if (mainWindow === undefined) {
|
|
throw new Error(i18n.t(`Error.MainWindowMissing`));
|
|
}
|
|
const userInfo = await this.authService.getStorageServiceUserInfo(storageService);
|
|
|
|
if (userInfo?.accessToken) {
|
|
// sync in non-blocking way
|
|
void container.get<ISyncService>(serviceIdentifier.Sync).syncWikiIfNeeded(workspace);
|
|
} else {
|
|
// user not login into Github or something else
|
|
void dialog.showMessageBox(mainWindow, {
|
|
title: i18n.t('Dialog.StorageServiceUserInfoNoFound'),
|
|
message: i18n.t('Dialog.StorageServiceUserInfoNoFoundDetail'),
|
|
buttons: ['OK'],
|
|
cancelId: 0,
|
|
defaultId: 0,
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.error('wikiStartup sync failed', {
|
|
function: 'initializeAllWorkspaceView',
|
|
error,
|
|
});
|
|
}
|
|
};
|
|
|
|
const addViewWhenInitializeWorkspaceView = async (): Promise<void> => {
|
|
// adding WebContentsView for each workspace
|
|
// skip view initialize if this is a sub wiki
|
|
if (isWikiWorkspace(workspace) && workspace.isSubWiki) {
|
|
return;
|
|
}
|
|
// if we run this due to RestartService, then skip the view adding and the while loop, because the workspaceMetadata.isLoading will be false, because addViewForAllBrowserViews will return before it run loadInitialUrlWithCatch
|
|
if (await container.get<IViewService>(serviceIdentifier.View).alreadyHaveView(workspace)) {
|
|
logger.debug('Skip because alreadyHaveView');
|
|
return;
|
|
}
|
|
// Create browserView, and if user want a tidgi mini window, we also create a new window for that
|
|
await this.addViewForAllBrowserViews(workspace);
|
|
if (isNew && options.from === WikiCreationMethod.Create) {
|
|
const view = container.get<IViewService>(serviceIdentifier.View).getView(workspace.id, WindowNames.main);
|
|
if (view !== undefined) {
|
|
// if is newly created wiki, we set the language as user preference
|
|
const currentLanguage = await this.preferenceService.get('language');
|
|
const tiddlywikiLanguageName = tiddlywikiLanguagesMap[currentLanguage];
|
|
if (tiddlywikiLanguageName === undefined) {
|
|
const errorMessage = `When creating new wiki, and switch to language "${currentLanguage}", there is no corresponding tiddlywiki language registered`;
|
|
logger.error(errorMessage, {
|
|
tiddlywikiLanguagesMap,
|
|
});
|
|
} else {
|
|
logger.debug('setting wiki language on init', { function: 'initializeWorkspaceView', currentLanguage, tiddlywikiLanguageName });
|
|
await container.get<IWikiService>(serviceIdentifier.Wiki).setWikiLanguage(workspace.id, tiddlywikiLanguageName);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
logger.debug('calling wikiStartup', {
|
|
function: 'initializeWorkspaceView',
|
|
});
|
|
await Promise.all([
|
|
container.get<IWikiService>(serviceIdentifier.Wiki).wikiStartup(workspace),
|
|
addViewWhenInitializeWorkspaceView(),
|
|
]);
|
|
void syncGitWhenInitializeWorkspaceView();
|
|
}
|
|
|
|
public async addViewForAllBrowserViews(workspace: IWorkspace): Promise<void> {
|
|
const mainTask = container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.main);
|
|
|
|
// For tidgi mini window, decide which workspace to show based on preferences
|
|
const tidgiMiniWindowTask = (async () => {
|
|
const tidgiMiniWindow = await this.preferenceService.get('tidgiMiniWindow');
|
|
if (!tidgiMiniWindow) {
|
|
return;
|
|
}
|
|
const { shouldSync, targetWorkspaceId } = await getTidgiMiniWindowTargetWorkspace(workspace.id);
|
|
// If syncing with main window, use the current workspace
|
|
if (shouldSync) {
|
|
await container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.tidgiMiniWindow);
|
|
return;
|
|
}
|
|
// If not syncing and a fixed workspace is set, only add view if this IS the fixed workspace
|
|
if (targetWorkspaceId && workspace.id === targetWorkspaceId) {
|
|
await container.get<IViewService>(serviceIdentifier.View).addView(workspace, WindowNames.tidgiMiniWindow);
|
|
}
|
|
// If not syncing and no fixed workspace is set, don't add any view (user needs to select one)
|
|
})();
|
|
|
|
await Promise.all([mainTask, tidgiMiniWindowTask]);
|
|
}
|
|
|
|
public async openWorkspaceWindowWithView(workspace: IWorkspace, configs?: { uri?: string }): Promise<void> {
|
|
const uriToOpen = configs?.uri ?? (isWikiWorkspace(workspace) ? workspace.lastUrl : undefined) ?? (isWikiWorkspace(workspace) ? workspace.homeUrl : undefined);
|
|
logger.debug('Open workspace in new window. uriToOpen here will overwrite the decision in initializeWorkspaceViewHandlersAndLoad.', {
|
|
id: workspace.id,
|
|
uriToOpen,
|
|
function: 'openWorkspaceWindowWithView',
|
|
});
|
|
const browserWindow = await container.get<IWindowService>(serviceIdentifier.Window).open(WindowNames.secondary, undefined, { multiple: true }, true);
|
|
const sharedWebPreferences = await container.get<IViewService>(serviceIdentifier.View).getSharedWebPreferences(workspace);
|
|
const view = await container.get<IViewService>(serviceIdentifier.View).createViewAddToWindow(workspace, browserWindow, sharedWebPreferences, WindowNames.secondary);
|
|
logger.debug('View created in new window.', { id: workspace.id, uriToOpen, function: 'openWorkspaceWindowWithView' });
|
|
await container.get<IViewService>(serviceIdentifier.View).initializeWorkspaceViewHandlersAndLoad(browserWindow, view, {
|
|
workspace,
|
|
sharedWebPreferences,
|
|
windowName: WindowNames.secondary,
|
|
uri: uriToOpen,
|
|
});
|
|
}
|
|
|
|
public async updateLastUrl(
|
|
workspaceID: string,
|
|
view: Electron.CrossProcessExports.WebContentsView | undefined = container.get<IViewService>(serviceIdentifier.View).getView(workspaceID, WindowNames.main),
|
|
): Promise<void> {
|
|
if (view?.webContents) {
|
|
const currentUrl = view.webContents.getURL();
|
|
logger.debug('Updating lastUrl for workspace', {
|
|
workspaceID,
|
|
currentUrl,
|
|
function: 'updateLastUrl',
|
|
});
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).update(workspaceID, {
|
|
lastUrl: currentUrl,
|
|
});
|
|
} else {
|
|
logger.warn(`Can't update lastUrl for workspace ${workspaceID}, view is not found`);
|
|
}
|
|
}
|
|
|
|
public async openUrlInWorkspace(url: string, id: string): Promise<void> {
|
|
if (typeof id === 'string' && id.length > 0) {
|
|
// if id is defined, switch to that workspace
|
|
await this.setActiveWorkspaceView(id);
|
|
await container.get<IMenuService>(serviceIdentifier.MenuService).buildMenu();
|
|
// load url in the current workspace
|
|
const activeWorkspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getActiveWorkspace();
|
|
if (activeWorkspace !== undefined) {
|
|
await this.loadURL(url, activeWorkspace.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async setWorkspaceView(workspaceID: string, workspaceOptions: IWorkspace): Promise<void> {
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).set(workspaceID, workspaceOptions);
|
|
container.get<IViewService>(serviceIdentifier.View).setViewsAudioPref();
|
|
container.get<IViewService>(serviceIdentifier.View).setViewsNotificationsPref();
|
|
}
|
|
|
|
public async setWorkspaceViews(workspaces: Record<string, IWorkspace>): Promise<void> {
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).setWorkspaces(workspaces);
|
|
container.get<IViewService>(serviceIdentifier.View).setViewsAudioPref();
|
|
container.get<IViewService>(serviceIdentifier.View).setViewsNotificationsPref();
|
|
}
|
|
|
|
public async wakeUpWorkspaceView(workspaceID: string): Promise<void> {
|
|
const workspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(workspaceID);
|
|
if (workspace !== undefined) {
|
|
await Promise.all([
|
|
container.get<IWorkspaceService>(serviceIdentifier.Workspace).update(workspaceID, {
|
|
hibernated: false,
|
|
}),
|
|
this.authService.getUserName(workspace).then(userName => container.get<IWikiService>(serviceIdentifier.Wiki).startWiki(workspaceID, userName)),
|
|
this.addViewForAllBrowserViews(workspace),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public async hibernateWorkspaceView(workspaceID: string): Promise<void> {
|
|
const workspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(workspaceID);
|
|
logger.debug('hibernating workspace', {
|
|
function: 'hibernateWorkspaceView',
|
|
workspaceID,
|
|
active: String(workspace?.active),
|
|
});
|
|
if (workspace !== undefined && !workspace.active) {
|
|
await Promise.all([
|
|
container.get<IWikiService>(serviceIdentifier.Wiki).stopWiki(workspaceID),
|
|
container.get<IWorkspaceService>(serviceIdentifier.Workspace).update(workspaceID, {
|
|
hibernated: true,
|
|
}),
|
|
]);
|
|
container.get<IViewService>(serviceIdentifier.View).removeAllViewOfWorkspace(workspaceID, true);
|
|
}
|
|
}
|
|
|
|
public async setActiveWorkspaceView(nextWorkspaceID: string): Promise<void> {
|
|
logger.debug('setActiveWorkspaceView', { nextWorkspaceID });
|
|
const [oldActiveWorkspace, newWorkspace] = await Promise.all([
|
|
container.get<IWorkspaceService>(serviceIdentifier.Workspace).getActiveWorkspace(),
|
|
container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(nextWorkspaceID),
|
|
]);
|
|
if (newWorkspace === undefined) {
|
|
throw new Error(`Workspace id ${nextWorkspaceID} does not exist. When setActiveWorkspaceView().`);
|
|
}
|
|
logger.debug(
|
|
`Set active workspace oldActiveWorkspace.id: ${oldActiveWorkspace?.id ?? 'undefined'} nextWorkspaceID: ${nextWorkspaceID} newWorkspace.isSubWiki ${
|
|
String(
|
|
isWikiWorkspace(newWorkspace) ? newWorkspace.isSubWiki : false,
|
|
)
|
|
}`,
|
|
);
|
|
|
|
// Handle page workspace - only update workspace state, no view management needed
|
|
if (newWorkspace.pageType) {
|
|
logger.debug(`${nextWorkspaceID} is a page workspace, only updating workspace state.`);
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).setActiveWorkspace(nextWorkspaceID, oldActiveWorkspace?.id);
|
|
// Hide old workspace view if switching from a regular workspace
|
|
if (oldActiveWorkspace !== undefined && oldActiveWorkspace.id !== nextWorkspaceID && !oldActiveWorkspace.pageType) {
|
|
await this.hideWorkspaceView(oldActiveWorkspace.id);
|
|
if (isWikiWorkspace(oldActiveWorkspace) && oldActiveWorkspace.hibernateWhenUnused) {
|
|
await this.hibernateWorkspaceView(oldActiveWorkspace.id);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (isWikiWorkspace(newWorkspace) && newWorkspace.isSubWiki && typeof newWorkspace.mainWikiID === 'string') {
|
|
logger.debug(`${nextWorkspaceID} is a subwiki, set its main wiki ${newWorkspace.mainWikiID} to active instead.`);
|
|
await this.setActiveWorkspaceView(newWorkspace.mainWikiID);
|
|
if (typeof newWorkspace.tagName === 'string') {
|
|
await container.get<IWikiService>(serviceIdentifier.Wiki).wikiOperationInBrowser(WikiChannel.openTiddler, newWorkspace.mainWikiID, [newWorkspace.tagName]);
|
|
}
|
|
return;
|
|
}
|
|
// later process will use the current active workspace
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).setActiveWorkspace(nextWorkspaceID, oldActiveWorkspace?.id);
|
|
if (isWikiWorkspace(newWorkspace) && newWorkspace.hibernated) {
|
|
await this.wakeUpWorkspaceView(nextWorkspaceID);
|
|
}
|
|
try {
|
|
await container.get<IViewService>(serviceIdentifier.View).setActiveViewForAllBrowserViews(nextWorkspaceID);
|
|
await this.realignActiveWorkspace(nextWorkspaceID);
|
|
} catch (error) {
|
|
logger.error('setActiveWorkspaceView error', {
|
|
function: 'setActiveWorkspaceView',
|
|
error,
|
|
});
|
|
throw error;
|
|
}
|
|
// if we are switching to a new workspace, we hide and/or hibernate old view, and activate new view
|
|
if (oldActiveWorkspace !== undefined && oldActiveWorkspace.id !== nextWorkspaceID) {
|
|
await this.hideWorkspaceView(oldActiveWorkspace.id);
|
|
if (isWikiWorkspace(oldActiveWorkspace) && oldActiveWorkspace.hibernateWhenUnused) {
|
|
await this.hibernateWorkspaceView(oldActiveWorkspace.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async clearActiveWorkspaceView(idToDeactivate?: string): Promise<void> {
|
|
const activeWorkspace = idToDeactivate === undefined
|
|
? await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getActiveWorkspace()
|
|
: await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(idToDeactivate);
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).clearActiveWorkspace(activeWorkspace?.id);
|
|
if (activeWorkspace === undefined) {
|
|
return;
|
|
}
|
|
if (isWikiWorkspace(activeWorkspace) && activeWorkspace.isSubWiki && typeof activeWorkspace.mainWikiID === 'string') {
|
|
logger.debug(`${activeWorkspace.id} is a subwiki, set its main wiki ${activeWorkspace.mainWikiID} to deactivated instead.`, { function: 'clearActiveWorkspaceView' });
|
|
await this.clearActiveWorkspaceView(activeWorkspace.mainWikiID);
|
|
return;
|
|
}
|
|
try {
|
|
await this.hideWorkspaceView(activeWorkspace.id);
|
|
} catch (error) {
|
|
logger.error('setActiveWorkspaceView error', {
|
|
function: 'clearActiveWorkspaceView',
|
|
error,
|
|
});
|
|
throw error;
|
|
}
|
|
if (isWikiWorkspace(activeWorkspace) && activeWorkspace.hibernateWhenUnused) {
|
|
await this.hibernateWorkspaceView(activeWorkspace.id);
|
|
}
|
|
}
|
|
|
|
public async removeWorkspaceView(workspaceID: string): Promise<void> {
|
|
container.get<IViewService>(serviceIdentifier.View).removeAllViewOfWorkspace(workspaceID, true);
|
|
const mainWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
|
|
// if there's only one workspace left, clear all
|
|
if ((await container.get<IWorkspaceService>(serviceIdentifier.Workspace).countWorkspaces()) === 1) {
|
|
if (mainWindow !== undefined) {
|
|
mainWindow.setTitle(app.name);
|
|
}
|
|
} else if (
|
|
(await container.get<IWorkspaceService>(serviceIdentifier.Workspace).countWorkspaces()) > 1 &&
|
|
(await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(workspaceID))?.active === true
|
|
) {
|
|
const previousWorkspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getPreviousWorkspace(workspaceID);
|
|
if (previousWorkspace !== undefined) {
|
|
await this.setActiveWorkspaceView(previousWorkspace.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async restartWorkspaceViewService(id?: string): Promise<void> {
|
|
const workspaceToRestart = id === undefined
|
|
? await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getActiveWorkspace()
|
|
: await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(id);
|
|
if (workspaceToRestart === undefined) {
|
|
logger.warn(`restartWorkspaceViewService: no workspace ${id ?? 'id undefined'} to restart`);
|
|
return;
|
|
}
|
|
if (isWikiWorkspace(workspaceToRestart) && workspaceToRestart.isSubWiki) {
|
|
const mainWikiIDToRestart = workspaceToRestart.mainWikiID;
|
|
if (mainWikiIDToRestart) {
|
|
await this.restartWorkspaceViewService(mainWikiIDToRestart);
|
|
}
|
|
return;
|
|
}
|
|
logger.info(`Restarting workspace ${workspaceToRestart.id}`);
|
|
await this.updateLastUrl(workspaceToRestart.id);
|
|
// start restarting. Set isLoading to false, and it will be set by some callback elsewhere to true.
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).updateMetaData(workspaceToRestart.id, {
|
|
didFailLoadErrorMessage: null,
|
|
isLoading: false,
|
|
isRestarting: true,
|
|
});
|
|
await container.get<IWikiService>(serviceIdentifier.Wiki).stopWiki(workspaceToRestart.id);
|
|
await this.initializeWorkspaceView(workspaceToRestart, { syncImmediately: false });
|
|
if (await container.get<IWorkspaceService>(serviceIdentifier.Workspace).workspaceDidFailLoad(workspaceToRestart.id)) {
|
|
logger.warn('skip because workspaceDidFailLoad', { function: 'restartWorkspaceViewService' });
|
|
return;
|
|
}
|
|
await container.get<IViewService>(serviceIdentifier.View).reloadViewsWebContents(workspaceToRestart.id);
|
|
await container.get<IWikiService>(serviceIdentifier.Wiki).wikiOperationInBrowser(WikiChannel.generalNotification, workspaceToRestart.id, [
|
|
i18n.t('ContextMenu.RestartServiceComplete'),
|
|
]);
|
|
await container.get<IWorkspaceService>(serviceIdentifier.Workspace).updateMetaData(workspaceToRestart.id, { isRestarting: false });
|
|
}
|
|
|
|
public async restartAllWorkspaceView(): Promise<void> {
|
|
const workspaces = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getWorkspacesAsList();
|
|
await Promise.all(
|
|
workspaces.map(async (workspace) => {
|
|
await Promise.all(
|
|
[WindowNames.main, WindowNames.tidgiMiniWindow].map(async (windowName) => {
|
|
const view = container.get<IViewService>(serviceIdentifier.View).getView(workspace.id, windowName);
|
|
if (view !== undefined) {
|
|
await container.get<IViewService>(serviceIdentifier.View).loadUrlForView(workspace, view);
|
|
}
|
|
}),
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
|
|
public async clearBrowsingDataWithConfirm(): Promise<void> {
|
|
const availableWindowToShowDialog = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.preferences) ??
|
|
container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
|
|
if (availableWindowToShowDialog !== undefined) {
|
|
await dialog
|
|
.showMessageBox(availableWindowToShowDialog, {
|
|
type: 'question',
|
|
buttons: [i18n.t('Preference.ResetNow'), i18n.t('Cancel')],
|
|
message: i18n.t('Preference.ClearBrowsingDataMessage'),
|
|
cancelId: 1,
|
|
})
|
|
.then(async ({ response }) => {
|
|
if (response === 0) {
|
|
await this.clearBrowsingData();
|
|
}
|
|
})
|
|
.catch(console.error);
|
|
}
|
|
}
|
|
|
|
public async clearBrowsingData(): Promise<void> {
|
|
await session.defaultSession.clearStorageData();
|
|
const workspaces = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getWorkspaces();
|
|
await Promise.all(
|
|
Object.keys(workspaces).map(async (id) => {
|
|
await session.fromPartition(`persist:${id}`).clearStorageData();
|
|
}),
|
|
);
|
|
|
|
// shared session
|
|
await session.fromPartition('persist:shared').clearStorageData();
|
|
}
|
|
|
|
public async loadURL(url: string, id: string | undefined): Promise<void> {
|
|
const mainWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
|
|
const activeWorkspace = await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getActiveWorkspace();
|
|
const activeWorkspaceID = id ?? activeWorkspace?.id;
|
|
if (mainWindow !== undefined && activeWorkspaceID !== undefined) {
|
|
const view = container.get<IViewService>(serviceIdentifier.View).getView(activeWorkspaceID, WindowNames.main);
|
|
if (view?.webContents) {
|
|
view.webContents.focus();
|
|
await view.webContents.loadURL(url);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Seems this is for relocating WebContentsView in the electron window
|
|
*/
|
|
public async realignActiveWorkspace(id?: string): Promise<void> {
|
|
// this function only call browserView.setBounds
|
|
// do not attempt to recall browserView.webContents.focus()
|
|
// as it breaks page focus (cursor, scroll bar not visible)
|
|
await this.realignActiveWorkspaceView(id);
|
|
try {
|
|
await container.get<IMenuService>(serviceIdentifier.MenuService).buildMenu();
|
|
} catch (error) {
|
|
logger.error('realignActiveWorkspace buildMenu error', {
|
|
function: 'realignActiveWorkspace',
|
|
error,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async realignActiveWorkspaceView(id?: string): Promise<void> {
|
|
const workspaceToRealign = id === undefined
|
|
? await container.get<IWorkspaceService>(serviceIdentifier.Workspace).getActiveWorkspace()
|
|
: await container.get<IWorkspaceService>(serviceIdentifier.Workspace).get(id);
|
|
logger.debug('activeWorkspace.id', {
|
|
workspaceId: workspaceToRealign?.id ?? 'undefined',
|
|
stack: new Error('stack').stack?.replace('Error:', ''),
|
|
function: 'realignActiveWorkspaceView',
|
|
});
|
|
if (workspaceToRealign && isWikiWorkspace(workspaceToRealign) && workspaceToRealign.isSubWiki) {
|
|
logger.debug('skip because subwiki; realign main wiki instead', { workspaceId: workspaceToRealign.id, function: 'realignActiveWorkspaceView' });
|
|
if (workspaceToRealign.mainWikiID) {
|
|
await this.realignActiveWorkspaceView(workspaceToRealign.mainWikiID);
|
|
}
|
|
return;
|
|
}
|
|
const mainWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
|
|
const tidgiMiniWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.tidgiMiniWindow);
|
|
|
|
logger.info(
|
|
`realignActiveWorkspaceView: id ${workspaceToRealign?.id ?? 'undefined'}`,
|
|
);
|
|
if (workspaceToRealign === undefined) {
|
|
logger.warn('realignActiveWorkspaceView: no active workspace');
|
|
return;
|
|
}
|
|
if (mainWindow === undefined && tidgiMiniWindow === undefined) {
|
|
logger.warn('realignActiveWorkspaceView: no active window');
|
|
return;
|
|
}
|
|
const tasks = [];
|
|
if (mainWindow === undefined) {
|
|
logger.warn(`realignActiveWorkspaceView: no mainBrowserViewWebContent, skip main window for ${workspaceToRealign.id}.`);
|
|
} else {
|
|
tasks.push(container.get<IViewService>(serviceIdentifier.View).realignActiveView(mainWindow, workspaceToRealign.id, WindowNames.main));
|
|
logger.debug(`realignActiveWorkspaceView: realign main window for ${workspaceToRealign.id}.`);
|
|
}
|
|
if (tidgiMiniWindow === undefined) {
|
|
logger.info(`realignActiveWorkspaceView: no tidgiMiniWindowBrowserViewWebContent, skip tidgi mini window for ${workspaceToRealign.id}.`);
|
|
} else {
|
|
// For tidgi mini window, decide which workspace to show based on preferences
|
|
const { targetWorkspaceId } = await getTidgiMiniWindowTargetWorkspace(workspaceToRealign.id);
|
|
const tidgiMiniWindowWorkspaceId = targetWorkspaceId || workspaceToRealign.id;
|
|
tasks.push(container.get<IViewService>(serviceIdentifier.View).realignActiveView(tidgiMiniWindow, tidgiMiniWindowWorkspaceId, WindowNames.tidgiMiniWindow));
|
|
}
|
|
await Promise.all(tasks);
|
|
}
|
|
|
|
private async hideWorkspaceView(idToDeactivate: string): Promise<void> {
|
|
const mainWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.main);
|
|
const tidgiMiniWindow = container.get<IWindowService>(serviceIdentifier.Window).get(WindowNames.tidgiMiniWindow);
|
|
const tasks = [];
|
|
if (mainWindow === undefined) {
|
|
logger.warn(`hideWorkspaceView: no mainBrowserWindow, skip main window browserView.`);
|
|
} else {
|
|
logger.info(`hideWorkspaceView: hide main window browserView.`);
|
|
tasks.push(container.get<IViewService>(serviceIdentifier.View).hideView(mainWindow, WindowNames.main, idToDeactivate));
|
|
}
|
|
if (tidgiMiniWindow === undefined) {
|
|
logger.debug(`hideWorkspaceView: no tidgiMiniWindowBrowserWindow, skip tidgi mini window browserView.`);
|
|
} else {
|
|
// For tidgi mini window, only hide if syncing with main window OR if this is the fixed workspace being deactivated
|
|
const { shouldSync, targetWorkspaceId } = await getTidgiMiniWindowTargetWorkspace(idToDeactivate);
|
|
// Only hide tidgi mini window view if:
|
|
// 1. Syncing with main window (should hide when main window hides)
|
|
// 2. OR the workspace being hidden is the fixed workspace (rare case, but should be handled)
|
|
if (shouldSync || idToDeactivate === targetWorkspaceId) {
|
|
logger.info(`hideWorkspaceView: hide tidgi mini window browserView.`);
|
|
tasks.push(container.get<IViewService>(serviceIdentifier.View).hideView(tidgiMiniWindow, WindowNames.tidgiMiniWindow, idToDeactivate));
|
|
} else {
|
|
logger.debug(`hideWorkspaceView: skip hiding tidgi mini window browserView (fixed workspace: ${targetWorkspaceId || 'none'}).`);
|
|
}
|
|
}
|
|
await Promise.all(tasks);
|
|
logger.info(`hideWorkspaceView: done.`);
|
|
}
|
|
}
|