mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor(electron): create electron api package (#5334)
This commit is contained in:
@@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@toeverything/infra",
|
"name": "@toeverything/infra",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/index.js",
|
|
||||||
"main": "./dist/index.cjs",
|
|
||||||
"types": "./dist/src/index.d.ts",
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
|
||||||
"types": "./dist/src/index.d.ts",
|
|
||||||
"import": "./dist/index.js",
|
|
||||||
"require": "./dist/index.cjs"
|
|
||||||
},
|
|
||||||
"./blocksuite": {
|
"./blocksuite": {
|
||||||
"types": "./dist/src/blocksuite/index.d.ts",
|
"types": "./dist/src/blocksuite/index.d.ts",
|
||||||
"import": "./dist/blocksuite.js",
|
"import": "./dist/blocksuite.js",
|
||||||
@@ -20,26 +12,11 @@
|
|||||||
"import": "./dist/command.js",
|
"import": "./dist/command.js",
|
||||||
"require": "./dist/command.cjs"
|
"require": "./dist/command.cjs"
|
||||||
},
|
},
|
||||||
"./core/*": {
|
|
||||||
"types": "./dist/src/core/*.d.ts",
|
|
||||||
"import": "./dist/core/*.js",
|
|
||||||
"require": "./dist/core/*.cjs"
|
|
||||||
},
|
|
||||||
"./preload/*": {
|
|
||||||
"types": "./dist/src/preload/*.d.ts",
|
|
||||||
"import": "./dist/preload/*.js",
|
|
||||||
"require": "./dist/preload/*.cjs"
|
|
||||||
},
|
|
||||||
"./atom": {
|
"./atom": {
|
||||||
"type": "./dist/src/atom.d.ts",
|
"type": "./dist/src/atom.d.ts",
|
||||||
"import": "./dist/atom.js",
|
"import": "./dist/atom.js",
|
||||||
"require": "./dist/atom.cjs"
|
"require": "./dist/atom.cjs"
|
||||||
},
|
},
|
||||||
"./type": {
|
|
||||||
"type": "./dist/src/type.d.ts",
|
|
||||||
"import": "./dist/type.js",
|
|
||||||
"require": "./dist/type.cjs"
|
|
||||||
},
|
|
||||||
"./app-config-storage": {
|
"./app-config-storage": {
|
||||||
"type": "./dist/src/app-config-storage.d.ts",
|
"type": "./dist/src/app-config-storage.d.ts",
|
||||||
"import": "./dist/app-config-storage.js",
|
"import": "./dist/app-config-storage.js",
|
||||||
|
|||||||
3
packages/common/infra/preload/electron.d.ts
vendored
3
packages/common/infra/preload/electron.d.ts
vendored
@@ -1,3 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// @ts-ignore
|
|
||||||
export * from '../dist/src/preload/electron';
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
/// <reference types="../dist/preload/electron.d.ts" />
|
|
||||||
export * from '../dist/preload/electron.js';
|
|
||||||
@@ -72,12 +72,13 @@ const appSettingEffect = atomEffect(get => {
|
|||||||
// some values in settings should be synced into electron side
|
// some values in settings should be synced into electron side
|
||||||
if (environment.isDesktop) {
|
if (environment.isDesktop) {
|
||||||
console.log('set config', settings);
|
console.log('set config', settings);
|
||||||
window.apis?.updater
|
// this api type in @affine/electron-api, but it is circular dependency this package, use any here
|
||||||
|
(window as any).apis?.updater
|
||||||
.setConfig({
|
.setConfig({
|
||||||
autoCheckUpdate: settings.autoCheckUpdate,
|
autoCheckUpdate: settings.autoCheckUpdate,
|
||||||
autoDownloadUpdate: settings.autoDownloadUpdate,
|
autoDownloadUpdate: settings.autoDownloadUpdate,
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err: any) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2018 Andy Wermke
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
export type EventMap = {
|
|
||||||
[key: string]: (...args: any[]) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-safe event emitter.
|
|
||||||
*
|
|
||||||
* Use it like this:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* type MyEvents = {
|
|
||||||
* error: (error: Error) => void;
|
|
||||||
* message: (from: string, content: string) => void;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* const myEmitter = new EventEmitter() as TypedEmitter<MyEvents>;
|
|
||||||
*
|
|
||||||
* myEmitter.emit("error", "x") // <- Will catch this type error;
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Lifecycle:
|
|
||||||
* invoke -> handle -> emit -> on/once
|
|
||||||
*/
|
|
||||||
export interface TypedEventEmitter<Events extends EventMap> {
|
|
||||||
addListener<E extends keyof Events>(event: E, listener: Events[E]): this;
|
|
||||||
on<E extends keyof Events>(event: E, listener: Events[E]): this;
|
|
||||||
once<E extends keyof Events>(event: E, listener: Events[E]): this;
|
|
||||||
|
|
||||||
off<E extends keyof Events>(event: E, listener: Events[E]): this;
|
|
||||||
removeAllListeners<E extends keyof Events>(event?: E): this;
|
|
||||||
removeListener<E extends keyof Events>(event: E, listener: Events[E]): this;
|
|
||||||
|
|
||||||
emit<E extends keyof Events>(
|
|
||||||
event: E,
|
|
||||||
...args: Parameters<Events[E]>
|
|
||||||
): boolean;
|
|
||||||
// The sloppy `eventNames()` return type is to mitigate type incompatibilities - see #5
|
|
||||||
eventNames(): (keyof Events | string | symbol)[];
|
|
||||||
rawListeners<E extends keyof Events>(event: E): Events[E][];
|
|
||||||
listeners<E extends keyof Events>(event: E): Events[E][];
|
|
||||||
listenerCount<E extends keyof Events>(event: E): number;
|
|
||||||
|
|
||||||
handle<E extends keyof Events>(event: E, handler: Events[E]): this;
|
|
||||||
invoke<E extends keyof Events>(
|
|
||||||
event: E,
|
|
||||||
...args: Parameters<Events[E]>
|
|
||||||
): Promise<ReturnType<Events[E]>>;
|
|
||||||
|
|
||||||
getMaxListeners(): number;
|
|
||||||
setMaxListeners(maxListeners: number): this;
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import type {
|
|
||||||
ClipboardHandlers,
|
|
||||||
ConfigStorageHandlers,
|
|
||||||
DBHandlers,
|
|
||||||
DebugHandlers,
|
|
||||||
DialogHandlers,
|
|
||||||
ExportHandlers,
|
|
||||||
UIHandlers,
|
|
||||||
UpdaterHandlers,
|
|
||||||
WorkspaceHandlers,
|
|
||||||
} from './type.js';
|
|
||||||
import { HandlerManager } from './type.js';
|
|
||||||
|
|
||||||
export abstract class DBHandlerManager extends HandlerManager<
|
|
||||||
'db',
|
|
||||||
DBHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class DebugHandlerManager extends HandlerManager<
|
|
||||||
'debug',
|
|
||||||
DebugHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class DialogHandlerManager extends HandlerManager<
|
|
||||||
'dialog',
|
|
||||||
DialogHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class UIHandlerManager extends HandlerManager<
|
|
||||||
'ui',
|
|
||||||
UIHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class ClipboardHandlerManager extends HandlerManager<
|
|
||||||
'clipboard',
|
|
||||||
ClipboardHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class ExportHandlerManager extends HandlerManager<
|
|
||||||
'export',
|
|
||||||
ExportHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class UpdaterHandlerManager extends HandlerManager<
|
|
||||||
'updater',
|
|
||||||
UpdaterHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class WorkspaceHandlerManager extends HandlerManager<
|
|
||||||
'workspace',
|
|
||||||
WorkspaceHandlers
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export abstract class ConfigStorageHandlerManager extends HandlerManager<
|
|
||||||
'configStorage',
|
|
||||||
ConfigStorageHandlers
|
|
||||||
> {}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './handler.js';
|
|
||||||
export * from './type.js';
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
import type Buffer from 'buffer';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import type { AppConfigSchema } from './app-config-storage.js';
|
|
||||||
import type { TypedEventEmitter } from './core/event-emitter.js';
|
|
||||||
|
|
||||||
type Buffer = Buffer.Buffer;
|
|
||||||
|
|
||||||
export const packageJsonInputSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
version: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
affinePlugin: z.object({
|
|
||||||
release: z.union([z.boolean(), z.enum(['development'])]),
|
|
||||||
entry: z.object({
|
|
||||||
core: z.string(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const packageJsonOutputSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
version: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
affinePlugin: z.object({
|
|
||||||
release: z.union([z.boolean(), z.enum(['development'])]),
|
|
||||||
entry: z.object({
|
|
||||||
core: z.string(),
|
|
||||||
}),
|
|
||||||
assets: z.array(z.string()),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export abstract class HandlerManager<
|
|
||||||
Namespace extends string,
|
|
||||||
Handlers extends Record<string, PrimitiveHandlers>,
|
|
||||||
> {
|
|
||||||
static instance: HandlerManager<string, Record<string, PrimitiveHandlers>>;
|
|
||||||
private readonly _app: App<Namespace, Handlers>;
|
|
||||||
private readonly _namespace: Namespace;
|
|
||||||
private _handlers: Handlers;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
|
|
||||||
private _initialized = false;
|
|
||||||
|
|
||||||
registerHandlers(handlers: Handlers) {
|
|
||||||
if (this._initialized) {
|
|
||||||
throw new Error('Already initialized');
|
|
||||||
}
|
|
||||||
this._handlers = handlers;
|
|
||||||
for (const [name, handler] of Object.entries(this._handlers)) {
|
|
||||||
this._app.handle(`${this._namespace}:${name}`, (async (...args: any[]) =>
|
|
||||||
handler(...args)) as any);
|
|
||||||
}
|
|
||||||
this._initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
invokeHandler<K extends keyof Handlers>(
|
|
||||||
name: K,
|
|
||||||
...args: Parameters<Handlers[K]>
|
|
||||||
): Promise<ReturnType<Handlers[K]>> {
|
|
||||||
return this._handlers[name](...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getInstance(): HandlerManager<
|
|
||||||
string,
|
|
||||||
Record<string, PrimitiveHandlers>
|
|
||||||
> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkspaceMeta {
|
|
||||||
id: string;
|
|
||||||
mainDBPath: string;
|
|
||||||
secondaryDBPath?: string; // assume there will be only one
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PrimitiveHandlers = (...args: any[]) => Promise<any>;
|
|
||||||
|
|
||||||
export type DBHandlers = {
|
|
||||||
getDocAsUpdates: (
|
|
||||||
workspaceId: string,
|
|
||||||
subdocId?: string
|
|
||||||
) => Promise<Uint8Array | false>;
|
|
||||||
applyDocUpdate: (
|
|
||||||
id: string,
|
|
||||||
update: Uint8Array,
|
|
||||||
subdocId?: string
|
|
||||||
) => Promise<void>;
|
|
||||||
addBlob: (
|
|
||||||
workspaceId: string,
|
|
||||||
key: string,
|
|
||||||
data: Uint8Array
|
|
||||||
) => Promise<void>;
|
|
||||||
getBlob: (workspaceId: string, key: string) => Promise<Buffer | null>;
|
|
||||||
deleteBlob: (workspaceId: string, key: string) => Promise<void>;
|
|
||||||
getBlobKeys: (workspaceId: string) => Promise<string[]>;
|
|
||||||
getDefaultStorageLocation: () => Promise<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DebugHandlers = {
|
|
||||||
revealLogFile: () => Promise<string>;
|
|
||||||
logFilePath: () => Promise<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ErrorMessage =
|
|
||||||
| 'DB_FILE_ALREADY_LOADED'
|
|
||||||
| 'DB_FILE_PATH_INVALID'
|
|
||||||
| 'DB_FILE_INVALID'
|
|
||||||
| 'DB_FILE_MIGRATION_FAILED'
|
|
||||||
| 'FILE_ALREADY_EXISTS'
|
|
||||||
| 'UNKNOWN_ERROR';
|
|
||||||
|
|
||||||
export interface LoadDBFileResult {
|
|
||||||
workspaceId?: string;
|
|
||||||
error?: ErrorMessage;
|
|
||||||
canceled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveDBFileResult {
|
|
||||||
filePath?: string;
|
|
||||||
canceled?: boolean;
|
|
||||||
error?: ErrorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectDBFileLocationResult {
|
|
||||||
filePath?: string;
|
|
||||||
error?: ErrorMessage;
|
|
||||||
canceled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MoveDBFileResult {
|
|
||||||
filePath?: string;
|
|
||||||
error?: ErrorMessage;
|
|
||||||
canceled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// provide a backdoor to set dialog path for testing in playwright
|
|
||||||
export interface FakeDialogResult {
|
|
||||||
canceled?: boolean;
|
|
||||||
filePath?: string;
|
|
||||||
filePaths?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DialogHandlers = {
|
|
||||||
revealDBFile: (workspaceId: string) => Promise<void>;
|
|
||||||
loadDBFile: () => Promise<LoadDBFileResult>;
|
|
||||||
saveDBFileAs: (workspaceId: string) => Promise<SaveDBFileResult>;
|
|
||||||
moveDBFile: (
|
|
||||||
workspaceId: string,
|
|
||||||
dbFileLocation?: string
|
|
||||||
) => Promise<MoveDBFileResult>;
|
|
||||||
selectDBFileLocation: () => Promise<SelectDBFileLocationResult>;
|
|
||||||
setFakeDialogResult: (result: any) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UIHandlers = {
|
|
||||||
handleThemeChange: (theme: 'system' | 'light' | 'dark') => Promise<any>;
|
|
||||||
handleSidebarVisibilityChange: (visible: boolean) => Promise<any>;
|
|
||||||
handleMinimizeApp: () => Promise<any>;
|
|
||||||
handleMaximizeApp: () => Promise<any>;
|
|
||||||
handleCloseApp: () => Promise<any>;
|
|
||||||
getGoogleOauthCode: () => Promise<any>;
|
|
||||||
getChallengeResponse: (resource: string) => Promise<string>;
|
|
||||||
handleOpenMainApp: () => Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ClipboardHandlers = {
|
|
||||||
copyAsImageFromString: (dataURL: string) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ExportHandlers = {
|
|
||||||
savePDFFileAs: (title: string) => Promise<any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface UpdateMeta {
|
|
||||||
version: string;
|
|
||||||
allowAutoUpdate: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UpdaterConfig = {
|
|
||||||
autoCheckUpdate: boolean;
|
|
||||||
autoDownloadUpdate: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpdaterHandlers = {
|
|
||||||
currentVersion: () => Promise<string>;
|
|
||||||
quitAndInstall: () => Promise<void>;
|
|
||||||
downloadUpdate: () => Promise<void>;
|
|
||||||
getConfig: () => Promise<UpdaterConfig>;
|
|
||||||
setConfig: (newConfig: Partial<UpdaterConfig>) => Promise<void>;
|
|
||||||
checkForUpdates: () => Promise<{ version: string } | null>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type WorkspaceHandlers = {
|
|
||||||
list: () => Promise<[workspaceId: string, meta: WorkspaceMeta][]>;
|
|
||||||
delete: (id: string) => Promise<void>;
|
|
||||||
getMeta: (id: string) => Promise<WorkspaceMeta>;
|
|
||||||
clone: (id: string, newId: string) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ConfigStorageHandlers = {
|
|
||||||
set: (config: AppConfigSchema | Partial<AppConfigSchema>) => Promise<void>;
|
|
||||||
get: () => Promise<AppConfigSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UnwrapManagerHandlerToServerSide<
|
|
||||||
ElectronEvent extends {
|
|
||||||
frameId: number;
|
|
||||||
processId: number;
|
|
||||||
},
|
|
||||||
Manager extends HandlerManager<string, Record<string, PrimitiveHandlers>>,
|
|
||||||
> = Manager extends HandlerManager<infer _, infer Handlers>
|
|
||||||
? {
|
|
||||||
[K in keyof Handlers]: Handlers[K] extends (
|
|
||||||
...args: infer Args
|
|
||||||
) => Promise<infer R>
|
|
||||||
? (event: ElectronEvent, ...args: Args) => Promise<R>
|
|
||||||
: never;
|
|
||||||
}
|
|
||||||
: never;
|
|
||||||
|
|
||||||
export type UnwrapManagerHandlerToClientSide<
|
|
||||||
Manager extends HandlerManager<string, Record<string, PrimitiveHandlers>>,
|
|
||||||
> = Manager extends HandlerManager<infer _, infer Handlers>
|
|
||||||
? {
|
|
||||||
[K in keyof Handlers]: Handlers[K] extends (
|
|
||||||
...args: infer Args
|
|
||||||
) => Promise<infer R>
|
|
||||||
? (...args: Args) => Promise<R>
|
|
||||||
: never;
|
|
||||||
}
|
|
||||||
: never;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export type App<
|
|
||||||
Namespace extends string,
|
|
||||||
Handlers extends Record<string, PrimitiveHandlers>,
|
|
||||||
> = TypedEventEmitter<{
|
|
||||||
[K in keyof Handlers as `${Namespace}:${K & string}`]: Handlers[K];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export interface UpdaterEvents {
|
|
||||||
onUpdateAvailable: (fn: (versionMeta: UpdateMeta) => void) => () => void;
|
|
||||||
onUpdateReady: (fn: (versionMeta: UpdateMeta) => void) => () => void;
|
|
||||||
onDownloadProgress: (fn: (progress: number) => void) => () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApplicationMenuEvents {
|
|
||||||
onNewPageAction: (fn: () => void) => () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DBEvents {
|
|
||||||
onExternalUpdate: (
|
|
||||||
fn: (update: {
|
|
||||||
workspaceId: string;
|
|
||||||
update: Uint8Array;
|
|
||||||
docId?: string;
|
|
||||||
}) => void
|
|
||||||
) => () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkspaceEvents {
|
|
||||||
onMetaChange: (
|
|
||||||
fn: (workspaceId: string, meta: WorkspaceMeta) => void
|
|
||||||
) => () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UIEvents {
|
|
||||||
onMaximized: (fn: (maximized: boolean) => void) => () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventMap {
|
|
||||||
updater: UpdaterEvents;
|
|
||||||
applicationMenu: ApplicationMenuEvents;
|
|
||||||
db: DBEvents;
|
|
||||||
ui: UIEvents;
|
|
||||||
workspace: WorkspaceEvents;
|
|
||||||
}
|
|
||||||
@@ -12,12 +12,8 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: {
|
entry: {
|
||||||
blocksuite: resolve(root, 'src/blocksuite/index.ts'),
|
blocksuite: resolve(root, 'src/blocksuite/index.ts'),
|
||||||
index: resolve(root, 'src/index.ts'),
|
|
||||||
atom: resolve(root, 'src/atom/index.ts'),
|
atom: resolve(root, 'src/atom/index.ts'),
|
||||||
command: resolve(root, 'src/command/index.ts'),
|
command: resolve(root, 'src/command/index.ts'),
|
||||||
type: resolve(root, 'src/type.ts'),
|
|
||||||
'core/event-emitter': resolve(root, 'src/core/event-emitter.ts'),
|
|
||||||
'preload/electron': resolve(root, 'src/preload/electron.ts'),
|
|
||||||
'app-config-storage': resolve(root, 'src/app-config-storage.ts'),
|
'app-config-storage': resolve(root, 'src/app-config-storage.ts'),
|
||||||
},
|
},
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
|
"@affine/electron-api": "workspace:*",
|
||||||
"@affine/graphql": "workspace:*",
|
"@affine/graphql": "workspace:*",
|
||||||
"@affine/i18n": "workspace:*",
|
"@affine/i18n": "workspace:*",
|
||||||
"@affine/workspace": "workspace:*",
|
"@affine/workspace": "workspace:*",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import { ThemeProvider as NextThemeProvider, useTheme } 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';
|
import { memo, useRef } from 'react';
|
||||||
@@ -10,7 +11,7 @@ const DesktopThemeSync = memo(function DesktopThemeSync() {
|
|||||||
const onceRef = useRef(false);
|
const onceRef = useRef(false);
|
||||||
if (lastThemeRef.current !== theme || !onceRef.current) {
|
if (lastThemeRef.current !== theme || !onceRef.current) {
|
||||||
if (environment.isDesktop && theme) {
|
if (environment.isDesktop && theme) {
|
||||||
window.apis?.ui
|
apis?.ui
|
||||||
.handleThemeChange(theme as 'dark' | 'light' | 'system')
|
.handleThemeChange(theme as 'dark' | 'light' | 'system')
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../frontend/hooks"
|
"path": "../../frontend/hooks"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../frontend/electron-api"
|
||||||
|
},
|
||||||
{ "path": "../../frontend/workspace" },
|
{ "path": "../../frontend/workspace" },
|
||||||
{
|
{
|
||||||
"path": "../../common/debug"
|
"path": "../../common/debug"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"@affine/cmdk": "workspace:*",
|
"@affine/cmdk": "workspace:*",
|
||||||
"@affine/component": "workspace:*",
|
"@affine/component": "workspace:*",
|
||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
|
"@affine/electron-api": "workspace:*",
|
||||||
"@affine/env": "workspace:*",
|
"@affine/env": "workspace:*",
|
||||||
"@affine/graphql": "workspace:*",
|
"@affine/graphql": "workspace:*",
|
||||||
"@affine/i18n": "workspace:*",
|
"@affine/i18n": "workspace:*",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { ResetIcon } from '@blocksuite/icons';
|
import { ResetIcon } from '@blocksuite/icons';
|
||||||
import { updateReadyAtom } from '@toeverything/hooks/use-app-updater';
|
import { updateReadyAtom } from '@toeverything/hooks/use-app-updater';
|
||||||
@@ -21,7 +22,7 @@ export function registerAffineUpdatesCommands({
|
|||||||
label: t['com.affine.cmdk.affine.restart-to-upgrade'](),
|
label: t['com.affine.cmdk.affine.restart-to-upgrade'](),
|
||||||
preconditionStrategy: () => !!store.get(updateReadyAtom),
|
preconditionStrategy: () => !!store.get(updateReadyAtom),
|
||||||
run() {
|
run() {
|
||||||
window.apis?.updater.quitAndInstall().catch(err => {
|
apis?.updater.quitAndInstall().catch(err => {
|
||||||
// TODO: add error toast here
|
// TODO: add error toast here
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import { fetchWithTraceReport } from '@affine/graphql';
|
import { fetchWithTraceReport } from '@affine/graphql';
|
||||||
import { Turnstile } from '@marsidev/react-turnstile';
|
import { Turnstile } from '@marsidev/react-turnstile';
|
||||||
import { atom, useAtom, useSetAtom } from 'jotai';
|
import { atom, useAtom, useSetAtom } from 'jotai';
|
||||||
@@ -32,7 +33,7 @@ const generateChallengeResponse = async (challenge: string) => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await window.apis?.ui?.getChallengeResponse(challenge);
|
return await apis?.ui?.getChallengeResponse(challenge);
|
||||||
};
|
};
|
||||||
|
|
||||||
const captchaAtom = atom<string | undefined>(undefined);
|
const captchaAtom = atom<string | undefined>(undefined);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
} from '@affine/component/ui/modal';
|
} from '@affine/component/ui/modal';
|
||||||
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 { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { workspaceManagerAtom } from '@affine/workspace/atom';
|
import { workspaceManagerAtom } from '@affine/workspace/atom';
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
buildShowcaseWorkspace,
|
buildShowcaseWorkspace,
|
||||||
initEmptyPage,
|
initEmptyPage,
|
||||||
} from '@toeverything/infra/blocksuite';
|
} from '@toeverything/infra/blocksuite';
|
||||||
import type { LoadDBFileResult } from '@toeverything/infra/type';
|
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import type { KeyboardEvent } from 'react';
|
import type { KeyboardEvent } from 'react';
|
||||||
import { useLayoutEffect } from 'react';
|
import { useLayoutEffect } from 'react';
|
||||||
@@ -112,12 +112,12 @@ export const CreateWorkspaceModal = ({
|
|||||||
// 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 (!window.apis) {
|
if (!apis) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info('load db file');
|
logger.info('load db file');
|
||||||
setStep(undefined);
|
setStep(undefined);
|
||||||
const result: LoadDBFileResult = await window.apis.dialog.loadDBFile();
|
const result = await apis.dialog.loadDBFile();
|
||||||
if (result.workspaceId && !canceled) {
|
if (result.workspaceId && !canceled) {
|
||||||
workspaceManager._addLocalWorkspace(result.workspaceId);
|
workspaceManager._addLocalWorkspace(result.workspaceId);
|
||||||
onCreate(result.workspaceId);
|
onCreate(result.workspaceId);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { pushNotificationAtom } from '@affine/component/notification-center';
|
import { pushNotificationAtom } from '@affine/component/notification-center';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { Workspace, WorkspaceMetadata } from '@affine/workspace';
|
import type { Workspace, WorkspaceMetadata } from '@affine/workspace';
|
||||||
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
|
||||||
import type { SaveDBFileResult } from '@toeverything/infra/type';
|
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
@@ -30,8 +30,7 @@ export const ExportPanel = ({
|
|||||||
try {
|
try {
|
||||||
await workspace.engine.sync.waitForSynced();
|
await workspace.engine.sync.waitForSynced();
|
||||||
await workspace.engine.blob.sync();
|
await workspace.engine.blob.sync();
|
||||||
const result: SaveDBFileResult =
|
const result = await apis?.dialog.saveDBFileAs(workspaceId);
|
||||||
await window.apis?.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) {
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { FlexWrapper, toast } from '@affine/component';
|
|||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||||
|
import { apis, events } from '@affine/electron-api';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
|
||||||
import type { MoveDBFileResult } from '@toeverything/infra/type';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
const useDBFileSecondaryPath = (workspaceId: string) => {
|
const useDBFileSecondaryPath = (workspaceId: string) => {
|
||||||
const [path, setPath] = useState<string | undefined>(undefined);
|
const [path, setPath] = useState<string | undefined>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.apis && window.events && environment.isDesktop) {
|
if (apis && events && environment.isDesktop) {
|
||||||
window.apis?.workspace
|
apis?.workspace
|
||||||
.getMeta(workspaceId)
|
.getMeta(workspaceId)
|
||||||
.then(meta => {
|
.then(meta => {
|
||||||
setPath(meta.secondaryDBPath);
|
setPath(meta.secondaryDBPath);
|
||||||
@@ -20,7 +20,7 @@ const useDBFileSecondaryPath = (workspaceId: string) => {
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
return window.events.workspace.onMetaChange((newMeta: any) => {
|
return events.workspace.onMetaChange((newMeta: any) => {
|
||||||
if (newMeta.workspaceId === workspaceId) {
|
if (newMeta.workspaceId === workspaceId) {
|
||||||
const meta = newMeta.meta;
|
const meta = newMeta.meta;
|
||||||
setPath(meta.secondaryDBPath);
|
setPath(meta.secondaryDBPath);
|
||||||
@@ -43,7 +43,7 @@ export const StoragePanel = ({ workspaceMetadata }: StoragePanelProps) => {
|
|||||||
|
|
||||||
const [moveToInProgress, setMoveToInProgress] = useState<boolean>(false);
|
const [moveToInProgress, setMoveToInProgress] = useState<boolean>(false);
|
||||||
const onRevealDBFile = useCallback(() => {
|
const onRevealDBFile = useCallback(() => {
|
||||||
window.apis?.dialog.revealDBFile(workspaceId).catch(err => {
|
apis?.dialog.revealDBFile(workspaceId).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, [workspaceId]);
|
}, [workspaceId]);
|
||||||
@@ -53,9 +53,9 @@ export const StoragePanel = ({ workspaceMetadata }: StoragePanelProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setMoveToInProgress(true);
|
setMoveToInProgress(true);
|
||||||
window.apis?.dialog
|
apis?.dialog
|
||||||
.moveDBFile(workspaceId)
|
.moveDBFile(workspaceId)
|
||||||
.then((result: MoveDBFileResult) => {
|
.then(result => {
|
||||||
if (!result?.error && !result?.canceled) {
|
if (!result?.error && !result?.canceled) {
|
||||||
toast(t['Move folder success']());
|
toast(t['Move folder success']());
|
||||||
} else if (result?.error) {
|
} else if (result?.error) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apis, events } from '@affine/electron-api';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { atomWithObservable } from 'jotai/utils';
|
import { atomWithObservable } from 'jotai/utils';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
@@ -8,7 +9,7 @@ import * as style from './style.css';
|
|||||||
const maximizedAtom = atomWithObservable(() => {
|
const maximizedAtom = atomWithObservable(() => {
|
||||||
return new Observable<boolean>(subscriber => {
|
return new Observable<boolean>(subscriber => {
|
||||||
subscriber.next(false);
|
subscriber.next(false);
|
||||||
return window.events?.ui.onMaximized(maximized => {
|
return events?.ui.onMaximized(maximized => {
|
||||||
return subscriber.next(maximized);
|
return subscriber.next(maximized);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -76,17 +77,17 @@ const unmaximizedSVG = (
|
|||||||
|
|
||||||
export const WindowsAppControls = () => {
|
export const WindowsAppControls = () => {
|
||||||
const handleMinimizeApp = useCallback(() => {
|
const handleMinimizeApp = useCallback(() => {
|
||||||
window.apis?.ui.handleMinimizeApp().catch(err => {
|
apis?.ui.handleMinimizeApp().catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
const handleMaximizeApp = useCallback(() => {
|
const handleMaximizeApp = useCallback(() => {
|
||||||
window.apis?.ui.handleMaximizeApp().catch(err => {
|
apis?.ui.handleMaximizeApp().catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
const handleCloseApp = useCallback(() => {
|
const handleCloseApp = useCallback(() => {
|
||||||
window.apis?.ui.handleCloseApp().catch(err => {
|
apis?.ui.handleCloseApp().catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from '@affine/component/page-list';
|
} from '@affine/component/page-list';
|
||||||
import { Menu } from '@affine/component/ui/menu';
|
import { Menu } from '@affine/component/ui/menu';
|
||||||
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
|
||||||
|
import { apis, events } from '@affine/electron-api';
|
||||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import type { Workspace } from '@affine/workspace';
|
import type { Workspace } from '@affine/workspace';
|
||||||
@@ -141,7 +142,7 @@ export const RootAppSidebar = ({
|
|||||||
// Listen to the "New Page" action from the menu
|
// Listen to the "New Page" action from the menu
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (environment.isDesktop) {
|
if (environment.isDesktop) {
|
||||||
return window.events?.applicationMenu.onNewPageAction(onClickNewPage);
|
return events?.applicationMenu.onNewPageAction(onClickNewPage);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}, [onClickNewPage]);
|
}, [onClickNewPage]);
|
||||||
@@ -149,7 +150,7 @@ export const RootAppSidebar = ({
|
|||||||
const sidebarOpen = useAtomValue(appSidebarOpenAtom);
|
const sidebarOpen = useAtomValue(appSidebarOpenAtom);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (environment.isDesktop) {
|
if (environment.isDesktop) {
|
||||||
window.apis?.ui.handleSidebarVisibilityChange(sidebarOpen).catch(err => {
|
apis?.ui.handleSidebarVisibilityChange(sidebarOpen).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
resolveGlobalLoadingEventAtom,
|
resolveGlobalLoadingEventAtom,
|
||||||
} from '@affine/component/global-loading';
|
} from '@affine/component/global-loading';
|
||||||
import { pushNotificationAtom } from '@affine/component/notification-center';
|
import { pushNotificationAtom } from '@affine/component/notification-center';
|
||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import {
|
import {
|
||||||
HtmlTransformer,
|
HtmlTransformer,
|
||||||
@@ -49,7 +50,7 @@ async function exportHandler({ page, type }: ExportHandlerOptions) {
|
|||||||
break;
|
break;
|
||||||
case 'pdf':
|
case 'pdf':
|
||||||
if (environment.isDesktop && page.meta.mode === 'page') {
|
if (environment.isDesktop && page.meta.mode === 'page') {
|
||||||
await window.apis?.export.savePDFFileAs(
|
await apis?.export.savePDFFileAs(
|
||||||
(page.root as PageBlockModel).title.toString()
|
(page.root as PageBlockModel).title.toString()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import {
|
import {
|
||||||
type AppConfigSchema,
|
type AppConfigSchema,
|
||||||
AppConfigStorage,
|
AppConfigStorage,
|
||||||
@@ -13,11 +15,13 @@ class AppConfigProxy {
|
|||||||
value: AppConfigSchema = defaultAppConfig;
|
value: AppConfigSchema = defaultAppConfig;
|
||||||
|
|
||||||
async getSync(): Promise<AppConfigSchema> {
|
async getSync(): Promise<AppConfigSchema> {
|
||||||
return (this.value = await window.apis.configStorage.get());
|
assertExists(apis);
|
||||||
|
return (this.value = await apis.configStorage.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSync(): Promise<void> {
|
async setSync(): Promise<void> {
|
||||||
await window.apis.configStorage.set(this.value);
|
assertExists(apis);
|
||||||
|
await apis.configStorage.set(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(): AppConfigSchema {
|
get(): AppConfigSchema {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { redirect } from 'react-router-dom';
|
import { redirect } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -23,7 +25,8 @@ export const Component = () => {
|
|||||||
|
|
||||||
const openApp = useCallback(() => {
|
const openApp = useCallback(() => {
|
||||||
if (environment.isDesktop) {
|
if (environment.isDesktop) {
|
||||||
window.apis.ui.handleOpenMainApp().catch(err => {
|
assertExists(apis);
|
||||||
|
apis.ui.handleOpenMainApp().catch(err => {
|
||||||
console.log('failed to open main app', err);
|
console.log('failed to open main app', err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../frontend/workspace"
|
"path": "../../frontend/workspace"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../frontend/electron-api"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../common/debug"
|
"path": "../../common/debug"
|
||||||
},
|
},
|
||||||
|
|||||||
14
packages/frontend/electron-api/package.json
Normal file
14
packages/frontend/electron-api/package.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@affine/electron-api",
|
||||||
|
"version": "0.10.3-canary.2",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@toeverything/infra": "workspace:*",
|
||||||
|
"electron": "^27.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
37
packages/frontend/electron-api/src/index.ts
Normal file
37
packages/frontend/electron-api/src/index.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type {
|
||||||
|
events as helperEvents,
|
||||||
|
handlers as helperHandlers,
|
||||||
|
} from '@affine/electron/helper/exposed';
|
||||||
|
import type {
|
||||||
|
events as mainEvents,
|
||||||
|
handlers as mainHandlers,
|
||||||
|
} from '@affine/electron/main/exposed';
|
||||||
|
import type {
|
||||||
|
affine as exposedAffineGlobal,
|
||||||
|
appInfo as exposedAppInfo,
|
||||||
|
} from '@affine/electron/preload/electron-api';
|
||||||
|
|
||||||
|
type MainHandlers = typeof mainHandlers;
|
||||||
|
type HelperHandlers = typeof helperHandlers;
|
||||||
|
type HelperEvents = typeof helperEvents;
|
||||||
|
type MainEvents = typeof mainEvents;
|
||||||
|
type ClientHandler = {
|
||||||
|
[namespace in keyof MainHandlers]: {
|
||||||
|
[method in keyof MainHandlers[namespace]]: MainHandlers[namespace][method] extends (
|
||||||
|
arg0: any,
|
||||||
|
...rest: infer A
|
||||||
|
) => any
|
||||||
|
? (...args: A) => Promise<ReturnType<MainHandlers[namespace][method]>>
|
||||||
|
: never;
|
||||||
|
};
|
||||||
|
} & HelperHandlers;
|
||||||
|
type ClientEvents = MainEvents & HelperEvents;
|
||||||
|
|
||||||
|
export const appInfo = (window as any).appInfo as typeof exposedAppInfo | null;
|
||||||
|
export const apis = (window as any).apis as ClientHandler | null;
|
||||||
|
export const events = (window as any).events as ClientEvents | null;
|
||||||
|
export const affine = (window as any).affine as
|
||||||
|
| typeof exposedAffineGlobal
|
||||||
|
| null;
|
||||||
|
|
||||||
|
export type { UpdateMeta } from '@affine/electron/main/updater/event';
|
||||||
17
packages/frontend/electron-api/tsconfig.json
Normal file
17
packages/frontend/electron-api/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.json",
|
||||||
|
"include": ["./src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"noEmit": false,
|
||||||
|
"outDir": "lib"
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../common/infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../frontend/electron"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,13 +2,6 @@ import path from 'node:path';
|
|||||||
|
|
||||||
import { ValidationResult } from '@affine/native';
|
import { ValidationResult } from '@affine/native';
|
||||||
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
||||||
import type {
|
|
||||||
FakeDialogResult,
|
|
||||||
LoadDBFileResult,
|
|
||||||
MoveDBFileResult,
|
|
||||||
SaveDBFileResult,
|
|
||||||
SelectDBFileLocationResult,
|
|
||||||
} from '@toeverything/infra/type';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
@@ -28,6 +21,45 @@ import {
|
|||||||
getWorkspacesBasePath,
|
getWorkspacesBasePath,
|
||||||
} from '../workspace/meta';
|
} from '../workspace/meta';
|
||||||
|
|
||||||
|
export type ErrorMessage =
|
||||||
|
| 'DB_FILE_ALREADY_LOADED'
|
||||||
|
| 'DB_FILE_PATH_INVALID'
|
||||||
|
| 'DB_FILE_INVALID'
|
||||||
|
| 'DB_FILE_MIGRATION_FAILED'
|
||||||
|
| 'FILE_ALREADY_EXISTS'
|
||||||
|
| 'UNKNOWN_ERROR';
|
||||||
|
|
||||||
|
export interface LoadDBFileResult {
|
||||||
|
workspaceId?: string;
|
||||||
|
error?: ErrorMessage;
|
||||||
|
canceled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveDBFileResult {
|
||||||
|
filePath?: string;
|
||||||
|
canceled?: boolean;
|
||||||
|
error?: ErrorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectDBFileLocationResult {
|
||||||
|
filePath?: string;
|
||||||
|
error?: ErrorMessage;
|
||||||
|
canceled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MoveDBFileResult {
|
||||||
|
filePath?: string;
|
||||||
|
error?: ErrorMessage;
|
||||||
|
canceled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// provide a backdoor to set dialog path for testing in playwright
|
||||||
|
export interface FakeDialogResult {
|
||||||
|
canceled?: boolean;
|
||||||
|
filePath?: string;
|
||||||
|
filePaths?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// we are using native dialogs because HTML dialogs do not give full file paths
|
// we are using native dialogs because HTML dialogs do not give full file paths
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
import type {
|
|
||||||
DBHandlers,
|
|
||||||
DialogHandlers,
|
|
||||||
WorkspaceHandlers,
|
|
||||||
} from '@toeverything/infra/type';
|
|
||||||
|
|
||||||
import { dbEvents, dbHandlers } from './db';
|
import { dbEvents, dbHandlers } from './db';
|
||||||
import { dialogHandlers } from './dialog';
|
import { dialogHandlers } from './dialog';
|
||||||
import { provideExposed } from './provide';
|
import { provideExposed } from './provide';
|
||||||
import { workspaceEvents, workspaceHandlers } from './workspace';
|
import { workspaceEvents, workspaceHandlers } from './workspace';
|
||||||
|
|
||||||
type AllHandlers = {
|
|
||||||
db: DBHandlers;
|
|
||||||
workspace: WorkspaceHandlers;
|
|
||||||
dialog: DialogHandlers;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handlers = {
|
export const handlers = {
|
||||||
db: dbHandlers,
|
db: dbHandlers,
|
||||||
workspace: workspaceHandlers,
|
workspace: workspaceHandlers,
|
||||||
dialog: dialogHandlers,
|
dialog: dialogHandlers,
|
||||||
} satisfies AllHandlers;
|
};
|
||||||
|
|
||||||
export const events = {
|
export const events = {
|
||||||
db: dbEvents,
|
db: dbEvents,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { RendererToHelper } from '@toeverything/infra/preload/electron';
|
|
||||||
import { AsyncCall } from 'async-call-rpc';
|
import { AsyncCall } from 'async-call-rpc';
|
||||||
|
|
||||||
|
import type { RendererToHelper } from '../shared/type';
|
||||||
import { events, handlers } from './exposed';
|
import { events, handlers } from './exposed';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type {
|
|
||||||
HelperToMain,
|
|
||||||
MainToHelper,
|
|
||||||
} from '@toeverything/infra/preload/electron';
|
|
||||||
import { AsyncCall } from 'async-call-rpc';
|
import { AsyncCall } from 'async-call-rpc';
|
||||||
|
|
||||||
|
import type { HelperToMain, MainToHelper } from '../shared/type';
|
||||||
import { exposed } from './provide';
|
import { exposed } from './provide';
|
||||||
|
|
||||||
const helperToMainServer: HelperToMain = {
|
const helperToMainServer: HelperToMain = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ExposedMeta } from '@toeverything/infra/preload/electron';
|
import type { ExposedMeta } from '../shared/type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A naive DI implementation to get rid of circular dependency.
|
* A naive DI implementation to get rid of circular dependency.
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
import type {
|
|
||||||
ClipboardHandlerManager,
|
|
||||||
ConfigStorageHandlerManager,
|
|
||||||
DebugHandlerManager,
|
|
||||||
ExportHandlerManager,
|
|
||||||
UIHandlerManager,
|
|
||||||
UnwrapManagerHandlerToServerSide,
|
|
||||||
UpdaterHandlerManager,
|
|
||||||
} from '@toeverything/infra/index';
|
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
|
|
||||||
import { clipboardHandlers } from './clipboard';
|
import { clipboardHandlers } from './clipboard';
|
||||||
@@ -25,33 +16,6 @@ export const debugHandlers = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type AllHandlers = {
|
|
||||||
debug: UnwrapManagerHandlerToServerSide<
|
|
||||||
Electron.IpcMainInvokeEvent,
|
|
||||||
DebugHandlerManager
|
|
||||||
>;
|
|
||||||
clipboard: UnwrapManagerHandlerToServerSide<
|
|
||||||
Electron.IpcMainInvokeEvent,
|
|
||||||
ClipboardHandlerManager
|
|
||||||
>;
|
|
||||||
export: UnwrapManagerHandlerToServerSide<
|
|
||||||
Electron.IpcMainInvokeEvent,
|
|
||||||
ExportHandlerManager
|
|
||||||
>;
|
|
||||||
ui: UnwrapManagerHandlerToServerSide<
|
|
||||||
Electron.IpcMainInvokeEvent,
|
|
||||||
UIHandlerManager
|
|
||||||
>;
|
|
||||||
updater: UnwrapManagerHandlerToServerSide<
|
|
||||||
Electron.IpcMainInvokeEvent,
|
|
||||||
UpdaterHandlerManager
|
|
||||||
>;
|
|
||||||
configStorage: UnwrapManagerHandlerToServerSide<
|
|
||||||
Electron.IpcMainInvokeEvent,
|
|
||||||
ConfigStorageHandlerManager
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Note: all of these handlers will be the single-source-of-truth for the apis exposed to the renderer process
|
// Note: all of these handlers will be the single-source-of-truth for the apis exposed to the renderer process
|
||||||
export const allHandlers = {
|
export const allHandlers = {
|
||||||
debug: debugHandlers,
|
debug: debugHandlers,
|
||||||
@@ -60,7 +24,7 @@ export const allHandlers = {
|
|||||||
export: exportHandlers,
|
export: exportHandlers,
|
||||||
updater: updaterHandlers,
|
updater: updaterHandlers,
|
||||||
configStorage: configStorageHandlers,
|
configStorage: configStorageHandlers,
|
||||||
} satisfies AllHandlers;
|
};
|
||||||
|
|
||||||
export const registerHandlers = () => {
|
export const registerHandlers = () => {
|
||||||
// TODO: listen to namespace instead of individual event types
|
// TODO: listen to namespace instead of individual event types
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import type {
|
|
||||||
HelperToMain,
|
|
||||||
MainToHelper,
|
|
||||||
} from '@toeverything/infra/preload/electron';
|
|
||||||
import { type _AsyncVersionOf, AsyncCall } from 'async-call-rpc';
|
import { type _AsyncVersionOf, AsyncCall } from 'async-call-rpc';
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
@@ -15,6 +11,7 @@ import {
|
|||||||
type WebContents,
|
type WebContents,
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
|
|
||||||
|
import type { HelperToMain, MainToHelper } from '../shared/type';
|
||||||
import { MessageEventChannel } from '../shared/utils';
|
import { MessageEventChannel } from '../shared/utils';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,15 @@
|
|||||||
import { contextBridge, ipcRenderer } from 'electron';
|
import { contextBridge } from 'electron';
|
||||||
|
|
||||||
(async () => {
|
import { affine, appInfo, getElectronAPIs } from './electron-api';
|
||||||
const { appInfo, getElectronAPIs } = await import(
|
|
||||||
'@toeverything/infra/preload/electron'
|
|
||||||
);
|
|
||||||
const { apis, events } = getElectronAPIs();
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('appInfo', appInfo);
|
const { apis, events } = getElectronAPIs();
|
||||||
contextBridge.exposeInMainWorld('apis', apis);
|
|
||||||
contextBridge.exposeInMainWorld('events', events);
|
|
||||||
|
|
||||||
// Credit to microsoft/vscode
|
contextBridge.exposeInMainWorld('appInfo', appInfo);
|
||||||
const globals = {
|
contextBridge.exposeInMainWorld('apis', apis);
|
||||||
ipcRenderer: {
|
contextBridge.exposeInMainWorld('events', events);
|
||||||
send(channel: string, ...args: any[]) {
|
|
||||||
ipcRenderer.send(channel, ...args);
|
|
||||||
},
|
|
||||||
|
|
||||||
invoke(channel: string, ...args: any[]) {
|
try {
|
||||||
return ipcRenderer.invoke(channel, ...args);
|
contextBridge.exposeInMainWorld('affine', affine);
|
||||||
},
|
} catch (error) {
|
||||||
|
console.error('Failed to expose affine APIs to window object!', error);
|
||||||
on(
|
}
|
||||||
channel: string,
|
|
||||||
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
|
||||||
) {
|
|
||||||
ipcRenderer.on(channel, listener);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
once(
|
|
||||||
channel: string,
|
|
||||||
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
|
||||||
) {
|
|
||||||
ipcRenderer.once(channel, listener);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
removeListener(
|
|
||||||
channel: string,
|
|
||||||
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
|
||||||
) {
|
|
||||||
ipcRenderer.removeListener(channel, listener);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
contextBridge.exposeInMainWorld('affine', globals);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to expose affine APIs to window object!', error);
|
|
||||||
}
|
|
||||||
})().catch(err => {
|
|
||||||
console.error('Failed to bootstrap preload script!', err);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,37 +1,50 @@
|
|||||||
// Please add modules to `external` in `rollupOptions` to avoid wrong bundling.
|
// Please add modules to `external` in `rollupOptions` to avoid wrong bundling.
|
||||||
import { AsyncCall, type EventBasedChannel } from 'async-call-rpc';
|
import { AsyncCall, type EventBasedChannel } from 'async-call-rpc';
|
||||||
import type { app, dialog, shell } from 'electron';
|
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export interface ExposedMeta {
|
import type {
|
||||||
handlers: [string, string[]][];
|
ExposedMeta,
|
||||||
events: [string, string[]][];
|
HelperToRenderer,
|
||||||
}
|
RendererToHelper,
|
||||||
|
} from '../shared/type';
|
||||||
|
|
||||||
// render <-> helper
|
export const affine = {
|
||||||
export interface RendererToHelper {
|
ipcRenderer: {
|
||||||
postEvent: (channel: string, ...args: any[]) => void;
|
send(channel: string, ...args: any[]) {
|
||||||
}
|
ipcRenderer.send(channel, ...args);
|
||||||
|
},
|
||||||
|
|
||||||
export interface HelperToRenderer {
|
invoke(channel: string, ...args: any[]) {
|
||||||
[key: string]: (...args: any[]) => Promise<any>;
|
return ipcRenderer.invoke(channel, ...args);
|
||||||
}
|
},
|
||||||
|
|
||||||
// helper <-> main
|
on(
|
||||||
export interface HelperToMain {
|
channel: string,
|
||||||
getMeta: () => ExposedMeta;
|
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
||||||
}
|
) {
|
||||||
|
ipcRenderer.on(channel, listener);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
export type MainToHelper = Pick<
|
once(
|
||||||
typeof dialog & typeof shell & typeof app,
|
channel: string,
|
||||||
| 'showOpenDialog'
|
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
||||||
| 'showSaveDialog'
|
) {
|
||||||
| 'openExternal'
|
ipcRenderer.once(channel, listener);
|
||||||
| 'showItemInFolder'
|
return this;
|
||||||
| 'getPath'
|
},
|
||||||
>;
|
|
||||||
|
removeListener(
|
||||||
|
channel: string,
|
||||||
|
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
||||||
|
) {
|
||||||
|
ipcRenderer.removeListener(channel, listener);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export function getElectronAPIs() {
|
export function getElectronAPIs() {
|
||||||
const mainAPIs = getMainAPIs();
|
const mainAPIs = getMainAPIs();
|
||||||
29
packages/frontend/electron/src/shared/type.ts
Normal file
29
packages/frontend/electron/src/shared/type.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { app, dialog, shell } from 'electron';
|
||||||
|
|
||||||
|
export interface ExposedMeta {
|
||||||
|
handlers: [string, string[]][];
|
||||||
|
events: [string, string[]][];
|
||||||
|
}
|
||||||
|
|
||||||
|
// render <-> helper
|
||||||
|
export interface RendererToHelper {
|
||||||
|
postEvent: (channel: string, ...args: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HelperToRenderer {
|
||||||
|
[key: string]: (...args: any[]) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper <-> main
|
||||||
|
export interface HelperToMain {
|
||||||
|
getMeta: () => ExposedMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MainToHelper = Pick<
|
||||||
|
typeof dialog & typeof shell & typeof app,
|
||||||
|
| 'showOpenDialog'
|
||||||
|
| 'showSaveDialog'
|
||||||
|
| 'openExternal'
|
||||||
|
| 'showItemInFolder'
|
||||||
|
| 'getPath'
|
||||||
|
>;
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
|
"@affine/electron-api": "workspace:*",
|
||||||
"@affine/env": "workspace:*",
|
"@affine/env": "workspace:*",
|
||||||
"@affine/workspace": "workspace:*",
|
"@affine/workspace": "workspace:*",
|
||||||
"@blocksuite/block-std": "0.11.0-nightly-202312220916-e3abcbb",
|
"@blocksuite/block-std": "0.11.0-nightly-202312220916-e3abcbb",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { apis, events, type UpdateMeta } from '@affine/electron-api';
|
||||||
import { isBrowser } from '@affine/env/constant';
|
import { isBrowser } from '@affine/env/constant';
|
||||||
import { appSettingAtom } from '@toeverything/infra/atom';
|
import { appSettingAtom } from '@toeverything/infra/atom';
|
||||||
import type { UpdateMeta } from '@toeverything/infra/type';
|
|
||||||
import { atom, useAtom, useAtomValue } from 'jotai';
|
import { atom, useAtom, useAtomValue } from 'jotai';
|
||||||
import { atomWithObservable, atomWithStorage } from 'jotai/utils';
|
import { atomWithObservable, atomWithStorage } from 'jotai/utils';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
@@ -47,21 +47,21 @@ function rpcToObservable<
|
|||||||
// download complete, ready to install
|
// download complete, ready to install
|
||||||
export const updateReadyAtom = atomWithObservable(() => {
|
export const updateReadyAtom = atomWithObservable(() => {
|
||||||
return rpcToObservable(null as UpdateMeta | null, {
|
return rpcToObservable(null as UpdateMeta | null, {
|
||||||
event: window.events?.updater.onUpdateReady,
|
event: events?.updater.onUpdateReady,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// update available, but not downloaded yet
|
// update available, but not downloaded yet
|
||||||
export const updateAvailableAtom = atomWithObservable(() => {
|
export const updateAvailableAtom = atomWithObservable(() => {
|
||||||
return rpcToObservable(null as UpdateMeta | null, {
|
return rpcToObservable(null as UpdateMeta | null, {
|
||||||
event: window.events?.updater.onUpdateAvailable,
|
event: events?.updater.onUpdateAvailable,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// downloading new update
|
// downloading new update
|
||||||
export const downloadProgressAtom = atomWithObservable(() => {
|
export const downloadProgressAtom = atomWithObservable(() => {
|
||||||
return rpcToObservable(null as number | null, {
|
return rpcToObservable(null as number | null, {
|
||||||
event: window.events?.updater.onDownloadProgress,
|
event: events?.updater.onDownloadProgress,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ export const currentVersionAtom = atom(async () => {
|
|||||||
if (!isBrowser) {
|
if (!isBrowser) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const currentVersion = await window.apis?.updater.currentVersion();
|
const currentVersion = await apis?.updater.currentVersion();
|
||||||
return currentVersion;
|
return currentVersion;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ export const useAppUpdater = () => {
|
|||||||
const quitAndInstall = useCallback(() => {
|
const quitAndInstall = useCallback(() => {
|
||||||
if (updateReady) {
|
if (updateReady) {
|
||||||
setAppQuitting(true);
|
setAppQuitting(true);
|
||||||
window.apis?.updater.quitAndInstall().catch(err => {
|
apis?.updater.quitAndInstall().catch(err => {
|
||||||
// TODO: add error toast here
|
// TODO: add error toast here
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
@@ -134,7 +134,7 @@ export const useAppUpdater = () => {
|
|||||||
}
|
}
|
||||||
setCheckingForUpdates(true);
|
setCheckingForUpdates(true);
|
||||||
try {
|
try {
|
||||||
const updateInfo = await window.apis?.updater.checkForUpdates();
|
const updateInfo = await apis?.updater.checkForUpdates();
|
||||||
return updateInfo?.version ?? false;
|
return updateInfo?.version ?? false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error checking for updates:', err);
|
console.error('Error checking for updates:', err);
|
||||||
@@ -145,7 +145,7 @@ export const useAppUpdater = () => {
|
|||||||
}, [checkingForUpdates, setCheckingForUpdates]);
|
}, [checkingForUpdates, setCheckingForUpdates]);
|
||||||
|
|
||||||
const downloadUpdate = useCallback(() => {
|
const downloadUpdate = useCallback(() => {
|
||||||
window.apis?.updater.downloadUpdate().catch(err => {
|
apis?.updater.downloadUpdate().catch(err => {
|
||||||
console.error('Error downloading update:', err);
|
console.error('Error downloading update:', err);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
{ "path": "../../common/y-indexeddb" },
|
{ "path": "../../common/y-indexeddb" },
|
||||||
{ "path": "../../common/debug" },
|
{ "path": "../../common/debug" },
|
||||||
{ "path": "../../common/infra" },
|
{ "path": "../../common/infra" },
|
||||||
|
{ "path": "../electron-api" },
|
||||||
{ "path": "../workspace" }
|
{ "path": "../workspace" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine-test/fixtures": "workspace:*",
|
"@affine-test/fixtures": "workspace:*",
|
||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
|
"@affine/electron-api": "workspace:*",
|
||||||
"@affine/env": "workspace:*",
|
"@affine/env": "workspace:*",
|
||||||
"@affine/graphql": "workspace:*",
|
"@affine/graphql": "workspace:*",
|
||||||
"@toeverything/infra": "workspace:*",
|
"@toeverything/infra": "workspace:*",
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
|
|
||||||
import type { BlobStorage } from '../../engine/blob';
|
import type { BlobStorage } from '../../engine/blob';
|
||||||
import { bufferToBlob } from '../../utils/buffer-to-blob';
|
import { bufferToBlob } from '../../utils/buffer-to-blob';
|
||||||
|
|
||||||
export const createSQLiteBlobStorage = (workspaceId: string): BlobStorage => {
|
export const createSQLiteBlobStorage = (workspaceId: string): BlobStorage => {
|
||||||
const apis = window.apis;
|
|
||||||
assertExists(apis);
|
assertExists(apis);
|
||||||
return {
|
return {
|
||||||
name: 'sqlite',
|
name: 'sqlite',
|
||||||
readonly: false,
|
readonly: false,
|
||||||
get: async (key: string) => {
|
get: async (key: string) => {
|
||||||
|
assertExists(apis);
|
||||||
const buffer = await apis.db.getBlob(workspaceId, key);
|
const buffer = await apis.db.getBlob(workspaceId, key);
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
return bufferToBlob(buffer);
|
return bufferToBlob(buffer);
|
||||||
@@ -17,6 +18,7 @@ export const createSQLiteBlobStorage = (workspaceId: string): BlobStorage => {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
set: async (key: string, value: Blob) => {
|
set: async (key: string, value: Blob) => {
|
||||||
|
assertExists(apis);
|
||||||
await apis.db.addBlob(
|
await apis.db.addBlob(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
key,
|
key,
|
||||||
@@ -25,9 +27,11 @@ export const createSQLiteBlobStorage = (workspaceId: string): BlobStorage => {
|
|||||||
return key;
|
return key;
|
||||||
},
|
},
|
||||||
delete: async (key: string) => {
|
delete: async (key: string) => {
|
||||||
|
assertExists(apis);
|
||||||
return apis.db.deleteBlob(workspaceId, key);
|
return apis.db.deleteBlob(workspaceId, key);
|
||||||
},
|
},
|
||||||
list: async () => {
|
list: async () => {
|
||||||
|
assertExists(apis);
|
||||||
return apis.db.getBlobKeys(workspaceId);
|
return apis.db.getBlobKeys(workspaceId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||||
import { difference } from 'lodash-es';
|
import { difference } from 'lodash-es';
|
||||||
@@ -96,8 +97,8 @@ export function createLocalWorkspaceListProvider(): WorkspaceListProvider {
|
|||||||
JSON.stringify(allWorkspaceIDs.filter(x => x !== workspaceId))
|
JSON.stringify(allWorkspaceIDs.filter(x => x !== workspaceId))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (window.apis && environment.isDesktop) {
|
if (apis && environment.isDesktop) {
|
||||||
await window.apis.workspace.delete(workspaceId);
|
await apis.workspace.delete(workspaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify all browser tabs, so they can update their workspace list
|
// notify all browser tabs, so they can update their workspace list
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
|
import { apis } from '@affine/electron-api';
|
||||||
import { encodeStateVectorFromUpdate } from 'yjs';
|
import { encodeStateVectorFromUpdate } from 'yjs';
|
||||||
|
|
||||||
import type { SyncStorage } from '../../engine/sync';
|
import type { SyncStorage } from '../../engine/sync';
|
||||||
|
|
||||||
export function createSQLiteStorage(workspaceId: string): SyncStorage {
|
export function createSQLiteStorage(workspaceId: string): SyncStorage {
|
||||||
if (!window.apis?.db) {
|
if (!apis?.db) {
|
||||||
throw new Error('sqlite datasource is not available');
|
throw new Error('sqlite datasource is not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'sqlite',
|
name: 'sqlite',
|
||||||
async pull(docId, _state) {
|
async pull(docId, _state) {
|
||||||
const update = await window.apis.db.getDocAsUpdates(
|
if (!apis?.db) {
|
||||||
|
throw new Error('sqlite datasource is not available');
|
||||||
|
}
|
||||||
|
const update = await apis.db.getDocAsUpdates(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
workspaceId === docId ? undefined : docId
|
workspaceId === docId ? undefined : docId
|
||||||
);
|
);
|
||||||
@@ -25,7 +29,10 @@ export function createSQLiteStorage(workspaceId: string): SyncStorage {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
async push(docId, data) {
|
async push(docId, data) {
|
||||||
return window.apis.db.applyDocUpdate(
|
if (!apis?.db) {
|
||||||
|
throw new Error('sqlite datasource is not available');
|
||||||
|
}
|
||||||
|
return apis.db.applyDocUpdate(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
data,
|
data,
|
||||||
workspaceId === docId ? undefined : docId
|
workspaceId === docId ? undefined : docId
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
{ "path": "../../common/env" },
|
{ "path": "../../common/env" },
|
||||||
{ "path": "../../common/debug" },
|
{ "path": "../../common/debug" },
|
||||||
{ "path": "../../common/infra" },
|
{ "path": "../../common/infra" },
|
||||||
{ "path": "../../frontend/graphql" }
|
{ "path": "../../frontend/graphql" },
|
||||||
|
{ "path": "../../frontend/electron-api" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import type { apis } from '@affine/electron-api';
|
||||||
import { test } from '@affine-test/kit/electron';
|
import { test } from '@affine-test/kit/electron';
|
||||||
import { clickSideBarCurrentWorkspaceBanner } from '@affine-test/kit/utils/sidebar';
|
import { clickSideBarCurrentWorkspaceBanner } from '@affine-test/kit/utils/sidebar';
|
||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
apis: typeof apis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test('check workspace has a DB file', async ({ appInfo, workspace }) => {
|
test('check workspace has a DB file', async ({ appInfo, workspace }) => {
|
||||||
const w = await workspace.current();
|
const w = await workspace.current();
|
||||||
const dbPath = path.join(
|
const dbPath = path.join(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@affine-test/fixtures": "workspace:*",
|
"@affine-test/fixtures": "workspace:*",
|
||||||
"@affine-test/kit": "workspace:*",
|
"@affine-test/kit": "workspace:*",
|
||||||
|
"@affine/electron-api": "workspace:*",
|
||||||
"@playwright/test": "^1.39.0",
|
"@playwright/test": "^1.39.0",
|
||||||
"@types/fs-extra": "^11.0.2",
|
"@types/fs-extra": "^11.0.2",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../tests/fixtures"
|
"path": "../../tests/fixtures"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../packages/frontend/electron-api"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"./e2e-enhance/*": "./e2e-enhance/*.ts"
|
"./e2e-enhance/*": "./e2e-enhance/*.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@affine/electron-api": "workspace:*",
|
||||||
"@node-rs/argon2": "^1.5.2",
|
"@node-rs/argon2": "^1.5.2",
|
||||||
"@playwright/test": "^1.39.0",
|
"@playwright/test": "^1.39.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
|
import type { affine } from '@affine/electron-api';
|
||||||
// Credit: https://github.com/spaceagetv/electron-playwright-helpers/blob/main/src/ipc_helpers.ts
|
// Credit: https://github.com/spaceagetv/electron-playwright-helpers/blob/main/src/ipc_helpers.ts
|
||||||
import type { Page } from '@playwright/test';
|
import type { Page } from '@playwright/test';
|
||||||
import type { ElectronApplication } from 'playwright';
|
import type { ElectronApplication } from 'playwright';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
affine: typeof affine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function ipcRendererInvoke(page: Page, channel: string, ...args: any[]) {
|
export function ipcRendererInvoke(page: Page, channel: string, ...args: any[]) {
|
||||||
return page.evaluate(
|
return page.evaluate(
|
||||||
({ channel, args }) => {
|
({ channel, args }) => {
|
||||||
return window.affine.ipcRenderer.invoke(channel, ...args);
|
return window.affine?.ipcRenderer.invoke(channel, ...args);
|
||||||
},
|
},
|
||||||
{ channel, args }
|
{ channel, args }
|
||||||
);
|
);
|
||||||
@@ -14,7 +21,7 @@ export function ipcRendererInvoke(page: Page, channel: string, ...args: any[]) {
|
|||||||
export function ipcRendererSend(page: Page, channel: string, ...args: any[]) {
|
export function ipcRendererSend(page: Page, channel: string, ...args: any[]) {
|
||||||
return page.evaluate(
|
return page.evaluate(
|
||||||
({ channel, args }) => {
|
({ channel, args }) => {
|
||||||
window.affine.ipcRenderer.send(channel, ...args);
|
window.affine?.ipcRenderer.send(channel, ...args);
|
||||||
},
|
},
|
||||||
{ channel, args }
|
{ channel, args }
|
||||||
);
|
);
|
||||||
|
|||||||
47
tools/@types/env/__all.d.ts
vendored
47
tools/@types/env/__all.d.ts
vendored
@@ -1,53 +1,6 @@
|
|||||||
import type { Environment, RuntimeConfig } from '@affine/env/global';
|
import type { Environment, RuntimeConfig } from '@affine/env/global';
|
||||||
import type {
|
|
||||||
ConfigStorageHandlerManager,
|
|
||||||
DBHandlerManager,
|
|
||||||
DebugHandlerManager,
|
|
||||||
DialogHandlerManager,
|
|
||||||
EventMap,
|
|
||||||
ExportHandlerManager,
|
|
||||||
UIHandlerManager,
|
|
||||||
UnwrapManagerHandlerToClientSide,
|
|
||||||
UpdaterHandlerManager,
|
|
||||||
WorkspaceHandlerManager,
|
|
||||||
} from '@toeverything/infra/index';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
|
||||||
appInfo: {
|
|
||||||
electron: boolean;
|
|
||||||
};
|
|
||||||
apis: {
|
|
||||||
db: UnwrapManagerHandlerToClientSide<DBHandlerManager>;
|
|
||||||
debug: UnwrapManagerHandlerToClientSide<DebugHandlerManager>;
|
|
||||||
dialog: UnwrapManagerHandlerToClientSide<DialogHandlerManager>;
|
|
||||||
export: UnwrapManagerHandlerToClientSide<ExportHandlerManager>;
|
|
||||||
ui: UnwrapManagerHandlerToClientSide<UIHandlerManager>;
|
|
||||||
updater: UnwrapManagerHandlerToClientSide<UpdaterHandlerManager>;
|
|
||||||
workspace: UnwrapManagerHandlerToClientSide<WorkspaceHandlerManager>;
|
|
||||||
configStorage: UnwrapManagerHandlerToClientSide<ConfigStorageHandlerManager>;
|
|
||||||
};
|
|
||||||
events: EventMap;
|
|
||||||
affine: {
|
|
||||||
ipcRenderer: {
|
|
||||||
send(channel: string, ...args: any[]): void;
|
|
||||||
invoke(channel: string, ...args: any[]): Promise<any>;
|
|
||||||
on(
|
|
||||||
channel: string,
|
|
||||||
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
|
||||||
): this;
|
|
||||||
once(
|
|
||||||
channel: string,
|
|
||||||
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
|
||||||
): this;
|
|
||||||
removeListener(
|
|
||||||
channel: string,
|
|
||||||
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
|
||||||
): this;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var process: {
|
var process: {
|
||||||
env: Record<string, string>;
|
env: Record<string, string>;
|
||||||
|
|||||||
3
tools/@types/env/package.json
vendored
3
tools/@types/env/package.json
vendored
@@ -4,8 +4,7 @@
|
|||||||
"types": "./__all.d.ts",
|
"types": "./__all.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine/env": "workspace:*",
|
"@affine/env": "workspace:*"
|
||||||
"@toeverything/infra": "workspace:*"
|
|
||||||
},
|
},
|
||||||
"version": "0.11.0"
|
"version": "0.11.0"
|
||||||
}
|
}
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@@ -115,6 +115,7 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@affine-test/fixtures": "workspace:*"
|
"@affine-test/fixtures": "workspace:*"
|
||||||
"@affine-test/kit": "workspace:*"
|
"@affine-test/kit": "workspace:*"
|
||||||
|
"@affine/electron-api": "workspace:*"
|
||||||
"@playwright/test": "npm:^1.39.0"
|
"@playwright/test": "npm:^1.39.0"
|
||||||
"@types/fs-extra": "npm:^11.0.2"
|
"@types/fs-extra": "npm:^11.0.2"
|
||||||
fs-extra: "npm:^11.1.1"
|
fs-extra: "npm:^11.1.1"
|
||||||
@@ -156,6 +157,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine-test/kit@workspace:tests/kit"
|
resolution: "@affine-test/kit@workspace:tests/kit"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@affine/electron-api": "workspace:*"
|
||||||
"@node-rs/argon2": "npm:^1.5.2"
|
"@node-rs/argon2": "npm:^1.5.2"
|
||||||
"@playwright/test": "npm:^1.39.0"
|
"@playwright/test": "npm:^1.39.0"
|
||||||
express: "npm:^4.18.2"
|
express: "npm:^4.18.2"
|
||||||
@@ -209,6 +211,7 @@ __metadata:
|
|||||||
resolution: "@affine/component@workspace:packages/frontend/component"
|
resolution: "@affine/component@workspace:packages/frontend/component"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
|
"@affine/electron-api": "workspace:*"
|
||||||
"@affine/graphql": "workspace:*"
|
"@affine/graphql": "workspace:*"
|
||||||
"@affine/i18n": "workspace:*"
|
"@affine/i18n": "workspace:*"
|
||||||
"@affine/workspace": "workspace:*"
|
"@affine/workspace": "workspace:*"
|
||||||
@@ -309,6 +312,7 @@ __metadata:
|
|||||||
"@affine/cmdk": "workspace:*"
|
"@affine/cmdk": "workspace:*"
|
||||||
"@affine/component": "workspace:*"
|
"@affine/component": "workspace:*"
|
||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
|
"@affine/electron-api": "workspace:*"
|
||||||
"@affine/env": "workspace:*"
|
"@affine/env": "workspace:*"
|
||||||
"@affine/graphql": "workspace:*"
|
"@affine/graphql": "workspace:*"
|
||||||
"@affine/i18n": "workspace:*"
|
"@affine/i18n": "workspace:*"
|
||||||
@@ -418,6 +422,15 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@affine/electron-api@workspace:*, @affine/electron-api@workspace:packages/frontend/electron-api":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@affine/electron-api@workspace:packages/frontend/electron-api"
|
||||||
|
dependencies:
|
||||||
|
"@toeverything/infra": "workspace:*"
|
||||||
|
electron: "npm:^27.1.0"
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@affine/electron@workspace:packages/frontend/electron":
|
"@affine/electron@workspace:packages/frontend/electron":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine/electron@workspace:packages/frontend/electron"
|
resolution: "@affine/electron@workspace:packages/frontend/electron"
|
||||||
@@ -788,6 +801,7 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@affine-test/fixtures": "workspace:*"
|
"@affine-test/fixtures": "workspace:*"
|
||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
|
"@affine/electron-api": "workspace:*"
|
||||||
"@affine/env": "workspace:*"
|
"@affine/env": "workspace:*"
|
||||||
"@affine/graphql": "workspace:*"
|
"@affine/graphql": "workspace:*"
|
||||||
"@testing-library/react": "npm:^14.0.0"
|
"@testing-library/react": "npm:^14.0.0"
|
||||||
@@ -13438,6 +13452,7 @@ __metadata:
|
|||||||
resolution: "@toeverything/hooks@workspace:packages/frontend/hooks"
|
resolution: "@toeverything/hooks@workspace:packages/frontend/hooks"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
|
"@affine/electron-api": "workspace:*"
|
||||||
"@affine/env": "workspace:*"
|
"@affine/env": "workspace:*"
|
||||||
"@affine/workspace": "workspace:*"
|
"@affine/workspace": "workspace:*"
|
||||||
"@blocksuite/block-std": "npm:0.11.0-nightly-202312220916-e3abcbb"
|
"@blocksuite/block-std": "npm:0.11.0-nightly-202312220916-e3abcbb"
|
||||||
@@ -13646,7 +13661,6 @@ __metadata:
|
|||||||
resolution: "@types/affine__env@workspace:tools/@types/env"
|
resolution: "@types/affine__env@workspace:tools/@types/env"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@affine/env": "workspace:*"
|
"@affine/env": "workspace:*"
|
||||||
"@toeverything/infra": "workspace:*"
|
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user