@@ -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();
|
||||
}
|
||||
}
|
||||
19
packages/frontend/core/src/modules/desktop-api/impl/index.ts
Normal file
19
packages/frontend/core/src/modules/desktop-api/impl/index.ts
Normal file
@@ -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!;
|
||||
}
|
||||
27
packages/frontend/core/src/modules/desktop-api/index.ts
Normal file
27
packages/frontend/core/src/modules/desktop-api/index.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user