81 files changed
+753
-532
No files matched your search
@@ -73,6 +73,7 @@
|
|||||||
"async-call-rpc": "^6.4.2",
|
"async-call-rpc": "^6.4.2",
|
||||||
"electron-updater": "^6.2.1",
|
"electron-updater": "^6.2.1",
|
||||||
"link-preview-js": "^3.0.5",
|
"link-preview-js": "^3.0.5",
|
||||||
|
"next-themes": "^0.3.0",
|
||||||
"yjs": "patch:yjs@npm%3A13.6.18#~/.yarn/patches/yjs-npm-13.6.18-ad0d5f7c43.patch"
|
"yjs": "patch:yjs@npm%3A13.6.18#~/.yarn/patches/yjs-npm-13.6.18-ad0d5f7c43.patch"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { router } from '@affine/core/desktop/router';
|
|||||||
import { configureCommonModules } from '@affine/core/modules';
|
import { configureCommonModules } from '@affine/core/modules';
|
||||||
import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header';
|
import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header';
|
||||||
import { ValidatorProvider } from '@affine/core/modules/cloud';
|
import { ValidatorProvider } from '@affine/core/modules/cloud';
|
||||||
|
import {
|
||||||
|
configureDesktopApiModule,
|
||||||
|
DesktopApiService,
|
||||||
|
} from '@affine/core/modules/desktop-api';
|
||||||
|
import { configureFindInPageModule } from '@affine/core/modules/find-in-page';
|
||||||
import { I18nProvider } from '@affine/core/modules/i18n';
|
import { I18nProvider } from '@affine/core/modules/i18n';
|
||||||
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
|
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
|
||||||
import { CustomThemeModifier } from '@affine/core/modules/theme-editor';
|
import { CustomThemeModifier } from '@affine/core/modules/theme-editor';
|
||||||
@@ -21,7 +26,6 @@ import {
|
|||||||
configureSqliteWorkspaceEngineStorageProvider,
|
configureSqliteWorkspaceEngineStorageProvider,
|
||||||
} from '@affine/core/modules/workspace-engine';
|
} from '@affine/core/modules/workspace-engine';
|
||||||
import createEmotionCache from '@affine/core/utils/create-emotion-cache';
|
import createEmotionCache from '@affine/core/utils/create-emotion-cache';
|
||||||
import { apis, appInfo } from '@affine/electron-api';
|
|
||||||
import { CacheProvider } from '@emotion/react';
|
import { CacheProvider } from '@emotion/react';
|
||||||
import {
|
import {
|
||||||
Framework,
|
Framework,
|
||||||
@@ -32,6 +36,8 @@ import {
|
|||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { DesktopThemeSync } from './theme-sync';
|
||||||
|
|
||||||
const desktopWhiteList = [
|
const desktopWhiteList = [
|
||||||
'/open-app/signin-redirect',
|
'/open-app/signin-redirect',
|
||||||
'/open-app/url',
|
'/open-app/url',
|
||||||
@@ -64,26 +70,38 @@ configureSqliteWorkspaceEngineStorageProvider(framework);
|
|||||||
configureSqliteUserspaceStorageProvider(framework);
|
configureSqliteUserspaceStorageProvider(framework);
|
||||||
configureDesktopWorkbenchModule(framework);
|
configureDesktopWorkbenchModule(framework);
|
||||||
configureAppTabsHeaderModule(framework);
|
configureAppTabsHeaderModule(framework);
|
||||||
framework.impl(PopupWindowProvider, {
|
configureFindInPageModule(framework);
|
||||||
|
configureDesktopApiModule(framework);
|
||||||
|
|
||||||
|
framework.impl(PopupWindowProvider, p => {
|
||||||
|
const apis = p.get(DesktopApiService).api;
|
||||||
|
return {
|
||||||
open: (url: string) => {
|
open: (url: string) => {
|
||||||
apis?.ui.openExternal(url).catch(e => {
|
apis.handler.ui.openExternal(url).catch(e => {
|
||||||
console.error('Failed to open external URL', e);
|
console.error('Failed to open external URL', e);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
framework.impl(ClientSchemeProvider, {
|
framework.impl(ClientSchemeProvider, p => {
|
||||||
|
const appInfo = p.get(DesktopApiService).appInfo;
|
||||||
|
return {
|
||||||
getClientScheme() {
|
getClientScheme() {
|
||||||
return appInfo?.scheme;
|
return appInfo?.scheme;
|
||||||
},
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
framework.impl(ValidatorProvider, {
|
framework.impl(ValidatorProvider, p => {
|
||||||
|
const apis = p.get(DesktopApiService).api;
|
||||||
|
return {
|
||||||
async validate(_challenge, resource) {
|
async validate(_challenge, resource) {
|
||||||
const token = await apis?.ui?.getChallengeResponse(resource);
|
const token = await apis.handler.ui.getChallengeResponse(resource);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error('Challenge failed');
|
throw new Error('Challenge failed');
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
const frameworkProvider = framework.provider();
|
const frameworkProvider = framework.provider();
|
||||||
|
|
||||||
@@ -100,6 +118,7 @@ export function App() {
|
|||||||
<CacheProvider value={cache}>
|
<CacheProvider value={cache}>
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<AffineContext store={getCurrentStore()}>
|
<AffineContext store={getCurrentStore()}>
|
||||||
|
<DesktopThemeSync />
|
||||||
<Telemetry />
|
<Telemetry />
|
||||||
<CustomThemeModifier />
|
<CustomThemeModifier />
|
||||||
<GlobalLoading />
|
<GlobalLoading />
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
import './setup';
|
import './setup';
|
||||||
|
|
||||||
import { appConfigProxy } from '@affine/core/components/hooks/use-app-config-storage';
|
import { appConfigProxy } from '@affine/core/components/hooks/use-app-config-storage';
|
||||||
import { apis, appInfo, events } from '@affine/electron-api';
|
import { StrictMode } from 'react';
|
||||||
import {
|
|
||||||
init,
|
|
||||||
reactRouterV6BrowserTracingIntegration,
|
|
||||||
setTags,
|
|
||||||
} from '@sentry/react';
|
|
||||||
import { debounce } from 'lodash-es';
|
|
||||||
import { StrictMode, useEffect } from 'react';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import {
|
|
||||||
createRoutesFromChildren,
|
|
||||||
matchRoutes,
|
|
||||||
useLocation,
|
|
||||||
useNavigationType,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
|
|
||||||
import { App } from './app';
|
import { App } from './app';
|
||||||
|
|
||||||
@@ -26,82 +13,6 @@ function main() {
|
|||||||
.getSync()
|
.getSync()
|
||||||
.catch(() => console.error('failed to load app config'));
|
.catch(() => console.error('failed to load app config'));
|
||||||
|
|
||||||
// skip bootstrap setup for desktop onboarding
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
appInfo?.windowName === 'onboarding' ||
|
|
||||||
appInfo?.windowName === 'theme-editor'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (BUILD_CONFIG.debug || window.SENTRY_RELEASE) {
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/electron/
|
|
||||||
init({
|
|
||||||
dsn: process.env.SENTRY_DSN,
|
|
||||||
environment: process.env.BUILD_TYPE ?? 'development',
|
|
||||||
integrations: [
|
|
||||||
reactRouterV6BrowserTracingIntegration({
|
|
||||||
useEffect,
|
|
||||||
useLocation,
|
|
||||||
useNavigationType,
|
|
||||||
createRoutesFromChildren,
|
|
||||||
matchRoutes,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
setTags({
|
|
||||||
appVersion: BUILD_CONFIG.appVersion,
|
|
||||||
editorVersion: BUILD_CONFIG.editorVersion,
|
|
||||||
});
|
|
||||||
|
|
||||||
apis?.ui.handleNetworkChange(navigator.onLine);
|
|
||||||
window.addEventListener('offline', () => {
|
|
||||||
apis?.ui.handleNetworkChange(false);
|
|
||||||
});
|
|
||||||
window.addEventListener('online', () => {
|
|
||||||
apis?.ui.handleNetworkChange(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMaximized = (maximized: boolean | undefined) => {
|
|
||||||
document.documentElement.dataset.maximized = String(maximized);
|
|
||||||
};
|
|
||||||
const handleFullscreen = (fullscreen: boolean | undefined) => {
|
|
||||||
document.documentElement.dataset.fullscreen = String(fullscreen);
|
|
||||||
};
|
|
||||||
apis?.ui.isMaximized().then(handleMaximized).catch(console.error);
|
|
||||||
apis?.ui.isFullScreen().then(handleFullscreen).catch(console.error);
|
|
||||||
events?.ui.onMaximized(handleMaximized);
|
|
||||||
events?.ui.onFullScreen(handleFullscreen);
|
|
||||||
|
|
||||||
const tabId = appInfo?.viewId;
|
|
||||||
const handleActiveTabChange = (active: boolean) => {
|
|
||||||
document.documentElement.dataset.active = String(active);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (tabId) {
|
|
||||||
apis?.ui
|
|
||||||
.isActiveTab()
|
|
||||||
.then(active => {
|
|
||||||
handleActiveTabChange(active);
|
|
||||||
events?.ui.onActiveTabChanged(id => {
|
|
||||||
handleActiveTabChange(id === tabId);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleResize = debounce(() => {
|
|
||||||
apis?.ui.handleWindowResize().catch(console.error);
|
|
||||||
}, 50);
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
window.addEventListener('dragstart', () => {
|
|
||||||
document.documentElement.dataset.dragging = 'true';
|
|
||||||
});
|
|
||||||
window.addEventListener('dragend', () => {
|
|
||||||
document.documentElement.dataset.dragging = 'false';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
mountApp();
|
mountApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
AppTabsHeader,
|
AppTabsHeader,
|
||||||
configureAppTabsHeaderModule,
|
configureAppTabsHeaderModule,
|
||||||
} from '@affine/core/modules/app-tabs-header';
|
} from '@affine/core/modules/app-tabs-header';
|
||||||
|
import { configureDesktopApiModule } from '@affine/core/modules/desktop-api';
|
||||||
import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n';
|
import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n';
|
||||||
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
|
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
|
||||||
import { SplitViewFallback } from '@affine/core/modules/workbench/view/split-view/split-view';
|
import { SplitViewFallback } from '@affine/core/modules/workbench/view/split-view/split-view';
|
||||||
@@ -23,6 +24,7 @@ configureElectronStateStorageImpls(framework);
|
|||||||
configureAppTabsHeaderModule(framework);
|
configureAppTabsHeaderModule(framework);
|
||||||
configureAppSidebarModule(framework);
|
configureAppSidebarModule(framework);
|
||||||
configureI18nModule(framework);
|
configureI18nModule(framework);
|
||||||
|
configureDesktopApiModule(framework);
|
||||||
const frameworkProvider = framework.provider();
|
const frameworkProvider = framework.provider();
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
import './setup';
|
import './setup';
|
||||||
|
|
||||||
import { apis, events } from '@affine/electron-api';
|
import { events } from '@affine/electron-api';
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { App } from './app';
|
import { App } from './app';
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const handleMaximized = (maximized: boolean | undefined) => {
|
|
||||||
document.documentElement.dataset.maximized = String(maximized);
|
|
||||||
};
|
|
||||||
const handleFullscreen = (fullscreen: boolean | undefined) => {
|
|
||||||
document.documentElement.dataset.fullscreen = String(fullscreen);
|
|
||||||
};
|
|
||||||
const handleActive = (active: boolean | undefined) => {
|
const handleActive = (active: boolean | undefined) => {
|
||||||
document.documentElement.dataset.active = String(active);
|
document.documentElement.dataset.active = String(active);
|
||||||
};
|
};
|
||||||
|
|
||||||
apis?.ui.isMaximized().then(handleMaximized).catch(console.error);
|
|
||||||
apis?.ui.isFullScreen().then(handleFullscreen).catch(console.error);
|
|
||||||
events?.ui.onMaximized(handleMaximized);
|
|
||||||
events?.ui.onFullScreen(handleFullscreen);
|
|
||||||
events?.ui.onTabShellViewActiveChange(handleActive);
|
events?.ui.onTabShellViewActiveChange(handleActive);
|
||||||
|
|
||||||
mountApp();
|
mountApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
export const DesktopThemeSync = () => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const lastThemeRef = useRef(theme);
|
||||||
|
const onceRef = useRef(false);
|
||||||
|
|
||||||
|
const handler = useService(DesktopApiService).api.handler;
|
||||||
|
|
||||||
|
if (lastThemeRef.current !== theme || !onceRef.current) {
|
||||||
|
if (BUILD_CONFIG.isElectron && theme) {
|
||||||
|
handler.ui
|
||||||
|
.handleThemeChange(theme as 'dark' | 'light' | 'system')
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lastThemeRef.current = theme;
|
||||||
|
onceRef.current = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -56,6 +56,8 @@ export const appInfo = {
|
|||||||
scheme,
|
scheme,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AppInfo = typeof appInfo;
|
||||||
|
|
||||||
function getMainAPIs() {
|
function getMainAPIs() {
|
||||||
const meta: ExposedMeta = (() => {
|
const meta: ExposedMeta = (() => {
|
||||||
const val = process.argv
|
const val = process.argv
|
||||||
|
|||||||
@@ -94,3 +94,5 @@ export const sharedStorage = {
|
|||||||
globalState,
|
globalState,
|
||||||
globalCache,
|
globalCache,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SharedStorage = typeof sharedStorage;
|
||||||
@@ -1,33 +1,12 @@
|
|||||||
import { apis } from '@affine/electron-api';
|
import { ThemeProvider as NextThemeProvider } from 'next-themes';
|
||||||
import { ThemeProvider as NextThemeProvider, useTheme } from 'next-themes';
|
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { memo, useRef } from 'react';
|
|
||||||
|
|
||||||
const themes = ['dark', 'light'];
|
const themes = ['dark', 'light'];
|
||||||
|
|
||||||
const DesktopThemeSync = memo(function DesktopThemeSync() {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const lastThemeRef = useRef(theme);
|
|
||||||
const onceRef = useRef(false);
|
|
||||||
if (lastThemeRef.current !== theme || !onceRef.current) {
|
|
||||||
if (BUILD_CONFIG.isElectron && theme) {
|
|
||||||
apis?.ui
|
|
||||||
.handleThemeChange(theme as 'dark' | 'light' | 'system')
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
lastThemeRef.current = theme;
|
|
||||||
onceRef.current = true;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
export const ThemeProvider = ({ children }: PropsWithChildren) => {
|
||||||
return (
|
return (
|
||||||
<NextThemeProvider themes={themes} enableSystem={true}>
|
<NextThemeProvider themes={themes} enableSystem={true}>
|
||||||
{children}
|
{children}
|
||||||
<DesktopThemeSync />
|
|
||||||
</NextThemeProvider>
|
</NextThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -5,7 +5,7 @@ import { appSettingAtom } from '@toeverything/infra';
|
|||||||
import type { createStore } from 'jotai';
|
import type { createStore } from 'jotai';
|
||||||
import type { useTheme } from 'next-themes';
|
import type { useTheme } from 'next-themes';
|
||||||
|
|
||||||
import type { EditorSettingService } from '../modules/editor-settting';
|
import type { EditorSettingService } from '../modules/editor-setting';
|
||||||
import { registerAffineCommand } from './registry';
|
import { registerAffineCommand } from './registry';
|
||||||
|
|
||||||
export function registerAffineSettingsCommands({
|
export function registerAffineSettingsCommands({
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { notify } from '@affine/component';
|
import { notify } from '@affine/component';
|
||||||
import { updateReadyAtom } from '@affine/core/components/hooks/use-app-updater';
|
import { updateReadyAtom } from '@affine/core/components/hooks/use-app-updater';
|
||||||
import { apis } from '@affine/electron-api';
|
|
||||||
import type { useI18n } from '@affine/i18n';
|
import type { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { ResetIcon } from '@blocksuite/icons/rc';
|
import { ResetIcon } from '@blocksuite/icons/rc';
|
||||||
@@ -11,9 +10,11 @@ import { registerAffineCommand } from './registry';
|
|||||||
export function registerAffineUpdatesCommands({
|
export function registerAffineUpdatesCommands({
|
||||||
t,
|
t,
|
||||||
store,
|
store,
|
||||||
|
quitAndInstall,
|
||||||
}: {
|
}: {
|
||||||
t: ReturnType<typeof useI18n>;
|
t: ReturnType<typeof useI18n>;
|
||||||
store: ReturnType<typeof createStore>;
|
store: ReturnType<typeof createStore>;
|
||||||
|
quitAndInstall: () => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const unsubs: Array<() => void> = [];
|
const unsubs: Array<() => void> = [];
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ export function registerAffineUpdatesCommands({
|
|||||||
run() {
|
run() {
|
||||||
track.$.cmdk.updates.quitAndInstall();
|
track.$.cmdk.updates.quitAndInstall();
|
||||||
|
|
||||||
apis?.updater.quitAndInstall().catch(err => {
|
quitAndInstall().catch(err => {
|
||||||
notify.error({
|
notify.error({
|
||||||
title: 'Failed to restart to upgrade',
|
title: 'Failed to restart to upgrade',
|
||||||
message: 'Please restart the app manually to upgrade.',
|
message: 'Please restart the app manually to upgrade.',
|
||||||
|
|||||||
+10
-5
@@ -1,10 +1,14 @@
|
|||||||
import { Button } from '@affine/component';
|
import { Button } from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { ThemeEditorService } from '@affine/core/modules/theme-editor';
|
import { ThemeEditorService } from '@affine/core/modules/theme-editor';
|
||||||
import { UrlService } from '@affine/core/modules/url';
|
import { UrlService } from '@affine/core/modules/url';
|
||||||
import { apis } from '@affine/electron-api';
|
|
||||||
import { DeleteIcon } from '@blocksuite/icons/rc';
|
import { DeleteIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import {
|
||||||
|
useLiveData,
|
||||||
|
useService,
|
||||||
|
useServiceOptional,
|
||||||
|
} from '@toeverything/infra';
|
||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
@@ -12,14 +16,15 @@ export const ThemeEditorSetting = () => {
|
|||||||
const themeEditor = useService(ThemeEditorService);
|
const themeEditor = useService(ThemeEditorService);
|
||||||
const modified = useLiveData(themeEditor.modified$);
|
const modified = useLiveData(themeEditor.modified$);
|
||||||
const urlService = useService(UrlService);
|
const urlService = useService(UrlService);
|
||||||
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
|
|
||||||
const open = useCallback(() => {
|
const open = useCallback(() => {
|
||||||
if (BUILD_CONFIG.isElectron) {
|
if (desktopApi) {
|
||||||
apis?.ui.openThemeEditor().catch(console.error);
|
desktopApi.handler.ui.openThemeEditor().catch(console.error);
|
||||||
} else if (BUILD_CONFIG.isMobileWeb || BUILD_CONFIG.isWeb) {
|
} else if (BUILD_CONFIG.isMobileWeb || BUILD_CONFIG.isWeb) {
|
||||||
urlService.openPopupWindow(location.origin + '/theme-editor');
|
urlService.openPopupWindow(location.origin + '/theme-editor');
|
||||||
}
|
}
|
||||||
}, [urlService]);
|
}, [desktopApi, urlService]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
|||||||
Slider,
|
Slider,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
ConnectorMode,
|
ConnectorMode,
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
|||||||
type RadioItem,
|
type RadioItem,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { LayoutType, MindmapStyle } from '@blocksuite/affine/blocks';
|
import { LayoutType, MindmapStyle } from '@blocksuite/affine/blocks';
|
||||||
import type { Doc } from '@blocksuite/affine/store';
|
import type { Doc } from '@blocksuite/affine/store';
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
|||||||
Slider,
|
Slider,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
createEnumMap,
|
createEnumMap,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { MenuItem, MenuTrigger, Slider } from '@affine/component';
|
import { MenuItem, MenuTrigger, Slider } from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { LineColor, LineColorMap } from '@blocksuite/affine/blocks';
|
import { LineColor, LineColorMap } from '@blocksuite/affine/blocks';
|
||||||
import type { Doc } from '@blocksuite/affine/store';
|
import type { Doc } from '@blocksuite/affine/store';
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
|||||||
Slider,
|
Slider,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { Skeleton } from '@affine/component';
|
import { Skeleton } from '@affine/component';
|
||||||
import type { EditorSettingSchema } from '@affine/core/modules/editor-settting';
|
import type { EditorSettingSchema } from '@affine/core/modules/editor-setting';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||||
import { BlockStdScope } from '@blocksuite/affine/block-std';
|
import { BlockStdScope } from '@blocksuite/affine/block-std';
|
||||||
import type { GfxPrimitiveElementModel } from '@blocksuite/affine/block-std/gfx';
|
import type { GfxPrimitiveElementModel } from '@blocksuite/affine/block-std/gfx';
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
|||||||
type RadioItem,
|
type RadioItem,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
FontFamily,
|
FontFamily,
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import {
|
|||||||
EditorSettingService,
|
EditorSettingService,
|
||||||
type FontFamily,
|
type FontFamily,
|
||||||
fontStyleOptions,
|
fontStyleOptions,
|
||||||
} from '@affine/core/modules/editor-settting';
|
} from '@affine/core/modules/editor-setting';
|
||||||
import {
|
import {
|
||||||
type FontData,
|
type FontData,
|
||||||
SystemFontFamilyService,
|
SystemFontFamilyService,
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ import {
|
|||||||
SettingRow,
|
SettingRow,
|
||||||
SettingWrapper,
|
SettingWrapper,
|
||||||
} from '@affine/component/setting-components';
|
} from '@affine/component/setting-components';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|||||||
+10
-5
@@ -3,9 +3,13 @@ import { SettingRow } from '@affine/component/setting-components';
|
|||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { useSystemOnline } from '@affine/core/components/hooks/use-system-online';
|
import { useSystemOnline } from '@affine/core/components/hooks/use-system-online';
|
||||||
import { apis } from '@affine/electron-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { Workspace, WorkspaceMetadata } from '@toeverything/infra';
|
import {
|
||||||
|
useService,
|
||||||
|
type Workspace,
|
||||||
|
type WorkspaceMetadata,
|
||||||
|
} from '@toeverything/infra';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
interface ExportPanelProps {
|
interface ExportPanelProps {
|
||||||
@@ -13,7 +17,7 @@ interface ExportPanelProps {
|
|||||||
workspace: Workspace | null;
|
workspace: Workspace | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExportPanel = ({
|
export const DesktopExportPanel = ({
|
||||||
workspaceMetadata,
|
workspaceMetadata,
|
||||||
workspace,
|
workspace,
|
||||||
}: ExportPanelProps) => {
|
}: ExportPanelProps) => {
|
||||||
@@ -21,6 +25,7 @@ export const ExportPanel = ({
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const isOnline = useSystemOnline();
|
const isOnline = useSystemOnline();
|
||||||
|
const desktopApi = useService(DesktopApiService);
|
||||||
|
|
||||||
const onExport = useAsyncCallback(async () => {
|
const onExport = useAsyncCallback(async () => {
|
||||||
if (saving || !workspace) {
|
if (saving || !workspace) {
|
||||||
@@ -33,7 +38,7 @@ export const ExportPanel = ({
|
|||||||
await workspace.engine.blob.sync();
|
await workspace.engine.blob.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await apis?.dialog.saveDBFileAs(workspaceId);
|
const result = await desktopApi.handler.dialog.saveDBFileAs(workspaceId);
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
} else if (!result?.canceled) {
|
} else if (!result?.canceled) {
|
||||||
@@ -44,7 +49,7 @@ export const ExportPanel = ({
|
|||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
}, [isOnline, saving, t, workspace, workspaceId]);
|
}, [isOnline, saving, t, workspace, workspaceId, desktopApi]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingRow name={t['Export']()} desc={t['Export Description']()}>
|
<SettingRow name={t['Export']()} desc={t['Export Description']()}>
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@ import { useCallback } from 'react';
|
|||||||
|
|
||||||
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
||||||
import { EnableCloudPanel } from './enable-cloud';
|
import { EnableCloudPanel } from './enable-cloud';
|
||||||
import { ExportPanel } from './export';
|
import { DesktopExportPanel } from './export';
|
||||||
import { LabelsPanel } from './labels';
|
import { LabelsPanel } from './labels';
|
||||||
import { MembersPanel } from './members';
|
import { MembersPanel } from './members';
|
||||||
import { ProfilePanel } from './profile';
|
import { ProfilePanel } from './profile';
|
||||||
@@ -71,7 +71,7 @@ export const WorkspaceSettingDetail = ({
|
|||||||
<SharingPanel />
|
<SharingPanel />
|
||||||
{BUILD_CONFIG.isElectron && (
|
{BUILD_CONFIG.isElectron && (
|
||||||
<SettingWrapper title={t['Storage and Export']()}>
|
<SettingWrapper title={t['Storage and Export']()}>
|
||||||
<ExportPanel
|
<DesktopExportPanel
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
workspaceMetadata={workspaceMetadata}
|
workspaceMetadata={workspaceMetadata}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { ServerConfigService } from '@affine/core/modules/cloud';
|
import { ServerConfigService } from '@affine/core/modules/cloud';
|
||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { JournalService } from '@affine/core/modules/journal';
|
import { JournalService } from '@affine/core/modules/journal';
|
||||||
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||||
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
|
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
|||||||
AIEdgelessRootBlockSpec,
|
AIEdgelessRootBlockSpec,
|
||||||
AIPageRootBlockSpec,
|
AIPageRootBlockSpec,
|
||||||
} from '@affine/core/blocksuite/presets/ai';
|
} from '@affine/core/blocksuite/presets/ai';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { mixpanel } from '@affine/track';
|
import { mixpanel } from '@affine/track';
|
||||||
import {
|
import {
|
||||||
ConfigExtension,
|
ConfigExtension,
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ import {
|
|||||||
type useConfirmModal,
|
type useConfirmModal,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import type { EditorService } from '@affine/core/modules/editor';
|
import type { EditorService } from '@affine/core/modules/editor';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { resolveLinkToDoc } from '@affine/core/modules/navigation';
|
import { resolveLinkToDoc } from '@affine/core/modules/navigation';
|
||||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { toast } from '@affine/component';
|
import { toast } from '@affine/component';
|
||||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||||
import type { DocCollection } from '@blocksuite/affine/store';
|
import type { DocCollection } from '@blocksuite/affine/store';
|
||||||
|
|||||||
+3
-3
@@ -4,17 +4,17 @@ import {
|
|||||||
} from '@affine/core/commands';
|
} from '@affine/core/commands';
|
||||||
import { FindInPageService } from '@affine/core/modules/find-in-page/services/find-in-page';
|
import { FindInPageService } from '@affine/core/modules/find-in-page/services/find-in-page';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useServiceOptional } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
export function useRegisterFindInPageCommands() {
|
export function useRegisterFindInPageCommands() {
|
||||||
const findInPage = useService(FindInPageService).findInPage;
|
const findInPage = useServiceOptional(FindInPageService)?.findInPage;
|
||||||
const toggleVisible = useCallback(() => {
|
const toggleVisible = useCallback(() => {
|
||||||
// get the selected text in page
|
// get the selected text in page
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
const selectedText = selection?.toString();
|
const selectedText = selection?.toString();
|
||||||
|
|
||||||
findInPage.toggleVisible(selectedText);
|
findInPage?.toggleVisible(selectedText);
|
||||||
}, [findInPage]);
|
}, [findInPage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// todo(@pengx17): remove jotai
|
||||||
import { UrlService } from '@affine/core/modules/url';
|
import { UrlService } from '@affine/core/modules/url';
|
||||||
import type { UpdateMeta } from '@affine/electron-api';
|
import type { UpdateMeta } from '@affine/electron-api';
|
||||||
import { apis, events } from '@affine/electron-api';
|
import { apis, events } from '@affine/electron-api';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { JournalService } from '@affine/core/modules/journal';
|
import { JournalService } from '@affine/core/modules/journal';
|
||||||
import { i18nTime } from '@affine/i18n';
|
import { i18nTime } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||||
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { I18nService } from '@affine/core/modules/i18n';
|
import { I18nService } from '@affine/core/modules/i18n';
|
||||||
import { UrlService } from '@affine/core/modules/url';
|
import { UrlService } from '@affine/core/modules/url';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
import {
|
||||||
|
useService,
|
||||||
|
useServiceOptional,
|
||||||
|
WorkspaceService,
|
||||||
|
} from '@toeverything/infra';
|
||||||
import { useStore } from 'jotai';
|
import { useStore } from 'jotai';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@@ -21,7 +26,7 @@ import {
|
|||||||
} from '../../commands';
|
} from '../../commands';
|
||||||
import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils';
|
import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils';
|
||||||
import { CreateWorkspaceDialogService } from '../../modules/create-workspace';
|
import { CreateWorkspaceDialogService } from '../../modules/create-workspace';
|
||||||
import { EditorSettingService } from '../../modules/editor-settting';
|
import { EditorSettingService } from '../../modules/editor-setting';
|
||||||
import { CMDKQuickSearchService } from '../../modules/quicksearch/services/cmdk';
|
import { CMDKQuickSearchService } from '../../modules/quicksearch/services/cmdk';
|
||||||
import { useActiveBlocksuiteEditor } from './use-block-suite-editor';
|
import { useActiveBlocksuiteEditor } from './use-block-suite-editor';
|
||||||
import { useNavigateHelper } from './use-navigate-helper';
|
import { useNavigateHelper } from './use-navigate-helper';
|
||||||
@@ -77,6 +82,9 @@ export function useRegisterWorkspaceCommands() {
|
|||||||
const appSidebarService = useService(AppSidebarService);
|
const appSidebarService = useService(AppSidebarService);
|
||||||
const i18n = useService(I18nService).i18n;
|
const i18n = useService(I18nService).i18n;
|
||||||
|
|
||||||
|
const quitAndInstall =
|
||||||
|
useServiceOptional(DesktopApiService)?.handler.updater.quitAndInstall;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsub = registerCMDKCommand(cmdkQuickSearchService, editor);
|
const unsub = registerCMDKCommand(cmdkQuickSearchService, editor);
|
||||||
|
|
||||||
@@ -87,15 +95,20 @@ export function useRegisterWorkspaceCommands() {
|
|||||||
|
|
||||||
// register AffineUpdatesCommands
|
// register AffineUpdatesCommands
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!quitAndInstall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const unsub = registerAffineUpdatesCommands({
|
const unsub = registerAffineUpdatesCommands({
|
||||||
store,
|
store,
|
||||||
t,
|
t,
|
||||||
|
quitAndInstall,
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsub();
|
unsub();
|
||||||
};
|
};
|
||||||
}, [store, t]);
|
}, [quitAndInstall, store, t]);
|
||||||
|
|
||||||
// register AffineNavigationCommands
|
// register AffineNavigationCommands
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
resolveGlobalLoadingEventAtom,
|
resolveGlobalLoadingEventAtom,
|
||||||
} from '@affine/component/global-loading';
|
} from '@affine/component/global-loading';
|
||||||
import { SidebarSwitch } from '@affine/core/modules/app-sidebar/views';
|
import { SidebarSwitch } from '@affine/core/modules/app-sidebar/views';
|
||||||
|
import { WorkspaceDesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks';
|
import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks';
|
||||||
import {
|
import {
|
||||||
@@ -33,7 +34,7 @@ import { Map as YMap } from 'yjs';
|
|||||||
|
|
||||||
import { AIProvider } from '../../blocksuite/presets/ai';
|
import { AIProvider } from '../../blocksuite/presets/ai';
|
||||||
import { AppTabsHeader } from '../../modules/app-tabs-header';
|
import { AppTabsHeader } from '../../modules/app-tabs-header';
|
||||||
import { EditorSettingService } from '../../modules/editor-settting';
|
import { EditorSettingService } from '../../modules/editor-setting';
|
||||||
import { NavigationButtons } from '../../modules/navigation';
|
import { NavigationButtons } from '../../modules/navigation';
|
||||||
import { useRegisterNavigationCommands } from '../../modules/navigation/view/use-register-navigation-commands';
|
import { useRegisterNavigationCommands } from '../../modules/navigation/view/use-register-navigation-commands';
|
||||||
import { QuickSearchContainer } from '../../modules/quicksearch';
|
import { QuickSearchContainer } from '../../modules/quicksearch';
|
||||||
@@ -179,6 +180,8 @@ export const WorkspaceLayoutProviders = ({ children }: PropsWithChildren) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DesktopLayout = ({ children }: PropsWithChildren) => {
|
const DesktopLayout = ({ children }: PropsWithChildren) => {
|
||||||
|
// is there a better way to make sure service is always available even if it's not explicitly used?
|
||||||
|
useService(WorkspaceDesktopApiService);
|
||||||
return (
|
return (
|
||||||
<div className={styles.desktopAppViewContainer}>
|
<div className={styles.desktopAppViewContainer}>
|
||||||
<div className={styles.desktopTabsHeader}>
|
<div className={styles.desktopTabsHeader}>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { EditorService } from '../modules/editor';
|
|||||||
import {
|
import {
|
||||||
EditorSettingService,
|
EditorSettingService,
|
||||||
fontStyleOptions,
|
fontStyleOptions,
|
||||||
} from '../modules/editor-settting';
|
} from '../modules/editor-setting';
|
||||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||||
import * as styles from './page-detail-editor.css';
|
import * as styles from './page-detail-editor.css';
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { NotificationCenter, notify } from '@affine/component';
|
import { NotificationCenter, notify } from '@affine/component';
|
||||||
import { events } from '@affine/electron-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import {
|
import {
|
||||||
GlobalContextService,
|
GlobalContextService,
|
||||||
useLiveData,
|
useLiveData,
|
||||||
useService,
|
useService,
|
||||||
|
useServiceOptional,
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
WorkspacesService,
|
WorkspacesService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
@@ -61,17 +62,16 @@ export const Setting = () => {
|
|||||||
[setOpenSettingModalAtom]
|
[setOpenSettingModalAtom]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (BUILD_CONFIG.isElectron) {
|
return desktopApi?.events?.applicationMenu.openAboutPageInSettingModal(() =>
|
||||||
return events?.applicationMenu.openAboutPageInSettingModal(() =>
|
|
||||||
setOpenSettingModalAtom({
|
setOpenSettingModalAtom({
|
||||||
activeTab: 'about',
|
activeTab: 'about',
|
||||||
open: true,
|
open: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}, [desktopApi?.events?.applicationMenu, setOpenSettingModalAtom]);
|
||||||
return;
|
|
||||||
}, [setOpenSettingModalAtom]);
|
|
||||||
|
|
||||||
if (!open) {
|
if (!open) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,37 +1,9 @@
|
|||||||
import { apis, events } from '@affine/electron-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useService } from '@toeverything/infra';
|
||||||
import { atomWithObservable } from 'jotai/utils';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { combineLatest, map, Observable } from 'rxjs';
|
|
||||||
|
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
|
|
||||||
const maximized$ = new Observable<boolean>(subscriber => {
|
|
||||||
subscriber.next(false);
|
|
||||||
if (events) {
|
|
||||||
return events.ui.onMaximized(res => {
|
|
||||||
subscriber.next(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return () => {};
|
|
||||||
});
|
|
||||||
|
|
||||||
const fullscreen$ = new Observable<boolean>(subscriber => {
|
|
||||||
subscriber.next(false);
|
|
||||||
if (events) {
|
|
||||||
return events.ui.onFullScreen(res => {
|
|
||||||
subscriber.next(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return () => {};
|
|
||||||
});
|
|
||||||
|
|
||||||
const maximizedAtom = atomWithObservable(() => {
|
|
||||||
return combineLatest([maximized$, fullscreen$]).pipe(
|
|
||||||
map(([maximized, fullscreen]) => maximized || fullscreen)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const minimizeSVG = (
|
const minimizeSVG = (
|
||||||
<svg
|
<svg
|
||||||
width="10"
|
width="10"
|
||||||
@@ -93,23 +65,34 @@ const unmaximizedSVG = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const WindowsAppControls = () => {
|
export const WindowsAppControls = () => {
|
||||||
const handleMinimizeApp = useCallback(() => {
|
const desktopApi = useService(DesktopApiService);
|
||||||
apis?.ui.handleMinimizeApp().catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
const handleMaximizeApp = useCallback(() => {
|
|
||||||
apis?.ui.handleMaximizeApp().catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
const handleCloseApp = useCallback(() => {
|
|
||||||
apis?.ui.handleCloseApp().catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const maximized = useAtomValue(maximizedAtom);
|
const handleMinimizeApp = useCallback(() => {
|
||||||
|
desktopApi.handler.ui.handleMinimizeApp().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [desktopApi.handler.ui]);
|
||||||
|
const handleMaximizeApp = useCallback(() => {
|
||||||
|
desktopApi.handler.ui.handleMaximizeApp().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [desktopApi.handler.ui]);
|
||||||
|
const handleCloseApp = useCallback(() => {
|
||||||
|
desktopApi.handler.ui.handleCloseApp().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [desktopApi.handler.ui]);
|
||||||
|
|
||||||
|
const [maximized, setMaximized] = useState(false);
|
||||||
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return desktopApi.events.ui.onMaximized(setMaximized);
|
||||||
|
}, [desktopApi.events.ui]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return desktopApi.events.ui.onFullScreen(setFullscreen);
|
||||||
|
}, [desktopApi.events.ui]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -128,7 +111,7 @@ export const WindowsAppControls = () => {
|
|||||||
className={style.windowAppControl}
|
className={style.windowAppControl}
|
||||||
onClick={handleMaximizeApp}
|
onClick={handleMaximizeApp}
|
||||||
>
|
>
|
||||||
{maximized ? unmaximizedSVG : maximizeSVG}
|
{maximized || fullscreen ? unmaximizedSVG : maximizeSVG}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
data-type="close"
|
data-type="close"
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
|
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
|
||||||
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
|
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
|
||||||
import { isNewTabTrigger } from '@affine/core/utils';
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import { apis, events } from '@affine/electron-api';
|
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import type { Doc } from '@blocksuite/affine/store';
|
import type { Doc } from '@blocksuite/affine/store';
|
||||||
@@ -39,7 +38,7 @@ import {
|
|||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import type { MouseEvent, ReactElement } from 'react';
|
import type { MouseEvent, ReactElement } from 'react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { WorkbenchService } from '../../modules/workbench';
|
import { WorkbenchService } from '../../modules/workbench';
|
||||||
import { usePageHelper } from '../blocksuite/block-suite-page-list/utils';
|
import { usePageHelper } from '../blocksuite/block-suite-page-list/utils';
|
||||||
@@ -105,25 +104,6 @@ export const RootAppSidebar = (): ReactElement => {
|
|||||||
[pageHelper]
|
[pageHelper]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (BUILD_CONFIG.isElectron) {
|
|
||||||
return events?.applicationMenu.onNewPageAction(() => {
|
|
||||||
apis?.ui
|
|
||||||
.isActiveTab()
|
|
||||||
.then(isActive => {
|
|
||||||
if (!isActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onClickNewPage();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}, [onClickNewPage]);
|
|
||||||
|
|
||||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||||
|
|
||||||
const onOpenSettingModal = useCallback(() => {
|
const onOpenSettingModal = useCallback(() => {
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ import {
|
|||||||
NotFoundPage,
|
NotFoundPage,
|
||||||
} from '@affine/component/not-found-page';
|
} from '@affine/component/not-found-page';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { apis } from '@affine/electron-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import {
|
||||||
|
useLiveData,
|
||||||
|
useService,
|
||||||
|
useServiceOptional,
|
||||||
|
} from '@toeverything/infra';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
@@ -22,6 +26,7 @@ export const PageNotFound = ({
|
|||||||
noPermission?: boolean;
|
noPermission?: boolean;
|
||||||
}): ReactElement => {
|
}): ReactElement => {
|
||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
const account = useLiveData(authService.session.account$);
|
const account = useLiveData(authService.session.account$);
|
||||||
const { jumpToIndex } = useNavigateHelper();
|
const { jumpToIndex } = useNavigateHelper();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -41,8 +46,8 @@ export const PageNotFound = ({
|
|||||||
}, [authService]);
|
}, [authService]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
apis?.ui.pingAppLayoutReady().catch(console.error);
|
desktopApi?.handler.ui.pingAppLayoutReady().catch(console.error);
|
||||||
}, []);
|
}, [desktopApi]);
|
||||||
|
|
||||||
// not using workbench location or router location deliberately
|
// not using workbench location or router location deliberately
|
||||||
// strip the origin
|
// strip the origin
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { apis } from '@affine/electron-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import {
|
import {
|
||||||
useLiveData,
|
useLiveData,
|
||||||
useService,
|
useService,
|
||||||
|
useServiceOptional,
|
||||||
WorkspacesService,
|
WorkspacesService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import {
|
import {
|
||||||
@@ -118,9 +119,11 @@ export const Component = ({
|
|||||||
defaultIndexRoute,
|
defaultIndexRoute,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
apis?.ui.pingAppLayoutReady().catch(console.error);
|
desktopApi?.handler.ui.pingAppLayoutReady().catch(console.error);
|
||||||
}, []);
|
}, [desktopApi]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { apis } from '@affine/electron-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api/service';
|
||||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
import { useServiceOptional } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { redirect } from 'react-router-dom';
|
import { redirect } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -25,18 +25,18 @@ export const loader = () => {
|
|||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const { jumpToIndex } = useNavigateHelper();
|
const { jumpToIndex } = useNavigateHelper();
|
||||||
const [, setOnboarding] = useAppConfigStorage('onBoarding');
|
const [, setOnboarding] = useAppConfigStorage('onBoarding');
|
||||||
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
|
|
||||||
const openApp = useCallback(() => {
|
const openApp = useCallback(() => {
|
||||||
if (BUILD_CONFIG.isElectron) {
|
if (BUILD_CONFIG.isElectron) {
|
||||||
assertExists(apis);
|
desktopApi?.handler.ui.handleOpenMainApp().catch(err => {
|
||||||
apis.ui.handleOpenMainApp().catch(err => {
|
|
||||||
console.log('failed to open main app', err);
|
console.log('failed to open main app', err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
jumpToIndex(RouteLogic.REPLACE);
|
jumpToIndex(RouteLogic.REPLACE);
|
||||||
setOnboarding(false);
|
setOnboarding(false);
|
||||||
}
|
}
|
||||||
}, [jumpToIndex, setOnboarding]);
|
}, [jumpToIndex, setOnboarding, desktopApi]);
|
||||||
|
|
||||||
return <Onboarding onOpenApp={openApp} />;
|
return <Onboarding onOpenApp={openApp} />;
|
||||||
};
|
};
|
||||||
@@ -53,7 +53,7 @@ export const Component = (): ReactElement => {
|
|||||||
match &&
|
match &&
|
||||||
match.params.docId &&
|
match.params.docId &&
|
||||||
match.params.workspaceId &&
|
match.params.workspaceId &&
|
||||||
// // TODO(eyhn): need a better way to check if it's a docId
|
// TODO(eyhn): need a better way to check if it's a docId
|
||||||
workbenchRoutes.find(route =>
|
workbenchRoutes.find(route =>
|
||||||
matchPath(route.path, '/' + match.params.docId)
|
matchPath(route.path, '/' + match.params.docId)
|
||||||
)?.path === '/:pageId'
|
)?.path === '/:pageId'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { getBaseFontStyleOptions } from '@affine/core/components/affine/setting-
|
|||||||
import {
|
import {
|
||||||
EditorSettingService,
|
EditorSettingService,
|
||||||
type FontFamily,
|
type FontFamily,
|
||||||
} from '@affine/core/modules/editor-settting';
|
} from '@affine/core/modules/editor-setting';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { type Framework } from '@toeverything/infra';
|
import { type Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { AppTabsHeaderService } from './services/app-tabs-header-service';
|
import { AppTabsHeaderService } from './services/app-tabs-header-service';
|
||||||
|
|
||||||
export { AppTabsHeader } from './views/app-tabs-header';
|
export { AppTabsHeader } from './views/app-tabs-header';
|
||||||
|
|
||||||
export function configureAppTabsHeaderModule(framework: Framework) {
|
export function configureAppTabsHeaderModule(framework: Framework) {
|
||||||
framework.service(AppTabsHeaderService);
|
framework.service(AppTabsHeaderService, [DesktopApiService]);
|
||||||
}
|
}
|
||||||
+15
-14
@@ -1,24 +1,25 @@
|
|||||||
import { apis, events } from '@affine/electron-api';
|
|
||||||
import { LiveData, Service } from '@toeverything/infra';
|
import { LiveData, Service } from '@toeverything/infra';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import type { ClientEvents, DesktopApiService } from '../../desktop-api';
|
||||||
|
|
||||||
export type TabStatus = Parameters<
|
export type TabStatus = Parameters<
|
||||||
Parameters<NonNullable<typeof events>['ui']['onTabsStatusChange']>[0]
|
Parameters<NonNullable<ClientEvents>['ui']['onTabsStatusChange']>[0]
|
||||||
>[0][number];
|
>[0][number];
|
||||||
|
|
||||||
export class AppTabsHeaderService extends Service {
|
export class AppTabsHeaderService extends Service {
|
||||||
constructor() {
|
constructor(private readonly desktopApi: DesktopApiService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
tabsStatus$ = LiveData.from<TabStatus[]>(
|
tabsStatus$ = LiveData.from<TabStatus[]>(
|
||||||
new Observable(subscriber => {
|
new Observable(subscriber => {
|
||||||
let unsub: (() => void) | undefined;
|
let unsub: (() => void) | undefined;
|
||||||
apis?.ui
|
this.desktopApi.handler.ui
|
||||||
.getTabsStatus()
|
.getTabsStatus()
|
||||||
.then(tabs => {
|
.then(tabs => {
|
||||||
subscriber.next(tabs);
|
subscriber.next(tabs);
|
||||||
unsub = events?.ui.onTabsStatusChange(tabs => {
|
unsub = this.desktopApi.events.ui.onTabsStatusChange(tabs => {
|
||||||
subscriber.next(tabs);
|
subscriber.next(tabs);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -31,20 +32,20 @@ export class AppTabsHeaderService extends Service {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
showContextMenu = apis?.ui.showTabContextMenu;
|
showContextMenu = this.desktopApi.handler.ui.showTabContextMenu;
|
||||||
|
|
||||||
activateView = apis?.ui.activateView;
|
activateView = this.desktopApi.handler.ui.activateView;
|
||||||
|
|
||||||
closeTab = apis?.ui.closeTab;
|
closeTab = this.desktopApi.handler.ui.closeTab;
|
||||||
|
|
||||||
onAddTab = apis?.ui.addTab;
|
onAddTab = this.desktopApi.handler.ui.addTab;
|
||||||
|
|
||||||
onAddDocTab = async (
|
onAddDocTab = async (
|
||||||
docId: string,
|
docId: string,
|
||||||
targetTabId?: string,
|
targetTabId?: string,
|
||||||
edge?: 'left' | 'right'
|
edge?: 'left' | 'right'
|
||||||
) => {
|
) => {
|
||||||
await apis?.ui.addTab({
|
await this.desktopApi.handler.ui.addTab({
|
||||||
view: {
|
view: {
|
||||||
path: {
|
path: {
|
||||||
pathname: '/' + docId,
|
pathname: '/' + docId,
|
||||||
@@ -60,7 +61,7 @@ export class AppTabsHeaderService extends Service {
|
|||||||
targetTabId?: string,
|
targetTabId?: string,
|
||||||
edge?: 'left' | 'right'
|
edge?: 'left' | 'right'
|
||||||
) => {
|
) => {
|
||||||
await apis?.ui.addTab({
|
await this.desktopApi.handler.ui.addTab({
|
||||||
view: {
|
view: {
|
||||||
path: {
|
path: {
|
||||||
pathname: '/tag/' + tagId,
|
pathname: '/tag/' + tagId,
|
||||||
@@ -76,7 +77,7 @@ export class AppTabsHeaderService extends Service {
|
|||||||
targetTabId?: string,
|
targetTabId?: string,
|
||||||
edge?: 'left' | 'right'
|
edge?: 'left' | 'right'
|
||||||
) => {
|
) => {
|
||||||
await apis?.ui.addTab({
|
await this.desktopApi.handler.ui.addTab({
|
||||||
view: {
|
view: {
|
||||||
path: {
|
path: {
|
||||||
pathname: '/collection/' + collectionId,
|
pathname: '/collection/' + collectionId,
|
||||||
@@ -87,7 +88,7 @@ export class AppTabsHeaderService extends Service {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onToggleRightSidebar = apis?.ui.toggleRightSidebar;
|
onToggleRightSidebar = this.desktopApi.handler.ui.toggleRightSidebar;
|
||||||
|
|
||||||
moveTab = apis?.ui.moveTab;
|
moveTab = this.desktopApi.handler.ui.moveTab;
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
|
import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { apis, events } from '@affine/electron-api';
|
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { CloseIcon, PlusIcon, RightSidebarIcon } from '@blocksuite/icons/rc';
|
import { CloseIcon, PlusIcon, RightSidebarIcon } from '@blocksuite/icons/rc';
|
||||||
@@ -31,6 +30,7 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { AppSidebarService } from '../../app-sidebar';
|
import { AppSidebarService } from '../../app-sidebar';
|
||||||
|
import { DesktopApiService } from '../../desktop-api';
|
||||||
import { iconNameToIcon } from '../../workbench/constants';
|
import { iconNameToIcon } from '../../workbench/constants';
|
||||||
import { DesktopStateSynchronizer } from '../../workbench/services/desktop-state-synchronizer';
|
import { DesktopStateSynchronizer } from '../../workbench/services/desktop-state-synchronizer';
|
||||||
import {
|
import {
|
||||||
@@ -274,17 +274,18 @@ const WorkbenchTab = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useIsFullScreen = () => {
|
const useIsFullScreen = () => {
|
||||||
|
const desktopApi = useServiceOptional(DesktopApiService);
|
||||||
const [fullScreen, setFullScreen] = useState(false);
|
const [fullScreen, setFullScreen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
apis?.ui
|
desktopApi?.handler.ui
|
||||||
.isFullScreen()
|
.isFullScreen()
|
||||||
.then(setFullScreen)
|
.then(setFullScreen)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
events?.ui.onFullScreen(setFullScreen);
|
desktopApi?.events.ui.onFullScreen(setFullScreen);
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}, []);
|
}, [desktopApi?.events.ui, desktopApi?.handler.ui]);
|
||||||
return fullScreen;
|
return fullScreen;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -309,6 +310,8 @@ export const AppTabsHeader = ({
|
|||||||
const isWindowsDesktop = BUILD_CONFIG.isElectron && environment.isWindows;
|
const isWindowsDesktop = BUILD_CONFIG.isElectron && environment.isWindows;
|
||||||
const fullScreen = useIsFullScreen();
|
const fullScreen = useIsFullScreen();
|
||||||
|
|
||||||
|
const desktopApi = useService(DesktopApiService);
|
||||||
|
|
||||||
const tabsHeaderService = useService(AppTabsHeaderService);
|
const tabsHeaderService = useService(AppTabsHeaderService);
|
||||||
const tabs = useLiveData(tabsHeaderService.tabsStatus$);
|
const tabs = useLiveData(tabsHeaderService.tabsStatus$);
|
||||||
|
|
||||||
@@ -328,9 +331,9 @@ export const AppTabsHeader = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === 'app') {
|
if (mode === 'app') {
|
||||||
apis?.ui.pingAppLayoutReady().catch(console.error);
|
desktopApi.handler.ui.pingAppLayoutReady().catch(console.error);
|
||||||
}
|
}
|
||||||
}, [mode]);
|
}, [mode, desktopApi]);
|
||||||
|
|
||||||
const onDrop = useAsyncCallback(
|
const onDrop = useAsyncCallback(
|
||||||
async (data: DropTargetDropEvent<AffineDNDData>, targetId?: string) => {
|
async (data: DropTargetDropEvent<AffineDNDData>, targetId?: string) => {
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { notify } from '@affine/component';
|
|
||||||
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||||
import { apis, events } from '@affine/electron-api';
|
|
||||||
import type { OAuthProviderType } from '@affine/graphql';
|
import type { OAuthProviderType } from '@affine/graphql';
|
||||||
import { I18n } from '@affine/i18n';
|
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import {
|
import {
|
||||||
ApplicationFocused,
|
ApplicationFocused,
|
||||||
@@ -78,33 +75,6 @@ export class AuthService extends Service {
|
|||||||
|
|
||||||
private onApplicationStart() {
|
private onApplicationStart() {
|
||||||
this.session.revalidate();
|
this.session.revalidate();
|
||||||
|
|
||||||
if (BUILD_CONFIG.isElectron) {
|
|
||||||
events?.ui.onAuthenticationRequest(({ method, payload }) => {
|
|
||||||
(async () => {
|
|
||||||
if (!(await apis?.ui.isActiveTab())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (method) {
|
|
||||||
case 'magic-link': {
|
|
||||||
const { email, token } = payload;
|
|
||||||
await this.signInMagicLink(email, token);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'oauth': {
|
|
||||||
const { code, state, provider } = payload;
|
|
||||||
await this.signInOauth(code, state, provider);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})().catch(e => {
|
|
||||||
notify.error({
|
|
||||||
title: I18n['com.affine.auth.toast.title.failed'](),
|
|
||||||
message: (e as any).message,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onApplicationFocused() {
|
private onApplicationFocused() {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { CloudSvg } from '@affine/core/components/affine/share-page-modal/cloud-
|
|||||||
import { authAtom } from '@affine/core/components/atoms';
|
import { authAtom } from '@affine/core/components/atoms';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { apis } from '@affine/electron-api';
|
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
useLiveData,
|
useLiveData,
|
||||||
useService,
|
useService,
|
||||||
|
useServiceOptional,
|
||||||
WorkspacesService,
|
WorkspacesService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
@@ -20,6 +20,7 @@ import { useCallback, useLayoutEffect, useState } from 'react';
|
|||||||
import { AuthService } from '../../../modules/cloud';
|
import { AuthService } from '../../../modules/cloud';
|
||||||
import { _addLocalWorkspace } from '../../../modules/workspace-engine';
|
import { _addLocalWorkspace } from '../../../modules/workspace-engine';
|
||||||
import { buildShowcaseWorkspace } from '../../../utils/first-app-data';
|
import { buildShowcaseWorkspace } from '../../../utils/first-app-data';
|
||||||
|
import { DesktopApiService } from '../../desktop-api';
|
||||||
import { CreateWorkspaceDialogService } from '../services/dialog';
|
import { CreateWorkspaceDialogService } from '../services/dialog';
|
||||||
import * as styles from './dialog.css';
|
import * as styles from './dialog.css';
|
||||||
|
|
||||||
@@ -162,6 +163,7 @@ const CreateWorkspaceDialog = () => {
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const workspacesService = useService(WorkspacesService);
|
const workspacesService = useService(WorkspacesService);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const electronApi = useServiceOptional(DesktopApiService);
|
||||||
|
|
||||||
// TODO(@Peng): maybe refactor using xstate?
|
// TODO(@Peng): maybe refactor using xstate?
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -173,11 +175,11 @@ const CreateWorkspaceDialog = () => {
|
|||||||
// after it is done, it will effectively add a new workspace to app-data folder
|
// after it is done, it will effectively add a new workspace to app-data folder
|
||||||
// so after that, we will be able to load it via importLocalWorkspace
|
// so after that, we will be able to load it via importLocalWorkspace
|
||||||
(async () => {
|
(async () => {
|
||||||
if (!apis) {
|
if (!electronApi) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info('load db file');
|
logger.info('load db file');
|
||||||
const result = await apis.dialog.loadDBFile();
|
const result = await electronApi.handler.dialog.loadDBFile();
|
||||||
if (result.workspaceId && !canceled) {
|
if (result.workspaceId && !canceled) {
|
||||||
_addLocalWorkspace(result.workspaceId);
|
_addLocalWorkspace(result.workspaceId);
|
||||||
workspacesService.list.revalidate();
|
workspacesService.list.revalidate();
|
||||||
@@ -201,7 +203,7 @@ const CreateWorkspaceDialog = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
};
|
};
|
||||||
}, [createWorkspaceDialogService, mode, t, workspacesService]);
|
}, [createWorkspaceDialogService, electronApi, mode, t, workspacesService]);
|
||||||
|
|
||||||
const onConfirmName = useAsyncCallback(
|
const onConfirmName = useAsyncCallback(
|
||||||
async (name: string, workspaceFlavour: WorkspaceFlavour) => {
|
async (name: string, workspaceFlavour: WorkspaceFlavour) => {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
import { Entity } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { DesktopApiProvider } from '../provider';
|
||||||
|
|
||||||
|
export class DesktopApi extends Entity {
|
||||||
|
constructor(public readonly provider: DesktopApiProvider) {
|
||||||
|
super();
|
||||||
|
if (!provider.handler || !provider.events || !provider.sharedStorage) {
|
||||||
|
throw new Error('DesktopApiProvider is not correctly initialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get handler() {
|
||||||
|
return this.provider.handler!;
|
||||||
|
}
|
||||||
|
|
||||||
|
get events() {
|
||||||
|
return this.provider.events!;
|
||||||
|
}
|
||||||
|
|
||||||
|
get sharedStorage() {
|
||||||
|
return this.provider.sharedStorage!;
|
||||||
|
}
|
||||||
|
|
||||||
|
get appInfo() {
|
||||||
|
return this.provider.appInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DesktopAppInfo extends Entity {
|
||||||
|
constructor(public readonly provider: DesktopApiProvider) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
import { apis, appInfo, events, sharedStorage } from '@affine/electron-api';
|
||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { DesktopApiProvider } from '../provider';
|
||||||
|
|
||||||
|
export class ElectronApiImpl extends Service implements DesktopApiProvider {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (!apis || !events || !sharedStorage || !appInfo) {
|
||||||
|
throw new Error('Failed to initialize DesktopApiImpl');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler = apis;
|
||||||
|
events = events;
|
||||||
|
sharedStorage = sharedStorage;
|
||||||
|
appInfo = appInfo!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {
|
||||||
|
DocsService,
|
||||||
|
type Framework,
|
||||||
|
WorkspaceScope,
|
||||||
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { WorkbenchService } from '../workbench';
|
||||||
|
import { DesktopApi } from './entities/electron-api';
|
||||||
|
import { ElectronApiImpl } from './impl';
|
||||||
|
import { DesktopApiProvider } from './provider';
|
||||||
|
import { DesktopApiService, WorkspaceDesktopApiService } from './service';
|
||||||
|
|
||||||
|
export function configureDesktopApiModule(framework: Framework) {
|
||||||
|
framework
|
||||||
|
.impl(DesktopApiProvider, ElectronApiImpl)
|
||||||
|
.entity(DesktopApi, [DesktopApiProvider])
|
||||||
|
.service(DesktopApiService, [DesktopApi])
|
||||||
|
.scope(WorkspaceScope)
|
||||||
|
.service(WorkspaceDesktopApiService, [
|
||||||
|
DesktopApiService,
|
||||||
|
DocsService,
|
||||||
|
WorkbenchService,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from './service';
|
||||||
|
export type { ClientEvents, TabViewsMetaSchema } from '@affine/electron-api';
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { AppInfo } from '@affine/electron/preload/electron-api';
|
||||||
|
import type {
|
||||||
|
ClientEvents,
|
||||||
|
ClientHandler,
|
||||||
|
SharedStorage,
|
||||||
|
} from '@affine/electron-api';
|
||||||
|
import { createIdentifier } from '@toeverything/infra';
|
||||||
|
|
||||||
|
// for now desktop api's type are all inferred from electron-api
|
||||||
|
export interface DesktopApiProvider {
|
||||||
|
handler?: ClientHandler;
|
||||||
|
events?: ClientEvents;
|
||||||
|
sharedStorage?: SharedStorage;
|
||||||
|
appInfo: AppInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DesktopApiProvider =
|
||||||
|
createIdentifier<DesktopApiProvider>('DesktopApiProvider');
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import { notify } from '@affine/component';
|
||||||
|
import { I18n } from '@affine/i18n';
|
||||||
|
import {
|
||||||
|
init,
|
||||||
|
reactRouterV6BrowserTracingIntegration,
|
||||||
|
setTags,
|
||||||
|
} from '@sentry/react';
|
||||||
|
import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra';
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
createRoutesFromChildren,
|
||||||
|
matchRoutes,
|
||||||
|
useLocation,
|
||||||
|
useNavigationType,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AuthService } from '../../cloud';
|
||||||
|
import type { DesktopApi } from '../entities/electron-api';
|
||||||
|
|
||||||
|
@OnEvent(ApplicationStarted, e => e.setupStartListener)
|
||||||
|
export class DesktopApiService extends Service {
|
||||||
|
constructor(public readonly api: DesktopApi) {
|
||||||
|
super();
|
||||||
|
if (!api.handler || !api.events) {
|
||||||
|
throw new Error('DesktopApi is not initialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get appInfo() {
|
||||||
|
return this.api.appInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
get handler() {
|
||||||
|
return this.api.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
get events() {
|
||||||
|
return this.api.events;
|
||||||
|
}
|
||||||
|
|
||||||
|
get sharedStorage() {
|
||||||
|
return this.api.sharedStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupStartListener() {
|
||||||
|
this.setupSentry();
|
||||||
|
this.setupCommonUIEvents();
|
||||||
|
this.setupAuthRequestEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSentry() {
|
||||||
|
if (
|
||||||
|
BUILD_CONFIG.debug ||
|
||||||
|
window.SENTRY_RELEASE ||
|
||||||
|
this.api.appInfo.windowName !== 'main'
|
||||||
|
) {
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/electron/
|
||||||
|
init({
|
||||||
|
dsn: process.env.SENTRY_DSN,
|
||||||
|
environment: process.env.BUILD_TYPE ?? 'development',
|
||||||
|
integrations: [
|
||||||
|
reactRouterV6BrowserTracingIntegration({
|
||||||
|
useEffect,
|
||||||
|
useLocation,
|
||||||
|
useNavigationType,
|
||||||
|
createRoutesFromChildren,
|
||||||
|
matchRoutes,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
setTags({
|
||||||
|
appVersion: BUILD_CONFIG.appVersion,
|
||||||
|
editorVersion: BUILD_CONFIG.editorVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.api.handler.ui
|
||||||
|
.handleNetworkChange(navigator.onLine)
|
||||||
|
.catch(console.error);
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
|
this.api.handler.ui.handleNetworkChange(false).catch(console.error);
|
||||||
|
});
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
this.api.handler.ui.handleNetworkChange(true).catch(console.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupCommonUIEvents() {
|
||||||
|
const handleMaximized = (maximized: boolean | undefined) => {
|
||||||
|
document.documentElement.dataset.maximized = String(maximized);
|
||||||
|
};
|
||||||
|
const handleFullscreen = (fullscreen: boolean | undefined) => {
|
||||||
|
document.documentElement.dataset.fullscreen = String(fullscreen);
|
||||||
|
};
|
||||||
|
this.api.handler.ui
|
||||||
|
.isMaximized()
|
||||||
|
.then(handleMaximized)
|
||||||
|
.catch(console.error);
|
||||||
|
this.api.handler.ui
|
||||||
|
.isFullScreen()
|
||||||
|
.then(handleFullscreen)
|
||||||
|
.catch(console.error);
|
||||||
|
this.api.events.ui.onMaximized(handleMaximized);
|
||||||
|
this.api.events.ui.onFullScreen(handleFullscreen);
|
||||||
|
|
||||||
|
const tabId = this.api.appInfo.viewId;
|
||||||
|
|
||||||
|
if (tabId && this.api.appInfo.windowName === 'main') {
|
||||||
|
let isActive = false;
|
||||||
|
const handleActiveTabChange = (active: boolean) => {
|
||||||
|
isActive = active;
|
||||||
|
document.documentElement.dataset.active = String(active);
|
||||||
|
};
|
||||||
|
this.api.handler.ui
|
||||||
|
.isActiveTab()
|
||||||
|
.then(active => {
|
||||||
|
handleActiveTabChange(active);
|
||||||
|
this.api.events.ui.onActiveTabChanged(id => {
|
||||||
|
handleActiveTabChange(id === tabId);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
const handleResize = debounce(() => {
|
||||||
|
if (isActive) {
|
||||||
|
this.api.handler.ui.handleWindowResize().catch(console.error);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
window.addEventListener('dragstart', () => {
|
||||||
|
document.documentElement.dataset.dragging = 'true';
|
||||||
|
});
|
||||||
|
window.addEventListener('dragend', () => {
|
||||||
|
document.documentElement.dataset.dragging = 'false';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupAuthRequestEvent() {
|
||||||
|
this.events.ui.onAuthenticationRequest(({ method, payload }) => {
|
||||||
|
(async () => {
|
||||||
|
const authService = this.framework.get(AuthService);
|
||||||
|
if (!(await this.api.handler.ui.isActiveTab())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (method) {
|
||||||
|
case 'magic-link': {
|
||||||
|
const { email, token } = payload;
|
||||||
|
await authService.signInMagicLink(email, token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'oauth': {
|
||||||
|
const { code, state, provider } = payload;
|
||||||
|
await authService.signInOauth(code, state, provider);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})().catch(e => {
|
||||||
|
notify.error({
|
||||||
|
title: I18n['com.affine.auth.toast.title.failed'](),
|
||||||
|
message: (e as any).message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './desktop-api';
|
||||||
|
export * from './workspace-events';
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import type { DocsService } from '@toeverything/infra';
|
||||||
|
import { OnEvent, Service, WorkspaceInitialized } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { EditorSettingService } from '../../editor-setting';
|
||||||
|
import type { WorkbenchService } from '../../workbench';
|
||||||
|
import type { DesktopApiService } from './desktop-api';
|
||||||
|
|
||||||
|
// setup desktop events for workspace scope
|
||||||
|
@OnEvent(WorkspaceInitialized, e => e.setupApplicationMenuEvents)
|
||||||
|
export class WorkspaceDesktopApiService extends Service {
|
||||||
|
constructor(
|
||||||
|
private readonly desktopApi: DesktopApiService,
|
||||||
|
private readonly docsService: DocsService,
|
||||||
|
private readonly workbenchService: WorkbenchService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setupApplicationMenuEvents() {
|
||||||
|
this.desktopApi.events.applicationMenu.onNewPageAction(() => {
|
||||||
|
const editorSetting =
|
||||||
|
this.framework.get(EditorSettingService).editorSetting;
|
||||||
|
|
||||||
|
const docProps = {
|
||||||
|
note: editorSetting.get('affine:note'),
|
||||||
|
};
|
||||||
|
this.desktopApi.handler.ui
|
||||||
|
.isActiveTab()
|
||||||
|
.then(isActive => {
|
||||||
|
if (!isActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const page = this.docsService.createDoc({ docProps });
|
||||||
|
this.workbenchService.workbench.openDoc(page.id);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,4 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { apis } from '@affine/electron-api';
|
|
||||||
import { Entity, LiveData } from '@toeverything/infra';
|
import { Entity, LiveData } from '@toeverything/infra';
|
||||||
import {
|
import {
|
||||||
debounceTime,
|
debounceTime,
|
||||||
@@ -10,6 +9,8 @@ import {
|
|||||||
tap,
|
tap,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
|
import type { DesktopApiService } from '../../desktop-api';
|
||||||
|
|
||||||
const logger = new DebugLogger('affine:find-in-page');
|
const logger = new DebugLogger('affine:find-in-page');
|
||||||
|
|
||||||
export class FindInPage extends Entity {
|
export class FindInPage extends Entity {
|
||||||
@@ -38,10 +39,10 @@ export class FindInPage extends Entity {
|
|||||||
let findNext = true;
|
let findNext = true;
|
||||||
return this.direction$.pipe(
|
return this.direction$.pipe(
|
||||||
switchMap(direction => {
|
switchMap(direction => {
|
||||||
if (apis?.findInPage) {
|
if (this.electronApi?.handler?.findInPage) {
|
||||||
this.isSearching$.next(true);
|
this.isSearching$.next(true);
|
||||||
const currentId = ++searchId;
|
const currentId = ++searchId;
|
||||||
return apis?.findInPage
|
return this.electronApi.handler.findInPage
|
||||||
.find(searchText, {
|
.find(searchText, {
|
||||||
forward: direction === 'forward',
|
forward: direction === 'forward',
|
||||||
findNext,
|
findNext,
|
||||||
@@ -69,7 +70,7 @@ export class FindInPage extends Entity {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
constructor() {
|
constructor(private readonly electronApi: DesktopApiService) {
|
||||||
super();
|
super();
|
||||||
// TODO(@Peng): hide on navigation
|
// TODO(@Peng): hide on navigation
|
||||||
}
|
}
|
||||||
@@ -112,6 +113,6 @@ export class FindInPage extends Entity {
|
|||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
logger.debug('clear');
|
logger.debug('clear');
|
||||||
apis?.findInPage.clear().catch(logger.error);
|
this.electronApi.handler.findInPage.clear().catch(logger.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { Framework } from '@toeverything/infra';
|
import type { Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { FindInPage } from './entities/find-in-page';
|
import { FindInPage } from './entities/find-in-page';
|
||||||
import { FindInPageService } from './services/find-in-page';
|
import { FindInPageService } from './services/find-in-page';
|
||||||
|
|
||||||
export function configureFindInPageModule(framework: Framework) {
|
export function configureFindInPageModule(framework: Framework) {
|
||||||
framework.service(FindInPageService).entity(FindInPage);
|
framework.service(FindInPageService).entity(FindInPage, [DesktopApiService]);
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,9 @@ import { configureDocInfoModule } from './doc-info';
|
|||||||
import { configureDocLinksModule } from './doc-link';
|
import { configureDocLinksModule } from './doc-link';
|
||||||
import { configureDocsSearchModule } from './docs-search';
|
import { configureDocsSearchModule } from './docs-search';
|
||||||
import { configureEditorModule } from './editor';
|
import { configureEditorModule } from './editor';
|
||||||
import { configureEditorSettingModule } from './editor-settting';
|
import { configureEditorSettingModule } from './editor-setting';
|
||||||
import { configureExplorerModule } from './explorer';
|
import { configureExplorerModule } from './explorer';
|
||||||
import { configureFavoriteModule } from './favorite';
|
import { configureFavoriteModule } from './favorite';
|
||||||
import { configureFindInPageModule } from './find-in-page';
|
|
||||||
import { configureI18nModule } from './i18n';
|
import { configureI18nModule } from './i18n';
|
||||||
import { configureImportTemplateModule } from './import-template';
|
import { configureImportTemplateModule } from './import-template';
|
||||||
import { configureJournalModule } from './journal';
|
import { configureJournalModule } from './journal';
|
||||||
@@ -43,7 +42,6 @@ export function configureCommonModules(framework: Framework) {
|
|||||||
configureShareDocsModule(framework);
|
configureShareDocsModule(framework);
|
||||||
configureShareSettingModule(framework);
|
configureShareSettingModule(framework);
|
||||||
configureTelemetryModule(framework);
|
configureTelemetryModule(framework);
|
||||||
configureFindInPageModule(framework);
|
|
||||||
configurePeekViewModule(framework);
|
configurePeekViewModule(framework);
|
||||||
configureDocDisplayMetaModule(framework);
|
configureDocDisplayMetaModule(framework);
|
||||||
configureQuickSearchModule(framework);
|
configureQuickSearchModule(framework);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { EditorSettingService } from '../../editor-settting';
|
import { EditorSettingService } from '../../editor-setting';
|
||||||
import type { PeekViewAnimation, PeekViewMode } from '../entities/peek-view';
|
import type { PeekViewAnimation, PeekViewMode } from '../entities/peek-view';
|
||||||
import * as styles from './modal-container.css';
|
import * as styles from './modal-container.css';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Text } from '@blocksuite/affine/store';
|
|||||||
import type { DocProps, DocsService } from '@toeverything/infra';
|
import type { DocProps, DocsService } from '@toeverything/infra';
|
||||||
import { Service } from '@toeverything/infra';
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
import { EditorSettingService } from '../../editor-settting';
|
import { EditorSettingService } from '../../editor-setting';
|
||||||
import type { WorkbenchService } from '../../workbench';
|
import type { WorkbenchService } from '../../workbench';
|
||||||
import { CollectionsQuickSearchSession } from '../impls/collections';
|
import { CollectionsQuickSearchSession } from '../impls/collections';
|
||||||
import { CommandsQuickSearchSession } from '../impls/commands';
|
import { CommandsQuickSearchSession } from '../impls/commands';
|
||||||
|
|||||||
@@ -1,58 +1,66 @@
|
|||||||
import { sharedStorage } from '@affine/electron-api';
|
|
||||||
import type { GlobalCache, GlobalState } from '@toeverything/infra';
|
import type { GlobalCache, GlobalState } from '@toeverything/infra';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
import type { DesktopApiService } from '../../desktop-api';
|
||||||
const ensureSharedStorage = sharedStorage!;
|
|
||||||
|
|
||||||
export class ElectronGlobalState implements GlobalState {
|
export class ElectronGlobalState implements GlobalState {
|
||||||
|
constructor(private readonly electronApi: DesktopApiService) {}
|
||||||
|
|
||||||
keys(): string[] {
|
keys(): string[] {
|
||||||
return ensureSharedStorage.globalState.keys();
|
return this.electronApi.sharedStorage.globalState.keys();
|
||||||
}
|
}
|
||||||
get<T>(key: string): T | undefined {
|
get<T>(key: string): T | undefined {
|
||||||
return ensureSharedStorage.globalState.get(key);
|
return this.electronApi.sharedStorage.globalState.get(key);
|
||||||
}
|
}
|
||||||
watch<T>(key: string) {
|
watch<T>(key: string) {
|
||||||
return new Observable<T | undefined>(subscriber => {
|
return new Observable<T | undefined>(subscriber => {
|
||||||
const unsubscribe = ensureSharedStorage.globalState.watch<T>(key, i => {
|
const unsubscribe = this.electronApi.sharedStorage.globalState.watch<T>(
|
||||||
|
key,
|
||||||
|
i => {
|
||||||
subscriber.next(i);
|
subscriber.next(i);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
return () => unsubscribe();
|
return () => unsubscribe();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
set<T>(key: string, value: T): void {
|
set<T>(key: string, value: T): void {
|
||||||
ensureSharedStorage.globalState.set(key, value);
|
this.electronApi.sharedStorage.globalState.set(key, value);
|
||||||
}
|
}
|
||||||
del(key: string): void {
|
del(key: string): void {
|
||||||
ensureSharedStorage.globalState.del(key);
|
this.electronApi.sharedStorage.globalState.del(key);
|
||||||
}
|
}
|
||||||
clear(): void {
|
clear(): void {
|
||||||
ensureSharedStorage.globalState.clear();
|
this.electronApi.sharedStorage.globalState.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElectronGlobalCache implements GlobalCache {
|
export class ElectronGlobalCache implements GlobalCache {
|
||||||
|
constructor(private readonly electronApi: DesktopApiService) {}
|
||||||
|
|
||||||
keys(): string[] {
|
keys(): string[] {
|
||||||
return ensureSharedStorage.globalCache.keys();
|
return this.electronApi.sharedStorage.globalCache.keys();
|
||||||
}
|
}
|
||||||
get<T>(key: string): T | undefined {
|
get<T>(key: string): T | undefined {
|
||||||
return ensureSharedStorage.globalCache.get(key);
|
return this.electronApi.sharedStorage.globalCache.get(key);
|
||||||
}
|
}
|
||||||
watch<T>(key: string) {
|
watch<T>(key: string) {
|
||||||
return new Observable<T | undefined>(subscriber => {
|
return new Observable<T | undefined>(subscriber => {
|
||||||
const unsubscribe = ensureSharedStorage.globalCache.watch<T>(key, i => {
|
const unsubscribe = this.electronApi.sharedStorage.globalCache.watch<T>(
|
||||||
|
key,
|
||||||
|
i => {
|
||||||
subscriber.next(i);
|
subscriber.next(i);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
return () => unsubscribe();
|
return () => unsubscribe();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
set<T>(key: string, value: T): void {
|
set<T>(key: string, value: T): void {
|
||||||
ensureSharedStorage.globalCache.set(key, value);
|
this.electronApi.sharedStorage.globalCache.set(key, value);
|
||||||
}
|
}
|
||||||
del(key: string): void {
|
del(key: string): void {
|
||||||
ensureSharedStorage.globalCache.del(key);
|
this.electronApi.sharedStorage.globalCache.del(key);
|
||||||
}
|
}
|
||||||
clear(): void {
|
clear(): void {
|
||||||
ensureSharedStorage.globalCache.clear();
|
this.electronApi.sharedStorage.globalCache.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { type Framework, GlobalCache, GlobalState } from '@toeverything/infra';
|
import { type Framework, GlobalCache, GlobalState } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { ElectronGlobalCache, ElectronGlobalState } from './impls/electron';
|
import { ElectronGlobalCache, ElectronGlobalState } from './impls/electron';
|
||||||
import {
|
import {
|
||||||
LocalStorageGlobalCache,
|
LocalStorageGlobalCache,
|
||||||
@@ -12,6 +13,6 @@ export function configureLocalStorageStateStorageImpls(framework: Framework) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function configureElectronStateStorageImpls(framework: Framework) {
|
export function configureElectronStateStorageImpls(framework: Framework) {
|
||||||
framework.impl(GlobalCache, ElectronGlobalCache);
|
framework.impl(GlobalCache, ElectronGlobalCache, [DesktopApiService]);
|
||||||
framework.impl(GlobalState, ElectronGlobalState);
|
framework.impl(GlobalState, ElectronGlobalState, [DesktopApiService]);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { apis } from '@affine/electron-api';
|
|
||||||
import type {
|
import type {
|
||||||
ByteKV,
|
ByteKV,
|
||||||
ByteKVBehavior,
|
ByteKVBehavior,
|
||||||
@@ -8,6 +7,8 @@ import type {
|
|||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { AsyncLock } from '@toeverything/infra';
|
import { AsyncLock } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { DesktopApiService } from '../../desktop-api';
|
||||||
|
|
||||||
class BroadcastChannelDocEventBus implements DocEventBus {
|
class BroadcastChannelDocEventBus implements DocEventBus {
|
||||||
senderChannel = new BroadcastChannel('user-db:' + this.userId);
|
senderChannel = new BroadcastChannel('user-db:' + this.userId);
|
||||||
constructor(private readonly userId: string) {}
|
constructor(private readonly userId: string) {}
|
||||||
@@ -29,22 +30,26 @@ class BroadcastChannelDocEventBus implements DocEventBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SqliteUserspaceDocStorage implements DocStorage {
|
export class SqliteUserspaceDocStorage implements DocStorage {
|
||||||
constructor(private readonly userId: string) {}
|
constructor(
|
||||||
|
private readonly userId: string,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
eventBus = new BroadcastChannelDocEventBus(this.userId);
|
eventBus = new BroadcastChannelDocEventBus(this.userId);
|
||||||
readonly doc = new Doc(this.userId);
|
readonly doc = new Doc(this.userId, this.electronApi);
|
||||||
readonly syncMetadata = new SyncMetadataKV(this.userId);
|
readonly syncMetadata = new SyncMetadataKV(this.userId, this.electronApi);
|
||||||
readonly serverClock = new ServerClockKV(this.userId);
|
readonly serverClock = new ServerClockKV(this.userId, this.electronApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
type DocType = DocStorage['doc'];
|
type DocType = DocStorage['doc'];
|
||||||
|
|
||||||
class Doc implements DocType {
|
class Doc implements DocType {
|
||||||
lock = new AsyncLock();
|
lock = new AsyncLock();
|
||||||
constructor(private readonly userId: string) {
|
apis = this.electronApi.api.handler;
|
||||||
if (!apis?.db) {
|
|
||||||
throw new Error('sqlite datasource is not available');
|
constructor(
|
||||||
}
|
private readonly userId: string,
|
||||||
}
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
|
|
||||||
async transaction<T>(
|
async transaction<T>(
|
||||||
cb: (transaction: ByteKVBehavior) => Promise<T>
|
cb: (transaction: ByteKVBehavior) => Promise<T>
|
||||||
@@ -58,10 +63,7 @@ class Doc implements DocType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get(docId: string) {
|
async get(docId: string) {
|
||||||
if (!apis?.db) {
|
const update = await this.apis.db.getDocAsUpdates(
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
const update = await apis.db.getDocAsUpdates(
|
|
||||||
'userspace',
|
'userspace',
|
||||||
this.userId,
|
this.userId,
|
||||||
docId
|
docId
|
||||||
@@ -82,10 +84,7 @@ class Doc implements DocType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async set(docId: string, data: Uint8Array) {
|
async set(docId: string, data: Uint8Array) {
|
||||||
if (!apis?.db) {
|
await this.apis.db.applyDocUpdate('userspace', this.userId, data, docId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
await apis.db.applyDocUpdate('userspace', this.userId, data, docId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void | Promise<void> {
|
clear(): void | Promise<void> {
|
||||||
@@ -93,93 +92,68 @@ class Doc implements DocType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async del(docId: string) {
|
async del(docId: string) {
|
||||||
if (!apis?.db) {
|
await this.apis.db.deleteDoc('userspace', this.userId, docId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
await apis.db.deleteDoc('userspace', this.userId, docId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncMetadataKV implements ByteKV {
|
class SyncMetadataKV implements ByteKV {
|
||||||
constructor(private readonly userId: string) {}
|
apis = this.electronApi.api.handler;
|
||||||
|
constructor(
|
||||||
|
private readonly userId: string,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
||||||
return cb(this);
|
return cb(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getSyncMetadata('userspace', this.userId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getSyncMetadata('userspace', this.userId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, data: Uint8Array): void | Promise<void> {
|
set(key: string, data: Uint8Array): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.setSyncMetadata('userspace', this.userId, key, data);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.setSyncMetadata('userspace', this.userId, key, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keys(): string[] | Promise<string[]> {
|
keys(): string[] | Promise<string[]> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getSyncMetadataKeys('userspace', this.userId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getSyncMetadataKeys('userspace', this.userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
del(key: string): void | Promise<void> {
|
del(key: string): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.delSyncMetadata('userspace', this.userId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.delSyncMetadata('userspace', this.userId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void | Promise<void> {
|
clear(): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.clearSyncMetadata('userspace', this.userId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.clearSyncMetadata('userspace', this.userId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerClockKV implements ByteKV {
|
class ServerClockKV implements ByteKV {
|
||||||
constructor(private readonly userId: string) {}
|
apis = this.electronApi.api.handler;
|
||||||
|
constructor(
|
||||||
|
private readonly userId: string,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
||||||
return cb(this);
|
return cb(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getServerClock('userspace', this.userId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getServerClock('userspace', this.userId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, data: Uint8Array): void | Promise<void> {
|
set(key: string, data: Uint8Array): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.setServerClock('userspace', this.userId, key, data);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.setServerClock('userspace', this.userId, key, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keys(): string[] | Promise<string[]> {
|
keys(): string[] | Promise<string[]> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getServerClockKeys('userspace', this.userId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getServerClockKeys('userspace', this.userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
del(key: string): void | Promise<void> {
|
del(key: string): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.delServerClock('userspace', this.userId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.delServerClock('userspace', this.userId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void | Promise<void> {
|
clear(): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.clearServerClock('userspace', this.userId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.clearServerClock('userspace', this.userId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ export { UserspaceService as UserDBService } from './services/userspace';
|
|||||||
import type { Framework } from '@toeverything/infra';
|
import type { Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
import { AuthService, WebSocketService } from '../cloud';
|
import { AuthService, WebSocketService } from '../cloud';
|
||||||
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { CurrentUserDB } from './entities/current-user-db';
|
import { CurrentUserDB } from './entities/current-user-db';
|
||||||
import { UserDB } from './entities/user-db';
|
import { UserDB } from './entities/user-db';
|
||||||
import { UserDBEngine } from './entities/user-db-engine';
|
import { UserDBEngine } from './entities/user-db-engine';
|
||||||
@@ -32,9 +33,9 @@ export function configureIndexedDBUserspaceStorageProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function configureSqliteUserspaceStorageProvider(framework: Framework) {
|
export function configureSqliteUserspaceStorageProvider(framework: Framework) {
|
||||||
framework.impl(UserspaceStorageProvider, {
|
framework.impl(UserspaceStorageProvider, p => ({
|
||||||
getDocStorage(userId: string) {
|
getDocStorage(userId: string) {
|
||||||
return new SqliteUserspaceDocStorage(userId);
|
return new SqliteUserspaceDocStorage(userId, p.get(DesktopApiService));
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ export class Workbench extends Entity {
|
|||||||
show?: boolean;
|
show?: boolean;
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
this.newTabHandler({
|
this.newTabHandler.handle({
|
||||||
basename: this.basename$.value,
|
basename: this.basename$.value,
|
||||||
to,
|
to,
|
||||||
show: show ?? true,
|
show: show ?? true,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
WorkspaceScope,
|
WorkspaceScope,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { SidebarTab } from './entities/sidebar-tab';
|
import { SidebarTab } from './entities/sidebar-tab';
|
||||||
import { View } from './entities/view';
|
import { View } from './entities/view';
|
||||||
import { Workbench } from './entities/workbench';
|
import { Workbench } from './entities/workbench';
|
||||||
@@ -59,7 +60,10 @@ export function configureDesktopWorkbenchModule(services: Framework) {
|
|||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.impl(WorkbenchDefaultState, DesktopWorkbenchDefaultState, [
|
.impl(WorkbenchDefaultState, DesktopWorkbenchDefaultState, [
|
||||||
GlobalStateService,
|
GlobalStateService,
|
||||||
|
DesktopApiService,
|
||||||
])
|
])
|
||||||
.impl(WorkbenchNewTabHandler, () => DesktopWorkbenchNewTabHandler)
|
.impl(WorkbenchNewTabHandler, DesktopWorkbenchNewTabHandler, [
|
||||||
.service(DesktopStateSynchronizer, [WorkbenchService]);
|
DesktopApiService,
|
||||||
|
])
|
||||||
|
.service(DesktopStateSynchronizer, [WorkbenchService, DesktopApiService]);
|
||||||
}
|
}
|
||||||
+14
-10
@@ -1,13 +1,16 @@
|
|||||||
import { apis, appInfo, events } from '@affine/electron-api';
|
|
||||||
import { LiveData, Service } from '@toeverything/infra';
|
import { LiveData, Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { DesktopApiService } from '../../desktop-api';
|
||||||
import type { WorkbenchService } from '../../workbench';
|
import type { WorkbenchService } from '../../workbench';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronize workbench state with state stored in main process
|
* Synchronize workbench state with state stored in main process
|
||||||
*/
|
*/
|
||||||
export class DesktopStateSynchronizer extends Service {
|
export class DesktopStateSynchronizer extends Service {
|
||||||
constructor(private readonly workbenchService: WorkbenchService) {
|
constructor(
|
||||||
|
private readonly workbenchService: WorkbenchService,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.startSync();
|
this.startSync();
|
||||||
}
|
}
|
||||||
@@ -18,8 +21,9 @@ export class DesktopStateSynchronizer extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workbench = this.workbenchService.workbench;
|
const workbench = this.workbenchService.workbench;
|
||||||
|
const appInfo = this.electronApi.appInfo;
|
||||||
|
|
||||||
events?.ui.onTabAction(event => {
|
this.electronApi.events.ui.onTabAction(event => {
|
||||||
if (
|
if (
|
||||||
event.type === 'open-in-split-view' &&
|
event.type === 'open-in-split-view' &&
|
||||||
event.payload.tabId === appInfo?.viewId
|
event.payload.tabId === appInfo?.viewId
|
||||||
@@ -51,7 +55,7 @@ export class DesktopStateSynchronizer extends Service {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
events?.ui.onToggleRightSidebar(tabId => {
|
this.electronApi.events.ui.onToggleRightSidebar(tabId => {
|
||||||
if (tabId === appInfo?.viewId) {
|
if (tabId === appInfo?.viewId) {
|
||||||
workbench.sidebarOpen$.next(!workbench.sidebarOpen$.value);
|
workbench.sidebarOpen$.next(!workbench.sidebarOpen$.value);
|
||||||
}
|
}
|
||||||
@@ -74,11 +78,11 @@ export class DesktopStateSynchronizer extends Service {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}).subscribe(views => {
|
}).subscribe(views => {
|
||||||
if (!apis || !appInfo?.viewId) {
|
if (!appInfo?.viewId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
apis.ui
|
this.electronApi.handler.ui
|
||||||
.updateWorkbenchMeta(appInfo.viewId, {
|
.updateWorkbenchMeta(appInfo.viewId, {
|
||||||
views,
|
views,
|
||||||
})
|
})
|
||||||
@@ -86,11 +90,11 @@ export class DesktopStateSynchronizer extends Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
workbench.activeViewIndex$.subscribe(activeViewIndex => {
|
workbench.activeViewIndex$.subscribe(activeViewIndex => {
|
||||||
if (!apis || !appInfo?.viewId) {
|
if (!appInfo?.viewId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
apis.ui
|
this.electronApi.handler.ui
|
||||||
.updateWorkbenchMeta(appInfo.viewId, {
|
.updateWorkbenchMeta(appInfo.viewId, {
|
||||||
activeViewIndex: activeViewIndex,
|
activeViewIndex: activeViewIndex,
|
||||||
})
|
})
|
||||||
@@ -98,11 +102,11 @@ export class DesktopStateSynchronizer extends Service {
|
|||||||
});
|
});
|
||||||
|
|
||||||
workbench.basename$.subscribe(basename => {
|
workbench.basename$.subscribe(basename => {
|
||||||
if (!apis || !appInfo?.viewId) {
|
if (!appInfo?.viewId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
apis.ui
|
this.electronApi.handler.ui
|
||||||
.updateWorkbenchMeta(appInfo.viewId, {
|
.updateWorkbenchMeta(appInfo.viewId, {
|
||||||
basename: basename,
|
basename: basename,
|
||||||
})
|
})
|
||||||
|
|||||||
+20
-17
@@ -1,37 +1,40 @@
|
|||||||
import { apis } from '@affine/electron-api';
|
import { createIdentifier, Service } from '@toeverything/infra';
|
||||||
import { createIdentifier } from '@toeverything/infra';
|
|
||||||
import { parsePath, type To } from 'history';
|
import { parsePath, type To } from 'history';
|
||||||
|
|
||||||
export type WorkbenchNewTabHandler = (option: {
|
import type { DesktopApiService } from '../../desktop-api';
|
||||||
basename: string;
|
|
||||||
to: To;
|
export type WorkbenchNewTabHandler = {
|
||||||
show: boolean;
|
handle: (option: { basename: string; to: To; show: boolean }) => void;
|
||||||
}) => void;
|
};
|
||||||
|
|
||||||
export const WorkbenchNewTabHandler = createIdentifier<WorkbenchNewTabHandler>(
|
export const WorkbenchNewTabHandler = createIdentifier<WorkbenchNewTabHandler>(
|
||||||
'WorkbenchNewTabHandler'
|
'WorkbenchNewTabHandler'
|
||||||
);
|
);
|
||||||
|
|
||||||
export const BrowserWorkbenchNewTabHandler: WorkbenchNewTabHandler = ({
|
export const BrowserWorkbenchNewTabHandler: WorkbenchNewTabHandler = {
|
||||||
basename,
|
handle: ({ basename, to }) => {
|
||||||
to,
|
|
||||||
}) => {
|
|
||||||
const link =
|
const link =
|
||||||
basename +
|
basename +
|
||||||
(typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`);
|
(typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`);
|
||||||
window.open(link, '_blank');
|
window.open(link, '_blank');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DesktopWorkbenchNewTabHandler: WorkbenchNewTabHandler = ({
|
export class DesktopWorkbenchNewTabHandler
|
||||||
basename,
|
extends Service
|
||||||
to,
|
implements WorkbenchNewTabHandler
|
||||||
}) => {
|
{
|
||||||
|
constructor(private readonly electronApi: DesktopApiService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
handle({ basename, to }: { basename: string; to: To }) {
|
||||||
const path = typeof to === 'string' ? parsePath(to) : to;
|
const path = typeof to === 'string' ? parsePath(to) : to;
|
||||||
apis?.ui
|
this.electronApi.api.handler.ui
|
||||||
.addTab({
|
.addTab({
|
||||||
basename,
|
basename,
|
||||||
view: { path },
|
view: { path },
|
||||||
show: false,
|
show: false,
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { appInfo, type TabViewsMetaSchema } from '@affine/electron-api';
|
|
||||||
import type { GlobalStateService } from '@toeverything/infra';
|
import type { GlobalStateService } from '@toeverything/infra';
|
||||||
import { createIdentifier, Service } from '@toeverything/infra';
|
import { createIdentifier, Service } from '@toeverything/infra';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
import type { DesktopApiService, TabViewsMetaSchema } from '../../desktop-api';
|
||||||
import type { ViewIconName } from '../constants';
|
import type { ViewIconName } from '../constants';
|
||||||
|
|
||||||
export type WorkbenchDefaultState = {
|
export type WorkbenchDefaultState = {
|
||||||
@@ -34,7 +34,10 @@ export class DesktopWorkbenchDefaultState
|
|||||||
extends Service
|
extends Service
|
||||||
implements WorkbenchDefaultState
|
implements WorkbenchDefaultState
|
||||||
{
|
{
|
||||||
constructor(private readonly globalStateService: GlobalStateService) {
|
constructor(
|
||||||
|
private readonly globalStateService: GlobalStateService,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +48,9 @@ export class DesktopWorkbenchDefaultState
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
tabViewsMeta?.workbenches.find(w => w.id === appInfo?.viewId) ||
|
tabViewsMeta?.workbenches.find(
|
||||||
InMemoryWorkbenchDefaultState
|
w => w.id === this.electronApi.appInfo.viewId
|
||||||
|
) || InMemoryWorkbenchDefaultState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
import { apis } from '@affine/electron-api';
|
import type { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
|
||||||
import type { BlobStorage } from '@toeverything/infra';
|
import type { BlobStorage } from '@toeverything/infra';
|
||||||
|
|
||||||
import { bufferToBlob } from '../../utils/buffer-to-blob';
|
import { bufferToBlob } from '../../utils/buffer-to-blob';
|
||||||
|
|
||||||
export class SqliteBlobStorage implements BlobStorage {
|
export class SqliteBlobStorage implements BlobStorage {
|
||||||
constructor(private readonly workspaceId: string) {}
|
constructor(
|
||||||
|
private readonly workspaceId: string,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
name = 'sqlite';
|
name = 'sqlite';
|
||||||
readonly = false;
|
readonly = false;
|
||||||
async get(key: string) {
|
async get(key: string) {
|
||||||
assertExists(apis);
|
const buffer = await this.electronApi.handler.db.getBlob(
|
||||||
const buffer = await apis.db.getBlob('workspace', this.workspaceId, key);
|
'workspace',
|
||||||
|
this.workspaceId,
|
||||||
|
key
|
||||||
|
);
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
return bufferToBlob(buffer);
|
return bufferToBlob(buffer);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
async set(key: string, value: Blob) {
|
async set(key: string, value: Blob) {
|
||||||
assertExists(apis);
|
await this.electronApi.handler.db.addBlob(
|
||||||
await apis.db.addBlob(
|
|
||||||
'workspace',
|
'workspace',
|
||||||
this.workspaceId,
|
this.workspaceId,
|
||||||
key,
|
key,
|
||||||
@@ -27,11 +31,16 @@ export class SqliteBlobStorage implements BlobStorage {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
delete(key: string) {
|
delete(key: string) {
|
||||||
assertExists(apis);
|
return this.electronApi.handler.db.deleteBlob(
|
||||||
return apis.db.deleteBlob('workspace', this.workspaceId, key);
|
'workspace',
|
||||||
|
this.workspaceId,
|
||||||
|
key
|
||||||
|
);
|
||||||
}
|
}
|
||||||
list() {
|
list() {
|
||||||
assertExists(apis);
|
return this.electronApi.handler.db.getBlobKeys(
|
||||||
return apis.db.getBlobKeys('workspace', this.workspaceId);
|
'workspace',
|
||||||
|
this.workspaceId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
import { apis } from '@affine/electron-api';
|
import type { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||||
import type { ByteKV, ByteKVBehavior, DocStorage } from '@toeverything/infra';
|
import type { ByteKV, ByteKVBehavior, DocStorage } from '@toeverything/infra';
|
||||||
import { AsyncLock } from '@toeverything/infra';
|
import { AsyncLock } from '@toeverything/infra';
|
||||||
|
|
||||||
import { BroadcastChannelDocEventBus } from './doc-broadcast-channel';
|
import { BroadcastChannelDocEventBus } from './doc-broadcast-channel';
|
||||||
|
|
||||||
export class SqliteDocStorage implements DocStorage {
|
export class SqliteDocStorage implements DocStorage {
|
||||||
constructor(private readonly workspaceId: string) {}
|
constructor(
|
||||||
|
private readonly workspaceId: string,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
eventBus = new BroadcastChannelDocEventBus(this.workspaceId);
|
eventBus = new BroadcastChannelDocEventBus(this.workspaceId);
|
||||||
readonly doc = new Doc(this.workspaceId);
|
readonly doc = new Doc(this.workspaceId, this.electronApi);
|
||||||
readonly syncMetadata = new SyncMetadataKV(this.workspaceId);
|
readonly syncMetadata = new SyncMetadataKV(
|
||||||
readonly serverClock = new ServerClockKV(this.workspaceId);
|
this.workspaceId,
|
||||||
|
this.electronApi
|
||||||
|
);
|
||||||
|
readonly serverClock = new ServerClockKV(this.workspaceId, this.electronApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
type DocType = DocStorage['doc'];
|
type DocType = DocStorage['doc'];
|
||||||
|
|
||||||
class Doc implements DocType {
|
class Doc implements DocType {
|
||||||
lock = new AsyncLock();
|
lock = new AsyncLock();
|
||||||
constructor(private readonly workspaceId: string) {
|
apis = this.electronApi.handler;
|
||||||
if (!apis?.db) {
|
constructor(
|
||||||
throw new Error('sqlite datasource is not available');
|
private readonly workspaceId: string,
|
||||||
}
|
private readonly electronApi: DesktopApiService
|
||||||
}
|
) {}
|
||||||
|
|
||||||
async transaction<T>(
|
async transaction<T>(
|
||||||
cb: (transaction: ByteKVBehavior) => Promise<T>
|
cb: (transaction: ByteKVBehavior) => Promise<T>
|
||||||
@@ -34,10 +40,7 @@ class Doc implements DocType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get(docId: string) {
|
async get(docId: string) {
|
||||||
if (!apis?.db) {
|
const update = await this.apis.db.getDocAsUpdates(
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
const update = await apis.db.getDocAsUpdates(
|
|
||||||
'workspace',
|
'workspace',
|
||||||
this.workspaceId,
|
this.workspaceId,
|
||||||
docId
|
docId
|
||||||
@@ -58,10 +61,12 @@ class Doc implements DocType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async set(docId: string, data: Uint8Array) {
|
async set(docId: string, data: Uint8Array) {
|
||||||
if (!apis?.db) {
|
await this.apis.db.applyDocUpdate(
|
||||||
throw new Error('sqlite datasource is not available');
|
'workspace',
|
||||||
}
|
this.workspaceId,
|
||||||
await apis.db.applyDocUpdate('workspace', this.workspaceId, data, docId);
|
data,
|
||||||
|
docId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void | Promise<void> {
|
clear(): void | Promise<void> {
|
||||||
@@ -69,93 +74,78 @@ class Doc implements DocType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async del(docId: string) {
|
async del(docId: string) {
|
||||||
if (!apis?.db) {
|
await this.apis.db.deleteDoc('workspace', this.workspaceId, docId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
await apis.db.deleteDoc('workspace', this.workspaceId, docId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncMetadataKV implements ByteKV {
|
class SyncMetadataKV implements ByteKV {
|
||||||
constructor(private readonly workspaceId: string) {}
|
apis = this.electronApi.handler;
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceId: string,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
||||||
return cb(this);
|
return cb(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getSyncMetadata('workspace', this.workspaceId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getSyncMetadata('workspace', this.workspaceId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, data: Uint8Array): void | Promise<void> {
|
set(key: string, data: Uint8Array): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.setSyncMetadata(
|
||||||
throw new Error('sqlite datasource is not available');
|
'workspace',
|
||||||
}
|
this.workspaceId,
|
||||||
return apis.db.setSyncMetadata('workspace', this.workspaceId, key, data);
|
key,
|
||||||
|
data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
keys(): string[] | Promise<string[]> {
|
keys(): string[] | Promise<string[]> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getSyncMetadataKeys('workspace', this.workspaceId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getSyncMetadataKeys('workspace', this.workspaceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
del(key: string): void | Promise<void> {
|
del(key: string): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.delSyncMetadata('workspace', this.workspaceId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.delSyncMetadata('workspace', this.workspaceId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void | Promise<void> {
|
clear(): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.clearSyncMetadata('workspace', this.workspaceId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.clearSyncMetadata('workspace', this.workspaceId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerClockKV implements ByteKV {
|
class ServerClockKV implements ByteKV {
|
||||||
constructor(private readonly workspaceId: string) {}
|
apis = this.electronApi.handler;
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceId: string,
|
||||||
|
private readonly electronApi: DesktopApiService
|
||||||
|
) {}
|
||||||
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
transaction<T>(cb: (behavior: ByteKVBehavior) => Promise<T>): Promise<T> {
|
||||||
return cb(this);
|
return cb(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
get(key: string): Uint8Array | null | Promise<Uint8Array | null> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getServerClock('workspace', this.workspaceId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getServerClock('workspace', this.workspaceId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, data: Uint8Array): void | Promise<void> {
|
set(key: string, data: Uint8Array): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.setServerClock(
|
||||||
throw new Error('sqlite datasource is not available');
|
'workspace',
|
||||||
}
|
this.workspaceId,
|
||||||
return apis.db.setServerClock('workspace', this.workspaceId, key, data);
|
key,
|
||||||
|
data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
keys(): string[] | Promise<string[]> {
|
keys(): string[] | Promise<string[]> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.getServerClockKeys('workspace', this.workspaceId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.getServerClockKeys('workspace', this.workspaceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
del(key: string): void | Promise<void> {
|
del(key: string): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.delServerClock('workspace', this.workspaceId, key);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.delServerClock('workspace', this.workspaceId, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void | Promise<void> {
|
clear(): void | Promise<void> {
|
||||||
if (!apis?.db) {
|
return this.apis.db.clearServerClock('workspace', this.workspaceId);
|
||||||
throw new Error('sqlite datasource is not available');
|
|
||||||
}
|
|
||||||
return apis.db.clearServerClock('workspace', this.workspaceId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { apis } from '@affine/electron-api';
|
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { DocCollection } from '@blocksuite/affine/store';
|
import { DocCollection } from '@blocksuite/affine/store';
|
||||||
import type {
|
import type {
|
||||||
@@ -20,6 +19,7 @@ import { nanoid } from 'nanoid';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||||
|
|
||||||
|
import { DesktopApiService } from '../../desktop-api';
|
||||||
import type { WorkspaceEngineStorageProvider } from '../providers/engine';
|
import type { WorkspaceEngineStorageProvider } from '../providers/engine';
|
||||||
import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel';
|
import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel';
|
||||||
import { StaticBlobStorage } from './engine/blob-static';
|
import { StaticBlobStorage } from './engine/blob-static';
|
||||||
@@ -72,8 +72,10 @@ export class LocalWorkspaceFlavourProvider
|
|||||||
async deleteWorkspace(id: string): Promise<void> {
|
async deleteWorkspace(id: string): Promise<void> {
|
||||||
setLocalWorkspaceIds(ids => ids.filter(x => x !== id));
|
setLocalWorkspaceIds(ids => ids.filter(x => x !== id));
|
||||||
|
|
||||||
if (BUILD_CONFIG.isElectron && apis) {
|
const electronApi = this.framework.getOptional(DesktopApiService);
|
||||||
await apis.workspace.delete(id);
|
|
||||||
|
if (BUILD_CONFIG.isElectron && electronApi) {
|
||||||
|
await electronApi.handler.workspace.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify all browser tabs, so they can update their workspace list
|
// notify all browser tabs, so they can update their workspace list
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
WorkspaceFlavourProvider,
|
WorkspaceFlavourProvider,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { CloudWorkspaceFlavourProviderService } from './impls/cloud';
|
import { CloudWorkspaceFlavourProviderService } from './impls/cloud';
|
||||||
import { IndexedDBBlobStorage } from './impls/engine/blob-indexeddb';
|
import { IndexedDBBlobStorage } from './impls/engine/blob-indexeddb';
|
||||||
import { SqliteBlobStorage } from './impls/engine/blob-sqlite';
|
import { SqliteBlobStorage } from './impls/engine/blob-sqlite';
|
||||||
@@ -57,13 +58,16 @@ export function configureIndexedDBWorkspaceEngineStorageProvider(
|
|||||||
export function configureSqliteWorkspaceEngineStorageProvider(
|
export function configureSqliteWorkspaceEngineStorageProvider(
|
||||||
framework: Framework
|
framework: Framework
|
||||||
) {
|
) {
|
||||||
framework.impl(WorkspaceEngineStorageProvider, {
|
framework.impl(WorkspaceEngineStorageProvider, p => {
|
||||||
|
const electronApi = p.get(DesktopApiService);
|
||||||
|
return {
|
||||||
getDocStorage(workspaceId: string) {
|
getDocStorage(workspaceId: string) {
|
||||||
return new SqliteDocStorage(workspaceId);
|
return new SqliteDocStorage(workspaceId, electronApi);
|
||||||
},
|
},
|
||||||
getBlobStorage(workspaceId: string) {
|
getBlobStorage(workspaceId: string) {
|
||||||
return new SqliteBlobStorage(workspaceId);
|
return new SqliteBlobStorage(workspaceId, electronApi);
|
||||||
},
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import type {
|
|||||||
handlers as mainHandlers,
|
handlers as mainHandlers,
|
||||||
} from '@affine/electron/main/exposed';
|
} from '@affine/electron/main/exposed';
|
||||||
import type { appInfo as exposedAppInfo } from '@affine/electron/preload/electron-api';
|
import type { appInfo as exposedAppInfo } from '@affine/electron/preload/electron-api';
|
||||||
import type { sharedStorage as exposedSharedStorage } from '@affine/electron/preload/shared-storage';
|
import type { SharedStorage } from '@affine/electron/preload/shared-storage';
|
||||||
|
|
||||||
type MainHandlers = typeof mainHandlers;
|
type MainHandlers = typeof mainHandlers;
|
||||||
type HelperHandlers = typeof helperHandlers;
|
type HelperHandlers = typeof helperHandlers;
|
||||||
type HelperEvents = typeof helperEvents;
|
type HelperEvents = typeof helperEvents;
|
||||||
type MainEvents = typeof mainEvents;
|
type MainEvents = typeof mainEvents;
|
||||||
type ClientHandler = {
|
export type ClientHandler = {
|
||||||
[namespace in keyof MainHandlers]: {
|
[namespace in keyof MainHandlers]: {
|
||||||
[method in keyof MainHandlers[namespace]]: MainHandlers[namespace][method] extends (
|
[method in keyof MainHandlers[namespace]]: MainHandlers[namespace][method] extends (
|
||||||
arg0: any,
|
arg0: any,
|
||||||
@@ -27,17 +27,19 @@ type ClientHandler = {
|
|||||||
: never;
|
: never;
|
||||||
};
|
};
|
||||||
} & HelperHandlers;
|
} & HelperHandlers;
|
||||||
type ClientEvents = MainEvents & HelperEvents;
|
export type ClientEvents = MainEvents & HelperEvents;
|
||||||
|
|
||||||
export const appInfo = (globalThis as any).__appInfo as
|
export const appInfo = (globalThis as any).__appInfo as
|
||||||
| typeof exposedAppInfo
|
| typeof exposedAppInfo
|
||||||
| null;
|
| null;
|
||||||
export const apis = (globalThis as any).__apis as ClientHandler | null;
|
export const apis = (globalThis as any).__apis as ClientHandler | undefined;
|
||||||
export const events = (globalThis as any).__events as ClientEvents | null;
|
export const events = (globalThis as any).__events as ClientEvents | undefined;
|
||||||
|
|
||||||
export const sharedStorage = (globalThis as any).__sharedStorage as
|
export const sharedStorage = (globalThis as any).__sharedStorage as
|
||||||
| typeof exposedSharedStorage
|
| SharedStorage
|
||||||
| null;
|
| undefined;
|
||||||
|
|
||||||
|
export type { SharedStorage };
|
||||||
|
|
||||||
export type { UpdateMeta } from '@affine/electron/main/updater/event';
|
export type { UpdateMeta } from '@affine/electron/main/updater/event';
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -529,6 +529,7 @@ __metadata:
|
|||||||
link-preview-js: "npm:^3.0.5"
|
link-preview-js: "npm:^3.0.5"
|
||||||
lodash-es: "npm:^4.17.21"
|
lodash-es: "npm:^4.17.21"
|
||||||
nanoid: "npm:^5.0.7"
|
nanoid: "npm:^5.0.7"
|
||||||
|
next-themes: "npm:^0.3.0"
|
||||||
react: "npm:^18.2.0"
|
react: "npm:^18.2.0"
|
||||||
react-dom: "npm:^18.2.0"
|
react-dom: "npm:^18.2.0"
|
||||||
react-router-dom: "npm:^6.22.3"
|
react-router-dom: "npm:^6.22.3"
|
||||||
|
|||||||
Reference in new issue
Block a user