feat: wire up new UI windows + misc integration

- Add NodeManagement, RemoteTerminal, Subscription window entries
- Update window properties and routing for new pages
- Update database interface, menu template, type definitions
- Formatting normalization across touched files
This commit is contained in:
lin onetwo 2026-04-05 17:27:13 +08:00
parent d1c5983cb2
commit 485dd4ec28
7 changed files with 286 additions and 154 deletions

View file

@ -1,23 +1,24 @@
import { Helmet } from '@dr.pogodin/react-helmet';
import { styled } from '@mui/material/styles';
import { lazy, Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { Route, Switch } from 'wouter';
import { Helmet } from "@dr.pogodin/react-helmet";
import { styled } from "@mui/material/styles";
import { lazy, Suspense } from "react";
import { useTranslation } from "react-i18next";
import { Route, Switch } from "wouter";
import { PageType } from '@/constants/pageTypes';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { WindowNames } from '@services/windows/WindowProperties';
import { ContentLoading } from './ContentLoading';
import FindInPage from './FindInPage';
import { SideBar } from './Sidebar';
import { useAskAIWithSelection } from './useAskAIWithSelection';
import { useInitialPage } from './useInitialPage';
import { PageType } from "@/constants/pageTypes";
import { ToolApprovalDialog } from "@/components/ToolApprovalDialog";
import { usePreferenceObservable } from "@services/preferences/hooks";
import { WindowNames } from "@services/windows/WindowProperties";
import { ContentLoading } from "./ContentLoading";
import FindInPage from "./FindInPage";
import { SideBar } from "./Sidebar";
import { useAskAIWithSelection } from "./useAskAIWithSelection";
import { useInitialPage } from "./useInitialPage";
import { subPages } from './subPages';
import { subPages } from "./subPages";
const WikiBackground = lazy(() => import('../WikiBackground'));
const WikiBackground = lazy(() => import("../WikiBackground"));
const OuterRoot = styled('div')`
const OuterRoot = styled("div")`
display: flex;
flex-direction: column;
height: 100vh;
@ -25,7 +26,7 @@ const OuterRoot = styled('div')`
overflow: hidden;
`;
const Root = styled('div')`
const Root = styled("div")`
display: flex;
flex-direction: row;
flex: 1;
@ -43,7 +44,7 @@ const Root = styled('div')`
}
`;
const ContentRoot = styled('div')<{ $sidebar: boolean }>(
const ContentRoot = styled("div")<{ $sidebar: boolean }>(
({ theme, $sidebar }) => `
flex: 1;
display: flex;
@ -70,23 +71,33 @@ export default function Main(): React.JSX.Element {
const windowName = window.meta().windowName;
const preferences = usePreferenceObservable();
const isTidgiMiniWindow = windowName === WindowNames.tidgiMiniWindow;
const showSidebar = (isTidgiMiniWindow ? preferences?.tidgiMiniWindowShowSidebar : preferences?.sidebar) ?? true;
const showSidebar =
(isTidgiMiniWindow
? preferences?.tidgiMiniWindowShowSidebar
: preferences?.sidebar) ?? true;
return (
<OuterRoot>
<Helmet>
<title>{t('Menu.TidGi')}{isTidgiMiniWindow ? ` [${t('Menu.TidGiMiniWindow')}]` : ' [App]'}</title>
<title>
{t("Menu.TidGi")}
{isTidgiMiniWindow ? ` [${t("Menu.TidGiMiniWindow")}]` : " [App]"}
</title>
</Helmet>
<ToolApprovalDialog />
<Root data-windowname={windowName} data-showsidebar={showSidebar}>
{showSidebar && <SideBar />}
<ContentRoot $sidebar={showSidebar}>
<FindInPage />
<Suspense fallback={<ContentLoading />}>
<Switch>
<Route path={`/${PageType.wiki}/:id/`} component={WikiBackground} />
<Route
path={`/${PageType.wiki}/:id/`}
component={WikiBackground}
/>
<Route path={`/${PageType.agent}`} component={subPages.Agent} />
<Route path={`/${PageType.guide}`} component={subPages.Guide} />
<Route path={`/${PageType.help}`} component={subPages.Help} />
<Route path='/' component={subPages.Guide} />
<Route path="/" component={subPages.Guide} />
<Route component={subPages.Guide} />
</Switch>
</Suspense>

View file

@ -1,16 +1,22 @@
import { DatabaseChannel } from '@/constants/channels';
import type { IUserInfos } from '@services/auth/interface';
import { AIGlobalSettings } from '@services/providerRegistry/interface';
import type { IPreferences } from '@services/preferences/interface';
import type { ISyncableWikiConfig, IWorkspace } from '@services/workspaces/interface';
import { ProxyPropertyType } from 'electron-ipc-cat/common';
import { DataSource } from 'typeorm';
import { DatabaseChannel } from "@/constants/channels";
import type { IUserInfos } from "@services/auth/interface";
import { AIGlobalSettings } from "@services/providerRegistry/interface";
import type { IPreferences } from "@services/preferences/interface";
import type { IToolPermissionEntry } from "@services/toolPermissions/interface";
import type {
ISyncableWikiConfig,
IWorkspace,
} from "@services/workspaces/interface";
import { ProxyPropertyType } from "electron-ipc-cat/common";
import { DataSource } from "typeorm";
export interface ISettingFile {
preferences: IPreferences;
userInfos: IUserInfos;
workspaces: Record<string, IWorkspace>;
aiSettings?: AIGlobalSettings;
"toolPermissions.blacklist"?: IToolPermissionEntry[];
"toolPermissions.whitelist"?: IToolPermissionEntry[];
}
/**
@ -45,7 +51,10 @@ export interface IDatabaseService {
* @param key setting file top level key like `userInfos`
* @param value whole setting from a service
*/
setSetting<K extends keyof ISettingFile>(key: K, value: ISettingFile[K]): void;
setSetting<K extends keyof ISettingFile>(
key: K,
value: ISettingFile[K],
): void;
/**
* Initialize database for specific key
@ -55,7 +64,11 @@ export interface IDatabaseService {
/**
* Get database connection for specific key
*/
getDatabase(key: string, options?: DatabaseInitOptions, isRetry?: boolean): Promise<DataSource>;
getDatabase(
key: string,
options?: DatabaseInitOptions,
isRetry?: boolean,
): Promise<DataSource>;
/**
* Close database connection
@ -88,7 +101,9 @@ export interface IDatabaseService {
* Exposed over IPC so the renderer can pre-fill the Add Workspace form when
* importing an existing wiki with "use tidgi.config" enabled.
*/
readWikiConfig(wikiFolderLocation: string): Promise<Partial<ISyncableWikiConfig> | undefined>;
readWikiConfig(
wikiFolderLocation: string,
): Promise<Partial<ISyncableWikiConfig> | undefined>;
}
export const DatabaseServiceIPCDescriptor = {

View file

@ -1,137 +1,165 @@
import { container } from '@services/container';
import { i18n } from '@services/libs/i18n';
import serviceIdentifier from '@services/serviceIdentifier';
import type { IUpdaterService } from '@services/updater/interface';
import type { IWindowService } from '@services/windows/interface';
import { WindowNames } from '@services/windows/WindowProperties';
import type { IWorkspaceService } from '@services/workspaces/interface';
import { isWikiWorkspace } from '@services/workspaces/interface';
import { shell } from 'electron';
import { DeferredMenuItemConstructorOptions } from './interface';
import { container } from "@services/container";
import { i18n } from "@services/libs/i18n";
import serviceIdentifier from "@services/serviceIdentifier";
import type { IUpdaterService } from "@services/updater/interface";
import type { IWindowService } from "@services/windows/interface";
import { WindowNames } from "@services/windows/WindowProperties";
import type { IWorkspaceService } from "@services/workspaces/interface";
import { isWikiWorkspace } from "@services/workspaces/interface";
import { shell } from "electron";
import { DeferredMenuItemConstructorOptions } from "./interface";
/**
* Defer to i18next ready to call this
*/
export function loadDefaultMenuTemplate(): DeferredMenuItemConstructorOptions[] {
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const updaterService = container.get<IUpdaterService>(serviceIdentifier.Updater);
const updaterService = container.get<IUpdaterService>(
serviceIdentifier.Updater,
);
return [
{
label: () => i18n.t('Menu.TidGi'),
id: 'TidGi',
label: () => i18n.t("Menu.TidGi"),
id: "TidGi",
submenu: [
{
label: () => i18n.t('ContextMenu.About'),
label: () => i18n.t("ContextMenu.About"),
click: async () => {
await windowService.open(WindowNames.about);
},
},
{ type: 'separator' },
{ type: "separator" },
{
id: 'update',
label: () => i18n.t('Updater.CheckUpdate'),
id: "update",
label: () => i18n.t("Updater.CheckUpdate"),
click: async () => {
await updaterService.checkForUpdates();
},
},
{
label: () => i18n.t('ContextMenu.Preferences'),
accelerator: 'CmdOrCtrl+,',
label: () => i18n.t("ContextMenu.Preferences"),
accelerator: "CmdOrCtrl+,",
click: async () => {
await windowService.open(WindowNames.preferences);
},
},
{ type: 'separator' },
{ type: "separator" },
{
label: () => i18n.t('Preference.Notifications'),
label: () => i18n.t("Preference.Notifications"),
click: async () => {
await windowService.open(WindowNames.notifications);
},
accelerator: 'CmdOrCtrl+Shift+N',
accelerator: "CmdOrCtrl+Shift+N",
},
{
label: () => i18n.t("Menu.NodeManagement"),
click: async () => {
await windowService.open(WindowNames.nodeManagement);
},
accelerator: "CmdOrCtrl+Shift+M",
},
{ type: "separator" },
{ role: "hide" },
{ role: "hideOthers" },
{ role: "unhide" },
{
label: () => i18n.t("ContextMenu.Quit") + i18n.t("Menu.TidGi"),
role: "quit",
},
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ label: () => i18n.t('ContextMenu.Quit') + i18n.t('Menu.TidGi'), role: 'quit' },
],
},
{
label: () => i18n.t('Menu.Edit'),
id: 'Edit',
role: 'editMenu',
label: () => i18n.t("Menu.Edit"),
id: "Edit",
role: "editMenu",
},
{
label: () => i18n.t('Menu.View'),
id: 'View',
label: () => i18n.t("Menu.View"),
id: "View",
},
{
label: () => i18n.t('Menu.Language'),
id: 'Language',
label: () => i18n.t("Menu.Language"),
id: "Language",
},
{
label: () => i18n.t('Menu.History'),
id: 'History',
label: () => i18n.t("Menu.History"),
id: "History",
},
{
label: () => i18n.t('Menu.Workspaces'),
id: 'Workspaces',
label: () => i18n.t("Menu.Workspaces"),
id: "Workspaces",
submenu: [],
},
{
label: () => i18n.t('Menu.Wiki'),
id: 'Wiki',
label: () => i18n.t("Menu.Wiki"),
id: "Wiki",
submenu: [],
visible: async () => {
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const workspaceService = container.get<IWorkspaceService>(
serviceIdentifier.Workspace,
);
const activeWorkspace = await workspaceService.getActiveWorkspace();
// Only show Wiki menu when there's an active wiki workspace
return activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace);
return (
activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace)
);
},
},
{
label: () => i18n.t('Menu.Sync'),
id: 'Sync',
label: () => i18n.t("Menu.Sync"),
id: "Sync",
submenu: [],
visible: async () => {
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const workspaceService = container.get<IWorkspaceService>(
serviceIdentifier.Workspace,
);
const activeWorkspace = await workspaceService.getActiveWorkspace();
return activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace);
return (
activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace)
);
},
},
{
label: () => i18n.t('Menu.Window'),
role: 'windowMenu',
id: 'Window',
label: () => i18n.t("Menu.Window"),
role: "windowMenu",
id: "Window",
},
{
label: () => i18n.t('Menu.Help'),
role: 'help',
id: 'help',
label: () => i18n.t("Menu.Help"),
role: "help",
id: "help",
submenu: [
{
label: () => i18n.t('ContextMenu.TidGiSupport'),
label: () => i18n.t("ContextMenu.TidGiSupport"),
click: async () => {
await shell.openExternal('https://github.com/tiddly-gittly/TidGi-desktop/issues');
await shell.openExternal(
"https://github.com/tiddly-gittly/TidGi-desktop/issues",
);
},
},
{
label: () => i18n.t('Menu.ReportBugViaGithub'),
label: () => i18n.t("Menu.ReportBugViaGithub"),
click: async () => {
await shell.openExternal('https://github.com/tiddly-gittly/TidGi-desktop/issues');
await shell.openExternal(
"https://github.com/tiddly-gittly/TidGi-desktop/issues",
);
},
},
{
label: () => i18n.t('Menu.RequestFeatureViaGithub'),
label: () => i18n.t("Menu.RequestFeatureViaGithub"),
click: async () => {
await shell.openExternal('https://github.com/tiddly-gittly/TidGi-desktop/issues/new?template=feature.md&title=feature%3A+');
await shell.openExternal(
"https://github.com/tiddly-gittly/TidGi-desktop/issues/new?template=feature.md&title=feature%3A+",
);
},
},
{
label: () => i18n.t('Menu.LearnMore'),
label: () => i18n.t("Menu.LearnMore"),
click: async () => {
await shell.openExternal('https://github.com/tiddly-gittly/TidGi-desktop/');
await shell.openExternal(
"https://github.com/tiddly-gittly/TidGi-desktop/",
);
},
},
],

View file

@ -77,6 +77,21 @@ function getInfoTiddlerFields(updateInfoTiddlersCallback: (infos: Array<{ text:
asyncInfoTiddlerFields.push({ title: '$:/info/tidgi/tokenAuth', text: mapBoolean(tokenAuth) }, { title: '$:/info/tidgi/enableHTTPAPI', text: mapBoolean(enableHTTPAPI) });
// Add memeloop node URL for mobile sync — mobile now connects to memeloop node, not TiddlyWiki HTTP server
try {
const memeloopStatus = await tidgiService.memeloopNode.getServerStatus();
if (memeloopStatus.running && memeloopStatus.port) {
const memeloopUrl = await tidgiService.native.getLocalHostUrlWithActualInfo(
getDefaultHTTPServerIP(memeloopStatus.port),
workspaceID,
);
const memeloopUrlObject = new URL(memeloopUrl);
asyncInfoTiddlerFields.push({ title: '$:/info/tidgi/memeloopNodeUrl', text: memeloopUrlObject.origin });
}
} catch {
// non-fatal: memeloop node may not be available
}
// Add workspace name for QR code
if (workspaceName) {
asyncInfoTiddlerFields.push({ title: '$:/info/tidgi/workspaceName', text: workspaceName });

View file

@ -1,44 +1,59 @@
import type { CreateWorkspaceTabs } from '@/windows/AddWorkspace/constants';
import type { PreferenceSections } from '@services/preferences/interface';
import { IWorkspace } from '@services/workspaces/interface';
import type { CreateWorkspaceTabs } from "@/windows/AddWorkspace/constants";
import type { PreferenceSections } from "@services/preferences/interface";
import { IWorkspace } from "@services/workspaces/interface";
export enum WindowNames {
about = 'about',
addWorkspace = 'addWorkspace',
about = "about",
addWorkspace = "addWorkspace",
/**
* Open any website URL, this is a popup window that user can open a help resource.
*/
any = 'any',
auth = 'auth',
editWorkspace = 'editWorkspace',
any = "any",
auth = "auth",
editWorkspace = "editWorkspace",
/**
* Git history viewer window
*/
gitHistory = 'gitHistory',
gitHistory = "gitHistory",
/**
* Window with workspace list and new wiki button on left side bar
* We only have a single instance of main window, that is the app window.
*/
main = 'main',
tidgiMiniWindow = 'tidgiMiniWindow',
notifications = 'notifications',
preferences = 'preferences',
main = "main",
tidgiMiniWindow = "tidgiMiniWindow",
notifications = "notifications",
preferences = "preferences",
/**
* Remote terminal viewer window
*/
remoteTerminal = "remoteTerminal",
/**
* Node management window for discovered/connected nodes
*/
nodeManagement = "nodeManagement",
/**
* Subscription management window for memeloop cloud
*/
subscription = "subscription",
/**
* Second wiki window in a popup window.
*/
secondary = 'secondary',
spellcheck = 'spellcheck',
secondary = "secondary",
spellcheck = "spellcheck",
/**
* browserView that loads the wiki webpage
* We will have multiple view window, each main workspace will have one.
*/
view = 'view',
view = "view",
}
/**
* Width height of windows
*/
export const windowDimension: Record<WindowNames, { height?: number; width?: number }> = {
export const windowDimension: Record<
WindowNames,
{ height?: number; width?: number }
> = {
[WindowNames.main]: {
width: 1200,
height: 768,
@ -83,6 +98,18 @@ export const windowDimension: Record<WindowNames, { height?: number; width?: num
width: 840,
height: 700,
},
[WindowNames.remoteTerminal]: {
width: 1400,
height: 900,
},
[WindowNames.nodeManagement]: {
width: 1200,
height: 800,
},
[WindowNames.subscription]: {
width: 900,
height: 700,
},
[WindowNames.notifications]: {
width: 400,
height: 585,
@ -113,11 +140,16 @@ export interface WindowMeta {
[WindowNames.tidgiMiniWindow]: undefined;
[WindowNames.notifications]: undefined;
[WindowNames.preferences]: IPreferenceWindowMeta;
[WindowNames.remoteTerminal]: { nodeId?: string };
[WindowNames.nodeManagement]: undefined;
[WindowNames.subscription]: undefined;
[WindowNames.spellcheck]: undefined;
[WindowNames.secondary]: undefined;
[WindowNames.view]: IBrowserViewMetaData;
}
export type IPossibleWindowMeta<M extends WindowMeta[WindowNames] = WindowMeta[WindowNames.main]> = {
export type IPossibleWindowMeta<
M extends WindowMeta[WindowNames] = WindowMeta[WindowNames.main],
> = {
windowName: WindowNames;
} & M;

View file

@ -1,28 +1,31 @@
import type { IAgentBrowserService } from '@services/agentBrowser/interface';
import type { IAgentDefinitionService } from '@services/agentDefinition/interface';
import type { IAgentInstanceService } from '@services/agentInstance/interface';
import type { IAuthenticationService } from '@services/auth/interface';
import type { IContextService } from '@services/context/interface';
import type { IDatabaseService } from '@services/database/interface';
import type { IDeepLinkService } from '@services/deepLink/interface';
import type { IGitService } from '@services/git/interface';
import type { IGitServerService } from '@services/gitServer/interface';
import type { IMenuService } from '@services/menu/interface';
import type { INativeService } from '@services/native/interface';
import type { INotificationService } from '@services/notifications/interface';
import type { IPreferenceService } from '@services/preferences/interface';
import type { IProviderRegistryService } from '@services/providerRegistry/interface';
import type { ISyncService } from '@services/sync/interface';
import type { ISystemPreferenceService } from '@services/systemPreferences/interface';
import type { IThemeService } from '@services/theme/interface';
import type { IUpdaterService } from '@services/updater/interface';
import type { IViewService } from '@services/view/interface';
import type { IWikiService } from '@services/wiki/interface';
import type { IWikiEmbeddingService } from '@services/wikiEmbedding/interface';
import type { IWikiGitWorkspaceService } from '@services/wikiGitWorkspace/interface';
import type { IWindowService } from '@services/windows/interface';
import type { IWorkspaceService } from '@services/workspaces/interface';
import type { IWorkspaceViewService } from '@services/workspacesView/interface';
import type { IAgentBrowserService } from "@services/agentBrowser/interface";
import type { IAgentDefinitionService } from "@services/agentDefinition/interface";
import type { IAgentInstanceService } from "@services/agentInstance/interface";
import type { IAuthenticationService } from "@services/auth/interface";
import type { IContextService } from "@services/context/interface";
import type { IDatabaseService } from "@services/database/interface";
import type { IDeepLinkService } from "@services/deepLink/interface";
import type { IGitService } from "@services/git/interface";
import type { IGitServerService } from "@services/gitServer/interface";
import type { IMenuService } from "@services/menu/interface";
import type { IMemeloopNodeService } from "@services/memeloopNode/interface";
import type { INativeService } from "@services/native/interface";
import type { INotificationService } from "@services/notifications/interface";
import type { IPreferenceService } from "@services/preferences/interface";
import type { IProviderRegistryService } from "@services/providerRegistry/interface";
import type { IRemoteTerminalService } from "@services/remoteTerminal/interface";
import type { ISyncService } from "@services/sync/interface";
import type { ISystemPreferenceService } from "@services/systemPreferences/interface";
import type { IThemeService } from "@services/theme/interface";
import type { IToolPermissionsService } from "@services/toolPermissions/interface";
import type { IUpdaterService } from "@services/updater/interface";
import type { IViewService } from "@services/view/interface";
import type { IWikiService } from "@services/wiki/interface";
import type { IWikiEmbeddingService } from "@services/wikiEmbedding/interface";
import type { IWikiGitWorkspaceService } from "@services/wikiGitWorkspace/interface";
import type { IWindowService } from "@services/windows/interface";
import type { IWorkspaceService } from "@services/workspaces/interface";
import type { IWorkspaceViewService } from "@services/workspacesView/interface";
export type TidgiService = {
agentBrowser: IAgentBrowserService;
@ -36,12 +39,15 @@ export type TidgiService = {
git: IGitService;
gitServer?: IGitServerService;
menu: IMenuService;
memeloopNode: IMemeloopNodeService;
native: INativeService;
notification: INotificationService;
preference: IPreferenceService;
remoteTerminal: IRemoteTerminalService;
sync: ISyncService;
systemPreference: ISystemPreferenceService;
theme: IThemeService;
toolPermissions: IToolPermissionsService;
updater: IUpdaterService;
view: IViewService;
wiki: IWikiService;
@ -52,7 +58,7 @@ export type TidgiService = {
workspaceView: IWorkspaceViewService;
};
declare module 'tiddlywiki' {
declare module "tiddlywiki" {
interface ITiddlyWiki {
tidgi: {
service: TidgiService;

View file

@ -1,16 +1,18 @@
import { HelmetProvider } from '@dr.pogodin/react-helmet';
import { WindowNames } from '@services/windows/WindowProperties';
import { lazy, useEffect } from 'react';
import { Route, Switch, useLocation } from 'wouter';
import { HelmetProvider } from "@dr.pogodin/react-helmet";
import { WindowNames } from "@services/windows/WindowProperties";
import { lazy, useEffect } from "react";
import { Route, Switch, useLocation } from "wouter";
const AboutPage = lazy(() => import('./About'));
const DialogAddWorkspace = lazy(() => import('./AddWorkspace'));
const EditWorkspace = lazy(() => import('./EditWorkspace'));
const GitHistory = lazy(() => import('./GitLog'));
const Main = lazy(() => import('../pages/Main'));
const DialogNotifications = lazy(() => import('./Notifications'));
const DialogPreferences = lazy(() => import('./Preferences'));
const SpellcheckLanguages = lazy(() => import('./SpellcheckLanguages'));
const AboutPage = lazy(() => import("./About"));
const DialogAddWorkspace = lazy(() => import("./AddWorkspace"));
const EditWorkspace = lazy(() => import("./EditWorkspace"));
const GitHistory = lazy(() => import("./GitLog"));
const Main = lazy(() => import("../pages/Main"));
const DialogNotifications = lazy(() => import("./Notifications"));
const DialogPreferences = lazy(() => import("./Preferences"));
const RemoteTerminal = lazy(() => import("./RemoteTerminal"));
const NodeManagement = lazy(() => import("./NodeManagement"));
const SpellcheckLanguages = lazy(() => import("./SpellcheckLanguages"));
export function Pages(): React.JSX.Element {
const [location, setLocation] = useLocation();
@ -27,14 +29,37 @@ export function Pages(): React.JSX.Element {
<HelmetProvider>
<Switch>
<Route path={`/${WindowNames.about}`} component={AboutPage} />
<Route path={`/${WindowNames.addWorkspace}`} component={DialogAddWorkspace} />
<Route path={`/${WindowNames.editWorkspace}`} component={EditWorkspace} />
<Route
path={`/${WindowNames.addWorkspace}`}
component={DialogAddWorkspace}
/>
<Route
path={`/${WindowNames.editWorkspace}`}
component={EditWorkspace}
/>
<Route path={`/${WindowNames.gitHistory}`} component={GitHistory} />
<Route path={`/${WindowNames.notifications}`} component={DialogNotifications} />
<Route path={`/${WindowNames.preferences}`} component={DialogPreferences} />
<Route path={`/${WindowNames.spellcheck}`} component={SpellcheckLanguages} />
<Route
path={`/${WindowNames.notifications}`}
component={DialogNotifications}
/>
<Route
path={`/${WindowNames.preferences}`}
component={DialogPreferences}
/>
<Route
path={`/${WindowNames.remoteTerminal}`}
component={RemoteTerminal}
/>
<Route
path={`/${WindowNames.nodeManagement}`}
component={NodeManagement}
/>
<Route
path={`/${WindowNames.spellcheck}`}
component={SpellcheckLanguages}
/>
<Route path={`/${WindowNames.main}`} component={Main} nest />
<Route path='/' component={Main} nest />
<Route path="/" component={Main} nest />
</Switch>
</HelmetProvider>
);