docs: remove temp documentations (#1807)

This commit is contained in:
Wenzhao Hu
2024-04-07 11:20:00 +08:00
committed by GitHub
parent 0121407ff9
commit 88461d08af
6 changed files with 1 additions and 488 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ pnpm dev:demo
### Architecture
Please refer to the architecture doc. The [Chinese version](https://univer.ai/guides/architecture/architecture/).
Please refer to the [architecture doc](https://github.com/dream-num/univer/wiki/Univer-Architecture).
### Source code organization
-315
View File
@@ -1,315 +0,0 @@
# Architecture Nodes
## Univer Architecture Introduction
Univer is a web-based office collaboration and data processing SDK, mainly in the form of Office products (sheet / doc / slide). Univer organizes code in a plugin-based manner, allowing users to choose plugins according to their actual needs to form a Univer application. For example, users can add capabilities such as collaborative editing, macro recording, and AI-generated scripting languages to traditional spreadsheets in a plugin-based manner. Users can also embed Univer applications into their own applications, and integrate the capabilities of Univer and their own applications through plugins. And, with the help of Univer's official database connectors, user can load and process data in Univer, leveraging Univer's plugin ecosystem.
## Core Requirements for Univer Architecture Design
Some of these design requirements come from the product planning of Univer, and some come from the learning of team members participating in other project research and development.
1. **100% embrace the web technology stack**. Univer needs to run in a considerable number of environments, meet the needs of rapid iteration, and allow customers, ISVs, and communities to have the ability to develop secondary development. The only technology stack that can meet these needs is the technology stack with web technology as its core.
2. **Pluginization and high scalability**. Univer's modules should be as plug-in as possible, and the coupling relationship between plug-ins should be decoupled as much as possible, so as to reduce the cost of adapting to different user needs and different operating environments, and reduce the threshold for secondary development.
3. **Hierarchical structure and one-way dependency**. Univer's modules are not allowed to have circular dependencies, which allows us to load the required levels and modules according to the needs of different environments.
4. **Design for multiple platforms**. Decouple the coupling relationship between code and specific operating environment to facilitate migration to different operating environments.
5. **Design for high testability**. The modules are based on interfaces as much as possible to establish dependency relationships, which is convenient for independent testing.
## Plugins and Dependency Injection
### Plugin
Univer's modules should be considered from the perspective of **business type (Sheet / Doc / Slide), concern (configuration management / UI / shortcut keys / canvas rendering), function (Sheet basic operation / Sheet conditional formatting / Sheet filter, operating environment (desktop / mobile / Node.js, etc.)** and other factors, divided into various plugins (plugin), combined into a Univer application.
For example, you could create a standard Spreadsheet application like this:
```ts
import { UniverDocs } from '@univerjs/docs';
import { UniverRenderEngine } from '@univerjs/engine-render';
import { sheetsPlugin } from '@univerjs/sheets';
import { UniverUI } from '@univerjs/ui';
import { LocaleType, Univer } from '@univerjs/core';
import { defaultTheme } from '@univerjs/design';
import { FormulaPlugin } from '@univerjs/sheets-formula';
import { UniverSheetsUI } from '@univerjs/sheets-ui';
const univer = new Univer({
theme: defaultTheme,
locale: LocaleType.ZH_CN,
});
univer.registerPlugin(UniverDocs, {
hasScroll: false,
});
univer.registerPlugin(UniverRenderEngine);
univer.registerPlugin(UniverUI, {
container: 'univer-container', // where to mount the UI
header: true,
footer: true,
});
univer.registerPlugin(sheetsPlugin);
univer.registerPlugin(UniverSheetsUI);
univer.registerPlugin(FormulaPlugin);
// call univer.createUniverSheet() to create a spreadsheet
```
The design based on plugins enables Univer to meet various operating environments (PC browser / Node / mobile terminal), different functional requirements, different configuration requirements, secondary development, third-party plugins and other needs.
You can refer to the document [Plugin Extension Capability](./plugin-extension-capability.md) for more information about the plugin extension capability.
### Dependency injection
<img width="962" alt="" src="https://github.com/dream-num/univer/assets/12122021/2102f948-4ede-49b2-ac7d-45adf943a5b5">
The plugin can divide the code into the various layers of modules introduced in the "Hierarchical Structure" section below according to actual needs. The service and controller in these modules need to be added to the dependency injection system of Univer, so that Univer can automatically resolve the dependency relationship between these modules and instantiate these modules. The documentation of the dependency injection system refers to [redi - redi](https://redi.wendell.fun/zh-CN).
### Public and private modules of plugins and extension points
You can export the identifier of these modules in the index.ts file of each plugin. If the identifier of a module is exported, then other plugins can import the identifier of these modules to establish a dependency relationship with these modules, and these modules become public modules of the previous plugin, otherwise it is a private module. If you are familiar with Angular, you will easily find that this is very similar to the concept of NgModule, except that we do not need to declare the exports field, but use the export of es module to distinguish public modules.
### Plugin lifecycle
Plugins have the following four lifecycles
```ts
export const enum LifecycleStages {
Starting,
Ready,
Rendered,
Steady,
}
```
* `Starting` The first lifecycle of the plugin mounted on the Univer instance, at this time the Univer business instance has not been created. The plugin should add its own modules to the dependency injection system during this lifecycle. It is not recommended to initialize the internal modules of the plugin outside this lifecycle.
* `Ready` The first business instance of Univer has been created, and the plugin can do most of the initialization work during this lifecycle.
* `Rendered` The first rendering has been completed, and the plugin can do initialization work that requires DOM dependency during this lifecycle.
* `Steady` Triggered after a period of time after `Rendered`, the plugin can do non-first screen must work during this lifecycle to improve loading performance.
Correspondingly, there are four lifecycle hooks on the Plugin type
```ts
/**
* Plug-in base class, all plug-ins must inherit from this base class. Provide basic methods.
*/
export abstract class Plugin {
onStarting(_injector: Injector): void {}
onReady(): void {}
onRendered(): void {}
onSteady(): void {}
}
```
In addition to these four lifecycle hooks, modules within the plugin can use the OnLifecycle decorator to declare that they need to be initialized at a specific lifecycle stage, for example:
```ts
@OnLifecycle(LifecycleStages.Rendered, IMEInputController)
export class IMEInputController extends Disposable {}
```
You can also listen to lifecycle events by injecting `LifecycleService`.
```ts
export class YourService {
constructor(
@Inject(LifecycleService) private _lifecycleService: LifecycleService,
) {
super();
this._lifecycleService.lifecycle$.subscribe((stage) => this._initModulesOnStage(stage));
}
}
```
### When should you write a plugin?
The division of plugins is primarily based on whether certain modules need to be loaded in specific scenarios. For example, when running on the Node.js platform, UI-related modules may not be required, so these modules can be placed in a separate plugin and not loaded on the Node.js platform. Another example is when you want to allow users to choose whether to load a particular feature, you can place that feature in a separate plugin.
## Layers
![image](../img/layers.png)
The modules within a plugin should generally belong to the following layers:
* View: Handles rendering and interaction, including canvas rendering and React components.
* Controller: Encapsulates business logic, especially functional logic, and dispatches commands.
* Command: Executes logic using the command pattern, modifying the state or data of lower layers such as Service/Model.
* Service: Encapsulates functionality based on concerns for use by higher-level modules, stores internal application state, and manipulates underlying data, etc.
* Model: Stores business data.
There should be a unidirectional dependency relationship between the layers. Except for some Controllers that act as view-models in MVVM and may hold references to UI layer objects, other layers are prohibited from referencing code from higher-level modules.
Note: The code within a plugin is not limited to belonging to only one layer. For example, a plugin may provide both View and Controller simultaneously.
## Command System
Changes to the application state and data are executed through the command system. The Univer core provides a command service, with the dependency injection token ICommandService. Higher-level modules can encapsulate business logic within commands and execute the business logic by accessing other services through the command system. With the command system, Univer can easily implement collaborative editing, macro recording, undo/redo, and follow browsing capabilities.
Plugins can register commands using the registerCommand interface provided by ICommandService and execute commands using the executeCommand interface.
```ts
export interface ICommand<P extends object = object, R = boolean> {
/**
* ${businessName}.${type}.${name}
*/
readonly id: string;
readonly type: CommandType;
handler(accessor: IAccessor, params?: P): Promise<R>;
/** When this command is unregistered, this function would be called. */
onDispose?: () => void;
}
export interface ICommandService {
registerCommand(command: ICommand): IDisposable;
executeCommand<P extends object = object, R = boolean>(
id: string,
params?: P,
options?: IExecutionOptions
): Promise<R> | R;
}
```
There are three types of commands in total:
```ts
export const enum CommandType {
/** Command could generate some operations or mutations. */
COMMAND = 0,
/** An operation that do not require conflict resolve. */
OPERATION = 1,
/** An operation that need to be resolved before applied on peer client. */
MUTATION = 2,
}
```
* `COMMAND` is responsible for creating, orchestrating, and executing `MUTATION` or `OPERATION` based on specific business logic. For example, a **Delete Row `COMMAND`** would generate a **Delete Row `MUTATION`**, an **Insert Row `MUTATION`** for undo, and a **Set Cell Content `MUTATION`**.
* `COMMAND` is the main carrier of business logic. If a _user action_ requires different _underlying behaviors_ based on the application state—for example, when a user clicks the bold text button and the effective range of the bold operation needs to be determined based on the current selection—the corresponding logic should be handled by the `COMMAND`.
* It can dispatch other `COMMAND`, `OPERATION`, or `MUTATION`.
* Asynchronous execution is allowed.
* `MUTATION` represents the changes made to the persisted data and involves conflict resolution in collaborative editing. Examples include inserting rows or columns, modifying cell content, changing filter ranges, and other operations.
* It cannot dispatch any other commands.
* **It must be executed synchronously**.
* `OPERATION` represents the changes made to non-persisted data (or application state) and does not involve conflict resolution. Examples include modifying scroll position, changing sidebar states, and other operations.
* It cannot dispatch any other commands.
* **It must be executed synchronously**.
### Collaborative Editing
`ICommandService` provides event listening interfaces that allow plugins to listen to which commands have been executed and what parameters were used for execution. In practice, an event is dispatched after a command is executed. The event looks like:
```ts
/**
* The command info, only a command id and responsible params
*/
export interface ICommandInfo<T extends object = object> {
id: string;
type: CommandType;
/**
* Args should be serializable.
*/
params?: T;
}
```
For collaborative editing, the collaboration plugin can listen to all `MUTATION` type commands and, through collaborative editing algorithms, send these `MUTATION` to other collaborative clients. The plugin can then use `ICommandService` to reapply these `MUTATION`.
### Operation Recording and Playback
By listening to the execution of `OPERATION` and `MUTATION`, plugins can record user actions and implement features such as:
* Collaborative cursors
* Magic Share, similar to Lark video
* Macro recording
* AppScript
and more.
## User Interface
Univer provides mechanisms to simplify UI development and reduce the workload of menus, shortcuts, and interaction components across different devices. Functional plugins do not need to concern themselves with UI details; they can focus solely on business logic.
### ShortcutService
By injecting an `IShortcutItem` into the `IShortcutService`, you can register a shortcut key and configure its key combination, priority, trigger conditions, and the associated command to be executed.
```ts
export interface IShortcutItem<P extends object = object> {
/** This should reuse the corresponding command's id. */
id: string;
description?: string;
priority?: number;
/** A callback that will be triggered to examine if the shortcut should be invoked. */
preconditions?: (contextService: IContextService) => boolean;
/** A command can be bound to several bindings, with different static parameters perhaps. */
binding: number;
mac?: number;
win?: number;
linux?: number;
/** Static parameters of this shortcut. Would be send to `CommandService.executeCommand`. */
staticParameters?: P;
}
export interface IShortcutService {
registerShortcut(shortcut: IShortcutItem): IDisposable;
getCommandShortcut(id: string): string | null;
}
```
### MenuService
By registering an IMenuItem with the IMenuService, you can configure a menu item.
```ts
interface IMenuItemBase<V> {
/** ID of the menu item. Normally it should be the same as the ID of the command that it would invoke. */
id: string;
title: string;
description?: string;
icon?: string;
tooltip?: string;
/** In what menu should the item display. */
positions: OneOrMany<MenuPosition | string>;
/** @deprecated this type seems unnecessary */
type: MenuItemType;
/**
* Custom label component id.
* */
label?:
| string
| {
name: string;
props?: Record<string, string | number>;
};
hidden$?: Observable<boolean>;
disabled$?: Observable<boolean>;
/** On observable value that should emit the value of the corresponding selection component. */
value$?: Observable<V>;
}
export interface IMenuService {
menuChanged$: Observable<void>;
addMenuItem(item: IMenuItem): IDisposable;
/** Get menu items for display at a given position or a submenu. */
getMenuItems(position: MenuPosition | string): Array<IDisplayMenuItem<IMenuItem>>;
getMenuItem(id: string): IMenuItem | null;
}
```
-5
View File
@@ -1,5 +0,0 @@
# Univer Documentation
The documentation is still under construction. Sorry for the incovenience.
If you prefer to read the documentation in Chinese, please to [here](./zh/index.md).
-53
View File
@@ -1,53 +0,0 @@
# Plugin Extension Capability
Univer follows a "small core" + "multiple plugins" architecture pattern. The core code (mainly the core package) and plugins can provide extension points to enrich the functionality of Univer.
As mentioned in the Architecture Overview section, plugin extension points should be added to the dependency injection system as public modules.
The following section provides a brief introduction to the core package of Univer and some of the main extension points exposed by plugins. Detailed API documentation will be generated using a documentation generation tool later.
## Core Package
The core package, as the lowest-level package, provides the Univer container type and core extension points, including:
* Command System (ICommandService)
* Registering commands/mutations/operations
* Listening to command execution
* Context (IContextService)
* Recording application runtime state information
* Configuration Management (IConfigService)
* Lifecycle (LifecycleService)
* Logging (ILogService)
* Printing different types of logs
* Controlling log storage and reporting methods
* Internationalization (ILocaleService)
* Permissions (IPermissionService)
* Controlling execution permissions for commands
* Controlling permissions for Univer documents
* Undo/Redo (IUndoRedoService)
## base-ui
Provides basic operations and UI capabilities:
* Provides basic React components
* Global interactions
* Popup dialogs or notifications (INotificationService / IMessageService)
* Toolbar and menu (IMenuService)
* Registering menu items in different locations (toolbar, context menu, etc.)
* Shortcuts (IShortcutService)
* Registering keyboard shortcuts
* Getting the shortcut keys associated with a command
* Component mounting point (IDesktopUIController)
* Rendering custom content at specified locations
* Copy and paste (IClipboardService)
* Supplementing clipboard content when copying to the clipboard, supplementing or modifying mutations when pasting from the clipboard
* Focus management (ILayoutService)
## base-render
Provides basic rendering capabilities:
* Custom rendering
* Handling mouse interactions
* Rendering rich text content
-108
View File
@@ -1,108 +0,0 @@
# Facade
Facade 的意图是作为
1. 用户使用 Univer 的简单接口。如果用户没有自定义开发的需要而只是简单地使用一个表格组件,那么可以直接使用 Facade 提供的接口。
2. 用户使用 Apps Scripts 的接口。
Facade 和 Microsoft Excel 的 Office Scripts 以及 Google 的 Apps Scripts 有着相似的设计思路。它们都是为了简化用户的开发流程,让用户可以直接使用简单的接口来完成复杂的操作。中国国内的部分产品也提供了基于 API 的访问,例如钉钉文档的表格。
### Microsoft Excel 的 Office Scripts
[官方文档链接](https://learn.microsoft.com/en-us/office/dev/scripts/)
语法上使用了现代 TypeScript 语法,并且支持 async await 等语言特性。
```ts
function main(workbook: ExcelScript.Workbook) {
// Add a new worksheet to store our email table
let emailsSheet = workbook.addWorksheet("Emails");
// Add data and create a table
emailsSheet.getRange("A1:D1").setValues([
["Date", "Day of the week", "Email address", "Subject"]
]);
let newTable = workbook.addTable(emailsSheet.getRange("A1:D2"), true);
newTable.setName("EmailTable");
// Add a new PivotTable to a new worksheet
let pivotWorksheet = workbook.addWorksheet("Subjects");
let newPivotTable = workbook.addPivotTable("Pivot", "EmailTable", pivotWorksheet.getRange("A3:C20"));
// Setup the pivot hierarchies
newPivotTable.addRowHierarchy(newPivotTable.getHierarchy("Day of the week"));
newPivotTable.addRowHierarchy(newPivotTable.getHierarchy("Email address"));
newPivotTable.addDataHierarchy(newPivotTable.getHierarchy("Subject"));
}
```
另外值得注意的是 Excel 还有另外一套 API 叫做 office-js,这套 API 的语法较为复杂,官方文档声明 office-js 面向开发者而 Office Scripts 面向一般用户。
据了解 office-js 是 Office Scripts 的底层,Office Scripts 对需要异步转同步的地方都做了一次类似于添加 await 语法的预编译。
### 钉钉表格的 API
[官方文档链接](https://open.dingtalk.com/document/orgapp/overview-of-dingtalk-scripts)
钉钉的语法设计和 Office Scripts 非常类似,除了它不用从一个 main 函数开始执行而是直接自顶向下执行脚本。
### Google 的 Apps Scripts
[官方文档](https://developers.google.com/apps-script/reference/spreadsheet?hl=zh-cn)
Apps Scripts 不仅可以操作表格,还可以操作 Google 的其他产品,例如 Google DocsGoogle Drive 等等。它的语法和 JavaScript 非常类似,但是它的语法不支持 async await 等语言特性。实际上 Apps Scripts 的代码是同步阻塞执行的,并且 Apps Scripts 并非运行在浏览器中,而是运行在 Google 的服务器上,猜想应该是 hack 了解释器或者别的什么方式控制了脚本的执行过程。
```js
function createAndSendDocument() {
try {
// Create a new Google Doc named 'Hello, world!'
const doc = DocumentApp.create('Hello, world!');
// Access the body of the document, then add a paragraph.
doc.getBody().appendParagraph('This document was created by Google Apps Script.');
// Get the URL of the document.
const url = doc.getUrl();
// Get the email address of the active user - that's you.
const email = Session.getActiveUser().getEmail();
// Get the name of the document to use as an email subject line.
const subject = doc.getName();
// Append a new string to the "url" variable to use as an email body.
const body = 'Link to your doc: ' + url;
// Send yourself an email with a link to the document.
GmailApp.sendEmail(email, subject, body);
} catch (err) {
// TODO (developer) - Handle exception
console.log('Failed with error %s', err.message);
}
}
```
## 可选的方案以及优缺点分析
我们设计 Facade 的 API 时受到以下条件的约束(或者不?)
1. 为了和 Apps Scripts 的语法保持一致(一定吗?),API 看起来要像是同步执行的
2. 必然有一些操作行为是异步的,例如读取文件,网络请求等等
所以我们需要设计一个机制,用户编写它时,看似是同步执行的,但是实际上是异步执行的。从这种思路出发有以下方案
1. 预编译。在用户编写代码之后,我们对代码进行预编译,将异步的操作转换为同步的操作(补充 async await 操作符)。
1. 优点是:实现起来可能较为简单
2. 缺点是:Facade 作为简单 API 和 Scripts 语法时语法不一致,并且可能无法给 Scripts 用户正确的类型信息
2. 同步阻塞式调用。在 Facade 内将所有需要用到异步语法 API 的地方全部改为一个同步的 XHRHttpRequest 调用,Facade 本身在 web worker 内运行并向主线程请求操作(通过 service worker 实现)。
1. 优点是:可以实现真正的同步 API,而且可以给 Scripts 用户正确的类型信息
2. 缺点是:只能跑在 web worker 里,无法作为简单 API 使用;复杂度很高
3. 自己控制脚本执行过程。暂时还没有明确的方案。
1. 优点是:可以实现真正的同步 API,而且可以给 Scripts 用户正确的类型信息
2. 缺点是:只能运行在服务器环境,无法作为简单 API 使用;复杂度极高
如果我们愿意放弃和 Apps Scripts 语法保持一致的前提,引入 async await 语法,那么我们可以使用以下方案
1. 引入 async await 语法。
1. 优点是:Facade 作为简单 API 和 Scripts 语法效果完全一致;Scripts 用户可以得到完善的代码编辑提示;实现非常简单
2. 需要引导用户使用 async await 语法;生成 Scripts 的 AI 模型需要额外的工作(现在有这样的模型吗?)
-6
View File
@@ -1,6 +0,0 @@
# Univer 开发文档
## 架构设计
- [架构概要](./achitecture.md)
- [Univer Sheet 架构](./sheet-architecture.md)