mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 21:38:44 +08:00
refactor: new project struct (#8199)
packages/frontend/web -> packages/frontend/apps/web packages/frontend/mobile -> packages/frontend/apps/mobile packages/frontend/electron -> packages/frontend/apps/electron
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
export interface AuthenticationRequest {
|
||||
method: 'magic-link' | 'oauth';
|
||||
payload: Record<string, any>;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Menu } from 'electron';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import {
|
||||
addTab,
|
||||
closeTab,
|
||||
reloadView,
|
||||
type TabAction,
|
||||
WebContentViewsManager,
|
||||
} from './tab-views';
|
||||
|
||||
export const showTabContextMenu = async (tabId: string, viewIndex: number) => {
|
||||
const workbenches = WebContentViewsManager.instance.tabViewsMeta.workbenches;
|
||||
const tabMeta = workbenches.find(w => w.id === tabId);
|
||||
if (!tabMeta) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { resolve, promise } = Promise.withResolvers<TabAction | null>();
|
||||
|
||||
const template: Parameters<typeof Menu.buildFromTemplate>[0] = [
|
||||
tabMeta.pinned
|
||||
? {
|
||||
label: 'Unpin tab',
|
||||
click: () => {
|
||||
WebContentViewsManager.instance.pinTab(tabId, false);
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: 'Pin tab',
|
||||
click: () => {
|
||||
WebContentViewsManager.instance.pinTab(tabId, true);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Refresh tab',
|
||||
click: () => {
|
||||
reloadView().catch(logger.error);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Duplicate tab',
|
||||
click: () => {
|
||||
addTab({
|
||||
basename: tabMeta.basename,
|
||||
view: tabMeta.views,
|
||||
show: false,
|
||||
}).catch(logger.error);
|
||||
},
|
||||
},
|
||||
|
||||
{ type: 'separator' },
|
||||
|
||||
tabMeta.views.length > 1
|
||||
? {
|
||||
label: 'Separate tabs',
|
||||
click: () => {
|
||||
WebContentViewsManager.instance.separateView(tabId, viewIndex);
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: 'Open in split view',
|
||||
click: () => {
|
||||
WebContentViewsManager.instance.openInSplitView({ tabId });
|
||||
},
|
||||
},
|
||||
|
||||
...(workbenches.length > 1
|
||||
? ([
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Close tab',
|
||||
click: () => {
|
||||
closeTab(tabId).catch(logger.error);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Close other tabs',
|
||||
click: () => {
|
||||
const tabsToRetain =
|
||||
WebContentViewsManager.instance.tabViewsMeta.workbenches.filter(
|
||||
w => w.id === tabId || w.pinned
|
||||
);
|
||||
|
||||
WebContentViewsManager.instance.patchTabViewsMeta({
|
||||
workbenches: tabsToRetain,
|
||||
activeWorkbenchId: tabId,
|
||||
});
|
||||
},
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
];
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
menu.popup();
|
||||
// eslint-disable-next-line prefer-const
|
||||
let unsub: (() => void) | undefined;
|
||||
const subscription = WebContentViewsManager.instance.tabAction$.subscribe(
|
||||
action => {
|
||||
resolve(action);
|
||||
unsub?.();
|
||||
}
|
||||
);
|
||||
menu.on('menu-will-close', () => {
|
||||
setTimeout(() => {
|
||||
resolve(null);
|
||||
unsub?.();
|
||||
});
|
||||
});
|
||||
unsub = () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
return promise;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { BrowserWindow, type Display, screen } from 'electron';
|
||||
|
||||
import { isMacOS } from '../../shared/utils';
|
||||
import { customThemeViewUrl } from '../constants';
|
||||
import { logger } from '../logger';
|
||||
|
||||
let customThemeWindow: Promise<BrowserWindow> | undefined;
|
||||
|
||||
const getScreenSize = (display: Display) => {
|
||||
const { width, height } = isMacOS() ? display.bounds : display.workArea;
|
||||
return { width, height };
|
||||
};
|
||||
|
||||
async function createCustomThemeWindow(additionalArguments: string[]) {
|
||||
logger.info('creating custom theme window');
|
||||
|
||||
const { width: maxWidth, height: maxHeight } = getScreenSize(
|
||||
screen.getPrimaryDisplay()
|
||||
);
|
||||
|
||||
const browserWindow = new BrowserWindow({
|
||||
width: Math.min(maxWidth, 800),
|
||||
height: Math.min(maxHeight, 600),
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
webPreferences: {
|
||||
webgl: true,
|
||||
preload: join(__dirname, './preload.js'),
|
||||
additionalArguments: additionalArguments,
|
||||
},
|
||||
});
|
||||
|
||||
await browserWindow.loadURL(customThemeViewUrl);
|
||||
|
||||
browserWindow.on('closed', () => {
|
||||
customThemeWindow = undefined;
|
||||
});
|
||||
|
||||
return browserWindow;
|
||||
}
|
||||
|
||||
const getWindowAdditionalArguments = async () => {
|
||||
const { getExposedMeta } = await import('../exposed');
|
||||
const mainExposedMeta = getExposedMeta();
|
||||
return [
|
||||
`--main-exposed-meta=` + JSON.stringify(mainExposedMeta),
|
||||
`--window-name=theme-editor`,
|
||||
];
|
||||
};
|
||||
|
||||
export async function getOrCreateCustomThemeWindow() {
|
||||
const additionalArguments = await getWindowAdditionalArguments();
|
||||
if (
|
||||
!customThemeWindow ||
|
||||
(await customThemeWindow.then(w => w.isDestroyed()))
|
||||
) {
|
||||
customThemeWindow = createCustomThemeWindow(additionalArguments);
|
||||
}
|
||||
|
||||
return customThemeWindow;
|
||||
}
|
||||
|
||||
export async function getCustomThemeWindow() {
|
||||
if (!customThemeWindow) return;
|
||||
const window = await customThemeWindow;
|
||||
if (window.isDestroyed()) return;
|
||||
return window;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './authentication';
|
||||
export * from './launcher';
|
||||
export * from './main-window';
|
||||
export * from './onboarding';
|
||||
export * from './stage';
|
||||
export * from './tab-views';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,24 @@
|
||||
import { logger } from '../logger';
|
||||
import { initAndShowMainWindow } from './main-window';
|
||||
import { getOnboardingWindow, getOrCreateOnboardingWindow } from './onboarding';
|
||||
import { launchStage } from './stage';
|
||||
|
||||
/**
|
||||
* Launch app depending on launch stage
|
||||
*/
|
||||
export async function launch() {
|
||||
const stage = launchStage.value;
|
||||
if (stage === 'main') {
|
||||
initAndShowMainWindow().catch(e => {
|
||||
logger.error('Failed to restore or create window:', e);
|
||||
});
|
||||
|
||||
getOnboardingWindow()
|
||||
.then(w => w?.destroy())
|
||||
.catch(e => logger.error('Failed to destroy onboarding window:', e));
|
||||
}
|
||||
if (stage === 'onboarding')
|
||||
getOrCreateOnboardingWindow().catch(e => {
|
||||
logger.error('Failed to restore or create onboarding window:', e);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { BrowserWindow, nativeTheme } from 'electron';
|
||||
import electronWindowState from 'electron-window-state';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { isLinux, isMacOS, isWindows } from '../../shared/utils';
|
||||
import { buildType } from '../config';
|
||||
import { mainWindowOrigin } from '../constants';
|
||||
import { ensureHelperProcess } from '../helper-process';
|
||||
import { logger } from '../logger';
|
||||
import { uiSubjects } from '../ui/subject';
|
||||
|
||||
const IS_DEV: boolean =
|
||||
process.env.NODE_ENV === 'development' && !process.env.CI;
|
||||
|
||||
function closeAllWindows() {
|
||||
BrowserWindow.getAllWindows().forEach(w => {
|
||||
if (!w.isDestroyed()) {
|
||||
w.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class MainWindowManager {
|
||||
static readonly instance = new MainWindowManager();
|
||||
mainWindowReady: Promise<BrowserWindow> | undefined;
|
||||
mainWindow$ = new BehaviorSubject<BrowserWindow | undefined>(undefined);
|
||||
|
||||
private hiddenMacWindow: BrowserWindow | undefined;
|
||||
|
||||
get mainWindow() {
|
||||
return this.mainWindow$.value;
|
||||
}
|
||||
|
||||
// #region private methods
|
||||
private preventMacAppQuit() {
|
||||
if (!this.hiddenMacWindow && isMacOS()) {
|
||||
this.hiddenMacWindow = new BrowserWindow({
|
||||
show: false,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
this.hiddenMacWindow.on('close', () => {
|
||||
this.cleanupWindows();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupWindows() {
|
||||
closeAllWindows();
|
||||
this.mainWindowReady = undefined;
|
||||
this.mainWindow$.next(undefined);
|
||||
this.hiddenMacWindow?.destroy();
|
||||
this.hiddenMacWindow = undefined;
|
||||
}
|
||||
|
||||
private async createMainWindow() {
|
||||
logger.info('create window');
|
||||
const mainWindowState = electronWindowState({
|
||||
defaultWidth: 1000,
|
||||
defaultHeight: 800,
|
||||
});
|
||||
|
||||
await ensureHelperProcess();
|
||||
|
||||
const browserWindow = new BrowserWindow({
|
||||
titleBarStyle: isMacOS()
|
||||
? 'hiddenInset'
|
||||
: isWindows()
|
||||
? 'hidden'
|
||||
: 'default',
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
autoHideMenuBar: isLinux(),
|
||||
minWidth: 640,
|
||||
minHeight: 480,
|
||||
visualEffectState: 'active',
|
||||
vibrancy: 'under-window',
|
||||
// backgroundMaterial: 'mica',
|
||||
height: mainWindowState.height,
|
||||
show: false, // Use 'ready-to-show' event to show window
|
||||
webPreferences: {
|
||||
webgl: true,
|
||||
contextIsolation: true,
|
||||
sandbox: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (isLinux()) {
|
||||
browserWindow.setIcon(
|
||||
// __dirname is `packages/frontend/apps/electron/dist` (the bundled output directory)
|
||||
join(__dirname, `../resources/icons/icon_${buildType}_64x64.png`)
|
||||
);
|
||||
}
|
||||
|
||||
nativeTheme.themeSource = 'light';
|
||||
mainWindowState.manage(browserWindow);
|
||||
|
||||
this.bindEvents(browserWindow);
|
||||
return browserWindow;
|
||||
}
|
||||
|
||||
private bindEvents(mainWindow: BrowserWindow) {
|
||||
/**
|
||||
* If you install `show: true` then it can cause issues when trying to close the window.
|
||||
* Use `show: false` and listener events `ready-to-show` to fix these issues.
|
||||
*
|
||||
* @see https://github.com/electron/electron/issues/25012
|
||||
*/
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
logger.info('main window is ready to show');
|
||||
|
||||
uiSubjects.onMaximized$.next(mainWindow.isMaximized());
|
||||
uiSubjects.onFullScreen$.next(mainWindow.isFullScreen());
|
||||
});
|
||||
|
||||
mainWindow.on('close', e => {
|
||||
// TODO(@pengx17): gracefully close the app, for example, ask user to save unsaved changes
|
||||
e.preventDefault();
|
||||
if (!isMacOS()) {
|
||||
closeAllWindows();
|
||||
} else {
|
||||
// hide window on macOS
|
||||
// application quit will be handled by closing the hidden window
|
||||
//
|
||||
// explanation:
|
||||
// - closing the top window (by clicking close button or CMD-w)
|
||||
// - will be captured in "close" event here
|
||||
// - hiding the app to make the app open faster when user click the app icon
|
||||
// - quit the app by "cmd+q" or right click on the dock icon and select "quit"
|
||||
// - all browser windows will capture the "close" event
|
||||
// - the hidden window will close all windows
|
||||
// - "window-all-closed" event will be emitted and eventually quit the app
|
||||
if (mainWindow.isFullScreen()) {
|
||||
mainWindow.once('leave-full-screen', () => {
|
||||
mainWindow.hide();
|
||||
});
|
||||
mainWindow.setFullScreen(false);
|
||||
} else {
|
||||
mainWindow.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const refreshBound = (timeout = 0) => {
|
||||
setTimeout(() => {
|
||||
// FIXME: workaround for theme bug in full screen mode
|
||||
const size = mainWindow.getSize();
|
||||
mainWindow.setSize(size[0] + 1, size[1] + 1);
|
||||
mainWindow.setSize(size[0], size[1]);
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
mainWindow.on('leave-full-screen', () => {
|
||||
// seems call this too soon may cause the app to crash
|
||||
refreshBound();
|
||||
refreshBound(1000);
|
||||
uiSubjects.onMaximized$.next(false);
|
||||
uiSubjects.onFullScreen$.next(false);
|
||||
});
|
||||
|
||||
mainWindow.on('maximize', () => {
|
||||
uiSubjects.onMaximized$.next(true);
|
||||
});
|
||||
|
||||
mainWindow.on('unmaximize', () => {
|
||||
uiSubjects.onMaximized$.next(false);
|
||||
});
|
||||
|
||||
// full-screen == maximized in UI on windows
|
||||
mainWindow.on('enter-full-screen', () => {
|
||||
uiSubjects.onFullScreen$.next(true);
|
||||
});
|
||||
|
||||
mainWindow.on('leave-full-screen', () => {
|
||||
uiSubjects.onFullScreen$.next(false);
|
||||
});
|
||||
}
|
||||
// #endregion
|
||||
|
||||
async ensureMainWindow(): Promise<BrowserWindow> {
|
||||
if (
|
||||
!this.mainWindowReady ||
|
||||
(await this.mainWindowReady.then(w => w.isDestroyed()))
|
||||
) {
|
||||
this.mainWindowReady = this.createMainWindow();
|
||||
this.mainWindow$.next(await this.mainWindowReady);
|
||||
this.preventMacAppQuit();
|
||||
}
|
||||
return this.mainWindowReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init main BrowserWindow. Will create a new window if it's not created yet.
|
||||
*/
|
||||
async initAndShowMainWindow() {
|
||||
const mainWindow = await this.ensureMainWindow();
|
||||
|
||||
if (IS_DEV) {
|
||||
// do not gain focus in dev mode
|
||||
mainWindow.showInactive();
|
||||
} else {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
this.preventMacAppQuit();
|
||||
|
||||
return mainWindow;
|
||||
}
|
||||
}
|
||||
|
||||
export async function initAndShowMainWindow() {
|
||||
return MainWindowManager.instance.initAndShowMainWindow();
|
||||
}
|
||||
|
||||
export async function getMainWindow() {
|
||||
return MainWindowManager.instance.ensureMainWindow();
|
||||
}
|
||||
|
||||
export async function showMainWindow() {
|
||||
const window = await getMainWindow();
|
||||
if (!window) return;
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
}
|
||||
window.focus();
|
||||
}
|
||||
|
||||
const getWindowAdditionalArguments = async () => {
|
||||
const { getExposedMeta } = await import('../exposed');
|
||||
const mainExposedMeta = getExposedMeta();
|
||||
return [
|
||||
`--main-exposed-meta=` + JSON.stringify(mainExposedMeta),
|
||||
`--window-name=hidden-window`,
|
||||
];
|
||||
};
|
||||
|
||||
function transformToAppUrl(url: URL) {
|
||||
const params = url.searchParams;
|
||||
return mainWindowOrigin + url.pathname + '?' + params.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a URL in a hidden window.
|
||||
*/
|
||||
export async function openUrlInHiddenWindow(urlObj: URL) {
|
||||
const url = transformToAppUrl(urlObj);
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, './preload.js'),
|
||||
additionalArguments: await getWindowAdditionalArguments(),
|
||||
},
|
||||
show: environment.isDebug,
|
||||
});
|
||||
|
||||
if (environment.isDebug) {
|
||||
win.webContents.openDevTools({
|
||||
mode: 'detach',
|
||||
});
|
||||
}
|
||||
|
||||
win.on('close', e => {
|
||||
e.preventDefault();
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.destroy();
|
||||
}
|
||||
});
|
||||
logger.info('loading page at', url);
|
||||
win.loadURL(url).catch(e => {
|
||||
logger.error('failed to load url', e);
|
||||
});
|
||||
return win;
|
||||
}
|
||||
|
||||
// TODO(@pengx17): somehow the page won't load the url passed, help needed
|
||||
export async function openUrlInMainWindow(urlObj: URL) {
|
||||
const url = transformToAppUrl(urlObj);
|
||||
logger.info('loading page at', url);
|
||||
const mainWindow = await getMainWindow();
|
||||
if (mainWindow) {
|
||||
await mainWindow.loadURL(url);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import type { Display } from 'electron';
|
||||
import { BrowserWindow, screen } from 'electron';
|
||||
|
||||
import { isMacOS } from '../../shared/utils';
|
||||
import { onboardingViewUrl } from '../constants';
|
||||
// import { getExposedMeta } from './exposed';
|
||||
import { logger } from '../logger';
|
||||
|
||||
const getScreenSize = (display: Display) => {
|
||||
const { width, height } = isMacOS() ? display.bounds : display.workArea;
|
||||
return { width, height };
|
||||
};
|
||||
|
||||
// todo: not all window need all of the exposed meta
|
||||
const getWindowAdditionalArguments = async () => {
|
||||
const { getExposedMeta } = await import('../exposed');
|
||||
const mainExposedMeta = getExposedMeta();
|
||||
return [
|
||||
`--main-exposed-meta=` + JSON.stringify(mainExposedMeta),
|
||||
`--window-name=onboarding`,
|
||||
];
|
||||
};
|
||||
|
||||
function fullscreenAndCenter(browserWindow: BrowserWindow) {
|
||||
const position = browserWindow.getPosition();
|
||||
const size = browserWindow.getSize();
|
||||
const currentDisplay = screen.getDisplayNearestPoint({
|
||||
x: position[0] + size[0] / 2,
|
||||
y: position[1] + size[1] / 2,
|
||||
});
|
||||
if (!currentDisplay) return;
|
||||
const { width, height } = getScreenSize(currentDisplay);
|
||||
browserWindow.setSize(width, height);
|
||||
browserWindow.center();
|
||||
}
|
||||
|
||||
async function createOnboardingWindow(additionalArguments: string[]) {
|
||||
logger.info('creating onboarding window');
|
||||
|
||||
// get user's screen size
|
||||
const { width, height } = getScreenSize(screen.getPrimaryDisplay());
|
||||
|
||||
const browserWindow = new BrowserWindow({
|
||||
width,
|
||||
height,
|
||||
frame: false,
|
||||
show: false,
|
||||
resizable: false,
|
||||
closable: false,
|
||||
minimizable: false,
|
||||
movable: false,
|
||||
titleBarStyle: 'hidden',
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
// skipTaskbar: true,
|
||||
transparent: true,
|
||||
hasShadow: false,
|
||||
roundedCorners: false,
|
||||
webPreferences: {
|
||||
webgl: true,
|
||||
preload: join(__dirname, './preload.js'),
|
||||
additionalArguments: additionalArguments,
|
||||
},
|
||||
});
|
||||
|
||||
// workaround for the phantom title bar on windows when losing focus
|
||||
// see https://github.com/electron/electron/issues/39959#issuecomment-1758736966
|
||||
browserWindow.on('focus', () => {
|
||||
browserWindow.setBackgroundColor('#00000000');
|
||||
});
|
||||
|
||||
browserWindow.on('blur', () => {
|
||||
browserWindow.setBackgroundColor('#00000000');
|
||||
});
|
||||
|
||||
browserWindow.on('ready-to-show', () => {
|
||||
// forcing zoom factor to 1 to avoid onboarding display issues
|
||||
browserWindow.webContents.setZoomFactor(1);
|
||||
fullscreenAndCenter(browserWindow);
|
||||
// TODO(@catsjuice): add a timeout to avoid flickering, window is ready, but dom is not ready
|
||||
setTimeout(() => {
|
||||
browserWindow.show();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// When moved to another screen, resize to fit the screen
|
||||
browserWindow.on('moved', () => {
|
||||
fullscreenAndCenter(browserWindow);
|
||||
});
|
||||
|
||||
await browserWindow.loadURL(onboardingViewUrl);
|
||||
|
||||
return browserWindow;
|
||||
}
|
||||
|
||||
let onBoardingWindow: Promise<BrowserWindow> | undefined;
|
||||
|
||||
export async function getOrCreateOnboardingWindow() {
|
||||
const additionalArguments = await getWindowAdditionalArguments();
|
||||
if (
|
||||
!onBoardingWindow ||
|
||||
(await onBoardingWindow.then(w => w.isDestroyed()))
|
||||
) {
|
||||
onBoardingWindow = createOnboardingWindow(additionalArguments);
|
||||
}
|
||||
|
||||
return onBoardingWindow;
|
||||
}
|
||||
|
||||
export async function getOnboardingWindow() {
|
||||
if (!onBoardingWindow) return;
|
||||
const window = await onBoardingWindow;
|
||||
if (window.isDestroyed()) return;
|
||||
return window;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { persistentConfig } from '../config-storage/persist';
|
||||
import type { LaunchStage } from './types';
|
||||
|
||||
export const launchStage: { value: LaunchStage } = {
|
||||
value: persistentConfig.get('onBoarding') ? 'onboarding' : 'main',
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const workbenchViewIconNameSchema = z.enum([
|
||||
'trash',
|
||||
'allDocs',
|
||||
'collection',
|
||||
'tag',
|
||||
'doc', // refers to a doc whose mode is not yet being resolved
|
||||
'page',
|
||||
'edgeless',
|
||||
'journal',
|
||||
]);
|
||||
|
||||
export const workbenchViewMetaSchema = z.object({
|
||||
id: z.string(),
|
||||
path: z
|
||||
.object({
|
||||
pathname: z.string().optional(),
|
||||
hash: z.string().optional(),
|
||||
search: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
// todo: move title/module to cached stated
|
||||
title: z.string().optional(),
|
||||
iconName: workbenchViewIconNameSchema.optional(),
|
||||
});
|
||||
|
||||
export const workbenchMetaSchema = z.object({
|
||||
id: z.string(),
|
||||
activeViewIndex: z.number(),
|
||||
pinned: z.boolean().optional(),
|
||||
basename: z.string(),
|
||||
views: z.array(workbenchViewMetaSchema),
|
||||
});
|
||||
|
||||
export const tabViewsMetaSchema = z.object({
|
||||
activeWorkbenchId: z.string().optional(),
|
||||
workbenches: z.array(workbenchMetaSchema).default([]),
|
||||
});
|
||||
|
||||
export const TabViewsMetaKey = 'tabViewsMetaSchema';
|
||||
export type TabViewsMetaSchema = z.infer<typeof tabViewsMetaSchema>;
|
||||
export type WorkbenchMeta = z.infer<typeof workbenchMetaSchema>;
|
||||
export type WorkbenchViewMeta = z.infer<typeof workbenchViewMetaSchema>;
|
||||
export type WorkbenchViewModule = z.infer<typeof workbenchViewIconNameSchema>;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
export type LaunchStage = 'main' | 'onboarding';
|
||||
Reference in New Issue
Block a user