mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 13:02:21 +08:00
@@ -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);
|
||||||
open: (url: string) => {
|
configureDesktopApiModule(framework);
|
||||||
apis?.ui.openExternal(url).catch(e => {
|
|
||||||
console.error('Failed to open external URL', e);
|
framework.impl(PopupWindowProvider, p => {
|
||||||
});
|
const apis = p.get(DesktopApiService).api;
|
||||||
},
|
return {
|
||||||
|
open: (url: string) => {
|
||||||
|
apis.handler.ui.openExternal(url).catch(e => {
|
||||||
|
console.error('Failed to open external URL', e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
framework.impl(ClientSchemeProvider, {
|
framework.impl(ClientSchemeProvider, p => {
|
||||||
getClientScheme() {
|
const appInfo = p.get(DesktopApiService).appInfo;
|
||||||
return appInfo?.scheme;
|
return {
|
||||||
},
|
getClientScheme() {
|
||||||
|
return appInfo?.scheme;
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
framework.impl(ValidatorProvider, {
|
framework.impl(ValidatorProvider, p => {
|
||||||
async validate(_challenge, resource) {
|
const apis = p.get(DesktopApiService).api;
|
||||||
const token = await apis?.ui?.getChallengeResponse(resource);
|
return {
|
||||||
if (!token) {
|
async validate(_challenge, resource) {
|
||||||
throw new Error('Challenge failed');
|
const token = await apis.handler.ui.getChallengeResponse(resource);
|
||||||
}
|
if (!token) {
|
||||||
return token;
|
throw new Error('Challenge failed');
|
||||||
},
|
}
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>(
|
||||||
subscriber.next(i);
|
key,
|
||||||
});
|
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>(
|
||||||
subscriber.next(i);
|
key,
|
||||||
});
|
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,
|
||||||
})
|
})
|
||||||
|
|||||||
+31
-28
@@ -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 =
|
||||||
}) => {
|
basename +
|
||||||
const link =
|
(typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`);
|
||||||
basename +
|
window.open(link, '_blank');
|
||||||
(typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`);
|
},
|
||||||
window.open(link, '_blank');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DesktopWorkbenchNewTabHandler: WorkbenchNewTabHandler = ({
|
export class DesktopWorkbenchNewTabHandler
|
||||||
basename,
|
extends Service
|
||||||
to,
|
implements WorkbenchNewTabHandler
|
||||||
}) => {
|
{
|
||||||
const path = typeof to === 'string' ? parsePath(to) : to;
|
constructor(private readonly electronApi: DesktopApiService) {
|
||||||
apis?.ui
|
super();
|
||||||
.addTab({
|
}
|
||||||
basename,
|
handle({ basename, to }: { basename: string; to: To }) {
|
||||||
view: { path },
|
const path = typeof to === 'string' ? parsePath(to) : to;
|
||||||
show: false,
|
this.electronApi.api.handler.ui
|
||||||
})
|
.addTab({
|
||||||
.catch(console.error);
|
basename,
|
||||||
};
|
view: { path },
|
||||||
|
show: false,
|
||||||
|
})
|
||||||
|
.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 => {
|
||||||
getDocStorage(workspaceId: string) {
|
const electronApi = p.get(DesktopApiService);
|
||||||
return new SqliteDocStorage(workspaceId);
|
return {
|
||||||
},
|
getDocStorage(workspaceId: string) {
|
||||||
getBlobStorage(workspaceId: string) {
|
return new SqliteDocStorage(workspaceId, electronApi);
|
||||||
return new SqliteBlobStorage(workspaceId);
|
},
|
||||||
},
|
getBlobStorage(workspaceId: string) {
|
||||||
|
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