commit a6b429a781acdd5dee9dec0b1ef3b805c6ec4e1d Author: dream-num Date: Thu Sep 29 20:12:19 2022 +0800 init diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000000..e5b6d8d6a6 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000000..536b04957a --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..f2030cffcb --- /dev/null +++ b/.eslintignore @@ -0,0 +1,9 @@ +.eslintrc.js +vite.config.ts +**/*.test.js +dist +lib +node_modules +package.json +tsconfig.json +jest.config.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..88839d5359 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,113 @@ +module.exports = { + // env: { + // browser: true, + // es2021: true, + // node: true, + // jest: true, + // }, + globals: { + page: true, + browser: true, + context: true, + jestPuppeteer: true, + }, + // parser: '@typescript-eslint/parser', + // extends: ['airbnb-typescript', 'prettier', 'eslint:recommended', 'plugin:import/recommended', 'plugin:import/typescript'], + extends: ['airbnb-base', 'airbnb-typescript/base', 'prettier'], + env: { + browser: true, + es2021: true, + }, + parser: '@typescript-eslint/parser', + parserOptions: { + warnOnUnsupportedTypeScriptVersion: false, + ecmaVersion: 12, + sourceType: 'module', + tsconfigRootDir: __dirname, + project: ['./packages/*/tsconfig.json'], + }, + plugins: ['@typescript-eslint', 'prettier', 'import', 'import-newlines', 'unused-imports'], + rules: { + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], + 'import/prefer-default-export': 'off', + 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], + // turn on errors for missing imports + 'import/no-unresolved': [2, { ignore: ['\\.less$', '^@'] }], + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-shadow': 'off', + 'import/no-cycle': 'off', + 'no-param-reassign': 'off', + 'no-bitwise': 'off', + 'default-case': 'off', + 'class-methods-use-this': 'off', + 'consistent-return': 'off', + 'no-underscore-dangle': 'off', + 'no-restricted-syntax': 'off', + 'max-classes-per-file': 'off', + 'prefer-destructuring': 'off', + 'no-plusplus': 'off', + 'no-return-assign': 'off', + 'no-continue': 'off', + 'no-loop-func': 'off', + '@typescript-eslint/no-loop-func': 'off', + 'guard-for-in': 'off', + 'no-prototype-builtins': 'off', + 'no-lonely-if': 'off', + 'prefer-const': 'off', + radix: 'off', + 'no-nested-ternary': 'off', + 'no-new': 'off', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + 'no-console': 'off', + 'no-multi-assign': 'off', + 'no-restricted-properties': 'off', + 'no-control-regex': 'off', + '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], + 'no-await-in-loop': 'off', + '@typescript-eslint/explicit-member-accessibility': [ + 'error', + { + accessibility: 'no-public', + }, + ], + '@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'as', objectLiteralTypeAssertions: 'allow-as-parameter' }], + 'spaced-comment': 'off', + eqeqeq: ['error', 'always', { null: 'ignore' }], + + // eslint-plugin-unused-imports + '@typescript-eslint/no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + + 'prefer-regex-literals': 'off', + '@typescript-eslint/default-param-last': 'off', + 'grouped-accessor-pairs': 'off', + }, + settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + typescript: { + // always try to resolve config under `@config` + // directory even it doesn't contain any source code, + // like `@config/unist` + alwaysTryTypes: true, + // Choose from one of the "project" configs below or omit + // to use /tsconfig.json by default + // use /path/to/folder/tsconfig.json + project: './tsconfig.json', + }, + node: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + }, +}; diff --git a/.github/contributing-zh.md b/.github/contributing-zh.md new file mode 100644 index 0000000000..cbff80cd19 --- /dev/null +++ b/.github/contributing-zh.md @@ -0,0 +1,1287 @@ +# Univer 贡献指南 + +## 工具 + +- vite +- Typescript/tsx +- Typescript references +- pnpm workspace + +## 工程介绍 + +pnpm 管理的多包项目, + +- packages 插件和核心 +- examples 案例 +- templates 插件模板 +- scripts 脚本 + +## 基本命令 + +开发 + +```sh +npm run dev +``` + +测试 + +```sh +npm run test +``` + +打包 + +```sh +npm run build +``` + +生成 API + +```sh +npm run api +``` + +重新安装依赖 + +```sh +npm run clean +``` + +## 脚本 + +### api + +使用 `npm run api`,会调用 `scripts/api`文件,内部会触发各个插件生成 API 的命令,插件内部,使用更详细的 node.js 脚本来执行具体的 API 解析指令。 + +## 开发 + +### 安装依赖 + +Node.js >= 14.19 + +```sh +git clone http://github.com/dream-num/univer +cd univer +npm i -g pnpm # MacOS : sudo npm i -g pnpm +npx playwright install +pnmp i +npm run dev +``` + +### 命令 + +在某个子包里执行某个命令 + +```sh +pnpm run --filter [package name] [command] +``` + +比如启动 `packages/sheets-plugin-sort` 工程的`dev`开发模式 + +```sh +pnpm run --filter @univer/sheets-plugin-sort dev + +``` + +### 清除 + +遇到任何 npm 安装问题,请先尝试一键重新安装依赖 + +```sh +npm run clean +``` + +### 安装依赖 + +如果更新了项目,发现缺少依赖库,尝试安装一下 + +```sh +pnpm install +``` + +## 风格指南 + +我们严格参照 [Google 开源项目风格指南 - TypeScript 风格指南](https://zh-google-styleguide.readthedocs.io/en/latest/google-typescript-styleguide/contents/#),本文档的风格指南,主要列出不同的地方和需要重点强调的部分。 + +> 英文: https://google.github.io/styleguide/tsguide.html + +部分规范通过配置 eslint 和 Prettier 规则就能从工具层面解决,这里可能不会赘述,请严格根据 eslint 提示来修复问题,如果使用 VS Code 来开发的话,强烈推荐安装 [eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 和 [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 插件。结合 `.vscode/settings.json` 配置,已经实现了保存文件时自动修复简单的 eslint 问题。 + +### 命名规范 + +1. 命名规则 + + | 命名法 | 分类 | + | ---------------------------------- | ---------------------------------------------- | + | 帕斯卡命名法 `UpperCamelCase` | 类、接口、类型、枚举、装饰器、类型参数、文件名 | + | 驼峰式命名法 `lowerCamelCase` | 变量、参数、函数、方法、属性、模块别名 | + | 全大写下划线命名法 `CONSTANT_CASE` | 全局常量、枚举值 | + | 横线命名 `param-case` | css 变量名 | + + - 模块的文件名和文件夹名称用帕斯卡命名法 `UpperCamelCase`,比如 `Domain`文件夹,`Range`文件名 + - 类型目录统一叫 `Types`,位于`src`下级 + - 工具目录统一叫 `Shared`,位于`src`下级,Shared 原则上不应该引入其他功能性的模块,可以引入基础模块,或者被其他模块引用 + - 测试目录统一叫 `test`,位于`src`同级 + - 模块文件夹中如果包含多个模块文件,推荐使用 `index.ts` 搜集起来统一导出,在使用这些模块时导入文件夹名称即可 + - 接口名以 `I` 开头,比如:`IColorStyle` + - [Action](../packages/core/command/Action)命名,采用`动词 + 模块 + 行为/属性`的模式,如果意思比较明确的,直接采用 `动词 + 模块`,比如 + - `动词 + 模块 + 行为`: `SetWorkSheetActivateAction` + - `动词 + 模块 + 属性`: `SetWorkSheetNameAction` + - `动词 + 模块`: `RemoveColumnAction` + - 类型为 `Map/WeakMap` 的变量,推荐使用 `XXXMap` 来命名,比如 `sheetMap` + - 类型为 `Array/Set` 的变量推荐使用 `XXXList`来命名,`sheetList` + +2. 推荐为私有属性或方法名添加下划线 \_ 前缀 + + 比如 + + ```ts + class MyClass { + // 推荐 + private _attr: String; + private _func() {} + } + ``` + +3. 变量名、方法名等取名字应该遵循的规则是,在表达清楚含义的前提下尽量简洁。推荐使用完整的单词,切勿使用汉字、拼音,如果想不到好的单词,可以到 [codelf](https://unbug.github.io/codelf/) 搜索下。 + +### 语言特性 + +1. 推荐在构造函数中显式地对类成员进行初始化,这样在执行多种参数处理时会更清晰 + + 比如 + + ```ts + class Foo { + private _barService: BarService; + private _fooService: FooService; + + // 推荐 + constructor(barService: BarService, fooConfig: FooConfig) { + this._barService = barService; // 直接赋值 + this._fooService = this.handleFoo(fooConfig); // 需要转换 + } + } + ``` + + 像下面的参数属性写法,只适用于简单的情况,为保持统一,我们推荐上面的显式写法 + + ```ts + class Foo { + // 不推荐 + constructor(private readonly barService: BarService) {} + } + ``` + +2. 一个通用方法是否需要独立到类之外,取决于它的通用性。多个类都会用到的话,可以抽离到类之外,类似一个工具函数,否则我们推荐作为私有方法写到同一个类中,让模块组织更清晰。 + +3. 推荐使用 `new Array` 来生成指定长度的数组,会让代码更简洁 + +4. 推荐使用存取器 `get` 和 `set` 来包装内部私有属性,这样写的好处是保留了扩展性。如果这个方法可能会考虑翻译到后端(比如公式),或者经常变化的业务数据,推荐使用 `getFunction`、`setFunction`。 + + 比如 + + ```ts + class Bar { + private _barInternal = ''; + + // 推荐 + get bar() { + return this._barInternal; + } + + // 推荐 + set bar(value: string) { + this._barInternal = value; + } + } + ``` + + 上面这个例子,如果已经有了`get bar` 和 `set bar`,最好不要再出现 `getBar` 和 `setBar` 方法,以免引起混淆。当你需要修改数据并支持传入多个参数时,应该起一个更具体的方法名称 + + 比如 + + ```ts + class Bar { + private _barInternal = ''; + + // 推荐 + get bar() { + return this._barInternal; + } + + // 推荐 + set bar(value: string) { + this._barInternal = value; + } + + // 推荐,多个入参,更具体的方法名称 + setBarName(value: string, name: string) { + this._barInternal = value + name; + } + + // 不推荐,会与 set bar 混淆 + setBar(value: string) { + this._barInternal = value; + } + } + ``` + +5. 类中的修饰符参照`private` `protected` `public`的顺序组织,内部使用 eslint 规则进行提示约束 `'@typescript-eslint/member-ordering': ['error', { default: ['private-field', 'protected-field', 'public-field', 'private-method', 'protected-method', 'public-method'] }],` + +6. 当使用 `forEach` 来简化 `for` 循环时,有三点注意: + + - `forEach` 如果有异步调用,需要关注变量是否被置为 `null`,必要时可以换为 `for-of` 形式的迭代 + + 比如 + + ```ts + // 使用 forEach 时需要主动避免出现这种情况 + let x: string | null = 'abc'; + myArray.forEach(() => { + // 异步方法,在使用 x 的时候,x 已被置空 + asyncFunc(x); + }); + + x = null; + ``` + + - 涉及嵌套循环及要求高性能的情况下,则不推荐使用 `forEach`,尽量用 `for` 循环 + - 数组中定位查找元素尽可能使用 `find`、`findIndex`、`some` 代替 `forEach` + +7. CSS 单位优先使用`px`, + +### 项目规范 + +1. 可能的优化点,暂未实现的功能,加 TODO + +2. 交互比较简单的 UI 推荐放到 `Spreadsheet` 插件,比如 `showGridlines`、`zoomRatio` + +3. 单个文件的行数不应该超过 1000 行,过多的行数可能意味着模块组织不合理 + +4. 一个 API 只能调用一个 Command,插件的 action 需要注入到这一个 Command 中,用于保证撤销重做正常执行 + +5. Action 默认情况下需要加入撤销重做栈、发送后台、修改本地数据,但是有的操作可能不需要撤销重做或者发送协同后台,这时候要配置 operationType。比如:切换 sheet 页,不需要发送协同;切换选区功能,不需要进撤销重做栈。 + +## 能力扩展 + +核心能力的扩展,依靠 Plugin、Registry 和 Register 来提供 + +1. Plugin:大的功能块,使用插件扩展核心能力,核心提供了 install 安装插件 +2. Registry:插件内部小的功能块,使用核心提供的一个 Registry 静态注册方法,方便在项目初始化的时候快速搜集内部模块的扩展实例 +3. Register:Register 将上一步 Registry 注册的方法,再统一动态注册管理,并且提供直接将内部模块动态注册上去的方法,以便开发者在外部进行动态扩增加某一个模块 + +## 插件开发流程 + +这里演示在 Univer 主仓库内部新建一个插件的流程 + +1. 首先到 cli 目录下安装 cli 工具的依赖 + + ```sh + cd cli + npm i + ``` + + 然后再回到主目录运行生成插件的命令 + +2. 通过下方命令,在主仓库内部生成一个名称为`data-validation`的插件 + + ```sh + npm run univer-cli create data-validation inner + ``` + + 程序将会在 `packages` 文件夹下,生成一个完整的插件模板,比如`packages/sheets-plugin-data-validation`。 + + 其中 inner 意思就是在 Univer 主仓库内部新建插件,也就是放到 packages 文件夹下。如果想在自己电脑的一个空白目录用脚手架新建一个插件,就不用加 inner。 + +3. 根据命令行的提示,使用 pnpm 安装依赖 和 启动开发模式。 + + 比如 `data-validation` 插件安装依赖 + + ```sh + pnpm i --filter @univer/sheets-plugin-data-validation + ``` + + 启动开发模式 + + ```sh + pnpm run --filter @univer/sheets-plugin-data-validation dev + ``` + + 这里用到了 pnpm 的 `--filter` 的功能来筛选出指定的插件来运行命令,更多学习内容请移步[官网 pnpm 过滤](https://pnpm.io/zh/filtering) + +4. 依据插件模板生成的新插件,默认会将插件里的 DOM 元素挂载到界面上的工具栏,内部是在插件里引入了`@univer/base-sheets`,然后通过其 API `addButton` 来动态挂载 DOM 元素,所以工具栏会多出一个`data-validation`插件按钮,即表明插件加载成功 + +5. 如果需要测试其他的插件和当前插件一起运行,需要在当前插件里安装您所需要的插件,比如以下命令是在`@univer/base-sheets` 插件中安装 `@univer/sheets-plugin-alternating-colors` 插件: + + ```sh + pnpm add @univer/sheets-plugin-alternating-colors --filter @univer/base-sheets + ``` + + 如果您还有协同开发的朋友,他本地的开发需要执行`pnpm install`来链接到安装的插件 + + Tips: + + > 为了能方便的调试核心和其它插件的代码,我们建议在主仓库内部进行插件开发,同时我们也提供了插件 CLI,方便在任意文件夹进行插件初始化,请到 [cli](../cli/README.md)) 详细了解 + + 常用组件 `PLUGIN_NAMES.SPREADSHEET`的方式,其它插件采用自己抛出变量,如`ALTERNATING_PLUGIN_NAME` + +## 插件开发实践 + +以 base-sheets 插件为例 + +### 插件目录 + +一个标准的插件目录结构如下所示 + +```sh +│ index.ts # 插件出口 +│ main.tsx # 插件调试预览入口 +│ preact.d.ts # preact 声明文件 +│ SpreadsheetPlugin.tsx # 插件核心导出类(实际名称会根据插件名称替换) +│ +├─Controller # 根据Model改变view状态;如果是preact,那就只涉及一些公共业务逻辑,修改view还是在preact的view里 +│ index.ts # 出口 +│ SpreadsheetController.ts # 具体的数据更新类(实际名称会根据插件名称替换) +│ +├─Basic # 基础工具函数、常量、接口 +│ ├─Const # 常量 +│ │ index.ts # 出口 +│ │ +│ ├─Enum # 枚举 +│ │ index.ts # 出口 +│ │ +│ │ +│ └─Shared # 公共方法 +│ index.ts # 出口 +│ +├─Locale # 国际化 +│ en.ts # 英文国际化翻译文件 +│ index.ts # 出口 +│ zh.ts # 中文国际化翻译文件 +│ +├─Model # 数据 CRDT +│ +├─Service # 通用的一些公共的外部服务连接,比如权限、其他插件的能力对接 +│ +├─Types # 声明文件 +│ index.d.ts # 具体的声明文件 +│ +└─View # 视图,dom和canvas的生成和管理 + ├─Render # canvas 组件 + │ index.ts # 出口 + │ + └─UI # DOM 组件 + SpreadsheetButton.tsx # 具体的DOM组件(实际名称会根据插件名称替换) + SpreadsheetButton.module.less # SpreadsheetButton组件的样式文件(实际名称会根据插件名称替换) +``` + +针对 DOM 组件,如果简单的组件写在一个 `Component.tsx组件即可`,如果组件比较复杂,建议新建一个组件命名的文件夹,来组织多个`.tsx`、`.less`文件,需要的话,还可以将类型和枚举类拆分出来定义。 + +### 挂载设置面板 + +- filterPlugin 类中的 initialize 初始化时,使用 SpreadsheetPlugin 插件提供的 API 来挂载 JSX 组件 + + 现有的 API 有 + + - addButton:挂载按钮到工具栏 + - addSider:挂载到右外侧边栏,并且配套了`showSiderByName`支持打开关闭对应插件的侧边栏 + + 原理:内部使用 preact `setState`将 JSX 组件更新到组件中,并保持国际化 context 的热更新 + + 一个可以参照的案例: + + 使用`addButton`将筛选插件的弹窗框触发按钮和弹出框的组件一同挂载到工具栏 + + ```tsx + export class FilterPlugin extends Plugin { + // 其他代码 + initialize(context: Context): void { + // 其他代码 + const item: IToolBarItemProps = { + locale: 'filter', + type: ISlotElement.JSX, + show: true, + label: , + }; + context.getPluginManager().getPluginByName('spreadsheet')?.addButton(item); + } + } + ``` + + 参照源码 [./packages/sheets-plugin-filter/src/FilterPlugin.tsx)](http://github.com/dream-num/univer/blob/master/packages/sheets-plugin-filter/src/FilterPlugin.tsx) + + 使用 `addMain`将插件组件挂载到中间的核心表格区域,并使用 `showMainByName`来控制组件的显示隐藏。 + + ```tsx + const mainItem: IMainProps = { + name: ALTERNATING_COLORS_PLUGIN_NAME, + type: ISlotElement.JSX, + content:
中间的alternating
, + }; + context.getPluginManager().getPluginByName('spreadsheet')?.addMain(mainItem); + context.getPluginManager().getPluginByName('spreadsheet')?.showMainByName(ALTERNATING_COLORS_PLUGIN_NAME, true); + ``` + + 参照源码 [./packages/sheets-plugin-alternating-colors/src/AlternatingColorsPlugin.tsx)](http://github.com/dream-num/univer/blob/master/packages/sheets-plugin-alternating-colors/src/AlternatingColorsPlugin.tsx) + + 使用`addSider`将交替颜色的设置面板挂载到侧边栏,交替颜色的按钮 + + ```tsx + export class AlternatingColorsPlugin extends Plugin { + initialize(context: Context): void { + // 其他代码 + const rangeData = { + startRow: 0, + endRow: 10, + startColumn: 0, + endColumn: 10, + }; + const banding = { + bandingTheme: { + headerColor: 'red', + + firstBandColor: 'lightgray', + + secondBandColor: 'lightgreen', + + footerColor: 'yellow', + }, + showHeader: true, + showFooter: true, + }; + + const panelItem: ISlotProps = { + name: ALTERNATING_COLORS_PLUGIN_NAME, + type: ISlotElement.JSX, + content: , + }; + context.getPluginManager().getPluginByName('spreadsheet')?.addSider(panelItem); + } + } + ``` + + 参照源码 [./packages/sheets-plugin-alternating-colors/src/AlternatingColorsPlugin.tsx)](http://github.com/dream-num/univer/blob/master/packages/sheets-plugin-alternating-colors/src/AlternatingColorsPlugin.tsx) + +- 除了没有您想要的挂载函数,还可以考虑使用 SpreadsheetPlugin 插件抛出的 DOM 元素,外部插件直接使用`appendChild`挂载。 + + 现有抛出元素的 API 有 + + - getToolBarRef:工具栏组件 + - getContentRef:中间内容区组件 + - getSplitLeftRef:中间左边内容区组件,在中间内容区被切分为两块内容区的时候存在 + + 原理:内部使用 preact `ref`链接到元素,布局组件初始化后挂载出来抛出 API,使用 `getContentRef().current`即可获取到具体的 DOM 元素 + + 一个可以参照的案例: + + ```tsx + export class SpreadsheetPlugin extends Plugin { + // 其他代码 + register(engineInstance: Engine) { + // 获取到内容区DOM + let container = this.context.getPluginManager().getPluginByName('spreadsheet')?.getContentRef().current!; + + // 将内容区设置为引擎的容器 + engineInstance.setContainer(container); + } + } + ``` + +### 获取 context + +在插件初始化的时候,会自动从核心(@univer/core)注入 context,context 包含 Univer 核心的所有上下文,我们根据 context 进行业务逻辑处理。 + +比如,获取 workbook + +```js +context.getWorkBook(); +``` + +### 挂载 meta data + +在核心的 workbook 和 worksheet 存储数据结构上预留了两个 `pluginMetaData` 对象,用来挂载插件数据。在插件初始化的时候,可以设置对应的插件数据。这个数据在核心渲染的时候会统一获取到之后和核心的数据做合并,得到的最终的结果用来渲染页面。 + +是否需要挂载 meta data,每个插件的情况不一样。插件的临时数据只需要插件自己存储,无需挂载到核心,只有需要存储的插件才需要挂载到核心上。如果一个插件的数据在每一个 sheet 页上都不一样,就要放在 worksheet 上,反之就挂载在 workbook 上 + +```js +const activeSheetId = context.getWorkBook().getActiveSheet().getSheetId(); +// add new AlternatingColors meta data +context.getWorkBook().setPluginMeta < IKeyType < IBandedRange >> (ALTERNATING_COLORS_PLUGIN_NAME, { [activeSheetId]: [] }); +``` + +### 国际化 + +从 context 调用 `getLocale` API 来动态加载国际化文件 + +- `context.getLocale()` 会得到 Locale 实例 +- `context.getLocale().options.currentLocale` 可以获取到当前语言,类似 `zh`、`en` +- `context.getLocale().load` 可以动态加载国际化文件,比如 + + ```js + context.getLocale().load({ + en: en, + zh: zh, + }); + ``` + +- `context.getLocale().get('key')`来取得 key 对应的翻译即可,语言不用特定获取,因为在初始化的时候就配置好了 + +### CSS 模块 + +我们内部使用 `less + CSS module` 模式来编写样式,一个`.module.less`结尾的 css 文件会被识别为 CSS Module,如果您不需要 CSS Module 功能,可以仅仅使用`.less`结尾。 + +通常来说,我们建议在组件的容器上加一个组件名开头的样式前缀来区分每一个 DOM 组件。 + +比如`alternating colors`插件,就会加一个`alternating-colors`前缀 + +```css +@import '@univer/style-universheet/assets/css/skin/base.module.less'; +.alternating-colors-panel-setting { + height: 100%; + + .section { + padding-top: @padding-base; + h3 { + margin-bottom: @margin-base; + } + } +} +``` + +在组件中引入样式 + +```tsx +import styles from './index.module.less'; +``` + +请注意,这里推荐使用 styles 这个变量名,因为一个自定义的 preact 组件,通常还需要支持从父级传下来的`style`样式,为了开发业务的用户能够方便的在自定义`tsx`组件中直接写 css 内联样式,自定义的 props 中最好能定义一个`style`属性,同样的,我们通常也支持`className`。 + +最后在`tsx`组件中或者 ts 方法中使用小驼峰写法来获取 css 变量 + +```tsx +// tsx组件中的className直接使用 +; + +// 或者 作为js变量使用 +const s = `${style.AlternatingColorsSideSetting}`; +``` + +### 不使用 preact 构造插件 UI + +#### 处理国际化 + +## 测试 + +核心代码测试使用 + +```sh +# 启动测试 +npm run test +``` + +在提交代码之前需要测试跑一下测试,保证自己的提交的代码一同编写了单元测试并通过。之后会加入 CI 自动化测试。 + +jest 测试指标含义: + +- `%stmts`是语句覆盖率(statement coverage):是不是每个语句都执行了? +- `%Branch`分支覆盖率(branch coverage):是不是每个 if 代码块都执行了? +- `%Funcs`函数覆盖率(function coverage):是不是每个函数都调用了? +- `%Lines`行覆盖率(line coverage):是不是每一行都执行了? + +我们的目标是所有覆盖率指标都在 85%以上。 + +UI 测试先安装依赖 + +```sh +npx playwright install +``` + +再执行测试 + +```sh +npm run test:ui +``` + +UI 测试主要是测试`@univer/style-universheet`的组件 + +- 未与核心耦合、未使用核心`Context`/`locale`的无状态组件,编写单元测试,测试文件的目录和组件在一个层级,参照`./packages/style-universheet/src/components/Container>`组件 + + ```sh + Container + │ container-zh.md # api文档 + │ container.md # api 英文文档 + │ Container.tsx # 组件核心tsx代码 + │ index.module.less # 组件核心less文件 + │ index.ts # 导出 + │ + └─test # 组件测试目录 + Container.test.tsx # 组件测试文件 + ``` + + 如果您想为`Container`组件增加测试代码,直接在`Container.test.tsx`文件中增加`test`方法即可。 + +- 与核心耦合、使用了核心`Context`/`locale`的有状态组件,编写 e2e 测试,技术上采用`Playwright`。因为要启动一个浏览器实例和本地服务,所以我们将所有需要 e2e 测试的组件放在一个文件中管理,测试文件为`./packages/style-universheet/test/components.e2e.spec.ts`,每个组件编写一个`it`用例即可,不要和之前的函数命名冲突即可。 + +Tips: + +测试 mock 数据 ,sheetId 统一用 sheet-01,因为可能会有多个 sheet + +测试命名:Test XXX + +测试时特别注意 切换 sheet 页对插件状态的影响 + +## 注释 + +注释全部采用英文,我们内部使用 api-extractor 来规范注释,并且会通过 api-documenter 来生成 API 文档 + +> 注释风格参考 [api-extractor 文档](https://api-extractor.com/pages/tsdoc/doc_comment_syntax/) + +Domain 下的核心 API/IData 开放的接口/工具类,需要加标签 `@alpha`、`@beta`、`@public`、`@internal` + +这是一个函数注释示例 + +```ts +/** @public */ +export class Statistics { + /** + * Returns the average of two numbers. + * + * @remarks + * This method is part of the {@link core-library#Statistics | Statistics subsystem}. + * + * @param x - The first input number + * @param y - The second input number + * @returns The arithmetic mean of `x` and `y` + * + * @beta + */ + static getAverage(x: number, y: number): number { + return (x + y) / 2.0; + } +} +``` + +接口默认值 + +```ts +export interface IWorksheetConfig { + /** + * Determine whether the sheet is hidden + * + * @remarks + * See {@link BooleanNumber | the BooleanNumber enum} for more details. + * + * @defaultValue `BooleanNumber.FALSE` + */ + hidden: BooleanNumber; +} +``` + +如果英文注释阅读比较困难,可以使用 vscode 插件`Comment Translate`来实时翻译注释, +或者直接添加双语注释 + +一个双语注释的例子,将中文注释藏到 `@privateRemarks` 标签里,不会展示在英文文档中 + +```ts +/** + * + * Column subscript letter to number + * + * @privateRemarks + * zh: 列下标 字母转数字 + * + * @param a - Column subscript letter,e.g.,"A1" + * @returns Column subscript number,e.g.,0 + * + */ +static ABCatNum(a: string): number { +} + +``` + +绑定的事件需要加上 `@eventProperty` + +```ts +/** + * Listen for click events + * @eventProperty + */ +handleClick(...args: Array) { +} +``` + +API 还需要提供一个简短的案例,如果有更详细的案例,就不适合放在注释里,可以加一个 `@link` 链接到案例文档 + +````ts +/** + * Set the sheet name. + * + * @example + * Set new name: + * ```ts + * worksheet.setName('NewSheet') + * ``` + * + * @param name - new sheet name + * @returns current worksheet instance + * + * @alpha + */ +setName(name: string): WorkSheet { +} +```` + +针对范型参数也有特殊的标签来表示 `@typeParam` + +```ts +/** + * @typeParam T - plugin data structure + * @param name - plugin name + * @returns information stored by the plugin + */ +getPluginMeta(name: string): T { +} +``` + +如果是继承的抽象方法,可以在抽象方法定义的地方统一描述,子类使用 `@inheritDoc` 直接引用父类的描述 + +比如插件的设计中, + +接口定义 + +```ts +export interface BasePlugin { + /** + * mapping + * + * @param container - IOC container dependency injection + * @public + */ + onMapping(container: IOCContainer): void; +} +``` + +实现接口的抽象方法定义 + +```ts +export abstract class Plugin implements BasePlugin { + /** + * {@inheritDoc BasePlugin.onMapping} + */ + abstract onMapping(container: IOCContainer): void; +} +``` + +第三方插件定义 + +```ts +export class AlternatingColorsPlugin extends Plugin { + /** + * {@inheritDoc Plugin.onMapping} + */ + onMapping(IOC: IOCContainer): void {} +} +``` + +## 为核心贡献 API + +首先为您的 API 分好类别,在核心的[Domain](../packages/core/src/Sheets/Domain) 文件夹中,已经有多个模块分类到了不同的文件,比如典型的模块 `WorkSheet`、`WorkBook`、`Range`,如果您的 API 属于这些已分好类的模块,请将他们直接加到这些模块中。如果您的 API 不属于 Domain 文件夹下的任何一个模块,请新建一个文件来编写您的 API,写法上参照 `WorkSheet` 模块的写法即可。 + +- 如果要实现一个简单的查询 API,函数名是以`get`开头,比如`worksheet.getSheetId`,直接从实例存储的数据中获取 +- 如果要实现一个操作类的 API,函数名是以`set`开头,比如`setName`,通常用于参数设置和操作的 API 都需要执行 `Action` 来触发后续`Service`数据更新和协同 + - 如果已有 `Action` 可用,直接调用已有`Action`即可,比如 `range.setBackground` 和 `range.setFontColors` 这两个 API 都是调用的 `SetRangeStyleAction` + - 如果你的 API 涉及新的操作,那么需要编写对应的`Action`来触发`Service`数据更新和协同 + +`Univer`的核心架构是 + +- `Domain`提供 API,触发`Action` +- `Action`触发协同,调用`Service`具体操作数据 + +一个简单的设置 worksheet 切换按钮 color 的 API [setTabColor](../packages/core/src/Sheets/Domain/WorkSheet.ts) 的案例 + +1. 在[ACTION_NAMES](../packages/core/src/Const/ACTION_NAMES.ts)中预先定义一个`Action`名称 `SET_TAB_COLOR_ACTION` + +2. 将 `Action` 注册到[CommandManager](../packages/core/src/Command/RegisterAction.ts) + +3. 在[Action](../packages/core/src/Sheets/Action)文件夹中新建一个文件[SetTabColorAction.ts](../packages/core/src/Sheets/Action/SetTabColorAction.ts) + + 这个 API 只需要一个名称 `color` 即可,所以`Action`的接口格式可以为 + + ```js + // packages/core/src/Command/Action/SetTabColorAction.ts + interface ISetTabColorActionData extends IActionData { + color: Nullable; + } + ``` + + 构造器中需要手动将当前执行的数据存储到`this._doActionData`对象中吗,然后执行`this.do()`方法来调用[SetTabColorService](../packages/core/src/Sheets/Apply/SetTabColor.ts),`SetTabColorService`负责直接操作 `worksheet`上的数据,`Service`操作完之后有一个重要的工作就是返回历史数据给上层的`Action`。还是在构造器中,拿到历史数据后存储到 `this._oldActionData`用于撤销。因为我们设置 tab color 后,撤销的时候也是设置 tab color,所以在`this._oldActionData`也是存储`SET_TAB_COLOR_ACTION` + + ```ts + this._oldActionData = { + actionName: ACTION_NAMES.SET_TAB_COLOR_ACTION, + sheetId: actionData.sheetId, + color: this.do(), + }; + ``` + + 注意撤销的操作可能和执行不是同一个`Action`,比如清除范围[ClearRangeAction](../packages/core/src/Sheets/Action/ClearRangeAction.ts)之后,撤销的时候需要调用设置范围[SetRangeDataAction](../packages/core/src/Sheets/Action/SetRangeDataAction.ts)。并且也因为撤销重做用到的`Action`不同,所以在`Service`中返回的历史数据,要符合撤销的`Action`的所需的数据结构。 + +4. 我们在调用撤销 API `worksheet.getCommandManager().undo()` 的时候,核心的 `UndoManager` 会自动调用`this._oldActionData`所存储`Action`所包含的`undo` 方法来执行撤销,我们需要在 `undo`方法里调用对应的 [SetTabColorService](../packages/core/src/Sheets/Apply/SetTabColor.ts),同时将 `SetTabColorService`返回的数据重新存储到 `this._doActionData` 中。因为在撤销之前,数据可能会被其他客户端修改后协同同步过来,这样本地浏览器存储的历史记录是作废的,所以这里需要重新更新 `this._doActionData`数据。 +5. 同样的,我们在调用重做 API `worksheet.getCommandManager().redo()` 的时候,核心的 `UndoManager` 会自动调用`this._doActionData`所存储`Action`所包含的`redo` 方法来执行重做,操作完成后也是更新下`this._oldActionData`数据,原理同撤销。这样一个`Action`的核心功就完成了,其他的辅助的方法可以一同写在这个`Action`类中 + +6. 在`WorkSheet.ts`中增加一个 API,就叫 `setTabColor`,入参仅仅一个 `color` 就够了,同时支持 `Null`,我们内部很多的 API 是参照了 [Google Sheet API](https://developers.google.com/apps-script/reference/spreadsheet/sheet#settabcolorcolor),有不适配我们架构的地方会做部分修改。在这个 `setTabColor` API 中,内部组织一个`SET_TAB_COLOR_ACTION`的配置信息,用于生成`Command`,由`CommandManager`触发这个`Action`即可。 + + ```ts + /** + * Sets the sheet tab color. + * @param color A color code in CSS notation (like '#ffffff' or 'white'), or null to reset the tab color. + * @returns WorkSheet This sheet, for chaining. + */ + setTabColor(color: Nullable): WorkSheet { + const { _context, _commandManager } = this; + let setTabColor: ISetTabColorActionData = { + sheetId: this._sheetId, + actionName: ACTION_NAMES.SET_TAB_COLOR_ACTION, + color: color, + }; + let command = new Command(_context.getWorkBook(), setTabColor); + _commandManager.invoke(command); + return this; + } + ``` + + 注意,简单情况下,一个`Command`包装一个`Action`足够了,复杂情况下,一个 `Command`还可能包含多个 `Action`才能完成一个操作任务,必须[RangeList](../packages/core/src/Sheets/Domain/RangeList.ts)中的`setValue`方法会负责处理多个范围的数据 + +7. 每一个 API 和 `Action` 都需要编写一个单元测试,API 的测试文件统一放在[test/Domain](../packages/core/test/Domain)文件夹下,比如`WorkSheet.test.ts`测试文件就是专门为所有 worksheet 的 API 编写的测试,测试方法的命名规则为`Test [API Name|描述]`,比如这里的`Test setTabColor`,一个完整的单元测试包括初始化一个新的 Univer 实例,执行 API 操作,撤销,重做 + + ```ts + test('Test setTabColor', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { sheetId, tabColor: 'red' }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + // clear sheet + worksheet.setTabColor('blue'); + + // test current sheet tab color + const currentTabColor = worksheet.getConfig().tabColor; + expect(currentTabColor).toEqual('blue'); + + // undo + worksheet.getCommandManager().undo(); + + // test previous sheet tab color + const preTabColor = worksheet.getConfig().tabColor; + expect(preTabColor).toEqual('red'); + + // redo + worksheet.getCommandManager().redo(); + + // test next sheet tab color + const nextTabColor = worksheet.getConfig().tabColor; + expect(nextTabColor).toEqual('blue'); + }); + ``` + + 这样一个完整的 API 就开发完成了。 + +Tips: +核心是否要包装插件 API,要看核心是否提供了 Action,不允许核心调用插件 API,如 Find 插件提供 createTextFinder,核心不提供 + +## 组件开发 + +### 技术原理 + +我们采用`preact`框架和`tsx`语法来开发 DOM 组件。 + +### 设计原则 + +- 通用:可能在多个页面级组件中用到,达到组件复用的目的,尽量减少和其他模块的耦合性 +- 易用:为常用的功能提供设置项,完善的 API +- 方便扩展:如果默认功能不能满足用户要求,需要让用户能很容易看懂你的代码,以便修改源码来满足需要 +- 尽量拆分:我们遵循模块和组件拆分`越细越好`的原则 + +通常来说,通用组件设计成无状态组件比较好,但是如果一个无状态组件嵌套很深,在我们修改组件传参的时候,需要跨越好几个父级组件来传递参数,往往给组件开发带来很大的复杂度,所以一个`context`的设计显得比较重要,通过全局透传的`context`,取得一些统一的参数,这个设计对国际化的功能很有帮助。需不需要用到`context`,要看组件的复杂度,当你的组件元素非常多的时候,可以考虑`context`,构造一个像`Button`的简单小组件,不需要用到`context` + +### 目录和命名规范 + +一个新的`preact tsx`组件,主要包括组件核心逻辑、单元测试和文档。一个完整的`Container`组件的目录结构如下 + +```sh +├─Container +│ │ container-zh.md # API说明文档中文版 markdown文件名采用 +│ │ container.md # API说明文档英文版 +│ │ Container.tsx # 组件核心逻辑 组件名采用大驼峰写法 +│ │ index.module.less # 样式 +│ │ index.ts # 模块导出的出口 +│ └─test +│ Container.test.tsx # 单元测试 +``` + +如果遇到更复杂的组件需求,可以在组件文件夹内新建模块,或者将能够复用的部分拆分到更小的组件中,由几个小组件组合成一个大的组件。 + +### 开发组件 + +1. 在`Container.tsx`中正常编写组件核心逻辑 +2. 在`base-component/src/BaseComponent`中增加一个组件专用接口,比如`ContainerComponent` +3. 在`Container.tsx`中增加`UniverContainer`类导出组件 + + ```ts + export class UniverContainer implements ContainerComponent { + constructor() {} + render(): JSXComponent { + return Container; + } + } + ``` + + 这时候如果`Container`这里报错了,说明在`JSXElement`需要增加 `Container`的类型 + +4. 在`style-universheet/src/StyleUniver.ts`中的`UniverComponentFactory`类下,在`createComponent`中增加导出`UniverContainer`组件实例 +5. 组件已经构造好了,可以测试一下。比如在`base-sheets`中引入使用,在`base-sheets/src/Domain/Spreadsheet.ts`中定义组件类型 + + ```ts + export type ISheetContainerConfig = { + // other code + Container: FunctionComponent; + }; + ``` + + 在`base-sheets/src/SpreadsheetPlugin.tsx`中配置进`config`传输到`base-sheets/src/View/UI/SpreadsheetButton.tsx`组件中使用 + + ```ts + const config: ISheetContainerConfig = { + Button: Button, + Container: Container, + }; + ``` + + 正常使用组件 + + ```ts + render(props: IProps, state: IState) { + const { Button, Container } = props.config; + return ( +
+ + + +
+ ); + } + + ``` + +## 单元测试 + +### 核心 + +jest 单元测试 + +### preact 组件 + +| 测试种类 | 技术选型 | +| -------- | ------------- | +| 单元测试 | Jest + Enzyme | +| 快照测试 | Jest | +| E2E 测试 | Playwright | + +每一个组件都应该至少包含一个完整的 jest 单元测试,我们使用 preact 官房推荐的`@testing-library/preact`库来做 DOM 测试, + +> [preact 组件测试](https://preactjs.com/guide/v10/preact-testing-library/) + +比如测试一个`Container`组件是否正常渲染 + +```tsx +// --- ./packages/style-universheet/src/Components/Container/test/Container.test.tsx --- +import { h } from 'preact'; + +import { render } from '@testing-library/preact'; + +import { Container } from '..'; + +describe('Container', () => { + test('should display initial Container', () => { + const { container } = render(container content Text); + + expect(container.textContent).toMatch('container content Text'); + }); +}); +``` + +e2e 测试时,内部采用 `window.performance.memory` API 来监测内存使用情况 + +#### canvas 测试 + +单元测试,生成 canvas 组件类转换,进行 API 操作,获取参数是否符合预期 +e2e 测试,保证渲染一致性,用 canvas 组件生成一个图片,测试的时候用 render 生成 canvas 转成图片,对比之前的图片是否有差异 +使用 + +#### 公式测试 + +先自行整理一些常用公式,后期开放用户测试,搜集 log,用户填写的公式搜集起来,放到测试用例 + +### 性能测试 + +### 统计代码行数 + +```sh +git ls-files | xargs wc -l +``` + +### 说明文档 + +每一个组件都应该有一份详细的`.md`说明文档, 文档至少包括组件介绍、API、案例 + +比如一个简答的`Button`按钮说明文档 + +````md +--- +category: Components +type: 通用 +title: Button +subtitle: 按钮 +cover: '' +--- + +## 介绍 + +通用按钮组件 + +按钮类型 + +- 主按钮:类型`primary`,一般用于弹窗的确定按钮,每行的确认按钮,一个操作区域只能有一个主按钮 +- 默认按钮:工具栏按钮 +- 文本按钮:取消功能 + +按钮状态 + +- 禁用:工具栏禁用状态 +- 加载中:异步操作等待状态 + +## API + +通过设置 Button 的属性来产生不同的按钮样式,推荐顺序为:`type` -> `shape` -> `size` -> `loading` -> `disabled`。 + +按钮的属性说明如下: + +| 属性 | 说明 | 类型 | 默认值 | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | --------- | +| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | +| danger | 设置危险按钮 | boolean | false | +| disabled | 按钮失效状态 | boolean | false | +| htmlType | 设置 `button` 原生的 `type` 值,可选值请参考 [HTML 标准](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` | +| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | +| shape | 设置按钮形状 | `circle` \| `round` | - | +| size | 设置按钮大小 | `large` \| `middle` \| `small` | `middle` | +| type | 设置按钮类型 | `primary` \| `ghost` \| `dashed` \| `link` \| `text` \| `default` | `default` | +| onClick | 点击按钮时的回调 | (event) => void | - | +| children | 按钮内容或者子节点 | any | - | +| className | 点击按钮时的回调 | string | - | + +支持原生 button 的其他所有属性。 + +## 案例 + +```jsx +import { Button } from '@univer/style-universheet'; + +const ToolBar = () => { + const buttonText = '确认'; + return ( + + ); +}; +``` +```` + +## 依赖管理 + +### 安装依赖 + +- 安装生产依赖到某一个插件里,比如在`plugin-sort`里安装`preact`: `pnpm add preact --filter @univer/sheets-plugin-sort` +- 安装开发依赖到某一个插件里,加一个 `-D` 比如在`plugin-sort`里安装`jest`: `pnpm add -D jest --filter @univer/sheets-plugin-sort` +- 安装开发依赖到所有插件里,比如在所有插件里安装`jest`: `pnpm add -D jest`,这类插件一般称为公共依赖,公共依赖包一般安装在项目一级根目录的`package.json`中 +- 更新公共依赖包:`pnpm update` + +## 打包 + +### 整体打包 + +打包`packages`文件夹下的核心库和所有插件,在对应插件的下的`lib`目录下生成 `umd.js`和`esm.js`结尾的文件。 + +```sh +npm run build +``` + +### 单个打包 + +### 版本控制 + +### 发布 npm + +## 插件使用 + +### 安装插件 + +先用 npm 安装核心包`@univer/core`、渲染引擎 `@univer/base-render`、表格组件插件`@univer/base-sheets`、基础 UI 插件`@univer/style-universheet`和筛选插件`@univer/sheets-plugin-filter` + +```sh +npm i @univer/core @univer/base-render @univer/base-sheets @univer/style-universheet @univer/sheets-plugin-filter +``` + +然后再引入插件使用 + +```js +import { UniverSheet } from '@univer/core'; +import '@univer/core/lib/style.css'; +import { UniverComponentSheet } from '@univer/style-universheet'; +import { RenderEngine } from '@univer/base-render'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; + +// 初始化universheet +const univerSheet = UniverSheet.newInstance({ + id: 'workbook-01', +}); + +// 渲染引擎 +univerSheetUp.installPlugin(new RenderEngine()); + +// 加载基础 UI 插件 +univerSheetUp.installPlugin(new UniverComponentSheet()); + +// 基础 sheets 插件 +univerSheetUp.installPlugin( + new SpreadsheetPlugin({ + containerId: 'universheet-demo-up', + layout: 'auto', + }) +); +// 加载筛选插件 +univerSheet.installPlugin(new FilterPlugin()); +``` + +### 动态加载 + +为了提高首屏加载速度,可以使用`import()`动态加载 +先用 npm 安装插件 + +```sh +npm i @univer/core @univer/base-render @univer/base-sheets @univer/style-universheet @univer/sheets-plugin-filter +``` + +然后再动态引入插件使用 + +```js +import { UniverSheet } from '@univer/core'; +import '@univer/core/lib/style.css'; +import { UniverComponentSheet } from '@univer/style-universheet'; +import { RenderEngine } from '@univer/base-render'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; + +// 初始化universheet +const univerSheet = UniverSheet.newInstance({ + id: 'workbook-01', +}); + +// 渲染引擎 +univerSheetUp.installPlugin(new RenderEngine()); + +// 加载基础 UI 插件 +univerSheetUp.installPlugin(new UniverComponentSheet()); + +// 基础 sheets 插件 +univerSheetUp.installPlugin( + new SpreadsheetPlugin({ + containerId: 'universheet-demo-up', + layout: 'auto', + }) +); + +// 动态加载筛选插件 +import('@univer/sheets-plugin-filter').then(({ FilterPlugin }) => { + univerSheetUp.installPlugin(new FilterPlugin()); +}); +``` + +### 自定义打包插件 + +如果你感觉一个个安装插件比较麻烦,我们的自定义打包器可以把你需要的插件打包在一起,并且可以统一配置插件。 + +在工程根目录下运行自定义打包命令 + +```sh +# 将 sort 和 comment 与核心一起打包,需要打包更多插件只需列在后面 +npm run univer-cli -- build -- --include sort comment +``` + +将核心和插件一起打包为一个完整的模块 `univer-custom-build.es.js`,文件位于`./cli/univer-custom-build/lib/`目录下,同级还有一个`style.css`。 +将这两个文件复制到你的工程项目里,引入模块即可,然后通过`univerSheetCustom`初始化整个 universheet 和插件,可以统一配置核心和插件 + +```js +// 可能需要修改成你自己的模块路径 +import { univerSheetCustom } from './univer-custom-build.es.js'; +import './style.css'; + +// 可选的核心配置 +const workbookConfig = { + id: '', + sheetOrder: [], + socketEnable: BooleanNumber.FALSE, + socketUrl: '', + name: '', + timeZone: '', + appVersion: '', + theme: '', + skin: '', + locale: '', + creator: '', + styles: [], + sheets: [], + lastModifiedBy: '', + createdTime: '', + modifiedTime: '', +}; + +// 可选的spreadsheetConfig插件配置 +const spreadsheetConfig = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; + +// 统一配置入口 +const universheet = univerSheetCustom({ + workbookConfig: workbookConfig, + spreadsheetConfig: spreadsheetConfig, +}); +``` + +除了打包为一整个模块,还可以配置为动态模块, + +```sh +npm run build:customd +``` + +将每个插件单独打包出模块,内部自动使用`import()`动态载入插件,提升首屏加载速度,用法上和上面几乎一致,区别是需要将打包后所有文件复制到工程目录即可,其他配置一样。 + +## 已知问题 + +1. 遇上科学计数法 会卡死 + 输入 =1.2974966045650646E8 + 有时候输入后系统崩溃 + 有时候是输入后在操作其他单元格系统崩溃 diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000000..fac687a173 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,89 @@ +# Univer Contributing Guide + +## Tool + +- vite +- Typescript/tsx +- Typescript references +- pnpm workspace + +## Project Introduction + +For multi-package projects managed by lerna, there are plugins and cores in the packages directory, and examples are in the examples directory + +## Develop + +### Install dependencies + +Node.js >= 14.19 + +```bash +git clone http://github.com/dream-num/univer +cd univer +npm i -g pnpm # MacOS : sudo npm i -g pnpm +npx playwright install +pnmp install +npm run dev +``` + +### Command + +Execute a command in a subpackage + +```bash +pnpm run --filter [package name] [command] +``` + +For example, start the `dev` development mode of the `packages/sheets-plugin-sort` project + +```bash +pnpm run --filter @univer/sheets-plugin-sort dev + +``` + +### Clean + +If you encounter any npm installation problems, please try one-click reinstallation of dependencies first + +```bash +npm run clean +``` + +### Plug-in development + +1. Quickly generate plug-in template directory + +```bash +npm run cli +``` + +2. For example, select the `plugin-temp` template and enter the plugin name `filter` to generate the `packages/sheets-plugin-filter` plugin. + +3. Execute the following command to start the plug-in development mode + +```bash +pnpm run --filter @univer/sheets-plugin-filter dev +``` + +4. There will be an additional `filter` plugin button in the toolbar on the interface + +## Code comment + +English is preferred, if the comment is longer, you can add bilingual + +```js +/* + en: get result + + zh: 得出结果 +*/ +function(){ + +} +``` + +## Update a dependency package + +```bash +pnpm update +``` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..4f6cbb781c --- /dev/null +++ b/.gitignore @@ -0,0 +1,98 @@ +#ide +*.DS_Store +.idea +# .vscode + +#npm +package-lock.json +yarn.lock +pnpm-lock.yaml + +dist +lib +docs/.vuepress/dist + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Runtime config +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL History +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +#lerna-changelog +.changelog + +#editor workspace file +.idea/ + +.history +# examples + +screenshot + +univer-custom-build +universheet-custom-build + +temp +etc + +# api extractor, api documenter +docs/input +docs/markdown +doc \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000000..c743d18455 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# npm run lint diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..f87a044348 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +auto-install-peers=true \ No newline at end of file diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000000..6604107223 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + singleQuote: true, + trailingComma: 'es5', + tabWidth: 4, + semi: true, + printWidth: 180, +}; diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..12f9e45a95 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "name": "vscode-jest-tests", + "request": "launch", + "args": [ + "--runInBand", + "--watchAll=false" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "program": "${workspaceFolder}/node_modules/.bin/jest", + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..9c451bb16b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,64 @@ +{ + "prettier.eslintIntegration": true, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "commentTranslate.targetLanguage": "zh-CN", + "[html]": { + "editor.defaultFormatter": "vscode.html-language-features" + }, + "commentTranslate.hover.enabled": true, + "commentTranslate.source": "Google", + + "cSpell.words": [ + "Bandings", + "Catmull", + "clazz", + "codelf", + "compositionend", + "compositionstart", + "compositionupdate", + "CRDT", + "csstype", + "customd", + "Datas", + "esbuild", + "evented", + "execa", + "FONTFACE", + "fract", + "gainsboro", + "Gridlines", + "Hanalei", + "hdmx", + "Heiti", + "Hiragino", + "ilvl", + "Kaiti", + "Lerp", + "Liti", + "universheet", + "mengshukeji", + "Overlines", + "Pacifico", + "pnmp", + "ponyfill", + "preact", + "PWDEBUG", + "Quan", + "Sider", + "SUMIF", + "Tahoma", + "univer", + "universheet", + "Unmerge", + "Verdana", + "viewports", + "Xingkai", + "Xinwei" + ], + "javascript.preferences.importModuleSpecifier": "relative", + "typescript.preferences.importModuleSpecifier": "relative" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..9c2b36ed7a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020-present, DreamNum + +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. \ No newline at end of file diff --git a/README-zh.md b/README-zh.md new file mode 100644 index 0000000000..1945cb926c --- /dev/null +++ b/README-zh.md @@ -0,0 +1,133 @@ +# Univer + +## + +简体中文 | [English](./README.md) + +## 介绍 + +🚀Univer,原 [Luckysheet 2.0](https://github.com/dream-num/Luckysheet) 升级。 + +> ⚠️该项目仍在开发中,仅用于测试和学习,不得用于生产 + +## 开发 + +### 环境 + +[Node.js](https://nodejs.org/en/) Version >= 14.19 + + +### 安装 +``` +git clone http://github.com/dream-num/univer +cd univer +npm i -g pnpm # MacOS : sudo npm i -g pnpm +npx playwright install +pnmp i +``` +### 开发 +``` +npm run dev +``` + +更多开发教程请参考 [贡献指南](./.github/contributing-zh.md)。 + +## 问题反馈 + +请到 [Issues](http://github.com/dream-num/univer/issues) 提交问题。 + +## 开发计划 + +### Sheets + +- 基础能力 + - [x] 核心数据结构 + - [x] 渲染 + - [x] 界面 + - [x] 单元格编辑 + +- sheet操作 + - [x] sheet删除 + - [x] sheet复制 + - [x] sheet排序 + - [x] sheet重命名 + - [x] sheet更改颜色 + - [x] sheet隐藏 + - [x] sheet取消隐藏 + - [x] sheet向左移 + - [x] sheet向右移 + +- 右击菜单 + - [x] 复制 + - [x] 粘贴 + - [x] 插入行 + - [x] 插入列 + - [x] 删除选中行 + - [x] 删除选中列 + - [x] 删除单元格 + - [x] 清除内容 + +- 工具栏 + - [x] 撤销 + - [x] 重做 + - [ ] 格式刷 + - [ ] 货币格式 + - [ ] 百分比格式 + - [ ] 减少小数位数 + - [ ] 增加小数位数 + - [ ] 更多格式 + - [x] 字体 + - [x] 字体大小 + - [x] 加粗 + - [x] 斜体 + - [x] 删除线 + - [x] 下划线 + - [x] 文本颜色 + - [x] 单元格颜色 + - [x] 边框 + - [x] 合并单元格 + - [x] 水平对齐 + - [x] 垂直对齐 + - [x] 文本换行 + - [x] 文本旋转 + +- 计数栏 + - [x] 计数 + - [x] 缩放 + + +- 插件 + - [x] 剪切板 + - [ ] 单元格格式 + - [ ] 公式 + - [ ] 图片 + - [ ] 冻结 + - [ ] 链接 + - [ ] 批注 + - [ ] 数据透视表 + - [ ] 图表 + - [ ] 排序 + - [ ] 筛选 + - [ ] 条件格式 + - [ ] 交替颜色 + - [ ] 数据验证 + - [ ] 分列 + - [ ] 截图 + - [ ] 查找 + - [ ] 替换 + - [ ] 工作表保护 + - [ ] 打印 + - [ ] 导入导出 + - [ ] 协同 +### Docs + +- [x] 渲染 +- [ ] 界面 +- [ ] 待定 + +### Slides + +- [ ] 渲染 +- [ ] 界面 +- [ ] 待定 + diff --git a/README.md b/README.md new file mode 100644 index 0000000000..45182ef9a8 --- /dev/null +++ b/README.md @@ -0,0 +1,131 @@ +# Univer + +English| [简体中文](./README-zh.md) + +## Introduction + +Univer,[Luckysheet 2.0](https://github.com/dream-num/luckysheet) upgrade version. + +> ⚠️ This project is still in development, only for testing and learning, not for production + +## Development + +### Requirements + +[Node.js](https://nodejs.org/en/) Version >= 14.19 + + +### Installation +```` +git clone http://github.com/dream-num/univer +cd univer +npm i -g pnpm # MacOS : sudo npm i -g pnpm +npx playwright install +pnmp i +```` +### Development +```` +npm run dev +```` + +For more development tutorials, please refer to [Contribution Guide](./.github/contributing.md). + +## Issues + +Please file an issue at [Issues](http://github.com/dream-num/univer/issues). + +## Development Plan + +### Sheets + +- Basic ability + - [x] core data structures + - [x] render + - [x] UI + - [x] cell editing + +- Sheet operation + - [x] sheet delete + - [x] sheet copy + - [x] sheet sort + - [x] sheet rename + - [x] sheet change color + - [x] sheet hide + - [x] sheet unhide + - [x] sheet move left + - [x] sheet moves right + +- Right click menu + - [x] copy + - [x] paste + - [x] insert row + - [x] insert column + - [x] delete selected rows + - [x] delete selected columns + - [x] delete cell + - [x] clear content + +- Toolbar + - [x] undo + - [x] redo + - [ ] formatter + - [ ] currency format + - [ ] percent format + - [ ] Decrease decimal places + - [ ] increase the number of decimal places + - [ ] more formats + - [x] font family + - [x] font size + - [x] bold + - [x] italic + - [x] strikethrough + - [x] underscore + - [x] text color + - [x] fill color + - [x] border + - [x] merge cells + - [x] horizontal alignment + - [x] vertical alignment + - [x] text wrap + - [x] text rotation + +- Count bar + - [x] count + - [x] zoom + + +- Plugins + - [x] clipboard + - [ ] format + - [ ] formula + - [ ] image + - [ ] Frozen + - [ ] link + - [ ] comment + - [ ] pivot table + - [ ] chart + - [ ] sort + - [ ] filter + - [ ] conditional format + - [ ] alternating colors + - [ ] data validation + - [ ] split column + - [ ] screenshot + - [ ] find + - [ ] replace + - [ ] protection + - [ ] print + - [ ] import and export + - [ ] collaboration + +### Docs + +- [x] render +- [ ] UI +- [ ] Pending + +### Slides + +- [ ] render +- [ ] UI +- [ ] Pending \ No newline at end of file diff --git a/api-extractor.json b/api-extractor.json new file mode 100644 index 0000000000..f7dc09acd7 --- /dev/null +++ b/api-extractor.json @@ -0,0 +1,380 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + /** + * Optionally specifies another JSON config file that this file extends from. This provides a way for + * standard settings to be shared across multiple projects. + * + * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains + * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be + * resolved using NodeJS require(). + * + * SUPPORTED TOKENS: none + * DEFAULT VALUE: "" + */ + // "extends": "./shared/api-extractor-base.json" + // "extends": "my-package/include/api-extractor-base.json" + + /** + * Determines the "" token that can be used with other config file settings. The project folder + * typically contains the tsconfig.json and package.json config files, but the path is user-defined. + * + * The path is resolved relative to the folder of the config file that contains the setting. + * + * The default value for "projectFolder" is the token "", which means the folder is determined by traversing + * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder + * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error + * will be reported. + * + * SUPPORTED TOKENS: + * DEFAULT VALUE: "" + */ + // "projectFolder": "..", + + /** + * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor + * analyzes the symbols exported by this module. + * + * The file extension must be ".d.ts" and not ".ts". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + */ + "mainEntryPointFilePath": "/lib/src/index.d.ts", + + /** + * A list of NPM package names whose exports should be treated as part of this package. + * + * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", + * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part + * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly + * imports library2. To avoid this, we can specify: + * + * "bundledPackages": [ "library2" ], + * + * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been + * local files for library1. + */ + "bundledPackages": [], + + /** + * Determines how the TypeScript compiler engine will be invoked by API Extractor. + */ + "compiler": { + /** + * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * Note: This setting will be ignored if "overrideTsconfig" is used. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/tsconfig.json" + */ + // "tsconfigFilePath": "/tsconfig.json", + /** + * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. + * The object must conform to the TypeScript tsconfig schema: + * + * http://json.schemastore.org/tsconfig + * + * If omitted, then the tsconfig.json file will be read from the "projectFolder". + * + * DEFAULT VALUE: no overrideTsconfig section + */ + // "overrideTsconfig": { + // . . . + // } + /** + * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended + * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when + * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses + * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. + * + * DEFAULT VALUE: false + */ + // "skipLibCheck": true, + }, + + /** + * Configures how the API report file (*.api.md) will be generated. + */ + "apiReport": { + /** + * (REQUIRED) Whether to generate an API report. + */ + "enabled": true, + + /** + * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce + * a full file path. + * + * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". + * + * SUPPORTED TOKENS: , + * DEFAULT VALUE: ".api.md" + */ + // "reportFileName": ".api.md", + + /** + * Specifies the folder where the API report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, + * e.g. for an API review. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/etc/" + */ + "reportFolder": "/etc/" + + /** + * Specifies the folder where the temporary report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * After the temporary file is written to disk, it is compared with the file in the "reportFolder". + * If they are different, a production build will fail. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/" + */ + // "reportTempFolder": "/temp/" + }, + + /** + * Configures how the doc model file (*.api.json) will be generated. + */ + "docModel": { + /** + * (REQUIRED) Whether to generate a doc model file. + */ + "enabled": true + + /** + * The output path for the doc model file. The file extension should be ".api.json". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/.api.json" + */ + // "apiJsonFilePath": "/temp/.api.json" + }, + + /** + * Configures how the .d.ts rollup file will be generated. + */ + "dtsRollup": { + /** + * (REQUIRED) Whether to generate the .d.ts rollup file. + */ + "enabled": true + + /** + * Specifies the output path for a .d.ts rollup file to be generated without any trimming. + * This file will include all declarations that are exported by the main entry point. + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/dist/.d.ts" + */ + // "untrimmedFilePath": "/dist/.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release. + * This file will include only declarations that are marked as "@public", "@beta", or "@alpha". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "alphaTrimmedFilePath": "/dist/-alpha.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. + * This file will include only declarations that are marked as "@public" or "@beta". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "betaTrimmedFilePath": "/dist/-beta.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. + * This file will include only declarations that are marked as "@public". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "publicTrimmedFilePath": "/dist/-public.d.ts", + + /** + * When a declaration is trimmed, by default it will be replaced by a code comment such as + * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the + * declaration completely. + * + * DEFAULT VALUE: false + */ + // "omitTrimmingComments": true + }, + + /** + * Configures how the tsdoc-metadata.json file will be generated. + */ + "tsdocMetadata": { + /** + * Whether to generate the tsdoc-metadata.json file. + * + * DEFAULT VALUE: true + */ + // "enabled": true, + /** + * Specifies where the TSDoc metadata file should be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", + * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup + * falls back to "tsdoc-metadata.json" in the package folder. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + }, + + /** + * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files + * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. + * To use the OS's default newline kind, specify "os". + * + * DEFAULT VALUE: "crlf" + */ + // "newlineKind": "crlf", + + /** + * Configures how API Extractor reports error and warning messages produced during analysis. + * + * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. + */ + "messages": { + /** + * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing + * the input .d.ts files. + * + * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "compilerMessageReporting": { + /** + * Configures the default routing for messages that don't match an explicit rule in this table. + */ + "default": { + /** + * Specifies whether the message should be written to the the tool's output log. Note that + * the "addToApiReportFile" property may supersede this option. + * + * Possible values: "error", "warning", "none" + * + * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail + * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes + * the "--local" option), the warning is displayed but the build will not fail. + * + * DEFAULT VALUE: "warning" + */ + "logLevel": "warning" + + /** + * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), + * then the message will be written inside that file; otherwise, the message is instead logged according to + * the "logLevel" option. + * + * DEFAULT VALUE: false + */ + // "addToApiReportFile": false + } + + // "TS2551": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + + /** + * Configures handling of messages reported by API Extractor during its analysis. + * + * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" + * + * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings + */ + "extractorMessageReporting": { + // "default": { + // "logLevel": "warning" + // // "addToApiReportFile": false + // }, + "ae-forgotten-export": { + "logLevel": "warning", + "addToApiReportFile": true + } + + // "ae-extra-release-tag": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + + /** + * Configures handling of messages reported by the TSDoc parser when analyzing code comments. + * + * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "tsdocMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + } + + // "tsdoc-link-tag-unescaped-text": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + } + } +} diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000000..d62c80069c --- /dev/null +++ b/cli/README.md @@ -0,0 +1,46 @@ +# Univer CLI Tool + +## Usage + +Install dependency + +```sh +npm i -g @univer/cli +``` + +### Create Plugin +Create a Univer plugin named `my-plugin` from a template + +```sh +univer-cli create my-plugin +``` + +start develop plugin +```sh +cd my-plugin +npm i +npm run dev +``` + +### Custom Build + + +```sh +univer-cli build --include sort comment +``` + +## API + +### Create + +Command +```sh +univer-cli create [inner] +``` + +- plugin: Required parameters, specify a plugin name, Please use `param-case` format, such as `data-validation` +- inner: Optional parameter, use this tag inside the Univer repository. With this flag set, we will put the plugin in the packages directory, otherwise we will create a new plugin in the current directory where the script is executed by default + +### Build + +### Init \ No newline at end of file diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000000..78078ef078 --- /dev/null +++ b/cli/package.json @@ -0,0 +1,44 @@ +{ + "name": "@univer/cli", + "version": "1.0.26", + "description": "Quickly build univer plugins, custom package univer cores and plugins", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/type.d.ts", + "publishConfig": { + "main": "./lib/index.js", + "module": "./lib/index.js", + "types": "./lib/index.d.ts" + }, + "scripts": { + "dev": "tsc --project tsconfig.cli.json -w", + "cli": "ts-node --project tsconfig.cli.json src/cli.ts", + "build": "shx rm -rf lib/* && npx tsc --project tsconfig.cli.json && shx cp -r src/templates lib/templates" + }, + "author": "DreamNum ", + "license": "MIT", + "files": [ + "package.json", + "lib" + ], + "dependencies": { + "chalk": "4.1.2", + "commander": "^9.0.0", + "ejs": "^3.0.2", + "execa": "^4.1.0", + "inquirer": "^7.1.0", + "shelljs": "^0.8.4", + "shx": "^0.3.4" + }, + "devDependencies": { + "@types/ejs": "^3.0.2", + "@types/inquirer": "^6.5.0", + "@types/node": "^13.11.1", + "@types/shelljs": "^0.8.7", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc" + }, + "bin": { + "univer-cli": "./lib/cli.js" + } +} diff --git a/cli/src/build.ts b/cli/src/build.ts new file mode 100644 index 0000000000..5df712303e --- /dev/null +++ b/cli/src/build.ts @@ -0,0 +1,182 @@ +#!/usr/bin/env node + +import * as fs from 'fs'; +import * as path from 'path'; +import shell from 'shelljs'; +import { FgGreen, FgRed, Reset } from './utils/color'; +const execa = require('execa'); +import { renderContent } from './utils/template'; +import { generateRandomId } from './utils/tool'; +import { IEventBus } from './utils/event-bus'; +import { exec } from 'child_process'; + +// TODO 提供clear目录方法,用于下载完后删除项目 +// dynamic 模式 + +interface IOption { + all?: boolean; + include?: string[]; + exclude?: string[]; +} + +export interface IBuildJson { + [key: string]: string; +} + +export interface IObj { + msg: string; +} + +type PublishType = [IObj, number]; + +const defaultBuildList: IBuildJson = { + filter: '//', + sort: '//', + alternatingColors: '//', + comment: '//', +}; + +const allBuildList: IBuildJson = { + filter: ' ', + sort: ' ', + alternatingColors: ' ', + comment: ' ', +}; + +const CURR_DIR = process.cwd(); + +interface ILibOptions { + eventBus: IEventBus; + buildId: string; +} + +export function build(options: IOption, libOptions?: ILibOptions) { + return new Promise((resolve, reject) => { + const buildList: IBuildJson = filterBuild(options); + const { eventBus, buildId } = libOptions || {}; + // When we provide nodejs interface, support library mode, create a new project directory with a random name to prevent directory conflicts from running by multiple users at the same time. + const id = buildId ? buildId : ''; + const randomFolderName = 'univer-custom-build-' + id; + + eventBus?.publish('progress', { msg: 'Pull template files' }, 0.1); + + copyTemplate(randomFolderName); + + eventBus?.publish('progress', { msg: 'Update custom configuration' }, 0.2); + + updateEntry(buildList, randomFolderName); + + eventBus?.publish('progress', { msg: 'Install dependencies' }, 0.3); + + buildScript(resolve, reject, randomFolderName, eventBus); + }); +} + +function filterBuild(options: IOption): IBuildJson { + const { all, include, exclude } = options; + let filterBuildList: IBuildJson = defaultBuildList; + + if (all) { + filterBuildList = allBuildList; + } + + if (include) { + filterBuildList = defaultBuildList; + include.forEach((plugin) => { + if (plugin in defaultBuildList) { + filterBuildList[plugin] = ' '; + } + }); + } + if (exclude) { + filterBuildList = allBuildList; + exclude.forEach((plugin) => { + if (plugin in allBuildList) { + filterBuildList[plugin] = '//'; + } + }); + } + + return filterBuildList; +} + +function copyTemplate(folderName: string) { + const templatePath = path.join(__dirname, 'templates/univer-custom-build'); + copyFolderRecursiveSync(templatePath, CURR_DIR); + fs.rename(path.join(CURR_DIR, 'univer-custom-build'), path.join(CURR_DIR, folderName), () => {}); +} + +function updateEntry(json: IBuildJson, folderName: string) { + const entryFolder = path.join(CURR_DIR, folderName); + const entryFile = path.join(entryFolder, 'index-template.ts'); + + // read file content and transform it using template engine + let contents = fs.readFileSync(entryFile, 'utf8'); + + contents = renderContent(contents, json); + + // write file to destination folder + let writePath = path.join(entryFolder, 'src/index.custom.ts'); + fs.writeFileSync(writePath, contents, 'utf8'); +} + +async function buildScript(resolve: Function, reject: Function, folderName: string, eventBus?: IEventBus) { + shell.cd(folderName); + // execa('cd', ['univer-custom-build'], { stdio: 'inherit' }) + // The host environment may be production, which will cause development dependencies to fail to be installed, reference https://docs.npmjs.com/cli/v8/commands/npm-install + await execa('npm', ['i', '--production=false', '--prefer-offline'], { stdio: 'inherit' }); + + eventBus?.publish('progress', { msg: 'Build source code' }, 0.8); + + try { + await execa('npm', ['run', 'build'], { stdio: 'inherit' }); + console.log(`\n${FgGreen}Custom build success!${Reset} \n`); + eventBus?.publish('progress', { msg: 'Build complete' }, 1.0); + resolve('success'); + } catch (error) { + console.error(`\n${FgRed}Custom build fail!${Reset} \n`); + eventBus?.publish('progress', { msg: 'Build interrupt' }, 0); + reject('fail'); + } +} + +function copyFileSync(source: string, target: string) { + var targetFile = target; + + // If target is a directory, a new file with the same name will be created + if (fs.existsSync(target)) { + if (fs.lstatSync(target).isDirectory()) { + targetFile = path.join(target, path.basename(source)); + } + } + + fs.writeFileSync(targetFile, fs.readFileSync(source)); +} + +/** + * Reference https://stackoverflow.com/a/26038979 + * @param source + * @param target + */ +function copyFolderRecursiveSync(source: string, target: string) { + var files = []; + + // Check if folder needs to be created or integrated + var targetFolder = path.join(target, path.basename(source)); + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + // Copy + if (fs.lstatSync(source).isDirectory()) { + files = fs.readdirSync(source); + files.forEach(function (file) { + var curSource = path.join(source, file); + if (fs.lstatSync(curSource).isDirectory()) { + copyFolderRecursiveSync(curSource, targetFolder); + } else { + copyFileSync(curSource, targetFolder); + } + }); + } +} diff --git a/cli/src/cli.ts b/cli/src/cli.ts new file mode 100644 index 0000000000..b1816b8085 --- /dev/null +++ b/cli/src/cli.ts @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +import { Command } from 'commander'; +import { build } from './build'; +import { create } from './create'; +const program = new Command(); + +program + .command('build') + .option('-a, --all', 'Package the core and all plugins together') + .option('-i, --include ', 'List plugins that need to be packaged') + .option('-e, --exclude ', 'List plugins that do not need to be packaged') + .description('Package the core and plugins together,core and base plugins are packaged by default') + .action((options) => { + build(options); + }); + +// TODO:check npm package name conflict +program + .command('create [inner]') + .description('Create a Univer plugin from a template') + .action((plugin) => { + create(plugin); + }); + +program.parse(process.argv); diff --git a/cli/src/create.ts b/cli/src/create.ts new file mode 100644 index 0000000000..691b9074fa --- /dev/null +++ b/cli/src/create.ts @@ -0,0 +1,169 @@ +#!/usr/bin/env node + +/** + * Quickly generate plug-in directory + * + * Reference from https://github.com/DuwainevanDriel/project-template-generator + */ + +import chalk from 'chalk'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as template from './utils/template'; +interface CliOptions { + projectName: string; + templateName: string; + templatePath: string; + tartgetPath: string; +} + +// interface IOptions { +// inner?: boolean; +// } +export function create(plugin: string) { + console.log('plugin create', plugin); + + const cliOptions: CliOptions = { + //@ts-ignore + projectName: '', + //@ts-ignore + templateName: '', + templatePath: '', + tartgetPath: '', + }; + + const SKIP_FILES = ['node_modules', '.template.json']; + + const CURR_DIR = process.cwd(); + const inner = process.argv[4]; + + let DESTINATION_DIR = ''; + + if (inner === 'inner') { + // core repository create plugin + DESTINATION_DIR = path.join(CURR_DIR, '../packages'); + } else { + // cli + DESTINATION_DIR = path.join(CURR_DIR, ''); + } + + const TEMP_PLUGIN_NAME = 'Sort'; + + const projectChoice = 'plugin-temp'; + const projectValue = covertToCamelCase(plugin); + const projectUpperValue = covertToPascalCase(projectValue); + const projectName = covertToParamCase(projectValue); + + //@ts-ignore + const templatePath = path.join(__dirname, 'templates', projectChoice); + + // sheets-plugin-data-validation => sheets-plugin-data-validation + //@ts-ignore + const tartgetPath = path.join(DESTINATION_DIR, projectName); + + cliOptions.projectName = projectName; + cliOptions.templateName = projectChoice; + cliOptions.templatePath = templatePath; + cliOptions.tartgetPath = tartgetPath; + + if (!createProject(tartgetPath)) { + return; + } + + //@ts-ignore + createDirectoryContents(templatePath, projectName, cliOptions); + + // postProcess(cliOptions); + devTips(projectName); + + function createDirectoryContents(templatePath: string, projectName: string) { + // read all files/folders (1 level) from template folder + const filesToCreate = fs.readdirSync(templatePath); + // loop each file/folder + filesToCreate.forEach((file) => { + const origFilePath = path.join(templatePath, file); + + // get stats about the current file + const stats = fs.statSync(origFilePath); + + // skip files that should not be copied + if (SKIP_FILES.indexOf(file) > -1) return; + + if (stats.isFile()) { + // read file content and transform it using template engine + let contents = fs.readFileSync(origFilePath, 'utf8'); + + contents = template.render(contents, { projectValue, projectUpperValue, projectName: cliOptions.projectName }); + + // write file to destination folder + let writePath = path.join(DESTINATION_DIR, projectName, file); + + // change file name + if (writePath.indexOf(TEMP_PLUGIN_NAME) > -1) { + writePath = writePath.replace(TEMP_PLUGIN_NAME, projectUpperValue); + } + + fs.writeFileSync(writePath, contents, 'utf8'); + } else if (stats.isDirectory()) { + // create folder in destination folder + + let writeFolderPath = path.join(DESTINATION_DIR, projectName, file); + + // change folder name (Not recommended. Because the directory of each plug-in should be the same) + // if (writeFolderPath.indexOf(TEMP_PLUGIN_NAME) > -1) { + // writeFolderPath = writeFolderPath.replace(TEMP_PLUGIN_NAME, projectUpperValue); + // } + + fs.mkdirSync(writeFolderPath); + + // const destination = path.join(CURR_DIR, 'packages'); + // fs.mkdirSync(path.join(destination, projectName, file)); + // copy files/folder inside current folder recursively + createDirectoryContents(path.join(templatePath, file), path.join(projectName, file)); + } + }); + } +} + +function createProject(projectPath: string) { + if (fs.existsSync(projectPath)) { + console.log(chalk.red(`Folder ${projectPath} exists. Delete or use another name.`)); + return false; + } + fs.mkdirSync(projectPath); + + return true; +} + +function devTips(projectName: string) { + // pnpm run --filter @univer/sheets-plugin-<%= projectValue %> dev + console.log(chalk.green(`\nSuccess!\n`)); + console.log(chalk.white(`Install Dependencies: \n`, chalk.green(`pnpm i --filter @univer/${projectName}\n`))); + console.log(chalk.white(`Develop: \n`, chalk.green(`pnpm run --filter @univer/${projectName} dev\n`))); +} + +function covertToPascalCase(str: string) { + return str + .replace(/\b\w/g, (match) => { + return match.toUpperCase(); + }) + .replace(/-/g, ''); +} + +function covertToParamCase(str: string, prefix = 'plugin') { + return prefix + str.replace(/(-\w|[A-Z]|\b\w)/g, (match) => (match.indexOf('-') > -1 ? match.toLowerCase() : `-${match.toLowerCase()}`)); +} + +/** + * + * Reference: https://stackoverflow.com/a/2970667 + * @param str + * @returns + */ +function covertToCamelCase(str: string) { + return str + .replace(/(?:\b\w)/g, function (match, index) { + return index === 0 ? match.toLowerCase() : match.toUpperCase(); + }) + .replace(/-/g, ''); +} diff --git a/cli/src/index.ts b/cli/src/index.ts new file mode 100644 index 0000000000..ef02e6155e --- /dev/null +++ b/cli/src/index.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +import { build, IObj } from './build'; +import { create } from './create'; +import { EventBus, IEventBus } from './utils/event-bus'; + +export { build, create, EventBus}; +export type {IObj,IEventBus} diff --git a/cli/src/init.ts b/cli/src/init.ts new file mode 100644 index 0000000000..0b6b4aa5c0 --- /dev/null +++ b/cli/src/init.ts @@ -0,0 +1,166 @@ +#!/usr/bin/env node + +/** + * Quickly generate plug-in directory + * + * Reference from https://github.com/DuwainevanDriel/project-template-generator + */ + +import chalk from 'chalk'; +import * as fs from 'fs'; +import * as inquirer from 'inquirer'; +import * as path from 'path'; +import * as template from './utils/template'; +interface CliOptions { + projectName: string; + templateName: string; + templatePath: string; + tartgetPath: string; +} + +interface IOptions { + inner?: boolean; +} +export function init(options: IOptions) { + console.log('template init', options); + + const cliOptions: CliOptions = { + //@ts-ignore + projectName: '', + //@ts-ignore + templateName: '', + templatePath: '', + tartgetPath: '', + }; + + const SKIP_FILES = ['node_modules', '.template.json']; + + const CURR_DIR = process.cwd(); + + let DESTINATION_DIR = ''; + + if (options.inner == true) { + // core repository create plugin + DESTINATION_DIR = path.join(CURR_DIR, '../packages'); + } else { + // cli + DESTINATION_DIR = path.join(CURR_DIR, ''); + } + + const TEMP_PLUGIN_NAME = 'Sort'; + + let projectValue: string = ''; + let projectUpperValue: string = ''; + + const CHOICES = fs.readdirSync(path.join(__dirname, 'templates')); + const QUESTIONS = [ + { + name: 'template', + type: 'list', + message: 'What template would you like to use?', + choices: CHOICES, + }, + { + name: 'name', + type: 'input', + message: 'Please input a new plugin name:', + }, + ]; + + inquirer.prompt(QUESTIONS).then((answers) => { + const projectChoice = answers['template'] as string; + projectValue = answers['name'] as string; + projectUpperValue = capitalizeFirstLetter(projectValue); + + const projectName = ('plugin-' + projectValue).replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`); + + //@ts-ignore + const templatePath = path.join(__dirname, 'templates', projectChoice); + + // sheets-plugin-data-validation => sheets-plugin-data-validation + //@ts-ignore + const tartgetPath = path.join(DESTINATION_DIR, projectName); + + cliOptions.projectName = projectName; + cliOptions.templateName = projectChoice; + cliOptions.templatePath = templatePath; + cliOptions.tartgetPath = tartgetPath; + + if (!createProject(tartgetPath)) { + return; + } + + //@ts-ignore + createDirectoryContents(templatePath, projectName, cliOptions); + + // postProcess(cliOptions); + devTips(projectName); + }); + + function createDirectoryContents(templatePath: string, projectName: string) { + // read all files/folders (1 level) from template folder + const filesToCreate = fs.readdirSync(templatePath); + // loop each file/folder + filesToCreate.forEach((file) => { + const origFilePath = path.join(templatePath, file); + + // get stats about the current file + const stats = fs.statSync(origFilePath); + + // skip files that should not be copied + if (SKIP_FILES.indexOf(file) > -1) return; + + if (stats.isFile()) { + // read file content and transform it using template engine + let contents = fs.readFileSync(origFilePath, 'utf8'); + + contents = template.render(contents, { projectValue, projectUpperValue, projectName: cliOptions.projectName }); + + // write file to destination folder + let writePath = path.join(DESTINATION_DIR, projectName, file); + + // change file name + if (writePath.indexOf(TEMP_PLUGIN_NAME) > -1) { + writePath = writePath.replace(TEMP_PLUGIN_NAME, projectUpperValue); + } + + fs.writeFileSync(writePath, contents, 'utf8'); + } else if (stats.isDirectory()) { + // create folder in destination folder + + let writeFolderPath = path.join(DESTINATION_DIR, projectName, file); + + // change folder name (Not recommended. Because the directory of each plug-in should be the same) + // if (writeFolderPath.indexOf(TEMP_PLUGIN_NAME) > -1) { + // writeFolderPath = writeFolderPath.replace(TEMP_PLUGIN_NAME, projectUpperValue); + // } + + fs.mkdirSync(writeFolderPath); + + // const destination = path.join(CURR_DIR, 'packages'); + // fs.mkdirSync(path.join(destination, projectName, file)); + // copy files/folder inside current folder recursively + createDirectoryContents(path.join(templatePath, file), path.join(projectName, file)); + } + }); + } +} + +function createProject(projectPath: string) { + if (fs.existsSync(projectPath)) { + console.log(chalk.red(`Folder ${projectPath} exists. Delete or use another name.`)); + return false; + } + fs.mkdirSync(projectPath); + + return true; +} + +function capitalizeFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +function devTips(projectName: string) { + // pnpm run --filter @univer/sheets-plugin-<%= projectValue %> dev + console.log(chalk.green(` SUCCESS!\n\n`), chalk.white(`Check directory packages/${projectName}, run the following command to develop: \n\n`), chalk.green(`pnpm run --filter @univer/${projectName} dev`)); +} diff --git a/cli/src/templates/plugin-temp/.eslintrc.js b/cli/src/templates/plugin-temp/.eslintrc.js new file mode 100644 index 0000000000..7f4cf3e7ba --- /dev/null +++ b/cli/src/templates/plugin-temp/.eslintrc.js @@ -0,0 +1,51 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + parser: '@typescript-eslint/parser', + extends: [ + 'airbnb-typescript', + 'prettier', + 'eslint:recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + ], + parserOptions: { + ecmaVersion: 12, + sourceType: 'module', + project: './tsconfig.json', + }, + plugins: ['@typescript-eslint', 'prettier', 'import'], + rules: { + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], + 'import/prefer-default-export': 'off', + 'import/no-extraneous-dependencies': [ + 'error', + { devDependencies: true }, + ], + // turn on errors for missing imports + 'import/no-unresolved': 'error', + }, + settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + typescript: { + alwaysTryTypes: true, // always try to resolve config under `@config` directory even it doesn't contain any source code, like `@config/unist` + + // Choose from one of the "project" configs below or omit to use /tsconfig.json by default + + // use /path/to/folder/tsconfig.json + project: './tsconfig.json', + }, + }, + }, +}; diff --git a/cli/src/templates/plugin-temp/.gitignore b/cli/src/templates/plugin-temp/.gitignore new file mode 100644 index 0000000000..12e1bd6bec --- /dev/null +++ b/cli/src/templates/plugin-temp/.gitignore @@ -0,0 +1,101 @@ +#ide +*.DS_Store +.idea +.vscode + +#npm +package-lock.json + +dist +lib +docs/.vuepress/dist + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Runtime config +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL History +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +#lerna-changelog +.changelog + + +#editor workspace file +.idea/ + + +.DS_Store + +.history +# examples + + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.env.local +.env.development.local +.env.test.local +.env.production.local diff --git a/cli/src/templates/plugin-temp/.prettierrc.cjs b/cli/src/templates/plugin-temp/.prettierrc.cjs new file mode 100644 index 0000000000..6604107223 --- /dev/null +++ b/cli/src/templates/plugin-temp/.prettierrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + singleQuote: true, + trailingComma: 'es5', + tabWidth: 4, + semi: true, + printWidth: 180, +}; diff --git a/cli/src/templates/plugin-temp/README-zh.md b/cli/src/templates/plugin-temp/README-zh.md new file mode 100644 index 0000000000..ce9ee6ae8e --- /dev/null +++ b/cli/src/templates/plugin-temp/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet <%= projectUpperValue %>插件 + +### 安装 + +```bash +npm i @univer/<%= projectName %> +``` + +### 使用 + +```js +impport {<%= projectUpperValue %>} from '@univer/<%= projectName %>' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new <%= projectUpperValue %>()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/<%= projectName %>/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/<%= projectName %> dev +``` + +### 打包 + +``` +# 当前目录 ./packages/<%= projectName %>/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/<%= projectName %> build +``` diff --git a/cli/src/templates/plugin-temp/README.md b/cli/src/templates/plugin-temp/README.md new file mode 100644 index 0000000000..36a4be688c --- /dev/null +++ b/cli/src/templates/plugin-temp/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin <%= projectUpperValue %> + +### Installation + +```bash +npm i @univer/<%= projectName %> +``` + +### Usage + +```js +impport {<%= projectUpperValue %>} from '@univer/<%= projectName %>' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new <%= projectUpperValue %>()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/<%= projectName %>/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/<%= projectName %> dev +``` + +### Package + +``` +# Current directory ./packages/<%= projectName %>/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/<%= projectName %> build +``` diff --git a/cli/src/templates/plugin-temp/index.html b/cli/src/templates/plugin-temp/index.html new file mode 100644 index 0000000000..e8b1031294 --- /dev/null +++ b/cli/src/templates/plugin-temp/index.html @@ -0,0 +1,24 @@ + + + + + + + + UniverSheet + + + + +
+
+ + + + \ No newline at end of file diff --git a/cli/src/templates/plugin-temp/jest.config.js b/cli/src/templates/plugin-temp/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/cli/src/templates/plugin-temp/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/cli/src/templates/plugin-temp/package.json b/cli/src/templates/plugin-temp/package.json new file mode 100644 index 0000000000..78a40c41dc --- /dev/null +++ b/cli/src/templates/plugin-temp/package.json @@ -0,0 +1,59 @@ +{ + "name": "@univer/<%= projectName %>", + "version": "0.0.1", + "description": "UniverSheet normal <%= projectName %>", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.0.1", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "eslint": "^8.22.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0", + "less": "^4.1.1", + "prettier": "^2.3.2", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/base-render": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "preact": "^10.5.14" + } +} diff --git a/cli/src/templates/plugin-temp/src/Basic/Const/index.ts b/cli/src/templates/plugin-temp/src/Basic/Const/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/templates/plugin-temp/src/Basic/Enum/index.ts b/cli/src/templates/plugin-temp/src/Basic/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/templates/plugin-temp/src/Basic/Shared/index.ts b/cli/src/templates/plugin-temp/src/Basic/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/templates/plugin-temp/src/Basic/index.ts b/cli/src/templates/plugin-temp/src/Basic/index.ts new file mode 100644 index 0000000000..640d1bb8b3 --- /dev/null +++ b/cli/src/templates/plugin-temp/src/Basic/index.ts @@ -0,0 +1,4 @@ +export * from './Const'; +export * from './Enum'; +export * from '../Locale'; +export * from './Shared'; diff --git a/cli/src/templates/plugin-temp/src/Controller/index.ts b/cli/src/templates/plugin-temp/src/Controller/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/templates/plugin-temp/src/Locale/en.ts b/cli/src/templates/plugin-temp/src/Locale/en.ts new file mode 100644 index 0000000000..7c550a79a9 --- /dev/null +++ b/cli/src/templates/plugin-temp/src/Locale/en.ts @@ -0,0 +1,9 @@ +export default { + <%= projectValue %>Label: '<%= projectUpperValue %>', + <%= projectValue %>RightLabel: 'more <%= projectUpperValue %>', + <%= projectValue %>: { + one:'one', + two:'two', + three:'three', + }, +}; diff --git a/cli/src/templates/plugin-temp/src/Locale/index.ts b/cli/src/templates/plugin-temp/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/cli/src/templates/plugin-temp/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/cli/src/templates/plugin-temp/src/Locale/zh.ts b/cli/src/templates/plugin-temp/src/Locale/zh.ts new file mode 100644 index 0000000000..eaecc5a5b7 --- /dev/null +++ b/cli/src/templates/plugin-temp/src/Locale/zh.ts @@ -0,0 +1,9 @@ +export default { + <%= projectValue %>Label: '插件', + <%= projectValue %>RightLabel: '更多 <%= projectUpperValue %>', + <%= projectValue %>: { + one:'一', + two:'二', + three:'三', + }, +}; diff --git a/cli/src/templates/plugin-temp/src/Model/index.ts b/cli/src/templates/plugin-temp/src/Model/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/templates/plugin-temp/src/Service/index.ts b/cli/src/templates/plugin-temp/src/Service/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/templates/plugin-temp/src/SortPlugin.tsx b/cli/src/templates/plugin-temp/src/SortPlugin.tsx new file mode 100644 index 0000000000..2228e1fd5a --- /dev/null +++ b/cli/src/templates/plugin-temp/src/SortPlugin.tsx @@ -0,0 +1,47 @@ +import { Context, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { <%= projectUpperValue %>Button } from './View/UI/<%= projectUpperValue %>Button'; +import { zh, en } from './Locale'; + +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { IOCContainer } from '@univer/core'; + +interface IPluginConfig {} + +interface IConfig {} + +export class <%= projectUpperValue %>Plugin extends Plugin { + + constructor(config ?: IPluginConfig) { + super('plugin<%= projectUpperValue %>'); + } + + initialize(): void { + + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en: en, + zh: zh, + }); + const config: IConfig = {}; + + const item: IToolBarItemProps = { + locale: '<%= projectValue %>', + type: ISlotElement.JSX, + show: true, + label: <<%= projectUpperValue %>Button config={ config } /> + } + context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.addButton(item) +} + +onMapping(IOC: IOCContainer): void {} + +onMounted(ctx: Context): void { + this.initialize() +} + +onDestroy(): void {} +} diff --git a/cli/src/templates/plugin-temp/src/Types/index.d.ts b/cli/src/templates/plugin-temp/src/Types/index.d.ts new file mode 100644 index 0000000000..b1968350ee --- /dev/null +++ b/cli/src/templates/plugin-temp/src/Types/index.d.ts @@ -0,0 +1,12 @@ +/// + +export * from '../index'; +declare module '@univer/<%= projectName %>' {} + +import JSX = preact.JSX; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/cli/src/templates/plugin-temp/src/View/Render/index.ts b/cli/src/templates/plugin-temp/src/View/Render/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cli/src/templates/plugin-temp/src/View/UI/SortButton.tsx b/cli/src/templates/plugin-temp/src/View/UI/SortButton.tsx new file mode 100644 index 0000000000..8a1a354dbe --- /dev/null +++ b/cli/src/templates/plugin-temp/src/View/UI/SortButton.tsx @@ -0,0 +1,104 @@ +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { ISelectButton, IToolBarItemProps, Component } from '@univer/base-component'; + +interface IProps {} +// Types for state +interface IState { + <%= projectValue %>: IToolBarItemProps; +} + +export class <%= projectUpperValue %>Button extends Component { + + private _localeObserver: Nullable>; + initialize(props: IProps) { + + const OrderASCIcon = this.getComponentRender().renderFunction('OrderASCIcon'); + const OrderDESCIcon = this.getComponentRender().renderFunction('OrderDESCIcon'); + const OrderIcon = this.getComponentRender().renderFunction('OrderIcon'); + const NextIcon = this.getComponentRender().renderFunction('NextIcon'); + + const <%= projectValue %> = { + locale: '<%= projectValue %>', + type: 'select', + label: , + icon: , + show: true, + border: false, + selectType: ISelectButton.DOUBLE, + needChange: false, + children: [ + { + locale: '<%= projectValue %>.one', + icon: , + }, + { + locale: '<%= projectValue %>.two', + icon: , + }, + { + locale: '<%= projectValue %>.three', + icon: , + }, + ], + }; + + this.state = { + <%= projectValue %>: <%= projectValue %>, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable','core')?.add(() => { + this.setLocale(); + }); + } + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable','core')?.remove(this._localeObserver); + } + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.<%= projectValue %>; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}Label`); + item.tooltipRight = locale.get(`${item.locale}RightLabel`); + + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label == 'object' ? item.label : item.children![0].label; + + return { + <%= projectValue %>: item, + }; + }); + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const { <%= projectValue %> } = state; + const Select = this.getComponentRender().renderFunction('Select'); + // Set Provider for entire Container + return + + +
+ {update.label} +
+
+ {save.label} +
+ + ); + } +} diff --git a/packages/base-sheets/src/View/UI/InfoBar/index.less b/packages/base-sheets/src/View/UI/InfoBar/index.less new file mode 100644 index 0000000000..59113a5d75 --- /dev/null +++ b/packages/base-sheets/src/View/UI/InfoBar/index.less @@ -0,0 +1,46 @@ +@import '@univer/style-universheet/assets/css/skin/base.module.less'; + +.universheet-info-detail { + position: relative; + left: 0; + top: 0; + margin: 0; + padding: 0 17px; + height: 56px; + display: flex; + align-items: center; + background: var(--bg); + box-shadow: 0 -1px 0 0 @gray-16; + border-bottom: 1px solid var(--border-color-base); + + .universheet-info-return { + font-size: 16px; + padding: 0 5px; + font-size: 14px; + } + + .univericon.univericon-logo { + margin-left: 18px; + + svg { + width: 100%; + height: 100%; + } + } + + .sheet-name { + margin: 0 auto; + } + + .universheet-info-detail-update { + color: @gray-17; + font-size: 12px; + margin-left: 15px; + } + + .universheet-info-detail-save { + color: @gray-7; + font-size: 12px; + margin: 0 5px; + } +} diff --git a/packages/base-sheets/src/View/UI/InfoBar/index.ts b/packages/base-sheets/src/View/UI/InfoBar/index.ts new file mode 100644 index 0000000000..345955a1ce --- /dev/null +++ b/packages/base-sheets/src/View/UI/InfoBar/index.ts @@ -0,0 +1 @@ +export * from './InfoBar'; diff --git a/packages/base-sheets/src/View/UI/InfoBar/infobar-zh.md b/packages/base-sheets/src/View/UI/InfoBar/infobar-zh.md new file mode 100644 index 0000000000..89972db6ef --- /dev/null +++ b/packages/base-sheets/src/View/UI/InfoBar/infobar-zh.md @@ -0,0 +1,10 @@ +--- +category: Components +type: 通用 +title: InfoBar +cover: '' +--- + +## 介绍 + +最顶部的栏目,包含 logo/项目名 diff --git a/packages/base-sheets/src/View/UI/RightMenu/RightMenu.tsx b/packages/base-sheets/src/View/UI/RightMenu/RightMenu.tsx new file mode 100644 index 0000000000..5e38bd5dc2 --- /dev/null +++ b/packages/base-sheets/src/View/UI/RightMenu/RightMenu.tsx @@ -0,0 +1,188 @@ +import { BaseComponentRender, BaseComponentSheet, BaseRightMenuProps, BaseUlProps, Component, createRef } from '@univer/base-component'; +import { IMouseEvent } from '@univer/base-render'; +import { Nullable, Observer, PLUGIN_NAMES, WorkBook } from '@univer/core'; +import Style from './index.module.less'; + +interface BaseRightMenuChildrenProps extends BaseUlProps { + flag?: string; +} + +interface IState { + visible: boolean; + srcElement: any; + eventType: string | null; + children: BaseRightMenuChildrenProps[]; +} + +export class RightMenu extends Component { + private _localeObserver: Nullable>; + + ulRef = createRef(); + + Render: BaseComponentRender; + + initialize() { + const component = this._context.getPluginManager().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + visible: false, + srcElement: null, + eventType: null, + children: [], + }; + } + + root = createRef(); + + setMenuList(children: BaseRightMenuChildrenProps[]) { + for (let i = 0; i < children.length; i++) { + const item = children[i]; + item.label = this.jointJsx(item.label); + if (item.children) { + for (let j = 0; j < item.children.length; j++) { + item.children[j].label = this.jointJsx(item.children[j].label); + } + } + } + this.setState({ + children, + }); + } + + // TODO:添加到具体的元素 + componentDidMount() { + // 添加右键点击、点击事件监听 + document.addEventListener('click', this.handleClick); + this._context.getObserverManager().getObserver('onRightMenuDidMountObservable', PLUGIN_NAMES.SPREADSHEET)?.notifyObservers(this); + } + + componentWillUnmount() { + // 移除事件监听 + // document.removeEventListener('contextmenu', this.handleContextMenu); + document.removeEventListener('click', this.handleClick); + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + jointJsx(option: any) { + const Button = this.Render.renderFunction('Button'); + const Input = this.Render.renderFunction('Input'); + if (option instanceof Array) { + const arr = []; + for (let i = 0; i < option.length; i++) { + const item = option[i]; + if (item instanceof Object) { + if (item.type === 'input') { + if (item.format) { + arr.push(); + } else { + arr.push(); + } + } else if (item.type === 'button') { + arr.push(); + } else if (item.type === 'select') { + arr.push( + + ); + } + } else { + arr.push(item); + } + } + return arr; + } + return option; + } + + // 右键菜单事件 + handleContextMenu = async (event: IMouseEvent, rect?: any, down?: boolean) => { + event.preventDefault(); + + this.setState({ visible: true, srcElement: event.target, eventType: event.type }, () => { + new Promise((resolve, reject) => { + this.ulRef.current.showSelect(); + resolve(); + }).then(() => { + // clientX/Y 获取到的是触发点相对于浏览器可视区域左上角距离 + const clickX = !down ? event.clientX : rect.x; + const clickY = !down ? event.clientY : rect.y; + + // console.log(event); + + // window.innerWidth/innerHeight 获取的是当前浏览器窗口的视口宽度/高度 + const screenW = window.innerWidth; + const screenH = window.innerHeight; + // 获取自定义菜单的宽度/高度 + + const rootW = this.ulRef.current.base.offsetWidth; + const rootH = this.ulRef.current.base.offsetHeight; + + // right为true,说明鼠标点击的位置到浏览器的右边界的宽度可以放下菜单。否则,菜单放到左边。 + // bottom为true,说明鼠标点击位置到浏览器的下边界的高度可以放下菜单。否则,菜单放到上边。 + const right = screenW - clickX > rootW; + const left = !right; + const bottom = screenH - clickY > rootH; + const top = !bottom; + + if (right) { + this.root.current.style.left = `${clickX}px`; + } + + if (left) { + this.root.current.style.left = `${clickX - rootW}px`; + } + + if (bottom) { + this.root.current.style.top = `${clickY}px`; + } + if (top) { + this.root.current.style.top = `${clickY - rootH}px`; + } + }); + }); + }; + + // 鼠标单击事件,当鼠标在任何地方单击时,设置菜单不显示 + handleClick = (e: MouseEvent) => { + const { visible, eventType, srcElement } = this.state; + if (!this.root.current) return; + if (eventType === 'click' && srcElement && srcElement.contains(e.target)) { + return; + } + if (this.root.current.contains(e.target)) { + return; + } + if (visible) { + this.setState({ visible: false }); + } + }; + + render() { + if (!this.state.children.length) { + return; + } + + const wrapStyles = { ...this.props.style }; + const { visible } = this.state; + const Ul = this.Render.renderFunction('Ul'); + + return ( + visible && ( +
{ + e.preventDefault(); + }} + > +
    +
    + ) + ); + } +} diff --git a/packages/base-sheets/src/View/UI/RightMenu/index.module.less b/packages/base-sheets/src/View/UI/RightMenu/index.module.less new file mode 100644 index 0000000000..3976a391e7 --- /dev/null +++ b/packages/base-sheets/src/View/UI/RightMenu/index.module.less @@ -0,0 +1,135 @@ +@import '@univer/style-universheet/assets/css/skin/base.module.less'; + +.context-menu { + position: fixed; + + left: 0; + top: 26px; + background-color: var(--bg); + box-shadow: var(--box-shadow-base); + z-index: `getZindex('rightMenu') `; + + .item { + padding: 0.5em 0; + background-color: var(--bg); + } + + .item-item { + position: relative; + color: var(--text-color); + cursor: pointer; + list-style: none; + padding: 1px 16px; + margin: 0; + white-space: nowrap; + vertical-align: middle; + user-select: none; + line-height: 1.2; + + & > .item { + position: absolute; + top: 0; + } + + &:hover { + background-color: @gray-14; + } + + .item-content { + padding: 3px 0px 3px 1px; + display: flex; + align-content: space-between; + + // .item-content-right { + // margin-right: 0px; + // } + > div { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; + + .submenu-arrow { + transition: all 218ms; + font-size: 12px; + padding-right: 0; + opacity: 0.5; + color: var(--text-color); + user-select: none; + margin-left: 1.5em; + + span[role='img'] { + font-size: 24px; + } + + span[role='img'].univericon-rightIcon { + font-size: 14px; + color: turquoise; + } + } + } + } + } + + .item-line { + width: 80%; + border-top: 1px solid var(--text-color); + box-sizing: border-box; + margin: 5px auto; + } + + .cols-menu { + .cols-menuitem { + .cols-menuitem-content { + padding: 5px 15px 5px 25px; + + > div { + position: relative; + padding-right: 15px; + + .submenu-arrow { + position: absolute; + right: -15px; + } + } + } + + input { + width: 40px; + height: 20px; + text-align: center; + margin: 0 3px; + padding: 0; + } + + button { + margin-left: 5px; + padding: 2px 3px; + line-height: 12px; + font-size: 12px; + } + + .btn { + height: auto; + } + + .right-menu-center { + .cols-menuitem-content { + padding: 5px 15px; + + > div { + justify-content: center; + } + } + } + } + } + + > .cols-menu { + > .cols-menuitem { + > .cols-menuitem-content { + padding-right: 25px; + } + } + } +} diff --git a/packages/base-sheets/src/View/UI/RightMenu/index.ts b/packages/base-sheets/src/View/UI/RightMenu/index.ts new file mode 100644 index 0000000000..d8027a9cd6 --- /dev/null +++ b/packages/base-sheets/src/View/UI/RightMenu/index.ts @@ -0,0 +1 @@ +export * from './RightMenu'; diff --git a/packages/base-sheets/src/View/UI/SheetBar/SheetBar.tsx b/packages/base-sheets/src/View/UI/SheetBar/SheetBar.tsx new file mode 100644 index 0000000000..1c5f58a0f8 --- /dev/null +++ b/packages/base-sheets/src/View/UI/SheetBar/SheetBar.tsx @@ -0,0 +1,321 @@ +import { BaseComponentRender, BaseComponentSheet, BaseSheetBarProps, BaseUlProps, Component, createRef, RefObject } from '@univer/base-component'; +import { Nullable, Observer, PLUGIN_NAMES, WorkBook } from '@univer/core'; +import { Ul } from '@univer/style-universheet'; +import { SlideTabBar } from '../../../Model/Base/SlideTabBar/SlideTabBar'; +import styles from './index.module.less'; + +type SheetState = { + sheetList: BaseSheetBarProps[]; + menuList: BaseSheetBarProps[]; + sheetUl: BaseUlProps[]; + addSheet: Nullable<() => void>; + selectSheet: Nullable<() => void>; + contextMenu: Nullable<(e: Event) => void>; + changeSheetName: Nullable<(e: Event) => void>; + dragEnd: Nullable; +}; + +export class SheetBar extends Component { + ref = createRef(); + + ulRef = createRef(); + + sheetContainerRef = createRef(); + + sheetContentRef = createRef(); + + sheetBarContentRef = createRef(); + + slideTabBar: SlideTabBar; + + Render: BaseComponentRender; + + private _localeObserver: Nullable>; + + private _renderKey: number = 1; + + initialize(props: BaseSheetBarProps) { + const component = this._context.getPluginManager().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + sheetList: [], + sheetUl: [], + menuList: [], + addSheet: null, + contextMenu: null, + selectSheet: null, + changeSheetName: null, + dragEnd: null, + }; + } + + jointJsx(list: BaseSheetBarProps[] | BaseUlProps[]) { + list.forEach((item: any) => { + if (item.icon && typeof item.icon === 'string' && /Icon$/.test(item.icon)) { + const Icon = this.Render.renderFunction(item.icon); + item.icon = ; + } + if (item.selectType && item.selectType === 'jsx') { + const JSX: any = this.Render.renderFunction(item.label); + item.label = ( + { + item.onColor(color); + }} + /> + ); + } + if (item.children && item.children.length > 0) { + this.jointJsx(item.children); + } + }); + } + + // setstate + setValue = (value: any, fn?: () => void) => { + const { sheetList, menuList, sheetUl } = value; + if (sheetList) { + this.jointJsx(sheetList); + } + if (menuList) { + this.jointJsx(menuList); + } + if (sheetUl) { + this.jointJsx(sheetUl); + } + this.setState((prevState) => ({ ...value }), fn); + }; + + // 点击按钮左右滑动 + scrollLeft = (e: MouseEvent) => { + // let scroll = this.sheetContentRef.current; + // if (parseFloat(scroll.style.left) + 50 >= 0) { + // scroll.style.left = '0px'; + // return; + // } + // scroll.style.left = `${parseFloat(scroll.style.left) + 50}px`; + this.slideTabBar.getScrollbar().scrollX(this.slideTabBar.getScrollbar().getScrollX() - 50); + }; + + scrollRight = (e: MouseEvent) => { + // let scroll = this.sheetContentRef.current; + // let width = scroll.scrollWidth; + // let container = this.sheetContainerRef.current; + // if (width <= container.offsetWidth) { + // return; + // } + // const scrollLeft = parseFloat(scroll.style.left); + // if (width - Math.abs(scrollLeft) <= container.offsetWidth) { + // return; + // } + // if (width - Math.abs(scrollLeft) - container.offsetWidth <= 50) { + // scroll.style.left = `${scrollLeft - (width - Math.abs(scrollLeft) - container.offsetWidth)}px`; + // } else { + // scroll.style.left = `${scrollLeft - 50}px`; + // } + this.slideTabBar.getScrollbar().scrollX(this.slideTabBar.getScrollbar().getScrollX() + 50); + }; + + overGrid = () => { + // // 超过范围向左移动 + // const scroll = this.sheetContentRef.current; + // const width = scroll.scrollWidth; + // const container = this.sheetBarContentRef.current; + // if (width <= container.offsetWidth / 2) { + // return; + // } + // scroll.style.left = `${container.offsetWidth / 2 - width}px`; + }; + + // 右击显示菜单 + contextMenu = (e: MouseEvent) => { + e.preventDefault(); + this.showUlList(e); + }; + + // 显示下拉 + showSelect = (e: MouseEvent, ref: RefObject
      ) => { + e.stopImmediatePropagation(); + const current = ref.current; + if (current) current.showSelect(); + // 点击外部隐藏子组件 + window.addEventListener('click', this.hideSelect); + }; + + // 隐藏下拉 + hideSelect = (e: MouseEvent) => { + const current = this.ref.current; + const ulCurrent = this.ulRef.current; + if (current) current.hideSelect(); + if (ulCurrent) ulCurrent.hideSelect(); + window.removeEventListener('click', this.hideSelect); + }; + + // 点击不同sheet页显示ullist + showUlList = (e: MouseEvent) => { + new Promise((resolve) => { + const target = e.currentTarget as HTMLDivElement; + const id = target.dataset.id as string; + this.showSelect(e, this.ulRef); + resolve(); + }).then(() => { + const currentTarget = (e.currentTarget as HTMLElement).closest(`.${styles.slideTabItem}`); + if (currentTarget) { + const currentRect = currentTarget.getBoundingClientRect(); + const left = `${currentRect.left}px`; + const bottom = `${currentRect.height}px`; + const ul = this.ulRef.current.ulRef.current; + ul.style.left = left; + ul.style.bottom = bottom; + ul.style.top = 'auto'; + ul.style.right = 'auto'; + } + }); + }; + + // 重命名sheet + reNameSheet = (id: string) => { + const item = this.slideTabBar.getSlideTabItems().find((item) => item.primeval().dataset.id === id); + if (item) { + item.editor(); + } + // const sheetContentDiv = this.sheetContentRef.current.querySelectorAll('div'); + // Array.from(sheetContentDiv).forEach((node: any) => { + // if (node.dataset.id === id) { + // const span = node.querySelector('span'); + // const dblclick = new MouseEvent('dblclick'); + // span?.dispatchEvent(dblclick); + // } + // }); + }; + + changeEditable = (e: MouseEvent) => { + const target = e.target as HTMLDivElement; + let listener: (event: Event) => void; + target.contentEditable = 'true'; + target.focus(); + target.addEventListener( + 'blur', + (listener = (event) => { + if (this.state.changeSheetName) { + this.state.changeSheetName(e); + } + target.contentEditable = 'false'; + target.removeEventListener('blur', listener); + }) + ); + }; + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + componentDidUpdate() { + if (this.slideTabBar) { + this.slideTabBar.destroy(); + } + this.slideTabBar = new SlideTabBar({ + slideTabBarClassName: 'univer-slide-tab-bar', + slideTabBarItemActiveClassName: 'univer-slide-tab-active', + slideTabBarItemClassName: 'univer-slide-tab-item', + slideTabBarItemAutoSort: true, + onChangeName: (event: Event) => { + if (this.state.changeSheetName) { + this.state.changeSheetName(event); + } + }, + onSlideEnd: (event: Event) => { + if (this.state.dragEnd) { + this.state.dragEnd(this.slideTabBar.getSlideTabItems().map((item) => item.primeval())); + } + }, + }); + } + + componentDidMount() { + this._context.getObserverManager().getObserver('onSheetBarDidMountObservable', PLUGIN_NAMES.SPREADSHEET)?.notifyObservers(this); + } + + // 先生成移动对象的副本,移动对象固定定位,达到边界的时候将副本移位 + time = 0; + + target: any = null; + + cloneTarget: any = null; + + startLeft: number; + + startClientX: number; + + render(props: BaseSheetBarProps, state: SheetState) { + const { sheetList, menuList, sheetUl, addSheet, contextMenu, selectSheet } = state; + + if (!addSheet || !selectSheet || !contextMenu) return; + + const Button = this.Render.renderFunction('Button'); + const Ul = this.Render.renderFunction('Ul'); + const AddIcon = this.Render.renderFunction('AddIcon'); + const MenuIcon = this.Render.renderFunction('MenuIcon'); + const NextIcon = this.Render.renderFunction('NextIcon'); + + return ( +
      + {/* user options button */} +
      + + +
      + + {/* user slide button */} +
      + {sheetList.map((item) => ( +
      +
      +
      +
      + {item.label} +
      +
      + {item.icon} +
      +
      +
      +
      +
      +
      + ))} +
      + + {/* mouse right button context menu */} +
        + + {/* prev next scroll button */} +
        + + +
        +
        + ); + } +} diff --git a/packages/base-sheets/src/View/UI/SheetBar/index.module.less b/packages/base-sheets/src/View/UI/SheetBar/index.module.less new file mode 100644 index 0000000000..15d289c5c6 --- /dev/null +++ b/packages/base-sheets/src/View/UI/SheetBar/index.module.less @@ -0,0 +1,283 @@ +@import '@univer/style-universheet/assets/css/skin/base.module.less'; + +.sheet-bar { + position: relative; + height: 31px; + display: flex; + flex-direction: row; + background-color: var(--bg); +} + +.sheet-bar-options { + border-right: 1px solid var(--border-color-base); + display: flex; + flex-direction: row; + align-items: center; + padding-right: 5px; +} + +.sheet-bar-options-button { + padding: 0; + margin: 0; + border: none; + height: initial; + line-height: initial; + padding-right: 2px; +} + +.sheet_bar_scroll_button { + border: none; + border-left: 1px solid var(--border-color-base); + padding-left: 5px; +} + +// .sheet-bar { +// background-color: var(--bg); +// color: var(--text-color); +// height: 31px; +// padding-left: 44px; +// display: flex; +// // width: 100%; +// position: relative; + +// button { +// &:hover { +// background-color: @gray-16; +// } +// } + +// .sheet-icon { +// display: flex; +// align-items: center; + +// .btn { +// font-size: 21px; +// margin-right: 3px; +// display: flex; +// align-items: center; +// padding: 0 2px; +// } + +// .sheet-menu { +// position: relative; + +// ul { +// font-size: 13px; +// overflow: auto; +// max-height: 20em; +// } +// } +// } + +// .sheet-bar-content { +// display: flex; +// width: 700px; +// position: relative; + +// .sheet-container { +// max-width: 50%; +// position: relative; +// overflow: hidden; + +// .sheet-fade { +// position: absolute; +// display: flex; +// top: 0; +// width: 6px; +// height: 100%; +// z-index: ~`getZindex('bottom') `; + +// div { +// background-color: @gray-5; +// width: 2px; +// height: 100%; +// } + +// .sheet-fade1 { +// opacity: 0.82; +// } + +// .sheet-fade2 { +// opacity: 0.62; +// } + +// .sheet-fade3 { +// opacity: 0.4; +// } +// } + +// .sheet-left-bar { +// left: 0; +// } + +// .sheet-right-bar { +// right: 0; +// } + +// .sheet-content { +// position: relative; +// max-width: 500px; +// display: flex; + +// .sheet-content-item { +// display: flex; +// align-items: center; +// border-right: 1px solid @gray-15; +// border-left: 1px solid @gray-15; +// margin-right: -1px; +// cursor: pointer; +// padding: 0 5px; +// height: 29px; +// line-height: 29px; +// flex-shrink: 0; +// font-size: 13px; + +// span { +// flex-shrink: 0; +// } + +// &:hover { +// background-color: @gray-16; +// } + +// &-selected { +// background-color: @gray-16; +// } +// } + +// button { +// padding-left: 5px; +// } + +// span[role='img'] { +// margin-left: 5px; +// color: #a1a1a1; + +// &:hover { +// color: #2a2a2a; +// } +// } +// } +// } + +// .cols-menu { +// .cols-menuitem-content { +// padding: 6px 14px 6px 20px; +// } +// } +// } + +// .sheet-scroll { +// button { +// margin-left: 3px; +// height: 80%; +// padding: 0 3px; +// } + +// display: flex; +// align-items: center; +// text-align: center; +// background-color: transparent; +// } +// } + +.slide-tab-bar { + user-select: none; + max-width: 100%; + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + overflow: auto; + overflow-y: hidden; + scrollbar-width: none; +} +.slide-tab-bar::-webkit-scrollbar { + display: none; +} + +/** + 高亮标记 + */ +.slide-tab-active { + position: relative; +} + +/** + Tab Item +*/ +.slide-tab-item { + user-select: none; + display: flex; + flex-direction: column; + justify-content: center; + cursor: pointer; + height: 100%; + background-color: var(--bg); +} +.slide-tab-item:first-child .slide-tab-divider { + opacity: 0; +} +.slide-tab-item:hover { + background: #efefef; +} +.slide-tab-item:hover .slide-tab-divider { + opacity: 0; +} +.slide-tab-item:hover + .slide-tab-item .slide-tab-divider { + opacity: 0; +} + +/** + 内容区域 +*/ +.slide-tab-content { + display: flex; + flex-direction: row; + align-items: center; + flex-grow: 1; +} +.slide-tab-title { + font-size: 14px; + color: #676464; + margin-right: 3px; + margin-left: 6px; +} +.slide-tab-icon { + width: 16px; + height: 16px; + font-size: 15px; + color: #a1a1a1; + margin-right: 6px; +} +.slide-tab-divider { + width: 1px; + height: 16px; + background: var(--border-color-base); +} +.slide-tab-active .slide-tab-content { + background: #efefef; + color: #222; +} +.slide-tab-active .slide-tab-divider { + opacity: 0; +} +.slide-tab-active + .slide-tab-item .slide-tab-divider { + opacity: 0; +} + +/** + 底部区域 + */ +.slide-tab-footer { + height: auto; + width: 100%; + flex-grow: 0; +} +.slide-tab-active-bar { + height: 2px; + width: 100%; +} +.slide-tab-active .slide-tab-active-bar { + background: #d0d0d0; +} diff --git a/packages/base-sheets/src/View/UI/SheetBar/index.ts b/packages/base-sheets/src/View/UI/SheetBar/index.ts new file mode 100644 index 0000000000..327b3d528e --- /dev/null +++ b/packages/base-sheets/src/View/UI/SheetBar/index.ts @@ -0,0 +1 @@ +export * from './SheetBar'; diff --git a/packages/base-sheets/src/View/UI/SheetContainer/SheetContainer.tsx b/packages/base-sheets/src/View/UI/SheetContainer/SheetContainer.tsx new file mode 100644 index 0000000000..f699e19a9f --- /dev/null +++ b/packages/base-sheets/src/View/UI/SheetContainer/SheetContainer.tsx @@ -0,0 +1,725 @@ +import { + AppContext, + BaseSheetContainerProps, + ILayout, + IMainProps, + IMainState, + ISiderState, + ISlotElement, + ISlotProps, + JSXComponent, + SheetContainerComponent, + Component, + BaseComponentSheet, + BaseComponentRender, + Description, + createRef, + RefObject, + cloneElement, +} from '@univer/base-component'; +import { IKeyType, LocaleType, PLUGIN_NAMES, Tools, WorkBook } from '@univer/core'; +import cssVars from 'css-vars-ponyfill'; + +import { + Container, + Content, + Footer, + FormulaBar, + Header, + // InfoBar, + Layout, + // SheetBar, + Sider, +} from '@univer/style-universheet'; +import defaultSkin from '@univer/style-universheet/assets/css/skin/default.module.less'; +import darkSkin from '@univer/style-universheet/assets/css/skin/dark.module.less'; +// All skins' less file +import greenSkin from '@univer/style-universheet/assets/css/skin/green.module.less'; +// app context for skin and Locale +import { SpreadsheetPlugin } from '@SpreadsheetPlugin'; +import { RightMenu } from '../RightMenu'; +import { InfoBar } from '../InfoBar'; +import { SheetBar } from '../SheetBar'; +import style from './index.module.less'; +import { ToolBar } from '../ToolBar'; +import { CountBar } from '../CountBar/CountBar'; + +export const defaultLayout: ILayout = { + outerLeft: false, + + outerRight: true, + + header: true, + + footer: true, + + innerLeft: false, + + innerRight: false, + + frozenHeaderLT: true, + + frozenHeaderRT: true, + + frozenHeaderLM: true, + + frozenContent: true, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, // Undo redo + paintFormat: true, // Format brush + currencyFormat: true, // currency format + percentageFormat: true, // Percentage format + numberDecrease: true, // 'Decrease the number of decimal places' + numberIncrease: true, // 'Increase the number of decimal places + moreFormats: true, // 'More Formats' + font: true, // 'font' + fontSize: true, // 'Font size' + bold: true, // 'Bold (Ctrl+B)' + italic: true, // 'Italic (Ctrl+I)' + strikethrough: true, // 'Strikethrough (Alt+Shift+5)' + underline: true, // 'Underline (Alt+Shift+6)' + textColor: true, // 'Text color' + fillColor: true, // 'Cell color' + border: true, // 'border' + mergeCell: true, // 'Merge cells' + horizontalAlignMode: true, // 'Horizontal alignment' + verticalAlignMode: true, // 'Vertical alignment' + textWrapMode: true, // 'Wrap mode' + textRotateMode: true, // 'Text Rotation Mode' + image: true, // 'Insert picture' + link: true, // 'Insert link' + chart: true, // 'chart' (the icon is hidden, but if the chart plugin is configured, you can still create a new chart by right click) + comment: true, // 'comment' + pivotTable: true, // 'PivotTable' + function: true, // 'formula' + frozenMode: true, // 'freeze mode' + sortAndFilter: true, // 'Sort and filter' + conditionalFormat: true, // 'Conditional Format' + dataValidation: true, // 'Data Validation' + splitColumn: true, // 'Split column' + screenshot: true, // 'screenshot' + findAndReplace: true, // 'Find and Replace' + protection: true, // 'Worksheet protection' + print: true, // 'Print' + }, + + contentSplit: false, +}; + +/** + * SheetContainer + */ + +// Types for props +// type BaseSheetContainerProps = { config: ISheetContainerConfig }; + +// Types for state +type IState = { + layout: ILayout; + currentLocale: string; + currentSkin: string; + printVisible: boolean; + isCellEditorVisible: boolean; + siderPanelList: ISiderState[]; + mainList: IMainState[]; + showSider: boolean; + renderState: number; +}; +export class SheetContainer extends Component { + splitLeftRef = createRef(); + + rightRef = createRef(); + + cellRightRef = createRef(); + + btnRef = createRef(); + + contentRef = createRef(); + + layoutContainerRef = createRef(); + + containerRef = createRef(); + + refMap: IKeyType | any> = {}; + + leftContentLeft: number; + + leftContentTop: number; + + rightBorderX: number; + + rightBorderY: number; + + Render: BaseComponentRender; + + constructor(props: BaseSheetContainerProps) { + super(props, { coreContext: props.config.context }); + + const { container, skin, layout, context, addSider, addMain, showSiderByName, showMainByName } = props.config; + + addSider(this.addSider.bind(this)); + addMain(this.addMain.bind(this)); + showSiderByName(this.showSiderByName.bind(this)); + showMainByName(this.showMainByName.bind(this)); + + const component = this._context.getPluginManager().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.setSkin(container, skin); + + // init state + this.state = { + layout: + layout === 'auto' + ? Tools.deepClone(defaultLayout) // The defaultLayout must be cloned, otherwise the layout object will be referenced in multiple instances + : Tools.deepMerge(defaultLayout, layout), + currentLocale: context.getLocale().options.currentLocale, + currentSkin: skin, + printVisible: true, + isCellEditorVisible: false, + siderPanelList: [], + mainList: [], + showSider: false, + renderState: 0, + }; + } + + /** + * Change language + * @param {String} lang new language + * + * e: {target: HTMLSelectElement } reference from https://stackoverflow.com/a/48443771 + * + */ + changeLocale(e: Event) { + const target = e.target as HTMLSelectElement; + const locale = target.value; + this.props.config.context.getLocale().change(locale as LocaleType); + + // You must use setState to trigger the re-rendering of the child component + this.setState({ + currentLocale: locale, + }); + + // publish + this.props.config.context.getContextObserver('onAfterChangeUILocaleObservable')?.notifyObservers(); + } + + setSkin(container: HTMLElement, skin: string) { + // Collect all skins + let root = document.documentElement; + + // get all skins + const skins = { + default: defaultSkin, + dark: darkSkin, + green: greenSkin, + }; + + // current skin set by user + let currentSkin = skins[skin]; + + // transform "primaryColor" to "--primary-color" + currentSkin = Object.fromEntries(Object.keys(currentSkin).map((item) => [`--${item.replace(/([A-Z])/g, '-$1').toLowerCase()}`, currentSkin[item]])); + + // ie11 does not support css variables, use css-vars-ponyfill to handle + if (Tools.isIEBrowser()) { + cssVars({ + // Options... + + // The container is invalid as rootElement, so the default setting is root. + // Disadvantages: In ie11, only one set of skins can be used for multiple workbooks, and it is the skin set by the last workbook + rootElement: root, // default + + variables: currentSkin, + }); + } else { + // set css variable + + let sheet = getSkinStyleSheet(container.id); + + /** + * covert object to style, remove " and replace , to ; + * + * Example: + * + * before: {--primary-color:"#0188fb",--primary-color-hover:"#5391ff"} + * after: {--primary-color:#0188fb;--primary-color-hover:#5391ff;} + */ + + sheet.insertRule( + `#${container.id} ${JSON.stringify(currentSkin) + .replace(/"/g, '') + .replace(/,(?=--)/g, ';')}` + ); + } + + /** + * get skin style sheet + * @param id + * @returns + */ + + function getSkinStyleSheet(id: string) { + const title = 'universheet-skin-style'; + // avoid duplicates + for (let i = 0; i < document.styleSheets.length; i++) { + if (document.styleSheets[i].title === title) { + deleteStyleRuleIndexBySelector(document.styleSheets[i], id); + + return document.styleSheets[i]; + } + } + const head = document.head || document.getElementsByTagName('head')[0]; + const styleEle = document.createElement('style'); + styleEle.title = title; + head.appendChild(styleEle); + + return document.styleSheets[document.styleSheets.length - 1]; + } + + /** + * delete style rule in universheet-skin-style + * @param skinStyleSheet + * @param id + */ + function deleteStyleRuleIndexBySelector(skinStyleSheet: CSSStyleSheet, id: string) { + let index = 0; + for (let i = 0; i < skinStyleSheet.cssRules.length; i++) { + const rule = skinStyleSheet.cssRules[i]; + + if (rule instanceof CSSStyleRule && rule.selectorText === `#${id}`) { + index = i; + skinStyleSheet.deleteRule(index); + break; + } + } + } + } + + /** + * Change skin + * @param {String} lang new skin + */ + changeSkin(e: Event) { + const target = e.target as HTMLSelectElement; + const skin = target.value; + this.setSkin(this.props.config.container, skin); + + this.setState({ + currentSkin: skin, + }); + + // switch animation + // https://www.webtips.dev/how-to-make-an-animated-day-and-night-toggle-switch; + + // publish + this.props.config.context.getObserverManager().getObserver('onAfterChangeUISkinObservable')?.notifyObservers(this.props.config.context.getWorkBook()); + // this.props.config.context.onAfterChangeUISkinObservable.notifyObservers(this.props.config.context.getWorkBook()) + } + + /** + * 右键菜单 + */ + // rightMenu = (e: MouseEvent) => { + // e.preventDefault(); + + // this.rightRef.current && (this.rightRef.current as unknown as RightMenu).handleContextMenu(e); + // }; + + /** + * resize split content + */ + resizeSplitContent = () => {}; + + /** + * split mouse down + * @param e + */ + handleSplitBarMouseDown = (e: MouseEvent) => { + e = e || window.event; // Compatible with IE browser + + // Store the current mouse position + + this.leftContentLeft = this.splitLeftRef.current?.getBoundingClientRect().left!; + this.leftContentTop = this.splitLeftRef.current?.getBoundingClientRect().top!; + + const mainContainer = this.splitLeftRef.current?.parentElement; + this.rightBorderX = mainContainer?.getBoundingClientRect()?.width!; + this.rightBorderY = mainContainer?.getBoundingClientRect()?.height!; + + document.addEventListener('mousemove', this.handleSplitBarMouseMove, false); + document.addEventListener('mouseup', this.handleSplitBarMouseUp, false); + }; + + /** + * split mouse move + * @param e + */ + handleSplitBarMouseMove = (e: MouseEvent) => { + e = e || window.event; // Compatible with IE browser + + let diffLeft = e.clientX - this.leftContentLeft; + let diffTop = e.clientY - this.leftContentTop; + + // Prevent crossing borders + diffLeft = diffLeft >= this.rightBorderX ? this.rightBorderX : diffLeft; + diffTop = diffTop >= this.rightBorderY ? this.rightBorderY : diffTop; + + // set new width + if (this.state.layout.contentSplit === 'vertical') { + this.splitLeftRef.current!.style.height = `${diffTop}px`; + } else { + this.splitLeftRef.current!.style.width = `${diffLeft}px`; + } + }; + + /** + * split mouse up + * @param e + */ + handleSplitBarMouseUp = (e: MouseEvent) => { + document.removeEventListener('mousemove', this.handleSplitBarMouseMove, false); + document.removeEventListener('mouseup', this.handleSplitBarMouseUp, false); + }; + + /** + * click and double click + * + * @param e + */ + handleContentClick = (e: MouseEvent) => { + // TODO: get observer from observeManager + // this._onInputObserver = engine.onInputChangedObservable.add((eventData: IEvent)=>{ + // }) + }; + + /** + * add plugin sider panel + */ + addSider(item: ISlotProps): Promise { + /** + * There will be multiple plugin panel added, so you need to use prevState to add panel from the previous state, otherwise it will cause overwriting + */ + return new Promise((resolve) => { + this.setState( + (prevState) => { + const stateItem = Object.assign(item, { + show: false, + zIndex: prevState.siderPanelList.length + 1, + }); + return { + siderPanelList: [...prevState.siderPanelList, stateItem], + }; + }, + () => { + resolve(); + } + ); + }); + } + + /** + * add plugin main area + */ + addMain(item: IMainProps): Promise { + /** + * There will be multiple plugin main added, so you need to use prevState to add panel from the previous state, otherwise it will cause overwriting + */ + return new Promise((resolve) => { + this.setState( + (prevState) => { + const stateItem = Object.assign(item, { + show: false, + zIndex: prevState.mainList.length + 1, + }); + return { + mainList: [...prevState.mainList, stateItem], + }; + }, + () => { + resolve(); + } + ); + }); + } + + /** + * + * show or hide panel + * + * When you open two sidebars, you need to set the zIndex of the newly opened sidebar to the highest, so every time you open a sidebar, you need to recalculate the zIndex. For example, we already have 6 sidebars, this When the 3rd sidebar is opened + * + * 1 2 3 4 5 6 + * + * Set 3 to 6, then rearrange the order after 3 to 3, 4, 5, and get + * + * 1 2 6 3 4 5 + */ + showSiderByName(name: string, show: boolean): Promise { + /** + * There will be multiple plugin panel added, so you need to use prevState to add panel from the previous state, otherwise it will cause overwriting + */ + return new Promise((resolve) => { + this.setState( + (prevState) => { + let showSider = false; + + let currentZIndex = 0; + let zIndexList = prevState.siderPanelList.map((item) => { + if (item.name === name) { + currentZIndex = item.zIndex; + } + return item.zIndex; + }); + + const nextState = prevState.siderPanelList.map((item) => { + // Set according to user needs + if (item.name === name) { + item.show = show; + item.zIndex = Math.max(...zIndexList); + } else if (item.zIndex > currentZIndex) { + item.zIndex--; + } + + // As long as there is a panel to be displayed, display the container + if (item.show) { + showSider = true; + } + return item; + }); + + return { + siderPanelList: nextState, + showSider, + }; + }, + () => { + resolve(); + } + ); + }); + } + + /** + * show or hide main + */ + showMainByName(name: string, show: boolean): Promise { + /** + * There will be multiple plugin panel added, so you need to use prevState to add panel from the previous state, otherwise it will cause overwriting + */ + return new Promise((resolve) => { + this.setState( + (prevState) => { + const nextState = prevState.mainList.map((item) => { + // Set according to user needs + if (item.name === name) { + item.show = show; + } + + return item; + }); + + return { + mainList: nextState, + }; + }, + () => { + resolve(); + } + ); + }); + } + + /** + * destroy + */ + componentWillUnmount() {} + + componentDidMount() { + // mount can + this.props.config.getSplitLeftRef(this.splitLeftRef); + // mount canvas + this.props.config.getContentRef(this.contentRef); + + this.props.config.onDidMount(); + + this.setState( + (preState) => ({ + ...preState, + renderState: 1, + }), + () => { + // After the mount is successful, an observe is issued to notify other plug-ins that they can start using dom + this.getContext() + .getPluginManager() + .getRequirePluginByName(PLUGIN_NAMES.SPREADSHEET) + .getObserver('onSheetContainerDidMountObservable') + ?.notifyObservers(this); + } + ); + + // this.initEvent(); + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render() { + const { addButton, context } = this.props.config; + const { layout, currentLocale, currentSkin, siderPanelList, mainList, showSider, renderState } = this.state; + + // Set Provider for entire Container + return ( + + + + + +
        + + + +
        + + + {/* innerLeft */} + {/* + + */} + {/* + {/* { + this.setState({ + printVisible: !this.state.printVisible, + }); + }} + /> */} + + + {/* extend main content */} + {mainList.map((item: IMainState, i) => { + // if (!item.show) return null; + + if (item.type === ISlotElement.JSX) { + // return item.content; + return cloneElement(item.content as JSX.Element, { + style: { display: item.show ? '' : 'none' }, + }); + } + if (item.type === ISlotElement.JSX_STRING) { + // if (item.content === 'RichText') { + // const RichText = this.Render.renderFunction('RichText'); + // return ; + // } + const JSXElement = this.Render.renderFunction(item.content as keyof Description); + this.refMap[item.name] = createRef(); + return ; + } + return item; + })} + + {!!layout.contentSplit && ( + +
        +
        + )} + + + + {/*
        + 皮肤 + + + 语言 + + + + {renderState} +
        */} +
        +
        + + {/* innerRight */} + +
        +
        + + +
        +
        + + {siderPanelList.map((item: ISiderState, i) => { + if (item.type === ISlotElement.JSX) { + // return item.content; + return cloneElement(item.label as JSX.Element, { + style: { display: item.show ? '' : 'none', zIndex: item.zIndex }, + }); + } + return item; + })} + +
        +
        +
        + ); + } +} + +export class UniverSheetContainer implements SheetContainerComponent { + render(): JSXComponent { + return SheetContainer; + } +} diff --git a/packages/base-sheets/src/View/UI/SheetContainer/index.module.less b/packages/base-sheets/src/View/UI/SheetContainer/index.module.less new file mode 100644 index 0000000000..036457241b --- /dev/null +++ b/packages/base-sheets/src/View/UI/SheetContainer/index.module.less @@ -0,0 +1,79 @@ +@import '@univer/style-universheet/src/assets/css/skin/base.module.less'; + +.layout-container { + // theme + background-color: var(--bg); + color: var(--text-color); + height: 100%; + + .content-container-horizontal { + display: flex; + min-height: 14em; + position: relative; + + .content-inner-left-container { + // position: relative; + width: 50%; + padding-right: 10px; + flex: none; + + .hover-cursor { + position: absolute; + right: 0; + top: 0; + height: 100%; + width: 10px; + background: gray; + cursor: e-resize; + } + } + + .content-inner-right-container { + width: 50%; + flex: 1; + // position: relative; + } + } + + .content-container-vertical { + display: flex; + flex-direction: column; + min-height: 14em; + position: relative; + + .content-inner-left-container { + // position: relative; + height: 50%; + padding-bottom: 10px; + flex: none; + + .hover-cursor { + position: absolute; + left: 0; + bottom: 0; + height: 10px; + width: 100%; + background: gray; + cursor: n-resize; + } + } + + .content-inner-right-container { + height: 50%; + flex: 1; + } + } + + .main-content { + background: var(--bg); + } + + .outer-right-container { + // position: relative; + flex: 0 0 280px; + > div { + position: absolute; + width: 100%; + } + } +} diff --git a/packages/base-sheets/src/View/UI/SheetContainer/index.ts b/packages/base-sheets/src/View/UI/SheetContainer/index.ts new file mode 100644 index 0000000000..10af32f11d --- /dev/null +++ b/packages/base-sheets/src/View/UI/SheetContainer/index.ts @@ -0,0 +1 @@ +export * from './SheetContainer'; diff --git a/packages/base-sheets/src/View/UI/ToolBar/Const.ts b/packages/base-sheets/src/View/UI/ToolBar/Const.ts new file mode 100644 index 0000000000..de7d79ce34 --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/Const.ts @@ -0,0 +1,415 @@ +import { ISelectButton } from '@univer/base-component'; + +export const FONT_SIZE_CHILDREN = [ + { + label: '9', + value: 9, + }, + { + label: '10', + value: 10, + selected: true, + }, + { + label: '11', + value: 11, + }, + { + label: '12', + value: 12, + }, + { + label: '14', + value: 14, + }, + { + label: '16', + value: 16, + }, + { + label: '18', + value: 18, + }, + { + label: '20', + value: 20, + }, + + { + label: '22', + value: 22, + }, + { + label: '24', + value: 24, + }, + { + label: '26', + value: 26, + }, + { + label: '28', + value: 28, + }, + { + label: '36', + value: 36, + }, + { + label: '48', + value: 48, + }, + { + label: '72', + value: 72, + }, +]; + +export const MORE_FORMATS_CHILDREN = [ + { + name: 'defaultFmt.Automatic.text', + selected: true, + }, + { + name: 'defaultFmt.PlainText.text', + selected: false, + }, + { + name: 'defaultFmt.Number.text', + iconName: 'defaultFmt.Number.example', + selected: false, + border: true, + }, + { + name: 'defaultFmt.Percent.text', + iconName: 'defaultFmt.Percent.example', + selected: false, + }, + { + name: 'defaultFmt.Scientific.text', + iconName: 'defaultFmt.Scientific.example', + selected: false, + }, + { + name: 'defaultFmt.Accounting.text', + iconName: 'defaultFmt.Accounting.example', + selected: false, + border: true, + }, + { + name: 'defaultFmt.Thousand.text', + iconName: 'defaultFmt.Thousand.example', + selected: false, + }, + { + name: 'defaultFmt.Currency.text', + iconName: 'defaultFmt.Currency.example', + selected: false, + }, + { + name: 'defaultFmt.Digit.text', + iconName: 'defaultFmt.Digit.example', + selected: false, + }, + { + name: 'defaultFmt.Date.text', + selected: false, + iconName: 'defaultFmt.Date.example', + border: true, + }, + { + name: 'defaultFmt.Time.text', + iconName: 'defaultFmt.Time.example', + selected: false, + }, + { + name: 'defaultFmt.Time24H.text', + iconName: 'defaultFmt.Time24H.example', + selected: false, + }, + { + name: 'defaultFmt.DateTime.text', + iconName: 'defaultFmt.DateTime.example', + selected: false, + }, + { + name: 'defaultFmt.DateTime24H.text', + iconName: 'defaultFmt.DateTime.example', + selected: false, + }, +]; + +export const FONT_FAMILY_CHILDREN = [ + { + locale: 'fontFamily.TimesNewRoman', + style: { 'font-family': 'Times New Roman' }, + value: 'Times New Roman', + selected: true, + }, + { + locale: 'fontFamily.Arial', + style: { 'font-family': 'Arial' }, + value: 'Arial', + }, + { + locale: 'fontFamily.Tahoma', + style: { 'font-family': 'Tahoma' }, + value: 'Tahoma', + }, + { + locale: 'fontFamily.Verdana', + style: { 'font-family': 'Verdana' }, + value: 'Verdana', + }, + { + locale: 'fontFamily.MicrosoftAccorblack', + style: { 'font-family': '微软雅黑' }, + value: '微软雅黑', + }, + { + locale: 'fontFamily.SimSun', + style: { 'font-family': '宋体' }, + value: '宋体', + }, + { + locale: 'fontFamily.SimHei', + style: { 'font-family': '黑体' }, + value: '黑体', + }, + { + locale: 'fontFamily.Kaiti', + style: { 'font-family': '楷体' }, + value: '楷体', + }, + { + locale: 'fontFamily.FangSong', + style: { 'font-family': '仿宋' }, + value: '仿宋', + }, + { + locale: 'fontFamily.NSimSun', + style: { 'font-family': '新宋体' }, + value: '新宋体', + }, + { + locale: 'fontFamily.STXinwei', + style: { 'font-family': '华文新魏' }, + value: '华文新魏', + }, + { + locale: 'fontFamily.STXingkai', + style: { 'font-family': '华文行楷' }, + value: '华文行楷', + }, + { + locale: 'fontFamily.STLiti', + style: { 'font-family': '华文隶书' }, + value: '华文隶书', + }, + { + locale: 'fontFamily.HanaleiFill', + style: { 'font-family': 'HanaleiFill' }, + value: 'HanaleiFill', + }, + { + locale: 'fontFamily.Anton', + style: { 'font-family': 'Anton' }, + value: 'Anton', + }, + { + locale: 'fontFamily.Pacifico', + style: { 'font-family': 'Pacifico' }, + value: 'Pacifico', + }, +]; + +export const BORDER_LINE_CHILDREN = [ + { + locale: 'borderLine.borderTop', + icon: 'TopBorderIcon', + value: 'top', + }, + { + locale: 'borderLine.borderBottom', + icon: 'BottomBorderIcon', + value: 'bottom', + }, + { + locale: 'borderLine.borderLeft', + icon: 'LeftBorderIcon', + value: 'left', + }, + { + locale: 'borderLine.borderRight', + icon: 'RightBorderIcon', + value: 'right', + }, + { + locale: 'borderLine.borderNone', + icon: 'NoneBorderIcon', + value: 'none', + }, + { + locale: 'borderLine.borderAll', + icon: 'FullBorderIcon', + value: 'all', + selected: true, + }, + { + locale: 'borderLine.borderOutside', + icon: 'OuterBorderIcon', + value: 'outside', + }, + { + locale: 'borderLine.borderInside', + icon: 'InnerBorderIcon', + value: 'inside', + }, + { + locale: 'borderLine.borderHorizontal', + icon: 'StripingBorderIcon', + value: 'horizontal', + }, + { + locale: 'borderLine.borderVertical', + icon: 'VerticalBorderIcon', + value: 'vertical', + }, + { + locale: 'borderLine.borderColor', + icon: 'RightIcon', + children: [ + { + label: 'BorderLineColorPicker', + selectType: ISelectButton.JSX, + value: '#000', + }, + ], + }, + { + locale: 'borderLine.borderSize', + icon: 'RightIcon', + needChange: true, + value: 1, + children: [ + { locale: 'borderLine.borderNone', label: '', value: 0 }, + { locale: '', label: 'BorderThin', value: 1 }, + { locale: '', label: 'BorderHair', value: 2 }, + { locale: '', label: 'BorderDotted', value: 3 }, + { locale: '', label: 'BorderDashed', value: 4 }, + { locale: '', label: 'BorderDashDot', value: 5 }, + { locale: '', label: 'BorderDashDotDot', value: 6 }, + { locale: '', label: 'BorderMedium', value: 7 }, + { locale: '', label: 'BorderMediumDashed', value: 8 }, + { locale: '', label: 'BorderMediumDashDot', value: 9 }, + { locale: '', label: 'BorderMediumDashDotDot', value: 10 }, + { locale: '', label: 'BorderThick', value: 12 }, + ], + }, +]; + +export const MERGE_CHILDREN = [ + { + locale: 'merge.all', + value: 'all', + }, + { + locale: 'merge.vertical', + value: 'vertical', + }, + { + locale: 'merge.horizontal', + value: 'horizontal', + }, + { + locale: 'merge.cancel', + value: 'cancel', + }, +]; + +export const HORIZONTAL_ALIGN_CHILDREN = [ + { + locale: 'align.left', + icon: 'LeftAlignIcon', + value: 1, + }, + { + locale: 'align.center', + icon: 'CenterAlignIcon', + value: 2, + }, + { + locale: 'align.right', + icon: 'RightAlignIcon', + value: 3, + }, +]; + +export const VERTICAL_ALIGN_CHILDREN = [ + { + locale: 'align.top', + icon: 'TopVerticalIcon', + value: 1, + }, + { + locale: 'align.middle', + icon: 'CenterVerticalIcon', + value: 2, + }, + { + locale: 'align.bottom', + icon: 'BottomVerticalIcon', + value: 3, + }, +]; + +export const TEXT_WRAP_CHILDREN = [ + { + locale: 'textWrap.overflow', + icon: 'OverflowIcon', + value: 1, + }, + { + locale: 'textWrap.wrap', + icon: 'BrIcon', + value: 3, + }, + { + locale: 'textWrap.clip', + icon: 'CutIcon', + value: 2, + }, +]; + +export const TEXT_ROTATE_CHILDREN = [ + { + locale: 'textRotate.none', + icon: 'TextRotateIcon', + value: 0, + }, + { + locale: 'textRotate.angleUp', + icon: 'TextRotateAngleUpIcon', + value: -45, + }, + { + locale: 'textRotate.angleDown', + icon: 'TextRotateAngleDownIcon', + value: 45, + }, + { + locale: 'textRotate.vertical', + icon: 'TextRotateVerticalIcon', + value: 'v', + }, + { + locale: 'textRotate.rotationUp', + icon: 'TextRotateRotationUpIcon', + value: -90, + }, + { + locale: 'textRotate.rotationDown', + icon: 'TextRotateRotationDownIcon', + value: 90, + }, +]; diff --git a/packages/base-sheets/src/View/UI/ToolBar/DataBar.tsx b/packages/base-sheets/src/View/UI/ToolBar/DataBar.tsx new file mode 100644 index 0000000000..f0f4350aa8 --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/DataBar.tsx @@ -0,0 +1,22 @@ +type BarProps = { + list: Array>; + img: string; + onClick?: (item: {}, index: number) => void; +}; + +export const DataBar = (props: BarProps) => { + const { list, img, onClick } = props; + const background = `url(${img}) no-repeat`; + + let handleClick = (item: {}, index: number) => { + onClick?.call(this, item, index); + }; + + return ( +
        + {list.map((item, index) => ( +
        handleClick(item, index)} style={{ width: '28px', height: '26px', background, backgroundPosition: `${item.x} ${item.y}` }}>
        + ))} +
        + ); +}; diff --git a/packages/base-sheets/src/View/UI/ToolBar/SingleButton.tsx b/packages/base-sheets/src/View/UI/ToolBar/SingleButton.tsx new file mode 100644 index 0000000000..689c0c51dd --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/SingleButton.tsx @@ -0,0 +1,68 @@ +import { BaseSingleButtonProps, Component, JSXComponent, SingleButtonComponent } from '@univer/base-component'; +import { Button, Tooltip } from '@univer/style-universheet'; + +import styles from './index.module.less'; + +export interface SingleButtonProps { + label?: string | JSX.Element; + icon?: JSX.Element | string | null | undefined; + tooltip?: string; + name?: string; + key?: string; + onClick?: (...arg: any[]) => void; + active?: boolean; + triggerUpdate?: (func: Function) => void; +} +export interface IState { + active: boolean; +} + +class SingleButton extends Component { + initialize(props: SingleButtonProps): void { + // Mount update function, when updating the selection range, the cell formats corresponding to different ranges are different, and the toolbar status and values need to be updated in real time + const { triggerUpdate, active } = props; + triggerUpdate && triggerUpdate(this.update.bind(this)); + + this.state = { + active: active || false, + }; + } + + update(active: boolean) { + this.setState({ active }); + } + + onClick() { + this.setState( + (prevState: IState) => ({ + active: !prevState.active, + }), + () => { + this.props.onClick && this.props.onClick(this.state.active); + } + ); + } + + render() { + const { name, tooltip, label, icon, onClick, key } = this.props; + const { active } = this.state; + return ( +
        + + + +
        + ); + } +} + +export class UniverSingleButton implements SingleButtonComponent { + render(): JSXComponent { + return SingleButton; + } +} + +export { SingleButton }; diff --git a/packages/base-sheets/src/View/UI/ToolBar/ToolBar-zh.md b/packages/base-sheets/src/View/UI/ToolBar/ToolBar-zh.md new file mode 100644 index 0000000000..ffba806bfe --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/ToolBar-zh.md @@ -0,0 +1,92 @@ +--- +category: Components +type: 定制 +title: ToolBar +subtitle: 工具栏 +cover: '' +--- + +## 介绍 + +工具栏 + +## API + +工具栏的属性说明如下: + +| 属性 | 说明 | 类型 | 默认值 | +| ---------- | --------------------- | --------------------------- | -------------------------- | +| style | 自定义 css 央样式对象 | `JSX.CSSProperties` | - | +| config | 配置项 | `IShowToolBarConfig` | `IShowToolBarConfig` | +| forwardRef | preact refs 引用 | `RefObject` | - | +| func | 回调挂载 | `object` | `{ addButton: Function; }` | + +`IShowToolBarConfig`包含以下配置,默认都为 `true`,可以指定想隐藏的工具栏按钮为`false` + +```json +{ + "undoRedo": false, //撤销重做,注意撤消重做是两个按钮,由这一个配置决定显示还是隐藏 + "paintFormat": false, //格式刷 + "currencyFormat": false, //货币格式 + "percentageFormat": false, //百分比格式 + "numberDecrease": false, // '减少小数位数' + "numberIncrease": false, // '增加小数位数 + "moreFormats": false, // '更多格式' + "font": false, // '字体' + "fontSize": false, // '字号大小' + "bold": false, // '粗体 (Ctrl+B)' + "italic": false, // '斜体 (Ctrl+I)' + "strikethrough": false, // '删除线 (Alt+Shift+5)' + "underline": false, // '下划线 (Alt+Shift+6)' + "textColor": false, // '文本颜色' + "fillColor": false, // '单元格颜色' + "border": false, // '边框' + "mergeCell": false, // '合并单元格' + "horizontalAlignMode": false, // '水平对齐方式' + "verticalAlignMode": false, // '垂直对齐方式' + "textWrapMode": false, // '换行方式' + "textRotateMode": false // '文本旋转方式' +} +``` + +以下这些配置已经分配到各个插件中了 + +```json +{ + "link": false, // '插入链接' + "image": false, // '插入图片' + "chart": false, // '图表' + "comment": false, //'批注' + "pivotTable": false, //'数据透视表' + "function": false, // '公式' + "frozenMode": false, // '冻结方式' + "sort": false, // '排序' + "filter": false, // '筛选' + "conditionalalFormat": false, // '条件格式' + "dataValidation": false, // '数据验证' + "splitColumn": false, // '分列' + "screenshot": false, // '截图' + "findAndReplace": false, // '查找替换' + "protection": false, // '工作表保护' + "print": false // '打印' +} +``` + +## 案例 + +```jsx +import { ToolBar } from '@univer/style-universheet'; + +const SheetContainer = () => { + return ( + + ); +}; +``` diff --git a/packages/base-sheets/src/View/UI/ToolBar/ToolBar.md b/packages/base-sheets/src/View/UI/ToolBar/ToolBar.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/base-sheets/src/View/UI/ToolBar/ToolBar.tsx b/packages/base-sheets/src/View/UI/ToolBar/ToolBar.tsx new file mode 100644 index 0000000000..82e53e5b28 --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/ToolBar.tsx @@ -0,0 +1,541 @@ +import { + BaseComponentRender, + BaseComponentSheet, + BaseToolBarProps, + Component, + createRef, + IToolBarItemProps, + ISlotElement, + RefObject, + Description, + DescriptionEnum, + BaseSelectProps, +} from '@univer/base-component'; +import { Nullable, Observer, PLUGIN_NAMES, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '../../../SpreadsheetPlugin'; + +import styles from './index.module.less'; +import { SingleButton } from './SingleButton'; + +interface IState { + toolList: IToolBarItemProps[]; + moreToolList: IToolBarItemProps[]; + defaultToolList: IToolBarItemProps[]; + changeLabel: string[]; + more: boolean; + index: number; + toolbarListWidths: number[]; + moreText: string; +} + +/** + * button type + * + * 1. single : single button + * 2. select : click button, alert select options + * 3. doubleButton : left single button, right select + * 4. doubleButtonPopup : left single button, right popup + */ +export class ToolBar extends Component { + toolbarWarp = createRef(); + + toolbar = createRef(); + + moreBtn = createRef(); + + FormatModal = createRef(); + + moreToolRef = createRef(); + + Render: BaseComponentRender; + + private _localeObserver: Nullable>; + + initialize(props: BaseToolBarProps) { + // super(props); + + const { toolList, func } = props; + + // const { func } = this.props; + func?.addButton(this.addButton.bind(this)); + + const component = this._context.getPluginManager().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + // Button contains main button and drop down arrow, translation file contains main and right + changeLabel: ['horizontalAlignMode', 'verticalAlignMode', 'textWrapMode', 'textRotateMode', 'fillColor', 'textColor', 'border', 'mergeCell'], + more: false, + toolList, + defaultToolList: [], + moreToolList: [], + index: 0, + toolbarListWidths: [], + moreText: '', + }; + + this.initEvent(); + } + + initEvent() { + // this.getContext() + // .getObserverManager() + // .getObserver('onSheetRenderDidMountObservable', 'workbook') + // ?.add((e) => { + // const mainComponent = this.getContext().getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)!; + // mainComponent.getMainComponent().onPointerUpObserver.add((evt: IPointerEvent | IMouseEvent) => { + // const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; + // console.log('----mainCom----', evt); + // }); + // }); + } + + handleClickMoreFormats(...args: any[]) { + // console.dir(args); + } + + handleClick = (children: IToolBarItemProps, toolbarIndex: number, childrenIndex: number) => { + let prevState = this.state.toolList; + const changeLabel = this.state.changeLabel; + if (changeLabel.includes(prevState[toolbarIndex].locale!)) prevState[toolbarIndex].label! = children.icon!; + prevState[toolbarIndex].children?.forEach((item, i) => { + item.selected = i === childrenIndex; + }); + this.setState({ toolList: prevState }); + }; + + click = (toolbarIndex: number) => { + console.log(`this is ${this.state.toolList[toolbarIndex].locale}`); + }; + + // showFormatModal + showFormatModal = (e: MouseEvent, name: string) => { + this.FormatModal.current.showModal(name, true); + }; + + /** + * set toollist by config setting and Locale message, some buttons will be hidden + */ + setLocale(cb: () => void) { + const locale = this.context.locale; + + const more = locale.get('toolbar.more'); + + this.setState((prevState) => { + const currentToolList = prevState.toolList.map((item) => { + // set current Locale string for tooltip + item.tooltip = locale.get(`toolbar.${item.locale}`); + + if (this.state.changeLabel.includes(item.locale!)) { + item.tooltip = locale.get(`toolbar.${item.locale}.main`); + item.tooltipRight = locale.get(`toolbar.${item.locale}.right`); + } + // set current Locale string for select + if (item.type === 'select') { + if (!item.children?.length) return item; + this.setLocalIconAndLabel(item); + + // null or undefined + if (!item.label) { + item.label = typeof item.label === 'object' ? item.label : item.children[0].label; + } + // string, or JSX instanceof JSX.Element, HTML string + } + + // other props + + return item; + }); + + return { + toolList: currentToolList, + defaultToolList: currentToolList, + moreText: more, + }; + }, cb); + } + + setLocalIconAndLabel(item: IToolBarItemProps) { + const locale = this.context.locale; + if (item.children) { + item.children.forEach((ele) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + if (ele.iconName) { + ele.icon = locale.get(`${ele.iconName}`); + } + + this.setLocalIconAndLabel(ele); + }); + } + } + + /** + * add plugin button + */ + addButton(item: IToolBarItemProps): Promise { + /** + * en: There will be multiple toolbar buttons added, so you need to use prevState to add buttons from the previous state, otherwise it will cause overwriting + * + * zh: 会有多个工具栏按钮添加进来,所以需要使用prevState来从上一个状态新增按钮,否则会造成覆盖 + * + * TODO: 节流,多个Button一次性渲染 + */ + return new Promise((resolve) => { + this.setState( + (prevState) => ({ + toolList: [...prevState.toolList, item], + defaultToolList: [...prevState.toolList, item], + }), + () => { + resolve(); + // this.setToolbarListWidth + } + ); + }); + } + + setToolBar(toolList: IToolBarItemProps[]) { + this.setState( + { + toolList, + }, + () => { + this.setLocale(() => { + // this.setToolbarListWidth(); + // this.resetToolbar(); + }); + } + ); + } + + /** + * show more tool buttons + * + * TODO : 点击其他地方需要隐藏more buttons + */ + moreTool = () => { + let more = this.state.more; + this.setState({ more: !more }); + + if (!more) { + document.addEventListener('click', this.hide, true); + } + }; + + hide = (e: Event) => { + if (this.moreToolRef.current.contains(e.target)) return; + e.stopImmediatePropagation(); + this.setState({ more: false }); + document.removeEventListener('click', this.hide, true); + }; + + /** + * Gets the distance of each button from the parent element + */ + setToolbarListWidth = () => { + const toolbar = this.toolbarWarp.current.querySelector(`.${styles.toolbar}`); + const toolbarLeft = toolbar.getBoundingClientRect().left; + const toolbarListWidths: number[] = []; // 每个 btn 离父元素 left 距离 + const toolbarListWidths1: number[] = []; // 每个 btn 离父元素 left 距离 + + toolbar.childNodes.forEach((item: Element, i: number) => { + toolbarListWidths.push(parseInt((item.getBoundingClientRect().left - toolbarLeft + item.getBoundingClientRect().width).toString())); + }); + let max = 0; + let count = 0; + toolbarListWidths1.push(toolbarListWidths[0]); + toolbarListWidths.reduce((prev, next) => { + if (prev > next && count === 0) { + max = prev; + count++; + } + toolbarListWidths1.push(next + max); + return next + max; + }); + + this.setState( + () => ({ + toolbarListWidths: toolbarListWidths1, + }), + this.resetToolbar + ); + }; + + resetToolbar = () => { + /** + * TODO: resize 有性能问题。 + * 思路 + * + * 1. toolbarWarp.current.offsetWidth 存储起来 + * 2. 节流 + * 3. 插件主UI拆分到其他插槽,只有按钮和简单的items放到工具栏 + * + * + * zh: 根据插件数组长度,最后一个插件安装完成,执行一次。后面动态安装的插件,每安装一次计算一次 + * + * en: According to the length of the plug-in array, the last plug-in is installed and executed once. The dynamically installed plugins will be calculated once for each installation + * + */ + + const toolbarWarp = this.toolbarWarp; + // const bodyWidth = document.body.clientWidth - (152 * 2 + 70); + const toolbarList = this.state.toolbarListWidths; + let index = toolbarList.length; + for (let i = 0; i < toolbarList.length; i++) { + let itemLeft = toolbarList[i]; + // console.log(toolbarList); + + // console.log(itemLeft, toolbarWarp.current.offsetWidth - 60); + + if (itemLeft > toolbarWarp.current.offsetWidth - 60) { + index = i - 1; + break; + } + } + this.setState((preState) => { + const defaultToolList = preState.toolList.slice(0, index); + // 获取contentref + const wrapper = this._context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.getContentRef().current!; + // const wrapper = $$('.univer-content-inner-right-container'); + const height = `${(wrapper as HTMLDivElement).offsetHeight}px`; + // 下拉框最大高度设置为content高度 + defaultToolList.forEach((item) => { + if (item.type === 'select') { + if (item.style) { + item.style = { ...item.style, maxHeight: height }; + } else { + item.style = { maxHeight: height }; + } + } + }); + + const moreToolList = preState.toolList.slice(index); + return { + defaultToolList, + moreToolList, + }; + }); + }; + + /** + * init + */ + componentWillMount() { + // // init Locale message + // this.setLocale(); + // // when change Locale, update Locale message + // // do not use componentWillUpdate,it will listen all state changes + // this._localeObserver = this._context + // .getObserverManager() + // .getObserver('onAfterChangeUILocaleObservable', 'workbook') + // ?.add(() => { + // this.setLocale(); + // }); + // // resize listener + // window.addEventListener('resize', debounce(this.resetToolbar, 300)); + // this.setState((prevState) => ({ + // defaultToolList: prevState.toolList, + // index: prevState.toolList.length, + // })); + } + + componentDidMount() { + // this.setToolbarListWidth(); + // this.resetToolbar(); + + this._context.getObserverManager().getObserver('onToolBarDidMountObservable')?.notifyObservers(this); + } + + /** + * when change Locale, update Locale message + */ + componentWillUnmount() { + // this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + // window.removeEventListener('resize', debounce(this.resetToolbar, 300)); + } + + toolbarListRender(tool: IToolBarItemProps[]) { + let ele = tool.map((item: IToolBarItemProps, index: number) => { + if (!item.show) return null; + + this.itemRender(item); + + if (item.type === ISlotElement.SINGLE) { + return ( + + ); + } + + if (item.type === ISlotElement.SELECT) { + const Select = this.Render.renderFunction('Select'); + return ( + + ); + } + + if (item.type === ISlotElement.JSX) { + return item.label; + } + + return null; + })} */} + + {this.state.moreToolList.length === 0 ? null : ( +
        + + + +
        + )} + + +
        + {/* {this.toolbarListRender(this.state.moreToolList)} */} +
        + + + ); + } +} diff --git a/packages/base-sheets/src/View/UI/ToolBar/index.less b/packages/base-sheets/src/View/UI/ToolBar/index.less new file mode 100644 index 0000000000..3302e5aa4f --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/index.less @@ -0,0 +1,312 @@ +@import '@univer/style-universheet/assets/css/skin/base.module.less'; + +.universheet-wa-editor.toolbar { + height: 32px; + background: var(--bg); + position: relative; + padding: 5px 0 3px 15px; + border-bottom: 1px solid var(--bg); + white-space: nowrap; + transition: all 0.2s; + box-sizing: initial; + + span[role='img'] { + font-size: 24px; + display: inline-block; + } + + span[role='img'].univericon-nextIcon { + font-size: 13px; + } + + span[role='img'].univericon-rightIcon { + font-size: 13px; + } + + .universheet-toolbar-left-skin { + width: 15px; + position: absolute; + left: 0; + top: 1px; + bottom: 1px; + } + + .universheet-inline-block { + display: inline-block; + } + + .universheet-flex { + display: flex; + } + + .universheet-toolbar-button, + .universheet-toolbar-menu-button { + vertical-align: middle; + + &:hover { + background: rgba(0, 0, 0, 0.06); + } + + button { + min-width: 26px; + height: 26px; + padding: 0; + border: none; + .toolbar-button(); + + span { + height: 100%; + vertical-align: text-bottom; + } + } + } + + .universheet-toolbar-button-outer-box, + .universheet-toolbar-menu-button-outer-box { + .outer-inner-box(); + } + + .universheet-toolbar-select { + position: relative; + } + + .universheet-toolbar-button-inner-box, + .universheet-toolbar-menu-button-inner-box { + padding: 0 2px; + padding: 0; + text-align: center; + height: 26px; + min-width: 26px; + font-size: 12px; + } + + .universheet-toolbar-menu-button-caption { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-left: 1px; + text-align: center; + height: 100%; + } + + .universheet-toolbar-menu-button-text { + width: 55px !important; + } + + .universheet-toolbar-separator { + line-height: normal; + list-style: none; + outline: 0; + overflow: hidden; + padding: 0; + text-decoration: none; + width: 0; + height: 20px; + vertical-align: top; + border-left: 1px solid #e0e0e0; + margin: 5px 1px; + } + + .universheet-toolbar-combo-button { + font-weight: 700; + vertical-align: inherit; + margin: 0 1px; + .toolbar-button(); + } + + .universheet-toolbar-combo-button-inner-box, + .universheet-toolbar-combo-button-outer-box { + .outer-inner-box(); + } + + .universheet-toolbar-zoom-combobox .universheet-toolbar-combo-button-caption { + width: 36px !important; + } + + .universheet-toolbar-combo-button-caption { + padding: 0; + margin: 0 0 0 -3px; + } + + .universheet-toolbar-textinput { + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + border: 1px solid #d9d9d9; + border-top: 1px solid silver; + font-size: 13px; + height: 25px; + padding: 1px 0 1px 8px; + } + + .universheet-toolbar-combo-button-input { + background: 0 0; + border: 1px solid transparent !important; + color: var(--text-color); + font-family: Arial, sans-serif !important; + font-size: 11px !important; + font-weight: 700 !important; + height: 20px !important; + overflow: hidden !important; + color: rgba(0, 0, 0, 0.7); + height: 22px !important; + width: 22px; + } + + .universheet-cols-menu { + .menu-button(); + width: 252px; + left: 0; + top: 26px; + display: none; + } + + .universheet-cols-menu .universheet-cols-menuitem-hover, + .universheet-cols-menu .universheet-cols-menuitem:hover { + background-color: #efefef; + } + + .universheet-cols-menu .universheet-cols-menuitem { + position: relative; + color: var(--text-color); + cursor: pointer; + list-style: none; + margin: 0; + padding: 1px 6em 1px 20px; + white-space: nowrap; + padding-left: 8px; + vertical-align: middle; + padding-right: 24px; + user-select: none; + line-height: 16px; + } + + .universheet-submenu-arrow { + -webkit-transition: all 218ms; + -moz-transition: all 218ms; + -o-transition: all 218ms; + transition: all 218ms; + font-size: 12px; + left: auto; + right: 15px; + top: 50%; + transform: translateY(-50%); + padding-right: 0; + position: absolute; + text-align: right; + opacity: 0.5; + color: #000; + user-select: none; + font-family: Arial; + } + + .universheet-toolbar-dblbutton { + .universheet-toolbar-next-button { + border-left: 1px solid transparent; + position: relative; + + .universheet-next-menu { + position: absolute; + z-index: `getZindex('toolbar') `; + background-color: #fff; + box-shadow: var(--box-shadow-base); + font-size: 12px; + width: fit-content; + + .univer-menu-item > div { + width: 100%; + display: flex; + justify-content: space-between; + box-sizing: border-box; + padding: 0 5px 0 0; + + .universheet-next-menu-icon { + width: 25px; + + & ~ div { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; + } + } + } + + svg { + font-size: 24px; + vertical-align: top; + } + } + + svg { + font-size: 12px; + vertical-align: middle; + } + } + + .universheet-color-bottom { + width: 10px; + margin: 0 auto; + outline: 2px solid; + position: relative; + bottom: 7px; + } + + button.univer-btn { + min-width: 15px; + + &:active { + background: transparent !important; + } + } + + :hover .universheet-toolbar-next-button { + border-left: 1px solid #e0e0e0; + } + } +} + +// 重复样式 +.outer-inner-box() { + border: 0; + vertical-align: top; + margin: 0; + padding: 0; +} + +.toolbar-button() { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + background: 0; + border: 1px solid transparent; + outline: 0; + padding: 0; + list-style: none; + font-size: 11px; + text-decoration: none; + cursor: default; + height: 26px; + line-height: 26px; + color: var(--text-color); +} + +.menu-button() { + border-radius: 0; + box-shadow: 0 2px 4px rgb(0 0 0 / 20%); + transition: opacity 218ms; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + cursor: default; + font-size: 13px; + margin: 0; + outline: 0; + padding: 6px 0; + position: absolute; + z-index: ~`getZindex('toolbar') `; + box-sizing: border-box; + user-select: none; +} diff --git a/packages/base-sheets/src/View/UI/ToolBar/index.module.less b/packages/base-sheets/src/View/UI/ToolBar/index.module.less new file mode 100644 index 0000000000..1bfa244250 --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/index.module.less @@ -0,0 +1,153 @@ +@import '@univer/style-universheet/assets/css/skin/base.module.less'; + +.toolbar-warp { + height: 40px; + max-height: fit-content; + width: 100%; + overflow: unset; + box-sizing: border-box; + font-size: var(--font-size); + background-color: var(--bg); + position: relative; + display: flex; + align-items: center; + border-bottom: 1px solid var(--border-color-base); + + .single-button { + &.more-hide { + display: none; + } + + &.more-show { + display: block; + } + } +} + +.toolbar { + max-width: 100%; + color: var(--text-color); + padding: 5px 15px; + height: 40px; + position: relative; + + > div { + margin-top: 7px; + height: 26px; + display: inline-block; + vertical-align: middle; + + &.select-button:hover { + background-color: #efefef; + } + } + + .single-button { + margin: 0 3px; + + button { + .button(); + font-size: 24px; + } + } + + .double-button { + position: relative; + display: flex; + align-items: center; + margin: 0 5px; + + .color-bottom { + width: 10px; + margin: 0 auto; + outline: 2px solid; + position: relative; + bottom: 4px; + } + + .double-button-right { + height: 100%; + display: flex; + align-items: center; + + .double-button-right-popup { + box-shadow: var(--box-shadow-base); + background-color: var(--bg); + position: absolute; + left: 0; + top: 35px; + width: max-content; + + .double-button-menu-item { + display: flex; + align-content: center; + + .double-button-menu-item-icon { + width: 25px; + margin-right: 5px; + } + + .double-button-menu-item-content { + display: flex; + align-items: center; + } + } + } + + button { + padding: 0; + padding-right: 5px; + } + } + + button { + .button(); + } + + span[role='img'] { + font-size: 24px; + } + } +} + +.more-button { + height: 35px; + margin: 5px; + display: none; + .button(); + + &.more-show { + display: block; + } +} + +.more-tool { + height: 40px; + display: none; + align-items: center; + padding: 0 10px; + background-color: var(--bg); + box-shadow: var(--box-shadow-base); + position: absolute; + right: 2em; + top: 40px; + z-index: `getZindex('toolbar') `; + + &.more-show { + display: flex; + } + + button { + .button(); + font-size: 24px; + } +} + +.button() { + padding: 0px 5px; + line-height: 1; +} + +span[role='img'].univericon-nextIcon { + font-size: 12px; +} diff --git a/packages/base-sheets/src/View/UI/ToolBar/index.ts b/packages/base-sheets/src/View/UI/ToolBar/index.ts new file mode 100644 index 0000000000..436d3c8e3e --- /dev/null +++ b/packages/base-sheets/src/View/UI/ToolBar/index.ts @@ -0,0 +1,5 @@ +import { ToolBar } from './ToolBar'; + +export * from './SingleButton'; + +export { ToolBar }; diff --git a/packages/base-sheets/src/index.ts b/packages/base-sheets/src/index.ts new file mode 100644 index 0000000000..747a67cef9 --- /dev/null +++ b/packages/base-sheets/src/index.ts @@ -0,0 +1,3 @@ +import { SpreadsheetPlugin } from './SpreadsheetPlugin'; + +export { SpreadsheetPlugin }; diff --git a/packages/base-sheets/src/main.tsx b/packages/base-sheets/src/main.tsx new file mode 100644 index 0000000000..9e3a467561 --- /dev/null +++ b/packages/base-sheets/src/main.tsx @@ -0,0 +1,62 @@ +import { defaultWorkbookData, UniverSheet } from '@univer/core'; +import { UniverComponentSheet } from '@univer/style-universheet'; +import { RenderEngine } from '@univer/base-render'; +import { ClipboardPlugin } from '@univer/sheets-plugin-clipboard'; +import { FormulaPlugin } from '@univer/sheets-plugin-formula'; + +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: { + innerRight: false, + outerLeft: false, + toolBarConfig: { + paintFormat: false, + currencyFormat: false, + percentageFormat: false, + numberDecrease: false, + numberIncrease: false, + moreFormats: false, + }, + }, +}; +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); +univerSheetUp.installPlugin(new RenderEngine()); +univerSheetUp.installPlugin(new UniverComponentSheet()); +FormulaPlugin.create().installTo(univerSheetUp); + +// init spreadsheet plugin first +import('./SpreadsheetPlugin').then(({ SpreadsheetPlugin }) => { + let spreadsheetPlugin = new SpreadsheetPlugin(uiDefaultConfigUp); + let clipboardPlugin = new ClipboardPlugin(); + univerSheetUp.installPlugin(spreadsheetPlugin); + univerSheetUp.installPlugin(clipboardPlugin); + (window as any).spreadsheetPlugin = spreadsheetPlugin; +}); + +// const uiDefaultConfigDown = { +// containerId: 'universheet-demo-down', +// layout: { +// outerLeft: false, + +// outerRight: true, + +// innerLeft: false, + +// innerRight: false, + +// toolBar: true, + +// toolBarConfig: { +// undoRedo: true, +// font: true, +// }, +// contentSplit: true, +// }, +// }; + +// const univerSheetDown = UniverSheet.newInstance(defaultWorkbookData); + +// univerSheetDown.installPlugin(new StyleUniverSheet(uiDefaultConfigDown)); + +// univerSheetDown.installPlugin(new FilterPlugin()); +// univerSheetDown.installPlugin(new SpreadsheetPlugin()); diff --git a/packages/base-sheets/src/preact.d.ts b/packages/base-sheets/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/base-sheets/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/base-sheets/src/type.d.ts b/packages/base-sheets/src/type.d.ts new file mode 100644 index 0000000000..f94925675d --- /dev/null +++ b/packages/base-sheets/src/type.d.ts @@ -0,0 +1,10 @@ +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} + +declare module 'enzyme'; diff --git a/packages/base-sheets/tests/components.spec.ts b/packages/base-sheets/tests/components.spec.ts new file mode 100644 index 0000000000..0f73267750 --- /dev/null +++ b/packages/base-sheets/tests/components.spec.ts @@ -0,0 +1,77 @@ +import { expect, test } from '@playwright/test'; + +const os = require('os'); +/** + * Delay for a number of milliseconds + */ +function sleep(delay) { + var start = new Date().getTime(); + while (new Date().getTime() < start + delay); +} + +function osType() { + if (os.type() == 'Windows_NT') { + //windows + return 'windows'; + } else if (os.type() == 'Darwin') { + //mac + return 'mac'; + } else if (os.type() == 'Linux') { + //Linux + return 'linux'; + } else { + //Prompt not supported + return ''; + } +} + +/** + * Use Playwright to test UI + */ +test.describe('UniverSheet E2E Test with Playwright', () => { + test.beforeEach(async ({ page }) => { + // Go to the starting url before each test. + await page.goto('http://localhost:3103/'); + sleep(3000); + }); + + test('Test color picker Components', async ({ page }) => { + // click color picker arrow + await page.$$eval('.univer-tooltip-grop', (elHandles) => { + elHandles.forEach((el, i) => { + if (i === 8) { + (el as HTMLElement).click(); + } + }); + }); + // click red circle button + await page.$$eval('.univer-picker-swatch-btn', (elHandles) => { + elHandles.forEach((el, i) => { + if (i === 8) { + (el as HTMLElement).click(); + } + }); + }); + + // get selected color + const color = await page.evaluate((el) => window.getComputedStyle(el as Element).backgroundColor, await page.$('.univer-select-color')); + expect(color).toEqual('rgb(255, 0, 0)'); + + await page.screenshot({ path: 'screenshot/clicks_color_picker.png' }); + }); + + test('Test select input Components', async ({ page }) => { + const Element = await page.$('.univer-select-input .univer-input.univer-input-borderless'); + await Element?.click(); + if (osType() === 'windows') { + await page.keyboard.press('Control+A'); + } else if (osType() === 'mac') { + await page.keyboard.press('Meta+A'); + } + + await page.keyboard.press('Backspace'); + await Element?.type('24'); + const fontsize = await page.evaluate((el) => (el as HTMLInputElement).value, await page.$('.univer-select-input .univer-input.univer-input-borderless')); + expect(fontsize).toEqual('24'); + }); +}); diff --git a/packages/base-sheets/tsconfig.json b/packages/base-sheets/tsconfig.json new file mode 100644 index 0000000000..e67a65c947 --- /dev/null +++ b/packages/base-sheets/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "paths": { + "@*": ["src/*"] + }, + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/base-sheets/tsconfig.ref.json b/packages/base-sheets/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/base-sheets/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/base-sheets/tsconfig.test.json b/packages/base-sheets/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/base-sheets/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/base-sheets/vite.config.ts b/packages/base-sheets/vite.config.ts new file mode 100644 index 0000000000..27fd9a4239 --- /dev/null +++ b/packages/base-sheets/vite.config.ts @@ -0,0 +1,69 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import legacy from '@vitejs/plugin-legacy'; +import { name, version } from './package.json'; +import createExternal from 'vite-plugin-external'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginSpreadsheet', + formats: ['es', 'umd'], + fileName: 'univer-base-sheets', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + hmr: true, + }, + plugins: [ + preact(), + // legacy({ + // targets: ['ie >= 11'], + // additionalLegacyPolyfills: ['regenerator-runtime/runtime'], + // }), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/base-render': '@univer/base-render', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], + resolve: { + // preserveSymlinks: true, + // alias: [ + // { + // find: '@Base', + // replacement: path.resolve('..', '/src/Base'), + // }, + // ], + // alias: [ + // { + // find: /^@([^univer].*)$/, + // replacement: path.resolve(__dirname, './src/$1'), + // }, + // ], + }, +}); diff --git a/packages/common-plugin-collaboration/.eslintignore b/packages/common-plugin-collaboration/.eslintignore new file mode 100644 index 0000000000..3d09ffb347 --- /dev/null +++ b/packages/common-plugin-collaboration/.eslintignore @@ -0,0 +1,8 @@ +.eslintrc.js +vite.config.ts +**/*.test.js +dist +lib +node_modules +package.json +tsconfig.json diff --git a/packages/common-plugin-collaboration/README.md b/packages/common-plugin-collaboration/README.md new file mode 100644 index 0000000000..a40351e838 --- /dev/null +++ b/packages/common-plugin-collaboration/README.md @@ -0,0 +1 @@ +# @univer/core diff --git a/packages/common-plugin-collaboration/index.html b/packages/common-plugin-collaboration/index.html new file mode 100644 index 0000000000..9df55fe771 --- /dev/null +++ b/packages/common-plugin-collaboration/index.html @@ -0,0 +1,16 @@ + + + + + + + + UniverSheet + + + +
        + + + + \ No newline at end of file diff --git a/packages/common-plugin-collaboration/jest.config.js b/packages/common-plugin-collaboration/jest.config.js new file mode 100644 index 0000000000..8cbf8940cc --- /dev/null +++ b/packages/common-plugin-collaboration/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; \ No newline at end of file diff --git a/packages/common-plugin-collaboration/package.json b/packages/common-plugin-collaboration/package.json new file mode 100644 index 0000000000..63d8aa215b --- /dev/null +++ b/packages/common-plugin-collaboration/package.json @@ -0,0 +1,60 @@ +{ + "name": "@univer/common-plugin-collaboration", + "version": "0.0.1", + "description": "", + "author": "", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/type.d.ts", + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "directories": { + "lib": "lib" + }, + "files": [ + "package.json", + "lib" + ], + "scripts": { + "test": "cross-env NODE_ENV=--experimental-vm-modules jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "license": "MIT", + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "csstype": "^3.0.11", + "es6-proxy-polyfill": "^2.1.1", + "reflect-metadata": "^0.1.13" + }, + "devDependencies": { + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@typescript-eslint/eslint-plugin": "^4.28.5", + "@typescript-eslint/parser": "^4.28.5", + "@vitejs/plugin-legacy": "^1.5.0", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest": "29.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + } +} diff --git a/packages/common-plugin-collaboration/src/Convert/ArrayConvertor.ts b/packages/common-plugin-collaboration/src/Convert/ArrayConvertor.ts new file mode 100644 index 0000000000..49c31891ac --- /dev/null +++ b/packages/common-plugin-collaboration/src/Convert/ArrayConvertor.ts @@ -0,0 +1,12 @@ +import { ConvertorBase, EXTENSION_NAMES } from '@univer/core'; + +export class ArrayConvertor extends ConvertorBase { + constructor() { + super(); + this.name = EXTENSION_NAMES.ARRAY_CONVERTOR; + } + + execute(operation: string, type: string, attribute: string, key?: string, value?: string): object { + return {}; + } +} diff --git a/packages/common-plugin-collaboration/src/Convert/MatrixConvertor.ts b/packages/common-plugin-collaboration/src/Convert/MatrixConvertor.ts new file mode 100644 index 0000000000..e727ab134a --- /dev/null +++ b/packages/common-plugin-collaboration/src/Convert/MatrixConvertor.ts @@ -0,0 +1,12 @@ +import { ConvertorBase, EXTENSION_NAMES } from '@univer/core'; + +export class MatrixConvertor extends ConvertorBase { + constructor() { + super(); + this.name = EXTENSION_NAMES.MATRIX_CONVERTOR; + } + + execute(operation: string, type: string, attribute: string, key?: string, value?: string): object { + return {}; + } +} diff --git a/packages/common-plugin-collaboration/src/Convert/index.ts b/packages/common-plugin-collaboration/src/Convert/index.ts new file mode 100644 index 0000000000..c1575c4567 --- /dev/null +++ b/packages/common-plugin-collaboration/src/Convert/index.ts @@ -0,0 +1,2 @@ +export * from './ArrayConvertor'; +export * from './MatrixConvertor'; diff --git a/packages/common-plugin-collaboration/src/index.ts b/packages/common-plugin-collaboration/src/index.ts new file mode 100644 index 0000000000..ce3c4dce57 --- /dev/null +++ b/packages/common-plugin-collaboration/src/index.ts @@ -0,0 +1 @@ +export * from './Convert'; diff --git a/packages/common-plugin-collaboration/src/type.d.ts b/packages/common-plugin-collaboration/src/type.d.ts new file mode 100644 index 0000000000..c8d80d0d43 --- /dev/null +++ b/packages/common-plugin-collaboration/src/type.d.ts @@ -0,0 +1,2 @@ +export * from './index'; +declare module '@univer/core' {} diff --git a/packages/common-plugin-collaboration/tsconfig.json b/packages/common-plugin-collaboration/tsconfig.json new file mode 100644 index 0000000000..847d073f6d --- /dev/null +++ b/packages/common-plugin-collaboration/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports true + "jsx": "preserve", + "experimentalDecorators": true, + "suppressImplicitAnyIndexErrors": true, + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/common-plugin-collaboration/tsconfig.ref.json b/packages/common-plugin-collaboration/tsconfig.ref.json new file mode 100644 index 0000000000..b321caa9ce --- /dev/null +++ b/packages/common-plugin-collaboration/tsconfig.ref.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "exclude": ["test/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/common-plugin-collaboration/vite.config.ts b/packages/common-plugin-collaboration/vite.config.ts new file mode 100644 index 0000000000..2e70dbe0b0 --- /dev/null +++ b/packages/common-plugin-collaboration/vite.config.ts @@ -0,0 +1,39 @@ +import { defineConfig } from 'vite'; +import { name, version } from './package.json'; +import path from 'path'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + // build: { + // lib: { + // entry: resolve('src/index.ts'), + // name: 'UniverSheet', + // formats: ['es', 'umd'], + // fileName: 'index', + // }, + // outDir: './lib', + // }, + define: { + pkgJson: { name, version }, + }, + esbuild: { + jsxFactory: 'DOMcreateElement', + jsxFragment: 'DOMcreateFragment', + jsxInject: "import { DOMcreateElement, DOMcreateFragment } from '@/Common/jsxFactory'", + }, + server: { + port: 3102, + open: true, // // Automatically open the app in the browser on server start. + }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', + }, + }, +}); diff --git a/packages/core/.prettierrc.cjs b/packages/core/.prettierrc.cjs new file mode 100644 index 0000000000..1cf39b914d --- /dev/null +++ b/packages/core/.prettierrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + singleQuote: true, + trailingComma: 'es5', + tabWidth: 4, + semi: true, + printWidth: 85, +}; diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000000..6e136b8708 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,19 @@ +# @univer/core + +## dev + +```bash +pnpm run --filter @univer/sheets-plugin-alternating-colors dev +``` + +## test + +```bash +pnpm run --filter @univer/sheets-plugin-alternating-colors test +``` + +## test cover + +```bash +pnpm run --filter @univer/sheets-plugin-alternating-colors test:cover +``` diff --git a/packages/core/api-config.json b/packages/core/api-config.json new file mode 100644 index 0000000000..b8430a0437 --- /dev/null +++ b/packages/core/api-config.json @@ -0,0 +1,6 @@ +{ + "oldInputPath": "temp/core.api.json", + "newInputPath": "temp/input/core.api.json", + "oldMarkdownPath": "temp/core.api.md", + "newMarkdownPath": "temp/markdown/core.api.md" +} \ No newline at end of file diff --git a/packages/core/api-documenter.json b/packages/core/api-documenter.json new file mode 100644 index 0000000000..89e52304a7 --- /dev/null +++ b/packages/core/api-documenter.json @@ -0,0 +1,3 @@ +{ + "outputTarget": "markdown" +} diff --git a/packages/core/api-extractor.json b/packages/core/api-extractor.json new file mode 100644 index 0000000000..11f2fbe013 --- /dev/null +++ b/packages/core/api-extractor.json @@ -0,0 +1,4 @@ +{ + "extends": "../../api-extractor.json" + // "mainEntryPointFilePath": "/lib/index.d.ts" +} diff --git a/packages/core/api.js b/packages/core/api.js new file mode 100644 index 0000000000..7217eacef0 --- /dev/null +++ b/packages/core/api.js @@ -0,0 +1,133 @@ +const fs = require('fs'); +const path = require('path'); +const execa = require('execa'); +const rimraf = require('rimraf'); +const { spawn } = require('child_process'); + +const { Extractor, ExtractorConfig } = require('@microsoft/api-extractor'); + +try { + // clear folder + rimraf('./temp/*', () => { + execScript('npm', ['run', 'tsc'], () => { + // generate api + apiExtractor().then( + () => { + apiDocumenter(); + }, + () => { + apiDocumenter(); + } + ); + }); + }); +} catch (error) { + console.log(error); +} + +function apiExtractor() { + const apiExtractorJsonPath = path.join(__dirname, './api-extractor.json'); + + // Load and parse the api-extractor.json file + const extractorConfig = ExtractorConfig.loadFileAndPrepare(apiExtractorJsonPath); + + return new Promise((resolve, reject) => { + try { + // Invoke API Extractor + const extractorResult = Extractor.invoke(extractorConfig, { + // Equivalent to the "--local" command-line parameter + localBuild: true, + + // Equivalent to the "--verbose" command-line parameter + showVerboseMessages: true, + }); + + if (extractorResult.succeeded) { + console.log(`API Extractor completed successfully`); + process.exitCode = 0; + + resolve(); + } else { + console.error( + `API Extractor completed with ${extractorResult.errorCount} errors` + + ` and ${extractorResult.warningCount} warnings` + ); + process.exitCode = 1; + + reject(); + } + } catch (p) { + console.log(`Exit code: ${p.exitCode}`); + console.log(`Error: ${p.stderr}`); + + reject(); + } + }); +} + +async function apiDocumenter() { + // create empty folders + let input = 'temp/input'; + let markdown = 'temp/markdown'; + + let apiJsonData = fs.readFileSync('api-config.json'); + let { oldInputPath, newInputPath, oldMarkdownPath, newMarkdownPath } = + JSON.parse(apiJsonData); + + const mkmvInput = new Promise((resolve, reject) => { + mkdir(input, () => { + mv(oldInputPath, newInputPath, resolve); + }); + }); + const mkmvMarkdown = new Promise((resolve, reject) => { + mkdir(markdown, () => { + mv(oldMarkdownPath, newMarkdownPath, resolve); + }); + }); + + Promise.all([mkmvInput, mkmvMarkdown]).then(() => { + execScript( + 'api-documenter generate --input-folder ./temp/input --output-folder ./doc' + ); + }); +} + +function mkdir(dir, cb) { + fs.mkdir(path.join(__dirname, dir), (err) => { + if (err) { + return console.error(err); + } + console.log(`Successfully create ${dir}!`); + + cb && cb(); + }); +} + +function mv(oldPath, newPath, cb) { + fs.rename(oldPath, newPath, function (err) { + if (err) throw err; + console.log(`Successfully move ${oldPath} to ${newPath}!`); + cb && cb(); + }); +} + +function execScript(cmd, args = [], cb) { + //kick off process of listing files + const child = spawn(cmd, args, { shell: true }); + + //spit stdout to screen + child.stdout.on('data', function (data) { + process.stdout.write(data.toString()); + }); + + //spit stderr to screen + child.stderr.on('data', function (data) { + process.stdout.write(data.toString()); + }); + + child.on('close', function (code) { + console.log('Finished with code ' + code); + + cb && cb(); + }); +} diff --git a/packages/core/index.html b/packages/core/index.html new file mode 100644 index 0000000000..9df55fe771 --- /dev/null +++ b/packages/core/index.html @@ -0,0 +1,16 @@ + + + + + + + + UniverSheet + + + +
        + + + + \ No newline at end of file diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js new file mode 100644 index 0000000000..2c2018d9d0 --- /dev/null +++ b/packages/core/jest.config.js @@ -0,0 +1,27 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true, + coverageReporters: ['json', 'html'], + globalSetup: '/test/globalSetup.js', + // coveragePathIgnorePatterns: ['src/Render/'], + testMatch: ['/test/**/*.test.ts'], + // collectCoverageFrom: ['**/*.{js,jsx}', '!**/node_modules/**', '!**/vendor/**'], + transform: { + '^.+\\.ts?$': 'ts-jest', + }, + coveragePathIgnorePatterns: [ + '/src/Server', + '/src/Shared/RectTree', + '/src/Domain/Group.ts', + '/src/Module', + '/src/Convertor', + '/src/Shared/DropCell.ts', + '/src/Shared/Generate.ts', + '/src/Shared/IOHttp.ts', + '/src/Shared/IOSocket.ts', + '/src/Shared/TypeStoreBase.ts', + '/src/Shared/TransformTool.ts', + ], +}; diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000000..a5740ffa77 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,57 @@ +{ + "name": "@univer/core", + "version": "0.0.1", + "description": "", + "author": "", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/type.d.ts", + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./dist/core.d.ts" + }, + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "files": [ + "lib" + ], + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "cross-env NODE_ENV=--experimental-vm-modules jest --config=jest.config.js", + "dev": "vite", + "build": "tsc && vite build", + "api": "node ./api.js", + "tsc": "tsc", + "serve": "node test/Server/server.js" + }, + "license": "MIT", + "dependencies": { + "dayjs": "^1.11.5", + "es6-proxy-polyfill": "^2.1.1", + "nanoid": "4.0.0", + "numeral": "^2.0.6", + "reflect-metadata": "^0.1.13" + }, + "devDependencies": { + "@types/jest": "^28.1.8", + "@types/node": "^18.7.12", + "@typescript-eslint/eslint-plugin": "^5.34.0", + "@typescript-eslint/parser": "^5.34.0", + "@vitejs/plugin-legacy": "^2.0.1", + "debug": "^4.3.4", + "jest": "29.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.3", + "ts-jest": "29.0.1", + "ts-node": "^10.9.1", + "typescript": "^4.7.4", + "vite": "^3.0.9", + "vite-plugin-external": "1.2.4" + } +} diff --git a/packages/core/src/Basics/Bootstrap.ts b/packages/core/src/Basics/Bootstrap.ts new file mode 100644 index 0000000000..b989b841d8 --- /dev/null +++ b/packages/core/src/Basics/Bootstrap.ts @@ -0,0 +1,35 @@ +import { CommandManager, UndoManager } from '../Command'; +import { WorkBook, WorkSheet } from '../Sheets/Domain'; +import { IOCContainer } from '../IOC'; +import { Environment } from './Environment'; +import { UniverSheet } from './UniverSheet'; +import { PluginManager } from '../Plugin'; +import { ServerHttp, ServerSocket } from '../Server'; +import { Locale } from '../Shared'; +import { Context } from './Context'; +import { HooksManager } from '../Observer/HooksManager'; +import { ObserverManager } from '../Observer/ObserverManager'; + +/** + * Adding a mapping class to a IOCContainer + * + * @param container Target container + * @returns IOCContainer for chaining. + */ +export function Bootstrap(container: IOCContainer): IOCContainer { + container.addMapping('UniverSheet', UniverSheet); + container.addSingletonMapping('Environment', Environment); + container.addSingletonMapping('Server', ServerSocket); + container.addSingletonMapping('ServerSocket', ServerSocket); + container.addSingletonMapping('ServerHttp', ServerHttp); + container.addSingletonMapping('WorkBook', WorkBook); + container.addSingletonMapping('Locale', Locale); + container.addSingletonMapping('Context', Context); + container.addSingletonMapping('UndoManager', UndoManager); + container.addSingletonMapping('CommandManager', CommandManager); + container.addSingletonMapping('WorkSheet', WorkSheet); + container.addSingletonMapping('PluginManager', PluginManager); + container.addSingletonMapping('ObserverManager', ObserverManager); + container.addSingletonMapping('ObservableHooksManager', HooksManager); + return container; +} diff --git a/packages/core/src/Basics/Context.ts b/packages/core/src/Basics/Context.ts new file mode 100644 index 0000000000..11c14f6b57 --- /dev/null +++ b/packages/core/src/Basics/Context.ts @@ -0,0 +1,217 @@ +import { CommandManager, UndoManager } from '../Command'; +import { WorkBook } from '../Sheets/Domain'; +import { Inject, PostConstruct } from '../IOC'; +import { Observable, ObservableHooks, ObserverManager } from '../Observer'; +import { HooksManager } from '../Observer/HooksManager'; +import { PluginManager } from '../Plugin'; +import { Locale, Nullable, PropsFrom } from '../Shared'; +import { Environment } from './Environment'; +import { Observer } from './Observer'; + +/** + * Core context, mount important instances, managers + */ +export class Context { + @Inject('CommandManager') + protected _commandManager: CommandManager; + + @Inject('Environment') + protected _environment: Environment; + + @Inject('HooksManager') + protected _hooksManager: HooksManager; + + @Inject('WorkBook') + protected _workBook: WorkBook; + + @Inject('UndoManager') + protected _undoManager: UndoManager; + + @Inject('Locale') + protected _locale: Locale; + + @Inject('PluginManager') + protected _pluginManager: PluginManager; + + @Inject('ObserverManager') + protected _observerManager: ObserverManager; + + protected _setWorkbookObserver(): void { + const manager = this.getObserverManager(); + + manager.addObserver( + 'onAfterSetSelectionObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onBeforeChangeNameObservable', + 'core', + new Observable() + ); + manager.addObserver('onAfterChangeNameObservable', 'core', new Observable()); + + manager.addObserver( + 'onBeforeChangeActiveSheetObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterChangeActiveSheetObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onBeforeChangeSheetNameObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterChangeSheetNameObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onBeforeInsertSheetObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterInsertSheetObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onBeforeRemoveSheetObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterRemoveSheetObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onBeforeChangeSheetColorObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterChangeSheetColorObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onBeforeChangeSheetDataObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterChangeSheetDataObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onSheetBarDidMountObservable', + 'core', + new Observable() + ); + manager.addObserver('onInfoBarDidMountObservable', 'core', new Observable()); + manager.addObserver( + 'onRightMenuDidMountObservable', + 'core', + new Observable() + ); + + manager.addObserver( + 'onAfterChangeSheetScrollObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterChangeUILocaleObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onSheetRenderDidMountObservable', + 'core', + new Observable() + ); + manager.addObserver( + 'onAfterChangeUISkinObservable', + 'core', + new Observable() + ); + manager.addObserver('onHideSheetObservable', 'core', new Observable()); + manager.addObserver('onShowSheetObservable', 'core', new Observable()); + manager.addObserver( + 'onSheetTabColorChangeObservable', + 'core', + new Observable() + ); + manager.addObserver('onSheetOrderObservable', 'core', new Observable()); + manager.addObserver('onZoomRatioSheetObservable', 'core', new Observable()); + } + + constructor() { + this._hooksManager = new HooksManager(); + } + + @PostConstruct() + initialize(): void { + this._setWorkbookObserver(); + } + + getHook(path: string): Nullable> { + return this._hooksManager.getHooks(path); + } + + getWorkBook(): WorkBook { + return this._workBook; + } + + getLocale(): Locale { + return this._locale; + } + + getContextObserver( + value: Key + ): Observable> { + return this.getObserverManager().requiredObserver(value, 'core'); + } + + getHooksManager(): HooksManager { + return this._hooksManager; + } + + getObserverManager(): ObserverManager { + return this._observerManager; + } + + getPluginManager(): PluginManager { + return this._pluginManager; + } + + getUndoManager(): UndoManager { + return this._undoManager; + } + + getCommandManager(): CommandManager { + return this._commandManager; + } +} + +export module Context { + export interface WithContext { + withContext(context: Context): T; + getContext(): Context; + } +} diff --git a/packages/core/src/Basics/Environment.ts b/packages/core/src/Basics/Environment.ts new file mode 100644 index 0000000000..d30c9fd493 --- /dev/null +++ b/packages/core/src/Basics/Environment.ts @@ -0,0 +1,22 @@ +/** + * Operating environment, equipment, platform information + */ +export class Environment { + os: string; + + app_version: string; + + device_id: string; + + platform: string; + + os_version: string; + + constructor() { + this.os = ''; + this.app_version = ''; + this.platform = ''; + this.device_id = ''; + this.os_version = ''; + } +} diff --git a/packages/core/src/Basics/Observer.ts b/packages/core/src/Basics/Observer.ts new file mode 100644 index 0000000000..bbc11dbf4d --- /dev/null +++ b/packages/core/src/Basics/Observer.ts @@ -0,0 +1,39 @@ +import { Observable } from '../Observer/Observable'; +import { WorkSheet } from '../Sheets/Domain'; + +export type Observer = { + onBeforeInsertSheetObservable: Observable<{ index: number; sheetId: string }>; + onAfterInsertSheetObservable: Observable<{ index: number; sheetId: string }>; + + onAfterSetSelectionObservable: Observable; + onAfterChangeUILocaleObservable: Observable; + + onBeforeChangeNameObservable: Observable; + onAfterChangeNameObservable: Observable; + + onSheetTabColorChangeObservable: Observable; + + onSheetOrderObservable: Observable; + + onBeforeChangeSheetColorObservable: Observable; + onAfterChangeSheetColorObservable: Observable; + + onBeforeChangeSheetDataObservable: Observable; + onAfterChangeSheetDataObservable: Observable; + + onSheetRenderDidMountObservable: Observable; + + onZoomRatioSheetObservable: Observable<{ zoomRatio: number }>; + + onHideSheetObservable: Observable<{ sheet: WorkSheet }>; + onShowSheetObservable: Observable<{ sheet: WorkSheet }>; + + onBeforeChangeActiveSheetObservable: Observable<{ sheet: WorkSheet }>; + onAfterChangeActiveSheetObservable: Observable<{ sheet: WorkSheet }>; + + onBeforeChangeSheetNameObservable: Observable; + onAfterChangeSheetNameObservable: Observable<{ name: string; sheet: WorkSheet }>; + + onBeforeRemoveSheetObservable: Observable<{ index: number }>; + onAfterRemoveSheetObservable: Observable<{ index: number; sheetId: string }>; +}; diff --git a/packages/core/src/Basics/Registry.ts b/packages/core/src/Basics/Registry.ts new file mode 100644 index 0000000000..3df14711b8 --- /dev/null +++ b/packages/core/src/Basics/Registry.ts @@ -0,0 +1,40 @@ +export class Registry { + private _data: any[] = []; + + add(dataInstance: any) { + this._data.push(dataInstance); + } + + delete(dataInstance: any) { + const index = this._data.indexOf(dataInstance); + this._data.splice(index, 1); + } + + getData() { + return this._data; + } + + static create() { + return new Registry(); + } +} + +export class RegistryAsMap { + private _data: Map = new Map(); + + add(id: string, dataInstance: any) { + this._data.set(id, dataInstance); + } + + delete(id: string) { + this._data.delete(id); + } + + getData() { + return this._data; + } + + static create() { + return new RegistryAsMap(); + } +} diff --git a/packages/core/src/Basics/Sequence.ts b/packages/core/src/Basics/Sequence.ts new file mode 100644 index 0000000000..c313ece47d --- /dev/null +++ b/packages/core/src/Basics/Sequence.ts @@ -0,0 +1,66 @@ +// 列下标 字母转数字 +export function ABCToNumber(a: string) { + if (a == null || a.length === 0) { + return NaN; + } + let str = a.toLowerCase().split(''); + let al = str.length; + let getCharNumber = (charX: string) => charX.charCodeAt(0) - 96; + let numOut = 0; + let charnum = 0; + for (let i = 0; i < al; i++) { + charnum = getCharNumber(str[i]); + numOut += charnum * 26 ** (al - i - 1); + } + // console.log(a, numOut-1); + if (numOut === 0) { + return NaN; + } + return numOut - 1; +} + +const orderA = 'A'.charCodeAt(0); +const orderZ = 'Z'.charCodeAt(0); +const order_a = 'a'.charCodeAt(0); +const order_z = 'z'.charCodeAt(0); + +// 列下标 数字转字母 +export function numberToABC(n: number) { + const len = orderZ - orderA + 1; + + let s = ''; + + while (n >= 0) { + s = String.fromCharCode((n % len) + orderA) + s; + + n = Math.floor(n / len) - 1; + } + + return s; +} + +// 重复给定的字符串(第一个参数)num次(第二个参数)。如果num不是正数,则返回一个空字符串。 +export function repeatStringNumTimes(string: string, times: number) { + let repeatedString = ''; + while (times > 0) { + repeatedString += string; + times--; + } + return repeatedString; +} + +// 列下标 数字转列表样式的字母,例如25之后表示AA BB CC,而不是AA AB AC +export function numberToListABC(n: number, uppercase = false) { + const len = orderZ - orderA + 1; + + let order = order_a; + if (uppercase) { + order = orderA; + } + + const abc = String.fromCharCode((n % len) + order); + + let times = Math.floor(n / len) + 1; + + return repeatStringNumTimes(abc, times); +} diff --git a/packages/core/src/Basics/UniverSheet.ts b/packages/core/src/Basics/UniverSheet.ts new file mode 100644 index 0000000000..28c1b99302 --- /dev/null +++ b/packages/core/src/Basics/UniverSheet.ts @@ -0,0 +1,128 @@ +import { WorkBook } from '../Sheets/Domain'; +import { IWorkbookConfig } from '../Interfaces'; +import { Inject, IOCAttribute, IOCContainer } from '../IOC'; +import { BasePlugin, Plugin } from '../Plugin'; +import { IOHttp, IOHttpConfig, Logger } from '../Shared'; +import { Bootstrap } from './Bootstrap'; +import { Context } from './Context'; +import { VersionCode, VersionEnv } from './Version'; +import { ColorBuilder } from '../Sheets/Domain/ColorBuilder'; + +/** + * Externally provided UniverSheet root instance + */ +export class UniverSheet { + static newInstance(univerSheetData = {}): UniverSheet { + Logger.capsule(VersionEnv, VersionCode, 'powered by :: universheet :: '); + const attribute = new IOCAttribute(); + attribute.setValue(univerSheetData); + const container = new IOCContainer(attribute); + Bootstrap(container); + return container.getInstance('UniverSheet'); + } + + /** + * + * Request data + * + * @example + * Get data for all tables, including core and plugin data + * + * @param config + */ + static get(config: Omit): Promise { + return IOHttp({ ...config, type: 'GET' }); + } + + /** + * Submit data + * @param config + */ + static post(config: Omit): Promise { + return IOHttp({ ...config, type: 'POST' }); + } + + /** + * Load data + * + * @example + * UniverSheet.get gets all the core and plug-in data, UniverSheet.load(univerSheetInstance,data) internally calls the load API of each plug-in to centrally load the core and plug-in data + * + * @param sheet + * @param data + */ + static load( + sheet: UniverSheet, + data: T + ) { + sheet.getWorkBook().load(data.workbookConfig); + sheet.context + .getPluginManager() + .getPlugins() + .forEach((plugin: BasePlugin) => { + plugin.load(data[`${plugin.getPluginName()}Config`]); + }); + } + + static newColor(): ColorBuilder { + return new ColorBuilder(); + } + + @Inject('Context') + private _context: Context; + + /** + * get Context + */ + get context() { + return this._context; + } + + /** + * install plugin + * + * @param plugin - install plugin + */ + installPlugin(plugin: Plugin): void { + this._context.getPluginManager().install(plugin); + } + + /** + * uninstall plugin + * + * @param name - plugin name + */ + uninstallPlugin(name: string): void { + this._context.getPluginManager().uninstall(name); + } + + /** + * get WorkBook + * + * @returns WorkBook + */ + getWorkBook(): WorkBook { + return this._context.getWorkBook(); + } + + /** + * Save data + * + * @example + * get all the core and plug-in data + * + * @param univerSheet + */ + static toJson(univerSheet: UniverSheet) { + const workbookConfig = univerSheet.getWorkBook().save(); + const pluginConfig = {}; + univerSheet.context + .getPluginManager() + .getPlugins() + .forEach((plugin: BasePlugin) => { + pluginConfig[`${plugin.getPluginName()}Config`] = plugin.save(); + }); + + return { workbookConfig, ...pluginConfig }; + } +} diff --git a/packages/core/src/Basics/Version.ts b/packages/core/src/Basics/Version.ts new file mode 100644 index 0000000000..791d6ebddb --- /dev/null +++ b/packages/core/src/Basics/Version.ts @@ -0,0 +1,9 @@ +/** + * Default env + */ +export const VersionEnv = 'dev'; + +/** + * Default version + */ +export const VersionCode = 'v3.0.0'; diff --git a/packages/core/src/Basics/index.ts b/packages/core/src/Basics/index.ts new file mode 100644 index 0000000000..8e3d73d35b --- /dev/null +++ b/packages/core/src/Basics/index.ts @@ -0,0 +1,10 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './Bootstrap'; +export * from './Context'; +export * from './Environment'; +export * from './UniverSheet'; +export * from './Version'; +export * from './Registry'; +export * from './sortRules'; +export * from './Sequence'; +// @endindex diff --git a/packages/core/src/Basics/sortRules.ts b/packages/core/src/Basics/sortRules.ts new file mode 100644 index 0000000000..431fd023ad --- /dev/null +++ b/packages/core/src/Basics/sortRules.ts @@ -0,0 +1,19 @@ +export function sortRules(oa: any, ob: any) { + if (oa.zIndex > ob.zIndex) { + return 1; + } + if (oa.zIndex === ob.zIndex) { + return 0; + } + return -1; +} + +export function sortRulesByDesc(oa: any, ob: any) { + if (oa.zIndex > ob.zIndex) { + return -1; + } + if (oa.zIndex === ob.zIndex) { + return 0; + } + return 1; +} diff --git a/packages/core/src/Collaboration/IConflictRules.ts b/packages/core/src/Collaboration/IConflictRules.ts new file mode 100644 index 0000000000..7f99078350 --- /dev/null +++ b/packages/core/src/Collaboration/IConflictRules.ts @@ -0,0 +1 @@ +export interface IConflictRules {} diff --git a/packages/core/src/Command/ActionBase.ts b/packages/core/src/Command/ActionBase.ts new file mode 100644 index 0000000000..4ef9ddf386 --- /dev/null +++ b/packages/core/src/Command/ActionBase.ts @@ -0,0 +1,104 @@ +import { WorkBook, WorkSheet } from '../Sheets/Domain'; +import { ActionObservers } from './ActionObservers'; + +/** + * Format of action data param + */ +export interface IActionData { + actionName: string; + sheetId: string; + convertor?: object[]; + rangeRef?: string; + memberId?: string; +} + +/** + * Action Operation Type + */ +export enum ActionOperation { + /** + * send obs + */ + OBSERVER_ACTION = 1, + + /** + * send server + */ + SERVER_ACTION = 2, + + /** + * push to UNDO/REDO stack + */ + UNDO_ACTION = 3, +} + +/** + * Base class for action + * + * @beta + */ +export abstract class ActionBase< + D extends IActionData, + O extends IActionData = D, + R = void +> { + protected _workbook: WorkBook; + + protected _observers: ActionObservers; + + protected _doActionData: D; + + protected _oldActionData: O; + + protected _operation: ActionOperation; + + protected constructor( + actionData: D, + workbook: WorkBook, + observers: ActionObservers + ) { + this._doActionData = actionData; + this._workbook = workbook; + this._observers = observers; + this._operation = ActionOperation.OBSERVER_ACTION; + } + + abstract redo(): void; + + abstract undo(): void; + + abstract do(): R; + + abstract validate(): boolean; + + getDoActionData() { + return this._doActionData; + } + + getOldActionDaa() { + return this._oldActionData; + } + + getWorkSheet(): WorkSheet { + const { _workbook, _doActionData } = this; + const { sheetId } = _doActionData; + return _workbook.getSheetBySheetId(sheetId) as WorkSheet; + } + + getWorkBook(): WorkBook { + return this._workbook; + } + + hasOperation(operation: ActionOperation): boolean { + return (this._operation & operation) === operation; + } + + // TODO how to use + addOperation(operation: ActionOperation) { + this._operation |= operation; + } + + removeOperation(operation: ActionOperation) { + this._operation &= ~operation; + } +} diff --git a/packages/core/src/Command/ActionObservers.ts b/packages/core/src/Command/ActionObservers.ts new file mode 100644 index 0000000000..5da7948e3b --- /dev/null +++ b/packages/core/src/Command/ActionObservers.ts @@ -0,0 +1,20 @@ +import { Observable } from '../Observer'; +import { ActionBase, IActionData } from './ActionBase'; + +export enum ActionType { + REDO, + UNDO, +} + +export interface IActionObserverProps { + type: ActionType; + data: IActionData; + action: ActionBase; +} + +/** + * Observer for action base + * + * @beta + */ +export class ActionObservers extends Observable {} diff --git a/packages/core/src/Command/Command.ts b/packages/core/src/Command/Command.ts new file mode 100644 index 0000000000..1f2da25f40 --- /dev/null +++ b/packages/core/src/Command/Command.ts @@ -0,0 +1,34 @@ +import { CommandBase } from './CommandBase'; +import { CommandManager } from './CommandManager'; +import { CommandType } from './CommandObservers'; + +/** + * Execute the undo-redo command + */ +export class Command extends CommandBase { + redo(): void { + this._actions.forEach((action) => action.redo()); + CommandManager.getCommandObservers().notifyObservers({ + type: CommandType.REDO, + actions: this._actions, + }); + } + + undo(): void { + this._actions.forEach((action) => action.undo()); + CommandManager.getCommandObservers().notifyObservers({ + type: CommandType.UNDO, + actions: this._actions, + }); + } + + invoke(): void { + CommandManager.getCommandInjectorObservers().notifyObservers( + this.getInjector() + ); + CommandManager.getCommandObservers().notifyObservers({ + type: CommandType.REDO, + actions: this._actions, + }); + } +} diff --git a/packages/core/src/Command/CommandBase.ts b/packages/core/src/Command/CommandBase.ts new file mode 100644 index 0000000000..696c9da56b --- /dev/null +++ b/packages/core/src/Command/CommandBase.ts @@ -0,0 +1,59 @@ +import { ActionBase, IActionData } from './ActionBase'; +import { WorkBook } from '../Sheets/Domain'; +import { CommandManager } from './CommandManager'; +import { CommandType } from './CommandObservers'; +import { CommandInjector } from './CommandInjectorObservers'; +import { Class, Nullable } from '../Shared'; +/** + * Manage action instances and action data + */ +export class CommandBase { + protected _actions: Array>; + + protected _workbook: WorkBook; + + constructor(workbook: WorkBook, ...list: IActionData[]) { + this._workbook = workbook; + this._actions = []; + list.forEach((data) => { + const ActionClass = CommandManager.getAction(data.actionName); + const observers = CommandManager.getActionObservers(); + const action = new ActionClass(data, this._workbook, observers); + this._actions.push(action); + }); + CommandManager.getCommandObservers().notifyObservers({ + type: CommandType.REDO, + actions: this._actions, + }); + } + + getDoData(): IActionData[] { + return this._actions.map((action) => action.getDoActionData()); + } + + getOldData(): IActionData[] { + return this._actions.map((action) => action.getOldActionDaa()); + } + + getInjector(): CommandInjector { + const commandThis = this; + return new (class implements CommandInjector { + injectAction(action: ActionBase): void { + commandThis._actions.push(action); + } + + getActions(): Array> { + return commandThis._actions.concat([]); + } + + include(action: Class): Nullable { + for (let i = 0; i < commandThis._actions.length; i++) { + if (commandThis._actions[i] instanceof action) { + return commandThis._actions[i] as T; + } + } + return null; + } + })(); + } +} diff --git a/packages/core/src/Command/CommandInjectorObservers.ts b/packages/core/src/Command/CommandInjectorObservers.ts new file mode 100644 index 0000000000..f931054fbe --- /dev/null +++ b/packages/core/src/Command/CommandInjectorObservers.ts @@ -0,0 +1,13 @@ +import { Observable } from '../Observer'; +import { Class, Nullable } from '../Shared'; +import { ActionBase, IActionData } from './ActionBase'; + +export interface CommandInjector { + injectAction(action: ActionBase): void; + + include(action: Class): Nullable; + + getActions(): Array>; +} + +export class CommandInjectorObservers extends Observable {} diff --git a/packages/core/src/Command/CommandManager.ts b/packages/core/src/Command/CommandManager.ts new file mode 100644 index 0000000000..72cece5b66 --- /dev/null +++ b/packages/core/src/Command/CommandManager.ts @@ -0,0 +1,85 @@ +import { UndoManager } from './UndoManager'; +import { Inject } from '../IOC'; +import { Command } from './Command'; +import { Class } from '../Shared'; +import { ActionBase, IActionData } from './ActionBase'; +import { ActionObservers } from './ActionObservers'; +import { WorkBook } from '../Sheets/Domain'; +import { CommandObservers } from './CommandObservers'; +import { CommandInjectorObservers } from './CommandInjectorObservers'; + +/** + * Manage command + */ +export class CommandManager { + private static _actionClass: Map>>; + + private static _commandObservers: CommandObservers; + + private static _actionObservers: ActionObservers; + + private static _commandInjectorObservers: CommandInjectorObservers; + + static staticInitialize() { + this._actionClass = new Map>>(); + this._actionObservers = new ActionObservers(); + this._commandObservers = new CommandObservers(); + this._commandInjectorObservers = new CommandInjectorObservers(); + } + + static getAction(name: string) { + return this._actionClass[name]; + } + + static getActionObservers(): ActionObservers { + return this._actionObservers; + } + + static getCommandObservers(): CommandObservers { + return this._commandObservers; + } + + static register(name: string, clazz: Class>) { + this._actionClass[name] = clazz; + } + + static getCommandInjectorObservers(): CommandInjectorObservers { + return this._commandInjectorObservers; + } + + @Inject('WorkBook') + private _workbook: WorkBook; + + @Inject('UndoManager') + private _undoManager: UndoManager; + + undo(): void { + const { _workbook, _undoManager } = this; + const server = _workbook.getServer(); + const command = _undoManager.undo(); + if (command) { + command.undo(); + server.pushMessageQueue(command.getOldData()); + } + } + + redo(): void { + const { _workbook, _undoManager } = this; + const server = _workbook.getServer(); + const command = _undoManager.redo(); + if (command) { + command.redo(); + server.pushMessageQueue(command.getDoData()); + } + } + + invoke(command: Command): void { + const { _workbook, _undoManager } = this; + const server = _workbook.getServer(); + command.invoke(); + _undoManager.push(command); + server.pushMessageQueue(command.getDoData()); + } +} + +CommandManager.staticInitialize(); diff --git a/packages/core/src/Command/CommandObservers.ts b/packages/core/src/Command/CommandObservers.ts new file mode 100644 index 0000000000..91dee100ea --- /dev/null +++ b/packages/core/src/Command/CommandObservers.ts @@ -0,0 +1,14 @@ +import { Observable } from '../Observer'; +import { ActionBase, IActionData } from './ActionBase'; + +export enum CommandType { + REDO, + UNDO, +} + +interface ICommandObserverProps { + type: CommandType; + actions: Array>; +} + +export class CommandObservers extends Observable {} diff --git a/packages/core/src/Command/RegisterAction.ts b/packages/core/src/Command/RegisterAction.ts new file mode 100644 index 0000000000..c3a1a35d2c --- /dev/null +++ b/packages/core/src/Command/RegisterAction.ts @@ -0,0 +1,131 @@ +import { ACTION_NAMES } from '../Const'; +import { + AddNamedRangeAction, + DeleteNamedRangeAction, + SetNamedRangeAction, +} from '../Module/NamedRange/Action'; +import { + AddMergeAction, + ClearRangeAction, + DeleteRangeAction, + InsertColumnDataAction, + InsertRangeAction, + InsertRowAction, + InsertRowDataAction, + RemoveColumnDataAction, + RemoveMergeAction, + RemoveRowAction, + RemoveRowDataAction, + SetBorderAction, + SetCollapseAllColumnGroupsAction, + SetCollapseAllRowGroupsAction, + SetColumnHideAction, + SetColumnShowAction, + SetColumnWidthAction, + SetFrozenColumnsAction, + SetFrozenRowsAction, + SetHiddenGridlinesAction, + SetRangeDataAction, + SetRangeFormatAction, + SetRangeFormulaAction, + SetRangeNoteAction, + SetRangeStyleAction, + SetRangeValueAction, + SetRightToLeftAction, + SetRowHeightAction, + SetRowHideAction, + SetRowShowAction, + SetSelectionActivateAction, + SetSheetOrderAction, + SetTabColorAction, + SetWorkSheetActivateAction, + SetWorkSheetHideAction, + SetWorkSheetNameAction, + SetWorkSheetStatusAction, +} from '../Sheets/Action'; +import { InsertColumnAction } from '../Sheets/Action/InsertColumnAction'; +import { InsertSheetAction } from '../Sheets/Action/InsertSheetAction'; +import { RemoveColumnAction } from '../Sheets/Action/removeColumnAction'; +import { RemoveSheetAction } from '../Sheets/Action/RemoveSheetAction'; +import { CommandManager } from './CommandManager'; + +CommandManager.register(ACTION_NAMES.INSERT_ROW_DATA_ACTION, InsertRowDataAction); +CommandManager.register(ACTION_NAMES.INSERT_ROW_ACTION, InsertRowAction); +CommandManager.register(ACTION_NAMES.REMOVE_ROW_DATA_ACTION, RemoveRowDataAction); +CommandManager.register(ACTION_NAMES.REMOVE_ROW_ACTION, RemoveRowAction); +CommandManager.register(ACTION_NAMES.SET_RANGE_STYLE_ACTION, SetRangeStyleAction); +CommandManager.register( + ACTION_NAMES.SET_WORKSHEET_STATUS_ACTION, + SetWorkSheetStatusAction +); +CommandManager.register( + ACTION_NAMES.SET_SELECTION_ACTION, + SetSelectionActivateAction +); +CommandManager.register(ACTION_NAMES.SET_RANGE_DATA_ACTION, SetRangeDataAction); +CommandManager.register(ACTION_NAMES.SET_RANGE_VALUE_ACTION, SetRangeValueAction); +CommandManager.register(ACTION_NAMES.DELETE_RANGE_ACTION, DeleteRangeAction); +CommandManager.register(ACTION_NAMES.CLEAR_RANGE_ACTION, ClearRangeAction); +CommandManager.register(ACTION_NAMES.INSERT_RANGE_ACTION, InsertRangeAction); +CommandManager.register(ACTION_NAMES.SET_TAB_COLOR_ACTION, SetTabColorAction); +CommandManager.register(ACTION_NAMES.HIDE_SHEET_ACTION, SetWorkSheetHideAction); +CommandManager.register( + ACTION_NAMES.SET_WORKSHEET_ACTIVATE_ACTION, + SetWorkSheetActivateAction +); +CommandManager.register(ACTION_NAMES.SET_HIDE_ROW_ACTION, SetRowHideAction); +CommandManager.register(ACTION_NAMES.SET_SHOW_ROW_ACTION, SetRowShowAction); +CommandManager.register(ACTION_NAMES.SET_HIDE_COLUMN_ACTION, SetColumnHideAction); +CommandManager.register(ACTION_NAMES.SET_SHOW_COLUMN_ACTION, SetColumnShowAction); +CommandManager.register(ACTION_NAMES.ADD_MERGE_ACTION, AddMergeAction); +CommandManager.register(ACTION_NAMES.REMOVE_MERGE_ACTION, RemoveMergeAction); +CommandManager.register( + ACTION_NAMES.SET_WORKSHEET_NAME_ACTION, + SetWorkSheetNameAction +); +CommandManager.register( + ACTION_NAMES.SET_HIDDEN_GRIDLINES_ACTION, + SetHiddenGridlinesAction +); +CommandManager.register(ACTION_NAMES.SET_COLUMN_WIDTH_ACTION, SetColumnWidthAction); +CommandManager.register(ACTION_NAMES.SET_ROW_HEIGHT_ACTION, SetRowHeightAction); +CommandManager.register(ACTION_NAMES.SET_BORDER_ACTION, SetBorderAction); +CommandManager.register( + ACTION_NAMES.SET_COLLAPSE_ALL_COLUMN_GROUPS_ACTION, + SetCollapseAllColumnGroupsAction +); +CommandManager.register( + ACTION_NAMES.SET_COLLAPSE_ALL_ROW_GROUPS_ACTION, + SetCollapseAllRowGroupsAction +); +CommandManager.register( + ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + SetFrozenColumnsAction +); +CommandManager.register(ACTION_NAMES.SET_FROZEN_ROWS_ACTION, SetFrozenRowsAction); +CommandManager.register(ACTION_NAMES.SET_RIGHT_TO_LEFT_ACTION, SetRightToLeftAction); +CommandManager.register( + ACTION_NAMES.INSERT_COLUMN_DATA_ACTION, + InsertColumnDataAction +); +CommandManager.register( + ACTION_NAMES.REMOVE_COLUMN_DATA_ACTION, + RemoveColumnDataAction +); +CommandManager.register(ACTION_NAMES.INSERT_COLUMN_ACTION, InsertColumnAction); +CommandManager.register(ACTION_NAMES.REMOVE_COLUMN_ACTION, RemoveColumnAction); +CommandManager.register( + ACTION_NAMES.DELETE_NAMED_RANGE_ACTION, + DeleteNamedRangeAction +); +CommandManager.register(ACTION_NAMES.SET_NAMED_RANGE_ACTION, SetNamedRangeAction); +CommandManager.register(ACTION_NAMES.ADD_NAMED_RANGE_ACTION, AddNamedRangeAction); +CommandManager.register(ACTION_NAMES.SET_RANGE_FORMAT_ACTION, SetRangeFormatAction); +CommandManager.register( + ACTION_NAMES.SET_RANGE_FORMULA_ACTION, + SetRangeFormulaAction +); +CommandManager.register(ACTION_NAMES.SET_RANGE_NOTE_ACTION, SetRangeNoteAction); +CommandManager.register(ACTION_NAMES.INSERT_SHEET_ACTION, InsertSheetAction); +CommandManager.register(ACTION_NAMES.REMOVE_SHEET_ACTION, RemoveSheetAction); +CommandManager.register(ACTION_NAMES.SET_SHEET_ORDER_ACTION, SetSheetOrderAction); diff --git a/packages/core/src/Command/UndoManager.ts b/packages/core/src/Command/UndoManager.ts new file mode 100644 index 0000000000..6294a32e77 --- /dev/null +++ b/packages/core/src/Command/UndoManager.ts @@ -0,0 +1,35 @@ +import { Command } from './Command'; + +/** + * Manage Undo Redo + */ +export class UndoManager { + private _undoStack: Command[]; + + private _redoStack: Command[]; + + constructor() { + this._undoStack = []; + this._redoStack = []; + } + + push(command: Command): void { + this._redoStack.push(command); + } + + undo(): Command | undefined { + const command = this._redoStack.pop(); + if (command) { + this._undoStack.push(command); + return command; + } + } + + redo(): Command | undefined { + const command = this._undoStack.pop(); + if (command) { + this._redoStack.push(command); + return command; + } + } +} diff --git a/packages/core/src/Command/index.ts b/packages/core/src/Command/index.ts new file mode 100644 index 0000000000..e5c35fdf94 --- /dev/null +++ b/packages/core/src/Command/index.ts @@ -0,0 +1,16 @@ +// @index(['./*.ts','./Action/index.ts'], f => `export * from '${f.path}'`) +/** + * @internal + */ +export * from '../Sheets/Action/index'; +export * from './ActionBase'; +export * from './ActionObservers'; +export * from './Command'; +export * from './CommandBase'; +export * from './CommandManager'; +export * from './UndoManager'; + +/* eslint-disable import/first */ +import './RegisterAction'; + +// @endindex diff --git a/packages/core/src/Conflict/Conflict.ts b/packages/core/src/Conflict/Conflict.ts new file mode 100644 index 0000000000..e315a7765e --- /dev/null +++ b/packages/core/src/Conflict/Conflict.ts @@ -0,0 +1,7 @@ +import { ActionBase, IActionData } from '../Command/ActionBase'; + +export abstract class Conflict { + abstract comparison( + ...action: Array> + ): Array> | boolean; +} diff --git a/packages/core/src/Const/ACTION_NAMES.ts b/packages/core/src/Const/ACTION_NAMES.ts new file mode 100644 index 0000000000..7062a5f761 --- /dev/null +++ b/packages/core/src/Const/ACTION_NAMES.ts @@ -0,0 +1,53 @@ +/** + * Name set of all action + */ +export enum ACTION_NAMES { + /** @see src/Command/Action/InsertDataRowAction */ + INSERT_ROW_DATA_ACTION = 'InsertRowDataAction', + REMOVE_ROW_DATA_ACTION = 'RemoveRowDataAction', + INSERT_ROW_ACTION = 'InsertRowAction', + REMOVE_ROW_ACTION = 'RemoveRowAction', + SET_RANGE_STYLE_ACTION = 'SetRangeStyleAction', + SET_RANGE_FORMULA_ACTION = 'SetRangeFormulaAction', + SET_RANGE_FORMAT_ACTION = 'SetRangeFormatAction', + SET_SELECTION_ACTION = 'SetSelectionActivateAction', + SET_RANGE_DATA_ACTION = 'SetRangeDataAction', + SET_RANGE_VALUE_ACTION = 'SetRangeValueAction', + SET_RANGE_NOTE_ACTION = 'SetRangeNoteAction', + DELETE_RANGE_ACTION = 'DeleteRangeAction', + INSERT_SHEET_ACTION = 'InsertSheetAction', + REMOVE_SHEET_ACTION = 'RemoveSheetAction', + CLEAR_RANGE_ACTION = 'ClearRangeAction', + INSERT_RANGE_ACTION = 'InsertRangeAction', + SET_TAB_COLOR_ACTION = 'SetTabColorAction', + HIDE_SHEET_ACTION = 'SetWorkSheetHideAction', + SET_WORKSHEET_ACTIVATE_ACTION = 'SetWorkSheetActivateAction', + INSERT_COLUMN_ACTION = 'InsertColumnAction', + INSERT_COLUMN_DATA_ACTION = 'InsertColumnDataAction', + REMOVE_COLUMN_ACTION = 'RemoveColumnAction', + REMOVE_COLUMN_DATA_ACTION = 'RemoveColumnDataAction', + SET_HIDE_ROW_ACTION = 'SetRowHideAction', + SET_HIDE_COLUMN_ACTION = 'SetColumnHideAction', + SET_SHOW_ROW_ACTION = 'SetRowShowAction', + SET_SHOW_COLUMN_ACTION = 'SetColumnShowAction', + ADD_MERGE_ACTION = 'AddMergeAction', + REMOVE_MERGE_ACTION = 'RemoveMergeAction', + SET_WORKSHEET_NAME_ACTION = 'SetWorkSheetNameAction', + SET_WORKSHEET_STATUS_ACTION = 'SetWorkSheetStatusAction', + SET_BORDER_ACTION = 'SetBorderAction', + SET_COLUMN_WIDTH_ACTION = 'SetColumnWidthAction', + SET_ROW_HEIGHT_ACTION = 'SetRowHeightAction', + SET_HIDDEN_GRIDLINES_ACTION = 'SetHiddenGridlinesAction', + SET_FROZEN_COLUMNS_ACTION = 'SetFrozenColumnsAction', + SET_FROZEN_ROWS_ACTION = 'SetFrozenRowsAction', + SET_RIGHT_TO_LEFT_ACTION = 'SetRightToLeftAction', + SET_COLLAPSE_ALL_ROW_GROUPS_ACTION = 'SetCollapseAllRowGroupsAction', + SET_COLLAPSE_ALL_COLUMN_GROUPS_ACTION = 'SetCollapseAllColumnGroupsAction', + DELETE_BANDING_ACTION = 'DeleteBandingAction', + SET_BANDING_ACTION = 'SetBandingAction', + ADD_BANDING_ACTION = 'AddBandingAction', + DELETE_NAMED_RANGE_ACTION = 'DeleteNamedRangeAction', + SET_NAMED_RANGE_ACTION = 'SetNamedRangeAction', + ADD_NAMED_RANGE_ACTION = 'AddNamedRangeAction', + SET_SHEET_ORDER_ACTION = 'SetSheetOrderAction', +} diff --git a/packages/core/src/Const/CONST.ts b/packages/core/src/Const/CONST.ts new file mode 100644 index 0000000000..1f6cea37ba --- /dev/null +++ b/packages/core/src/Const/CONST.ts @@ -0,0 +1,248 @@ +import { + BooleanNumber, + HorizontalAlign, + LocaleType, + SheetTypes, + TextDirection, + VerticalAlign, + WrapStrategy, +} from '../Enum'; +import { IStyleData, IWorkbookConfig, IWorksheetConfig } from '../Interfaces'; + +/** + * Used as an illegal range array return value + */ +export const DEFAULT_RANGE_ARRAY = { + sheetId: '', + rangeData: { + startRow: -1, + endRow: -1, + startColumn: -1, + endColumn: -1, + }, +}; + +/** + * Used as an illegal range return value + */ +export const DEFAULT_RANGE = { + startRow: -1, + startColumn: -1, + endRow: -1, + endColumn: -1, +}; + +/** + * Used as an init selection return value + */ +export const DEFAULT_SELECTION = { + startRow: 0, + startColumn: 0, + endRow: 0, + endColumn: 0, +}; + +/** + * Used as an init workbook return value + */ +export const DEFAULT_WORKBOOK: IWorkbookConfig = { + extensions: [], + id: '', + sheetOrder: [], + socketEnable: BooleanNumber.FALSE, + socketUrl: '', + name: '', + timeZone: '', + appVersion: '', + theme: '', + skin: '', + locale: LocaleType.EN, + creator: '', + styles: {}, + sheets: {}, + lastModifiedBy: '', + createdTime: '', + modifiedTime: '', + pluginMeta: {}, + namedRanges: [], +}; + +/** + * Used as an init worksheet return value + */ +export const DEFAULT_WORKSHEET: IWorksheetConfig = { + type: SheetTypes.GRID, + name: '', + id: '', + tabColor: '', + hidden: BooleanNumber.FALSE, + freezeColumn: -1, + rowCount: 1000, + columnCount: 100, + freezeRow: -1, + zoomRatio: 1, + scrollTop: 0, + scrollLeft: 0, + defaultColumnWidth: 73, + defaultRowHeight: 19, + mergeData: [], + hideRow: [], + hideColumn: [], + cellData: {}, + rowData: {}, + columnData: {}, + status: BooleanNumber.FALSE, + showGridlines: BooleanNumber.TRUE, + rowTitle: { + width: 46, + hidden: BooleanNumber.FALSE, + }, + columnTitle: { + height: 20, + hidden: BooleanNumber.FALSE, + }, + selections: ['A1'], + rightToLeft: BooleanNumber.FALSE, + pluginMeta: {}, +}; + +/** + * Default styles + */ +export const DEFAULT_STYLES: IStyleData = { + /** + * fontFamily + */ + ff: 'Times New Roman', + /** + * fontSize + */ + fs: 14, + /** + * italic + * 0: false + * 1: true + */ + it: 0, + /** + * bold + * 0: false + * 1: true + */ + bl: 0, + /** + * underline + */ + ul: { + s: BooleanNumber.FALSE, + }, + /** + * strikethrough + */ + st: { + s: BooleanNumber.FALSE, + }, + /** + * overline + */ + ol: { + s: BooleanNumber.FALSE, + }, + /** + * textRotation + */ + tr: { + a: 0, + /** + * true : 1 + * false : 0 + */ + v: 0, + }, + /** * + * textDirection + */ + td: TextDirection.UNSPECIFIED, + /** + * color + */ + cl: { + rgb: '#000', + }, + /** + * background + */ + bg: { + rgb: '#fff', + }, + /** + * horizontalAlignment + */ + ht: HorizontalAlign.UNSPECIFIED, + /** + * verticalAlignment + */ + vt: VerticalAlign.UNSPECIFIED, + /** + * wrapStrategy + */ + tb: WrapStrategy.UNSPECIFIED, + /** + * padding + */ + pd: { + t: 0, + r: 0, + b: 0, + l: 0, + }, + /** + * format + */ + // fm: { f: 'string', t: 'string' }, +}; + +/** + * Convertor operation + */ +export const CONVERTOR_OPERATION = { + SET: 'set', + INSERT: 'insert', + REMOVE: 'remove', + CHANGE: 'change', +}; + +/** + * Random color for formula cell input + */ +export const LUCKY_COLOR = [ + '#c1232b', + '#27727b', + '#fcce10', + '#e87c25', + '#b5c334', + '#fe8463', + '#9bca63', + '#fad860', + '#f3a43b', + '#60c0dd', + '#d7504b', + '#c6e579', + '#f4e001', + '#f0805a', + '#26c0c0', + '#c12e34', + '#e6b600', + '#0098d9', + '#2b821d', + '#005eaa', + '#339ca8', + '#cda819', + '#32a487', + '#3fb1e3', + '#6be6c1', + '#626c91', + '#a0a7e6', + '#c4ebad', + '#96dee8', +]; diff --git a/packages/core/src/Const/DefaultWorkbookData.ts b/packages/core/src/Const/DefaultWorkbookData.ts new file mode 100644 index 0000000000..3fdb580132 --- /dev/null +++ b/packages/core/src/Const/DefaultWorkbookData.ts @@ -0,0 +1,514 @@ +import { + BaselineOffset, + BooleanNumber, + BorderStyleTypes, + HorizontalAlign, + LocaleType, + SheetTypes, + VerticalAlign, + WrapStrategy, +} from '../Enum'; +import { + BlockType, + IDocumentData, + IWorkbookConfig, + ParagraphElementType, +} from '../Interfaces'; +import { Tools } from '../Shared/Tools'; + +const richTextTest: IDocumentData = { + documentId: 'd', + body: { + blockElements: { + oneParagraph: { + blockId: 'oneParagraph', + st: 0, + ed: 15, + blockType: BlockType.PARAGRAPH, + paragraph: { + elements: { + oneElement: { + eId: 'oneElement', + st: 0, + ed: 15, + et: ParagraphElementType.TEXT_RUN, + tr: { + ct: '在“第1题”工作表中完成以下操作', + ts: { + fs: 12, + bg: { + rgb: 'rgb(200,0,90)', + }, + cl: { + rgb: 'rgb(255,130,0)', + }, + }, + }, + }, + smallSub: { + eId: 'twoElement', + st: 0, + ed: 15, + et: ParagraphElementType.TEXT_RUN, + tr: { + ct: '上标', + ts: { + fs: 14, + bg: { + rgb: 'rgb(2,128,2)', + }, + cl: { + rgb: 'rgb(0,1,55)', + }, + va: BaselineOffset.SUBSCRIPT, + }, + }, + }, + twoElement: { + eId: 'twoElement', + st: 0, + ed: 15, + et: ParagraphElementType.TEXT_RUN, + tr: { + ct: '日期列单元格数据验证,限制只能输入日期(介于1949年1月1日至2099年1月1日)', + ts: { + fs: 14, + bg: { + rgb: 'rgb(90,128,255)', + }, + cl: { + rgb: 'rgb(0,1,255)', + }, + }, + }, + }, + }, + elementOrder: [ + { + elementId: 'oneElement', + paragraphElementType: ParagraphElementType.TEXT_RUN, + }, + { + elementId: 'smallSub', + paragraphElementType: ParagraphElementType.TEXT_RUN, + }, + { + elementId: 'twoElement', + paragraphElementType: ParagraphElementType.TEXT_RUN, + }, + ], + }, + }, + }, + blockElementOrder: ['oneParagraph'], + }, + documentStyle: { + pageSize: { + width: Infinity, + height: Infinity, + }, + marginTop: 0, + marginBottom: 0, + marginRight: 2, + marginLeft: 2, + }, +}; + +/** + * Default workbook data + */ +export const defaultWorkbookData: IWorkbookConfig = { + id: 'workbook-01', + theme: 'default', + locale: LocaleType.EN, + creator: 'univer', + name: 'universheet', + skin: 'default', + socketUrl: '', + socketEnable: BooleanNumber.FALSE, + extensions: [], + sheetOrder: [], + pluginMeta: {}, + styles: { + '1': { + cl: { + rgb: 'blue', + }, + fs: 16, + bd: { + t: { + s: BorderStyleTypes.MEDIUM_DASHED, + cl: { + rgb: 'black', + }, + }, + l: { + s: BorderStyleTypes.MEDIUM_DASHED, + cl: { + rgb: 'black', + }, + }, + r: { + s: BorderStyleTypes.THICK, + cl: { + rgb: 'black', + }, + }, + b: { + s: BorderStyleTypes.THIN, + cl: { + rgb: 'red', + }, + }, + }, + ht: 3, + }, + '2': { + bg: { + rgb: 'rgb(200, 2, 0)', + }, + fs: 24, + }, + '3': { + ht: HorizontalAlign.RIGHT, + vt: VerticalAlign.TOP, + tb: WrapStrategy.WRAP, + // va: BaselineOffset.SUPERSCRIPT, + // cl: { + // rgb: 'rgb(0, 255, 0)', + // }, + // st: { + // s: BooleanNumber.TRUE, // show + // }, + bg: { + rgb: 'rgb(255, 255, 0)', + }, + tr: { + a: 45, + v: BooleanNumber.FALSE, + }, + bd: { + t: { + s: BorderStyleTypes.THIN, + cl: { + rgb: 'black', + }, + }, + l: { + s: BorderStyleTypes.THIN, + cl: { + rgb: 'black', + }, + }, + r: { + s: BorderStyleTypes.THIN, + cl: { + rgb: 'black', + }, + }, + b: { + s: BorderStyleTypes.THIN, + cl: { + rgb: 'red', + }, + }, + }, + }, + '4': { + bg: { + rgb: 'rgb(0, 0, 255)', + }, + }, + }, + timeZone: 'GMT+8', + createdTime: '2021-11-28 12:10:10', + modifiedTime: '2021-11-29 12:10:10', + appVersion: '3.0.0-alpha', + lastModifiedBy: 'univer', + sheets: { + 'sheet-01': { + type: SheetTypes.GRID, + id: 'sheet-01', + cellData: { + '0': { + '0': { + s: '1', + v: 1, + m: '1', + }, + '1': { + s: '1', + v: 1, + m: '1', + }, + '5': { + s: '1', + v: 8, + m: '8', + }, + '6': { + s: '2', + v: 8, + m: '8', + }, + }, + '1': { + '0': { + v: 1, + m: '1', + }, + '1': { + v: 1, + m: '1', + }, + '2': { + v: 80, + m: '80', + s: '3', + }, + '3': { + v: '富文本编辑', + m: '富文本编辑', + s: '2', + }, + }, + '2': { + '0': { + v: 1111, + m: '1111', + }, + '1': { + v: 1111, + m: '1111', + }, + '5': { + s: '1', + v: 8, + m: '8', + }, + '6': { + s: '1', + v: 8, + m: '8', + }, + '7': { + s: '1', + v: 8, + m: '8', + }, + '8': { + s: '1', + v: 8, + m: '8', + }, + }, + '3': { + '0': { + v: 1111, + m: '1111', + }, + '1': { + v: 1111, + m: '1111', + }, + }, + '10': { + '9': { + v: 1111, + s: '2', + m: '1111', + }, + }, + '13': { + '9': { + v: 1111, + s: '4', + m: '1111', + }, + }, + '17': { + '3': { + v: '做移动端开发和前端开发的人员,对 MVC、MVP、MVVM 这几个名词应该都不陌生,这是三个最常用的应用架构模式,目的都是为了将业务和视图的实现代码分离', + s: '3', + p: richTextTest, + }, + }, + '20': { + '10': { + v: '北京马拉松组委会办公室一位接电话的女士证实,的确有这项规定,她还称2014年马拉松的预登记工作将在本周启动。北京马拉松比赛将在10月19日举行。', + s: '3', + }, + '12': { + s: '4', + }, + }, + '29': { + '13': { + p: Tools.deepClone(richTextTest), + s: '4', + }, + }, + }, + name: 'sheet1', + tabColor: 'red', + hidden: BooleanNumber.FALSE, + freezeColumn: 1, + rowCount: 1000, + columnCount: 20, + freezeRow: 1, + zoomRatio: 1, + scrollTop: 200, + scrollLeft: 100, + defaultColumnWidth: 93, + defaultRowHeight: 27, + mergeData: [ + { + startRow: 0, + endRow: 1, + startColumn: 0, + endColumn: 1, + }, + { + startRow: 2, + endRow: 6, + startColumn: 5, + endColumn: 10, + }, + { + startRow: 10, + endRow: 12, + startColumn: 9, + endColumn: 12, + }, + { + startRow: 10, + endRow: 12, + startColumn: 9, + endColumn: 12, + }, + { + startRow: 17, + endRow: 21, + startColumn: 3, + endColumn: 6, + }, + { + startRow: 13, + endRow: 15, + startColumn: 9, + endColumn: 10, + }, + { + startRow: 24, + endRow: 27, + startColumn: 9, + endColumn: 10, + }, + ], + rowData: { + '3': { + h: 50, + hd: BooleanNumber.FALSE, + }, + '4': { + h: 60, + hd: BooleanNumber.FALSE, + }, + '29': { + h: 200, + hd: BooleanNumber.FALSE, + }, + }, + columnData: { + '5': { + w: 100, + hd: BooleanNumber.FALSE, + }, + '6': { + w: 200, + hd: BooleanNumber.FALSE, + }, + '13': { + w: 300, + hd: BooleanNumber.FALSE, + }, + }, + status: 1, + showGridlines: 1, + hideRow: [], + hideColumn: [], + rowTitle: { + width: 46, + hidden: BooleanNumber.FALSE, + }, + columnTitle: { + height: 20, + hidden: BooleanNumber.FALSE, + }, + selections: ['A2'], + rightToLeft: BooleanNumber.FALSE, + pluginMeta: {}, + // metaData: [], + }, + 'sheet-02': { + type: SheetTypes.GRID, + id: 'sheet-02', + name: 'sheet2', + cellData: { + '0': { + '0': { + s: '1', + v: 1, + m: '1', + }, + '1': { + s: '1', + v: 1, + m: '1', + }, + '5': { + s: '1', + v: 8, + m: '8', + }, + '6': { + s: '2', + v: 8, + m: '8', + }, + }, + '20': { + '0': { + v: 'sheet2', + m: 'sheet2', + }, + '1': { + v: 'sheet2 - 2', + m: 'sheet2 - 2', + }, + }, + }, + }, + 'sheet-03': { + type: SheetTypes.GRID, + id: 'sheet-03', + name: 'sheet3', + }, + 'sheet-04': { + type: SheetTypes.GRID, + id: 'sheet-04', + name: 'sheet4', + }, + 'sheet-05': { + type: SheetTypes.GRID, + id: 'sheet-05', + name: 'sheet5', + }, + 'sheet-06': { + type: SheetTypes.GRID, + id: 'sheet-06', + name: 'sheet6', + }, + }, + namedRanges: [], +}; diff --git a/packages/core/src/Const/EXTENSION_NAMES.ts b/packages/core/src/Const/EXTENSION_NAMES.ts new file mode 100644 index 0000000000..ed5d6c508b --- /dev/null +++ b/packages/core/src/Const/EXTENSION_NAMES.ts @@ -0,0 +1,7 @@ +/** + * Set of extension names + */ +export enum EXTENSION_NAMES { + ARRAY_CONVERTOR = 'ARRAY_CONVERTOR', + MATRIX_CONVERTOR = 'MATRIX_CONVERTOR', +} diff --git a/packages/core/src/Const/KEYBOARD_KEYS.ts b/packages/core/src/Const/KEYBOARD_KEYS.ts new file mode 100644 index 0000000000..8fdbecd0f6 --- /dev/null +++ b/packages/core/src/Const/KEYBOARD_KEYS.ts @@ -0,0 +1,338 @@ +/** + * + * Reference of non-printable Keyboard Event.key values + * + * reference:https://www.aarongreenlee.com/blog/list-of-non-printable-keys-for-keyboard-events-when-using-event-key/ + */ +export const KEYBOARD_KEYS: { [key: string]: string[] } = { + modifier: [ + 'Alt', //The Alt (Alternative) key. This key enables the alternate modifier function for interpreting concurrent or subsequent keyboard input. .This key value is also used for the Apple Option key. + 'AltGraph', //The Alternate Graphics (AltGr or AltGraph) key. This key is used enable the ISO Level 3 shift modifier (the standard Shift key is the level 2 modifier). See [ISO9995-1]. + 'CapsLock', //The Caps Lock (Capital) key. Toggle capital character lock function for interpreting subsequent keyboard input event. + 'Control', //The Control or Ctrl key, to enable control modifier function for interpreting concurrent or subsequent keyboard input. + 'Fn', //The Function switch Fn key. Activating this key simultaneously with another key changes that key’s value to an alternate character or function. This key is often handled directly in the keyboard hardware and does not usually generate key events. + 'FnLock', //The Function-Lock (FnLock or F-Lock) key. Activating this key switches the mode of the keyboard to changes some keys' values to an alternate character or function. This key is often handled directly in the keyboard hardware and does not usually generate key events. + 'Meta', //The Meta key, to enable meta modifier function for interpreting concurrent or subsequent keyboard input. This key value is used for the Windows Logo key and the Apple Command or ⌘ key. + 'NumLock', //The NumLock or Number Lock key, to toggle numpad mode function for interpreting subsequent keyboard input. + 'ScrollLock', //The Scroll Lock key, to toggle between scrolling and cursor movement modes. + 'Shift', //The Shift key, to enable shift modifier function for interpreting concurrent or subsequent keyboard input. + 'Symbol', //The Symbol modifier key (used on some virtual keyboards). + 'SymbolLock', //The Symbol Lock key. + ], + legacyModifier: [ + 'Hyper', //The Hyper key. + 'Super', //The Super key. + ], + whiteSpace: [ + 'Enter', //The Enter or ↵ key, to activate current selection or accept current input. This key value is also used for the Return (Macintosh numpad) key. This key value is also used for the Android KEYCODE_DPAD_CENTER. + 'Tab', //The Horizontal Tabulation Tab key. The space or spacebar key is encoded as " ". + ], + navigation: [ + 'ArrowDown', //The down arrow key, to navigate or traverse downward. (KEYCODE_DPAD_DOWN) + 'ArrowLeft', //The left arrow key, to navigate or traverse leftward. (KEYCODE_DPAD_LEFT) + 'ArrowRight', //The right arrow key, to navigate or traverse rightward. (KEYCODE_DPAD_RIGHT) + 'ArrowUp', //The up arrow key, to navigate or traverse upward. (KEYCODE_DPAD_UP) + 'End', //The End key, used with keyboard entry to go to the end of content (KEYCODE_MOVE_END). + 'Home', //The Home key, used with keyboard entry, to go to start of content (KEYCODE_MOVE_HOME). For the mobile phone Home key (which goes to the phone’s main screen), use "GoHome". + 'PageDown', //The Page Down key, to scroll down or display next page of content. + 'PageUp', //The Page Up key, to scroll up or display previous page of content. + ], + editing: [ + 'Backspace', //The Backspace key. This key value is also used for the key labeled Delete on MacOS keyboards. + 'Clear', //Remove the currently selected input. + 'Copy', //Copy the current selection. (APPCOMMAND_COPY) + 'CrSel', //The Cursor Select (Crsel) key. + 'Cut', //Cut the current selection. (APPCOMMAND_CUT) + 'Delete', //The Delete (Del) Key. This key value is also used for the key labeled Delete on MacOS keyboards when modified by the Fn key. + 'EraseEof', //The Erase to End of Field key. This key deletes all characters from the current cursor position to the end of the current field. + 'ExSel', //The Extend Selection (Exsel) key. + 'Insert', //The Insert (Ins) key, to toggle between text modes for insertion or overtyping. (KEYCODE_INSERT) + 'Paste', //The Paste key. (APPCOMMAND_PASTE) + 'Redo', //Redo the last action. (APPCOMMAND_REDO) + 'Undo', //Undo the last action. (APPCOMMAND_UNDO) + ], + ui: [ + 'Accept', //The Accept (Commit, OK) key. Accept current option or input method sequence conversion. + 'Again', //The Again key, to redo or repeat an action. + 'Attn', //The Attention (Attn) key. + 'Cancel', //The Cancel key. + 'ContextMenu', //Show the application’s context menu. This key is commonly found between the right Meta key and the right Control key. + 'Escape', //The Esc key. This key was originally used to initiate an escape sequence, but is now more generally used to exit or "escape" the current context, such as closing a dialog or exiting full screen mode. + 'Execute', //The Execute key. + 'Find', //Open the Find dialog. (APPCOMMAND_FIND) + 'Help', //Open a help dialog or toggle display of help information. (APPCOMMAND_HELP, KEYCODE_HELP) + 'Pause', //Pause the current state or application (as appropriate). Do not use this value for the Pause button on media controllers. Use "MediaPause" instead. + 'Play', //Play or resume the current state or application (as appropriate). Do not use this value for the Play button on media controllers. Use "MediaPlay" instead. + 'Props', //The properties (Props) key. + 'Select', //The Select key. + 'ZoomIn', //The ZoomIn key. (KEYCODE_ZOOM_IN) + 'ZoomOut', //The ZoomOut key. (KEYCODE_ZOOM_OUT) + ], + device: [ + 'BrightnessDown', //The Brightness Down key. Typically controls the display brightness. (KEYCODE_BRIGHTNESS_DOWN) + 'BrightnessUp', //The Brightness Up key. Typically controls the display brightness. (KEYCODE_BRIGHTNESS_UP) + 'Eject', //Toggle removable media to eject (open) and insert (close) state. (KEYCODE_MEDIA_EJECT) + 'LogOff', //The LogOff key. + 'Power', //Toggle power state. (KEYCODE_POWER) Note: Some devices might not expose this key to the operating environment. + 'PowerOff', //The PowerOff key. Sometime called PowerDown. + 'PrintScreen', //The Print Screen or SnapShot key, to initiate print-screen function. + 'Hibernate', //The Hibernate key. This key saves the current state of the computer to disk so that it can be restored. The computer will then shutdown. + 'Standby', //The Standby key. This key turns off the display and places the computer into a low-power mode without completely shutting down. It is sometimes labelled Suspend or Sleep key. (KEYCODE_SLEEP) + 'WakeUp', //The WakeUp key. (KEYCODE_WAKEUP) + ], + imeCompositionKeys: [ + 'AllCandidates', //The All Candidates key, to initate the multi-candidate mode. + 'Alphanumeric', //The Alphanumeric key. + 'CodeInput', //The Code Input key, to initiate the Code Input mode to allow characters to be entered by their code points. + 'Compose', //The Compose key, also known as Multi_key on the X Window System. This key acts in a manner similar to a dead key, triggering a mode where subsequent key presses are combined to produce a different character. + 'Convert', //The Convert key, to convert the current input method sequence. + 'Dead', //A dead key combining key. It may be any combining key from any keyboard layout. For example, on a PC/AT French keyboard, using a French mapping and without any modifier activiated, this is the key value U+0302 COMBINING CIRCUMFLEX ACCENT. In another layout this might be a different unicode combining key. For applications that need to differentiate between specific combining characters, the associated compositionupdate event’s data attribute provides the specific key value. + 'FinalMode', //The Final Mode Final key used on some Asian keyboards, to enable the final mode for IMEs. + 'GroupFirst', //Switch to the first character group. (ISO/IEC 9995) + 'GroupLast', //Switch to the last character group. (ISO/IEC 9995) + 'GroupNext', //Switch to the next character group. (ISO/IEC 9995) + 'GroupPrevious', //Switch to the previous character group. (ISO/IEC 9995) + 'ModeChange', //The Mode Change key, to toggle between or cycle through input modes of IMEs. + 'NextCandidate', //The Next Candidate function key. + 'NonConvert', //The NonConvert ("Don’t Convert") key, to accept current input method sequence without conversion in IMEs. + 'PreviousCandidate', //The Previous Candidate function key. + 'Process', //The Process key. + 'SingleCandidate', //The Single Candidate function key. + ], + koreanSpecific: [ + 'HangulMode', //The Hangul (Korean characters) Mode key, to toggle between Hangul and English modes. + 'HanjaMode', //The Hanja (Korean characters) Mode key. + 'JunjaMode', //The Junja (Korean characters) Mode key. + ], + japaneseSpecific: [ + 'Eisu', //The Eisu key. This key may close the IME, but its purpose is defined by the current IME. (KEYCODE_EISU) + 'Hankaku', //The (Half-Width) Characters key. + 'Hiragana', //The Hiragana (Japanese Kana characters) key. + 'HiraganaKatakana', //The Hiragana/Katakana toggle key. (KEYCODE_KATAKANA_HIRAGANA) + 'KanaMode', //The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from romaji mode). + 'KanjiMode', //The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is typically used to switch to a hiragana keyboard for the purpose of converting input into kanji. (KEYCODE_KANA) + 'Katakana', //The Katakana (Japanese Kana characters) key. + 'Romaji', //The Roman characters function key. + 'Zenkaku', //The Zenkaku (Full-Width) Characters key. + 'ZenkakuHankaku', //The Zenkaku/Hankaku (full-width/half-width) toggle key. (KEYCODE_ZENKAKU_HANKAKU) + ], + // The exact number of these general purpose function keys varies on different platforms, and only the first few are defined explicitly here. Additional function key names are implicitly defined by incrementing the base-10 index at the end of the function key name. Thus, "F24" and "Soft8" are all valid key values. + commonFunction: [ + 'F1', //The F1 key, a general purpose function key, as index 1. + 'F2', //The F2 key, a general purpose function key, as index 2. + 'F3', //The F3 key, a general purpose function key, as index 3. + 'F4', //The F4 key, a general purpose function key, as index 4. + 'F5', //The F5 key, a general purpose function key, as index 5. + 'F6', //The F6 key, a general purpose function key, as index 6. + 'F7', //The F7 key, a general purpose function key, as index 7. + 'F8', //The F8 key, a general purpose function key, as index 8. + 'F9', //The F9 key, a general purpose function key, as index 9. + 'F10', //The F10 key, a general purpose function key, as index 10. + 'F11', //The F11 key, a general purpose function key, as index 11. + 'F12', //The F12 key, a general purpose function key, as index 12. + 'Soft1', //General purpose virtual function key, as index 1. + 'Soft2', //General purpose virtual function key, as index 2. + 'Soft3', //General purpose virtual function key, as index 3. + 'Soft4', //General purpose virtual function key, as index 4. + ], + // These are extra keys found on "multimedia" keyboards. + multimedia: [ + 'ChannelDown', //Select next (numerically or logically) lower channel. (APPCOMMAND_MEDIA_CHANNEL_DOWN, KEYCODE_CHANNEL_DOWN) + 'ChannelUp', //Select next (numerically or logically) higher channel. (APPCOMMAND_MEDIA_CHANNEL_UP, KEYCODE_CHANNEL_UP) + 'Close', //Close the current document or message (Note: This doesn’t close the application). (APPCOMMAND_CLOSE) + 'MailForward', //Open an editor to forward the current message. (APPCOMMAND_FORWARD_MAIL) + 'MailReply', //Open an editor to reply to the current message. (APPCOMMAND_REPLY_TO_MAIL) + 'MailSend', //Send the current message. (APPCOMMAND_SEND_MAIL) + 'MediaClose', //Close the current media, for example to close a CD or DVD tray. (KEYCODE_MEDIA_CLOSE) + 'MediaFastForward', //Initiate or continue forward playback at faster than normal speed, or increase speed if already fast forwarding. (APPCOMMAND_MEDIA_FAST_FORWARD, KEYCODE_MEDIA_FAST_FORWARD) + 'MediaPause', //Pause the currently playing media. (APPCOMMAND_MEDIA_PAUSE, KEYCODE_MEDIA_PAUSE) Media controller devices should use this value rather than "Pause" for their pause keys. + 'MediaPlay', //Initiate or continue media playback at normal speed, if not currently playing at normal speed. (APPCOMMAND_MEDIA_PLAY, KEYCODE_MEDIA_PLAY) + 'MediaPlayPause', //Toggle media between play and pause states. (APPCOMMAND_MEDIA_PLAY_PAUSE, KEYCODE_MEDIA_PLAY_PAUSE) + 'MediaRecord', //Initiate or resume recording of currently selected media. (APPCOMMAND_MEDIA_RECORD, KEYCODE_MEDIA_RECORD) + 'MediaRewind', //Initiate or continue reverse playback at faster than normal speed, or increase speed if already rewinding. (APPCOMMAND_MEDIA_REWIND, KEYCODE_MEDIA_REWIND) + 'MediaStop', //Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. (APPCOMMAND_MEDIA_STOP, KEYCODE_MEDIA_STOP) + 'MediaTrackNext', //Seek to next media or program track. (APPCOMMAND_MEDIA_NEXTTRACK, KEYCODE_MEDIA_NEXT) + 'MediaTrackPrevious', //Seek to previous media or program track. (APPCOMMAND_MEDIA_PREVIOUSTRACK, KEYCODE_MEDIA_PREVIOUS) + 'New', //Open a new document or message. (APPCOMMAND_NEW) + 'Open', //Open an existing document or message. (APPCOMMAND_OPEN) + 'Print', //Print the current document or message. (APPCOMMAND_PRINT) + 'Save', //Save the current document or message. (APPCOMMAND_SAVE) + 'SpellCheck', //Spellcheck the current document or selection. (APPCOMMAND_SPELL_CHECK) + ], + // The normal 0 ... 9 numpad keys are encoded as "0" ... "9", but some multimedia keypads have buttons numbered from 1 ... 12. In these instances, the 10 key is often labeled 10 /0. Note: The 10 or 10 /0 key MUST be assigned a key value of "0". + multimediaNumpad: [ + 'Key11', //The 11 key found on media numpads that have buttons from 1 ... 12. + 'Key12', //The 12 key found on media numpads that have buttons from 1 ... 12. + ], + // Multimedia keys related to audio. + audio: [ + 'AudioBalanceLeft', //Adjust audio balance leftward. (VK_AUDIO_BALANCE_LEFT) + 'AudioBalanceRight', //Adjust audio balance rightward. (VK_AUDIO_BALANCE_RIGHT) + 'AudioBassBoostDown', //Decrease audio bass boost or cycle down through bass boost states. (APPCOMMAND_BASS_DOWN, VK_BASS_BOOST_DOWN) + 'AudioBassBoostToggle', //Toggle bass boost on/off. (APPCOMMAND_BASS_BOOST) + 'AudioBassBoostUp', //Increase audio bass boost or cycle up through bass boost states. (APPCOMMAND_BASS_UP, VK_BASS_BOOST_UP) + 'AudioFaderFront', //Adjust audio fader towards front. (VK_FADER_FRONT) + 'AudioFaderRear', //Adjust audio fader towards rear. (VK_FADER_REAR) + 'AudioSurroundModeNext', //Advance surround audio mode to next available mode. (VK_SURROUND_MODE_NEXT) + 'AudioTrebleDown', //Decrease treble. (APPCOMMAND_TREBLE_DOWN) + 'AudioTrebleUp', //Increase treble. (APPCOMMAND_TREBLE_UP) + 'AudioVolumeDown', //Decrease audio volume. (APPCOMMAND_VOLUME_DOWN, KEYCODE_VOLUME_DOWN) + 'AudioVolumeUp', //Increase audio volume. (APPCOMMAND_VOLUME_UP, KEYCODE_VOLUME_UP) + 'AudioVolumeMute', //Toggle between muted state and prior volume level. (APPCOMMAND_VOLUME_MUTE, KEYCODE_VOLUME_MUTE) + 'MicrophoneToggle', //Toggle the microphone on/off. (APPCOMMAND_MIC_ON_OFF_TOGGLE) + 'MicrophoneVolumeDown', //Decrease microphone volume. (APPCOMMAND_MICROPHONE_VOLUME_DOWN) + 'MicrophoneVolumeUp', //Increase microphone volume. (APPCOMMAND_MICROPHONE_VOLUME_UP) + 'MicrophoneVolumeMute', //Mute the microphone. (APPCOMMAND_MICROPHONE_VOLUME_MUTE, KEYCODE_MUTE) + ], + // Multimedia keys related to speech recognition. + speech: [ + 'SpeechCorrectionList', //Show correction list when a word is incorrectly identified. (APPCOMMAND_CORRECTION_LIST) + 'SpeechInputToggle', //Toggle between dictation mode and command/control mode. (APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE) + ], + // The Application Keys are special keys that are assigned to launch a particular application. Additional application key names can be defined by concatenating "Launch" with the name of the application. + application: [ + 'LaunchApplication1', //The first generic "LaunchApplication" key. This is commonly associated with launching "My Computer", and may have a computer symbol on the key. (APPCOMMAND_LAUNCH_APP1) + 'LaunchApplication2', //The second generic "LaunchApplication" key. This is commonly associated with launching "Calculator", and may have a calculator symbol on the key. (APPCOMMAND_LAUNCH_APP2, KEYCODE_CALCULATOR) + 'LaunchCalendar', //The "Calendar" key. (KEYCODE_CALENDAR) + 'LaunchContacts', //The "Contacts" key. (KEYCODE_CONTACTS) + 'LaunchMail', //The "Mail" key. (APPCOMMAND_LAUNCH_MAIL) + 'LaunchMediaPlayer', //The "Media Player" key. (APPCOMMAND_LAUNCH_MEDIA_SELECT) + 'LaunchMusicPlayer', //The "Music Player" key. + 'LaunchPhone', //The "Phone" key. + 'LaunchScreenSaver', //The "Screen Saver" key. + 'LaunchSpreadsheet', //The "Sheets" key. + 'LaunchWebBrowser', //The "Web Browser" key. + 'LaunchWebCam', //The "WebCam" key. + 'LaunchWordProcessor', //The "Word Processor" key. + ], + browser: [ + 'BrowserBack', //Navigate to previous content or page in current history. (APPCOMMAND_BROWSER_BACKWARD) + 'BrowserFavorites', //Open the list of browser favorites. (APPCOMMAND_BROWSER_FAVORITES) + 'BrowserForward', //Navigate to next content or page in current history. (APPCOMMAND_BROWSER_FORWARD) + 'BrowserHome', //Go to the user’s preferred home page. (APPCOMMAND_BROWSER_HOME) + 'BrowserRefresh', //Refresh the current page or content. (APPCOMMAND_BROWSER_REFRESH) + 'BrowserSearch', //Call up the user’s preferred search page. (APPCOMMAND_BROWSER_SEARCH) + 'BrowserStop', //Stop loading the current page or content. (APPCOMMAND_BROWSER_STOP) + ], + mobilePhone: [ + 'AppSwitch', //The Application switch key, which provides a list of recent apps to switch between. (KEYCODE_APP_SWITCH) + 'Call', //The Call key. (KEYCODE_CALL) + 'Camera', //The Camera key. (KEYCODE_CAMERA) + 'CameraFocus', //The Camera focus key. (KEYCODE_FOCUS) + 'EndCall', //The End Call key. (KEYCODE_ENDCALL) + 'GoBack', //The Back key. (KEYCODE_BACK) + 'GoHome', //The Home key, which goes to the phone’s main screen. (KEYCODE_HOME) + 'HeadsetHook', //The Headset Hook key. (KEYCODE_HEADSETHOOK) + 'LastNumberRedial', //The Last Number Redial key. + 'Notification', //The Notification key. (KEYCODE_NOTIFICATION) + 'MannerMode', //Toggle between manner mode state: silent, vibrate, ring, ... (KEYCODE_MANNER_MODE) + 'VoiceDial', //The Voice Dial key. + ], + tv: [ + 'TV', //Switch to viewing TV. (KEYCODE_TV) + 'TV3DMode', //TV 3D Mode. (KEYCODE_3D_MODE) + 'TVAntennaCable', //Toggle between antenna and cable input. (KEYCODE_TV_ANTENNA_CABLE) + 'TVAudioDescription', //Audio description. (KEYCODE_TV_AUDIO_DESCRIPTION) + 'TVAudioDescriptionMixDown', //Audio description mixing volume down. (KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN) + 'TVAudioDescriptionMixUp', //Audio description mixing volume up. (KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP) + 'TVContentsMenu', //Contents menu. (KEYCODE_TV_CONTENTS_MENU) + 'TVDataService', //Contents menu. (KEYCODE_TV_DATA_SERVICE) + 'TVInput', //Switch the input mode on an external TV. (KEYCODE_TV_INPUT) + 'TVInputComponent1', //Switch to component input #1. (KEYCODE_TV_INPUT_COMPONENT_1) + 'TVInputComponent2', //Switch to component input #2. (KEYCODE_TV_INPUT_COMPONENT_2) + 'TVInputComposite1', //Switch to composite input #1. (KEYCODE_TV_INPUT_COMPOSITE_1) + 'TVInputComposite2', //Switch to composite input #2. (KEYCODE_TV_INPUT_COMPOSITE_2) + 'TVInputHDMI1', //Switch to HDMI input #1. (KEYCODE_TV_INPUT_HDMI_1) + 'TVInputHDMI2', //Switch to HDMI input #2. (KEYCODE_TV_INPUT_HDMI_2) + 'TVInputHDMI3', //Switch to HDMI input #3. (KEYCODE_TV_INPUT_HDMI_3) + 'TVInputHDMI4', //Switch to HDMI input #4. (KEYCODE_TV_INPUT_HDMI_4) + 'TVInputVGA1', //Switch to VGA input #1. (KEYCODE_TV_INPUT_VGA_1) + 'TVMediaContext', //Media context menu. (KEYCODE_TV_MEDIA_CONTEXT_MENU) + 'TVNetwork', //Toggle network. (KEYCODE_TV_NETWORK) + 'TVNumberEntry', //Number entry. (KEYCODE_TV_NUMBER_ENTRY) + 'TVPower', //Toggle the power on an external TV. (KEYCODE_TV_POWER) + 'TVRadioService', //Radio. (KEYCODE_TV_RADIO_SERVICE) + 'TVSatellite', //Satellite. (KEYCODE_TV_SATELLITE) + 'TVSatelliteBS', //Broadcast Satellite. (KEYCODE_TV_SATELLITE_BS) + 'TVSatelliteCS', //Communication Satellite. (KEYCODE_TV_SATELLITE_CS) + 'TVSatelliteToggle', //Toggle between available satellites. (KEYCODE_TV_SATELLITE_SERVICE) + 'TVTerrestrialAnalog', //Analog Terrestrial. (KEYCODE_TV_TERRESTRIAL_ANALOG) + 'TVTerrestrialDigital', //Digital Terrestrial. (KEYCODE_TV_TERRESTRIAL_DIGITAL) + 'TVTimer', //Timer programming. (KEYCODE_TV_TIMER_PROGRAMMING) + ], + // The key attribute values for media controllers (e.g. remote controls for television, audio systems, and set-top boxes) are derived in part from the consumer electronics technical specifications: + mediaControls: [ + 'AVRInput', //Switch the input mode on an external AVR (audio/video receiver). (KEYCODE_AVR_INPUT) + 'AVRPower', //Toggle the power on an external AVR (audio/video receiver). (KEYCODE_AVR_POWER) + 'ColorF0Red', //General purpose color-coded media function key, as index 0 (red). (VK_COLORED_KEY_0, KEYCODE_PROG_RED) + 'ColorF1Green', //General purpose color-coded media function key, as index 1 (green). (VK_COLORED_KEY_1, KEYCODE_PROG_GREEN) + 'ColorF2Yellow', //General purpose color-coded media function key, as index 2 (yellow). (VK_COLORED_KEY_2, KEYCODE_PROG_YELLOW) + 'ColorF3Blue', //General purpose color-coded media function key, as index 3 (blue). (VK_COLORED_KEY_3, KEYCODE_PROG_BLUE) + 'ColorF4Grey', //General purpose color-coded media function key, as index 4 (grey). (VK_COLORED_KEY_4) + 'ColorF5Brown', //General purpose color-coded media function key, as index 5 (brown). (VK_COLORED_KEY_5) + 'ClosedCaptionToggle', //Toggle the display of Closed Captions. (VK_CC, KEYCODE_CAPTIONS) + 'Dimmer', //Adjust brightness of device, by toggling between or cycling through states. (VK_DIMMER) + 'DisplaySwap', //Swap video sources. (VK_DISPLAY_SWAP) + 'DVR', //Select Digital Video Rrecorder. (KEYCODE_DVR) + 'Exit', //Exit the current application. (VK_EXIT) + 'FavoriteClear0', //Clear program or content stored as favorite 0. (VK_CLEAR_FAVORITE_0) + 'FavoriteClear1', //Clear program or content stored as favorite 1. (VK_CLEAR_FAVORITE_1) + 'FavoriteClear2', //Clear program or content stored as favorite 2. (VK_CLEAR_FAVORITE_2) + 'FavoriteClear3', //Clear program or content stored as favorite 3. (VK_CLEAR_FAVORITE_3) + 'FavoriteRecall0', //Select (recall) program or content stored as favorite 0. (VK_RECALL_FAVORITE_0) + 'FavoriteRecall1', //Select (recall) program or content stored as favorite 1. (VK_RECALL_FAVORITE_1) + 'FavoriteRecall2', //Select (recall) program or content stored as favorite 2. (VK_RECALL_FAVORITE_2) + 'FavoriteRecall3', //Select (recall) program or content stored as favorite 3. (VK_RECALL_FAVORITE_3) + 'FavoriteStore0', //Store current program or content as favorite 0. (VK_STORE_FAVORITE_0) + 'FavoriteStore1', //Store current program or content as favorite 1. (VK_STORE_FAVORITE_1) + 'FavoriteStore2', //Store current program or content as favorite 2. (VK_STORE_FAVORITE_2) + 'FavoriteStore3', //Store current program or content as favorite 3. (VK_STORE_FAVORITE_3) + 'Guide', //Toggle display of program or content guide. (VK_GUIDE, KEYCODE_GUIDE) + 'GuideNextDay', //If guide is active and displayed, then display next day’s content. (VK_NEXT_DAY) + 'GuidePreviousDay', //If guide is active and displayed, then display previous day’s content. (VK_PREV_DAY) + 'Info', //Toggle display of information about currently selected context or media. (VK_INFO, KEYCODE_INFO) + 'InstantReplay', //Toggle instant replay. (VK_INSTANT_REPLAY) + 'Link', //Launch linked content, if available and appropriate. (VK_LINK) + 'ListProgram', //List the current program. (VK_LIST) + 'LiveContent', //Toggle display listing of currently available live content or programs. (VK_LIVE) + 'Lock', //Lock or unlock current content or program. (VK_LOCK) + 'MediaApps', //Show a list of media applications: audio/video players and image viewers. (VK_APPS) Do not confuse this key value with the Windows' VK_APPS / VK_CONTEXT_MENU key, which is encoded as "ContextMenu". + 'MediaAudioTrack', //Audio track key. (KEYCODE_MEDIA_AUDIO_TRACK) + 'MediaLast', //Select previously selected channel or media. (VK_LAST, KEYCODE_LAST_CHANNEL) + 'MediaSkipBackward', //Skip backward to next content or program. (KEYCODE_MEDIA_SKIP_BACKWARD) + 'MediaSkipForward', //Skip forward to next content or program. (VK_SKIP, KEYCODE_MEDIA_SKIP_FORWARD) + 'MediaStepBackward', //Step backward to next content or program. (KEYCODE_MEDIA_STEP_BACKWARD) + 'MediaStepForward', //Step forward to next content or program. (KEYCODE_MEDIA_STEP_FORWARD) + 'MediaTopMenu', //Media top menu. (KEYCODE_MEDIA_TOP_MENU) + 'NavigateIn', //Navigate in. (KEYCODE_NAVIGATE_IN) + 'NavigateNext', //Navigate to next key. (KEYCODE_NAVIGATE_NEXT) + 'NavigateOut', //Navigate out. (KEYCODE_NAVIGATE_OUT) + 'NavigatePrevious', //Navigate to previous key. (KEYCODE_NAVIGATE_PREVIOUS) + 'NextFavoriteChannel', //Cycle to next favorite channel (in favorites list). (VK_NEXT_FAVORITE_CHANNEL) + 'NextUserProfile', //Cycle to next user profile (if there are multiple user profiles). (VK_USER) + 'OnDemand', //Access on-demand content or programs. (VK_ON_DEMAND) + 'Pairing', //Pairing key to pair devices. (KEYCODE_PAIRING) + 'PinPDown', //Move picture-in-picture window down. (VK_PINP_DOWN) + 'PinPMove', //Move picture-in-picture window. (VK_PINP_MOVE) + 'PinPToggle', //Toggle display of picture-in-picture window. (VK_PINP_TOGGLE) + 'PinPUp', //Move picture-in-picture window up. (VK_PINP_UP) + 'PlaySpeedDown', //Decrease media playback speed. (VK_PLAY_SPEED_DOWN) + 'PlaySpeedReset', //Reset playback to normal speed. (VK_PLAY_SPEED_RESET) + 'PlaySpeedUp', //Increase media playback speed. (VK_PLAY_SPEED_UP) + 'RandomToggle', //Toggle random media or content shuffle mode. (VK_RANDOM_TOGGLE) + 'RcLowBattery', //Not a physical key, but this key code is sent when the remote control battery is low. (VK_RC_LOW_BATTERY) + 'RecordSpeedNext', //Toggle or cycle between media recording speeds. (VK_RECORD_SPEED_NEXT) + 'RfBypass', //Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). (VK_RF_BYPASS) + 'ScanChannelsToggle', //Toggle scan channels mode. (VK_SCAN_CHANNELS_TOGGLE) + 'ScreenModeNext', //Advance display screen mode to next available mode. (VK_SCREEN_MODE_NEXT) + 'Settings', //Toggle display of device settings screen. (VK_SETTINGS, KEYCODE_SETTINGS) + 'SplitScreenToggle', //Toggle split screen mode. (VK_SPLIT_SCREEN_TOGGLE) + 'STBInput', //Switch the input mode on an external STB (set top box). (KEYCODE_STB_INPUT) + 'STBPower', //Toggle the power on an external STB (set top box). (KEYCODE_STB_POWER) + 'Subtitle', //Toggle display of subtitles, if available. (VK_SUBTITLE) + 'Teletext', //Toggle display of teletext, if available (VK_TELETEXT, KEYCODE_TV_TELETEXT). + 'VideoModeNext', //Advance video mode to next available mode. (VK_VIDEO_MODE_NEXT) + 'Wink', //Cause device to identify itself in some manner, e.g., audibly or visibly. (VK_WINK) + 'ZoomToggle', //Toggle between full-screen and scaled content, or alter magnification level. (VK_ZOOM, KEYCODE_TV_ZOOM_MODE) + ], +}; diff --git a/packages/core/src/Const/PLUGIN_NAMES.ts b/packages/core/src/Const/PLUGIN_NAMES.ts new file mode 100644 index 0000000000..aada6c5df2 --- /dev/null +++ b/packages/core/src/Const/PLUGIN_NAMES.ts @@ -0,0 +1,13 @@ +/** + * Names of Univer Plugin + */ +export enum PLUGIN_NAMES { + // base- + BASE_COMPONENT = 'baseComponent', + BASE_RENDER = 'renderEngine', + + // office + SPREADSHEET = 'spreadsheet', + DOCUMENT = 'document', + SLIDE = 'slide', +} diff --git a/packages/core/src/Const/THEME_COLOR_MAP.ts b/packages/core/src/Const/THEME_COLOR_MAP.ts new file mode 100644 index 0000000000..83487bbbae --- /dev/null +++ b/packages/core/src/Const/THEME_COLOR_MAP.ts @@ -0,0 +1,18 @@ +import { ThemeColors, ThemeColorType } from '../Enum'; + +export const THEME_COLOR_MAP = new Map>(); + +const OFFICE = new Map(); +OFFICE.set(ThemeColorType.ACCENT1, '#4472C4'); +OFFICE.set(ThemeColorType.ACCENT2, '#ED7D31'); +OFFICE.set(ThemeColorType.ACCENT3, '#A5A5A5'); +OFFICE.set(ThemeColorType.ACCENT4, '#70AD47'); +OFFICE.set(ThemeColorType.ACCENT5, '#5B9BD5'); +OFFICE.set(ThemeColorType.ACCENT6, '#70AD47'); +OFFICE.set(ThemeColorType.DARK1, '#000000'); +OFFICE.set(ThemeColorType.DARK2, '#44546A'); +OFFICE.set(ThemeColorType.LIGHT1, '#FFFFFF'); +OFFICE.set(ThemeColorType.LIGHT2, '#E7E6E6'); +OFFICE.set(ThemeColorType.HYPERLINK, '#0563C1'); +OFFICE.set(ThemeColorType.FOLLOWED_HYPERLINK, '#954F72'); +THEME_COLOR_MAP.set(ThemeColors.OFFICE, OFFICE); diff --git a/packages/core/src/Const/index.ts b/packages/core/src/Const/index.ts new file mode 100644 index 0000000000..8b36c36974 --- /dev/null +++ b/packages/core/src/Const/index.ts @@ -0,0 +1,9 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './ACTION_NAMES'; +export * from './CONST'; +export * from './DefaultWorkbookData'; +export * from './EXTENSION_NAMES'; +export * from './KEYBOARD_KEYS'; +export * from './PLUGIN_NAMES'; +export * from './THEME_COLOR_MAP'; +// @endindex diff --git a/packages/core/src/Convertor/ConvertorBase.ts b/packages/core/src/Convertor/ConvertorBase.ts new file mode 100644 index 0000000000..808eeb0af5 --- /dev/null +++ b/packages/core/src/Convertor/ConvertorBase.ts @@ -0,0 +1,46 @@ +/** + * Action converter base class + */ +export abstract class ConvertorBase { + // 操作 remove / insertAndUpdate + operation: string; + + // workbook/WorkSheet + type: string; + + value?: string; + + // key/value 可选 + key?: string; + + // workbook/WorkSheet 节点中的属性 + attribute: string; + + constructor( + operation: string, + type: string, + attribute: string, + key?: string, + value?: string + ) { + this.operation = operation; + this.type = type; + this.attribute = attribute; + this.key = key; + this.value = value; + } + + getData(): object { + return this.toJSON(); + } + + toJSON(): object { + return { + operation: this.operation, + key: this.key, + value: this.value, + type: this.type, + attribute: this.attribute, + }; + } +} diff --git a/packages/core/src/Convertor/WorkBookConvertor.ts b/packages/core/src/Convertor/WorkBookConvertor.ts new file mode 100644 index 0000000000..00fcdabcb8 --- /dev/null +++ b/packages/core/src/Convertor/WorkBookConvertor.ts @@ -0,0 +1,10 @@ +import { ConvertorBase } from './ConvertorBase'; + +/** + * Data transformation methods related to workbooks + */ +export class WorkBookConvertor extends ConvertorBase { + constructor(operation: string, attribute: string, key?: string, value?: string) { + super(operation, 'workbook', attribute, key, value); + } +} diff --git a/packages/core/src/Convertor/WorkSheetConvertor.ts b/packages/core/src/Convertor/WorkSheetConvertor.ts new file mode 100644 index 0000000000..ebd77771b1 --- /dev/null +++ b/packages/core/src/Convertor/WorkSheetConvertor.ts @@ -0,0 +1,15 @@ +import { ConvertorBase } from './ConvertorBase'; + +/** + * Data conversion methods related to worksheets + */ +export class WorkSheetConvertor extends ConvertorBase { + constructor( + operation: string, + attribute: string = '', + key: string = '', + value: string = '' + ) { + super(operation, 'worksheet', attribute, key, value); + } +} diff --git a/packages/core/src/Convertor/index.ts b/packages/core/src/Convertor/index.ts new file mode 100644 index 0000000000..4928022897 --- /dev/null +++ b/packages/core/src/Convertor/index.ts @@ -0,0 +1,5 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './ConvertorBase'; +export * from './WorkBookConvertor'; +export * from './WorkSheetConvertor'; +// @endindex diff --git a/packages/core/src/Docs/index.ts b/packages/core/src/Docs/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/Enum/AutoFillSeries.ts b/packages/core/src/Enum/AutoFillSeries.ts new file mode 100644 index 0000000000..710398deb2 --- /dev/null +++ b/packages/core/src/Enum/AutoFillSeries.ts @@ -0,0 +1,9 @@ +/** + * Mode of auto fill + */ +export enum AutoFillSeries { + ALTERNATE_SERIES, + DEFAULT_SERIES, +} + +// TODO 同类型整合 diff --git a/packages/core/src/Enum/BooleanCriteria.ts b/packages/core/src/Enum/BooleanCriteria.ts new file mode 100644 index 0000000000..be39718701 --- /dev/null +++ b/packages/core/src/Enum/BooleanCriteria.ts @@ -0,0 +1,76 @@ +/** + * An enumeration representing the boolean criteria that can be used in conditional format or filter. + */ +export enum BooleanCriteria { + // The criteria is met when a cell is empty. + CELL_EMPTY = 'CELL_EMPTY', + + // The criteria is met when a cell is not empty. + CELL_NOT_EMPTY = 'CELL_NOT_EMPTY', + + // The criteria is met when a date is after the given value. + DATE_AFTER = 'DATE_AFTER', + + // The criteria is met when a date is before the given value. + DATE_BEFORE = 'DATE_BEFORE', + + // The criteria is met when a date is equal to the given value. + DATE_EQUAL_TO = 'DATE_EQUAL_TO', + + // The criteria is met when a date is not equal to the given value. + DATE_NOT_EQUAL_TO = 'DATE_NOT_EQUAL_TO', + + // The criteria is met when a date is after the relative date value. + DATE_AFTER_RELATIVE = 'DATE_AFTER_RELATIVE', + + // The criteria is met when a date is before the relative date value. + DATE_BEFORE_RELATIVE = 'DATE_BEFORE_RELATIVE', + + // The criteria is met when a date is equal to the relative date value. + DATE_EQUAL_TO_RELATIVE = 'DATE_EQUAL_TO_RELATIVE', + + // The criteria is met when a number that is between the given values. + NUMBER_BETWEEN = 'NUMBER_BETWEEN', + + // The criteria is met when a number that is equal to the given value. + NUMBER_EQUAL_TO = 'NUMBER_EQUAL_TO', + + // The criteria is met when a number that is greater than the given value. + NUMBER_GREATER_THAN = 'NUMBER_GREATER_THAN', + + // The criteria is met when a number that is greater than or equal to the given value. + NUMBER_GREATER_THAN_OR_EQUAL_TO = 'NUMBER_GREATER_THAN_OR_EQUAL_TO', + + // The criteria is met when a number that is less than the given value. + NUMBER_LESS_THAN = 'NUMBER_LESS_THAN', + + // The criteria is met when a number that is less than or equal to the given value. + NUMBER_LESS_THAN_OR_EQUAL_TO = 'NUMBER_LESS_THAN_OR_EQUAL_TO', + + // The criteria is met when a number that is not between the given values. + NUMBER_NOT_BETWEEN = 'NUMBER_NOT_BETWEEN', + + // The criteria is met when a number that is not equal to the given value. + NUMBER_NOT_EQUAL_TO = 'NUMBER_NOT_EQUAL_TO', + + // The criteria is met when the input contains the given value. + TEXT_CONTAINS = 'TEXT_CONTAINS', + + // The criteria is met when the input does not contain the given value. + TEXT_DOES_NOT_CONTAIN = 'TEXT_DOES_NOT_CONTAIN', + + // The criteria is met when the input is equal to the given value. + TEXT_EQUAL_TO = 'TEXT_EQUAL_TO', + + // The criteria is met when the input is not equal to the given value. + TEXT_NOT_EQUAL_TO = 'TEXT_NOT_EQUAL_TO', + + // The criteria is met when the input begins with the given value. + TEXT_STARTS_WITH = 'TEXT_STARTS_WITH', + + // The criteria is met when the input ends with the given value. + TEXT_ENDS_WITH = 'TEXT_ENDS_WITH', + + // The criteria is met when the input makes the given formula evaluate to true. + CUSTOM_FORMULA = 'CUSTOM_FORMULA', +} diff --git a/packages/core/src/Enum/BorderStyleTypes.ts b/packages/core/src/Enum/BorderStyleTypes.ts new file mode 100644 index 0000000000..7b2b702e18 --- /dev/null +++ b/packages/core/src/Enum/BorderStyleTypes.ts @@ -0,0 +1,32 @@ +/** + * Type set of border style + */ +export enum BorderStyleTypes { + NONE, + THIN, + HAIR, + DOTTED, + DASHED, + DASH_DOT, + DASH_DOT_DOT, + DOUBLE, + MEDIUM, + MEDIUM_DASHED, + MEDIUM_DASH_DOT, + MEDIUM_DASH_DOT_DOT, + SLANT_DASH_DOT, + THICK, +} + +export enum BorderType { + TOP = 'top', + BOTTOM = 'bottom', + LEFT = 'left', + RIGHT = 'right', + NONE = 'none', + ALL = 'all', + OUTSIDE = 'outside', + INSIDE = 'inside', + HORIZONTAL = 'horizontal', + VERTICAL = 'vertical', +} diff --git a/packages/core/src/Enum/ColorType.ts b/packages/core/src/Enum/ColorType.ts new file mode 100644 index 0000000000..03526921ad --- /dev/null +++ b/packages/core/src/Enum/ColorType.ts @@ -0,0 +1,9 @@ +/** + * Set of color types + */ +export enum ColorType { + UNSUPPORTED, + RGB, + HEX, + THEME, +} diff --git a/packages/core/src/Enum/CommonHideTypes.ts b/packages/core/src/Enum/CommonHideTypes.ts new file mode 100644 index 0000000000..853029bb9d --- /dev/null +++ b/packages/core/src/Enum/CommonHideTypes.ts @@ -0,0 +1,7 @@ +/** + * Hide type + */ +export enum CommonHideTypes { + ON, + OFF, +} diff --git a/packages/core/src/Enum/ConditionType.ts b/packages/core/src/Enum/ConditionType.ts new file mode 100644 index 0000000000..687e631b59 --- /dev/null +++ b/packages/core/src/Enum/ConditionType.ts @@ -0,0 +1,37 @@ +/** + * The type of condition. + */ +export enum ConditionType { + CONDITION_TYPE_UNSPECIFIED, // The default value, do not use. + NUMNUMBER_BETWEENR_GREATER, // The cell's value must be greater than the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue + NUMBER_GREATER_THAN_EQ, // The cell's value must be greater than or equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue . + NUMBER_LESS, // The cell's value must be less than the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue + NUMBER_LESS_THAN_EQ, // The cell's value must be less than or equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue . + NUMBER_EQ, // The cell's value must be equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. + NUMBER_NOT_EQ, // The cell's value must be not equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. + NUMBER_BETWEEN, // The cell's value must be between the two condition values. Supported by data validation, conditional formatting and filters. Requires exactly two ConditionValues . + NUMBER_NOT_BETWEEN, // The cell's value must not be between the two condition values. Supported by data validation, conditional formatting and filters. Requires exactly two ConditionValues . + TEXT_CONTAINS, // The cell's value must contain the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue . + TEXT_NOT_CONTAINS, // The cell's value must not contain the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue + TEXT_STARTS_WITH, // The cell's value must start with the condition's value. Supported by conditional formatting and filters. Requires a single ConditionValue . + TEXT_ENDS_WITH, // The cell's value must end with the condition's value. Supported by conditional formatting and filters. Requires a single ConditionValue . + TEXT_EQ, // The cell's value must be exactly the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. + TEXT_IS_EMAIL, // The cell's value must be a valid email address. Supported by data validation. Requires no ConditionValues . + TEXT_IS_URL, // The cell's value must be a valid URL. Supported by data validation. Requires no ConditionValues . + DATE_EQ, // The cell's value must be the same date as the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. + DATE_BEFORE, // The cell's value must be before the date of the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue that may be a relative date . + DATE_AFTER, // The cell's value must be after the date of the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue that may be a relative date . + DATE_ON_OR_BEFORE, // The cell's value must be on or before the date of the condition's value. Supported by data validation. Requires a single ConditionValue that may be a relative date . + DATE_ON_OR_AFTER, // The cell's value must be on or after the date of the condition's value. Supported by data validation. Requires a single ConditionValue that may be a relative date . + DATE_BETWEEN, // The cell's value must be between the dates of the two condition values. Supported by data validation. Requires exactly two ConditionValues . + DATE_NOT_BETWEEN, // The cell's value must be outside the dates of the two condition values. Supported by data validation. Requires exactly two ConditionValues . + DATE_IS_VALID, // The cell's value must be a date. Supported by data validation. Requires no ConditionValues . + ONE_OF_RANGE, // The cell's value must be listed in the grid in condition value's range. Supported by data validation. Requires a single ConditionValue , and the value must be a valid range in A1 notation. + ONE_OF_LIST, // The cell's value must be in the list of condition values. Supported by data validation. Supports any number of condition values , one per item in the list. Formulas are not supported in the values. + BLANK, // The cell's value must be empty. Supported by conditional formatting and filters. Requires no ConditionValues . + NOT_BLANK, // The cell's value must not be empty. Supported by conditional formatting and filters. Requires no ConditionValues . + CUSTOM_FORMULA, // The condition's formula must evaluate to true. Supported by data validation, conditional formatting and filters. Not supported by data source sheet filters. Requires a single ConditionValue . + BOOLEAN, // The cell's value must be TRUE/FALSE or in the list of condition values. Supported by data validation. Renders as a cell checkbox. Supports zero, one or two ConditionValues . No values indicates the cell must be TRUE or FALSE, where TRUE renders as checked and FALSE renders as unchecked. One value indicates the cell will render as checked when it contains that value and unchecked when it is blank. Two values indicate that the cell will render as checked when it contains the first value and unchecked when it contains the second value. For example, ["Yes","No"] indicates that the cell will render a checked box when it has the value "Yes" and an unchecked box when it has the value "No". + TEXT_NOT_EQ, // The cell's value must be exactly not the condition's value. Supported by filters on data source objects. Requires at least one ConditionValue . + DATE_NOT_EQ, // The cell's value must be exactly not the condition's value. Supported by filters on data source objects. Requires at least one ConditionValue . +} diff --git a/packages/core/src/Enum/CopyPasteType.ts b/packages/core/src/Enum/CopyPasteType.ts new file mode 100644 index 0000000000..31a4c701dc --- /dev/null +++ b/packages/core/src/Enum/CopyPasteType.ts @@ -0,0 +1,16 @@ +/** + * An enumeration of possible special paste types. + * + * @remarks + * Reference: https://developers.google.com/apps-script/reference/spreadsheet/copy-paste-type + */ +export enum CopyPasteType { + PASTE_NORMAL, // Paste values, formulas, formats and merges. + PASTE_NO_BORDERS, // Paste values, formulas, formats and merges but without borders. + PASTE_FORMAT, // Paste the style + PASTE_FORMULA, // Paste the formulas only. + PASTE_DATA_VALIDATION, // Paste the data validation only. + PASTE_VALUES, // Paste the values ONLY without formats, formulas or merges. + PASTE_CONDITIONAL_FORMATTING, // Paste the color rules only. + PASTE_COLUMN_WIDTHS, // Paste the column widths only. +} diff --git a/packages/core/src/Enum/DeveloperMetadataVisibility.ts b/packages/core/src/Enum/DeveloperMetadataVisibility.ts new file mode 100644 index 0000000000..2033999cfa --- /dev/null +++ b/packages/core/src/Enum/DeveloperMetadataVisibility.ts @@ -0,0 +1,7 @@ +/** + * An enumeration of the types of developer metadata visibility. + */ +export enum DeveloperMetadataVisibility { + DOCUMENT, // Document-visible metadata is accessible from any developer project with access to the document. + PROJECT, // Project-visible metadata is only visible to and accessible by the developer project that created the metadata. Do not use project-visible developer metadata as a security mechanism or to store secrets. It can be exposed to users with view access to the document. +} diff --git a/packages/core/src/Enum/Dimension.ts b/packages/core/src/Enum/Dimension.ts new file mode 100644 index 0000000000..36db6b1b12 --- /dev/null +++ b/packages/core/src/Enum/Dimension.ts @@ -0,0 +1,15 @@ +/** + * COLUMNS 0 + * + * ROWS 1 + */ +export enum Dimension { + /** + * The column (vertical) dimension. + */ + COLUMNS, + /** + * The row (horizontal) dimension. + */ + ROWS, +} diff --git a/packages/core/src/Enum/Direction.ts b/packages/core/src/Enum/Direction.ts new file mode 100644 index 0000000000..d66faee318 --- /dev/null +++ b/packages/core/src/Enum/Direction.ts @@ -0,0 +1,9 @@ +/** + * Direction: Top/Bottom/Left/Right + */ +export enum Direction { + LEFT, + RIGHT, + TOP, + BOTTOM, +} diff --git a/packages/core/src/Enum/FormatType.ts b/packages/core/src/Enum/FormatType.ts new file mode 100644 index 0000000000..717cec641e --- /dev/null +++ b/packages/core/src/Enum/FormatType.ts @@ -0,0 +1,23 @@ +/** + * The number format of the cell. + */ +export enum FormatType { + // The number format is not specified and is based on the contents of the cell. Do not explicitly use this. + NUMBER_FORMAT_TYPE_UNSPECIFIED, + // Text formatting, e.g 1000.12 + TEXT, + // Number formatting, e.g, 1,000.12 + NUMBER, + // Percent formatting, e.g 10.12% + PERCENT, + // Currency formatting, e.g $1,000.12 + CURRENCY, + // Date formatting, e.g 9/26/2008 + DATE, + // Time formatting, e.g 3:59:00 PM + TIME, + // Date+Time formatting, e.g 9/26/08 15:59:00 + DATE_TIME, + // Scientific number formatting, e.g 1.01E+03 + SCIENTIFIC, +} diff --git a/packages/core/src/Enum/GroupControlTogglePosition.ts b/packages/core/src/Enum/GroupControlTogglePosition.ts new file mode 100644 index 0000000000..60776cbd2d --- /dev/null +++ b/packages/core/src/Enum/GroupControlTogglePosition.ts @@ -0,0 +1,7 @@ +/** + * An enumeration representing the possible positions that a group control toggle can have. + */ +export enum GroupControlTogglePosition { + BEFORE, + AFTER, +} diff --git a/packages/core/src/Enum/InterpolationPointType.ts b/packages/core/src/Enum/InterpolationPointType.ts new file mode 100644 index 0000000000..20f4a33dcb --- /dev/null +++ b/packages/core/src/Enum/InterpolationPointType.ts @@ -0,0 +1,11 @@ +/** + * An enumeration representing the interpolation options for calculating a value to be used in a GradientCondition in a ConditionalFormatRule. + */ +export enum InterpolationPointType { + INTERPOLATION_POINT_TYPE_UNSPECIFIED, // The default value, do not use. + MIN, // The interpolation point uses the minimum value in the cells over the range of the conditional format. + MAX, // The interpolation point uses the maximum value in the cells over the range of the conditional format. + NUMBER, // The interpolation point uses exactly the value in InterpolationPoint.value + PERCENT, // The interpolation point is the given percentage over all the cells in the range of the conditional format. This is equivalent to NUMBER if the value was: =(MAX(FLATTEN(range)) * (value / 100)) + (MIN(FLATTEN(range)) * (1 - (value / 100))) (where errors in the range are ignored when flattening). + PERCENTILE, // The interpolation point is the given percentile over all the cells in the range of the conditional format. This is equivalent to NUMBER if the value was: =PERCENTILE(FLATTEN(range), value / 100) (where errors in the range are ignored when flattening). +} diff --git a/packages/core/src/Enum/KeyCode.ts b/packages/core/src/Enum/KeyCode.ts new file mode 100644 index 0000000000..e2f828cc78 --- /dev/null +++ b/packages/core/src/Enum/KeyCode.ts @@ -0,0 +1,44 @@ +/** + * Code of key in keyboard + */ +export enum KeyCode { + BACKSPACE = 8, + TAB = 9, + ENTER = 13, + SHIFT = 16, + CTRL = 17, + PAUSE = 19, + CAPSLOCK = 20, + ESC = 27, + + SPACE = 33, + PAGEUP = 33, + PAGEDOWN = 34, + END = 35, + HOME = 36, + LEFT = 37, + UP = 38, + RIGHT = 39, + DOWN = 40, + INSERT = 45, + DELETE = 46, + + WIN = 91, + WIN_R = 92, + MENU = 93, + + F1 = 112, + F2 = 113, + F3 = 114, + F4 = 115, + F5 = 116, + F6 = 117, + F7 = 118, + F8 = 119, + F9 = 120, + F10 = 121, + F11 = 122, + F12 = 123, + NUMLOCK = 144, + SCROLLLOCK = 145, +} diff --git a/packages/core/src/Enum/LocaleType.ts b/packages/core/src/Enum/LocaleType.ts new file mode 100644 index 0000000000..a820b34120 --- /dev/null +++ b/packages/core/src/Enum/LocaleType.ts @@ -0,0 +1,11 @@ +/** + * Internationalized enumeration information. + * + * Refer to {@link https://github.com/ladjs/i18n-locales | i18n-locales} + */ +export enum LocaleType { + EN = 'en', + ZH = 'zh', +} + +export type LocaleTypes = `${LocaleType}`; diff --git a/packages/core/src/Enum/ProtectionType.ts b/packages/core/src/Enum/ProtectionType.ts new file mode 100644 index 0000000000..9279a87482 --- /dev/null +++ b/packages/core/src/Enum/ProtectionType.ts @@ -0,0 +1,7 @@ +/** + * Type of sheet protection + */ +export enum ProtectionType { + RANGE, + SHEET, +} diff --git a/packages/core/src/Enum/RelativeDate.ts b/packages/core/src/Enum/RelativeDate.ts new file mode 100644 index 0000000000..6efb811108 --- /dev/null +++ b/packages/core/src/Enum/RelativeDate.ts @@ -0,0 +1,12 @@ +/** + * An enumeration representing the relative date options for calculating a value to be used in date-based BooleanCriteria. + */ +export enum RelativeDate { + RELATIVE_DATE_UNSPECIFIED, // Default value, do not use. + PAST_YEAR, + PAST_MONTH, + PAST_WEEK, + YESTERDAY, + TODAY, + TOMORROW, +} diff --git a/packages/core/src/Enum/SheetTypes.ts b/packages/core/src/Enum/SheetTypes.ts new file mode 100644 index 0000000000..9098802b65 --- /dev/null +++ b/packages/core/src/Enum/SheetTypes.ts @@ -0,0 +1,8 @@ +/** + * Types of sheet + */ +export enum SheetTypes { + GRID, + KANBAN, + GANTT, +} diff --git a/packages/core/src/Enum/TextStyle.ts b/packages/core/src/Enum/TextStyle.ts new file mode 100644 index 0000000000..3ecca08f69 --- /dev/null +++ b/packages/core/src/Enum/TextStyle.ts @@ -0,0 +1,128 @@ +/** + * Direction of text + */ +export enum TextDirection { + UNSPECIFIED, + LEFT_TO_RIGHT, + RIGHT_TO_LEFT, +} + +// 17.18.99 ST_Underline (Underline Patterns) +/** + * Types of text decoration + */ +export enum TextDecoration { + DASH, + DASH_DOT_DOT_HEAVY, + DASH_DOT_HEAVY, + DASHED_HEAVY, + DASH_LONG, + DASH_LONG_HEAVY, + DOT_DASH, + DOT_DOT_DASH, + DOTTED, + DOTTED_HEAVY, + DOUBLE, + NONE, + SINGLE, + THICK, + WAVE, + WAVY_DOUBLE, + WAVY_HEAVY, + WORDS, +} + +/** + * An enum that specifies the horizontal alignment of text. + */ +export enum HorizontalAlign { + UNSPECIFIED, // The horizontal alignment is not specified. Do not use this. + LEFT, // The text is explicitly aligned to the left of the cell. + CENTER, // The text is explicitly aligned to the center of the cell. + RIGHT, // The text is explicitly aligned to the right of the cell. + JUSTIFIED, // The paragraph is justified. +} + +/** + * An enum that specifies the vertical alignment of text. + */ +export enum VerticalAlign { + UNSPECIFIED, + TOP, // The text is explicitly aligned to the top of the cell. + MIDDLE, // The text is explicitly aligned to the middle of the cell. + BOTTOM, // The text is explicitly aligned to the bottom of the cell. +} + +/** + * An enumeration of the strategies used to handle cell text wrapping. + */ +export enum WrapStrategy { + UNSPECIFIED, + /** + * Lines that are longer than the cell width will be written in the next cell over, so long as that cell is empty. If the next cell over is non-empty, this behaves the same as CLIP . The text will never wrap to the next line unless the user manually inserts a new line. Example: + + + | First sentence. | + | Manual newline that is very long. <- Text continues into next cell + | Next newline. | + */ + OVERFLOW, + /** + * Lines that are longer than the cell width will be clipped. The text will never wrap to the next line unless the user manually inserts a new line. Example: + + + | First sentence. | + | Manual newline t| <- Text is clipped + | Next newline. | + */ + CLIP, + /** + * Words that are longer than a line are wrapped at the character level rather than clipped. Example: + + + | Cell has a | + | loooooooooo| <- Word is broken. + | ong word. | + */ + WRAP, +} + +/** + * FontItalic + */ +export enum FontItalic { + NORMAL, + ITALIC, +} + +/** + * FontWeight + */ +export enum FontWeight { + NORMAL, + BOLD, +} + +export enum BaselineOffset { + NORMAL, + SUBSCRIPT, + SUPERSCRIPT, +} + +/** + * General Boolean Enum + */ +export enum BooleanNumber { + FALSE, + TRUE, +} + +/** + * General Boolean Enum + */ +export enum CellValueType { + STRING, + NUMBER, + BOOLEAN, + FORCE_STRING, +} diff --git a/packages/core/src/Enum/ThemeColorType.ts b/packages/core/src/Enum/ThemeColorType.ts new file mode 100644 index 0000000000..9682db960d --- /dev/null +++ b/packages/core/src/Enum/ThemeColorType.ts @@ -0,0 +1,75 @@ +/** + * Theme color types. + * + * @remarks + * SpreadsheetProperties contain a SpreadsheetTheme that defines a mapping of these theme color types to concrete colors. + * + * Reference: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ThemeColor + * + * 17.18.97 ST_ThemeColor (Theme Color) + * + * + 2703 + 2704 + 2705 + 2706 + 2707 + 2708 + 2709 + 2710 + 2711 + 2712 + 2713 `export * from '${f.path}'`) +export * from './AutoFillSeries'; +export * from './BooleanCriteria'; +export * from './BorderStyleTypes'; +export * from './ColorType'; +export * from './CommonHideTypes'; +export * from './ConditionType'; +export * from './CopyPasteType'; +export * from './DeveloperMetadataVisibility'; +export * from './Dimension'; +export * from './Direction'; +export * from './FormatType'; +export * from './GroupControlTogglePosition'; +export * from './InterpolationPointType'; +export * from './KeyCode'; +export * from './LocaleType'; +export * from './ProtectionType'; +export * from './RelativeDate'; +export * from './SheetTypes'; +export * from './TextStyle'; +export * from './ThemeColorType'; +// @endindex diff --git a/packages/core/src/Error/Error.ts b/packages/core/src/Error/Error.ts new file mode 100644 index 0000000000..3902f14540 --- /dev/null +++ b/packages/core/src/Error/Error.ts @@ -0,0 +1,11 @@ +import { Context } from '../Basics'; + +class Error { + private readonly message: string; + + constructor(context: Context, msg: string) { + this.message = msg; + } +} + +export { Error }; diff --git a/packages/core/src/Error/NotFoundPlugin.ts b/packages/core/src/Error/NotFoundPlugin.ts new file mode 100644 index 0000000000..abe23c6b2a --- /dev/null +++ b/packages/core/src/Error/NotFoundPlugin.ts @@ -0,0 +1,8 @@ +import { Error } from './Error'; +import { Context } from '../Basics'; + +export class NotFoundPlugin extends Error { + constructor(context: Context, msg = 'plugin not found') { + super(context, msg); + } +} diff --git a/packages/core/src/Error/index.ts b/packages/core/src/Error/index.ts new file mode 100644 index 0000000000..f9dd235c24 --- /dev/null +++ b/packages/core/src/Error/index.ts @@ -0,0 +1,4 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './Error'; +export * from './NotFoundPlugin'; +// @endindex diff --git a/packages/core/src/Exception/NotFoundPlugin.ts b/packages/core/src/Exception/NotFoundPlugin.ts new file mode 100644 index 0000000000..fcde9d76ff --- /dev/null +++ b/packages/core/src/Exception/NotFoundPlugin.ts @@ -0,0 +1,6 @@ +export class NotFoundPlugin { + // TODO: need extends Exception + // constructor(context: Context, msg = 'plugin not found') { + // // super(context, msg); + // } +} diff --git a/packages/core/src/Hook/Hook.ts b/packages/core/src/Hook/Hook.ts new file mode 100644 index 0000000000..52450fc439 --- /dev/null +++ b/packages/core/src/Hook/Hook.ts @@ -0,0 +1,26 @@ +import { Nullable } from '../Shared'; + +export type HookResponse = (data: T) => R; + +export class Hook { + private _response: Nullable>; + + constructor(response: Nullable>) { + this._response = response; + } + + invoke(data: T, defaultValue: R): R { + if (this._response) { + return this._response(data); + } + return defaultValue; + } + + getResponse(): Nullable> { + return this._response; + } + + setResponse(response: HookResponse): void { + this._response = response; + } +} diff --git a/packages/core/src/Hook/WorkBookHook.ts b/packages/core/src/Hook/WorkBookHook.ts new file mode 100644 index 0000000000..01edc7518f --- /dev/null +++ b/packages/core/src/Hook/WorkBookHook.ts @@ -0,0 +1,5 @@ +import { Hook } from './Hook'; + +export class WorkSheetHook { + setSheetOrder = new Hook<{ sheetId: string; order: number }, boolean>(); +} diff --git a/packages/core/src/Hook/WorkSheetHook.ts b/packages/core/src/Hook/WorkSheetHook.ts new file mode 100644 index 0000000000..3d3b7aa04d --- /dev/null +++ b/packages/core/src/Hook/WorkSheetHook.ts @@ -0,0 +1,12 @@ +// import { Hook } from './Hook'; +// import {IWorksheetConfig} from "../Interfaces"; + +// export class WorkSheetHook { +// setNameHook = new Hook(); + +// constructor(sheet: IWorksheetConfig) { +// if (sheet.hooks.setNameHook) { +// this.setNameHook.setResponse(sheet.hooks.setNameHook); +// } +// } +// } diff --git a/packages/core/src/IOC/IOCContainer.ts b/packages/core/src/IOC/IOCContainer.ts new file mode 100644 index 0000000000..70bc8951e5 --- /dev/null +++ b/packages/core/src/IOC/IOCContainer.ts @@ -0,0 +1,508 @@ +import 'reflect-metadata'; + +/** + * IOC map class type + */ +export type IOCMapClass = { + new (...param: any): T; +}; + +function isFunction(value?: any): value is Function { + return typeof value === 'function'; +} +function isDefine(value?: T): value is T { + return value !== undefined && value !== null; +} +function unDefine(value?: any): value is undefined | null { + return value === undefined || value === null; +} +function isObject(value?: any): value is Object { + return typeof value === 'object'; +} +function isString(value?: any): value is String { + return typeof value === 'string'; +} +function isAttribute(value?: any): value is IOCAttribute { + return value instanceof IOCAttribute; +} + +/** + * Inject option + */ +export type InjectOption = InjectConfig | string; + +/** + * Register a Map of key value attributes + */ +export class AttributeMap { + static sMap = new Map(); + + static register(target: object, name: string): void { + let table = AttributeMap.sMap.get(target); + if (table) { + table.push(name); + } else { + table = new Array(); + table.push(name); + AttributeMap.sMap.set(target, table); + } + } + + static getTable(target: object): string[] { + let result = new Array(); + let parent = target; + while (parent != null) { + const table = AttributeMap.sMap.get(parent) ?? []; + result = result.concat(table); + parent = Object.getPrototypeOf(parent); + } + return result; + } +} + +/** + * Get and set the attribute of the IOC + */ +export class IOCAttribute { + private _config: AttributeConfig; + + private _value: any; + + constructor(config: AttributeConfig = {}) { + this._config = config; + this._value = config.value; + } + + getValue(): T { + return this._value; + } + + setValue(value: any) { + this._value = value; + } +} + +/** + * Register, get, inject instances + */ +export class IOCContainer { + static postDestroy(target: T): void { + if (target) { + const prototype = Object.getPrototypeOf(target); + const names = Object.getOwnPropertyNames(prototype); + for (const name of names) { + const preDestroy = Reflect.getMetadata( + 'PreDestroy', + prototype, + name + ); + if (isDefine(preDestroy)) { + const execute = prototype[name]; + if (isFunction(execute)) { + execute.apply(target); + } + } + } + } + } + + static postConstruct(target: T, argument: any[]): void { + if (target) { + const prototype = Object.getPrototypeOf(target); + const propertyNames = Object.getOwnPropertyNames(prototype); + for (const name of propertyNames) { + const postConstruct = Reflect.getMetadata( + 'PostConstruct', + prototype, + name + ); + if (isDefine(postConstruct)) { + const execute = prototype[name]; + if (isFunction(execute)) { + execute.apply(target, argument); + } + } + } + } + } + + protected _circulate: CirculateData[]; + + protected _mappings: Map; + + protected _instance: Map; + + protected _configure: Map; + + protected _attribute: IOCAttribute; + + private _printCirculate(): string { + let message = + 'The dependencies of some of the instance in the application context form a cycle:'; + message += '\n'; + this._circulate.reverse().forEach((element) => { + const { baseClass } = element; + const ImplClass: IOCMapClass = this._mappings.get(baseClass); + const { name } = ImplClass; + message += '\n'; + message += `Class: ${name} Field: ${baseClass}`; + }); + return message; + } + + private _clearCirculate(): void { + this._circulate = []; + } + + private _addCirculate(baseClass: any, scope: IOCScope): void { + this._circulate.forEach((element) => { + if (element.baseClass === baseClass) { + if (element.scope === scope) { + if (scope === IOCScope.prototype) { + this._circulate.push({ + baseClass, + scope, + }); + throw new Error(this._printCirculate()); + } + } + } + }); + this._circulate.push({ + baseClass, + scope, + }); + } + + private _popCirculate(): void { + this._circulate.pop(); + } + + private _inject(target: T, collect: any[]): void { + const prototype = Object.getPrototypeOf(target); + const table = AttributeMap.getTable(prototype); + for (const key of table) { + const container = Reflect.getMetadata('Container', prototype, key); + const injecting = Reflect.getMetadata('Inject', prototype, key); + const attribute = Reflect.getMetadata('Attribute', prototype, key); + const attributeValue = Reflect.getMetadata( + 'AttributeValue', + prototype, + key + ); + switch (typeof injecting) { + case 'string': { + const value = target; + value[key] = this._getInstance( + injecting, + collect, + IOCScope.nothing, + [] + ); + break; + } + case 'object': { + const config = injecting as InjectConfig; + const { mapping } = config; + const { scope } = config; + const { argument } = config; + const value = target; + value[key] = this._getInstance( + mapping, + collect, + scope as IOCScope, + argument as [] + ); + } + } + if (container) { + const value = target; + value[key] = this; + } + if (attribute) { + const value = target; + value[key] = this._attribute; + } + if (attributeValue) { + const value = target; + value[key] = this._attribute.getValue(); + } + } + } + + private _getInstance( + baseClass: any, + collect: any[], + scope: IOCScope, + argument: any[] + ) { + const ImplClass: IOCMapClass = this._mappings.get(baseClass); + if (ImplClass) { + if (scope === IOCScope.nothing) { + const configure = this._configure.get(baseClass); + if (configure) { + scope = configure.scope; + } else { + scope = IOCScope.prototype; + } + } + switch (scope) { + case IOCScope.singleton: { + const target = this._instance.get(baseClass); + if (isDefine(target)) { + return target; + } + const create: T = isDefine(argument) + ? new ImplClass(...argument) + : new ImplClass(); + this._instance.set(baseClass, create); + this._addCirculate(baseClass, scope); + this._inject(create, collect); + this._popCirculate(); + collect.push([create, argument]); + return create; + } + case IOCScope.prototype: { + const target: T = isDefine(argument) + ? new ImplClass(...argument) + : new ImplClass(); + this._addCirculate(baseClass, scope); + this._inject(target, collect); + this._popCirculate(); + collect.push([target, argument]); + return target; + } + } + } + } + + constructor(attribute: AttributeConfig | IOCAttribute = {}) { + this._mappings = new Map(); + this._instance = new Map(); + this._configure = new Map(); + this._attribute = isAttribute(attribute) + ? attribute + : new IOCAttribute(attribute); + this._circulate = []; + } + + inject(instance: object) { + if (instance) { + const collect: any = [[instance, []]]; + this._inject(instance, collect); + this._clearCirculate(); + for (const item of collect.reverse()) { + IOCContainer.postConstruct(item[0], item[1]); + } + } + } + + getAttribute(): IOCAttribute { + return this._attribute; + } + + getInstance(baseClass: any, ...argument: any): T { + const collect: any = []; + const instance = this._getInstance( + baseClass, + collect, + IOCScope.prototype, + argument + ); + this._clearCirculate(); + for (const item of collect.reverse()) { + IOCContainer.postConstruct(item[0], item[1]); + } + return instance; + } + + getSingleton(baseClass: any, ...argument: any): T { + const collect: any = []; + const instance = this._getInstance( + baseClass, + collect, + IOCScope.singleton, + argument + ); + this._clearCirculate(); + for (const item of collect.reverse()) { + IOCContainer.postConstruct(item[0], item[1]); + } + return instance; + } + + getBaseClassByInstance(target: any): any { + if (isObject(target)) { + const { constructor } = target; + const entries: IterableIterator = this._mappings.entries(); + for (const [key, value] of entries) { + if (constructor === value) { + return key; + } + } + } + return undefined; + } + + removeMapping(baseClass: any): void { + this._mappings.delete(baseClass); + const target = this._instance.get(baseClass); + const destroy = (instance: any) => { + const keys = Object.keys(instance); + for (const key of keys) { + const property = instance[key]; + const findClass = this.getBaseClassByInstance(property); + if (findClass) { + destroy(property); + IOCContainer.postDestroy(instance); + } + } + IOCContainer.postDestroy(instance); + }; + if (isDefine(target)) { + destroy(target); + } + this._instance.delete(baseClass); + } + + addMapping( + baseClass: any, + implClass: IOCMapClass, + config: MappingConfig | void + ): void { + this._mappings.set(baseClass, implClass); + if (isDefine(config)) { + this._configure.set(baseClass, config); + } + } + + addSingletonMapping(baseClass: any, implClass: IOCMapClass) { + return this.addMapping(baseClass, implClass, { + scope: IOCScope.singleton, + }); + } + + addClass(implClass: IOCMapClass, config: MappingConfig | void): void { + this.addMapping(implClass.name, implClass, config); + } + + removeClass(implClass: IOCMapClass): void { + this.removeMapping(implClass.name); + } + + addSingletonClass(implClass: IOCMapClass): void { + this.addSingletonMapping(implClass.name, implClass); + } +} + +/** + * Scopes of IOC + */ +export enum IOCScope { + singleton, + prototype, + nothing, +} + +/** + * Attribute config + */ +export type AttributeConfig = { + value?: any; +}; + +/** + * Inject config + */ +export type InjectConfig = { + mapping: any; + scope?: IOCScope; + argument?: any[]; +}; + +/** + * Mapping config + */ +export type MappingConfig = { + scope: IOCScope; +}; + +/** + * Circulate Data + */ +export type CirculateData = { + baseClass: any; + scope: IOCScope; +}; + +/** + * Execute function on Creation + */ +export function PostConstruct() { + return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { + AttributeMap.register(target, propertyKey); + Reflect.defineMetadata('PostConstruct', descriptor, target, propertyKey); + }; +} + +/** + * Execute function on unload + */ +export function PreDestroy() { + return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { + AttributeMap.register(target, propertyKey); + Reflect.defineMetadata('PreDestroy', descriptor, target, propertyKey); + }; +} + +/** + * Dependency injection + */ +export function Inject(option: InjectOption) { + return (target: any, propertyKey: string) => { + AttributeMap.register(target, propertyKey); + if (isString(option)) { + Reflect.defineMetadata('Inject', option, target, propertyKey); + return; + } + if (isDefine(option)) { + if (unDefine(option.argument)) { + option.argument = []; + } + if (unDefine(option.scope)) { + option.scope = IOCScope.prototype; + } + Reflect.defineMetadata('Inject', option, target, propertyKey); + } + }; +} + +/** + * Dependency injection as attribute + */ +export function Attribute() { + return (target: any, propertyKey: string) => { + AttributeMap.register(target, propertyKey); + Reflect.defineMetadata('Attribute', {}, target, propertyKey); + }; +} + +/** + * Dependency injection as container + */ +export function Container() { + return (target: any, propertyKey: string) => { + AttributeMap.register(target, propertyKey); + Reflect.defineMetadata('Container', {}, target, propertyKey); + }; +} + +/** + * Dependency injection as attribute value + */ +export function AttributeValue() { + return (target: any, propertyKey: string) => { + AttributeMap.register(target, propertyKey); + Reflect.defineMetadata('AttributeValue', {}, target, propertyKey); + }; +} diff --git a/packages/core/src/IOC/index.ts b/packages/core/src/IOC/index.ts new file mode 100644 index 0000000000..5c1a796eef --- /dev/null +++ b/packages/core/src/IOC/index.ts @@ -0,0 +1,3 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './IOCContainer'; +// @endindex diff --git a/packages/core/src/Interfaces/ICellData.ts b/packages/core/src/Interfaces/ICellData.ts new file mode 100644 index 0000000000..1f2c0eb00a --- /dev/null +++ b/packages/core/src/Interfaces/ICellData.ts @@ -0,0 +1,81 @@ +import { IDocumentData } from '.'; +import { FormatType } from '../Enum/FormatType'; +import { CellValueType } from '../Enum/TextStyle'; +import { Nullable } from '../Shared/Types'; +import { IStyleData } from './IStyleData'; + +/** + * Cell value type + */ +export type ICellV = string | number | boolean; + +/** + * 判断单元格类型, + * - 先判断 p 是否存在,存在即为 富文本 + * - 再判断 f 是否存在,存在即为 公式 + * - 再判断 s 对应 style 的 fm 格式判断单元格类型 + */ +export interface ICellData { + // The unique key, a random string, is used for the plug-in to associate the cell. When the cell information changes, the plug-in does not need to change the data, reducing the pressure on the back-end interface + // id?: string; + p?: IDocumentData; // univer docs + s?: string | Nullable; // style id + v?: Nullable; // origin value + m?: Nullable; // formatted value + // Usually the type is automatically determined based on the data, or the user directly specifies + t?: CellValueType; // 0 string, 1 number, 2 boolean, 3 force string, green icon + // f?: string; // formula '=SUM(1)' + /** + * format + */ + // fm?: IFormatData; + // TODO: plugin, move to meta data + // pluginMeta:{}, + // mt:{},// meta data // 例如 单元格图片功能,考虑存储在这 + // h?: string; // hyperlink{} + /** + * 类型type: + * 1. link + * 2. sheetId + * 3. rangeId + * 4. range + * 5. email + * + * u: "Formula!A1" // url + m: "" // message + t: "link" // type + h: 0 or 1 // hidden + */ + // n?: string; // note comment postil +} +/** + * Format of cell data + */ +export interface IFormatData { + /** + * format + */ + f: string; + /** + * 数字 n + * 字符串 g + * 时间 d + * 富文本 r + */ + t: FormatType; +} + +/** + * Cell data type + */ +export type ICellDataType = + | Nullable + | Nullable + | string + | number + | boolean; + +/** + * Cell data matrix type + */ +export type ICellDataMatrix = ICellDataType[][]; diff --git a/packages/core/src/Interfaces/IColumnData.ts b/packages/core/src/Interfaces/IColumnData.ts new file mode 100644 index 0000000000..e3bf96f52f --- /dev/null +++ b/packages/core/src/Interfaces/IColumnData.ts @@ -0,0 +1,15 @@ +import { BooleanNumber } from '../Enum'; + +/** + * Properties of column data + */ +export interface IColumnData { + /** + * width + */ + w: number; + /** + * hidden + */ + hd: BooleanNumber; +} diff --git a/packages/core/src/Interfaces/IDocumentData.ts b/packages/core/src/Interfaces/IDocumentData.ts new file mode 100644 index 0000000000..8217731af9 --- /dev/null +++ b/packages/core/src/Interfaces/IDocumentData.ts @@ -0,0 +1,774 @@ +import { IColorStyle, IStyleBase } from '.'; +import { + BooleanNumber, + HorizontalAlign, + TextDirection, + VerticalAlign, + WrapStrategy, +} from '../Enum'; + +// 注意:所有的尺寸单位,若无特殊说明,都是指pt,1 pt = 1 / 72 in +/** + * Properties of document + */ +export interface IDocumentData extends IReferenceSource { + documentId: string; + title?: string; + body?: IDocumentBody; + documentStyle: IDocumentStyle; +} + +export interface IReferenceSource { + footers?: IFooters; + headers?: IHeaders; + lists?: ILists; + drawings?: IDrawings; +} + +/** + * Set of headers + */ +export interface IHeaders { + [headerId: string]: IHeaderData; +} + +/** + * Set of footer + */ +export interface IFooters { + [footerId: string]: IFooterData; +} + +/** + * Set of lists + */ +export interface ILists { + [listId: string]: IListData; +} + +/** + * Set of Drawings + */ +export interface IDrawings { + [objectId: string]: IDrawing; +} + +/** + * A width and height. + */ +export interface ISizeData { + width?: number; + height?: number; +} + +/** + * Properties of document body + */ +export interface IDocumentBody { + blockElements: { [bId: string]: IBlockElement }; // block elements + blockElementOrder: string[]; // block element order +} + +/** + * Properties of doc footer + */ +export interface IFooterData { + footerId: string; + body: IDocumentBody; +} + +/** + * Properties of doc header + */ +export interface IHeaderData { + headerId: string; + body: IDocumentBody; +} + +/** + * The properties of a list which describe the look and feel of bullets belonging to paragraphs associated with a list. + */ +export interface IListData { + listId: string; + nestingLevel: INestingLevel[]; +} + +/** + * Contains properties describing the look and feel of a list bullet at a given level of nesting. + */ +export interface INestingLevel extends IIndentStart { + bulletAlignment: BulletAlignment; // 有序列表支持 + + // The glyph format contains one or more placeholders, and these placeholder are replaced with the appropriate values depending on the glyphType or glyphSymbol. The placeholders follow the pattern %[nestingLevel]. Furthermore, placeholders can have prefixes and suffixes. Thus, the glyph format follows the pattern %[nestingLevel]. Note that the prefix and suffix are optional and can be arbitrary strings. + // %[nestingLevel] + glyphFormat: string; // https://developers.google.com/docs/api/reference/rest/v1/documents#nestinglevel,ms word lvlText + textStyle: ITextStyle; + startNumber: number; + + // Union field glyph_kind can be only one of the following: + glyphType?: GlyphType | string; // 有序列表 string是为了支持自定义规则 https://developers.google.com/docs/api/reference/rest/v1/documents#glyphtype, ms numFmt: GlyphType | string + glyphSymbol?: string; // 无序列表的标志 + // End of list of possible types for union field glyph_kind. +} + +/** + * Follow the space after the list + */ +export enum FollowNumberWithType { + TAB, + SPACE, + NOTHING, +} + +/** + * An enumeration of the supported glyph types. + */ +export enum GlyphType { + GLYPH_TYPE_UNSPECIFIED, // The glyph type is unspecified or unsupported. + NONE, // An empty string. + DECIMAL, // A number, like 1, 2, or 3. + ZERO_DECIMAL, // A number where single digit numbers are prefixed with a zero, like 01, 02, or 03. Numbers with more than one digit are not prefixed with a zero. + UPPER_ALPHA, // An uppercase letter, like A, B, or C. + ALPHA, // A lowercase letter, like a, b, or c. + UPPER_ROMAN, // An uppercase Roman numeral, like I, II, or III. + ROMAN, // A lowercase Roman numeral, like i, ii, or iii. +} + +/** + * The types of alignment for a bullet. + */ +export enum BulletAlignment { + BULLET_ALIGNMENT_UNSPECIFIED, // The bullet alignment is unspecified. + START, // The bullet is aligned to the start of the space allotted for rendering the bullet. Left-aligned for LTR text, right-aligned otherwise. + CENTER, // The bullet is aligned to the center of the space allotted for rendering the bullet. + END, // The bullet is aligned to the end of the space allotted for rendering the bullet. Right-aligned for LTR text, left-aligned otherwise. +} + +/** + * Properties of block element + */ +export interface IBlockElement { + blockId: string; // blockId + st: number; // startIndex + ed: number; // endIndex + blockType: BlockType; // blockType + // Union field content can be only one of the following: + paragraph?: IParagraph; // paragraph + table?: ITable; // table + sectionBreak?: ISectionBreak; // SectionBreak https://support.microsoft.com/en-us/office/insert-a-section-break-eef20fd8-e38c-4ba6-a027-e503bdf8375c + tableOfContents?: IDocumentBody; // tableOfContents 目录 + customBlock?: ICustomBlock; // customBlock 用户通过插件自定义的block +} + +/** + * Type of block + */ +export enum BlockType { + PARAGRAPH, + TABLE, + SECTION_BREAK, + CUSTOM, +} + +export interface IMargin { + marginTop?: number; // marginTop + marginBottom?: number; // marginBottom + marginRight?: number; // marginRight + marginLeft?: number; // marginLeft +} + +/** + * Custom Block + */ +export interface ICustomBlock extends IMargin { + size: ISizeData; +} + +export interface IHeaderAndFooterBase { + defaultHeaderId?: string; // defaultHeaderId + defaultFooterId?: string; // defaultFooterId + evenPageHeaderId?: string; // evenPageHeaderId + evenPageFooterId?: string; // evenPageFooterId + firstPageHeaderId?: string; // firstPageHeaderId + firstPageFooterId?: string; // firstPageFooterId + useFirstPageHeaderFooter?: BooleanNumber; // useFirstPageHeaderFooter + useEvenPageHeaderFooter?: BooleanNumber; // useEvenPageHeaderFooter, +} + +/** + * Basic properties of doc style + */ +export interface IDocStyleBase extends IMargin { + pageNumberStart?: number; // pageNumberStart + pageSize?: ISizeData; // pageSize + + marginHeader?: number; // marginHeader + marginFooter?: number; // marginFooter + + renderConfig?: IDocumentRenderConfig; +} + +export interface IDocumentLayout { + // docGrid (Document Grid), open xml $17.6.5 + charSpace?: number; // charSpace + linePitch?: number; // linePitch + gridType?: GridType; // gridType + + defaultTabStop?: number; // 17.15.1.25 defaultTabStop (Distance Between Automatic Tab Stops) 默认0.5 in = 36pt,导出的情况下这个值还要转换为默认字号 + characterSpacingControl?: characterSpacingControlType; // characterSpacingControl 17.18.7 ST_CharacterSpacing (Character-Level Whitespace Compression Settings),default compressPunctuation + paragraphLineGapDefault?: number; // paragraphLineGapDefault 默认行间距 + spaceWidthEastAsian?: BooleanNumber; // add space between east asian and English +} + +export enum GridType { + // open xml $17.18.14 ST_DocGrid (Document Grid Types) + DEFAULT, + LINES, + LINES_AND_CHARS, + SNAP_TO_CHARS, +} + +export interface IDocumentStyle + extends IDocStyleBase, + IDocumentLayout, + IHeaderAndFooterBase { + textStyle?: ITextStyle; // 文字默认样式 +} + +export interface IDocumentRenderConfig { + // The following are the attributes used for secondary processing of skeleton, generally not related to word + verticalAlign?: VerticalAlign; // VerticalAlignment, only valid for pages, word does not have this arrangement, used for secondary calculation + centerAngle?: number; // Page text rotate, only valid for pages, word does not have this arrangement, it is used for secondary calculation + vertexAngle?: number; // Page text rotate, only valid for pages, word does not have this arrangement, it is used for secondary calculation + horizontalAlign?: HorizontalAlign; // HorizontalAlignment, only valid for pages, word does not have this arrangement, used for secondary calculations + isRotateNonEastAsian?: BooleanNumber; // Word is not rotate non-eastAsian + background?: IColorStyle; // background + wrapStrategy?: WrapStrategy; +} + +export interface ISectionBreakBase { + columnProperties?: ISectionColumnProperties[]; // columnProperties + columnSeparatorType?: ColumnSeparatorType; // ColumnSeparatorType + contentDirection?: TextDirection; // contentDirection + sectionType?: SectionType; // sectionType + sectionTypeNext?: SectionType; // sectionType + textDirection?: TextDirectionType; // textDirection +} + +/** + * Properties of section break + */ +export interface ISectionBreak + extends IDocStyleBase, + ISectionBreakBase, + IHeaderAndFooterBase {} + +/** + * Represents how the start of the current section is positioned relative to the previous section. + */ +export enum SectionType { + SECTION_TYPE_UNSPECIFIED, // The section type is unspecified. + CONTINUOUS, // The section starts immediately after the last paragraph of the previous section. + NEXT_PAGE, // The section starts on the next page. + EVEN_PAGE, // The section starts on the next page. + ODD_PAGE, // The section starts on the next page. +} + +/** + * The style of column separators between columns. + */ +export enum ColumnSeparatorType { + COLUMN_SEPARATOR_STYLE_UNSPECIFIED, // An unspecified column separator style. + NONE, // No column separator lines between columns. + BETWEEN_EACH_COLUMN, // Renders a column separator line between each column. +} + +/** + * Direction of text + */ +export enum TextDirectionType { + NORMAL, // Horizontal + TBRL, // Vertical + LRTBV, // Rotate Asian characters 270° +} + +/** + * Properties of section column + */ +export interface ISectionColumnProperties { + width: number; + paddingEnd: number; +} + +export interface IParagraph { + elements: { [eId: string]: IElement }; // elements + elementOrder: IElementsOrder[]; // element order + paragraphStyle?: IParagraphStyle; // paragraphStyle + bullet?: IBullet; // bullet + // dIds?: string[]; // drawingIds objectId +} + +export interface IElementsOrder { + elementId: string; // elementId + paragraphElementType: ParagraphElementType; // paragraphElementType +} + +/** + * Properties of paragraph + */ +export interface IElement { + eId: string; // elementId + st: number; // startIndex + ed: number; // endIndex + et: ParagraphElementType; // ParagraphElementType + // Union field content can be only one of the following: + tr?: ITextRun; // textRun + // dr?: IDrawing; // inlineDrawing https://developers.google.com/docs/api/reference/rest/v1/documents#InlineObjectElement + // https://developers.google.com/docs/api/reference/rest/v1/documents#AutoText + autoText?: {}; // autoText + pageBreak?: {}; // pageBreak + columnBreak?: {}; // columnBreak + footnoteReference?: {}; // footnoteReference + horizontalRule?: {}; // horizontalRule + equation?: {}; // equation + person?: {}; // person + richLink?: {}; // richLink + custom?: ICustomElement; // custom element +} + +export interface ICustomElement { + size: ISizeData; +} + +/** + * Types of paragraph element + */ +export enum ParagraphElementType { + TEXT_RUN, + AUTO_TEXT, + PAGE_BREAK, + COLUMN_BREAK, + FOOT_NOTE_REFERENCE, + HORIZONTAL_RULE, + EQUATION, + DRAWING, + PERSON, + RICH_LINK, +} + +/** + * A ParagraphElement that represents a run of text that all has the same styling. + */ +export interface ITextRun { + ct?: string; // content + ts?: ITextStyle; // textStyle + tab?: BooleanNumber; // 是否tab,默认为false +} + +/** + * Properties of list + */ +export interface IBullet { + listId: string; // listId + nestingLevel: number; // nestingLevel + textStyle: ITextStyle; // textStyle +} + +/** + * Properties of Drawing + */ +export interface IDrawing { + objectId: string; + + objectProperties: IObjectProperties; + + layoutType: PositionedObjectLayoutType; + + behindDoc?: BooleanNumber; // wrapNone + start?: number[]; // wrapPolygon + lineTo: number[][]; // wrapPolygon + wrapText?: WrapTextType; // wrapSquare | wrapThrough | wrapTight + distL?: number; // wrapSquare | wrapThrough | wrapTight + distR?: number; // wrapSquare | wrapThrough | wrapTight + distT?: number; // wrapSquare | wrapTopAndBottom + distB?: number; // wrapSquare | wrapTopAndBottom +} + +/** + * Types of wrap text + */ +export enum WrapTextType { + BOTH_SIDES, + LEFT, + RIGHT, + LARGEST, +} + +/** + * The possible layouts of a [PositionedObject] + */ +export enum PositionedObjectLayoutType { + INLINE, + WRAP_NONE, + WRAP_POLYGON, + WRAP_SQUARE, + WRAP_THROUGH, + WRAP_TIGHT, + WRAP_TOP_AND_BOTTOM, +} + +/** + * Properties of a draw object + */ +export interface IObjectProperties { + title: string; + description: string; + embeddedObjectBorder: IDocsBorder; + size: ISizeData; + positionH: ObjectPositionH; + positionV: ObjectPositionV; + angle: number; + // Union field properties can be only one of the following: + shapeProperties: IShapeProperties; + chartProperties: IChartProperties; + imageProperties: IImageProperties; +} + +/** + * ChartProperties + */ +export interface IChartProperties {} + +/** + * ShapeProperties + */ +export interface IShapeProperties {} + +/** + * Properties of Image + */ +export interface IImageProperties { + contentUri: string; + sourceUri: string; + brightness: number; + contrast: number; + transparency: number; + cropProperties: ICropProperties; + angle: number; +} + +/** + * Properties of crop image + */ +export interface ICropProperties { + offsetLeft: number; + offsetRight: number; + offsetTop: number; + offsetBottom: number; + angle: number; +} + +// export enum BulletType { +// NONE, +// ORDERED_LIST, +// UNORDERED_LIST, +// } + +/** + * Properties of text style + */ +export interface ITextStyle extends IStyleBase { + // bo?: BaselineOffset; // BaselineOffset, sup, sub + sc?: number; // spacing + pos?: number; // position + sa?: number; // scale +} +/** + * 3 选 1 + */ +export interface ILink { + url?: string; + bookmarkId?: string; // bookmarkId + headingId?: string; // headingId +} + +export interface IIndentStart { + indentFirstLine?: INumberUnit | number; // indentFirstLine,17.3.1.12 ind (Paragraph Indentation) + hanging?: INumberUnit | number; // hanging,悬挂,除第一行意外的首字偏移 + indentStart?: INumberUnit | number; // indentStart + tabStops?: ITabStop[]; // tabStops +} + +/** + * Properties of paragraph style + */ +export interface IParagraphStyle extends IIndentStart { + headingId?: string; // headingId + namedStyleType?: NamedStyleType; // namedStyleType + horizontalAlign?: HorizontalAlign; // Horizontal alignment + lineSpacing?: number; // lineSpacing 17.3.1.33 spacing (Spacing Between Lines and Above/Below Paragraph) + direction?: TextDirection; // direction + spacingRule?: SpacingRule; // SpacingRule + snapToGrid?: BooleanNumber; // snapToGrid 17.3.2.34 snapToGrid (Use Document Grid Settings For Inter-Character Spacing) + spaceAbove?: INumberUnit | number; // spaceAbove before beforeLines (Spacing Above Paragraph) + spaceBelow?: INumberUnit | number; // spaceBelow after afterLines (Spacing Below Paragraph) + borderBetween?: IParagraphBorder; // borderBetween + borderTop?: IParagraphBorder; // borderTop + borderBottom?: IParagraphBorder; // borderBottom + borderLeft?: IParagraphBorder; // borderLeft + borderRight?: IParagraphBorder; // borderRight + indentEnd?: INumberUnit | number; // indentEnd + keepLines?: BooleanNumber; // 17.3.1.14 keepLines (Keep All Lines On One Page) + keepNext?: BooleanNumber; // 17.3.1.15 keepNext (Keep Paragraph With Next Paragraph) + wordWrap?: BooleanNumber; // 17.3.1.45 wordWrap (Allow Line Breaking At Character Level) + widowControl?: BooleanNumber; // 17.3.1.44 widowControl (Allow First/Last Line to Display on a Separate Page) + shading?: IShading; // shading +} + +/** + * Types of name style + */ +export enum NamedStyleType { + NAMED_STYLE_TYPE_UNSPECIFIED, // The type of named style is unspecified. + NORMAL_TEXT, // Normal text. + TITLE, // Title. + SUBTITLE, // Subtitle. + HEADING_1, // Heading 1. + HEADING_2, // Heading 2. + HEADING_3, // Heading 3. + HEADING_4, // Heading 4. + HEADING_5, // Heading 5. + HEADING_6, // Heading 6. +} + +// export enum Alignment { +// ALIGNMENT_UNSPECIFIED, //The paragraph alignment is inherited from the parent. +// START, //The paragraph is aligned to the start of the line. Left-aligned for LTR text, right-aligned otherwise. +// CENTER, // The paragraph is centered. +// END, //The paragraph is aligned to the end of the line. Right-aligned for LTR text, left-aligned otherwise. +// JUSTIFIED, // The paragraph is justified. +// } + +export enum SpacingRule { + // 17.18.48 ST_LineSpacingRule (Line Spacing Rule) + AUTO, // Specifies that the line spacing of the parent object shall be automatically determined by the size of its contents, with no predetermined minimum or maximum size. + AT_LEAST, // Specifies that the height of the line shall be at least the value specified, but might be expanded to fit its content as needed. + EXACT, // Specifies that the height of the line shall be exactly the value specified, regardless of the size of the contents of the contents. +} + +/** + * Properties of doc border + */ +export interface IDocsBorder { + color: IColorStyle; // color + width: number; // width + dashStyle: DashStyleType; // dashStyle +} + +/** + * Properties of paragraph border + */ +export interface IParagraphBorder extends IDocsBorder { + padding: number; // padding +} + +/** + * The kinds of dashes with which linear geometry can be rendered. + */ +export enum DashStyleType { + DASH_STYLE_UNSPECIFIED, // Unspecified dash style. + SOLID, // Solid line. Corresponds to ECMA-376 ST_PresetLineDashVal value 'solid'. This is the default dash style. + DOT, // Dotted line. Corresponds to ECMA-376 ST_PresetLineDashVal value 'dot'. + DASH, // Dashed line. Corresponds to ECMA-376 ST_PresetLineDashVal value 'dash'. +} + +/** + * A tab stop within a paragraph. + */ +export interface ITabStop { + offset: number; // offset + alignment: TabStopAlignment; // alignment +} + +/** + * The alignment of the tab stop. + */ +export enum TabStopAlignment { + TAB_STOP_ALIGNMENT_UNSPECIFIED, // The tab stop alignment is unspecified. + START, // The tab stop is aligned to the start of the line. This is the default. + CENTER, // The tab stop is aligned to the center of the line. + END, // The tab stop is aligned to the end of the line. +} + +/** + * Properties of shading + */ +export interface IShading { + backgroundColor: IColorStyle; // backgroundColor +} + +/** + * Type of width + */ +export enum WidthType { + EVENLY_DISTRIBUTED = '0', + FIXED_WIDTH = '1', +} + +/** + * Properties of table + */ +export interface ITable { + rows: number; // rows + columns: number; // columns + tableRows: ITableRow[]; // tableRows + tableStyle: WidthType; // tableStyle + width: number; // width +} + +/** + * Properties of row of table + */ +export interface ITableRow { + st: number; // startIndex + ed: number; // endIndex + tableCells: ITableCell[]; // tableCells + tableRowStyle: ITableRowStyle; // tableRowStyle +} + +/** + * Properties of style table row + */ +export interface ITableRowStyle { + minRowHeight: number; // minRowHeight +} + +/** + * Properties of table cell + */ +export interface ITableCell { + st: number; // startIndex + ed: number; // endIndex + content: IBlockElement[]; // content + tableCellStyle: ITableCellStyle; // tableCellStyle +} + +/** + * Properties of style of table cell + */ +export interface ITableCellStyle { + rowSpan: number; // rowSpan + columnSpan: number; // columnSpan + backgroundColor: IColorStyle; // backgroundColor + borderLeft: ITableCellBorder; // borderLeft + borderRight: ITableCellBorder; // borderRight + borderTop: ITableCellBorder; // borderTop + borderBottom: ITableCellBorder; // borderBottom + paddingLeft: number; // paddingLeft + paddingRight: number; // paddingRight + paddingTop: number; // paddingTop + paddingBottom: number; // paddingBottom + contentAlignment: ContentAlignment; // contentAlignment +} + +/** + * Properties of cell border + */ +export interface ITableCellBorder { + color: IColorStyle; // color + width: number; // width + dashStyle: DashStyleType; // dashStyle +} +/** + * The content alignments for a Shape or TableCell. The supported alignments correspond to predefined text anchoring types from the ECMA-376 standard. + */ +export enum ContentAlignment { + CONTENT_ALIGNMENT_UNSPECIFIED, // An unspecified content alignment. The content alignment is inherited from the parent if one exists. + CONTENT_ALIGNMENT_UNSUPPORTED, // An unsupported content alignment. + TOP, // An alignment that aligns the content to the top of the content holder. Corresponds to ECMA-376 ST_TextAnchoringType 't'. + MIDDLE, // An alignment that aligns the content to the middle of the content holder. Corresponds to ECMA-376 ST_TextAnchoringType 'ctr'. + BOTTOM, // An alignment that aligns the content to the bottom of the content holder. Corresponds to ECMA-376 ST_TextAnchoringType 'b'. +} + +/** + * Types of font style + */ +export enum FontStyleType { + NORMAL = 'normal', + BOLD = 'bold', + ITALIC = 'italic', +} + +export interface INumberUnit { + v: number; // value 值 + u: NumberUnitType; // unit 单位 +} + +export interface ObjectPositionH { + relativeFrom: ObjectRelativeFromH; + // Union field properties can be only one of the following: + align?: AlignTypeH; + posOffset?: number; + percent?: number; +} + +export interface ObjectPositionV { + relativeFrom: ObjectRelativeFromV; + // Union field properties can be only one of the following: + align?: AlignTypeV; + posOffset?: number; + percent?: number; +} + +// 20.4.3.4 ST_RelFromH (Horizontal Relative Positioning) +export enum ObjectRelativeFromH { + CHARACTER, + COLUMN, + INSIDE_MARGIN, + LEFT_MARGIN, + MARGIN, + OUTSIDE_MARGIN, + PAGE, + RIGHT_MARGIN, +} + +// 20.4.3.4 ST_RelFromH (Horizontal Relative Positioning) +export enum ObjectRelativeFromV { + BOTTOM_MARGIN, + INSIDE_MARGIN, + LINE, + MARGIN, + OUTSIDE_MARGIN, + PAGE, + PARAGRAPH, + TOP_MARGIN, +} +export enum NumberUnitType { + POINT, + LINE, + CHARACTER, +} + +// 20.4.3.1 ST_AlignH (Relative Horizontal Alignment Positions) +export enum AlignTypeH { + CENTER, + INSIDE, + LEFT, + OUTSIDE, + RIGHT, +} + +// 20.4.3.2 ST_AlignV (Vertical Alignment Definition) +export enum AlignTypeV { + BOTTOM, + CENTER, + INSIDE, + OUTSIDE, + TOP, +} + +export enum characterSpacingControlType { + compressPunctuation, + compressPunctuationAndJapaneseKana, + doNotCompress, +} diff --git a/packages/core/src/Interfaces/IObserveData.ts b/packages/core/src/Interfaces/IObserveData.ts new file mode 100644 index 0000000000..72c4fd3625 --- /dev/null +++ b/packages/core/src/Interfaces/IObserveData.ts @@ -0,0 +1,9 @@ +import { WorkSheet } from '../Sheets/Domain'; +import { Observable } from '../Observer'; + +/** + * Worksheet Observable Data + */ +export type IWorksheetObservableData = { + onAfterSetSelectionObservable: Observable; +}; diff --git a/packages/core/src/Interfaces/IRangeData.ts b/packages/core/src/Interfaces/IRangeData.ts new file mode 100644 index 0000000000..559ef0e841 --- /dev/null +++ b/packages/core/src/Interfaces/IRangeData.ts @@ -0,0 +1,157 @@ +/** + * Range data structure + * + * One of the range types, + * + * e.g., + * { + * startRow:0 , + * startColumn:0, + * endRow:1, + * endColumn:1, + * } + * + * means "A1:B2" + */ +export interface IRangeData { + /** + * The start row (inclusive) of the range + * startRow + */ + startRow: number; + /** + * The start column (inclusive) of the range + * startColumn + */ + startColumn: number; + /** + * The end row (exclusive) of the range + * endRow + */ + endRow: number; + /** + * The end column (exclusive) of the range + * endColumn + */ + endColumn: number; +} + +/** + * Range data of grid + */ +export interface IGridRange { + sheetId: string; + rangeData: IRangeData; +} + +/** + * One of the range types, + * + * e.g.,"A1:B2","sheet1!A1:B2","A1","1:1","A:A","AA1:BB2" + */ +export type IRangeStringData = string; + +/** + * Row data type + */ +export type IRowStartEndData = [startRow: number, endRow: number] | number[]; + +/** + * Column data type + */ +export type IColumnStartEndData = + | [startColumn: number, endColumn: number] + | number[]; + +/** + * One of the range types, + * + * e.g., + * { + * row:[0,1], + * column:[0,1] + * } + *todo + + true false 枚举 + * means "A1:B2" + */ +export type IRangeArrayData = { + /** + * row + */ + row: IRowStartEndData; + /** + * column + */ + column: IColumnStartEndData; +}; + +/** + * range types + * + * Allow users to provide one of three formats, we need to convert to IRangeData to store + */ +export type IRangeType = IRangeData | IRangeStringData | IRangeArrayData; + +// export interface IInfoObjData extends IRangeArrayData { +// sheetId?: Nullable; +// } + +// export function isIRangeType(range: IRangeType | IRangeType[]) { +// return typeof range === 'string' || 'startRow' in range || 'row' in range; +// } +// export function isIRangeData(range: IRangeType | IRangeType[]) { +// return typeof range !== 'string' && 'startRow' in range; +// } +// export function isIRangeStringData(range: IRangeType | IRangeType[]) { +// return typeof range === 'string'; +// } +// export function isIRangeArrayData(range: IRangeType | IRangeType[]) { +// return typeof range !== 'string' && 'row' in range; +// } + +/** + * Whether to clear only the contents. Whether to clear only the format; note that clearing format also clears data validation rules. + */ +export interface IOptionData { + /** + * 1. designates that only the format should be copied + * + * 2. Whether to clear only the format; note that clearing format also clears data validation rules. + * + * 3. worksheet Whether to clear the format. + */ + formatOnly?: boolean; + /** + * 1. designates that only the content should be copied + * + * 2. Whether to clear only the contents. + * + * 3. worksheet Whether to clear the content. + * + */ + contentsOnly?: boolean; + /** + * Whether to clear only the comments. + */ +} +/** + * Whether to clear only the contents.Whether to clear only data validation rules.Whether to avoid clearing filtered rows. + */ +export interface IOptionsData extends IOptionData { + commentsOnly?: boolean; + /** + * Whether to clear only data validation rules. + */ + validationsOnly?: boolean; + /** + * Whether to avoid clearing filtered rows. + */ + skipFilteredRows?: boolean; +} + +/** + * Option of copyto function + */ +export interface ICopyToOptionsData extends IOptionData {} diff --git a/packages/core/src/Interfaces/IRowData.ts b/packages/core/src/Interfaces/IRowData.ts new file mode 100644 index 0000000000..b10882cfdf --- /dev/null +++ b/packages/core/src/Interfaces/IRowData.ts @@ -0,0 +1,19 @@ +import { BooleanNumber } from '../Enum'; + +/** + * Properties of row data + */ +export interface IRowData { + /** + * height + */ + h: number; + /** + * auto height + */ + ah?: number; + /** + * hidden + */ + hd: BooleanNumber; +} diff --git a/packages/core/src/Interfaces/ISelectionData.ts b/packages/core/src/Interfaces/ISelectionData.ts new file mode 100644 index 0000000000..d264dda3bd --- /dev/null +++ b/packages/core/src/Interfaces/ISelectionData.ts @@ -0,0 +1,30 @@ +import { Range } from '../Sheets/Domain'; +import { IRangeData, IRangeType } from './IRangeData'; + +/** + * Properties of selection data + */ +export interface ISelectionData { + selection?: IRangeType | IRangeType[] | Range; + cell?: IRangeType; +} + +export interface IPosition { + startX: number; + startY: number; + endX: number; + endY: number; +} + +export interface ICellInfo extends IPosition { + row: number; // current cell, if cell is in merge, isMerged is true, If the cell is in the upper left corner, isMergedMainCell is true. + column: number; + + isMerged: boolean; + + isMergedMainCell: boolean; + + mergeInfo: ISelection; // merge cell, start and end is upper left cell +} + +export interface ISelection extends IPosition, IRangeData {} diff --git a/packages/core/src/Interfaces/IServiceData.ts b/packages/core/src/Interfaces/IServiceData.ts new file mode 100644 index 0000000000..2aba8887d2 --- /dev/null +++ b/packages/core/src/Interfaces/IServiceData.ts @@ -0,0 +1,4 @@ +/** + * Properties of service + */ +export interface IServiceData {} diff --git a/packages/core/src/Interfaces/IStyleData.ts b/packages/core/src/Interfaces/IStyleData.ts new file mode 100644 index 0000000000..f7704f43d9 --- /dev/null +++ b/packages/core/src/Interfaces/IStyleData.ts @@ -0,0 +1,186 @@ +import { BorderStyleTypes } from '../Enum/BorderStyleTypes'; +import { + BooleanNumber, + HorizontalAlign, + TextDecoration, + TextDirection, + BaselineOffset, + VerticalAlign, + WrapStrategy, +} from '../Enum/TextStyle'; +import { ThemeColorType } from '../Enum/ThemeColorType'; +import { Nullable } from '../Shared/Types'; + +/** + * Properties of text decoration + */ +export interface ITextDecoration { + s: BooleanNumber; // show + cl?: IColorStyle; // color + t?: TextDecoration; // lineType +} + +/** + * RGB color or theme color + */ +export interface IColorStyle { + // rgb?: Nullable; + rgb?: Nullable; + th?: ThemeColorType; +} + +/** + * Format of RBGA color + */ +export interface IColor { + r: number; // Red + g: number; // Green + b: number; // Blue + a?: number; // Alpha +} + +/** + * Style properties of border + */ +export interface IBorderStyleData { + s: BorderStyleTypes; + cl: IColorStyle; +} + +/** + * Style properties of top, bottom, left and right border + */ +export interface IBorderData { + t?: Nullable; + r?: Nullable; + b?: Nullable; + l?: Nullable; +} + +// export interface IFormatData { +// /** +// * format +// */ +// f: string; +// /** +// * 数字 n +// * 字符串 g +// * 时间 d +// * 富文本 r +// */ +// t: string; +// } + +export interface ITextRotation { + /** + * angle + */ + a: number; + /** + * vertical + * true : 1 + * false : 0 + */ + v?: BooleanNumber; +} + +/** + * Top,right,bottom,left padding + */ +export interface IPaddingData { + t?: number; + r?: number; + b?: number; + l?: number; +} + +/** + * Base properties of cell style + */ +export interface IStyleBase { + /** + * fontFamily + */ + ff?: Nullable; + /** + * fontSize + * + * pt + */ + fs?: number; + /** + * italic + * 0: false + * 1: true + */ + it?: BooleanNumber; + /** + * bold + * 0: false + * 1: true + */ + bl?: BooleanNumber; + + /** + * underline + */ + ul?: ITextDecoration; + /** + * strikethrough + */ + st?: ITextDecoration; + /** + * overline + */ + ol?: ITextDecoration; + + /** + * background + */ + bg?: IColorStyle; + + /** + * border + */ + bd?: IBorderData; + + /** + * foreground + */ + cl?: IColorStyle; + + /** + * (Subscript 下标 /Superscript上标 Text) + */ + va?: BaselineOffset; +} + +/** + * Properties of cell style + */ +export interface IStyleData extends IStyleBase { + /** + * textRotation + */ + tr?: Nullable; + /** * + * textDirection + */ + td?: Nullable; + /** + * horizontalAlignment + */ + ht?: Nullable; + /** + * verticalAlignment + */ + vt?: Nullable; + /** + * wrapStrategy + */ + tb?: Nullable; + /** + * padding + */ + pd?: Nullable; +} diff --git a/packages/core/src/Interfaces/IWorkbookData.ts b/packages/core/src/Interfaces/IWorkbookData.ts new file mode 100644 index 0000000000..040f89d829 --- /dev/null +++ b/packages/core/src/Interfaces/IWorkbookData.ts @@ -0,0 +1,30 @@ +import { IKeyType, Nullable } from '../Shared/Types'; +import { BooleanNumber, LocaleType } from '../Enum'; +import { INamedRange } from '../Module/NamedRange/INamedRange'; +import { IStyleData } from './IStyleData'; +import { IWorksheetConfig } from './IWorksheetData'; + +/** + * Properties of a workbook's configuration + */ +export interface IWorkbookConfig { + createdTime: string; + lastModifiedBy: string; + id: string; + locale: LocaleType; + name: string; + theme: string; + skin: string; + modifiedTime: string; + timeZone: string; + creator: string; + appVersion: string; + socketUrl: string; // 协同 + socketEnable: BooleanNumber; // 协同 + extensions: []; + styles: IKeyType>; + sheets: { [sheetId: string]: Partial }; + sheetOrder: string[]; // sheet id order list ['xxxx-sheet3', 'xxxx-sheet1','xxxx-sheet2'] + pluginMeta: object; + namedRanges: INamedRange[]; +} diff --git a/packages/core/src/Interfaces/IWorksheetData.ts b/packages/core/src/Interfaces/IWorksheetData.ts new file mode 100644 index 0000000000..da2d040387 --- /dev/null +++ b/packages/core/src/Interfaces/IWorksheetData.ts @@ -0,0 +1,67 @@ +import { BooleanNumber, SheetTypes } from '../Enum'; +import { ObjectArrayType } from '../Shared/ObjectArray'; +import { ObjectMatrixType } from '../Shared/ObjectMatrix'; +import { ICellData } from './ICellData'; +import { IColumnData } from './IColumnData'; +import { IRangeData, IRangeType } from './IRangeData'; +import { IRowData } from './IRowData'; + +// type MetaData = { +// metadataId?: string; +// metadataKey: string; +// metadataValue?: string; +// visibility?: DeveloperMetadataVisibility; +// }; + +/** + * Properties of a worksheet's configuration + * + * TODO: 考虑将非通用配置,抽离到插件 + * + * 比如 showGridlines 是sheet特有的,而如果实现如普通表格,就不需要 showGridlines + */ +export interface IWorksheetConfig { + type: SheetTypes; + id: string; + name: string; + tabColor: string; + + /** + * Determine whether the sheet is hidden + * + * @remarks + * See {@link BooleanNumber| the BooleanNumber enum} for more details. + * + * @defaultValue `BooleanNumber.FALSE` + */ + hidden: BooleanNumber; + freezeRow: number; + freezeColumn: number; + rowCount: number; + columnCount: number; + zoomRatio: number; + scrollTop: number; + scrollLeft: number; + defaultColumnWidth: number; + defaultRowHeight: number; + mergeData: IRangeData[]; + hideRow: []; + hideColumn: []; + status: BooleanNumber; + cellData: ObjectMatrixType; + rowData: ObjectArrayType>; + columnData: ObjectArrayType>; + showGridlines: BooleanNumber; + rowTitle: { + width: number; + hidden?: BooleanNumber; + }; + columnTitle: { + height: number; + hidden?: BooleanNumber; + }; + selections: IRangeType[]; + rightToLeft: BooleanNumber; + // metaData: MetaData[]; + pluginMeta: {}; +} diff --git a/packages/core/src/Interfaces/IWorksheetOrderData.ts b/packages/core/src/Interfaces/IWorksheetOrderData.ts new file mode 100644 index 0000000000..1a3f5a97bb --- /dev/null +++ b/packages/core/src/Interfaces/IWorksheetOrderData.ts @@ -0,0 +1,4 @@ +/** + * Properties of WorksheetOrderData + */ +export interface IWorksheetOrderData {} diff --git a/packages/core/src/Interfaces/index.ts b/packages/core/src/Interfaces/index.ts new file mode 100644 index 0000000000..c11ab213de --- /dev/null +++ b/packages/core/src/Interfaces/index.ts @@ -0,0 +1,14 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './ICellData'; +export * from './IColumnData'; +export * from './IDocumentData'; +export * from './IObserveData'; +export * from './IRangeData'; +export * from './IRowData'; +export * from './ISelectionData'; +export * from './IServiceData'; +export * from './IStyleData'; +export * from './IWorkbookData'; +export * from './IWorksheetData'; +export * from './IWorksheetOrderData'; +// @endindex diff --git a/packages/core/src/Module/Collapse/index.ts b/packages/core/src/Module/Collapse/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/Module/Formula/CaculationChain.ts b/packages/core/src/Module/Formula/CaculationChain.ts new file mode 100644 index 0000000000..47cc8ab879 --- /dev/null +++ b/packages/core/src/Module/Formula/CaculationChain.ts @@ -0,0 +1,4 @@ +/** + * Formula calculation chain + */ +export class CaculationChain {} diff --git a/packages/core/src/Module/Formula/FormulaManager.ts b/packages/core/src/Module/Formula/FormulaManager.ts new file mode 100644 index 0000000000..8ce3e63f22 --- /dev/null +++ b/packages/core/src/Module/Formula/FormulaManager.ts @@ -0,0 +1,4 @@ +/** + * Run and add formulas + */ +export class FormulaManager {} diff --git a/packages/core/src/Module/Formula/index.ts b/packages/core/src/Module/Formula/index.ts new file mode 100644 index 0000000000..227ba578be --- /dev/null +++ b/packages/core/src/Module/Formula/index.ts @@ -0,0 +1,3 @@ +export * from './CaculationChain'; +export * from './FormulaManager'; +export * from './parse'; diff --git a/packages/core/src/Module/Formula/parse.ts b/packages/core/src/Module/Formula/parse.ts new file mode 100644 index 0000000000..8496e3f7d6 --- /dev/null +++ b/packages/core/src/Module/Formula/parse.ts @@ -0,0 +1,4 @@ +/** + * Parse + */ +export class Parse {} diff --git a/packages/core/src/Module/Freeze/Action/SetFrozenCancelAction.ts b/packages/core/src/Module/Freeze/Action/SetFrozenCancelAction.ts new file mode 100644 index 0000000000..e99ba86c58 --- /dev/null +++ b/packages/core/src/Module/Freeze/Action/SetFrozenCancelAction.ts @@ -0,0 +1,50 @@ +import { ActionBase, ActionObservers, IActionData } from '../../../Command'; +import { WorkBook } from '../../../Sheets/Domain'; +import { WorkSheetConvertor } from '../../../Convertor'; +import { SetFrozenCancelService } from '../Service/SetFrozenCancelService'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from '../../../Const'; +import { SetFrozenColumns, SetFrozenRows } from '../../../Sheets/Apply'; + +export class SetFrozenCancelAction extends ActionBase { + _numbers: number[] = []; + + constructor( + actionData: IActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + this._observers.onActionDoObserver.notifyObservers(this); + this._numbers = SetFrozenCancelService(worksheet); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + this._observers.onActionDoObserver.notifyObservers(this); + SetFrozenRows(worksheet, this._numbers[0]); + SetFrozenColumns(worksheet, this._numbers[1]); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Freeze/Action/SetFrozenColumnsAction.ts b/packages/core/src/Module/Freeze/Action/SetFrozenColumnsAction.ts new file mode 100644 index 0000000000..70c8fde8c9 --- /dev/null +++ b/packages/core/src/Module/Freeze/Action/SetFrozenColumnsAction.ts @@ -0,0 +1,63 @@ +import { ActionBase, ActionObservers, IActionData } from 'src/Command'; +import { WorkBook } from 'src/Sheets/Domain'; +import { WorkSheetConvertor } from 'src/Convertor'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from 'src/Const'; +import { SetFrozenColumnsService } from '../Service/SetFrozenColumnsService'; + +export interface ISetFrozenColumnsActionData extends IActionData { + numColumns: number; +} + +export class SetFrozenColumnsAction extends ActionBase { + constructor( + actionData: ISetFrozenColumnsActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + numColumns: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.validate(); + } + + do(): number { + const worksheet = this.getWorkSheet(); + this._observers.onActionDoObserver.notifyObservers(this); + return SetFrozenColumnsService(worksheet, this._doActionData.numColumns); + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + numColumns: this.do(), + }; + } + + undo(): void { + const { numColumns, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + this._observers.onActionUndoObserver.notifyObservers(this); + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId, + numColumns: SetFrozenColumnsService(worksheet, numColumns), + }; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Freeze/Action/SetFrozenRowsAction.ts b/packages/core/src/Module/Freeze/Action/SetFrozenRowsAction.ts new file mode 100644 index 0000000000..42f6a6eda1 --- /dev/null +++ b/packages/core/src/Module/Freeze/Action/SetFrozenRowsAction.ts @@ -0,0 +1,72 @@ +import { ActionBase, ActionObservers, ActionType, IActionData } from 'src/Command'; +import { WorkSheetConvertor } from 'src/Convertor'; +import { WorkBook } from 'src/Sheets/Domain'; +import { SetFrozenRows } from 'src/Sheets/Apply'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from 'src/Const'; + +export interface ISetFrozenRowsActionData extends IActionData { + numRows: number; +} + +export class SetFrozenRowsAction extends ActionBase { + constructor( + actionData: ISetFrozenRowsActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_ROWS_ACTION, + sheetId: actionData.sheetId, + numRows: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.validate(); + } + + do(): number { + const worksheet = this.getWorkSheet(); + const result = SetFrozenRows(worksheet, this._doActionData.numRows); + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + return result; + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.SET_FROZEN_ROWS_ACTION, + numRows: this.do(), + }; + } + + undo(): void { + const { numRows, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_FROZEN_ROWS_ACTION, + sheetId, + numRows: SetFrozenRows(worksheet, numRows), + }; + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Freeze/Freeze.ts b/packages/core/src/Module/Freeze/Freeze.ts new file mode 100644 index 0000000000..15f527d913 --- /dev/null +++ b/packages/core/src/Module/Freeze/Freeze.ts @@ -0,0 +1,46 @@ +import { WorkSheet } from '../../Sheets/Domain'; +import { Command, CommandManager } from '../../Command'; +import { Context } from '../../Basics'; +import { ACTION_NAMES } from '../../Const'; + +export class Freeze { + private _commandManager: CommandManager; + + private _context: Context; + + private _worksheet: WorkSheet; + + constructor(workSheet: WorkSheet) { + this._context = workSheet.getContext(); + this._commandManager = this._context.getCommandManager(); + this._worksheet = workSheet; + } + + setFrozenColumns(columns: number): WorkSheet { + const { _context, _commandManager } = this; + const configure = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: this._worksheet.getSheetId(), + numColumns: columns, + }; + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + + return this._worksheet; + } + + setCancelFrozen(): void {} + + setFrozenRows(rows: number): WorkSheet { + const { _context, _commandManager } = this; + const configure = { + actionName: ACTION_NAMES.SET_FROZEN_ROWS_ACTION, + sheetId: this._worksheet.getSheetId(), + numRows: rows, + }; + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + + return this._worksheet; + } +} diff --git a/packages/core/src/Module/Freeze/Service/SetFrozenCancelService.ts b/packages/core/src/Module/Freeze/Service/SetFrozenCancelService.ts new file mode 100644 index 0000000000..2cba999b87 --- /dev/null +++ b/packages/core/src/Module/Freeze/Service/SetFrozenCancelService.ts @@ -0,0 +1,17 @@ +import { WorkSheet } from '../../../Sheets/Domain'; + +export function SetFrozenCancelService(worksheet: WorkSheet): number[] { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldFreezeColumn = config.freezeColumn; + const oldFreezeRow = config.freezeRow; + + // set new sheet name + config.freezeColumn = -1; + config.freezeRow = -1; + + // return old sheet name to undo + return [oldFreezeRow, oldFreezeColumn]; +} diff --git a/packages/core/src/Module/Freeze/Service/SetFrozenColumnsService.ts b/packages/core/src/Module/Freeze/Service/SetFrozenColumnsService.ts new file mode 100644 index 0000000000..70421a6319 --- /dev/null +++ b/packages/core/src/Module/Freeze/Service/SetFrozenColumnsService.ts @@ -0,0 +1,18 @@ +import { WorkSheet } from 'src/Sheets/Domain'; + +export function SetFrozenColumnsService( + worksheet: WorkSheet, + numColumns: number +): number { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldStatus = config.freezeColumn; + + // set new sheet name + config.freezeColumn = numColumns; + + // return old sheet name to undo + return oldStatus; +} diff --git a/packages/core/src/Module/Freeze/Service/SetFrozenRowsService.ts b/packages/core/src/Module/Freeze/Service/SetFrozenRowsService.ts new file mode 100644 index 0000000000..da91e2f95e --- /dev/null +++ b/packages/core/src/Module/Freeze/Service/SetFrozenRowsService.ts @@ -0,0 +1,15 @@ +import { WorkSheet } from 'src/Sheets/Domain'; + +export function SetFrozenRowsService(worksheet: WorkSheet, numRows: number): number { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldStatus = config.freezeRow; + + // set new sheet name + config.freezeRow = numRows; + + // return old sheet name to undo + return oldStatus; +} diff --git a/packages/core/src/Module/Freeze/index.ts b/packages/core/src/Module/Freeze/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/Module/Group/StructGroup.ts b/packages/core/src/Module/Group/StructGroup.ts new file mode 100644 index 0000000000..2dda38403b --- /dev/null +++ b/packages/core/src/Module/Group/StructGroup.ts @@ -0,0 +1,213 @@ +/** + * Types of group line + */ +export enum GroupLineType { + prefix, + suffix, + include, + nothing, +} + +/** + * Status of group open + */ +export enum GroupOpenType { + open, + close, +} + +/** + * A group line, including start position, end position, state + */ +export class GroupLine { + start: number; + + end: number; + + type: GroupOpenType; + + constructor(start: number, end: number) { + this.start = start; + this.end = end; + this.type = GroupOpenType.open; + } + + isCrossLine(line: GroupLine): GroupLineType { + if (this.start > line.start && this.start < line.end) { + return GroupLineType.prefix; + } + if (this.end < line.end && this.start < line.start) { + return GroupLineType.suffix; + } + if (this.start === line.start && this.end === line.end) { + return GroupLineType.include; + } + return GroupLineType.nothing; + } +} + +/** + * A set of GroupLines + */ +export class GroupLayout { + lines: GroupLine[]; + + constructor() { + this.lines = []; + } + + findCrossLine(line: GroupLine): Map { + const lines = this.lines; + const length = lines.length; + const table = new Map(); + const include = new Array(); + const prefix = new Array(); + const suffix = new Array(); + for (let i = 0; i < length; i++) { + const item = lines[i]; + const type = line.isCrossLine(item); + switch (type) { + case GroupLineType.include: { + include.push(item); + break; + } + case GroupLineType.suffix: { + suffix.push(item); + break; + } + case GroupLineType.prefix: { + prefix.push(item); + break; + } + } + } + if (suffix.length > 0) { + table.set(GroupLineType.suffix, suffix); + } + if (prefix.length > 0) { + table.set(GroupLineType.prefix, prefix); + } + if (include.length > 0) { + table.set(GroupLineType.include, include); + } + return table; + } + + setAllGroupOpenType(type: GroupOpenType) { + this.lines.forEach((line) => (line.type = type)); + } + + addGroupLine(line: GroupLine): void { + this.lines.push(line); + } +} + +/** + * A set of GroupLayouts + */ +export class GroupDepth { + layouts: GroupLayout[]; + + findCrossLine(line: GroupLine): Array> { + const table = new Array>(); + const layouts = this.layouts; + const length = layouts.length; + for (let i = 0; i < length; i++) { + const item = layouts[i]; + const result = item.findCrossLine(line); + if (result.size > 0) { + table.push(result); + } + } + return table; + } + + setAllGroupOpenType(type: GroupOpenType) { + this.layouts.forEach((layout) => layout.setAllGroupOpenType(type)); + } + + addLineToDepth(depth: number, line: GroupLine): void { + const layouts = this.layouts; + let layout = layouts[depth]; + if (layout == null) { + layout = new GroupLayout(); + layouts[depth] = layout; + } + layout.addGroupLine(line); + } +} + +/** + * Handle GroupDepth + */ +export class StructGroup { + protected _groupDepth: GroupDepth; + + _processPrefix( + line: GroupLine, + table: Array> + ): void { + let filter: GroupLine[] = []; + let previous = line; + for (let i = 0; i < table.length; i++) { + const element = table[i]; + const lines = element.get(GroupLineType.suffix); + filter = filter.concat(lines || []); + } + for (let i = 0; i < filter.length; i++) { + const element = filter[i]; + if (i === filter.length - 1) { + line.end = element.end; + element.end = previous.end; + } else { + element.end = previous.end; + previous = element; + } + } + } + + _processSuffix( + line: GroupLine, + table: Array> + ): void { + let filter: GroupLine[] = []; + let previous = line; + for (let i = 0; i < table.length; i++) { + const element = table[i]; + const lines = element.get(GroupLineType.prefix); + filter = filter.concat(lines || []); + } + for (let i = 0; i < filter.length; i++) { + const element = filter[i]; + if (i === filter.length - 1) { + line.start = element.start; + element.start = previous.start; + } else { + element.start = previous.start; + previous = element; + } + } + } + + _processInclude( + line: GroupLine, + table: Array> + ): void {} + + constructor() { + this._groupDepth = new GroupDepth(); + } + + setAllGroupOpenType(type: GroupOpenType) { + this._groupDepth.setAllGroupOpenType(type); + } + + addGroupLine(line: GroupLine): void { + const groupDepth = this._groupDepth; + const table = groupDepth.findCrossLine(line); + this._processPrefix(line, table); + this._processSuffix(line, table); + this._processInclude(line, table); + this._groupDepth.addLineToDepth(table.length, line); + } +} diff --git a/packages/core/src/Module/Group/index.ts b/packages/core/src/Module/Group/index.ts new file mode 100644 index 0000000000..8ee776a960 --- /dev/null +++ b/packages/core/src/Module/Group/index.ts @@ -0,0 +1 @@ +export * from './StructGroup'; diff --git a/packages/core/src/Module/Image/Action/AddFloatImageAction.ts b/packages/core/src/Module/Image/Action/AddFloatImageAction.ts new file mode 100644 index 0000000000..699ce3a192 --- /dev/null +++ b/packages/core/src/Module/Image/Action/AddFloatImageAction.ts @@ -0,0 +1,13 @@ +import { ActionBase } from '../../../Command'; + +export class AddFloatImageAction extends ActionBase { + do(): void {} + + redo(): void {} + + undo(): void {} + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Image/Action/SetFloatImageOrderAction.ts b/packages/core/src/Module/Image/Action/SetFloatImageOrderAction.ts new file mode 100644 index 0000000000..6b5c6e015a --- /dev/null +++ b/packages/core/src/Module/Image/Action/SetFloatImageOrderAction.ts @@ -0,0 +1,35 @@ +import { ActionObservers, ActionBase, IActionData } from '../../../Command'; + +import { WorkBook } from '../../../Sheets/Domain'; + +export interface ISetFloatImageOrderActionData extends IActionData { + x: number; + y: number; + width: number; + height: number; +} + +export class SetFloatImageOrderAction extends ActionBase< + ISetFloatImageOrderActionData, + ISetFloatImageOrderActionData +> { + constructor( + actionData: ISetFloatImageOrderActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + } + + redo(): void {} + + do(): void { + this.redo(); + } + + undo(): void {} + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Image/CellImage.ts b/packages/core/src/Module/Image/CellImage.ts new file mode 100644 index 0000000000..297b79e429 --- /dev/null +++ b/packages/core/src/Module/Image/CellImage.ts @@ -0,0 +1,25 @@ +import { CellImageBuilder } from './CellImageBuilder'; + +export class CellImage { + constructor() {} + + getAltTextDescription(): string { + return ''; + } + + getUrl(): string { + return ''; + } + + getAltTextTitle(): string { + return ''; + } + + getContentUrl(): string { + return ''; + } + + toBuilder(): CellImageBuilder { + return new CellImageBuilder(); + } +} diff --git a/packages/core/src/Module/Image/CellImageBuilder.ts b/packages/core/src/Module/Image/CellImageBuilder.ts new file mode 100644 index 0000000000..19705f5950 --- /dev/null +++ b/packages/core/src/Module/Image/CellImageBuilder.ts @@ -0,0 +1,3 @@ +export class CellImageBuilder { + constructor() {} +} diff --git a/packages/core/src/Module/Image/OverGridImage.ts b/packages/core/src/Module/Image/OverGridImage.ts new file mode 100644 index 0000000000..ac8b7fb4ad --- /dev/null +++ b/packages/core/src/Module/Image/OverGridImage.ts @@ -0,0 +1,78 @@ +import { WorkSheet } from '../../Sheets/Domain'; +import { ICellData } from '../../Interfaces'; +import { Nullable } from '../../Shared'; +// import {IObjectFullState, IShapeConfig, Shape} from "@univer/base-render/src"; + +// export interface IOverGridImageConfig extends IShapeConfig, IObjectFullState { + +// } + +// export class OverGridImage extends Shape { + +export class OverGridImage { + constructor() { + // super(); + } + + setWidth(width: number): void {} + + setHeight(height: number): void {} + + getAltTextDescription(): string { + return ''; + } + + getAltTextTitle(): string { + return ''; + } + + getAnchorCell(): Nullable { + return undefined; + } + + getAnchorCellXOffset(): number { + return 0; + } + + getAnchorCellYOffset(): number { + return 0; + } + + getHeight(): number { + return 0; + } + + getInherentHeight(): number { + return 0; + } + + getInherentWidth(): number { + return 0; + } + + getSheet(): Nullable { + return null; + } + + getUrl(): string { + return ''; + } + + getWidth(): number { + return 0; + } + + remove(): void {} + + replace(url: string): Nullable { + return null; + } + + resetSize(): Nullable {} + + setAltTextDescription(description: string): Nullable {} + + setAltTextTitle(title: string): Nullable {} + + setAnchorCell(cell: ICellData): Nullable {} +} diff --git a/packages/core/src/Module/Image/index.ts b/packages/core/src/Module/Image/index.ts new file mode 100644 index 0000000000..9d93184c25 --- /dev/null +++ b/packages/core/src/Module/Image/index.ts @@ -0,0 +1 @@ +export * from './OverGridImage'; diff --git a/packages/core/src/Module/NamedRange/Action/AddNamedRangeAction.ts b/packages/core/src/Module/NamedRange/Action/AddNamedRangeAction.ts new file mode 100644 index 0000000000..0a05de593e --- /dev/null +++ b/packages/core/src/Module/NamedRange/Action/AddNamedRangeAction.ts @@ -0,0 +1,80 @@ +import { + ActionBase, + ActionObservers, + ActionType, + IActionData, +} from '../../../Command'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from '../../../Const'; +import { WorkSheetConvertor } from '../../../Convertor'; +import { WorkBook } from '../../../Sheets/Domain'; +import { INamedRange } from '../INamedRange'; +import { AddNamedRangeService } from '../Service/AddNamedRangeService'; +import { DeleteNamedRangeService } from '../Service/DeleteNamedRangeService'; +import { IDeleteNamedRangeActionData } from './DeleteNamedRangeAction'; + +export interface IAddNamedRangeActionData extends IActionData { + namedRange: INamedRange; +} + +export class AddNamedRangeAction extends ActionBase< + IAddNamedRangeActionData, + IDeleteNamedRangeActionData, + void +> { + constructor( + actionData: IAddNamedRangeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this._oldActionData = { + actionName: ACTION_NAMES.DELETE_NAMED_RANGE_ACTION, + sheetId: actionData.sheetId, + namedRangeId: actionData.namedRange.namedRangeId, + }; + this.validate(); + } + + do(): void { + const { namedRange } = this._doActionData; + const namedRanges = this._workbook.getConfig().namedRanges; + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + AddNamedRangeService(namedRanges, namedRange); + } + + redo(): void { + this.do(); + // no need store + } + + undo(): void { + const { namedRangeId, sheetId } = this._oldActionData; + const namedRanges = this._workbook.getConfig().namedRanges; + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.ADD_NAMED_RANGE_ACTION, + sheetId, + namedRange: DeleteNamedRangeService(namedRanges, namedRangeId), + }; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/NamedRange/Action/DeleteNamedRangeAction.ts b/packages/core/src/Module/NamedRange/Action/DeleteNamedRangeAction.ts new file mode 100644 index 0000000000..145ed7af9e --- /dev/null +++ b/packages/core/src/Module/NamedRange/Action/DeleteNamedRangeAction.ts @@ -0,0 +1,80 @@ +import { + ActionBase, + ActionObservers, + ActionType, + IActionData, +} from '../../../Command'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from '../../../Const'; +import { WorkSheetConvertor } from '../../../Convertor'; +import { WorkBook } from '../../../Sheets/Domain'; +import { INamedRange } from '../INamedRange'; +import { AddNamedRangeService } from '../Service/AddNamedRangeService'; +import { DeleteNamedRangeService } from '../Service/DeleteNamedRangeService'; +import { IAddNamedRangeActionData } from './AddNamedRangeAction'; + +export interface IDeleteNamedRangeActionData extends IActionData { + namedRangeId: string; +} + +export class DeleteNamedRangeAction extends ActionBase< + IDeleteNamedRangeActionData, + IAddNamedRangeActionData, + INamedRange +> { + constructor( + actionData: IDeleteNamedRangeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.ADD_NAMED_RANGE_ACTION, + sheetId: actionData.sheetId, + namedRange: this.do(), + }; + this.validate(); + } + + do(): INamedRange { + const { namedRangeId } = this._doActionData; + const namedRanges = this._workbook.getConfig().namedRanges; + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + return DeleteNamedRangeService(namedRanges, namedRangeId); + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.ADD_BANDING_ACTION, + namedRange: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.INSERT)], + }; + } + + undo(): void { + const { namedRange } = this._oldActionData; + const namedRanges = this._workbook.getConfig().namedRanges; + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + AddNamedRangeService(namedRanges, namedRange); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/NamedRange/Action/SetNamedRangeAction.ts b/packages/core/src/Module/NamedRange/Action/SetNamedRangeAction.ts new file mode 100644 index 0000000000..1d0fa99eb3 --- /dev/null +++ b/packages/core/src/Module/NamedRange/Action/SetNamedRangeAction.ts @@ -0,0 +1,82 @@ +import { + ActionBase, + ActionObservers, + ActionType, + IActionData, +} from '../../../Command'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from '../../../Const'; +import { WorkSheetConvertor } from '../../../Convertor'; +import { WorkBook } from '../../../Sheets/Domain'; +import { INamedRange } from '../INamedRange'; +import { SetNamedRangeService } from '../Service/SetNamedRangeService'; + +export interface ISetNamedRangeActionData extends IActionData { + namedRange: INamedRange; +} + +export class SetNamedRangeAction extends ActionBase< + ISetNamedRangeActionData, + ISetNamedRangeActionData, + INamedRange +> { + constructor( + actionData: ISetNamedRangeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_NAMED_RANGE_ACTION, + sheetId: actionData.sheetId, + namedRange: this.do(), + }; + this.validate(); + } + + do(): INamedRange { + const { namedRange } = this._doActionData; + const namedRanges = this._workbook.getConfig().namedRanges; + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + return SetNamedRangeService(namedRanges, namedRange); + } + + redo(): void { + // update pre data + const { sheetId, namedRange } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_NAMED_RANGE_ACTION, + sheetId, + namedRange: this.do(), + }; + } + + undo(): void { + const { namedRange, sheetId } = this._oldActionData; + const namedRanges = this._workbook.getConfig().namedRanges; + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_NAMED_RANGE_ACTION, + sheetId, + namedRange: SetNamedRangeService(namedRanges, namedRange), + }; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/NamedRange/Action/index.ts b/packages/core/src/Module/NamedRange/Action/index.ts new file mode 100644 index 0000000000..a9776b0b57 --- /dev/null +++ b/packages/core/src/Module/NamedRange/Action/index.ts @@ -0,0 +1,5 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './AddNamedRangeAction'; +export * from './DeleteNamedRangeAction'; +export * from './SetNamedRangeAction'; +// @endindex diff --git a/packages/core/src/Module/NamedRange/INamedRange.ts b/packages/core/src/Module/NamedRange/INamedRange.ts new file mode 100644 index 0000000000..9948c2566c --- /dev/null +++ b/packages/core/src/Module/NamedRange/INamedRange.ts @@ -0,0 +1,17 @@ +import { DEFAULT_SELECTION } from '../../Const'; +import { IGridRange } from '../../Interfaces'; + +export interface INamedRange { + namedRangeId: string; + name: string; + range: IGridRange; +} + +export const DEFAULT_NAMED_RANGE: INamedRange = { + namedRangeId: 'named-range-01', + name: 'namedRange01', + range: { + sheetId: 'sheet-01', + rangeData: DEFAULT_SELECTION, + }, +}; diff --git a/packages/core/src/Module/NamedRange/NamedRange.ts b/packages/core/src/Module/NamedRange/NamedRange.ts new file mode 100644 index 0000000000..61182f83c2 --- /dev/null +++ b/packages/core/src/Module/NamedRange/NamedRange.ts @@ -0,0 +1,172 @@ +import { Context } from '../../Basics'; +import { Command, CommandManager } from '../../Command'; +import { ACTION_NAMES } from '../../Const'; +import { WorkBook, WorkSheet } from '../../Sheets/Domain'; +import { + IAddNamedRangeActionData, + IDeleteNamedRangeActionData, + ISetNamedRangeActionData, +} from './Action'; +import { INamedRange } from './INamedRange'; + +/** + * Create, access and modify named ranges in a spreadsheet. Named ranges are ranges that have associated string aliases. + * + * Reference: https://developers.google.com/apps-script/reference/spreadsheet/named-range + */ +export class NamedRange { + private _workbook: WorkBook; + + private _worksheet: WorkSheet; + + private _commandManager: CommandManager; + + private _context: Context; + + // private _name: string; + // private _range: Range; + // private _namedRangeId: string; + + constructor(worksheet: WorkSheet) { + this._worksheet = worksheet; + this._workbook = this._worksheet.getContext().getWorkBook(); + this._commandManager = this._worksheet.getCommandManager(); + this._context = this._worksheet.getContext(); + + // this._name = name; + // this._range = range; + // this._namedRangeId = 'named-range-' + Tools.generateRandomId(); + // this._addNamedRange(); + } + + addNamedRange(namedRange: INamedRange) { + const { _context, _commandManager } = this; + + // const namedRange: INamedRange = { + // namedRangeId: this._namedRangeId, + // name: this._name, + // range: { + // sheetId: this._worksheet.getSheetId(), + // rangeData: this._range.getRangeData(), + // }, + // }; + // Organize action data + const actionData: IAddNamedRangeActionData = { + actionName: ACTION_NAMES.ADD_NAMED_RANGE_ACTION, + namedRange, + sheetId: this._worksheet.getSheetId(), + }; + + // Execute action + const command = new Command(_context.getWorkBook(), actionData); + _commandManager.invoke(command); + + // // manage instance + // this._workbook.getNamedRanges().push(this); + } + + setNamedRange(namedRange: INamedRange) { + const { _context, _commandManager } = this; + + // const namedRange: INamedRange = { + // namedRangeId: this._namedRangeId, + // name: this._name, + // range: { + // sheetId: this._worksheet.getSheetId(), + // rangeData: this._range.getRangeData(), + // }, + // }; + // Organize action data + const actionData: ISetNamedRangeActionData = { + actionName: ACTION_NAMES.SET_NAMED_RANGE_ACTION, + namedRange, + sheetId: this._worksheet.getSheetId(), + }; + + // Execute action + const command = new Command(_context.getWorkBook(), actionData); + _commandManager.invoke(command); + } + + /** + * Gets the name of this named range. + * + * @returns String — the name of this named range + */ + // getName(): string { + // return this._name; + // } + + /** + * Sets/updates the name of the named range. + * @param name The new name of the named range. + * + * @returns NamedRange — the range whose name was set by the call + */ + // setName(name: string): NamedRange { + // this._name = name; + + // this._setNamedRange(); + + // return this; + // } + + /** + * Gets the range referenced by this named range. + * + * @returns Range — the spreadsheet range that is associated with this named range + */ + // getRange(): Range { + // return this._range; + // } + + /** + * Sets/updates the range for this named range. + * @param range The spreadsheet range to associate with this named range. + * @returns NamedRange — the named range for which the spreadsheet range was set + */ + // setRange(range: Range): NamedRange { + // this._range = range; + + // this._setNamedRange(); + + // return this; + // } + + /** + * Gets the namedRangeId of this named range. + * + * @returns String — the namedRangeId of this named range + */ + // getNamedRangeId(): string { + // return this._namedRangeId; + // } + + /** + * Deletes this named range. + */ + remove(namedRangeId: string): void { + const { _context, _commandManager } = this; + + // Organize action data + const actionData: IDeleteNamedRangeActionData = { + actionName: ACTION_NAMES.DELETE_NAMED_RANGE_ACTION, + namedRangeId, + sheetId: this._worksheet.getSheetId(), + }; + + // Execute action + const command = new Command(_context.getWorkBook(), actionData); + _commandManager.invoke(command); + + // const namedRanges = this._workbook.getNamedRanges(); + + // return namedRanges.find((currentNamedRange: NamedRange, i) => { + // if (currentNamedRange.getNamedRangeId() === this._namedRangeId) { + // // remove + // namedRanges.splice(i, 1); + // return currentNamedRange; + // } + // })!; + } +} diff --git a/packages/core/src/Module/NamedRange/Service/AddNamedRangeService.ts b/packages/core/src/Module/NamedRange/Service/AddNamedRangeService.ts new file mode 100644 index 0000000000..e1f3855e51 --- /dev/null +++ b/packages/core/src/Module/NamedRange/Service/AddNamedRangeService.ts @@ -0,0 +1,8 @@ +import { INamedRange } from '../INamedRange'; + +export function AddNamedRangeService( + namedRanges: INamedRange[], + namedRange: INamedRange +): void { + namedRanges.push(namedRange); +} diff --git a/packages/core/src/Module/NamedRange/Service/DeleteNamedRangeService.ts b/packages/core/src/Module/NamedRange/Service/DeleteNamedRangeService.ts new file mode 100644 index 0000000000..ba1c789061 --- /dev/null +++ b/packages/core/src/Module/NamedRange/Service/DeleteNamedRangeService.ts @@ -0,0 +1,14 @@ +import { INamedRange } from '../INamedRange'; + +export function DeleteNamedRangeService( + namedRanges: INamedRange[], + namedRangeId: string +): INamedRange { + return namedRanges.find((currentNamedRange: INamedRange, i) => { + if (currentNamedRange.namedRangeId === namedRangeId) { + // remove + namedRanges.splice(i, 1); + return currentNamedRange; + } + })!; +} diff --git a/packages/core/src/Module/NamedRange/Service/SetNamedRangeService.ts b/packages/core/src/Module/NamedRange/Service/SetNamedRangeService.ts new file mode 100644 index 0000000000..5f0028e0d2 --- /dev/null +++ b/packages/core/src/Module/NamedRange/Service/SetNamedRangeService.ts @@ -0,0 +1,14 @@ +import { INamedRange } from '../INamedRange'; + +export function SetNamedRangeService( + namedRanges: INamedRange[], + namedRange: INamedRange +): INamedRange { + return namedRanges.find((currentNamedRange: INamedRange, i) => { + if (currentNamedRange.namedRangeId === namedRange.namedRangeId) { + // edit + namedRanges[i] = namedRange; + return currentNamedRange; + } + })!; +} diff --git a/packages/core/src/Module/NamedRange/Service/index.ts b/packages/core/src/Module/NamedRange/Service/index.ts new file mode 100644 index 0000000000..80812baca1 --- /dev/null +++ b/packages/core/src/Module/NamedRange/Service/index.ts @@ -0,0 +1,5 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './AddNamedRangeService'; +export * from './DeleteNamedRangeService'; +export * from './SetNamedRangeService'; +// @endindex diff --git a/packages/core/src/Module/Protection/Action/AddAllowedAction.ts b/packages/core/src/Module/Protection/Action/AddAllowedAction.ts new file mode 100644 index 0000000000..09c50bde55 --- /dev/null +++ b/packages/core/src/Module/Protection/Action/AddAllowedAction.ts @@ -0,0 +1,54 @@ +import { ActionBase, ActionObservers, IActionData } from '../../../Command'; +import { Allowed } from '../Allowed'; +import { WorkBook } from '../../../Sheets/Domain'; +import { WorkSheetConvertor } from '../../../Convertor'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from '../../../Const'; +import { AddAllowedService } from '../Service/AddAllowedService'; +import { RemoveAllowedService } from '../Service/RemoveAllowedService'; + +export interface IAddAllowedActionData extends IActionData { + allowed: Allowed; +} + +export class AddAllowedAction extends ActionBase< + IAddAllowedActionData, + IAddAllowedActionData +> { + constructor( + actionData: IAddAllowedActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + allowed: actionData.allowed, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + AddAllowedService(worksheet, this._doActionData.allowed); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + RemoveAllowedService(worksheet, this._doActionData.allowed); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Protection/Action/AddUnlockAction.ts b/packages/core/src/Module/Protection/Action/AddUnlockAction.ts new file mode 100644 index 0000000000..777453da61 --- /dev/null +++ b/packages/core/src/Module/Protection/Action/AddUnlockAction.ts @@ -0,0 +1,53 @@ +import { ActionBase, ActionObservers, IActionData } from '../../../Command'; +import { Range, WorkBook } from '../../../Sheets/Domain'; +import { WorkSheetConvertor } from '../../../Convertor'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from '../../../Const'; +import { AddUnlockService } from '../Service/AddUnlockService'; +import { RemoveUnlockService } from '../Service/RemoveUnlockService'; + +export interface IAddUnlockActionData extends IActionData { + unlock: Range; +} + +export class AddUnlockAction extends ActionBase< + IAddUnlockActionData, + IAddUnlockActionData +> { + constructor( + actionData: IAddUnlockActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + unlock: actionData.unlock, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + AddUnlockService(worksheet, this._doActionData.unlock); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + RemoveUnlockService(worksheet, this._doActionData.unlock); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Protection/Action/SetProtectionAction.ts b/packages/core/src/Module/Protection/Action/SetProtectionAction.ts new file mode 100644 index 0000000000..42ab6f6e78 --- /dev/null +++ b/packages/core/src/Module/Protection/Action/SetProtectionAction.ts @@ -0,0 +1,60 @@ +import { ActionBase, ActionObservers, IActionData } from '../../../Command'; +import { WorkBook } from '../../../Sheets/Domain'; +import { WorkSheetConvertor } from '../../../Convertor'; +import { ACTION_NAMES, CONVERTOR_OPERATION } from '../../../Const'; +import { SetProtectionService } from '../Service/SetProtectionService'; + +export interface ISetProtectionActionData extends IActionData { + enable: boolean; + password: string; +} + +export class SetProtectionAction extends ActionBase< + ISetProtectionActionData, + ISetProtectionActionData +> { + protected _oldValue: [boolean, string]; + + constructor( + actionData: ISetProtectionActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + enable: actionData.enable, + password: actionData.password, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + this._oldValue = SetProtectionService( + worksheet, + this._doActionData.enable, + this._doActionData.password + ); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + SetProtectionService(worksheet, this._oldValue[0], this._oldValue[1]); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Module/Protection/Action/index.ts b/packages/core/src/Module/Protection/Action/index.ts new file mode 100644 index 0000000000..6aac16d390 --- /dev/null +++ b/packages/core/src/Module/Protection/Action/index.ts @@ -0,0 +1,3 @@ +export * from './AddAllowedAction'; +export * from './AddUnlockAction'; +export * from './SetProtectionAction'; diff --git a/packages/core/src/Module/Protection/Allowed.ts b/packages/core/src/Module/Protection/Allowed.ts new file mode 100644 index 0000000000..e1165ae771 --- /dev/null +++ b/packages/core/src/Module/Protection/Allowed.ts @@ -0,0 +1,20 @@ +import { Range } from '../../Sheets/Domain'; + +export class Allowed { + protected _password: string; + + protected _range: Range; + + constructor(password: string, range: Range) { + this._password = password; + this._range = range; + } + + getRange(): Range { + return this._range; + } + + getPassword(): string { + return this._password; + } +} diff --git a/packages/core/src/Module/Protection/AllowedList.ts b/packages/core/src/Module/Protection/AllowedList.ts new file mode 100644 index 0000000000..09a1b6d5ce --- /dev/null +++ b/packages/core/src/Module/Protection/AllowedList.ts @@ -0,0 +1,41 @@ +import { Nullable, Rectangle } from '../../Shared'; +import { Allowed } from './Allowed'; + +export class AllowedList { + protected _allowList: Allowed[]; + + constructor() { + this._allowList = new Array(); + } + + removeAllow(allow: Allowed): void { + for (let i = 0; i < this._allowList.length; i++) { + if (this._allowList[i] === allow) { + this._allowList.splice(i, 1); + } + } + } + + addAllow(allow: Allowed): void { + this._allowList.push(allow); + } + + isLockCell(row: number, column: number): Nullable { + for (let i = 0; i < this._allowList.length; i++) { + const allowed = this._allowList[i]; + const range = allowed.getRange(); + const data = range.getRangeData(); + const rect = new Rectangle( + data.startRow, + data.startColumn, + data.endRow, + data.endColumn + ); + const item = new Rectangle(row, column, row, column); + if (rect.intersects(item)) { + return allowed; + } + } + return null; + } +} diff --git a/packages/core/src/Module/Protection/Protection.ts b/packages/core/src/Module/Protection/Protection.ts new file mode 100644 index 0000000000..25ca89dbdf --- /dev/null +++ b/packages/core/src/Module/Protection/Protection.ts @@ -0,0 +1,165 @@ +import { Nullable, Rectangle } from '../../Shared'; +import { Allowed } from './Allowed'; +import { AllowedList } from './AllowedList'; +import { UnlockList } from './UnlockList'; + +export class Protection { + protected _unlockList: UnlockList; + + protected _allowedList: AllowedList; + + protected _useColumnFormat: boolean; + + protected _useRowFormat: boolean; + + protected _useInsertRow: boolean; + + protected _useInsertColumn: boolean; + + protected _useRemoveRow: boolean; + + protected _useRemoveColumn: boolean; + + protected _useCellFormat: boolean; + + protected _useSort: boolean; + + protected _useFilter: boolean; + + protected _useSelectLockCell: boolean; + + protected _useSelectUnlockCell: boolean; + + protected _enable: boolean; + + protected _password: string; + + constructor() { + this._unlockList = new UnlockList(); + this._allowedList = new AllowedList(); + } + + isEnable(): boolean { + return this._enable; + } + + isLockCell(row: number, column: number): Nullable { + return this._unlockList.isLockCell(row, column); + } + + isAllowCell(row: number, column: number): Nullable { + return this._allowedList.isLockCell(row, column); + } + + getUseCellFormat(): boolean { + return this._useCellFormat; + } + + getUseSort(): boolean { + return this._useSort; + } + + getUseFilter(): boolean { + return this._useRemoveColumn; + } + + getUseSelectLockCell(): boolean { + return this._useSelectLockCell; + } + + getUseSelectUnlockCell(): boolean { + return this._useSelectUnlockCell; + } + + getUseColumnFormat(): boolean { + return this._useCellFormat; + } + + getUseRowFormat(): boolean { + return this._useCellFormat; + } + + getUseInsertRow(): boolean { + return this._useCellFormat; + } + + getUseInsertColumn(): boolean { + return this._useInsertColumn; + } + + getEnable(): boolean { + return this._enable; + } + + getUseRemoveRow(): boolean { + return this._useRemoveRow; + } + + getPassword(): string { + return this._password; + } + + getAllowedList(): AllowedList { + return this._allowedList; + } + + getUnlockList(): UnlockList { + return this._unlockList; + } + + getUseRemoveColumn(): boolean { + return this._useRemoveColumn; + } + + setUseSort(): boolean { + return this._useSort; + } + + setUseFilter(): boolean { + return this._useRemoveColumn; + } + + setUseCellFormat(): boolean { + return this._useCellFormat; + } + + setEnable(enable: boolean): void { + this._enable = enable; + } + + setUseSelectLockCell(value: boolean): void { + this._useSelectLockCell = value; + } + + setUseSelectUnlockCell(value: boolean): void { + this._useSelectUnlockCell = value; + } + + setUseColumnFormat(value: boolean): void { + this._useCellFormat = value; + } + + setPassword(password: string): void { + this._password = password; + } + + setUseRowFormat(value: boolean): void { + this._useCellFormat = value; + } + + setUseInsertRow(value: boolean): void { + this._useCellFormat = value; + } + + setUseInsertColumn(value: boolean): void { + this._useInsertColumn = value; + } + + setUseRemoveRow(value: boolean): void { + this._useRemoveRow = value; + } + + setUseRemoveColumn(value: boolean): void { + this._useRemoveColumn = value; + } +} diff --git a/packages/core/src/Module/Protection/Service/AddAllowedService.ts b/packages/core/src/Module/Protection/Service/AddAllowedService.ts new file mode 100644 index 0000000000..213a019b1d --- /dev/null +++ b/packages/core/src/Module/Protection/Service/AddAllowedService.ts @@ -0,0 +1,7 @@ +import { WorkSheet } from '../../../Sheets/Domain'; +import { Allowed } from '../Allowed'; + +export function AddAllowedService(workSheet: WorkSheet, allowed: Allowed) { + const protection = workSheet.getProtection(); + protection.getAllowedList().addAllow(allowed); +} diff --git a/packages/core/src/Module/Protection/Service/AddUnlockService.ts b/packages/core/src/Module/Protection/Service/AddUnlockService.ts new file mode 100644 index 0000000000..ad47f8ea79 --- /dev/null +++ b/packages/core/src/Module/Protection/Service/AddUnlockService.ts @@ -0,0 +1,6 @@ +import { WorkSheet, Range } from '../../../Sheets/Domain'; + +export function AddUnlockService(workSheet: WorkSheet, unlock: Range) { + const protection = workSheet.getProtection(); + protection.getUnlockList().addUnlock(unlock); +} diff --git a/packages/core/src/Module/Protection/Service/RemoveAllowedService.ts b/packages/core/src/Module/Protection/Service/RemoveAllowedService.ts new file mode 100644 index 0000000000..a31a74c11d --- /dev/null +++ b/packages/core/src/Module/Protection/Service/RemoveAllowedService.ts @@ -0,0 +1,7 @@ +import { WorkSheet } from '../../../Sheets/Domain'; +import { Allowed } from '../Allowed'; + +export function RemoveAllowedService(workSheet: WorkSheet, allowed: Allowed) { + const protection = workSheet.getProtection(); + protection.getAllowedList().removeAllow(allowed); +} diff --git a/packages/core/src/Module/Protection/Service/RemoveUnlockService.ts b/packages/core/src/Module/Protection/Service/RemoveUnlockService.ts new file mode 100644 index 0000000000..1efb4bfa7d --- /dev/null +++ b/packages/core/src/Module/Protection/Service/RemoveUnlockService.ts @@ -0,0 +1,6 @@ +import { Range, WorkSheet } from '../../../Sheets/Domain'; + +export function RemoveUnlockService(workSheet: WorkSheet, unlock: Range) { + const protection = workSheet.getProtection(); + protection.getUnlockList().removeUnlock(unlock); +} diff --git a/packages/core/src/Module/Protection/Service/SetProtectionService.ts b/packages/core/src/Module/Protection/Service/SetProtectionService.ts new file mode 100644 index 0000000000..9239a08b24 --- /dev/null +++ b/packages/core/src/Module/Protection/Service/SetProtectionService.ts @@ -0,0 +1,14 @@ +import { WorkSheet } from '../../../Sheets/Domain'; + +export function SetProtectionService( + workSheet: WorkSheet, + enable: boolean, + password: string +): [boolean, string] { + const protection = workSheet.getProtection(); + const oldEnable = protection.getEnable(); + const oldPassword = protection.getPassword(); + protection.setPassword(password); + protection.setEnable(enable); + return [oldEnable, oldPassword]; +} diff --git a/packages/core/src/Module/Protection/Service/index.ts b/packages/core/src/Module/Protection/Service/index.ts new file mode 100644 index 0000000000..f77b53954d --- /dev/null +++ b/packages/core/src/Module/Protection/Service/index.ts @@ -0,0 +1,5 @@ +export * from './AddAllowedService'; +export * from './AddUnlockService'; +export * from './RemoveUnlockService'; +export * from './RemoveAllowedService'; +export * from './SetProtectionService'; diff --git a/packages/core/src/Module/Protection/UnlockList.ts b/packages/core/src/Module/Protection/UnlockList.ts new file mode 100644 index 0000000000..8913cb3acf --- /dev/null +++ b/packages/core/src/Module/Protection/UnlockList.ts @@ -0,0 +1,40 @@ +import { Range } from '../../Sheets/Domain'; +import { Nullable, Rectangle } from '../../Shared'; + +export class UnlockList { + protected _rangeList: Range[]; + + constructor() { + this._rangeList = new Array(); + } + + removeUnlock(range: Range): void { + for (let i = 0; i < this._rangeList.length; i++) { + if (this._rangeList[i] === range) { + this._rangeList.splice(i, 1); + } + } + } + + addUnlock(range: Range): void { + this._rangeList.push(range); + } + + isLockCell(row: number, column: number): Nullable { + for (let i = 0; i < this._rangeList.length; i++) { + const range = this._rangeList[i]; + const data = range.getRangeData(); + const rect = new Rectangle( + data.startRow, + data.startColumn, + data.endRow, + data.endColumn + ); + const item = new Rectangle(row, column, row, column); + if (rect.intersects(item)) { + return rect; + } + } + return null; + } +} diff --git a/packages/core/src/Module/Protection/index.ts b/packages/core/src/Module/Protection/index.ts new file mode 100644 index 0000000000..6225b7b7d3 --- /dev/null +++ b/packages/core/src/Module/Protection/index.ts @@ -0,0 +1,4 @@ +export * from './Protection'; +export * from './Allowed'; +export * from './AllowedList'; +export * from './UnlockList'; diff --git a/packages/core/src/Module/Sorted/index.ts b/packages/core/src/Module/Sorted/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/Observer/HooksManager.ts b/packages/core/src/Observer/HooksManager.ts new file mode 100644 index 0000000000..fa2727b1b9 --- /dev/null +++ b/packages/core/src/Observer/HooksManager.ts @@ -0,0 +1,38 @@ +import { Nullable } from '../Shared'; +import { ObservableHooks } from './ObservableHooks'; + +/** + * Observer hook + */ +export class PathObservableHooks { + namespace: string; + + name: string; + + observableHooks: ObservableHooks; + + constructor(name: string, hooks: ObservableHooks) { + this.name = name; + this.observableHooks = hooks; + } +} + +/** + * Manage a set of observer hooks, add and get hooks + */ +export class HooksManager { + private _observableHooksArray: PathObservableHooks[]; + + constructor() { + this._observableHooksArray = new Array(); + } + + addHooks(name: string, hooks: ObservableHooks): void { + this._observableHooksArray.push(new PathObservableHooks(name, hooks)); + } + + getHooks(name: string): Nullable> { + const item = this._observableHooksArray.find((hook) => hook.name === name); + return item ? item.observableHooks : null; + } +} diff --git a/packages/core/src/Observer/Observable.ts b/packages/core/src/Observer/Observable.ts new file mode 100644 index 0000000000..8891c1e96c --- /dev/null +++ b/packages/core/src/Observer/Observable.ts @@ -0,0 +1,384 @@ +import { Nullable } from '../Shared'; +/** + * A class serves as a medium between the observable and its observers + */ +export class EventState { + /** + * Create a new EventState + * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true + * @param target defines the original target of the state + * @param currentTarget defines the current target of the state + */ + constructor(skipNextObservers = false) { + this.initialize(skipNextObservers); + } + + /** + * Initialize the current event state + * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true + * @param target defines the original target of the state + * @param currentTarget defines the current target of the state + * @returns the current event state + */ + initialize(skipNextObservers = false): EventState { + this.skipNextObservers = skipNextObservers; + return this; + } + + /** + * An Observer can set this property to true to prevent subsequent observers of being notified + */ + skipNextObservers: boolean | undefined; + + /** + * This will be populated with the return value of the last function that was executed. + * If it is the first function in the callback chain it will be the event data. + */ + lastReturnValue?: unknown; + + isStopPropagation: boolean = false; + + stopPropagation() { + this.isStopPropagation = true; + } +} + +interface INotifyObserversReturn { + lastReturnValue: unknown; + stopPropagation: boolean; +} + +/** + * Represent an Observer registered to a given Observable object. + */ +export class Observer { + /** @hidden */ + _willBeUnregistered = false; + + /** + * Gets or sets a property defining that the observer as to be unregistered after the next notification + */ + unregisterOnNextCall = false; + + /** + * Creates a new observer + * @param callback defines the callback to call when the observer is notified + */ + constructor( + /** + * Defines the callback to call when the observer is notified + */ + public callback: (eventData: T, eventState: EventState) => void + ) {} +} + +/** + * The Observable class is a simple implementation of the Observable pattern. + * + * @remarks + * There's one slight particularity though: a given Observable can notify its observer using a particular mask value, only the Observers registered with this mask value will be notified. + * This enable a more fine grained execution without having to rely on multiple different Observable objects. + * For instance you may have a given Observable that have four different types of notifications: Move (mask = 0x01), Stop (mask = 0x02), Turn Right (mask = 0X04), Turn Left (mask = 0X08). + * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right. + */ +export class Observable { + protected _observers = new Array>(); + + protected _eventState: EventState; + + protected _onObserverAdded: + | Nullable<(observer: Observer) => void> + | undefined; + + /** + * Gets the list of observers + */ + get observers(): Array> { + return this._observers; + } + + /** + * Creates a new observable + * @param onObserverAdded defines a callback to call when a new observer is added + */ + constructor(onObserverAdded?: (observer: Observer) => void) { + this._eventState = new EventState(); + + if (onObserverAdded) { + this._onObserverAdded = onObserverAdded; + } + } + + /** + * Create a new Observer with the specified callback + * @param callback the callback that will be executed for that Observer + * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present. + * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification + * @returns the new observer created for the callback + */ + add( + callback: (eventData: T, eventState: EventState) => void, + insertFirst = false, + unregisterOnFirstCall = false + ): Nullable> { + if (!callback) { + return null; + } + + const observer = new Observer(callback); + observer.unregisterOnNextCall = unregisterOnFirstCall; + + if (insertFirst) { + this._observers.unshift(observer); + } else { + this._observers.push(observer); + } + + if (this._onObserverAdded) { + this._onObserverAdded(observer); + } + + return observer; + } + + /** + * Create a new Observer with the specified callback and unregisters after the next notification + * @param callback the callback that will be executed for that Observer + * @returns the new observer created for the callback + */ + addOnce( + callback: (eventData: T, eventState: EventState) => void + ): Nullable> { + return this.add(callback, undefined, true); + } + + /** + * Remove an Observer from the Observable object + * @param observer the instance of the Observer to remove + * @returns false if it doesn't belong to this Observable + */ + remove(observer: Nullable>): boolean { + if (!observer) { + return false; + } + + const index = this._observers.indexOf(observer); + + if (index !== -1) { + this._deferUnregister(observer); + return true; + } + + return false; + } + + /** + * Remove a callback from the Observable object + * @param callback the callback to remove + * @returns false if it doesn't belong to this Observable + */ + removeCallback( + callback: (eventData: T, eventState: EventState) => void + ): boolean { + for (let index = 0; index < this._observers.length; index++) { + const observer = this._observers[index]; + if (observer._willBeUnregistered) { + continue; + } + if (observer.callback === callback) { + this._deferUnregister(observer); + return true; + } + } + + return false; + } + + private _deferUnregister(observer: Observer): void { + observer.unregisterOnNextCall = false; + observer._willBeUnregistered = true; + setTimeout(() => { + this._remove(observer); + }, 0); + } + + // This should only be called when not iterating over _observers to avoid callback skipping. + // Removes an observer from the _observer Array. + private _remove(observer: Nullable>): boolean { + if (!observer) { + return false; + } + + const index = this._observers.indexOf(observer); + + if (index !== -1) { + this._observers.splice(index, 1); + return true; + } + + return false; + } + + /** + * Moves the observable to the top of the observer list making it get called first when notified + * @param observer the observer to move + */ + makeObserverTopPriority(observer: Observer) { + this._remove(observer); + this._observers.unshift(observer); + } + + /** + * Moves the observable to the bottom of the observer list making it get called last when notified + * @param observer the observer to move + */ + makeObserverBottomPriority(observer: Observer) { + this._remove(observer); + this._observers.push(observer); + } + + /** + * Notify all Observers by calling their respective callback with the given data + * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute + * @param eventData defines the data to send to all observers + * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true) + */ + notifyObservers(eventData: T): Nullable { + if (!this._observers.length) { + return null; + } + + const state = this._eventState; + state.skipNextObservers = false; + state.lastReturnValue = eventData; + state.isStopPropagation = false; + let _isStopPropagation = false; + + for (let index = 0; index < this._observers.length; index++) { + const obs = this._observers[index]; + if (obs._willBeUnregistered) { + continue; + } + + state.lastReturnValue = obs.callback(eventData, state); + + if (obs.unregisterOnNextCall) { + this._deferUnregister(obs); + } + + if (state.isStopPropagation) { + _isStopPropagation = true; + } + + if (state.skipNextObservers) { + return { + lastReturnValue: state.lastReturnValue, + stopPropagation: _isStopPropagation, + }; + } + } + return { + lastReturnValue: state.lastReturnValue, + stopPropagation: _isStopPropagation, + }; + } + + /** + * Calling this will execute each callback, expecting it to be a promise or return a value. + * If at any point in the chain one function fails, the promise will fail and the execution will not continue. + * This is useful when a chain of Events (sometimes async Events) is needed to initialize a certain object + * and it is crucial that all callbacks will be executed. + * The order of the callbacks is kept, callbacks are not executed parallel. + * + * @param eventData The data to be sent to each callback + * @returns {Promise} will return a Promise than resolves when all callbacks executed successfully. + */ + notifyObserversWithPromise(eventData: T): Promise { + // create an empty promise + let p: Promise = Promise.resolve(eventData); + + // no observers? return this promise. + if (!this._observers.length) { + return p; + } + + const state = this._eventState; + state.skipNextObservers = false; + + // execute one callback after another (not using Promise.all, the order is important) + for (let index = 0; index < this._observers.length; index++) { + const obs = this._observers[index]; + if (state.skipNextObservers) { + continue; + } + if (obs._willBeUnregistered) { + continue; + } + + p = p.then(() => obs.callback(eventData, state)); + + if (obs.unregisterOnNextCall) { + this._deferUnregister(obs); + } + } + + // return the eventData + return p.then(() => eventData); + } + + /** + * Notify a specific observer + * @param observer defines the observer to notify + * @param eventData defines the data to be sent to each callback + */ + notifyObserver( + observer: Observer, + eventData: T + ): Nullable { + if (observer._willBeUnregistered) { + return; + } + + const state = this._eventState; + state.skipNextObservers = false; + + observer.callback(eventData, state); + + if (observer.unregisterOnNextCall) { + this._deferUnregister(observer); + } + + return { + lastReturnValue: state.lastReturnValue, + stopPropagation: state.isStopPropagation, + }; + } + + /** + * Gets a boolean indicating if the observable has at least one observer + * @returns true is the Observable has at least one Observer registered + */ + hasObservers(): boolean { + return this._observers.length > 0; + } + + /** + * Clear the list of observers + */ + clear(): void { + this._observers = new Array>(); + this._onObserverAdded = null; + } + + /** + * Clone the current observable + * @returns a new observable + */ + clone(): Observable { + const result = new Observable(); + + result._observers = this._observers.slice(0); + + return result; + } +} diff --git a/packages/core/src/Observer/ObservableHooks.ts b/packages/core/src/Observer/ObservableHooks.ts new file mode 100644 index 0000000000..4bc59f471e --- /dev/null +++ b/packages/core/src/Observer/ObservableHooks.ts @@ -0,0 +1,39 @@ +import { Nullable } from '../Shared'; +import { EventState, Observable, Observer } from '.'; + +/** + * Observable Hooks + */ +export class ObservableHooks extends Observable { + /** + * Create a new Observer with the specified callback + * @param callback the callback that will be executed for that Observer + * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present. + * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification + * @returns the new observer created for the callback + */ + add( + callback: (eventData: T, eventState: EventState) => void + ): Nullable> { + if (!callback) { + return null; + } + + const observer = new Observer(callback); + this._observers = [observer]; + + if (this._onObserverAdded) { + this._onObserverAdded(observer); + } + + return observer; + } + + makeObserverTopPriority() { + /** cancel * */ + } + + makeObserverBottomPriority() { + /** cancel * */ + } +} diff --git a/packages/core/src/Observer/ObserverManager.ts b/packages/core/src/Observer/ObserverManager.ts new file mode 100644 index 0000000000..df6b17b51f --- /dev/null +++ b/packages/core/src/Observer/ObserverManager.ts @@ -0,0 +1,129 @@ +import { Nullable, Tools } from '../Shared'; +import { Observable } from './Observable'; + +/** + * Observer for the specified path + */ +export class PathObservable { + namespace: string; + + path: string; + + observable: Observable; + + constructor(path: string, namespace: string, observable: Observable) { + this.path = path; + this.namespace = namespace; + this.observable = observable; + } +} + +/** + * Manage a set of PathObservables, Get, remove, add Observer + */ +export class ObserverManager { + private _observableArray: PathObservable[] = []; + + hasObserver(name: string, namespace: string): boolean { + return this.getObserver(name, namespace) != null; + } + + getObserver(name: string): Nullable>; + getObserver(name: string, namespace: string): Nullable>; + getObserver(...parameter: any): Nullable> { + if (parameter.length === 1) { + const name = parameter[0]; + + const item = this._observableArray.find((obs) => obs.path === name); + return item ? item.observable : null; + } + if (parameter.length === 2) { + const name = parameter[0]; + const namespace = parameter[1]; + + const item = this._observableArray.find( + (obs) => obs.path === name && obs.namespace === namespace + ); + return item ? item.observable : null; + } + } + + requiredObserver(name: string): Observable; + requiredObserver(name: string, namespace: string): Observable; + requiredObserver(...parameter: any): Observable { + if (Tools.hasLength(parameter, 1)) { + const name = parameter[0]; + const observable = this.getObserver(name); + if (observable == null) { + throw new Error(`not found observable ${name}`); + } + return observable; + } + if (Tools.hasLength(parameter, 2)) { + const name = parameter[0]; + const namespace = parameter[1]; + const observable = this.getObserver(name, namespace); + if (observable == null) { + throw new Error(`not found observable ${name}`); + } + return observable; + } + throw new Error(`requiredObserver arguments error`); + } + + removeObserver(name: string): void; + removeObserver(name: string, namespace: string): void; + removeObserver(...parameter: any): void { + if (Tools.hasLength(parameter, 1)) { + const name = parameter[0]; + const index = this._observableArray.findIndex( + (obs) => obs.path === name + ); + if (index > -1) { + this._observableArray.splice(index, 1); + } + return; + } + if (Tools.hasLength(parameter, 2)) { + const name = parameter[0]; + const namespace = parameter[1]; + const index = this._observableArray.findIndex( + (obs) => obs.path === name && obs.namespace === namespace + ); + if (index > -1) { + this._observableArray.splice(index, 1); + } + } + } + + addObserver(name: string, observable: Observable): void; + addObserver( + name: string, + namespace: string, + observable: Observable + ): void; + addObserver(...parameter: any): void { + if (parameter.length === 1) { + const name = parameter[0]; + const observable = parameter[1]; + if (this.hasObserver(name, name)) { + this.removeObserver(name); + } + this._observableArray.push( + new PathObservable(name, name, observable) + ); + return; + } + if (parameter.length === 3) { + const name = parameter[0]; + const namespace = parameter[1]; + const observable = parameter[2]; + if (this.hasObserver(name, name)) { + this.removeObserver(name, namespace); + } + this._observableArray.push( + new PathObservable(name, namespace, observable) + ); + } + } +} diff --git a/packages/core/src/Observer/index.ts b/packages/core/src/Observer/index.ts new file mode 100644 index 0000000000..789e599faa --- /dev/null +++ b/packages/core/src/Observer/index.ts @@ -0,0 +1,6 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './Observable'; +export * from './ObservableHooks'; +export * from './HooksManager'; +export * from './ObserverManager'; +// @endindex diff --git a/packages/core/src/Operation/IOperation.ts b/packages/core/src/Operation/IOperation.ts new file mode 100644 index 0000000000..b5db129b2c --- /dev/null +++ b/packages/core/src/Operation/IOperation.ts @@ -0,0 +1,4 @@ +/** + * Operation + */ +export interface IOperation {} diff --git a/packages/core/src/Operation/IOperationSet.ts b/packages/core/src/Operation/IOperationSet.ts new file mode 100644 index 0000000000..7f67d100cc --- /dev/null +++ b/packages/core/src/Operation/IOperationSet.ts @@ -0,0 +1,4 @@ +/** + * Set of operation + */ +export interface IOperationSet {} diff --git a/packages/core/src/Operation/MemberManger.ts b/packages/core/src/Operation/MemberManger.ts new file mode 100644 index 0000000000..1c70f19724 --- /dev/null +++ b/packages/core/src/Operation/MemberManger.ts @@ -0,0 +1,4 @@ +/** + * Member Manger + */ +export class MemberManger {} diff --git a/packages/core/src/Operation/OperationManager.ts b/packages/core/src/Operation/OperationManager.ts new file mode 100644 index 0000000000..89401dada7 --- /dev/null +++ b/packages/core/src/Operation/OperationManager.ts @@ -0,0 +1,4 @@ +/** + * Operation Manager + */ +export class OperationManager {} diff --git a/packages/core/src/Operation/index.ts b/packages/core/src/Operation/index.ts new file mode 100644 index 0000000000..ae3a396d64 --- /dev/null +++ b/packages/core/src/Operation/index.ts @@ -0,0 +1,6 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './IOperation'; +export * from './IOperationSet'; +export * from './MemberManger'; +export * from './OperationManager'; +// @endindex diff --git a/packages/core/src/Plugin/Plugin.ts b/packages/core/src/Plugin/Plugin.ts new file mode 100644 index 0000000000..67b60742cd --- /dev/null +++ b/packages/core/src/Plugin/Plugin.ts @@ -0,0 +1,99 @@ +import { Context } from '../Basics'; +import { Inject, IOCContainer } from '../IOC'; +import { Observable } from '../Observer'; +import { Nullable, PropsFrom } from '../Shared'; + +/** + * Basic function of plugin + */ +export interface BasePlugin { + getContext(): Context; + getPluginName(): string; + onMounted(context: Context): void; + /** + * mapping + * + * @param container - IOC container dependency injection + * @public + */ + onMapping(container: IOCContainer): void; + onDestroy(): void; + + deleteObserve(...names: string[]): void; + getPluginByName(name: string): Nullable; + /** + * save data + */ + save(): object; + /** + * load data + */ + load(data: T): void; +} + +/** + * Plug-in base class, all plug-ins must inherit from this base class. provides the basic method + */ +export abstract class Plugin implements BasePlugin { + @Inject('Context') + private _context: Context; + + private _name: string; + + private _observeNames: Array; + + protected constructor(name: string) { + this._name = name; + this._observeNames = []; + } + + load(data: T): void {} + + save(): object { + return Object(); + } + + onMounted(context: Context): void {} + + onDestroy(): void { + this.deleteObserve(...this._observeNames); + } + + onMapping(ioc: IOCContainer): void {} + + getPluginName(): string { + return this._name; + } + + getContext(): Context { + return this._context; + } + + getObserver( + name: K + ): Nullable>> { + const manager = this._context.getObserverManager(); + return manager.getObserver(name, this._name); + } + + getPluginByName(name: string): Nullable { + return this._context.getPluginManager().getPluginByName(name); + } + + pushToObserve(...names: K[]): void { + const manager = this._context.getObserverManager(); + names.forEach((name) => { + if (!this._observeNames.includes(name)) { + this._observeNames.push(name); + } + manager.addObserver(name, this._name, new Observable()); + }); + } + + deleteObserve(...names: K[]): void { + const manager = this._context.getObserverManager(); + names.forEach((name) => { + manager.removeObserver(name, this._name); + }); + } +} diff --git a/packages/core/src/Plugin/PluginManager.ts b/packages/core/src/Plugin/PluginManager.ts new file mode 100644 index 0000000000..6cd2fba959 --- /dev/null +++ b/packages/core/src/Plugin/PluginManager.ts @@ -0,0 +1,85 @@ +import { Context } from '../Basics'; +import { Container, Inject, IOCContainer, PostConstruct } from '../IOC'; +import { BasePlugin } from './Plugin'; +import { Nullable } from '../Shared'; + +/** + * Provides APIs to initialize, install, and uninstall plugins + */ +export class PluginManager { + @Container() + protected _container: IOCContainer; + + @Inject('Context') + protected _context: Context; + + protected _plugins: BasePlugin[]; + + protected _initialized: boolean; + + @PostConstruct() + protected _initialize() { + if (this._container && this._initialized) { + this._initialized = false; + this._plugins.forEach((plugin: BasePlugin) => { + plugin.onMapping(this._container); + this._container.inject(plugin); + plugin.onMounted(this._context); + }); + } + } + + constructor(context: Context, plugins: BasePlugin[] = []) { + this._context = context; + this._plugins = []; + this._initialized = true; + this._plugins = this._plugins.concat(plugins); + this._initialize(); + } + + install(plugin: BasePlugin) { + const { _plugins } = this; + _plugins.push(plugin); + plugin.onMapping(this._container); + this._container.inject(plugin); + plugin.onMounted(this._context); + } + + uninstall(name: string) { + const { _plugins } = this; + const index = _plugins.findIndex((item) => item.getPluginName() === name); + if (index > -1) { + const plugin = _plugins.splice(index, 1)[0]; + if (plugin) { + plugin.onDestroy(); + } + } + } + + setContext(context: Context) { + this._context = context; + } + + getRequirePluginByName(pluginName: string): T { + for (let i = 0; i < this._plugins.length; i++) { + if (this._plugins[i].getPluginName() === pluginName) { + return this._plugins[i] as T; + } + } + + throw new Error(`not found plugin ${pluginName}`); + } + + getPlugins(): BasePlugin[] { + return this._plugins; + } + + getPluginByName(pluginName: string): Nullable { + for (let i = 0; i < this._plugins.length; i++) { + if (this._plugins[i].getPluginName() === pluginName) { + return this._plugins[i] as T; + } + } + return null; + } +} diff --git a/packages/core/src/Plugin/index.ts b/packages/core/src/Plugin/index.ts new file mode 100644 index 0000000000..bb40d407d2 --- /dev/null +++ b/packages/core/src/Plugin/index.ts @@ -0,0 +1,4 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './Plugin'; +export * from './PluginManager'; +// @endindex diff --git a/packages/core/src/ProxyData/IProxyData.ts b/packages/core/src/ProxyData/IProxyData.ts new file mode 100644 index 0000000000..ac3f266f9a --- /dev/null +++ b/packages/core/src/ProxyData/IProxyData.ts @@ -0,0 +1 @@ +export interface IProxyData {} diff --git a/packages/core/src/Server/MessageQueue.ts b/packages/core/src/Server/MessageQueue.ts new file mode 100644 index 0000000000..3ada100a79 --- /dev/null +++ b/packages/core/src/Server/MessageQueue.ts @@ -0,0 +1,54 @@ +const STORAGE_NAME = 'MessageQueue'; + +/** + * Message Queue + */ +export class MessageQueue { + private _messages: T[]; + + constructor() { + this.load(); + } + + first() { + return this._messages[0]; + } + + last() { + return this._messages[this._messages.length - 1]; + } + + size() { + return this._messages.length; + } + + pop() { + const result = this._messages.shift(); + this.save(); + } + + push(data: T) { + this._messages.push(data); + this.save(); + } + + hasMessage() { + return this._messages.length > 0; + } + + isEmpty() { + return !this.hasMessage(); + } + + save() { + const json = JSON.stringify(this._messages); + localStorage.setItem(STORAGE_NAME, json); + } + + load() { + const json = localStorage.getItem(STORAGE_NAME); + if (json) { + this._messages = JSON.parse(json); + } + } +} diff --git a/packages/core/src/Server/ServerBase.ts b/packages/core/src/Server/ServerBase.ts new file mode 100644 index 0000000000..c3d582827c --- /dev/null +++ b/packages/core/src/Server/ServerBase.ts @@ -0,0 +1,54 @@ +import { IActionData } from '../Command'; +import { Tools } from '../Shared'; + +/** + * Properties of server message + */ +export interface IOServerMessage { + version: string; + changed: IActionData[]; +} + +/** + * Properties of server receive + */ +export interface IOServerReceive { + type: ServerReceiveType; + message: T; +} + +/** + * Type of server receive + */ +export enum ServerReceiveType { + MESSAGE_RESPONSE = 'message_response', +} + +/** + * Base class for Server + */ +export abstract class ServerBase { + static isMessageReponseReceive( + receive: IOServerReceive + ): receive is IOServerReceive { + return receive.type === ServerReceiveType.MESSAGE_RESPONSE; + } + + /** + * Pack the action data + * + * @privateRemarks + * zh: 将action数据打包 + * + * @param changed + * @returns + */ + packMessage(changed: IActionData[]): IOServerMessage { + return { + changed, + version: Tools.generateRandomId(), + }; + } + + abstract pushMessageQueue(data: IActionData[]): void; +} diff --git a/packages/core/src/Server/ServerHttp.ts b/packages/core/src/Server/ServerHttp.ts new file mode 100644 index 0000000000..74dbe52f81 --- /dev/null +++ b/packages/core/src/Server/ServerHttp.ts @@ -0,0 +1,11 @@ +import { IActionData } from '../Command'; +import { ServerBase } from './ServerBase'; + +/** + * Http wrapper + */ +export class ServerHttp extends ServerBase { + pushMessageQueue(data: IActionData[]): void { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/core/src/Server/ServerSocket.ts b/packages/core/src/Server/ServerSocket.ts new file mode 100644 index 0000000000..935d48bfc4 --- /dev/null +++ b/packages/core/src/Server/ServerSocket.ts @@ -0,0 +1,132 @@ +import { IActionData } from '../Command'; +import { IWorkbookConfig } from '../Interfaces'; +import { AttributeValue, PostConstruct } from '../IOC/IOCContainer'; +import { IOSocket, IOSocketListenType } from '../Shared'; +import { MessageQueue } from './MessageQueue'; +import { IOServerMessage, IOServerReceive, ServerBase } from './ServerBase'; + +/** + * Status of message queue + */ +export enum MessageQueueStatus { + WAIT = 'wait', + WORK = 'work', +} + +/** + * Manage messageQueue + */ +export class ServerSocket extends ServerBase { + @AttributeValue() + private config: IWorkbookConfig; + + private globalSendResolve: Function; + + private socket: IOSocket; + + private status: MessageQueueStatus; + + private messageQueue: MessageQueue; + + @PostConstruct() + initialize() { + this.messageQueue = new MessageQueue(); + this.status = MessageQueueStatus.WAIT; + this.globalSendResolve = () => {}; + if (this.config.socketEnable) { + this.socket = new IOSocket({ url: this.config.socketUrl }); + this.socket.on(IOSocketListenType.MESSAGE, (event: MessageEvent) => { + this.checkupReceiveMessage(event.data); + }); + this.socket.on(IOSocketListenType.ERROR, () => {}); + this.socket.on(IOSocketListenType.OPEN, () => {}); + this.socket.on(IOSocketListenType.CLOSE, () => {}); + this.socket.on(IOSocketListenType.RETRY, () => {}); + this.socket.on(IOSocketListenType.DESTROY, () => {}); + this.socket.link(); + } + } + + /** + * get first message + * + * @privateRemarks + * zh: 获取第一条消息 + * @returns + */ + private getTopMessage(): IOServerMessage { + return this.messageQueue.first(); + } + + /** + * zh: 弹出第一条消息 + * @returns + */ + private popTopMessage(): void { + this.messageQueue.pop(); + } + + /** + * zh: 序列化第一条消息 + * @returns + */ + private serializeTopMessage(): string { + const data = this.getTopMessage(); + return JSON.stringify(data) ?? ''; + } + + /** + * zh: 检查接收的消息 + * @param event + * @returns + */ + private checkupReceiveMessage(receive: IOServerReceive) { + if (ServerSocket.isMessageReponseReceive(receive)) { + if (this.getTopMessage().version === receive.message.version) { + this.popTopMessage(); + this.globalSendResolve(); + } + } + } + + /** + * zh: 消息添加到发送队列中 + * @param data + */ + pushMessageQueue(changed: IActionData[]): void { + if (this.config.socketEnable) { + this.messageQueue.push(this.packMessage(changed)); + this.sendMessageQueue(); + } + } + + /** + * zh: 发送第一条消息 + * @returns + */ + private async sendTopMessage(): Promise { + const message = this.serializeTopMessage(); + this.socket.send(message); + return new Promise( + // TODO + // eslint-disable-next-line no-promise-executor-return + (resolve) => (this.globalSendResolve = resolve) + ); + } + + /** + * zh: 发送消息队列 + * @returns + */ + private async sendMessageQueue(): Promise { + const { messageQueue, status } = this; + if (status === MessageQueueStatus.WAIT) { + this.status = MessageQueueStatus.WORK; + while (messageQueue.hasMessage()) { + await this.sendTopMessage(); + } + this.status = MessageQueueStatus.WAIT; + } + return this.status; + } +} diff --git a/packages/core/src/Server/index.ts b/packages/core/src/Server/index.ts new file mode 100644 index 0000000000..ac83e2c9a5 --- /dev/null +++ b/packages/core/src/Server/index.ts @@ -0,0 +1,6 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './MessageQueue'; +export * from './ServerBase'; +export * from './ServerHttp'; +export * from './ServerSocket'; +// @endindex diff --git a/packages/core/src/Shared/ArraySearch.ts b/packages/core/src/Shared/ArraySearch.ts new file mode 100644 index 0000000000..bff631e2b0 --- /dev/null +++ b/packages/core/src/Shared/ArraySearch.ts @@ -0,0 +1,74 @@ +export function binarySearchArray(arr: number[], pos: number) { + let low: number = 0; + let high = arr.length - 1; + + while (low <= high) { + let mid = Math.floor((high + low) / 2); + + if (pos < arr[mid] && (mid === 0 || pos >= arr[mid - 1])) { + return mid; + } + if (pos >= arr[mid]) { + low = mid + 1; + } else if (pos < arr[mid]) { + high = mid - 1; + } else { + return -1; + } + } + + return -1; +} + +export function orderSearchArray(arr: number[], pos: number) { + let i = 0; + let cur = 0; + let cur_pre = 0; + let cur_index = -1; + let i_ed = arr.length - 1; + + while (i < arr.length && i_ed >= 0 && i_ed >= i) { + cur = arr[i_ed]; + + if (i_ed === 0) { + cur_pre = 0; + } else { + cur_pre = arr[i_ed - 1]; + } + + if (pos >= cur_pre && pos < cur) { + cur_index = i_ed; + break; + } + + cur = arr[i]; + + if (i === 0) { + cur_pre = 0; + } else { + cur_pre = arr[i - 1]; + } + + if (pos >= cur_pre && pos < cur) { + cur_index = i; + break; + } + + i++; + i_ed--; + } + + return cur_index; +} + +export function searchArray(arr: number[], num: number) { + let index: number = arr.length - 1; + + if (arr.length < 40 || num <= arr[20] || num >= arr[index - 20]) { + index = orderSearchArray(arr, num); + } else { + index = binarySearchArray(arr, num); + } + + return index; +} diff --git a/packages/core/src/Shared/DateTime.ts b/packages/core/src/Shared/DateTime.ts new file mode 100644 index 0000000000..0844e70f6a --- /dev/null +++ b/packages/core/src/Shared/DateTime.ts @@ -0,0 +1,4 @@ +/** + * Date Tool + */ +export class DateTime {} diff --git a/packages/core/src/Shared/DropCell.ts b/packages/core/src/Shared/DropCell.ts new file mode 100644 index 0000000000..c93b22220a --- /dev/null +++ b/packages/core/src/Shared/DropCell.ts @@ -0,0 +1,2457 @@ +import dayjs from 'dayjs'; +import { AutoFillSeries, Direction, FormatType } from '../Enum'; +import { generate, update } from './Generate'; + +/** + * Drop down fill + * + * autofill: + 1.纯数字类型 + 2.扩展数字型(即一串字符最后面的是数字) + 3.日期类型 + 4.中文小写数字 或 一~日 + 5.周一~周日 星期一~星期日 + + */ +export class DropCell { + static applyType: AutoFillSeries | string | null; // 0复制单元格,1填充序列,2仅填充格式,3不带格式填充,4以天数填充,5以工作日填充,6以月填充,7以年填充,8以中文小写数字序列填充 + + static direction: Direction | null; // down-往下拖拽,right-往右拖拽,up-往上拖拽,left-往左拖拽 + + static chnNumChar: { + 零: 0; + 一: 1; + 二: 2; + 三: 3; + 四: 4; + 五: 5; + 六: 6; + 七: 7; + 八: 8; + 九: 9; + }; + + static chnNameValue: { + 十: { value: 10; secUnit: false }; + 百: { value: 100; secUnit: false }; + 千: { value: 1000; secUnit: false }; + 万: { value: 10000; secUnit: true }; + 亿: { value: 100000000; secUnit: true }; + }; + + static chnNumChar2: ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; + + static chnUnitChar: ['', '十', '百', '千']; + + static chnUnitSection: ['', '万', '亿', '万亿', '亿亿']; + + // constructor() {} + + static getApplyData( + copyD: any, + csLen: number, + asLen: number, + type: AutoFillSeries | string, + direction: Direction + ) { + const _this = this; + + const applyData = []; + + _this.applyType = type; + _this.direction = direction; + // let direction = _this.direction; + // let type = _this.applyType; + + const num = Math.floor(asLen / csLen); + const rsd = asLen % csLen; + + // 纯数字类型 + const copyD_number = copyD.number; + const applyD_number = []; + if (copyD_number) { + for (let i = 0; i < copyD_number.length; i++) { + const s = _this.getLenS(copyD_number[i].index, rsd); + const len = copyD_number[i].index.length * num + s; + + let arrData; + if (type === 1) { + arrData = _this.getDataByType( + copyD_number[i].data, + len, + direction!, + type, + 'number' + ); + } else if (type === '2') { + arrData = _this.getDataByType( + copyD_number[i].data, + len, + direction!, + type + ); + } else { + arrData = _this.getDataByType( + copyD_number[i].data, + len, + direction!, + '0' + ); + } + + const arrIndex = _this.getDataIndex( + csLen, + asLen, + copyD_number[i].index + ); + applyD_number.push({ data: arrData, index: arrIndex }); + } + } + + // 扩展数字型(即一串字符最后面的是数字) + const copyD_extendNumber = copyD.extendNumber; + const applyD_extendNumber = []; + if (copyD_extendNumber) { + for (let i = 0; i < copyD_extendNumber.length; i++) { + const s = _this.getLenS(copyD_extendNumber[i].index, rsd); + const len = copyD_extendNumber[i].index.length * num + s; + + let arrData; + if (type === '1' || type === '3') { + arrData = _this.getDataByType( + copyD_extendNumber[i].data, + len, + direction, + type, + 'extendNumber' + ); + } else if (type === '2') { + arrData = _this.getDataByType( + copyD_extendNumber[i].data, + len, + direction, + type + ); + } else { + arrData = _this.getDataByType( + copyD_extendNumber[i].data, + len, + direction, + '0' + ); + } + + const arrIndex = _this.getDataIndex( + csLen, + asLen, + copyD_extendNumber[i].index + ); + applyD_extendNumber.push({ data: arrData, index: arrIndex }); + } + } + + // 日期类型 + const copyD_date = copyD.date; + const applyD_date = []; + if (copyD_date) { + for (let i = 0; i < copyD_date.length; i++) { + const s = _this.getLenS(copyD_date[i].index, rsd); + const len = copyD_date[i].index.length * num + s; + + let arrData; + if (type === '1' || type === '3') { + arrData = _this.getDataByType( + copyD_date[i].data, + len, + direction, + type, + 'date' + ); + } else if (type === '8') { + arrData = _this.getDataByType( + copyD_date[i].data, + len, + direction, + '0' + ); + } else { + arrData = _this.getDataByType( + copyD_date[i].data, + len, + direction, + type + ); + } + + const arrIndex = _this.getDataIndex( + csLen, + asLen, + copyD_date[i].index + ); + applyD_date.push({ data: arrData, index: arrIndex }); + } + } + + // 中文小写数字 或 一~日 + const copyD_chnNumber = copyD.chnNumber; + const applyD_chnNumber = []; + if (copyD_chnNumber) { + for (let i = 0; i < copyD_chnNumber.length; i++) { + const s = _this.getLenS(copyD_chnNumber[i].index, rsd); + const len = copyD_chnNumber[i].index.length * num + s; + + let arrData; + if (type === '1' || type === '3') { + arrData = _this.getDataByType( + copyD_chnNumber[i].data, + len, + direction, + type, + 'chnNumber' + ); + } else if (type === '2' || type === '8') { + arrData = _this.getDataByType( + copyD_chnNumber[i].data, + len, + direction, + type + ); + } else { + arrData = _this.getDataByType( + copyD_chnNumber[i].data, + len, + direction, + '0' + ); + } + + const arrIndex = _this.getDataIndex( + csLen, + asLen, + copyD_chnNumber[i].index + ); + applyD_chnNumber.push({ data: arrData, index: arrIndex }); + } + } + + // 周一~周日 + const copyD_chnWeek2 = copyD.chnWeek2; + const applyD_chnWeek2 = []; + if (copyD_chnWeek2) { + for (let i = 0; i < copyD_chnWeek2.length; i++) { + const s = _this.getLenS(copyD_chnWeek2[i].index, rsd); + const len = copyD_chnWeek2[i].index.length * num + s; + + let arrData; + if (type === '1' || type === '3') { + arrData = _this.getDataByType( + copyD_chnWeek2[i].data, + len, + direction, + type, + 'chnWeek2' + ); + } else if (type === '2') { + arrData = _this.getDataByType( + copyD_chnWeek2[i].data, + len, + direction, + type + ); + } else { + arrData = _this.getDataByType( + copyD_chnWeek2[i].data, + len, + direction, + '0' + ); + } + + const arrIndex = _this.getDataIndex( + csLen, + asLen, + copyD_chnWeek2[i].index + ); + applyD_chnWeek2.push({ data: arrData, index: arrIndex }); + } + } + + // 星期一~星期日 + const copyD_chnWeek3 = copyD.chnWeek3; + const applyD_chnWeek3 = []; + if (copyD_chnWeek3) { + for (let i = 0; i < copyD_chnWeek3.length; i++) { + const s = _this.getLenS(copyD_chnWeek3[i].index, rsd); + const len = copyD_chnWeek3[i].index.length * num + s; + + let arrData; + if (type === '1' || type === '3') { + arrData = _this.getDataByType( + copyD_chnWeek3[i].data, + len, + direction, + type, + 'chnWeek3' + ); + } else if (type === '2') { + arrData = _this.getDataByType( + copyD_chnWeek3[i].data, + len, + direction, + type + ); + } else { + arrData = _this.getDataByType( + copyD_chnWeek3[i].data, + len, + direction, + '0' + ); + } + + const arrIndex = _this.getDataIndex( + csLen, + asLen, + copyD_chnWeek3[i].index + ); + applyD_chnWeek3.push({ data: arrData, index: arrIndex }); + } + } + + // 其它 + const copyD_other = copyD.other; + const applyD_other = []; + if (copyD_other) { + for (let i = 0; i < copyD_other.length; i++) { + const s = _this.getLenS(copyD_other[i].index, rsd); + const len = copyD_other[i].index.length * num + s; + + let arrData; + if (type === '2' || type === '3') { + arrData = _this.getDataByType( + copyD_other[i].data, + len, + direction, + type + ); + } else { + arrData = _this.getDataByType( + copyD_other[i].data, + len, + direction, + '0' + ); + } + + const arrIndex = _this.getDataIndex( + csLen, + asLen, + copyD_other[i].index + ); + applyD_other.push({ data: arrData, index: arrIndex }); + } + } + + for (let x = 1; x <= asLen; x++) { + if (applyD_number.length > 0) { + for (let y = 0; y < applyD_number.length; y++) { + if (x in applyD_number[y].index) { + applyData.push( + applyD_number[y].data[applyD_number[y].index[x]] + ); + } + } + } + if (applyD_extendNumber.length > 0) { + for (let y = 0; y < applyD_extendNumber.length; y++) { + if (x in applyD_extendNumber[y].index) { + applyData.push( + applyD_extendNumber[y].data[ + applyD_extendNumber[y].index[x] + ] + ); + } + } + } + + if (applyD_date.length > 0) { + for (let y = 0; y < applyD_date.length; y++) { + if (x in applyD_date[y].index) { + applyData.push(applyD_date[y].data[applyD_date[y].index[x]]); + } + } + } + + if (applyD_chnNumber.length > 0) { + for (let y = 0; y < applyD_chnNumber.length; y++) { + if (x in applyD_chnNumber[y].index) { + applyData.push( + applyD_chnNumber[y].data[applyD_chnNumber[y].index[x]] + ); + } + } + } + + if (applyD_chnWeek2.length > 0) { + for (let y = 0; y < applyD_chnWeek2.length; y++) { + if (x in applyD_chnWeek2[y].index) { + applyData.push( + applyD_chnWeek2[y].data[applyD_chnWeek2[y].index[x]] + ); + } + } + } + + if (applyD_chnWeek3.length > 0) { + for (let y = 0; y < applyD_chnWeek3.length; y++) { + if (x in applyD_chnWeek3[y].index) { + applyData.push( + applyD_chnWeek3[y].data[applyD_chnWeek3[y].index[x]] + ); + } + } + } + + if (applyD_other.length > 0) { + for (let y = 0; y < applyD_other.length; y++) { + if (x in applyD_other[y].index) { + applyData.push( + applyD_other[y].data[applyD_other[y].index[x]] + ); + } + } + } + } + + return applyData; + } + + static getCopyData( + d: any, + r1: number, + r2: number, + c1: number, + c2: number, + direction: Direction + ) { + const _this = this; + + const copyData = []; + + let a1: number = 0; + let a2: number = 0; + let b1: number = 0; + let b2: number = 0; + if (direction === Direction.BOTTOM || direction === Direction.TOP) { + a1 = c1; + a2 = c2; + b1 = r1; + b2 = r2; + } else if (direction === Direction.RIGHT || direction === Direction.LEFT) { + a1 = r1; + a2 = r2; + b1 = c1; + b2 = c2; + } + + for (let a = a1; a <= a2; a++) { + const obj = {}; + + let arrData = []; + let arrIndex = []; + let text = ''; + let extendNumberBeforeStr = null; + let extendNumberAfterStr = null; + let isSameStr = true; + + for (let b = b1, i = 0; b <= b2; b++, i++) { + // 单元格 + const data = d[i]; + // if (direction === Direction.BOTTOM || direction === Direction.TOP) { + // data = d[b][a]; + // } else if ( + // direction === Direction.RIGHT || + // direction === Direction.LEFT + // ) { + // data = d[a][b]; + // } + + // 单元格值类型 + let str; + if (!!data && !!data.v && !data.f) { + if (!!data.fm && data.fm.t === FormatType.NUMBER) { + str = 'number'; + extendNumberBeforeStr = null; + extendNumberAfterStr = null; + } else if (!!data.fm && data.fm.t === FormatType.DATE) { + str = 'date'; + extendNumberBeforeStr = null; + extendNumberAfterStr = null; + } else if (_this.isExtendNumber(data.m)[0]) { + str = 'extendNumber'; + + const isExtendNumber = _this.isExtendNumber(data.m); + + if ( + extendNumberBeforeStr === null || + extendNumberAfterStr === null + ) { + isSameStr = true; + extendNumberBeforeStr = isExtendNumber[2]; + extendNumberAfterStr = isExtendNumber[3]; + } else if ( + isExtendNumber[2] !== extendNumberBeforeStr || + isExtendNumber[3] !== extendNumberAfterStr + ) { + isSameStr = false; + extendNumberBeforeStr = isExtendNumber[2]; + extendNumberAfterStr = isExtendNumber[3]; + } else { + isSameStr = true; + } + } else if (_this.isChnNumber(data.m)) { + str = 'chnNumber'; + extendNumberBeforeStr = null; + extendNumberAfterStr = null; + } else if (_this.isChnWeek2(data.m)) { + str = 'chnWeek2'; + extendNumberBeforeStr = null; + extendNumberAfterStr = null; + } else if (_this.isChnWeek3(data.m)) { + str = 'chnWeek3'; + extendNumberBeforeStr = null; + extendNumberAfterStr = null; + } else { + str = 'other'; + extendNumberBeforeStr = null; + extendNumberAfterStr = null; + } + } else { + str = 'other'; + extendNumberBeforeStr = null; + extendNumberAfterStr = null; + } + if (str === 'extendNumber') { + if (b === b1) { + if (b1 === b2) { + text = str; + arrData.push(data); + arrIndex.push(b - b1 + 1); + + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } else { + text = str; + arrData.push(data); + arrIndex.push(b - b1 + 1); + } + } else if (b === b2) { + if (text === str && isSameStr) { + arrData.push(data); + arrIndex.push(b - b1 + 1); + + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + } else { + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + + text = str; + arrData = []; + arrData.push(data); + arrIndex = []; + arrIndex.push(b - b1 + 1); + + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + } + } else if (text === str && isSameStr) { + arrData.push(data); + arrIndex.push(b - b1 + 1); + } else { + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + + text = str; + arrData = []; + arrData.push(data); + arrIndex = []; + arrIndex.push(b - b1 + 1); + } + } else if (b === b1) { + if (b1 === b2) { + text = str; + arrData.push(data); + arrIndex.push(b - b1 + 1); + + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } else { + text = str; + arrData.push(data); + arrIndex.push(b - b1 + 1); + } + } else if (b === b2) { + if (text === str) { + arrData.push(data); + arrIndex.push(b - b1 + 1); + + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + } else { + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + + text = str; + arrData = []; + arrData.push(data); + arrIndex = []; + arrIndex.push(b - b1 + 1); + + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + } + } else if (text === str) { + arrData.push(data); + arrIndex.push(b - b1 + 1); + } else { + if (text in obj) { + obj[text].push({ data: arrData, index: arrIndex }); + } else { + obj[text] = []; + obj[text].push({ data: arrData, index: arrIndex }); + } + + text = str; + arrData = []; + arrData.push(data); + arrIndex = []; + arrIndex.push(b - b1 + 1); + } + } + + copyData.push(obj); + } + + return copyData; + } + + static ChineseToNumber(chnStr: string) { + const _this = this; + + let rtn = 0; + let section = 0; + let number = 0; + let secUnit = false; + const str = chnStr.split(''); + + for (let i = 0; i < str.length; i++) { + const num = _this.chnNumChar[str[i]]; + + if (typeof num !== 'undefined') { + number = num; + + if (i === str.length - 1) { + section += number; + } + } else { + const unit = _this.chnNameValue[str[i]].value; + secUnit = _this.chnNameValue[str[i]].secUnit; + + if (secUnit) { + section = (section + number) * unit; + rtn += section; + section = 0; + } else { + section += number * unit; + } + + number = 0; + } + } + + return rtn + section; + } + + static NumberToChinese(num: number) { + const _this = this; + + let unitPos = 0; + let strIns = ''; + let chnStr = ''; + let needZero = false; + + if (num === 0) { + return _this.chnNumChar2[0]; + } + while (num > 0) { + const section = num % 10000; + + if (needZero) { + chnStr = _this.chnNumChar2[0] + chnStr; + } + + strIns = _this.SectionToChinese(section); + strIns += + section !== 0 + ? _this.chnUnitSection[unitPos] + : _this.chnUnitSection[0]; + chnStr = strIns + chnStr; + needZero = section < 1000 && section > 0; + num = Math.floor(num / 10000); + unitPos++; + } + + return chnStr; + } + + static SectionToChinese(section: number) { + const _this = this; + + let strIns = ''; + let chnStr = ''; + let unitPos = 0; + let zero = true; + + while (section > 0) { + const v = section % 10; + + if (v === 0) { + if (!zero) { + zero = true; + chnStr = _this.chnNumChar2[v] + chnStr; + } + } else { + zero = false; + strIns = _this.chnNumChar2[v]; + strIns += _this.chnUnitChar[unitPos]; + chnStr = strIns + chnStr; + } + + unitPos++; + section = Math.floor(section / 10); + } + + return chnStr; + } + + static getLenS(indexArr: string | any[], rsd: number) { + let s = 0; + + for (let j = 0; j < indexArr.length; j++) { + if (indexArr[j] <= rsd) { + s++; + } else { + break; + } + } + + return s; + } + + static getDataByType( + data: any, + len: number, + direction: Direction, + type: AutoFillSeries | string, + dataType?: string + ): any { + const _this = this; + + let applyData = []; + + if (type === AutoFillSeries.ALTERNATE_SERIES) { + // 复制单元格 + if (direction === Direction.TOP || direction === Direction.LEFT) { + data.reverse(); + } + + applyData = _this.FillCopy(data, len); + } else if (type === AutoFillSeries.DEFAULT_SERIES) { + // 填充序列 + if (dataType === 'number') { + // 数据类型是 数字 + applyData = _this.FillSeries(data, len, direction); + } else if (dataType === 'extendNumber') { + // 扩展数字 + if (data.length === 1) { + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillExtendNumber(data, len, step); + } else { + const dataNumArr = []; + + for (let i = 0; i < data.length; i++) { + const txt = data[i].m; + dataNumArr.push(Number(_this.isExtendNumber(txt)[1])); + } + + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + dataNumArr.reverse(); + } + + if (_this.isEqualDiff(dataNumArr)) { + // 等差数列,以等差为step + const step = dataNumArr[1] - dataNumArr[0]; + applyData = _this.FillExtendNumber(data, len, step); + } else { + // 不是等差数列,复制数据 + applyData = _this.FillCopy(data, len); + } + } + } else if (dataType === 'date') { + // 数据类型是 日期 + if (data.length === 1) { + // 以一天为step + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillDays(data, len, step); + } else { + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + const judgeDate = _this.judgeDate(data); + if (judgeDate[0] && judgeDate[3]) { + // 日一样,月差为等差数列,以月差为step + const step = dayjs(data[1].m).diff( + dayjs(data[0].m), + 'months' + ); + applyData = _this.FillMonths(data, len, step); + } else if (!judgeDate[0] && judgeDate[2]) { + // 日不一样,日差为等差数列,以日差为step + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'days'); + applyData = _this.FillDays(data, len, step); + } else { + // 其它,复制数据 + applyData = _this.FillCopy(data, len); + } + } + } else if (dataType === 'chnNumber') { + // 数据类型是 中文小写数字 + if (data.length === 1) { + if (data[0].m === '日' || _this.ChineseToNumber(data[0].m) < 7) { + // 数字小于7,以周一~周日序列填充 + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillChnWeek(data, len, step); + } else { + // 数字大于7,以中文小写数字序列填充 + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillChnNumber(data, len, step); + } + } else { + let hasweek = false; + for (let i = 0; i < data.length; i++) { + if (data[i].m === '日') { + hasweek = true; + break; + } + } + + const dataNumArr = []; + let weekIndex = 0; + for (let i = 0; i < data.length; i++) { + if (data[i].m === '日') { + if (i === 0) { + dataNumArr.push(0); + } else { + weekIndex++; + dataNumArr.push(weekIndex * 7); + } + } else if ( + hasweek && + _this.ChineseToNumber(data[i].m) > 0 && + _this.ChineseToNumber(data[i].m) < 7 + ) { + dataNumArr.push( + _this.ChineseToNumber(data[i].m) + weekIndex * 7 + ); + } else { + dataNumArr.push(_this.ChineseToNumber(data[i].m)); + } + } + + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + dataNumArr.reverse(); + } + + if (_this.isEqualDiff(dataNumArr)) { + if ( + hasweek || + (dataNumArr[dataNumArr.length - 1] < 6 && + dataNumArr[0] > 0) || + (dataNumArr[0] < 6 && + dataNumArr[dataNumArr.length - 1] > 0) + ) { + // 以周一~周日序列填充 + const step = dataNumArr[1] - dataNumArr[0]; + applyData = _this.FillChnWeek(data, len, step); + } else { + // 以中文小写数字序列填充 + const step = dataNumArr[1] - dataNumArr[0]; + applyData = _this.FillChnNumber(data, len, step); + } + } else { + // 不是等差数列,复制数据 + applyData = _this.FillCopy(data, len); + } + } + } else if (dataType === 'chnWeek2') { + // 周一~周日 + if (data.length === 1) { + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillChnWeek2(data, len, step); + } else { + const dataNumArr = []; + let weekIndex = 0; + + for (let i = 0; i < data.length; i++) { + const lastTxt = data[i].m.substr(data[i].m.length - 1, 1); + if (data[i].m === '周日') { + if (i === 0) { + dataNumArr.push(0); + } else { + weekIndex++; + dataNumArr.push(weekIndex * 7); + } + } else { + dataNumArr.push( + _this.ChineseToNumber(lastTxt) + weekIndex * 7 + ); + } + } + + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + dataNumArr.reverse(); + } + + if (_this.isEqualDiff(dataNumArr)) { + // 等差数列,以等差为step + const step = dataNumArr[1] - dataNumArr[0]; + applyData = _this.FillChnWeek2(data, len, step); + } else { + // 不是等差数列,复制数据 + applyData = _this.FillCopy(data, len); + } + } + } else if (dataType === 'chnWeek3') { + // 星期一~星期日 + if (data.length === 1) { + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillChnWeek3(data, len, step); + } else { + const dataNumArr = []; + let weekIndex = 0; + + for (let i = 0; i < data.length; i++) { + const lastTxt = data[i].m.substr(data[i].m.length - 1, 1); + if (data[i].m === '星期日') { + if (i === 0) { + dataNumArr.push(0); + } else { + weekIndex++; + dataNumArr.push(weekIndex * 7); + } + } else { + dataNumArr.push( + _this.ChineseToNumber(lastTxt) + weekIndex * 7 + ); + } + } + + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + dataNumArr.reverse(); + } + + if (_this.isEqualDiff(dataNumArr)) { + // 等差数列,以等差为step + const step = dataNumArr[1] - dataNumArr[0]; + applyData = _this.FillChnWeek3(data, len, step); + } else { + // 不是等差数列,复制数据 + applyData = _this.FillCopy(data, len); + } + } + } else { + // 数据类型是 其它 + if (direction === Direction.TOP || direction === Direction.LEFT) { + data.reverse(); + } + + applyData = _this.FillCopy(data, len); + } + } else if (type === '2') { + // 仅填充格式 + if (direction === Direction.TOP || direction === Direction.LEFT) { + data.reverse(); + } + + applyData = _this.FillOnlyFormat(data, len); + } else if (type === '3') { + // 不带格式填充 + const dataArr = _this.getDataByType(data, len, direction, '1', dataType); + applyData = _this.FillWithoutFormat(dataArr); + } else if (type === '4') { + // 以天数填充 + if (data.length === 1) { + // 以一天为step + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillDays(data, len, step); + } else if (data.length === 2) { + // 以日差为step + if (direction === Direction.TOP || direction === Direction.LEFT) { + data.reverse(); + } + + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'days'); + applyData = _this.FillDays(data, len, step); + } else { + if (direction === Direction.TOP || direction === Direction.LEFT) { + data.reverse(); + } + + const judgeDate = _this.judgeDate(data); + if (judgeDate[0] && judgeDate[3]) { + // 日一样,且月差为等差数列,以月差为step + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'months'); + applyData = _this.FillMonths(data, len, step); + } else if (!judgeDate[0] && judgeDate[2]) { + // 日不一样,且日差为等差数列,以日差为step + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'days'); + applyData = _this.FillDays(data, len, step); + } else { + // 日差不是等差数列,复制数据 + applyData = _this.FillCopy(data, len); + } + } + } else if (type === '5') { + // 以工作日填充 + if (data.length === 1) { + // 以一天为step(若那天为休息日,则跳过) + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + const newLen = Math.round(len * 1.5); + for (let i = 1; i <= newLen; i++) { + const d = { ...data[i] }; + + const day = dayjs(d.m).add(i, 'days').day(); + if (day === 0 || day === 6) { + continue; + } + + const date = dayjs(d.m) + .add(step * i, 'days') + .format('YYYY-MM-DD'); + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + + if (applyData.length === len) { + break; + } + } + } else if (data.length === 2) { + if ( + dayjs(data[1].m).date() === dayjs(data[0].m).date() && + dayjs(data[1].m).diff(dayjs(data[0].m), 'months') !== 0 + ) { + // 日一样,且月差大于一月,以月差为step(若那天为休息日,则向前取最近的工作日) + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'months'); + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const day = dayjs(data[data.length - 1]) + .add(step * i, 'months') + .day(); + let date; + if (day === 0) { + date = dayjs(data[data.length - 1]) + .add(step * i, 'months') + .subtract(2, 'days') + .format('YYYY-MM-DD'); + } else if (day === 6) { + date = dayjs(data[data.length - 1]) + .add(step * i, 'months') + .subtract(1, 'days') + .format('YYYY-MM-DD'); + } else { + date = dayjs(data[data.length - 1]) + .add(step * i, 'months') + .format('YYYY-MM-DD'); + } + + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } else { + // 日不一样 + if (Math.abs(dayjs(data[1].m).diff(dayjs(data[0].m))) > 7) { + // 若日差大于7天,以一月为step(若那天是休息日,则向前取最近的工作日) + let step_month = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_month = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_month = -1; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_month * num, 'months') + .diff(dayjs(d.m), 'days'); + } + + const day = dayjs(d.m).add(step, 'days').day(); + let date; + if (day === 0) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(2, 'days') + .format('YYYY-MM-DD'); + } else if (day === 6) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(1, 'days') + .format('YYYY-MM-DD'); + } else { + date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + } + + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } else { + // 若日差小于等于7天,以7天为step(若那天是休息日,则向前取最近的工作日) + let step_day = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_day = 7; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_day = -7; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_day * num, 'days') + .diff(dayjs(d.m), 'days'); + } + + const day = dayjs(d.m).add(step, 'days').day(); + let date; + if (day === 0) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(2, 'days') + .format('YYYY-MM-DD'); + } else if (day === 6) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(1, 'days') + .format('YYYY-MM-DD'); + } else { + date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + } + + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } + } + } else { + const judgeDate = _this.judgeDate(data); + if (judgeDate[0] && judgeDate[3]) { + // 日一样,且月差为等差数列,以月差为step(若那天为休息日,则向前取最近的工作日) + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'months'); + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const day = dayjs(data[data.length - 1].m) + .add(step * i, 'months') + .day(); + let date; + if (day === 0) { + date = dayjs(data[data.length - 1].m) + .add(step * i, 'months') + .subtract(2, 'days') + .format('YYYY-MM-DD'); + } else if (day === 6) { + date = dayjs(data[data.length - 1].m) + .add(step * i, 'months') + .subtract(1, 'days') + .format('YYYY-MM-DD'); + } else { + date = dayjs(data[data.length - 1].m) + .add(step * i, 'months') + .format('YYYY-MM-DD'); + } + + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } else if (!judgeDate[0] && judgeDate[2]) { + // 日不一样,且日差为等差数列 + if (Math.abs(dayjs(data[1].m).diff(dayjs(data[0].m))) > 7) { + // 若日差大于7天,以一月为step(若那天是休息日,则向前取最近的工作日) + let step_month = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_month = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_month = -1; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_month * num, 'months') + .diff(dayjs(d.m), 'days'); + } + + const day = dayjs(d.m).add(step, 'days').day(); + let date; + if (day === 0) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(2, 'days') + .format('YYYY-MM-DD'); + } else if (day === 6) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(1, 'days') + .format('YYYY-MM-DD'); + } else { + date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + } + + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } else { + // 若日差小于等于7天,以7天为step(若那天是休息日,则向前取最近的工作日) + let step_day = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_day = 7; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_day = -7; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_day * num, 'days') + .diff(dayjs(d.m), 'days'); + } + + const day = dayjs(d.m).add(step, 'days').day(); + let date; + if (day === 0) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(2, 'days') + .format('YYYY-MM-DD'); + } else if (day === 6) { + date = dayjs(d.m) + .add(step, 'days') + .subtract(1, 'days') + .format('YYYY-MM-DD'); + } else { + date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + } + + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } + } else { + // 日差不是等差数列,复制数据 + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + applyData = _this.FillCopy(data, len); + } + } + } else if (type === '6') { + // 以月填充 + if (data.length === 1) { + // 以一月为step + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillMonths(data, len, step); + } else if (data.length === 2) { + if ( + dayjs(data[1].m).date() === dayjs(data[0].m).date() && + dayjs(data[1].m).diff(dayjs(data[0].m), 'months') !== 0 + ) { + // 日一样,且月差大于一月,以月差为step + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'months'); + applyData = _this.FillMonths(data, len, step); + } else { + // 以一月为step + let step_month = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_month = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_month = -1; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_month * num, 'months') + .diff(dayjs(d.m), 'days'); + } + + const date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } + } else { + const judgeDate = _this.judgeDate(data); + if (judgeDate[0] && judgeDate[3]) { + // 日一样,且月差为等差数列,以月差为step + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'months'); + applyData = _this.FillMonths(data, len, step); + } else if (!judgeDate[0] && judgeDate[2]) { + // 日不一样,且日差为等差数列,以一月为step + let step_month = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_month = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_month = -1; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_month * num, 'months') + .diff(dayjs(d.m), 'days'); + } + + const date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } else { + // 日差不是等差数列,复制数据 + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + applyData = _this.FillCopy(data, len); + } + } + } else if (type === '7') { + // 以年填充 + if (data.length === 1) { + // 以一年为step + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillYears(data, len, step); + } else if (data.length === 2) { + if ( + dayjs(data[1].m).date() === dayjs(data[0].m).date() && + dayjs(data[1].m).month() === dayjs(data[0].m).month() && + dayjs(data[1].m).diff(dayjs(data[0].m), 'years') !== 0 + ) { + // 日月一样,且年差大于一年,以年差为step + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'years'); + applyData = _this.FillYears(data, len, step); + } else { + // 以一年为step + let step_year = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_year = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_year = -1; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_year * num, 'years') + .diff(dayjs(d.m), 'days'); + } + + const date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } + } else { + const judgeDate = _this.judgeDate(data); + if (judgeDate[0] && judgeDate[1] && judgeDate[4]) { + // 日月一样,且年差为等差数列,以年差为step + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + const step = dayjs(data[1].m).diff(dayjs(data[0].m), 'years'); + applyData = _this.FillYears(data, len, step); + } else if ((judgeDate[0] && judgeDate[3]) || judgeDate[2]) { + // 日一样且月差为等差数列,或天差为等差数列,以一年为step + let step_year = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step_year = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step_year = -1; + data.reverse(); + } + + let step = 0; // 以数组第一个为对比 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = Math.ceil(i / data.length); + if (index === 0) { + step = dayjs(d.m) + .add(step_year * num, 'years') + .diff(dayjs(d.m), 'days'); + } + + const date = dayjs(d.m) + .add(step, 'days') + .format('YYYY-MM-DD'); + d.m = date; + d.v = generate(date)[2]; + applyData.push(d); + } + } else { + // 日差不是等差数列,复制数据 + if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + data.reverse(); + } + + applyData = _this.FillCopy(data, len); + } + } + } else if (type === '8') { + // 以中文小写数字序列填充 + if (data.length === 1) { + let step = 0; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + step = 1; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + step = -1; + } + + applyData = _this.FillChnNumber(data, len, step); + } else { + const dataNumArr = []; + for (let i = 0; i < data.length; i++) { + dataNumArr.push(_this.ChineseToNumber(data[i].m)); + } + + if (direction === Direction.TOP || direction === Direction.LEFT) { + data.reverse(); + dataNumArr.reverse(); + } + + if (_this.isEqualDiff(dataNumArr)) { + const step = dataNumArr[1] - dataNumArr[0]; + applyData = _this.FillChnNumber(data, len, step); + } else { + // 不是等差数列,复制数据 + applyData = _this.FillCopy(data, len); + } + } + } + + return applyData; + } + + static getDataIndex(csLen: number, asLen: number, indexArr: any[]) { + const obj = {}; + + const num = Math.floor(asLen / csLen); + const rsd = asLen % csLen; + + let sum = 0; + if (num > 0) { + for (let i = 1; i <= num; i++) { + for (let j = 0; j < indexArr.length; j++) { + obj[indexArr[j] + (i - 1) * csLen] = sum; + sum++; + } + } + for (let a = 0; a < indexArr.length; a++) { + if (indexArr[a] <= rsd) { + obj[indexArr[a] + csLen * num] = sum; + sum++; + } else { + break; + } + } + } else { + for (let a = 0; a < indexArr.length; a++) { + if (indexArr[a] <= rsd) { + obj[indexArr[a]] = sum; + sum++; + } else { + break; + } + } + } + + return obj; + } + + static FillCopy(data: string | any[], len: number) { + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + applyData.push(d); + } + + return applyData; + } + + static FillSeries(data: string | any[], len: number, direction: Direction) { + const _this = this; + + const applyData = []; + + const dataNumArr = []; + for (let j = 0; j < data.length; j++) { + dataNumArr.push(Number(data[j].v)); + } + + if (data.length > 2 && _this.isEqualRatio(dataNumArr)) { + // 等比数列 + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + let num; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + num = + Number(data[data.length - 1].v) * + (Number(data[1].v) / Number(data[0].v)) ** i; + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + num = + Number(data[0].v) / + (Number(data[1].v) / Number(data[0].v)) ** i; + } + + d.v = num; + d.m = num; + applyData.push(d); + } + } else { + // 线性数列 + const xArr = _this.getXArr(data.length); + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + let y; + if ( + direction === Direction.BOTTOM || + direction === Direction.RIGHT + ) { + y = _this.forecast(data.length + i, dataNumArr, xArr); + } else if ( + direction === Direction.TOP || + direction === Direction.LEFT + ) { + y = _this.forecast(1 - i, dataNumArr, xArr); + } + + d.v = y; + d.m = y; + applyData.push(d); + } + } + + return applyData; + } + + static FillExtendNumber(data: string | any[], len: number, step: number) { + const _this = this; + + const applyData = []; + const reg = /0|([1-9]+[0-9]*)/g; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const last = data[data.length - 1].m; + const match = last.match(reg); + const lastTxt = match[match.length - 1]; + + const num = Math.abs(Number(lastTxt) + step * i); + const lastIndex = last.lastIndexOf(lastTxt); + const valueTxt = + last.substr(0, lastIndex) + + num.toString() + + last.substr(lastIndex + lastTxt.length); + + d.v = valueTxt; + d.m = valueTxt; + + applyData.push(d); + } + + return applyData; + } + + static FillOnlyFormat(data: string | any[], len: number) { + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + delete d.f; + delete d.m; + delete d.v; + + applyData.push(d); + } + + return applyData; + } + + static FillWithoutFormat(dataArr: string | any[]) { + const applyData = []; + + for (let i = 0; i < dataArr.length; i++) { + const d = { ...dataArr[i] }; + + let obj; + if (d.f === null) { + obj = { m: d.v.toString(), v: d.v }; + } else { + obj = { f: d.f, m: d.v.toString(), v: d.v }; + } + + applyData.push(obj); + } + + return applyData; + } + + static FillDays(data: string | any[], len: number, step: number) { + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + let date = update('yyyy-MM-dd', d.v); + // let date = ; + date = dayjs(date) + .add(step * i, 'days') + .format('YYYY-MM-DD'); + + d.v = generate(date)[2]; + d.m = update(d.fm.f, d.v); + + applyData.push(d); + } + + return applyData; + } + + static FillMonths(data: string | any[], len: number, step: number) { + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + let date = update('yyyy-MM-dd', d.v); + date = dayjs(date) + .add(step * i, 'months') + .format('YYYY-MM-DD'); + + d.v = generate(date)[2]; + d.m = update(d.fm.f, d.v); + + applyData.push(d); + } + + return applyData; + } + + static FillYears(data: string | any[], len: number, step: number) { + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + let date = update('yyyy-MM-dd', d.v); + date = dayjs(date) + .add(step * i, 'years') + .format('YYYY-MM-DD'); + + d.v = generate(date)[2]; + d.m = update(d.fm.t, d.v); + + applyData.push(d); + } + + return applyData; + } + + static FillChnWeek(data: string | any[], len: number, step: number) { + const _this = this; + + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + let num; + if (data[data.length - 1].m === '日') { + num = 7 + step * i; + } else { + num = _this.ChineseToNumber(data[data.length - 1].m) + step * i; + } + + if (num < 0) { + num = Math.ceil(Math.abs(num) / 7) * 7 + num; + } + + const rsd = num % 7; + if (rsd === 0) { + d.m = '日'; + d.v = '日'; + } else if (rsd === 1) { + d.m = '一'; + d.v = '一'; + } else if (rsd === 2) { + d.m = '二'; + d.v = '二'; + } else if (rsd === 3) { + d.m = '三'; + d.v = '三'; + } else if (rsd === 4) { + d.m = '四'; + d.v = '四'; + } else if (rsd === 5) { + d.m = '五'; + d.v = '五'; + } else if (rsd === 6) { + d.m = '六'; + d.v = '六'; + } + + applyData.push(d); + } + + return applyData; + } + + static FillChnWeek2(data: string | any[], len: number, step: number) { + const _this = this; + + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + let num; + if (data[data.length - 1].m === '周日') { + num = 7 + step * i; + } else { + const last = data[data.length - 1].m; + const txt = last.substr(last.length - 1, 1); + num = _this.ChineseToNumber(txt) + step * i; + } + + if (num < 0) { + num = Math.ceil(Math.abs(num) / 7) * 7 + num; + } + + const rsd = num % 7; + if (rsd === 0) { + d.m = '周日'; + d.v = '周日'; + } else if (rsd === 1) { + d.m = '周一'; + d.v = '周一'; + } else if (rsd === 2) { + d.m = '周二'; + d.v = '周二'; + } else if (rsd === 3) { + d.m = '周三'; + d.v = '周三'; + } else if (rsd === 4) { + d.m = '周四'; + d.v = '周四'; + } else if (rsd === 5) { + d.m = '周五'; + d.v = '周五'; + } else if (rsd === 6) { + d.m = '周六'; + d.v = '周六'; + } + + applyData.push(d); + } + + return applyData; + } + + static FillChnWeek3(data: string | any[], len: number, step: number) { + const _this = this; + + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + let num; + if (data[data.length - 1].m === '星期日') { + num = 7 + step * i; + } else { + const last = data[data.length - 1].m; + const txt = last.substr(last.length - 1, 1); + num = _this.ChineseToNumber(txt) + step * i; + } + + if (num < 0) { + num = Math.ceil(Math.abs(num) / 7) * 7 + num; + } + + const rsd = num % 7; + if (rsd === 0) { + d.m = '星期日'; + d.v = '星期日'; + } else if (rsd === 1) { + d.m = '星期一'; + d.v = '星期一'; + } else if (rsd === 2) { + d.m = '星期二'; + d.v = '星期二'; + } else if (rsd === 3) { + d.m = '星期三'; + d.v = '星期三'; + } else if (rsd === 4) { + d.m = '星期四'; + d.v = '星期四'; + } else if (rsd === 5) { + d.m = '星期五'; + d.v = '星期五'; + } else if (rsd === 6) { + d.m = '星期六'; + d.v = '星期六'; + } + + applyData.push(d); + } + + return applyData; + } + + static FillChnNumber(data: string | any[], len: number, step: number) { + const _this = this; + + const applyData = []; + + for (let i = 1; i <= len; i++) { + const index = (i - 1) % data.length; + const d = { ...data[index] }; + + const num = _this.ChineseToNumber(data[data.length - 1].m) + step * i; + let txt; + if (num <= 0) { + txt = '零'; + } else { + txt = _this.NumberToChinese(num); + } + + d.v = txt; + d.m = txt.toString(); + applyData.push(d); + } + + return applyData; + } + + static isEqualDiff(arr: string | any[]) { + let diff = true; + const step = arr[1] - arr[0]; + + for (let i = 1; i < arr.length; i++) { + if (arr[i] - arr[i - 1] !== step) { + diff = false; + break; + } + } + + return diff; + } + + static isChnNumber(txt: string) { + const _this = this; + + let isChnNumber = true; + + if (txt.length === 1) { + if (txt === '日' || txt in _this.chnNumChar) { + isChnNumber = true; + } else { + isChnNumber = false; + } + } else { + const str = txt.split(''); + for (let i = 0; i < str.length; i++) { + if (!(str[i] in _this.chnNumChar || str[i] in _this.chnNameValue)) { + isChnNumber = false; + break; + } + } + } + + return isChnNumber; + } + + static isExtendNumber(txt: string) { + const reg = /0|([1-9]+[0-9]*)/g; + const isExtendNumber = reg.test(txt); + + if (isExtendNumber) { + const match = txt.match(reg)!; + const matchTxt = match[match.length - 1]; + const matchIndex = txt.lastIndexOf(matchTxt); + const beforeTxt = txt.substr(0, matchIndex); + const afterTxt = txt.substr(matchIndex + matchTxt.length); + + return [isExtendNumber, Number(matchTxt), beforeTxt, afterTxt]; + } + return [isExtendNumber]; + } + + static isChnWeek1(txt: string) { + const _this = this; + + let isChnWeek1; + if (txt.length === 1) { + if (txt === '日' || _this.ChineseToNumber(txt) < 7) { + isChnWeek1 = true; + } else { + isChnWeek1 = false; + } + } else { + isChnWeek1 = false; + } + + return isChnWeek1; + } + + static isChnWeek2(txt: string | any[]) { + let isChnWeek2; + if (txt.length === 2) { + if ( + txt === '周一' || + txt === '周二' || + txt === '周三' || + txt === '周四' || + txt === '周五' || + txt === '周六' || + txt === '周日' + ) { + isChnWeek2 = true; + } else { + isChnWeek2 = false; + } + } else { + isChnWeek2 = false; + } + + return isChnWeek2; + } + + static isChnWeek3(txt: string | any[]) { + let isChnWeek3; + if (txt.length === 3) { + if ( + txt === '星期一' || + txt === '星期二' || + txt === '星期三' || + txt === '星期四' || + txt === '星期五' || + txt === '星期六' || + txt === '星期日' + ) { + isChnWeek3 = true; + } else { + isChnWeek3 = false; + } + } else { + isChnWeek3 = false; + } + + return isChnWeek3; + } + + static isEqualRatio(arr: string | any[]) { + let ratio = true; + const step = arr[1] / arr[0]; + + for (let i = 1; i < arr.length; i++) { + if (arr[i] / arr[i - 1] !== step) { + ratio = false; + break; + } + } + + return ratio; + } + + static getXArr(len: number) { + const xArr = []; + + for (let i = 1; i <= len; i++) { + xArr.push(i); + } + + return xArr; + } + + static forecast(x: number, yArr: number[], xArr: string | any[]) { + function getAverage(arr: string | any[]) { + let sum = 0; + + for (let i = 0; i < arr.length; i++) { + sum += arr[i]; + } + + return sum / arr.length; + } + + const ax = getAverage(xArr); // x数组 平均值 + const ay = getAverage(yArr); // y数组 平均值 + + let sum_d = 0; + let sum_n = 0; + for (let j = 0; j < xArr.length; j++) { + // 分母和 + sum_d += (xArr[j] - ax) * (yArr[j] - ay); + // 分子和 + sum_n += (xArr[j] - ax) * (xArr[j] - ax); + } + + let b; + if (sum_n === 0) { + b = 1; + } else { + b = sum_d / sum_n; + } + + const a = ay - b * ax; + + return Math.round((a + b * x) * 100000) / 100000; + } + + static judgeDate(data: string | any[]) { + let isSameDay = true; + let isSameMonth = true; + let isEqualDiffDays = true; + let isEqualDiffMonths = true; + let isEqualDiffYears = true; + const sameDay = dayjs(data[0].m).date(); + const sameMonth = dayjs(data[0].m).month(); + const equalDiffDays = dayjs(data[1].m).diff(dayjs(data[0].m), 'days'); + const equalDiffMonths = dayjs(data[1].m).diff(dayjs(data[0].m), 'months'); + const equalDiffYears = dayjs(data[1].m).diff(dayjs(data[0].m), 'years'); + + for (let i = 1; i < data.length; i++) { + // 日是否一样 + if (dayjs(data[i].m).date() !== sameDay) { + isSameDay = false; + } + // 月是否一样 + if (dayjs(data[i].m).month() !== sameMonth) { + isSameMonth = false; + } + // 日差是否是 等差数列 + if ( + dayjs(data[i].m).diff(dayjs(data[i - 1].m), 'days') !== equalDiffDays + ) { + isEqualDiffDays = false; + } + // 月差是否是 等差数列 + if ( + dayjs(data[i].m).diff(dayjs(data[i - 1].m), 'months') !== + equalDiffMonths + ) { + isEqualDiffMonths = false; + } + // 年差是否是 等差数列 + if ( + dayjs(data[i].m).diff(dayjs(data[i - 1].m), 'years') !== + equalDiffYears + ) { + isEqualDiffYears = false; + } + } + + if (equalDiffDays === 0) { + isEqualDiffDays = false; + } + if (equalDiffMonths === 0) { + isEqualDiffMonths = false; + } + if (equalDiffYears === 0) { + isEqualDiffYears = false; + } + + return [ + isSameDay, + isSameMonth, + isEqualDiffDays, + isEqualDiffMonths, + isEqualDiffYears, + ]; + } +} diff --git a/packages/core/src/Shared/ExcelDateTime.ts b/packages/core/src/Shared/ExcelDateTime.ts new file mode 100644 index 0000000000..e03753f7a4 --- /dev/null +++ b/packages/core/src/Shared/ExcelDateTime.ts @@ -0,0 +1,4 @@ +/** + * Date Tool + */ +export class ExcelDateTime {} diff --git a/packages/core/src/Shared/Generate.ts b/packages/core/src/Shared/Generate.ts new file mode 100644 index 0000000000..963796f91f --- /dev/null +++ b/packages/core/src/Shared/Generate.ts @@ -0,0 +1,356 @@ +import numeral from 'numeral'; + +export const error = { + v: '#VALUE!', // 错误的参数或运算符 + n: '#NAME?', // 公式名称错误 + na: '#N/A', // 函数或公式中没有可用数值 + r: '#REF!', // 删除了由其他公式引用的单元格 + d: '#DIV/0!', // 除数是0或空单元格 + nm: '#NUM!', // 当公式或函数中某个数字有问题时 + nl: '#NULL!', // 交叉运算符(空格)使用不正确 + sp: '#SPILL!', // 数组范围有其它值 +}; +// 是否是错误类型 +function valueIsError(value: string) { + let isError = false; + + for (const x in error) { + if (value === error[x]) { + isError = true; + break; + } + } + + return isError; +} + +// 是否是纯数字 +function isRealNum(val: string | number) { + if (val === null || val.toString().replace(/\s/g, '') === '') { + return false; + } + + if (typeof val === 'boolean') { + return false; + } + + if (!Number.isNaN(val as number)) { + return true; + } + return false; +} + +function isdatetime(s: string | null) { + if (s === null || s.toString().length < 5) { + return false; + } + if (checkDateTime(s)) { + return true; + } + return false; + + function checkDateTime(str: string) { + const reg1 = + /^(\d{4})-(\d{1,2})-(\d{1,2})(\s(\d{1,2}):(\d{1,2})(:(\d{1,2}))?)?$/; + const reg2 = + /^(\d{4})\/(\d{1,2})\/(\d{1,2})(\s(\d{1,2}):(\d{1,2})(:(\d{1,2}))?)?$/; + + if (!reg1.test(str) && !reg2.test(str)) { + return false; + } + + const year = RegExp.$1 as unknown as number; + const month = RegExp.$2 as unknown as number; + const day = RegExp.$3 as unknown as number; + + if (year < 1900) { + return false; + } + + if (month > 12) { + return false; + } + + if (day > 31) { + return false; + } + + if (month === 2) { + if (new Date(year, 1, 29).getDate() === 29 && day > 29) { + return false; + } + if (new Date(year, 1, 29).getDate() !== 29 && day > 28) { + return false; + } + } + + return true; + } +} + +let good_pd_date = new Date('2017-02-19T19:06:09.000Z'); +if (Number.isNaN(good_pd_date.getFullYear())) good_pd_date = new Date('2/19/17'); +const good_pd = good_pd_date.getFullYear() === 2017; +function parseDate(str: string | Date, fixdate?: number) { + const d = new Date(str); + if (good_pd) { + if ((fixdate as number) > 0) { + d.setTime(d.getTime() + d.getTimezoneOffset() * 60 * 1000); + } else if ((fixdate as number) < 0) { + d.setTime(d.getTime() - d.getTimezoneOffset() * 60 * 1000); + } + return d; + } + if (str instanceof Date) return str; + if (good_pd_date.getFullYear() === 1917 && !Number.isNaN(d.getFullYear())) { + const s = d.getFullYear(); + if (str.indexOf(`${s}`) > -1) return d; + d.setFullYear(d.getFullYear() + 100); + return d; + } + const n = str.match(/\d+/g) || ['2017', '2', '19', '0', '0', '0']; + let out = new Date(+n[0], +n[1] - 1, +n[2], +n[3] || 0, +n[4] || 0, +n[5] || 0); + if (str.indexOf('Z') > -1) + out = new Date(out.getTime() - out.getTimezoneOffset() * 60 * 1000); + return out; +} + +const base1904 = new Date(1900, 2, 1, 0, 0, 0); +export function datenum_local(v: Date, date1904?: number) { + let epoch = Date.UTC( + v.getFullYear(), + v.getMonth(), + v.getDate(), + v.getHours(), + v.getMinutes(), + v.getSeconds() + ); + const dnthresh_utc = Date.UTC(1899, 11, 31, 0, 0, 0); + + if (date1904) epoch -= 1461 * 24 * 60 * 60 * 1000; + else if (v >= base1904) epoch += 24 * 60 * 60 * 1000; + return (epoch - dnthresh_utc) / (24 * 60 * 60 * 1000); +} + +export function generate(value: string): any { + // 万 单位格式增加!!! + const ret = []; + let m = null; + let ct: any = {}; + let v: any = value; + + if (value === null) { + return null; + } + + if (/^-?[0-9]{1,}[,][0-9]{3}(.[0-9]{1,2})?$/.test(value)) { + // 表述金额的字符串,如:12,000.00 或者 -12,000.00 + m = value; + v = Number(value.split('.')[0].replace(',', '')); + let fa = '#,##0'; + if (value.split('.')[1]) { + fa = '#,##0.'; + for (let i = 0; i < value.split('.')[1].length; i++) { + fa += 0; + } + } + ct = { fa, t: 'n' }; + } else if (value.toString().substr(0, 1) === "'") { + m = value.toString().substr(1); + ct = { fa: '@', t: 's' }; + } else if (value.toString().toUpperCase() === 'TRUE') { + m = 'TRUE'; + ct = { fa: 'General', t: 'b' }; + v = true; + } else if (value.toString().toUpperCase() === 'FALSE') { + m = 'FALSE'; + ct = { fa: 'General', t: 'b' }; + v = false; + } else if (valueIsError(value)) { + m = value.toString(); + ct = { fa: 'General', t: 'e' }; + } else if ( + /^\d{6}(18|19|20)?\d{2}(0[1-9]|1[12])(0[1-9]|[12]\d|3[01])\d{3}(\d|X)$/i.test( + value + ) + ) { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } else if ( + isRealNum(value) && + Math.abs(parseFloat(value)) > 0 && + (Math.abs(parseFloat(value)) >= 1e11 || Math.abs(parseFloat(value)) < 1e-9) + ) { + v = numeral(value).value(); + const str = v.toExponential(); + if (str.indexOf('.') > -1) { + let strlen = str.split('.')[1].split('e')[0].length; + if (strlen > 5) { + strlen = 5; + } + + ct = { fa: `#0.${new Array(strlen + 1).join('0')}E+00`, t: 'n' }; + } else { + ct = { fa: '#0.E+00', t: 'n' }; + } + + m = SSF.format(ct.fa, v); + } else if (value.toString().indexOf('%') > -1) { + const index = value.toString().indexOf('%'); + let value2 = value.toString().substr(0, index); + const value3 = value2.replace(/,/g, ''); + + if (index === value.toString().length - 1 && isRealNum(value3)) { + if (value2.indexOf('.') > -1) { + if (value2.indexOf('.') === value2.lastIndexOf('.')) { + const value4 = value2.split('.')[0]; + const value5 = value2.split('.')[1]; + + let len = value5.length; + if (len > 9) { + len = 9; + } + + if (value4.indexOf(',') > -1) { + let isThousands = true; + let ThousandsArr = value4.split(','); + + for (let i = 1; i < ThousandsArr.length; i++) { + if (ThousandsArr[i].length < 3) { + isThousands = false; + break; + } + } + + if (isThousands) { + ct = { + fa: `#,##0.${new Array(len + 1).join('0')}%`, + t: 'n', + }; + v = numeral(value).value(); + m = SSF.format(ct.fa, v); + } else { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } + } else { + ct = { + fa: `0.${new Array(len + 1).join('0')}%`, + t: 'n', + }; + v = numeral(value).value(); + m = SSF.format(ct.fa, v); + } + } else { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } + } else if (value2.indexOf(',') > -1) { + let isThousands = true; + let ThousandsArr = value2.split(','); + + for (let i = 1; i < ThousandsArr.length; i++) { + if (ThousandsArr[i].length < 3) { + isThousands = false; + break; + } + } + + if (isThousands) { + ct = { fa: '#,##0%', t: 'n' }; + v = numeral(value).value(); + m = SSF.format(ct.fa, v); + } else { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } + } else { + ct = { fa: '0%', t: 'n' }; + v = numeral(value).value(); + m = SSF.format(ct.fa, v); + } + } else { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } + } else if (value.toString().indexOf('.') > -1) { + if (value.toString().indexOf('.') === value.toString().lastIndexOf('.')) { + const value1 = value.toString().split('.')[0]; + let value2 = value.toString().split('.')[1]; + + let len = value2.length; + if (len > 9) { + len = 9; + } + + if (value1.indexOf(',') > -1) { + let isThousands = true; + let ThousandsArr = value1.split(','); + + for (let i = 1; i < ThousandsArr.length; i++) { + if (!isRealNum(ThousandsArr[i]) || ThousandsArr[i].length < 3) { + isThousands = false; + break; + } + } + + if (isThousands) { + ct = { fa: `#,##0.${new Array(len + 1).join('0')}`, t: 'n' }; + v = numeral(value).value(); + m = SSF.format(ct.fa, v); + } else { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } + } else if (isRealNum(value1) && isRealNum(value2)) { + ct = { fa: `0.${new Array(len + 1).join('0')}`, t: 'n' }; + v = numeral(value).value(); + m = SSF.format(ct.fa, v); + } else { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } + } else { + m = value.toString(); + ct = { fa: '@', t: 's' }; + } + } else if (isRealNum(value)) { + m = value.toString(); + ct = { fa: 'General', t: 'n' }; + v = parseFloat(value); + } else if ( + isdatetime(value) && + (value.toString().indexOf('.') > -1 || + value.toString().indexOf(':') > -1 || + value.toString().length < 16) + ) { + v = datenum_local(parseDate(value.toString().replace(/-/g, '/'))); + + if (v.toString().indexOf('.') > -1) { + if (value.toString().length > 18) { + ct.fa = 'yyyy-MM-dd hh:mm:ss'; + } else if (value.toString().length > 11) { + ct.fa = 'yyyy-MM-dd hh:mm'; + } else { + ct.fa = 'yyyy-MM-dd'; + } + } else { + ct.fa = 'yyyy-MM-dd'; + } + + ct.t = 'd'; + m = SSF.format(ct.fa, v); + } else { + m = value; + ct.fa = 'General'; + ct.t = 'g'; + } + + return [m, ct, v]; +} + +const SSF = { + format(a: any, b: any) {}, +}; + +export const update = (a: any, b: any): any => {}; diff --git a/packages/core/src/Shared/GroupModel.ts b/packages/core/src/Shared/GroupModel.ts new file mode 100644 index 0000000000..2aa59f9257 --- /dev/null +++ b/packages/core/src/Shared/GroupModel.ts @@ -0,0 +1,3 @@ +export interface GroupModel { + getGroupModel(): T; +} diff --git a/packages/core/src/Shared/IOHttp.ts b/packages/core/src/Shared/IOHttp.ts new file mode 100644 index 0000000000..70e1acbc59 --- /dev/null +++ b/packages/core/src/Shared/IOHttp.ts @@ -0,0 +1,283 @@ +function deleteEmpty(object: object) { + if (object) { + Object.keys(object).forEach((key) => { + const value = object[key]; + if (unDef(value) || value === '') { + delete object[key]; + } + }); + } + return object; +} + +function extend(src: object, ...target: object[]) { + for (const item of target) { + if (item) { + for (const key in item) { + if (item.hasOwnProperty(key)) { + if (isDef(item[key])) { + src[key] = item[key]; + } + } + } + } + } + return src; +} + +function objectToUrlSearch(object: object) { + if (unDef(object)) return ''; + let str = ''; + for (const key in object) { + if ( + object.hasOwnProperty(key) && + object[key] !== undefined && + object[key] !== null + ) { + str += `${key}=${object[key]}&`; + } + } + str = str.substring(0, str.length - 1); + return str ? `?${encodeURI(str)}` : ''; +} + +function isDef(v: unknown) { + return v !== undefined && v !== null; +} + +function unDef(v: unknown) { + return v === undefined || v === null; +} + +function urlSearchToObject(search: string) { + if (unDef(search)) return {}; + search = decodeURI(search); + const index = search.indexOf('?'); + const url = search.substring(0, index); + search = search.substring(index); + search = search.replace(/\?/, ''); + const array = search.split('&'); + const object = { + $$url: url, + }; + array.forEach((value) => { + const keyValue = value.split('='); + object[keyValue[0]] = keyValue[1]; + }); + return object; +} + +function isString(value?: any): value is string { + return Object.prototype.toString.apply(value) === '[object String]'; +} + +function ispPlain(value: any): value is {} { + return value + ? Object.getPrototypeOf(value) === Object.getPrototypeOf({}) + : false; +} + +function isArray(value: any): value is [] { + return Object.prototype.toString.apply(value) === '[object Array]'; +} + +function isFormData(value?: any): value is FormData { + return value instanceof FormData; +} + +const noop = () => {}; + +const defaultFilter = (data: object) => data; + +/** + * Types of Io Http Request + */ +export enum IOHttpRequestType { + POST = 'POST', + GET = 'GET', + PUT = 'PUT', + DELETE = 'DELETE', +} + +/** + * Status of Io Http Ready + */ +export enum IOHttpReadyState { + UNSENT = 0, + OPENED = 1, + HEADERS_RECEIVED = 2, + LOADING = 3, + DONE = 4, +} + +/** + * Types of Io Http Response + */ +export enum IOHttpResponseType { + ARRAY_BUFFER = 'arraybuffer', + BLOB = 'blob', + TEXT = 'text', + DOCUMENT = 'document', + JSON = 'json', + STREAM = 'stream', +} + +/** + * IOHTTP header type + */ +export type IOHttpHeaderType = { + [key: string]: string; +}; + +/** + * IOHTTP request body type + */ +export type IOHttpRequestBodyType = string | {} | [] | FormData; + +/** + * Config of http communication + */ +export interface IOHttpConfig { + type: keyof typeof IOHttpRequestType; + url: string; + dataType?: IOHttpResponseType; + async?: boolean; + header?: IOHttpHeaderType; + contentType?: string; + data?: IOHttpRequestBodyType; + timeout?: number; + username?: string; + password?: string; + beforeSend?: Function; + dataFilter?: Function; + error?: Function; + success?: Function; + complete?: Function; +} + +const defaultConfig: IOHttpConfig = { + type: IOHttpRequestType.POST, + url: '', + async: true, + header: {}, + dataType: IOHttpResponseType.JSON, + contentType: 'application/x-www-form-urlencoded; charset=UTF-8', + data: {}, + username: '', + password: '', + timeout: 500, + error: noop, + beforeSend: defaultFilter, + dataFilter: defaultFilter, + success: noop, + complete: noop, +}; + +/** + * HTTP request + * + * @param config The available config options for making requests + * @returns + */ +export function IOHttp(config: IOHttpConfig): Promise { + return new Promise((resolve, reject) => { + const setting = extend({}, defaultConfig, config) as Required; + const request = new XMLHttpRequest(); + setting.beforeSend(setting); + + request.responseType = setting.dataType as XMLHttpRequestResponseType; + request.timeout = setting.timeout; + request.addEventListener('readystatechange', (event) => { + if (request.readyState === IOHttpReadyState.DONE) { + if (request.status === 200) { + const filter = setting.dataFilter(request.response); + setting.success(filter); + setting.complete(); + resolve(filter); + } else { + setting.error(event); + reject(event); + } + } + }); + request.addEventListener('timeout', (event) => { + setting.error(event); + }); + request.open( + setting.type, + setting.url, + setting.async, + setting.username, + setting.password + ); + + Object.keys(setting.header).forEach((key) => { + const value = setting.header[key]; + request.setRequestHeader(key, value); + }); + + if (isFormData(setting.data)) { + request.setRequestHeader('Content-Type', 'multipart/form-data'); + } else if (setting.contentType) { + request.setRequestHeader('Content-Type', setting.contentType); + } + + switch (setting.type) { + case IOHttpRequestType.DELETE: + case IOHttpRequestType.GET: { + request.send(); + break; + } + case IOHttpRequestType.POST: + case IOHttpRequestType.PUT: { + if (ispPlain(setting.data)) { + request.send(JSON.stringify(setting.data)); + return; + } + if (isString(setting.data)) { + request.send(setting.data); + return; + } + if (isArray(setting.data)) { + request.send(JSON.stringify(setting.data)); + return; + } + if (isFormData(setting.data)) { + request.send(setting.data); + } + break; + } + } + }); +} + +/** + * Send a POST request + * + * @param url The server URL that will be used for the request + * @param data The data to be sent as the request body + * @param success Successful callback + */ +export function Post( + url: string, + data: object = {}, + success: Function = noop +): Promise { + return IOHttp({ url, type: IOHttpRequestType.POST, data, success }); +} + +/** + * Send a Get request + * + * @param url The server URL that will be used for the request + * @param data The data to be sent as the request body + * @param success Successful callback + */ +export function Get( + url: string, + data: object = {}, + success: Function = noop +): Promise { + url += objectToUrlSearch(data); + return IOHttp({ url, type: IOHttpRequestType.GET, success }); +} diff --git a/packages/core/src/Shared/IOSocket.ts b/packages/core/src/Shared/IOSocket.ts new file mode 100644 index 0000000000..9d000817d8 --- /dev/null +++ b/packages/core/src/Shared/IOSocket.ts @@ -0,0 +1,179 @@ +function isDef(v: any) { + return v !== undefined && v !== null; +} + +function unDef(v: any) { + return v === undefined || v === null; +} + +function isBlank(v: string) { + return unDef(v) || v.toString() === ''; +} + +function extend(src: object, ...target: object[]) { + for (const item of target) { + if (item) { + for (const key in item) { + if (item.hasOwnProperty(key)) { + if (isDef(item[key])) { + src[key] = item[key]; + } + } + } + } + } + return src; +} + +/** + * Properties of socket config + */ +export interface IOSocketConfig { + url: string; + heartbeatTime?: number; +} + +/** + * Types if Io Socket Listen + */ +export enum IOSocketListenType { + MESSAGE = 'message', + OPEN = 'open', + CLOSE = 'close', + ERROR = 'error', + RETRY = 'retry', + DESTROY = 'destroy', +} + +const defaultConfig = { + url: '', + heartbeatTime: 1000 * 6, +}; + +/** + * IOSocket send body + */ +export type IOSocketSendBody = string | ArrayBufferLike | Blob | ArrayBufferView; + +/** + * IOSocket type + */ +export class IOSocket { + private _config: IOSocketConfig; + + private _listens: Map; + + private _timer: number; + + private _socket: WebSocket; + + constructor(config: IOSocketConfig) { + const setting = extend( + {}, + defaultConfig, + config + ) as Required; + if (isBlank(setting.url)) { + throw new Error('url must be input'); + } + this._listens = new Map(); + this._timer = -1; + this._config = config; + } + + private _create(): void { + const { _config } = this; + this._socket = new WebSocket(_config.url); + } + + private _bind(): void { + this._socket.addEventListener(IOSocketListenType.MESSAGE, this._message); + this._socket.addEventListener(IOSocketListenType.OPEN, this._open); + this._socket.addEventListener(IOSocketListenType.CLOSE, this._close); + this._socket.addEventListener(IOSocketListenType.ERROR, this._error); + } + + private _message(event: Event): void { + const listen = this._listens.get(IOSocketListenType.MESSAGE); + if (listen) { + listen(event); + } + } + + private _open(event: Event): void { + const listen = this._listens.get(IOSocketListenType.OPEN); + if (listen) { + listen(event); + this._heartbeat(); + } + } + + private _error(event: Event): void { + const listen = this._listens.get(IOSocketListenType.ERROR); + if (listen) { + listen(event); + this._retry(); + } + } + + private _close(event: Event): void { + const listen = this._listens.get(IOSocketListenType.CLOSE); + if (listen) { + listen(event); + this.destroy(); + } + } + + private _clear(): void { + if (this._timer > -1) { + clearInterval(this._timer); + } + this._timer = -1; + this._socket.removeEventListener(IOSocketListenType.MESSAGE, this._message); + this._socket.removeEventListener(IOSocketListenType.OPEN, this._open); + this._socket.removeEventListener(IOSocketListenType.CLOSE, this._close); + this._socket.removeEventListener(IOSocketListenType.ERROR, this._error); + this._socket.close(); + } + + private _heartbeat(): void { + const { _socket, _config } = this; + const { heartbeatTime } = _config; + function handle() { + _socket.send('heartbea_message'); + } + this._timer = setInterval(handle, heartbeatTime) as unknown as number; + } + + private _retry(): void { + const listen = this._listens.get(IOSocketListenType.RETRY); + if (listen) { + listen(); + } + this._clear(); + this._create(); + this._bind(); + } + + link(): void { + this._create(); + this._bind(); + } + + send(body: IOSocketSendBody): void { + this._socket.send(body); + } + + destroy(): void { + const listen = this._listens.get(IOSocketListenType.DESTROY); + if (listen) { + listen(); + } + this._clear(); + this._listens.clear(); + } + + on(type: string, listen: Function): void { + this._listens[type] = listen; + } +} diff --git a/packages/core/src/Shared/KeyboardUtil.ts b/packages/core/src/Shared/KeyboardUtil.ts new file mode 100644 index 0000000000..f8ec9ad205 --- /dev/null +++ b/packages/core/src/Shared/KeyboardUtil.ts @@ -0,0 +1,15 @@ +import { KEYBOARD_KEYS } from '../Const/KEYBOARD_KEYS'; + +const allKeys = Object.keys(KEYBOARD_KEYS).reduce( + (acc, category) => [...acc, ...KEYBOARD_KEYS[category]], + [] as string[] +); + +/** + * Reference of non-printable Keyboard Event.key values + * + * https://www.aarongreenlee.com/blog/list-of-non-printable-keys-for-keyboard-events-when-using-event-key/ + */ +export function isKeyPrintable(key: string) { + return allKeys.indexOf(key) === -1; +} diff --git a/packages/core/src/Shared/LRU/LRUHelper.ts b/packages/core/src/Shared/LRU/LRUHelper.ts new file mode 100644 index 0000000000..0d3892a4fb --- /dev/null +++ b/packages/core/src/Shared/LRU/LRUHelper.ts @@ -0,0 +1,21 @@ +export class LRUHelper { + static hasLength(array: unknown[], size: number) { + return array.length === size; + } + + static getValueType(value: any): string { + return Object.prototype.toString.apply(value); + } + + static isObject(value?: any): value is T { + return this.getValueType(value) === '[object Object]'; + } + + static isIterable(value?: any): value is Iterable { + return value[Symbol.iterator] != null; + } + + static isNumber(value?: any): value is number { + return this.getValueType(value) === '[object Number]'; + } +} diff --git a/packages/core/src/Shared/LRU/LRUMap.ts b/packages/core/src/Shared/LRU/LRUMap.ts new file mode 100644 index 0000000000..935844df6e --- /dev/null +++ b/packages/core/src/Shared/LRU/LRUMap.ts @@ -0,0 +1,351 @@ +import { LRUHelper } from './LRUHelper'; + +const NEWER = Symbol('newer'); +const OLDER = Symbol('older'); + +export class KeyIterator implements IterableIterator { + entry: Entry | undefined; + + constructor(oldestEntry: Entry | undefined) { + this.entry = oldestEntry; + } + + [Symbol.iterator](): IterableIterator { + return this; + } + + next(): IteratorResult { + let ent = this.entry; + if (ent) { + this.entry = ent[NEWER]; + return { done: false, value: ent.key }; + } + return { done: true, value: undefined }; + } +} + +export class ValueIterator implements IterableIterator { + entry: Entry | undefined; + + constructor(oldestEntry: Entry | undefined) { + this.entry = oldestEntry; + } + + [Symbol.iterator](): IterableIterator { + return this; + } + + next(): IteratorResult { + let ent = this.entry; + if (ent) { + this.entry = ent[NEWER]; + return { done: false, value: ent.value }; + } + return { done: true, value: undefined }; + } +} + +export class EntryIterator implements IterableIterator<[K, V]> { + entry: Entry | undefined; + + constructor(oldestEntry: Entry | undefined) { + this.entry = oldestEntry; + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this; + } + + next(): IteratorResult<[K, V], [K, V] | undefined> { + let ent = this.entry; + if (ent) { + this.entry = ent[NEWER]; + return { done: false, value: [ent.key, ent.value] }; + } + return { done: true, value: undefined }; + } +} + +export class Entry { + key: K; + + value: V; + + [NEWER]: Entry | undefined; + + [OLDER]: Entry | undefined; + + constructor(key: K, value: V) { + this.key = key; + this.value = value; + + this[NEWER] = undefined; + this[OLDER] = undefined; + } + + toJSON(): { key: K; value: V } { + return { + key: this.key, + value: this.value, + }; + } +} + +export class LRUMap { + _keymap: Map>; + + size: number; + + limit: number; + + oldest: Entry | undefined; + + newest: Entry | undefined; + + _initialize(limit: number, entries: Iterable<[K, V]> | undefined): void { + this.oldest = undefined; + this.newest = undefined; + this.size = 0; + this.limit = limit; + this._keymap = new Map(); + + if (entries) { + this.assign(entries); + if (limit < 1) { + this.limit = this.size; + } + } + } + + _markEntryAsUsed(entry: Entry) { + if (entry === this.newest) { + // Already the most recently used entry, so no need to update the list + return; + } + // HEAD--------------TAIL + // <.older .newer> + // <--- add direction -- + // A B C E + if (entry[NEWER]) { + if (entry === this.oldest) { + this.oldest = entry[NEWER]; + } + entry[NEWER][OLDER] = entry[OLDER]; // C <-- E. + } + if (entry[OLDER]) { + entry[OLDER][NEWER] = entry[NEWER]; // C. --> E + } + entry[NEWER] = undefined; // D --x + entry[OLDER] = this.newest; // D. --> E + if (this.newest) { + this.newest[NEWER] = entry; // E. <-- D + } + this.newest = entry; + } + + constructor(entries: Iterable<[K, V]>); + constructor(limit: number); + constructor(limit: number, entries: Iterable<[K, V]>); + constructor(...parameter: any) { + if (LRUHelper.hasLength(parameter, 1)) { + if (LRUHelper.isNumber(parameter[0])) { + const limit = parameter[0]; + this._initialize(limit, undefined); + return; + } + if (LRUHelper.isIterable<[K, V]>(parameter[0])) { + const entries = parameter[0]; + this._initialize(0, entries); + return; + } + return; + } + if (LRUHelper.hasLength(parameter, 2)) { + const limit = parameter[0]; + const entries = parameter[1]; + this._initialize(limit, entries); + } + } + + assign(entries: Iterable<[K, V]>): void { + let entry; + let limit = this.limit || Number.MAX_VALUE; + this._keymap.clear(); + let it = entries[Symbol.iterator](); + for (let itv = it.next(); !itv.done; itv = it.next()) { + let e = new Entry(itv.value[0], itv.value[1]); + this._keymap.set(e.key, e); + if (!entry) { + this.oldest = e; + } else { + entry[NEWER] = e; + e[OLDER] = entry; + } + entry = e; + if (limit-- === 0) { + throw new Error('overflow'); + } + } + this.newest = entry; + this.size = this._keymap.size; + } + + set(key: K, value: V): LRUMap { + let entry = this._keymap.get(key); + + if (entry) { + // update existing + entry.value = value; + this._markEntryAsUsed(entry); + return this; + } + + // new entry + this._keymap.set(key, (entry = new Entry(key, value))); + + if (this.newest) { + // link previous tail to the new tail (entry) + this.newest[NEWER] = entry; + entry[OLDER] = this.newest; + } else { + // we're first in -- yay + this.oldest = entry; + } + + // add new entry to the end of the linked list -- it's now the freshest entry. + this.newest = entry; + ++this.size; + if (this.size > this.limit) { + // we hit the limit -- remove the head + this.shift(); + } + + return this; + } + + shift(): [K, V] | undefined { + // todo: handle special case when limit == 1 + let entry = this.oldest; + if (entry) { + if (this.oldest && this.oldest[NEWER]) { + // advance the list + this.oldest = this.oldest[NEWER]; + this.oldest[OLDER] = undefined; + } else { + // the cache is exhausted + this.oldest = undefined; + this.newest = undefined; + } + // Remove last strong reference to and remove links from the purged + // entry being returned: + entry[NEWER] = entry[OLDER] = undefined; + this._keymap.delete(entry.key); + --this.size; + return [entry.key, entry.value]; + } + } + + get(key: K): V | undefined { + // First, find our cache entry + const entry = this._keymap.get(key); + if (!entry) return; // Not cached. Sorry. + // As was found in the cache, register it as being requested recently + this._markEntryAsUsed(entry); + return entry.value; + } + + has(key: K): boolean { + return this._keymap.has(key); + } + + find(key: K): V | undefined { + let e = this._keymap.get(key); + return e ? e.value : undefined; + } + + delete(key: K): V | undefined { + let entry = this._keymap.get(key); + if (!entry) return; + this._keymap.delete(entry.key); + if (entry[NEWER] && entry[OLDER]) { + // relink the older entry with the newer entry + entry[OLDER][NEWER] = entry[NEWER]; + entry[NEWER][OLDER] = entry[OLDER]; + } else if (entry[NEWER]) { + // remove the link to us + entry[NEWER][OLDER] = undefined; + // link the newer entry to head + this.oldest = entry[NEWER]; + } else if (entry[OLDER]) { + // remove the link to us + entry[OLDER][NEWER] = undefined; + // link the newer entry to head + this.newest = entry[OLDER]; + } else { + // if(entry[OLDER] === undefined && entry.newer === undefined) { + this.oldest = this.newest = undefined; + } + + this.size--; + return entry.value; + } + + clear(): void { + // Not clearing links should be safe, as we don't expose live links to user + this.oldest = undefined; + this.newest = undefined; + this.size = 0; + this._keymap.clear(); + } + + keys(): Iterator { + return new KeyIterator(this.oldest); + } + + values(): Iterator { + return new ValueIterator(this.oldest); + } + + entries(): Iterator<[K, V], [K, V] | undefined> { + return this[Symbol.iterator](); + } + + [Symbol.iterator](): Iterator<[K, V], [K, V] | undefined> { + return new EntryIterator(this.oldest); + } + + forEach(fun: (value: V, key: K, m: LRUMap) => void, thisObj?: any): void { + if (typeof thisObj !== 'object') { + thisObj = this; + } + let entry = this.oldest; + while (entry) { + fun.call(thisObj, entry.value, entry.key, this); + entry = entry[NEWER]; + } + } + + toJSON(): Array<{ key: K; value: V }> { + let s = new Array(this.size); + let i = 0; + let entry = this.oldest; + while (entry) { + s[i++] = { key: entry.key, value: entry.value }; + entry = entry[NEWER]; + } + return s; + } + + toString(): string { + let s = String(); + let entry = this.oldest; + while (entry) { + s += `${String(entry.key)}:${entry.value}`; + entry = entry[NEWER]; + if (entry) { + s += ' < '; + } + } + return s; + } +} diff --git a/packages/core/src/Shared/LRU/index.ts b/packages/core/src/Shared/LRU/index.ts new file mode 100644 index 0000000000..25ea0f7dcc --- /dev/null +++ b/packages/core/src/Shared/LRU/index.ts @@ -0,0 +1,2 @@ +export * from './LRUHelper'; +export * from './LRUMap'; diff --git a/packages/core/src/Shared/Locale.ts b/packages/core/src/Shared/Locale.ts new file mode 100644 index 0000000000..d0e15df5a8 --- /dev/null +++ b/packages/core/src/Shared/Locale.ts @@ -0,0 +1,150 @@ +import { Tools } from '.'; +import { LocaleType } from '../Enum'; +import { IWorkbookConfig } from '../Interfaces'; +import { Attribute, IOCAttribute, PostConstruct } from '../IOC'; +import { Nullable } from './Types'; + +/** + * The data structure stored by the Locale class + */ +export type ILocaleOptions = { + /** + * Current Locale such as 'en' / 'en-US' / 'zh' / ''zh-CN / 'zh-TW' + * + * @remarks + * See {@link LocaleType| the LocaleType enum} for more details. + * + * @defaultValue `LocaleType.EN` + */ + currentLocale: LocaleType; + + /** + * The content of international translation, stored in key-value form, + * + * @example + * Locale data like + * ```json + * { + * "en": { + * "name": "value" + * }, + * "zh": { + * "name": "值" + * } + * } + * ``` + */ + locales: object; +}; + +/** + * Basic English internationalization object + * + * The internationalized language pack of the plug-in will be dynamically loaded in each plug-in. + */ +const zh = {}; +const en = {}; + +/** + * Internationalization + * + * @remarks + * Get inspiration from the react-intl-universal project, see {@link https://github.com/alibaba/react-intl-universal | the protocol spec}. + * + * @beta + */ +export class Locale { + @Attribute() + private _attribute: IOCAttribute; + + options: ILocaleOptions; + + @PostConstruct() + init() { + const config: IWorkbookConfig = this._attribute.getValue(); + const locale = config.locale; + + this.options = { + // use config first, or get language setting from browser + currentLocale: + locale || ['zh', 'zh-CN'].includes(Tools.getLanguage()) + ? LocaleType.ZH + : LocaleType.EN, + locales: { zh, en }, + }; + } + + /** + * Get the formatted message by key + * + * @example + * ```ts + * Locale.get('name') + * ``` + * + * @param key - The string representing key in Locale data file + * @returns Get the translation corresponding to the Key + */ + get(key: string | undefined): string { + if (key) { + const { locales, currentLocale } = this.options; + return (Locale.getValue(locales[currentLocale], key) as string) || ''; + } + return String(); + } + + getObject(key: string): T { + const { locales, currentLocale } = this.options; + return Locale.getValue(locales[currentLocale], key) as unknown as T; + } + + /** + * Load more locales after init + * + * @example + * ```ts + * Locale.load({zh,en}) + * ``` + * + * @param locales - Locale object + * @returns void + * + */ + load(locales: object): void { + Tools.deepMerge(this.options.locales, locales); + } + + /** + * change Locale + * + * @example + * Change to Chinese + * ```ts + * Locale.change(LocaleType.ZH) + * ``` + * + * @param locale - Locale Type, see {@link LocaleType} + * + */ + change(locale: LocaleType): void { + this.options.currentLocale = locale; + } + + /** + * get value from Locale object and key + * @param locale - A specified language pack + * @param key - Specify key + * @returns Get the translation corresponding to the Key + * + * @private + */ + private static getValue(locale: object, key: string): Nullable { + try { + return locale[key] + ? locale[key] + : key.split('.').reduce((a, b) => a[b], locale); + } catch (error) { + console.error('Key %s not found', key); + } + } +} diff --git a/packages/core/src/Shared/Logger.ts b/packages/core/src/Shared/Logger.ts new file mode 100644 index 0000000000..f8580281ce --- /dev/null +++ b/packages/core/src/Shared/Logger.ts @@ -0,0 +1,43 @@ +/** + * Logging tool + */ +export class Logger { + static info(...value: unknown[]): void { + if (console) { + console.log(...value); + } + } + + static error(...value: unknown[]): void { + if (console) { + console.error(...value); + } + } + + static warn(...value: unknown[]): void { + if (console) { + console.warn(...value); + } + } + + static br(): void { + Logger.info('\n'); + } + + static capsule( + env: string, + version: string, + prefix: string = '', + suffix = '' + ): void { + if (console) { + console.log( + `%c${prefix}%c${env}%c${version}%c${suffix}`, + '', + 'padding:3px;color:white;background:#023047', + 'padding:3px;color:white;background:#219EBC', + '' + ); + } + } +} diff --git a/packages/core/src/Shared/Migrate/Border.ts b/packages/core/src/Shared/Migrate/Border.ts new file mode 100644 index 0000000000..4a7a7c8512 --- /dev/null +++ b/packages/core/src/Shared/Migrate/Border.ts @@ -0,0 +1,754 @@ +import { IWorksheetConfig, IBorderData } from '../../Interfaces'; + +export function border(newSheet: Partial, sheet: any) { + newSheet.cellData = {}; + for (let borderInfo of sheet.config.borderInfo) { + if (borderInfo.rangeType === 'cell') { + const rowIndex = borderInfo.value.row_index; + const colIndex = borderInfo.value.col_index; + + if (!newSheet.cellData[String(rowIndex)]) { + newSheet.cellData[String(rowIndex)] = {}; + } + + if (!newSheet.cellData[String(rowIndex)][String(colIndex)]) { + newSheet.cellData[String(rowIndex)][String(colIndex)] = {}; + } + + if (!newSheet.cellData[String(rowIndex)][String(colIndex)].s) { + newSheet.cellData[String(rowIndex)][String(colIndex)].s = {}; + } + + const newBorder: IBorderData = {}; + + if (borderInfo.value.l) { + newBorder.l = { + s: borderInfo.value.l.style, + cl: { + rgb: borderInfo.value.l.color, + }, + }; + + if ( + newSheet.cellData[String(rowIndex)] && + newSheet.cellData[String(rowIndex)][String(colIndex - 1)]?.s?.bd + ?.r + ) { + delete newSheet.cellData[String(rowIndex)][String(colIndex - 1)] + .s.bd.r; + } + } + if (borderInfo.value.r) { + newBorder.r = { + s: borderInfo.value.r.style, + cl: { + rgb: borderInfo.value.r.color, + }, + }; + + if ( + newSheet.cellData[String(rowIndex)] && + newSheet.cellData[String(rowIndex)][String(Number(colIndex) + 1)] + ?.s?.bd?.l + ) { + delete newSheet.cellData[String(rowIndex)][String(colIndex - 1)] + .s.bd.l; + } + } + if (borderInfo.value.t) { + newBorder.t = { + s: borderInfo.value.t.style, + cl: { + rgb: borderInfo.value.t.color, + }, + }; + + if ( + newSheet.cellData[String(rowIndex - 1)] && + newSheet.cellData[String(rowIndex)][String(colIndex)]?.s?.bd?.b + ) { + delete newSheet.cellData[String(rowIndex)][String(colIndex - 1)] + .s.bd.b; + } + } + if (borderInfo.value.b) { + newBorder.b = { + s: borderInfo.value.b.style, + cl: { + rgb: borderInfo.value.b.color, + }, + }; + + if ( + newSheet.cellData[String(Number(rowIndex) + 1)] && + newSheet.cellData[String(rowIndex)][String(colIndex)]?.s?.bd?.t + ) { + delete newSheet.cellData[String(rowIndex)][String(colIndex - 1)] + .s.bd.t; + } + } + + newSheet.cellData[String(rowIndex)][String(colIndex)].s.bd = + Object.assign( + newSheet.cellData[String(rowIndex)][String(colIndex)].s.bd || {}, + newBorder + ); + } else if (borderInfo.rangeType === 'range') { + for (const range of borderInfo.range) { + const startRow = range.row[0]; + const endRow = range.row[1]; + const startColumn = range.column[0]; + const endColumn = range.column[1]; + + switch (borderInfo.borderType) { + case 'border-left': + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c < startColumn + 1; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + newBorder.l = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newSheet.cellData[String(r)][String(c)].s.bd = + Object.assign( + newSheet.cellData[String(r)][String(c)].s + .bd || {}, + newBorder + ); + } + } + + // 去除 左侧单元格的右边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn - 1; c < startColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.r + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.r; + } + } + } + + break; + case 'border-right': + for (let r = startRow; r <= endRow; r++) { + for (let c = endColumn; c < endColumn + 1; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + newBorder.r = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newSheet.cellData[String(r)][String(c)].s.bd = + Object.assign( + newSheet.cellData[String(r)][String(c)].s + .bd || {}, + newBorder + ); + } + } + + // 去除 右侧单元格的左边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = endColumn + 1; c < endColumn + 2; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.l + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.l; + } + } + } + + break; + case 'border-top': + for (let r = startRow; r < startRow + 1; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + newBorder.t = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newSheet.cellData[String(r)][String(c)].s.bd = + Object.assign( + newSheet.cellData[String(r)][String(c)].s + .bd || {}, + newBorder + ); + } + } + + // 去除 上侧单元格的下边框 + for (let r = startRow - 1; r < startRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.b + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.b; + } + } + } + + break; + case 'border-bottom': + for (let r = endRow; r < endRow + 1; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + newBorder.b = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newSheet.cellData[String(r)][String(c)].s.bd = + Object.assign( + newSheet.cellData[String(r)][String(c)].s + .bd || {}, + newBorder + ); + } + } + + // 去除 下侧单元格的上边框 + for (let r = endRow + 1; r < endRow + 2; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.t + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.t; + } + } + } + + break; + case 'border-all': + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + // 所有单元格都有左上边框,最后一列补 右边框,最后一行补 下边框 + newBorder.l = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newBorder.t = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + if (c === endColumn) { + newBorder.r = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } + + if (r === endRow) { + newBorder.b = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } + + newSheet.cellData[String(r)][String(c)].s.bd = + newBorder; + } + } + + // 去除 左侧单元格的右边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn - 1; c < startColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.r + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.r; + } + } + } + + // 去除 右侧单元格的左边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = endColumn + 1; c < endColumn + 2; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.l + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.l; + } + } + } + + // 去除 上侧单元格的下边框 + for (let r = startRow - 1; r < startRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.b + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.b; + } + } + } + + // 去除 下侧单元格的上边框 + for (let r = endRow + 1; r < endRow + 2; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.t + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.t; + } + } + } + + break; + case 'border-outside': + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + // 第一行 + if (r === startRow) { + newBorder.t = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } + // 最后一行 + if (r === endRow) { + newBorder.b = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } + // 第一列 + if (c === startColumn) { + newBorder.l = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } + // 最后一列 + if (c === endColumn) { + newBorder.r = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } + + newSheet.cellData[String(r)][String(c)].s.bd = + Object.assign( + newSheet.cellData[String(r)][String(c)].s + .bd || {}, + newBorder + ); + } + } + + // 去除 左侧单元格的右边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn - 1; c < startColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.r + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.r; + } + } + } + + // 去除 右侧单元格的左边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = endColumn + 1; c < endColumn + 2; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.l + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.l; + } + } + } + + // 去除 上侧单元格的下边框 + for (let r = startRow - 1; r < startRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.b + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.b; + } + } + } + + // 去除 下侧单元格的上边框 + for (let r = endRow + 1; r < endRow + 2; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.t + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.t; + } + } + } + + break; + case 'border-inside': + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + // 第一行第一列 没有样式 + // 第一行 左边框 + // 第一列 上边框 + // 其他 左上边框 + if (r === startRow && c === startColumn) { + continue; + } + + if (r === startRow) { + newBorder.l = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } else if (c === startColumn) { + newBorder.t = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } else { + newBorder.l = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newBorder.t = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + } + + newSheet.cellData[String(r)][String(c)].s.bd = + newBorder; + } + } + + break; + case 'border-horizontal': + for (let r = startRow; r < endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + newBorder.b = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newSheet.cellData[String(r)][String(c)].s.bd = + Object.assign( + newSheet.cellData[String(r)][String(c)].s + .bd || {}, + newBorder + ); + } + } + + // 去除 第二行开始的上边框 + for (let r = startRow + 1; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.t + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.t; + } + } + } + + break; + case 'border-vertical': + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c < endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + newSheet.cellData[String(r)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + newSheet.cellData[String(r)][String(c)] = {}; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + newSheet.cellData[String(r)][String(c)].s = {}; + } + + const newBorder: IBorderData = {}; + + newBorder.r = { + s: borderInfo.style, + cl: { + rgb: borderInfo.color, + }, + }; + + newSheet.cellData[String(r)][String(c)].s.bd = + Object.assign( + newSheet.cellData[String(r)][String(c)].s + .bd || {}, + newBorder + ); + } + } + + // 去除 第二列开始的左边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn + 1; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.l + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.l; + } + } + } + + break; + case 'border-none': + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if (!newSheet.cellData[String(r)]) { + continue; + } + + if (!newSheet.cellData[String(r)][String(c)]) { + continue; + } + + if (!newSheet.cellData[String(r)][String(c)].s) { + continue; + } + + // 置空 + const newBorder: IBorderData = {}; + + newSheet.cellData[String(r)][String(c)].s.bd = + newBorder; + } + } + + // 去除 左侧单元格的右边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn - 1; c < startColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.r + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.r; + } + } + } + + // 去除 右侧单元格的左边框 + for (let r = startRow; r <= endRow; r++) { + for (let c = endColumn + 1; c < endColumn + 2; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.l + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.l; + } + } + } + + // 去除 上侧单元格的下边框 + for (let r = startRow - 1; r < startRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.b + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.b; + } + } + } + + // 去除 下侧单元格的上边框 + for (let r = endRow + 1; r < endRow + 2; r++) { + for (let c = startColumn; c <= endColumn; c++) { + if ( + newSheet.cellData[String(r)] && + newSheet.cellData[String(r)][String(c)]?.s?.bd?.t + ) { + delete newSheet.cellData[String(r)][String(c)].s + .bd.t; + } + } + } + + break; + + default: + break; + } + } + } + } +} diff --git a/packages/core/src/Shared/Migrate/Migrate.ts b/packages/core/src/Shared/Migrate/Migrate.ts new file mode 100644 index 0000000000..a3cbac1293 --- /dev/null +++ b/packages/core/src/Shared/Migrate/Migrate.ts @@ -0,0 +1,416 @@ +import { nanoid } from 'nanoid'; +import { + IWorkbookConfig, + IWorksheetConfig, + ICellData, + IElementsOrder, + ITextStyle, + IStyleData, +} from '../../Interfaces'; +import { IKeyValue } from '../Types'; +import { border } from './Border'; + +export function migrate(config: any): Partial { + const newConfig: Partial = {}; + newConfig.styles = {}; + if (config.hasOwnProperty('lang')) { + newConfig.locale = config.lang; + } + if (config.hasOwnProperty('data')) { + newConfig.sheets = {}; + for (let sheet of config.data) { + const newSheet: Partial = {}; + + // id + if (sheet.hasOwnProperty('index')) { + newSheet.id = sheet.index as string; + } else { + newSheet.id = nanoid(6); + } + + // 缩放比例 + if (sheet.hasOwnProperty('zoomRatio')) { + newSheet.zoomRatio = sheet.zoomRatio; + } + + // 最大行号 + if (sheet.hasOwnProperty('row')) { + newSheet.rowCount = sheet.row; + } + + // 最大列号 + if (sheet.hasOwnProperty('column')) { + newSheet.columnCount = sheet.column; + } + + // 是否激活 + if (sheet.hasOwnProperty('status')) { + newSheet.status = sheet.status; + } + + // sheet 名称 + if (sheet.hasOwnProperty('name')) { + newSheet.name = sheet.name; + } + + // 激活选区 + if (sheet.hasOwnProperty('universheet_select_save')) { + newSheet.selections = []; + sheet.universheet_select_save.forEach((selection: IKeyValue) => { + newSheet.selections?.push({ + startRow: selection.row[0], + endRow: selection.row[1], + startColumn: selection.column[0], + endColumn: selection.column[1], + }); + }); + } + + // 横向滚动条位置 + if (sheet.hasOwnProperty('scrollLeft')) { + newSheet.scrollLeft = sheet.scrollLeft; + } + + // 竖向滚动条位置 + if (sheet.hasOwnProperty('scrollTop')) { + newSheet.scrollTop = sheet.scrollTop; + } + + if (sheet.hasOwnProperty('config')) { + // 合并单元格 + if (sheet.config.merge) { + newSheet.mergeData = []; + for (const key of Object.keys(sheet.config.merge)) { + const merge: { + r: number; + c: number; + rs: number; + cs: number; + } = sheet.config.merge[key]; + const mergeReact = { + startRow: merge.r, + endRow: merge.r + merge.rs - 1, + startColumn: merge.c, + endColumn: merge.c + merge.cs - 1, + }; + newSheet.mergeData.push(mergeReact); + } + } + + // 边框,先按顺序设置到每一个单元格对象中,设置时需要遍历四个方向的相邻单元格做去重,最后单元格样式确定之后,再统一提取到styles上 + if (sheet.config.borderInfo) { + border(newSheet, sheet); + } + + // 行高 + if (sheet.config.rowlen) { + newSheet.rowData = {}; + for (const [rowIndex, height] of Object.entries( + sheet.config.rowlen + )) { + newSheet.rowData[String(rowIndex)] = { + h: height, + hd: 0, + }; + } + } + // 列宽 + if (sheet.config.columnlen) { + newSheet.columnData = {}; + for (const [colIndex, width] of Object.entries( + sheet.config.columnlen + )) { + newSheet.columnData[String(colIndex)] = { + w: width, + hd: 0, + }; + } + } + // 隐藏行 + if (sheet.config.rowhidden) { + if (!newSheet.rowData) newSheet.rowData = {}; + for (const [rowIndex, isHidden] of Object.entries( + sheet.config.rowhidden + )) { + if (!newSheet.rowData[String(rowIndex)]) { + newSheet.rowData[String(rowIndex)] = {}; + } + + newSheet.rowData[String(rowIndex)].hd = isHidden; + } + } + // 隐藏列 + if (sheet.config.colhidden) { + if (!newSheet.columnData) newSheet.columnData = {}; + for (const [colIndex, isHidden] of Object.entries( + sheet.config.colhidden + )) { + if (!newSheet.columnData[String(colIndex)]) { + newSheet.columnData[String(colIndex)] = {}; + } + newSheet.columnData[String(colIndex)].hd = isHidden; + } + } + } + + // 单元格数据 + if (sheet.hasOwnProperty('celldata')) { + if (!newSheet.cellData) { + newSheet.cellData = {}; + } + + for (const cellItem of sheet.celldata) { + if (!newSheet.cellData[cellItem.r]) { + newSheet.cellData[cellItem.r] = {}; + } + if (!newSheet.cellData[cellItem.r][cellItem.c]) { + newSheet.cellData[cellItem.r][cellItem.c] = {}; + } + + const cell = cellItem.v; + + const newCell: ICellData = + newSheet.cellData[cellItem.r][cellItem.c]; + + if (cell?.ct?.t === 'inlineStr') { + const elements = {}; + const elementOrder: IElementsOrder[] = []; + + cell.ct.s.forEach((element: any) => { + const textStyle: ITextStyle = {}; + + // 背景颜色 + if (element.hasOwnProperty('bg')) { + textStyle.bg = { + rgb: element.fc, + }; + } + + // 字体 + if (element.hasOwnProperty('ff')) { + textStyle.ff = element.ff; + } + + // 字体颜色 + if (element.hasOwnProperty('fc')) { + textStyle.cl = { + rgb: element.fc, + }; + } + + // 字体大小 + if (element.hasOwnProperty('fs')) { + textStyle.fs = element.fs; + } + + // 粗体 + if (element.hasOwnProperty('bl')) { + textStyle.bl = element.bl; + } + + // 斜体 + if (element.hasOwnProperty('it')) { + textStyle.it = element.it; + } + + // 删除线 + if (element.hasOwnProperty('cl')) { + textStyle.st = { + s: 1, + }; + } + + // 下划线 + if (element.hasOwnProperty('un')) { + textStyle.ul = { + s: element.un, + }; + } + + const eId = nanoid(6); + elements[eId] = { + eId, + st: 0, + ed: element.v.length - 1, + et: 0, + tr: { + ct: element.v, + ts: textStyle, + }, + }; + + elementOrder.push({ + elementId: eId, + paragraphElementType: 0, + }); + }); + + newCell.p = { + documentId: nanoid(6), + body: { + blockElements: { + p1: { + blockId: 'p1', + st: 0, + ed: cell.ct.s.length - 1, + blockType: 0, + paragraph: { + elements, + elementOrder, + }, + }, + }, + blockElementOrder: ['p1'], + }, + documentStyle: {}, + }; + } else { + // 原始值 + if (cell.hasOwnProperty('v')) { + newCell.v = cell.v; + } + + // 显示值 + if (cell.hasOwnProperty('m')) { + newCell.m = cell.m; + } + + const cellStyle: IStyleData = {}; + + // 背景颜色 + if (cell.hasOwnProperty('bg')) { + cellStyle.bg = { + rgb: cell.bg, + }; + } + + // 字体 + if (cell.hasOwnProperty('ff')) { + cellStyle.ff = cell.ff; + } + + // 字体颜色 + if (cell.hasOwnProperty('fc')) { + cellStyle.cl = { + rgb: cell.fc, + }; + } + + // 字体大小 + if (cell.hasOwnProperty('fs')) { + cellStyle.fs = cell.fs; + } + + // 粗体 + if (cell.hasOwnProperty('bl')) { + cellStyle.bl = cell.bl; + } + + // 斜体 + if (cell.hasOwnProperty('it')) { + cellStyle.it = cell.it; + } + + // 删除线 + if (cell.hasOwnProperty('cl')) { + cellStyle.st = { + s: cell.cl, + }; + } + + // 下划线 + if (cell.hasOwnProperty('un')) { + cellStyle.ul = { + s: cell.un, + }; + } + + const sMap = { + '0': 2, + '1': 1, + '2': 3, + }; + + // 垂直对齐 + if (cell.hasOwnProperty('vt')) { + cellStyle.vt = sMap[String(cell.vt)]; + } + + // 水平对齐 + if (cell.hasOwnProperty('ht')) { + cellStyle.ht = sMap[String(cell.ht)]; + } + + // 竖排文字 + if (cell.hasOwnProperty('tr')) { + switch (cell.tr) { + case '0': + cellStyle.tr = { + a: 0, + v: 0, + }; + break; + + case '1': + cellStyle.tr = { + a: 45, + v: 0, + }; + break; + + case '2': + cellStyle.tr = { + a: -45, + v: 0, + }; + break; + + case '3': + cellStyle.tr = { + a: 0, + v: 1, + }; + break; + + case '4': + cellStyle.tr = { + a: 90, + v: 0, + }; + break; + + case '5': + cellStyle.tr = { + a: -90, + v: 0, + }; + break; + + default: + break; + } + } + + // 文字旋转角度 + if (cell.hasOwnProperty('rt')) { + cellStyle.tr = { + a: cell.tr, + v: 0, + }; + } + + // 文本换行 + if (cell.hasOwnProperty('tb')) { + cellStyle.tb = sMap[String(cell.tb)]; + } + + newCell.s = Object.assign(newCell.s || {}, cellStyle); + } + } + } + + newConfig.sheets[newSheet.id] = newSheet; + } + } + return newConfig; +} diff --git a/packages/core/src/Shared/Migrate/index.ts b/packages/core/src/Shared/Migrate/index.ts new file mode 100644 index 0000000000..bc87ae7480 --- /dev/null +++ b/packages/core/src/Shared/Migrate/index.ts @@ -0,0 +1 @@ +export * from './Migrate'; diff --git a/packages/core/src/Shared/NameGen.ts b/packages/core/src/Shared/NameGen.ts new file mode 100644 index 0000000000..40b35989c9 --- /dev/null +++ b/packages/core/src/Shared/NameGen.ts @@ -0,0 +1,29 @@ +const include: string[] = []; +let identity: number = 0; + +/** + * Generate unique sheet name + */ +export class NameGen { + private static _generateName(): string { + return `sheet${++identity}`; + } + + private static _checkedName(name: string): boolean { + const checked = include.includes(name); + if (!checked) { + include.push(name); + } + return checked; + } + + static getSheetName(name?: string): string { + if (name === undefined) { + name = NameGen._generateName(); + } + while (NameGen._checkedName(name)) { + name = NameGen._generateName(); + } + return name; + } +} diff --git a/packages/core/src/Shared/ObjectArray.ts b/packages/core/src/Shared/ObjectArray.ts new file mode 100644 index 0000000000..8968be8ca1 --- /dev/null +++ b/packages/core/src/Shared/ObjectArray.ts @@ -0,0 +1,468 @@ +import { Tools } from './Tools'; +import { Nullable } from './Types'; + +/** + * Predicate Function type + */ +export type PredicateFunction = (index: number, value: T) => Nullable; + +/** + * Function type + */ +export type Function = (value: Nullable) => S; + +/** + * Unary Operator Function type + */ +export type UnaryOperatorFunction = (value: T) => T; + +/** + * Object Array Primitive Type + */ +export type ObjectArrayPrimitiveType = { [key: number]: T }; + +/** + * Object Array Type + */ +export type ObjectArrayType = ObjectArray | ObjectArrayPrimitiveType; + +const define = (value?: T): value is T => value !== undefined && value !== null; + +/** + * Arrays in object form and provide an array-like API + * + * @beta + */ +export class ObjectArray { + static getMaxLength(array: Nullable>) { + if (array) { + const keys: unknown[] = Object.keys(array); + if (keys.length) { + return Math.max(...(keys as number[])) + 1; + } + } + return 0; + } + + private _array: ObjectArrayPrimitiveType; + + private _length: number; + + constructor(); + constructor(array: object); + constructor(size: number); + constructor(array: object, size: number); + constructor(...argument: any) { + if (Tools.hasLength(argument, 1)) { + if (Tools.isObject(argument[0])) { + this._array = argument[0] as { [index: number]: T }; + this._length = ObjectArray.getMaxLength(this._array); + return; + } + if (Tools.isNumber(argument[0])) { + this._array = {}; + this._length = argument[0]; + return; + } + } + if (Tools.hasLength(argument, 2)) { + const array = argument[0]; + const size = argument[1]; + this._array = array; + this._length = size; + return; + } + if (Tools.hasLength(argument, 0)) { + this._array = {}; + this._length = 0; + } + } + + obtain(index: number, defaultValue: T): T { + return this._array[index] ?? defaultValue; + } + + getKeys(): string[] { + return Object.keys(this._array); + } + + get(index: number): Nullable { + return this._array[index]; + } + + set(index: number, value: T): void { + if (define(value)) { + const length = this._length; + this._array[index] = value; + if (index >= length) { + this._length = index + 1; + } + } + } + + pop(): Nullable { + const length = this._length; + const array = this._array; + if (length > 0) { + const lastIndex = length - 1; + const lastValue = array[lastIndex]; + if (lastIndex > 0) { + delete array[lastIndex]; + this._length--; + } + return lastValue; + } + return null; + } + + push(value: T): void { + if (define(value)) { + let length = this._length; + const array = this._array; + array[length++] = value; + this._length = length; + } + } + + first(): Nullable { + return this._array[0]; + } + + last(): Nullable { + const length = this._length - 1; + return this._array[length]; + } + + shift(): Nullable { + const length = this._length; + if (length > 0) { + const first = this._array[0]; + const sizeOf = length - 1; + let next: T; + for (let i = 0; i < sizeOf; i++) { + next = this._array[i + 1]; + if (define(next)) { + this._array[i] = next; + } + } + this._length--; + delete this._array[sizeOf]; + return first; + } + return null; + } + + unshift(topValue: T): void { + if (define(topValue)) { + const length = this._length; + const array = this._array; + const sizeOf = length + 1; + let last = array[0]; + for (let i = 1; i < sizeOf; i++) { + const temp = this._array[i]; + if (define(last)) { + array[i] = last; + } + last = temp; + } + array[0] = topValue; + this._length = sizeOf; + } + } + + clear(): void { + this._array = {}; + this._length = 0; + } + + getLength(): number { + return this._length; + } + + getSizeOf(): number { + const array = this._array; + const keys = Object.keys(array); + return keys.length; + } + + toJSON(): ObjectArrayPrimitiveType { + return this._array; + } + + toArray(): T[] { + const native = new Array(); + const array = this._array; + const keys = Object.keys(array); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + native[key] = array[key]; + } + return native; + } + + forEach(callback: PredicateFunction): ObjectArray { + const array = this._array; + const keys = Object.keys(array); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + const result = callback(key, array[key]); + if (result === false) { + return this; + } + } + return this; + } + + clone(callback: Nullable>): ObjectArray { + const instance = new ObjectArray(); + if (callback) { + this.forEach((idx, val) => { + instance.set(idx, callback(val)); + }); + } else { + this.forEach((idx, val) => { + instance.set(idx, val); + }); + } + return instance; + } + + isEmpty(): boolean { + const array = this._array; + const keys = Object.keys(array); + return keys.length === 0; + } + + delete(index: number): void { + this.splice(index, 1); + } + + includes(target: T): boolean { + const array = this._array; + const keys = Object.keys(array); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + const value = array[key]; + if (value === target) { + return true; + } + } + return false; + } + + slice(start: number, end: number): ObjectArray { + const array = this._array; + const length = this._length; + if (length > 0) { + const fragment = {}; + let effective = 0; + for (let i = start; i < end; i++) { + const item = array[i]; + if (define(item)) { + fragment[effective] = array[i]; + effective++; + } + } + return new ObjectArray(fragment, effective); + } + return new ObjectArray(); + } + + concat(target: ObjectArray): ObjectArray { + const srcArray = this._array; + const srcKeys = Object.keys(srcArray); + const srcLength = srcKeys.length; + + const targetArray = target._array; + const targetKeys = Object.keys(targetArray); + const targetLength = targetKeys.length; + + const container = new ObjectArray(srcLength + targetLength); + const containerArray = container._array; + let master = 0; + + for (let i = 0; i < srcLength; i++, master++) { + const key = srcKeys[i]; + containerArray[master] = srcArray[key]; + } + for (let i = 0; i < targetLength; i++, master++) { + const key = targetKeys[i]; + containerArray[master] = targetArray[key]; + } + + return container; + } + + find(callback: PredicateFunction): Nullable { + const array = this._array; + const keys = Object.keys(array); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + const value = array[key]; + const result = callback(key, value); + if (result === true) { + return value; + } + } + return null; + } + + splice(start: number, count: number): ObjectArray { + const length = this._length; + if (length > 0) { + const end = start + count; + const array = this._array; + let effective = 0; + const splice = {}; + for (let i = start; i < end; i++) { + const item = array[i]; + if (define(item)) { + delete array[i]; + splice[effective] = item; + effective++; + } + } + + const diff = end - start; + const last = length; + this._length -= diff; + + if (this._length <= 0) { + this._array = {}; + this._length = 0; + } else { + for (let i = end; i < last; i++) { + const item = array[i]; + if (define(item)) { + array[i - diff] = array[i]; + delete array[i]; + } + } + } + return new ObjectArray(splice, effective); + } + return new ObjectArray(); + } + + findIndex(callback: PredicateFunction): number { + const array = this._array; + const keys = Object.keys(array); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + const result = callback(key, array[key]); + if (result === true) { + return i; + } + } + return -1; + } + + map(callback: Function): ObjectArray { + const array = this._array; + const keys = Object.keys(array); + const length = keys.length; + const result = {}; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + const value = array[key]; + result[i] = callback(value); + } + return new ObjectArray(result); + } + + filter(callback: PredicateFunction): ObjectArray { + const array = this._array; + const keys = Object.keys(array); + const length = keys.length; + const filter = {}; + let master = 0; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + const value = array[key]; + if (callback(key, value)) { + filter[master] = value; + master++; + } + } + return new ObjectArray(filter, master); + } + + insert(index: number, value: T): ObjectArray { + const length = this._length; + const array = this._array; + for (let i = length - 1; i >= index; i--) { + array[i + 1] = array[i]; + } + array[index] = value; + this._length = length + 1; + return this; + } + + inserts(index: number, target: ObjectArray): ObjectArray { + const targetArray = target._array; + const targetLength = target._length; + + const srcArray = this._array; + const srcLength = this._length; + + const lastIndex = srcLength - 1; + for (let i = lastIndex; i >= index; i--) { + const item = srcArray[i]; + if (define(item)) { + delete srcArray[i]; + srcArray[i + targetLength] = item; + } + } + for (let i = 0; i < targetLength; i++) { + const item = targetArray[i]; + if (define(item)) { + srcArray[i + index] = targetArray[i]; + } + } + this._length += targetLength; + return this; + } + + [Symbol.iterator](): IterableIterator { + return new ObjectArrayIterableIterator(this); + } +} + +/** + * Iterator with ObjectArray + */ +export class ObjectArrayIterableIterator implements IterableIterator { + private _array: ObjectArray; + + private _keys: string[]; + + private _cursor: number; + + constructor(objectArray: ObjectArray) { + this._array = objectArray; + this._keys = objectArray.getKeys(); + this._cursor = 0; + } + + next(): IteratorResult { + const array = this._array; + const keys = this._keys; + const cursor = this._cursor; + if (cursor < keys.length) { + const key = keys[cursor]; + const value = array.get(+key); + this._cursor++; + return { value: value as T, done: false }; + } + return { value: null, done: true }; + } + + [Symbol.iterator](): IterableIterator { + return new ObjectArrayIterableIterator(this._array); + } +} diff --git a/packages/core/src/Shared/ObjectMatrix.ts b/packages/core/src/Shared/ObjectMatrix.ts new file mode 100644 index 0000000000..9aac83522d --- /dev/null +++ b/packages/core/src/Shared/ObjectMatrix.ts @@ -0,0 +1,301 @@ +import { IRangeData } from '../Interfaces/IRangeData'; +import { ObjectArray, PredicateFunction } from './ObjectArray'; +import { Tools } from './Tools'; +import { Nullable } from './Types'; + +/** + * Object Matrix Primitive Type + */ +export type ObjectMatrixPrimitiveType = { + [key: number]: { [key: number]: T }; +}; + +/** + * Object Matrix Type + */ +export type ObjectMatrixType = + | ObjectMatrix + | ObjectArray> + | ObjectMatrixPrimitiveType; + +/** + * A two-dimensional array represented by a two-level deep object and provides an array-like API + + @beta + */ +export class ObjectMatrix { + private _matrix: ObjectArray>; + + constructor(objectArray: ObjectMatrixType = {}) { + if (Tools.isAssignableFrom(objectArray, ObjectMatrix)) { + this._matrix = objectArray._matrix; + return; + } + if (Tools.isAssignableFrom(objectArray, ObjectArray)) { + this._matrix = objectArray; + return; + } + if (Tools.isPlainObject(objectArray)) { + this._matrix = new ObjectArray>(objectArray); + } + } + + getMatrix() { + return this._matrix; + } + + forEach(callback: PredicateFunction>): ObjectMatrix { + const array = this._matrix; + const keys = array.getKeys(); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = +keys[i]; + const value = this.getRow(key) as ObjectArray; + const result = callback(key, value); + if (result === false) { + return this; + } + } + return this; + } + + swapRow(src: number, target: number): void { + const srcRow = this._matrix.get(src); + const targetRow = this._matrix.get(target); + this._matrix.set(target, srcRow as ObjectArray); + this._matrix.set(src, targetRow as ObjectArray); + } + + getRow(rowIndex: number): Nullable> { + const { _matrix } = this; + let element = _matrix.get(rowIndex); + if (Tools.isAssignableFrom(element, ObjectArray)) { + return element; + } + if (Tools.isPlainObject(element)) { + element = new ObjectArray(element); + _matrix.set(rowIndex, element); + return element; + } + } + + getRowOrCreate(rowIndex: number): ObjectArray { + const objectArray = this.getRow(rowIndex); + if (objectArray) { + return objectArray; + } + const createArray = new ObjectArray(); + this._matrix.set(rowIndex, createArray); + return createArray; + } + + reset() { + this._matrix = new ObjectArray>({}); + } + + hasValue() { + const matrix = this._matrix; + for (const row of matrix) { + if (!row.isEmpty()) { + return true; + } + } + return false; + } + + getValue(row: number, column: number): Nullable { + const objectArray = this.getRow(row); + if (objectArray) { + return objectArray.get(column); + } + return null; + } + + setValue(row: number, column: number, value: Nullable): void { + const objectArray = this.getRowOrCreate(row); + objectArray.set(column, value as T); + } + + deleteValue(row: number, column: number): void { + const objectArray = this.getRow(row); + if (objectArray) { + objectArray.delete(column); + } + } + + spliceRows(start: number, count: number): ObjectMatrix { + const splice = this._matrix.splice(start, count); + return new ObjectMatrix(splice); + } + + pushRow(row: ObjectArray): void { + this._matrix.push(row); + } + + insertRow(rowIndex: number, row: ObjectArray): void { + this._matrix.insert(rowIndex, row); + } + + insertRows(rowIndex: number, matrix: ObjectMatrix) { + this._matrix.inserts(rowIndex, matrix._matrix); + } + + spliceColumns(start: number, count: number): ObjectMatrix { + const columnData = new ObjectMatrix(); + this._matrix.forEach((index, value) => { + for (let i = start; i < start + count; i++) { + columnData.setValue(index, i - start, this.getValue(index, i)); + } + this._matrix.get(index)?.splice(start, count); + }); + + return columnData; + } + + // insertColumn(columnIndex: number, columnData: T) { + // this._matrix.forEach((index, value) => { + // value.insert(columnIndex, columnData[index]); + // }); + // } + + insertColumns(columnIndex: number, columnData: ObjectMatrix) { + const count = columnData.getRow(0)!.getLength(); + this._matrix.forEach((index, value) => { + for (let i = columnIndex; i < columnIndex + count; i++) { + const data = columnData.getRow(index)?.get(i - columnIndex); + value.insert(i, columnData.getRow(index)?.get(i - columnIndex) as T); + } + }); + } + + getFragments( + startRow: number, + endRow: number, + startColumn: number, + endColumn: number + ): ObjectMatrix { + const objectMatrix = new ObjectMatrix(); + for (let r = startRow; r <= endRow; r++) { + const row = new ObjectArray(); + for (let c = startColumn; c <= endColumn; c++) { + const value = this.getValue(r, c) as T; + row.push(value); + } + objectMatrix.pushRow(row); + } + return objectMatrix; + } + + getSizeOf(): number { + return this._matrix.getSizeOf(); + } + + getLength(): number { + return this._matrix.getLength(); + } + + getRange(): IRangeData { + const startRow = 0; + const startColumn = 0; + const endRow = this.getSizeOf(); + let endColumn = 0; + const length = this.getLength(); + for (let i = 0; i < length; i++) { + const row = this.getRow(i); + if (row) { + const sizeof = row.getSizeOf(); + if (row.getSizeOf() > endColumn) { + endColumn = sizeof - 1; + } + } + } + return { + startRow, + startColumn, + endRow, + endColumn, + }; + } + + toArray(): T[][] { + return this._matrix.toArray().map((item) => item.toArray()); + } + + toJSON(): ObjectMatrixPrimitiveType { + const { _matrix } = this; + return _matrix.toJSON() as unknown as ObjectMatrixPrimitiveType; + } + + getData(): ObjectMatrixPrimitiveType { + const json = JSON.stringify(this); + return JSON.parse(json); + } + + getArrayData(): ObjectMatrixPrimitiveType { + let startRow = 0; + let startColumn = 0; + + let initRow = false; + let initColumn = false; + const objectMatrix = new ObjectMatrix(); + this.forEach((rowIndex, row) => { + if (!initRow) { + initRow = true; + startRow = rowIndex; + } + row.forEach((columnIndex, column) => { + if (!initColumn) { + initColumn = true; + startColumn = columnIndex; + } else if (columnIndex < startColumn) { + startColumn = columnIndex; + } + const value = this.getValue(rowIndex, columnIndex) as T; + objectMatrix.setValue( + rowIndex - startRow, + columnIndex - startColumn, + value + ); + }); + }); + + return objectMatrix.getData(); + } + + getDataRange(): IRangeData { + let startRow = 0; + let startColumn = 0; + let endColumn = 0; + + let initRow = false; + let initColumn = false; + + this.forEach((rowIndex, row) => { + if (!initRow) { + initRow = true; + startRow = rowIndex; + } + + const rowSize = row.getLength() - 1; + if (rowSize > endColumn) { + endColumn = rowSize; + } + row.forEach((columnIndex, column) => { + if (!initColumn) { + initColumn = true; + startColumn = columnIndex; + } else if (columnIndex < startColumn) { + startColumn = columnIndex; + } + }); + }); + const endRow = startRow + this.getSizeOf() - 1; + + return { + startRow, + startColumn, + endRow, + endColumn, + }; + } +} diff --git a/packages/core/src/Shared/RectTree/BorderTree.ts b/packages/core/src/Shared/RectTree/BorderTree.ts new file mode 100644 index 0000000000..c3be0d029d --- /dev/null +++ b/packages/core/src/Shared/RectTree/BorderTree.ts @@ -0,0 +1,122 @@ +import { ESRectTree } from './ESRectTree'; +import { RectTree } from './RectTree'; + +export class BorderTree { + rightTree: ESRectTree; + + leftTree: ESRectTree; + + topTree: ESRectTree; + + bottomTree: ESRectTree; + + constructor() { + this.leftTree = new ESRectTree(); + this.rightTree = new ESRectTree(); + this.topTree = new ESRectTree(); + this.bottomTree = new ESRectTree(); + } + + setLeft(node: BorderTree.Node): void { + this.leftTree.destructCrossNode(node); + this.leftTree.insert(node); + } + + setRight(node: BorderTree.Node): void { + this.rightTree.destructCrossNode(node); + this.rightTree.insert(node); + } + + setTop(node: BorderTree.Node): void { + this.topTree.destructCrossNode(node); + this.topTree.insert(node); + } + + setBottom(node: BorderTree.Node): void { + this.bottomTree.destructCrossNode(node); + this.bottomTree.insert(node); + } + + setWhole(node: BorderTree.Node): void { + this.setLeft(node); + this.setRight(node); + this.setTop(node); + this.setBottom(node); + } + + clearLeft(node: BorderTree.Node): void { + this.leftTree.remove(node, (other) => node.equals(other as BorderTree.Node)); + } + + clearRight(node: BorderTree.Node): void { + this.rightTree.remove(node, (other) => + node.equals(other as BorderTree.Node) + ); + } + + clearTop(node: BorderTree.Node): void { + this.topTree.remove(node, (other) => node.equals(other as BorderTree.Node)); + } + + clearBottom(node: BorderTree.Node): void { + this.bottomTree.remove(node, (other) => + node.equals(other as BorderTree.Node) + ); + } + + clearWhole(node: BorderTree.Node): void { + this.clearLeft(node); + this.clearRight(node); + this.clearTop(node); + this.clearBottom(node); + } +} + +export namespace BorderTree { + export class Node extends RectTree.Node { + type: number; + + width: number; + + color: number; + + stamp: number; + + constructor({ + minY = Infinity, + minX = Infinity, + maxY = -Infinity, + maxX = -Infinity, + leaf = true, + height = 1, + childrenNodes = [], + type = 0, + width = 0, + color = 0, + stamp = 0, + }: Partial = {}) { + super({ + minY, + minX, + maxY, + maxX, + leaf, + height, + childrenNodes, + }); + this.type = type; + this.width = width; + this.color = color; + this.stamp = stamp; + } + + equals(other: BorderTree.Node) { + return ( + this.minX === other.minX && + this.minY === other.minY && + other.maxX === this.maxX && + other.maxY === this.maxY + ); + } + } +} diff --git a/packages/core/src/Shared/RectTree/ESRectTree.ts b/packages/core/src/Shared/RectTree/ESRectTree.ts new file mode 100644 index 0000000000..42e03c5412 --- /dev/null +++ b/packages/core/src/Shared/RectTree/ESRectTree.ts @@ -0,0 +1,108 @@ +import { RectTree } from './RectTree'; + +export class ESRectTree extends RectTree { + rowExpandSplitAxis(index: number, count: number = 1): void { + const baseLineNode = new RectTree.Node({ + minY: index, + minX: 0, + maxY: index, + maxX: Infinity, + }); + const crossNodes = this.search(baseLineNode); + const collectNode = new Array(); + for (let i = 0; i < crossNodes.length; i++) { + const node = crossNodes[i]; + this.remove(node); + if (node.minY >= index) { + node.minY += count; + } + if (node.maxY >= index) { + node.maxY += count; + } + collectNode.push(node); + } + this.load(collectNode); + } + + colExpandSplitAxis(index: number, count: number = 1): void { + const baseLineNode = new RectTree.Node({ + minY: 0, + minX: index, + maxY: Infinity, + maxX: index, + }); + const crossNodes = this.search(baseLineNode); + const collectNode = new Array(); + for (let i = 0; i < crossNodes.length; i++) { + const node = crossNodes[i]; + this.remove(node); + if (node.minX >= index) { + node.minX += count; + } + if (node.maxX >= index) { + node.maxX += count; + } + collectNode.push(node); + } + this.load(collectNode); + } + + rowShrinkSplitAxis(index: number, count: number = 1): void { + const baseLineNode = new RectTree.Node({ + minY: index, + minX: 0, + maxY: index, + maxX: Infinity, + }); + const crossNodes = this.search(baseLineNode); + const collectNode = new Array(); + for (let i = 0, len = crossNodes.length; i < len; i++) { + const node = crossNodes[i]; + this.remove(node); + if (node.minY >= index) { + node.minY -= count; + } + if (node.maxY >= index) { + node.maxY -= count; + } + collectNode.push(node); + } + this.load(collectNode); + } + + colShrinkSplitAxis(index: number, count: number = 1): void { + const baseLineNode = new RectTree.Node({ + minY: 0, + minX: index, + maxY: Infinity, + maxX: index, + }); + const crossNodes = this.search(baseLineNode); + const collectNode = new Array(); + for (let i = 0; i < crossNodes.length; i++) { + const node = crossNodes[i]; + this.remove(node); + if (node.minX >= index) { + node.minX -= count; + } + if (node.maxX >= index) { + node.maxX -= count; + } + collectNode.push(node); + } + this.load(collectNode); + } + + destructCrossNode(master: RectTree.Node): RectTree.Node[] { + const crossNodes = this.search(master); + const collectNode = new Array(); + for (const node of crossNodes) { + if (!master.contains(node)) { + master.difference(node, collectNode); + } + this.remove(node); + } + this.load(collectNode); + return crossNodes; + } +} diff --git a/packages/core/src/Shared/RectTree/MergeTree.ts b/packages/core/src/Shared/RectTree/MergeTree.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/Shared/RectTree/QuickSelect.ts b/packages/core/src/Shared/RectTree/QuickSelect.ts new file mode 100644 index 0000000000..5a9e64ad18 --- /dev/null +++ b/packages/core/src/Shared/RectTree/QuickSelect.ts @@ -0,0 +1,84 @@ +function QuickSwap(array: T[], i: number, j: number): void { + const tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; +} + +function QuickCompare(a: T, b: T): number { + return a < b ? -1 : a > b ? 1 : 0; +} + +function QuickStep( + array: T[], + k: number, + left: number, + right: number, + compare: Compare +): void { + while (right > left) { + if (right - left > 600) { + const n = right - left + 1; + const m = k - left + 1; + const z = Math.log(n); + const s = 0.5 * Math.exp((2 * z) / 3); + const sd = + 0.5 * Math.sqrt((z * s * (n - s)) / n) * (m - n / 2 < 0 ? -1 : 1); + const newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd)); + const newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd)); + QuickStep(array, k, newLeft, newRight, compare); + } + + const t = array[k]; + let i = left; + let j = right; + + QuickSwap(array, left, k); + if (compare(array[right], t) > 0) { + QuickSwap(array, left, right); + } + + while (i < j) { + QuickSwap(array, i, j); + i++; + j--; + while (compare(array[i], t) < 0) { + i++; + } + while (compare(array[j], t) > 0) { + j--; + } + } + + if (compare(array[left], t) === 0) { + QuickSwap(array, left, j); + } else { + j++; + QuickSwap(array, j, right); + } + + if (j <= k) { + left = j + 1; + } + if (k <= j) { + right = j - 1; + } + } +} + +export type Compare = (a: T, b: T) => number; + +export function QuickSelect( + array: T[], + k: number, + left: number, + right: number, + compare: Compare +) { + QuickStep( + array, + k, + left || 0, + right || array.length - 1, + compare || QuickCompare + ); +} diff --git a/packages/core/src/Shared/RectTree/RectTree.ts b/packages/core/src/Shared/RectTree/RectTree.ts new file mode 100644 index 0000000000..894e1def2a --- /dev/null +++ b/packages/core/src/Shared/RectTree/RectTree.ts @@ -0,0 +1,684 @@ +import { Nullable } from '../Types'; +import { Compare, QuickSelect } from './QuickSelect'; + +const defaultCompareNodeMinX = (a: RectTree.Node, b: RectTree.Node) => + a.minX - b.minX; +const defaultCompareNodeMinY = (a: RectTree.Node, b: RectTree.Node) => + a.minY - b.minY; + +export class RectTree { + static findItem(item: T, items: T[], equalsFn: Nullable>) { + if (!equalsFn) { + return items.indexOf(item); + } + for (let i = 0; i < items.length; i++) { + if (equalsFn(item, items[i])) return i; + } + return -1; + } + + static multiSelect( + array: RectTree.Node[], + left: number, + right: number, + n: number, + compare: Compare + ) { + // 对数组进行排序,以便项目以 n 个未排序项目为一组,各组在彼此之间排序; + // 将选择算法与二元分治法相结合 + const stack: number[] = [left, right]; + while (stack.length) { + right = stack.pop() as number; + left = stack.pop() as number; + if (right - left <= n) { + continue; + } + const mid = left + Math.ceil((right - left) / n / 2) * n; + QuickSelect(array, mid, left, right, compare); + stack.push(left, mid, mid, right); + } + } + + private _maxEntries: number; + + private _minEntries: number; + + private _data: RectTree.Node; + + private _compareMinX: Compare; + + private _compareMinY: Compare; + + private _chooseSplitAxis(node: RectTree.Node, m: number, M: number) { + // 按最佳轴对节点子节点进行排序以进行拆分 + const compareMinX = node.leaf ? this._compareMinX : defaultCompareNodeMinX; + const compareMinY = node.leaf ? this._compareMinY : defaultCompareNodeMinY; + const xMargin = this._allDistMargin(node, m, M, compareMinX); + const yMargin = this._allDistMargin(node, m, M, compareMinY); + // 如果 x 的总分配边际值最小,则按 minX 排序, + // 否则它已经按 minY 排序 + if (xMargin < yMargin) { + node.childrenNodes.sort(compareMinX); + } + } + + private _allDistMargin( + node: RectTree.Node, + m: number, + M: number, + compare: Compare + ) { + node.childrenNodes.sort(compare); + const leftBBox = node.distBBox(0, m); + const rightBBox = node.distBBox(M - m, M); + let margin = leftBBox.bboxMargin() + rightBBox.bboxMargin(); + + for (let i = m; i < M - m; i++) { + const child = node.childrenNodes[i]; + leftBBox.extend(child); + margin += leftBBox.bboxMargin(); + } + for (let i = M - m - 1; i >= m; i--) { + const child = node.childrenNodes[i]; + rightBBox.extend(child); + margin += rightBBox.bboxMargin(); + } + + return margin; + } + + private _split(insertPath: RectTree.Node[], level: number) { + // 将溢出的节点一分为二 + const node = insertPath[level]; + const M = node.childrenNodes.length; + const m = this._minEntries; + + this._chooseSplitAxis(node, m, M); + + const splitIndex = this._chooseSplitIndex(node, m, M); + + const newNode = RectTree.Node.makeParentNode( + node.childrenNodes.splice( + splitIndex, + node.childrenNodes.length - splitIndex + ) + ); + newNode.height = node.height; + newNode.leaf = node.leaf; + + node.calcBBox(); + newNode.calcBBox(); + + if (level) { + insertPath[level - 1].childrenNodes.push(newNode); + } else { + this._splitRoot(node, newNode); + } + } + + private _condense(path: RectTree.Node[]) { + // 遍历路径,删除空节点并更新 bboxes + for (let i = path.length - 1, siblings; i >= 0; i--) { + if (path[i].childrenNodes.length === 0) { + if (i > 0) { + siblings = path[i - 1].childrenNodes; + siblings.splice(siblings.indexOf(path[i]), 1); + } else { + this.clear(); + } + } else { + path[i].calcBBox(); + } + } + } + + private _splitRoot(node: RectTree.Node, newNode: RectTree.Node) { + // 分裂根节点 + this._data = RectTree.Node.makeParentNode([node, newNode]); + this._data.height = node.height + 1; + this._data.leaf = false; + this._data.calcBBox(); + } + + private _insert(item: RectTree.Node, level: number) { + const bbox: RectTree.Node = item; + const insertPath: RectTree.Node[] = []; + + // 找到容纳物品的最佳节点,同时保存路径上的所有节点 + const node = this._chooseSubtree(bbox, this._data, level, insertPath); + + // 将项目放入节点 + node.childrenNodes.push(item); + node.extend(bbox); + + // 节点溢出时分裂; 必要时向上传播 + while (level >= 0) { + if (insertPath[level].childrenNodes.length > this._maxEntries) { + this._split(insertPath, level); + level--; + } else { + break; + } + } + + // 沿插入路径调整 bboxes + this._adjustParentBBoxes(bbox, insertPath, level); + } + + private _build( + items: RectTree.Node[], + left: number, + right: number, + height: number + ) { + const N = right - left + 1; + let M = this._maxEntries; + let node; + + if (N <= M) { + // 达到叶级; 回叶 + node = RectTree.Node.makeParentNode(items.slice(left, right + 1)); + node.calcBBox(); + return node; + } + + if (!height) { + // 散装树的目标高度 + height = Math.ceil(Math.log(N) / Math.log(M)); + // 目标根条目数以最大限度地提高存储利用率 + M = Math.ceil(N / M ** (height - 1)); + } + + node = RectTree.Node.makeParentNode([]); + node.leaf = false; + node.height = height; + + // 将项目分成 M 个主要为方形的元素 + const N2 = Math.ceil(N / M); + const N1 = N2 * Math.ceil(Math.sqrt(M)); + + RectTree.multiSelect(items, left, right, N1, defaultCompareNodeMinX); + + for (let i = left; i <= right; i += N1) { + const right2 = Math.min(i + N1 - 1, right); + RectTree.multiSelect(items, i, right2, N2, defaultCompareNodeMinY); + for (let j = i; j <= right2; j += N2) { + const right3 = Math.min(j + N2 - 1, right2); + // 递归地打包每个条目 + node.childrenNodes.push(this._build(items, j, right3, height - 1)); + } + } + + node.calcBBox(); + return node; + } + + private _chooseSplitIndex(node: RectTree.Node, m: number, M: number) { + let index; + let minOverlap = Infinity; + let minArea = Infinity; + for (let i = m; i <= M - m; i++) { + const bbox1 = node.distBBox(0, i); + const bbox2 = node.distBBox(i, M); + + const overlap = bbox1.intersectionArea(bbox2); + const area = bbox1.bboxArea() + bbox2.bboxArea(); + + // 选择重叠最小的分布 + if (overlap < minOverlap) { + minOverlap = overlap; + index = i; + minArea = area < minArea ? area : minArea; + } else if (overlap === minOverlap) { + // 否则选择面积最小的分布 + if (area < minArea) { + minArea = area; + index = i; + } + } + } + return index || M - m; + } + + private _adjustParentBBoxes( + bbox: RectTree.Node, + path: RectTree.Node[], + level: number + ) { + // 沿给定的树路径调整 bbox + for (let i = level; i >= 0; i--) { + path[i].extend(bbox); + } + } + + private _chooseSubtree( + bbox: RectTree.Node, + node: RectTree.Node, + level: number, + path: RectTree.Node[] + ) { + while (true) { + path.push(node); + + if (node.leaf || path.length - 1 === level) { + break; + } + + let minArea = Infinity; + let minEnlargement = Infinity; + let targetNode; + + for (let i = 0; i < node.childrenNodes.length; i++) { + const child = node.childrenNodes[i]; + const area = child.bboxArea(); + const enlargement = bbox.enlargedArea(child) - area; + // 选择最小面积扩大的入口 + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = area < minArea ? area : minArea; + targetNode = child; + } else if (enlargement === minEnlargement) { + // 否则选择面积最小的一个 + if (area < minArea) { + minArea = area; + targetNode = child; + } + } + } + + node = targetNode || node.childrenNodes[0]; + } + return node; + } + + constructor(max: number = 9) { + // 默认情况下,节点中的最大条目数为 9; 最小节点填充为 40% 以获得最佳性能 + this._maxEntries = Math.max(4, max); + this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); + this._data = null as unknown as RectTree.Node; + this._compareMinX = defaultCompareNodeMinX; + this._compareMinY = defaultCompareNodeMinY; + this.clear(); + } + + all(): RectTree.Node[] { + return RectTree.Node.expandNode(this._data, []); + } + + load(data: RectTree.Node[]): RectTree { + if (!(data && data.length)) { + return this; + } + + if (data.length < this._minEntries) { + for (let i = 0; i < data.length; i++) { + this.insert(data[i]); + } + return this; + } + + // 使用 OMT 算法从头开始使用给定数据递归构建树 + let node = this._build(data.slice(), 0, data.length - 1, 0); + + if (!this._data.childrenNodes.length) { + // 如果树为空,则按原样保存 + this._data = node; + } else if (this._data.height === node.height) { + // 如果树具有相同的高度,则拆分根 + this._splitRoot(this._data, node); + } else { + if (this._data.height < node.height) { + // 如果插入的树更大,则交换树 + const tmpNode = this._data; + this._data = node; + node = tmpNode; + } + // 将小树插入大树中适当的层次 + this._insert(node, this._data.height - node.height - 1); + } + + return this; + } + + clear(): RectTree { + this._data = RectTree.Node.makeParentNode([]); + return this; + } + + remove( + item: RectTree.Node, + equals: Nullable> + ): RectTree { + if (!item) { + return this; + } + + let node = this._data; + const path: RectTree.Node[] = []; + const indexes: number[] = []; + const bbox = item; + let i = 0; + let goingUp: boolean = false; + let parent: RectTree.Node = null as unknown as RectTree.Node; + + // 深度优先迭代树遍历 + while (node || path.length) { + if (!node) { + // 往上 + node = path.pop() as RectTree.Node; + parent = path[path.length - 1]; + i = indexes.pop() as number; + goingUp = true; + } + + if (node.leaf) { + // 检查当前节点 + const index = RectTree.findItem(item, node.childrenNodes, equals); + if (index !== -1) { + // 找到项目,移除项目并向上压缩树 + node.childrenNodes.splice(index, 1); + path.push(node); + this._condense(path); + return this; + } + } + + if (!goingUp && !node.leaf && node.contains(bbox)) { + // 往下 + path.push(node); + indexes.push(i as number); + i = 0; + parent = node; + node = node.childrenNodes[0]; + } else if (parent) { + // 向右走 + i++; + node = parent.childrenNodes[i]; + goingUp = false; + } else { + // 没有发现 + node = null as unknown as RectTree.Node; + } + } + + return this; + } + + insert(item: RectTree.Node): RectTree { + if (item) { + this._insert(item, this._data.height - 1); + } + return this; + } + + collides(bbox: RectTree.Node): boolean { + let node: RectTree.Node = this._data; + + if (!bbox.intersects(node)) { + return false; + } + + const nodesToSearch = []; + + while (node) { + for (let i = 0; i < node.childrenNodes.length; i++) { + const child = node.childrenNodes[i]; + const childBBox = child; + if (bbox.intersects(childBBox)) { + if (node.leaf || bbox.contains(childBBox)) { + return true; + } + nodesToSearch.push(child); + } + } + node = nodesToSearch.pop() as RectTree.Node; + } + + return false; + } + + first(bbox: RectTree.Node): RectTree.Node { + return this.search(bbox)[0]; + } + + search(bbox: RectTree.Node): RectTree.Node[] { + let node = this._data; + const result: RectTree.Node[] = []; + + if (!bbox.intersects(node)) { + return result; + } + + const nodesToSearch = []; + + while (node) { + for (let i = 0; i < node.childrenNodes.length; i++) { + const child = node.childrenNodes[i]; + const childBBox = child; + if (bbox.intersects(childBBox)) { + if (node.leaf) { + result.push(child); + } else if (bbox.contains(childBBox)) { + RectTree.Node.expandNode(child, result); + } else { + nodesToSearch.push(child); + } + } + } + node = nodesToSearch.pop() as RectTree.Node; + } + + return result; + } +} + +export type EqualsFunction = (a: T, b: T) => boolean; + +export namespace RectTree { + export class Node { + static makeParentNode(childrenNodes: RectTree.Node[]) { + return new RectTree.Node({ childrenNodes }); + } + + static expandNode( + node: RectTree.Node, + result: RectTree.Node[] + ): RectTree.Node[] { + const nodesToSearch: RectTree.Node[] = []; + while (node) { + if (node.leaf) { + result.push(...node.childrenNodes); + } else { + nodesToSearch.push(...node.childrenNodes); + } + node = nodesToSearch.pop() as RectTree.Node; + } + return result; + } + + minX: number; + + range: Range; + + minY: number; + + maxX: number; + + maxY: number; + + leaf: boolean; + + height: number; + + childrenNodes: RectTree.Node[]; + + constructor({ + minY = Infinity, // sri + minX = Infinity, // sci + maxY = -Infinity, // eri + maxX = -Infinity, // eci + leaf = true, + height = 1, + childrenNodes = [], + }: Partial = {}) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + this.leaf = leaf; + this.height = height; + this.childrenNodes = childrenNodes; + } + + difference(other: RectTree.Node, collect: Nullable) { + const ret: RectTree.Node[] = collect || []; + const addNode = ( + minY: number, + minX: number, + maxY: number, + maxX: number + ) => { + ret.push( + new RectTree.Node({ + minY, + minX, + maxY, + maxX, + }) + ); + }; + const { minY, minX, maxY, maxX } = this; + const diffMinY = other.minY - minY; + const diffMinX = other.minX - minX; + const diffMaxY = maxY - other.maxY; + const diffMaxX = maxX - other.maxX; + if (diffMinY > 0) { + addNode(minY, minX, other.minY - 1, maxX); + if (diffMaxY > 0) { + addNode(other.maxY + 1, minX, maxY, maxX); + if (diffMinX > 0) { + addNode(other.minY, minX, other.maxY, other.minX - 1); + } + if (diffMaxX > 0) { + addNode(other.minY, other.maxX + 1, other.maxY, maxX); + } + } else { + if (diffMinX > 0) { + addNode(other.minY, minX, maxY, other.minX - 1); + } + if (diffMaxX > 0) { + addNode(other.minY, other.maxX + 1, maxY, maxX); + } + } + } else if (diffMaxY > 0) { + addNode(other.maxY + 1, minX, maxY, maxX); + if (diffMinX > 0) { + addNode(minY, minX, other.maxY, other.minX - 1); + } + if (diffMaxX > 0) { + addNode(minY, other.maxX + 1, other.maxY, maxX); + } + } + if (diffMinX > 0) { + addNode(minY, minX, maxY, other.minX - 1); + if (diffMaxX > 0) { + addNode(minY, other.maxY + 1, maxY, maxX); + if (diffMinY > 0) { + addNode(minY, other.minX, other.minY - 1, other.maxX); + } + if (diffMaxY > 0) { + addNode(other.minY + 1, other.minX, maxY, other.maxX); + } + } else { + if (diffMinY > 0) { + addNode(minY, other.minX, other.minY - 1, maxX); + } + if (diffMaxY > 0) { + addNode(other.minY + 1, other.minX, maxY, maxX); + } + } + } else if (diffMaxX > 0) { + addNode(minY, other.maxX + 1, maxY, maxX); + if (diffMinY > 0) { + addNode(minY, minX, other.minY - 1, other.maxX); + } + if (diffMaxY > 0) { + addNode(other.maxY + 1, minX, maxY, other.maxX); + } + } + return ret; + } + + bboxArea() { + return (this.maxX - this.minX) * (this.maxY - this.minY); + } + + bboxMargin() { + return this.maxX - this.minX + (this.maxY - this.minY); + } + + calcBBox() { + // 从其子节点的框计算节点框 + this.distBBox(0, this.childrenNodes.length, this); + } + + distBBox(k: number, p: number, destNode: Nullable) { + // 从 k 到 p-1 的节点子节点的最小边界矩形 + if (!destNode) { + destNode = RectTree.Node.makeParentNode( + null as unknown as RectTree.Node[] + ); + } + destNode.minX = Infinity; + destNode.minY = Infinity; + destNode.maxX = -Infinity; + destNode.maxY = -Infinity; + for (let i = k; i < p; i++) { + const child = this.childrenNodes[i]; + destNode.extend(child); + } + return destNode; + } + + contains(other: RectTree.Node) { + return ( + this.minX <= other.minX && + this.minY <= other.minY && + other.maxX <= this.maxX && + other.maxY <= this.maxY + ); + } + + intersects(other: RectTree.Node) { + return ( + other.minX <= this.maxX && + other.minY <= this.maxY && + other.maxX >= this.minX && + other.maxY >= this.minY + ); + } + + extend(other: RectTree.Node): RectTree.Node { + this.minX = Math.min(this.minX, other.minX); + this.minY = Math.min(this.minY, other.minY); + this.maxX = Math.max(this.maxX, other.maxX); + this.maxY = Math.max(this.maxY, other.maxY); + return this; + } + + enlargedArea(other: RectTree.Node) { + return ( + (Math.max(other.maxX, this.maxX) - Math.min(other.minX, this.minX)) * + (Math.max(other.maxY, this.maxY) - Math.min(other.minY, this.minY)) + ); + } + + intersectionArea(other: RectTree.Node) { + const minX = Math.max(this.minX, other.minX); + const minY = Math.max(this.minY, other.minY); + const maxX = Math.min(this.maxX, other.maxX); + const maxY = Math.min(this.maxY, other.maxY); + return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); + } + } +} diff --git a/packages/core/src/Shared/RectTree/StylesTree.ts b/packages/core/src/Shared/RectTree/StylesTree.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/Shared/Rectangle.ts b/packages/core/src/Shared/Rectangle.ts new file mode 100644 index 0000000000..1026e7766d --- /dev/null +++ b/packages/core/src/Shared/Rectangle.ts @@ -0,0 +1,111 @@ +import { IRangeData } from '../Interfaces'; +import { Tools } from './Tools'; + +/** + * A square area containing four position information: startRow, startColumn, endRow, and endColumn + */ +export class Rectangle { + startRow: number; + + startColumn: number; + + endRow: number; + + endColumn: number; + + constructor(); + constructor(rectangle: IRangeData); + constructor( + startRow: number, + startColumn: number, + endRow: number, + endColumn: number + ); + constructor(...argument: any[]) { + if (Tools.hasLength(argument, 0)) { + this.startRow = 0; + this.startColumn = 0; + this.endRow = 0; + this.endColumn = 0; + return; + } + if (Tools.hasLength(argument, 1)) { + const rectangle = argument[0]; + this.startRow = rectangle.startRow; + this.startColumn = rectangle.startColumn; + this.endRow = rectangle.endRow; + this.endColumn = rectangle.endColumn; + return; + } + if (Tools.hasLength(argument, 4)) { + const startRow = argument[0]; + const startColumn = argument[1]; + const endRow = argument[2]; + const endColumn = argument[3]; + this.startRow = startRow; + this.startColumn = startColumn; + this.endRow = endRow; + this.endColumn = endColumn; + } + } + + intersects(rectangle: Rectangle): boolean { + const currentStartRow = this.startRow; + const currentEndRow = this.endRow; + const currentStartColumn = this.startColumn; + const currentEndColumn = this.endColumn; + + const incomingStartRow = rectangle.startRow; + const incomingEndRow = rectangle.endRow; + const incomingStartColumn = rectangle.startColumn; + const incomingEndColumn = rectangle.endColumn; + + const zx = Math.abs( + currentStartColumn + + currentEndColumn - + incomingStartColumn - + incomingEndColumn + ); + const x = + Math.abs(currentStartColumn - currentEndColumn) + + Math.abs(incomingStartColumn - incomingEndColumn); + const zy = Math.abs( + currentStartRow + currentEndRow - incomingStartRow - incomingEndRow + ); + const y = + Math.abs(currentStartRow - currentEndRow) + + Math.abs(incomingStartRow - incomingEndRow); + + return zx <= x && zy <= y; + } + + union(rectangle: Rectangle) { + const { startRow, startColumn, endRow, endColumn } = this; + return new Rectangle( + rectangle.startRow < this.startRow ? rectangle.startRow : startRow, + rectangle.startColumn < startColumn + ? rectangle.startColumn + : startColumn, + rectangle.endRow > endRow ? rectangle.endRow : endRow, + rectangle.endColumn > endColumn ? rectangle.endColumn : endColumn + ); + } + + getData() { + return { + startRow: this.startRow, + startColumn: this.startColumn, + endRow: this.endRow, + endColumn: this.endColumn, + }; + } + + equals(rectangle: Rectangle) { + return ( + this.endRow === rectangle.endRow && + this.endColumn === rectangle.endColumn && + this.startRow === rectangle.startRow && + this.startColumn === rectangle.startColumn + ); + } +} diff --git a/packages/core/src/Shared/Reference.ts b/packages/core/src/Shared/Reference.ts new file mode 100644 index 0000000000..3eccea0faa --- /dev/null +++ b/packages/core/src/Shared/Reference.ts @@ -0,0 +1,82 @@ +import { IRangeData } from '../Interfaces'; +import { Tools } from './Tools'; + +const COLON_SYMBOL = ':'; + +const $ROW_REGEX = /[^0-9]/g; + +const $COLUMN_REGEX = /[^A-Za-z]/g; + +export interface IGridRangeName { + sheetName: string; + rangeData: IRangeData; +} + +function singleReferenceToGrid(refBody: string) { + let row = parseInt(refBody.replace($ROW_REGEX, '')) - 1; + let column = Tools.ABCatNum(refBody.replace($COLUMN_REGEX, '')); + + return { + row, + column, + }; +} + +export function referenceToGrid(refString: string): IGridRangeName { + const sheetNameIndex = refString.indexOf('!'); + let sheetName: string = ''; + let refBody: string = ''; + if (sheetNameIndex > -1) { + sheetName = refString.substring(0, sheetNameIndex); + refBody = refString.substring(sheetNameIndex); + } else { + refBody = refString; + } + + let colonIndex = refBody.indexOf(COLON_SYMBOL); + + if (colonIndex === -1) { + const grid = singleReferenceToGrid(refBody); + const row = grid.row; + const column = grid.column; + const rangeData: IRangeData = { + startRow: row, + startColumn: column, + endRow: row, + endColumn: column, + }; + + return { + sheetName, + rangeData, + }; + } + + const refStartString = refBody.substring(0, colonIndex); + + const refEndString = refBody.substring(colonIndex + 1); + + const startGrid = singleReferenceToGrid(refStartString); + + const endGrid = singleReferenceToGrid(refEndString); + + const startRow = startGrid.row; + + const startColumn = startGrid.column; + + const endRow = endGrid.row; + + const endColumn = endGrid.column; + + const rangeData: IRangeData = { + startRow, + startColumn, + endRow, + endColumn, + }; + + return { + sheetName, + rangeData, + }; +} diff --git a/packages/core/src/Shared/Serialize/Sequence.ts b/packages/core/src/Shared/Serialize/Sequence.ts new file mode 100644 index 0000000000..35339ee730 --- /dev/null +++ b/packages/core/src/Shared/Serialize/Sequence.ts @@ -0,0 +1,3 @@ +export declare interface Sequence { + className?: string; +} diff --git a/packages/core/src/Shared/Serialize/Serialize.ts b/packages/core/src/Shared/Serialize/Serialize.ts new file mode 100644 index 0000000000..01345bc841 --- /dev/null +++ b/packages/core/src/Shared/Serialize/Serialize.ts @@ -0,0 +1,10 @@ +import { Sequence } from './Sequence'; + +export declare class Serialize { + static newInstance(sequence: Sequence): Serialize; + + static fromSequence(sequence: Sequence[]): Serialize[]; + static fromSequence(sequence: Sequence): Serialize; + + toSequence(): Sequence; +} diff --git a/packages/core/src/Shared/Serialize/SerializeManager.ts b/packages/core/src/Shared/Serialize/SerializeManager.ts new file mode 100644 index 0000000000..56ba2c78bc --- /dev/null +++ b/packages/core/src/Shared/Serialize/SerializeManager.ts @@ -0,0 +1,69 @@ +import { Sequence } from './Sequence'; +import { Tools } from '../Tools'; +import { Serialize } from './Serialize'; + +type SerializeClassType = T; +type SerializeInstance = I; +type SequenceInstance = I; + +export class SerializeManager { + protected _storage = new Map(); + + addSerializer(clazz: SerializeClassType): void; + addSerializer(clazz: SerializeClassType, name: string): void; + addSerializer(...parameter: any): void { + if (Tools.hasLength(parameter, 1)) { + const clazz = parameter[0]; + this._storage.set(clazz.name, clazz); + return; + } + if (Tools.hasLength(parameter, 2)) { + const clazz = parameter[0]; + const name = parameter[1]; + this._storage.set(name, clazz); + } + } + + fromSequence( + sequence: object[] + ): Array>; + fromSequence(sequence: object): SerializeInstance; + fromSequence( + sequence: object | object[] + ): Array> | SerializeInstance { + if (Tools.isObject(sequence)) { + const Clazz = this._storage.get(sequence.className!) as typeof Serialize; + return Clazz.fromSequence(sequence) as SerializeInstance; + } + if (Tools.isArray(sequence)) { + let result = new Array>(); + for (let element of sequence) { + result.push(this.fromSequence(element) as SerializeInstance); + } + return result; + } + return []; + } + + toSequence(instance: SerializeInstance[]): SequenceInstance[]; + toSequence(instance: SerializeInstance): SequenceInstance; + toSequence( + instance: SerializeInstance | SerializeInstance[] + ): SequenceInstance[] | SequenceInstance { + if (Tools.isObject(instance)) { + return instance.toSequence(); + } + if (Tools.isArray(instance)) { + let result = new Array(); + for (let element of instance) { + result.push(this.toSequence(element)); + } + return result; + } + return []; + } + + fromStringifyParse(jsonString: string): SerializeInstance[] | SerializeInstance { + return this.fromSequence(JSON.parse(jsonString)); + } +} diff --git a/packages/core/src/Shared/Serialize/Serializer.ts b/packages/core/src/Shared/Serialize/Serializer.ts new file mode 100644 index 0000000000..38385f2e24 --- /dev/null +++ b/packages/core/src/Shared/Serialize/Serializer.ts @@ -0,0 +1,43 @@ +import { Tools } from '../Tools'; +import { Sequence } from './Sequence'; + +type SerializerNewInstanceType = { newInstance: (sequence: Sequence) => Serializer }; + +export abstract class Serializer { + static fromSequence(sequence: Sequence[]): T[]; + static fromSequence(sequence: Sequence): T; + static fromSequence( + sequence: Sequence | Sequence[] + ): T | T[] { + if (Tools.isObject(sequence)) { + if (Serializer.requiredNewInstance(this)) { + const SerializerClass = this; + return SerializerClass.newInstance(sequence) as T; + } + } + if (Tools.isArray(sequence)) { + if (Serializer.requiredNewInstance(this)) { + const SerializerClass = this; + return sequence.map((element: Sequence) => + SerializerClass.newInstance(element) + ) as T[]; + } + } + return []; + } + + toSequence(): Sequence { + return { className: Tools.getClassName(this) }; + } +} + +export namespace Serializer { + export function requiredNewInstance( + target: any + ): target is SerializerNewInstanceType { + if (Tools.isDefine(target.newInstance)) { + return true; + } + throw new Error('sequence newInstance need subclass implement!'); + } +} diff --git a/packages/core/src/Shared/Serialize/index.ts b/packages/core/src/Shared/Serialize/index.ts new file mode 100644 index 0000000000..3bcc98f5ca --- /dev/null +++ b/packages/core/src/Shared/Serialize/index.ts @@ -0,0 +1,4 @@ +export * from './Sequence'; +export * from './Serialize'; +export * from './SerializeManager'; +export * from './Serializer'; diff --git a/packages/core/src/Shared/Tools.ts b/packages/core/src/Shared/Tools.ts new file mode 100644 index 0000000000..1ffacf4e2f --- /dev/null +++ b/packages/core/src/Shared/Tools.ts @@ -0,0 +1,741 @@ +import { ColorBuilder } from '../Sheets/Domain/ColorBuilder'; +import { Environment } from '../Basics'; +import { ICellData, ICellInfo, IColorStyle, ISelection } from '../Interfaces'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from './ObjectMatrix'; +import { Class, IKeyValue, Nullable } from './Types'; + +const rmsPrefix = /^-ms-/; +const rDashAlpha = /-([a-z])/g; + +const alphabets = [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', +]; + +/** + * Universal tool library + */ +export class Tools { + static stringAt(index: number): string { + let str = ''; + let idx = index; + while (idx >= alphabets.length) { + idx /= alphabets.length; + idx -= 1; + str += alphabets[idx % alphabets.length]; + } + const last = index % alphabets.length; + str += alphabets[last]; + return str; + } + + static getEnvironment(): Environment { + const { appVersion } = navigator; + const system = this.getSystemType(); + return { + app_version: appVersion, + os: system, + os_version: system, + device_id: '', + platform: 'web', + }; + } + + static indexAt(code: string): number { + let ret = 0; + for (let i = 0; i < code.length - 1; i += 1) { + const idx = code.charCodeAt(i) - 65; + const expoNet = code.length - 1 - i; + ret += alphabets.length ** expoNet + alphabets.length * idx; + } + ret += code.charCodeAt(code.length - 1) - 65; + return ret; + } + + static deleteBlank(value?: string) { + if (Tools.isString(value)) { + return value.replace(/\s/g, ''); + } + return value; + } + + static getSystemType(): string { + const sUserAgent = navigator.userAgent; + const isWin = + navigator.platform === 'Win32' || navigator.platform === 'Windows'; + const isMac = + navigator.platform === 'Mac68K' || + navigator.platform === 'MacPPC' || + navigator.platform === 'Macintosh' || + navigator.platform === 'MacIntel'; + if (isMac) return 'Mac'; + const isUnix = navigator.platform === 'X11' && !isWin && !isMac; + if (isUnix) return 'Unix'; + const isLinux = String(navigator.platform).indexOf('Linux') > -1; + if (isLinux) return 'Linux'; + if (isWin) { + const isWin2K = + sUserAgent.indexOf('Windows NT 5.0') > -1 || + sUserAgent.indexOf('Windows 2000') > -1; + if (isWin2K) return 'Windows 2000'; + const isWinXP = + sUserAgent.indexOf('Windows NT 5.1') > -1 || + sUserAgent.indexOf('Windows XP') > -1; + if (isWinXP) return 'Windows XP'; + const isWin2003 = + sUserAgent.indexOf('Windows NT 5.2') > -1 || + sUserAgent.indexOf('Windows 2003') > -1; + if (isWin2003) return 'Windows 2003'; + const isWinVista = + sUserAgent.indexOf('Windows NT 6.0') > -1 || + sUserAgent.indexOf('Windows Vista') > -1; + if (isWinVista) return 'Windows Vista'; + const isWin7 = + sUserAgent.indexOf('Windows NT 6.1') > -1 || + sUserAgent.indexOf('Windows 7') > -1; + if (isWin7) return 'Windows 7'; + const isWin10 = + sUserAgent.indexOf('Windows NT 10') > -1 || + sUserAgent.indexOf('Windows 10') > -1; + if (isWin10) return 'Windows 10'; + const isWin11 = + sUserAgent.indexOf('Windows NT 11') > -1 || + sUserAgent.indexOf('Windows 11') > -1; + if (isWin11) return 'Windows 11'; + } + return 'Unknown system'; + } + + static getBrowserType(): string { + const userAgent = navigator.userAgent; + const isOpera = userAgent.indexOf('Opera') > -1; + const isIE = + userAgent.indexOf('compatible') > -1 && + userAgent.indexOf('MSIE') > -1 && + !isOpera; + const isIE11 = + userAgent.indexOf('Trident') > -1 && userAgent.indexOf('rv:11.0') > -1; + const isEdge = userAgent.indexOf('Edge') > -1; + const isFF = userAgent.indexOf('Firefox') > -1; + const isSafari = + userAgent.indexOf('Safari') > -1 && userAgent.indexOf('Chrome') === -1; + const isChrome = + userAgent.indexOf('Chrome') > -1 && userAgent.indexOf('Safari') > -1; + if (isIE) { + const reIE = new RegExp('MSIE (\\d+\\.\\d+);'); + reIE.test(userAgent); + const fIEVersion = parseFloat(RegExp.$1); + if (fIEVersion === 7) { + return 'IE7'; + } + if (fIEVersion === 8) { + return 'IE8'; + } + if (fIEVersion === 9) { + return 'IE9'; + } + if (fIEVersion === 10) { + return 'IE10'; + } + return '0'; + } + if (isFF) { + return 'FF'; + } + if (isOpera) { + return 'Opera'; + } + if (isSafari) { + return 'Safari'; + } + if (isChrome) { + return 'Chrome'; + } + if (isEdge) { + return 'Edge'; + } + if (isIE11) { + return 'IE11'; + } + return 'Unknown browser'; + } + + static generateRandomId(): string { + function S4() { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + } + return `${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`; + } + + static getClassName(instance: object): string { + return instance.constructor.name; + } + + static deepMerge(target: any, ...sources: any[]): any { + sources.forEach((item) => item && deepItem(item)); + + function deepArray(array: any[], to: any[]) { + array.forEach((value, key) => { + if (Tools.isArray(value)) { + const origin = to[key] ?? []; + to[key] = origin; + deepArray(value, origin); + return; + } + if (Tools.isObject(value)) { + const origin = to[key] ?? {}; + to[key] = origin; + deepObject(value, origin); + return; + } + to[key] = value; + }); + } + + function deepObject(object: any, to: any) { + Object.keys(object).forEach((key) => { + const value = object[key]; + if (Tools.isObject(value)) { + const origin = to[key] ?? {}; + to[key] = origin; + deepObject(value, origin); + return; + } + if (Tools.isArray(value)) { + const origin = to[key] ?? []; + to[key] = origin; + deepArray(value, origin); + return; + } + to[key] = value; + }); + } + + function deepItem(item: any) { + Object.keys(item).forEach((key) => { + const value = item[key]; + if (Tools.isArray(value)) { + const origin = target[key] ?? []; + target[key] = origin; + deepArray(value, origin); + return; + } + if (Tools.isObject(value)) { + const origin = target[key] ?? {}; + target[key] = origin; + deepObject(value, origin); + return; + } + target[key] = value; + }); + } + + return target; + } + + static numberFixed(value: number, digit: number): number { + return Number(Number(value).toFixed(digit)); + } + + static diffValue(one: any, tow: any) { + function diffValue(oneValue: any, towValue: any) { + const oneType = Tools.getValueType(oneValue); + const towType = Tools.getValueType(towValue); + if (oneType !== towType) { + return false; + } + if (Tools.isArray(oneValue)) { + return diffArrays(oneValue, towValue); + } + if (Tools.isObject(oneValue)) { + return diffObject(oneValue as Object, towValue); + } + if (Tools.isDate(oneValue)) { + return (oneValue as Date).getTime() === towValue.getTime(); + } + if (Tools.isRegExp(oneValue)) { + return (oneValue as string).toString() === towValue.toString(); + } + return oneValue === towValue; + } + + function diffArrays(oneArray: any[], towArray: any[]) { + if (one.length !== tow.length) { + return false; + } + for (let i = 0, len = oneArray.length; i < len; i++) { + const oneValue = oneArray[i]; + const towValue = towArray[i]; + if (!diffValue(oneValue, towValue)) { + return false; + } + } + return true; + } + + function diffObject(oneObject: object, towObject: object) { + const oneKeys = Object.keys(oneObject); + const towKeys = Object.keys(towObject); + if (oneKeys.length !== towKeys.length) { + return false; + } + for (const key of oneKeys) { + if (!towKeys.includes(key)) { + return false; + } + const oneValue = oneObject[key]; + const towValue = towObject[key]; + if (!diffValue(oneValue, towValue)) { + return false; + } + } + return true; + } + + return diffValue(one, tow); + } + + static deepClone(value: any): any { + if (!this.isDefine(value)) { + return value; + } + if (this.isRegExp(value)) { + return new RegExp(value); + } + if (this.isDate(value)) { + return new Date(value); + } + if (this.isArray(value)) { + const clone: any[] = []; + value.forEach((item, index) => { + clone[index] = Tools.deepClone(item); + }); + return clone; + } + if (this.isObject(value)) { + const clone = {}; + Object.keys(value as IKeyValue).forEach((key) => { + const item = (value as IKeyValue)[key]; + clone[key] = Tools.deepClone(item); + }); + Object.setPrototypeOf(clone, Object.getPrototypeOf(value)); + return clone; + } + return value; + } + + static getLanguage(): string { + const defaultValue = 'en-US'; + if (globalThis.navigator) { + return ( + (navigator.languages && navigator.languages[0]) || + navigator.language || + defaultValue + ); + } + return defaultValue; + } + + static getValueType(value: any): string { + return Object.prototype.toString.apply(value); + } + + static isDefine(value?: T | void): value is T { + return value !== undefined && value !== null; + } + + static isBlank(value: any): boolean { + if (!this.isDefine(value)) { + return true; + } + if (this.isString(value)) { + return value.trim() === ''; + } + return false; + } + + static isAssignableFrom(object: any, clazz: Class): object is T { + return object instanceof clazz; + } + + static isBoolean(value?: any): value is boolean { + return this.getValueType(value) === '[object Boolean]'; + } + + static isPlainObject(value: any): value is object { + if (!this.isDefine(value)) { + return false; + } + return Object.getPrototypeOf(value) === Object.getPrototypeOf({}); + } + + static isFunction(value?: any): value is boolean { + return this.getValueType(value) === '[object Function]'; + } + + static isDate(value?: Date): value is Date { + return this.getValueType(value) === '[object Date]'; + } + + static isRegExp(value?: any): value is RegExp { + return this.getValueType(value) === '[object RegExp]'; + } + + static isArray(value?: any): value is T[] { + return this.getValueType(value) === '[object Array]'; + } + + static isString(value?: any): value is string { + return this.getValueType(value) === '[object String]'; + } + + static isNumber(value?: any): value is number { + return this.getValueType(value) === '[object Number]'; + } + + static isObject(value?: any): value is T { + return this.getValueType(value) === '[object Object]'; + } + + static isEmptyObject(value?: any): boolean { + // eslint-disable-next-line no-unreachable-loop + for (const key in value) { + return false; + } + return true; + } + + static isMobile(): boolean { + let clientWidth = 0; + let clientHeight = 0; + if (document.body.clientWidth) { + clientWidth = document.body.clientWidth; + } + if (document.body.clientHeight) { + clientHeight = document.body.clientHeight; + } + return ( + this.isAndroid() || + this.isIPhone() || + this.isTablet() || + (clientWidth < 350 && clientHeight < 500) + ); + } + + static isTablet(): boolean { + return /ipad|android|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( + navigator.userAgent.toLowerCase() + ); + } + + static isWeChat(): boolean { + const userAgent = navigator.userAgent.toLowerCase(); + return String(userAgent.match(/MicroMessenger/i)) === 'micromessenger' + ? !0 + : !1; + } + + static isAndroid(): boolean { + const userAgent = navigator.userAgent; + return userAgent.indexOf('Android') > -1 || userAgent.indexOf('Linux') > -1; + } + + static isIPhone(): boolean { + return /iPhone/i.test(navigator.userAgent); + } + + static isIEBrowser(): boolean { + return this.getBrowserType().startsWith('IE'); + } + + static itCount(count: number): Function { + return (callback: Function) => { + for (let i = 0; i < count; i++) { + callback && callback(); + } + }; + } + + static hasLength(target: IArguments | any[] | string, length?: number): boolean { + if (Tools.isDefine(target)) { + if (Tools.isDefine(length)) { + return target.length === length; + } + return target.length > 0; + } + return false; + } + + static capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + // Used by camelCase as callback to replace() + static fCamelCase(_all: any, letter: string) { + return letter.toUpperCase(); + } + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 15 + // Microsoft forgot to hump their vendor prefix (#9572) + static camelCase(str: string) { + return str.replace(rmsPrefix, 'ms-').replace(rDashAlpha, this.fCamelCase); + } + + /** + * remove all null from object + * @param obj + * @returns + */ + static removeNull(value: IKeyValue): object { + if (this.isObject(value)) { + const clone = {}; + Object.keys(value).forEach((key) => { + const item = value[key]; + if (item == null) { + delete value[key]; + } else { + Tools.removeNull(item); + } + }); + return clone; + } + return value; + } + + /** + * Generate a two-dimensional array with the specified number of rows and columns, and fill in the values + * @param rows row length + * @param columns column length + * @param value value to be set + * @returns + */ + static fillTwoDimensionalArray( + rows: number, + columns: number, + value: any + ): any[][] { + return new Array(rows) + .fill(value) + .map((item) => new Array(columns).fill(value)); + } + + /** + * Generate a two-dimensional array with the specified number of rows and columns, and fill in the values + * @param rows row length + * @param columns column length + * @param value value to be set + * @returns + */ + static fillObjectMatrix( + rows: number, + columns: number, + value: T + ): ObjectMatrixPrimitiveType { + const matrix = new ObjectMatrix(); + for (let r = 0; r < rows; r++) { + for (let c = 0; c < columns; c++) { + matrix.setValue(r, c, value); + } + } + return matrix.getData(); + } + + static numToWord(x: number) { + let s = ''; + while (x > 0) { + let m = x % 26; + m = m === 0 ? (m = 26) : m; + s = String.fromCharCode(96 + m) + s; + x = (x - m) / 26; + } + return s.toLocaleUpperCase(); + } + + /** + * + * Column subscript letter to number + * + * @privateRemarks + * zh: 列下标 字母转数字 + * + * @param a - Column subscript letter,e.g.,"A1" + * @returns Column subscript number,e.g.,0 + * + */ + + static ABCatNum(a: string): number { + if (a == null || a.length === 0) { + return NaN; + } + + const str = a.toLowerCase().split(''); + const al = str.length; + let numOut = 0; + let charnum = 0; + for (let i = 0; i < al; i++) { + charnum = str[i].charCodeAt(0) - 96; + numOut += charnum * 26 ** (al - i - 1); + } + if (numOut === 0) { + return NaN; + } + return numOut - 1; + } + + /** + * en: Column subscript number to letter + * + * zh: 列下标 数字转字母 + * + * @param n Column subscript number,e.g.,0 + * @returns Column subscript letter,e.g.,"A1" + */ + static chatAtABC(n: number): string { + const ord_a = 'a'.charCodeAt(0); + + const ord_z = 'z'.charCodeAt(0); + + const len = ord_z - ord_a + 1; + + let s = ''; + + while (n >= 0) { + s = String.fromCharCode((n % len) + ord_a) + s; + + n = Math.floor(n / len) - 1; + } + + return s.toUpperCase(); + } + + static randSort(arr: T[]) { + for (let i = 0, len = arr.length; i < len; i++) { + const rand = parseInt((Math.random() * len).toString()); + const temp = arr[rand]; + arr[rand] = arr[i]; + arr[i] = temp; + } + return arr; + } + + /** + * extend two objects + * @param originJson + * @param extendJson + * @returns + */ + static commonExtend(originJson: object, extendJson: object): T { + let resultJsonObject = {}; + + for (let attr in originJson) { + resultJsonObject[attr] = originJson[attr]; + } + + for (let attr in extendJson) { + // undefined is equivalent to no setting + if (extendJson[attr] == null) { + continue; + } + resultJsonObject[attr] = extendJson[attr]; + } + + return resultJsonObject as unknown as T; + } +} + +export function makeCellToSelection( + cellInfo: Nullable +): Nullable { + if (!cellInfo) { + return; + } + let { row, column, startY, endY, startX, endX, isMerged, mergeInfo } = cellInfo; + let startRow = row; + let startColumn = column; + let endRow = row; + let endColumn = column; + if (isMerged && mergeInfo) { + const { + startRow: mergeStartRow, + startColumn: mergeStartColumn, + endRow: mergeEndRow, + endColumn: mergeEndColumn, + startY: mergeStartY, + endY: mergeEndY, + startX: mergeStartX, + endX: mergeEndX, + } = mergeInfo; + startRow = mergeStartRow; + startColumn = mergeStartColumn; + endRow = mergeEndRow; + endColumn = mergeEndColumn; + startY = mergeStartY; + endY = mergeEndY; + startX = mergeStartX; + endX = mergeEndX; + } + + return { + startRow, + startColumn, + endRow, + endColumn, + startY, + endY, + startX, + endX, + }; +} + +export function isEmptyCell(cell: Nullable) { + if (!cell) { + return true; + } + + const content = cell?.m || ''; + if (content.length === 0 && !cell.p) { + return true; + } + return false; +} + +export function getColorStyle(color: Nullable): Nullable { + if (color) { + if (color.rgb) { + return color.rgb; + } + if (color.th) { + return new ColorBuilder() + .setThemeColor(color.th) + .asThemeColor() + .asRgbColor() + .getCssString(); + } + } + return null; +} diff --git a/packages/core/src/Shared/Tuples.ts b/packages/core/src/Shared/Tuples.ts new file mode 100644 index 0000000000..6bc97fb9f3 --- /dev/null +++ b/packages/core/src/Shared/Tuples.ts @@ -0,0 +1,66 @@ +import { Tools } from './Tools'; + +/** + * Tool library for detecting data types + */ +export class Tuples { + static STRING_TYPE = 1; + + static NUMBER_TYPE = 2; + + static BOOLEAN_TYPE = 3; + + static FUNCTION_TYPE = 4; + + static OBJECT_TYPE = 5; + + static checkup(target: IArguments | unknown[], ...types: any[]): boolean { + if (target.length !== types.length) { + return false; + } + const length = target.length; + for (let i = 0; i < length; i++) { + const element = target[i]; + const type = types[i]; + switch (type) { + case Tuples.BOOLEAN_TYPE: { + if (!Tools.isBoolean(element)) { + return false; + } + break; + } + case Tuples.NUMBER_TYPE: { + if (!Tools.isNumber(element)) { + return false; + } + break; + } + case Tuples.STRING_TYPE: { + if (!Tools.isString(element)) { + return false; + } + break; + } + case Tuples.FUNCTION_TYPE: { + if (!Tools.isFunction(element)) { + return false; + } + break; + } + case Tuples.OBJECT_TYPE: { + if (!Tools.isPlainObject(element)) { + return false; + } + break; + } + default: { + if (!Tools.isAssignableFrom(element, type)) { + return false; + } + break; + } + } + } + return true; + } +} diff --git a/packages/core/src/Shared/TypeStore.ts b/packages/core/src/Shared/TypeStore.ts new file mode 100644 index 0000000000..01a5fbe61c --- /dev/null +++ b/packages/core/src/Shared/TypeStore.ts @@ -0,0 +1,13 @@ +/** + * Register, get type + */ +export class TypeStoreBase { + static RegisteredTypes: { [key: string]: Object } = {}; + + static GetClass(fqdn: string): any { + if (this.RegisteredTypes && this.RegisteredTypes[fqdn]) { + return this.RegisteredTypes[fqdn]; + } + return null; + } +} diff --git a/packages/core/src/Shared/Types.ts b/packages/core/src/Shared/Types.ts new file mode 100644 index 0000000000..b554569cde --- /dev/null +++ b/packages/core/src/Shared/Types.ts @@ -0,0 +1,94 @@ +import { Observable } from '../Observer'; + +/** Alias type for value that can be null */ +export type Nullable = T | null | undefined | void; + +export type NoNeedCheckedType = any; + +export type PropsFrom = T extends Observable ? Props : void; + +// export type Voidable = T | null | undefined | void; +/** + * Alias type for number that are floats + * @ignorenaming + */ +export type float = number; +/** + * Alias type for number that are doubles. + * @ignorenaming + */ +export type double = number; +/** + * Alias type for number that are integer + * @ignorenaming + */ +export type int = number; + +/** Alias type for number array or Float32Array */ +export type FloatArray = number[] | Float32Array; +/** Alias type for number array or Float32Array or Int32Array or Uint32Array or Uint16Array */ +export type IndicesArray = number[] | Int32Array | Uint32Array | Uint16Array; + +/** + * Alias for types that can be used by a Buffer or VertexBuffer. + */ +export type DataArray = number[] | ArrayBuffer | ArrayBufferView; + +/** + * Alias type for primitive types + * @ignorenaming + */ +type Primitive = undefined | null | boolean | string | number | Function; + +/** + * Type modifier to make all the properties of an object Readonly + */ +export type Immutable = T extends Primitive + ? T + : T extends Array + ? readonly U[] + : /* T extends Map ? ReadonlyMap : // es2015+ only */ + DeepImmutable; + +/** + * Type modifier to make all the properties of an object Readonly recursively + */ +export type DeepImmutable = T extends Primitive + ? T + : T extends Array + ? DeepImmutableArray + : /* T extends Map ? DeepImmutableMap : // es2015+ only */ + DeepImmutableObject; + +/** + * Type modifier to make object properties readonly. + */ +export type DeepImmutableObject = { + readonly [K in keyof T]: DeepImmutable; +}; + +/** @hidden */ +interface DeepImmutableArray extends ReadonlyArray> {} +/** @hidden */ +/* interface DeepImmutableMap extends ReadonlyMap, DeepImmutable> {} // es2015+ only */ + +/** + * Class type + */ +export type Class = { + new (...param: any): T; +}; + +/** + * Key value object + */ +export interface IKeyValue { + [key: string]: any; +} + +/** + * Custom type of key + */ +export type IKeyType = { + [key: string]: T; +}; diff --git a/packages/core/src/Shared/index.ts b/packages/core/src/Shared/index.ts new file mode 100644 index 0000000000..eb1115bdca --- /dev/null +++ b/packages/core/src/Shared/index.ts @@ -0,0 +1,25 @@ +// @index(['./*.ts','./**/index.ts'], f => `export * from '${f.path}'`) +export * from './ArraySearch'; +export * from './DateTime'; +export * from './DropCell'; +export * from './ExcelDateTime'; +export * from './Generate'; +export * from './GroupModel'; +export * from './IOHttp'; +export * from './IOSocket'; +export * from './KeyboardUtil'; +export * from './Locale'; +export * from './Logger'; +export * from './LRU/index'; +export * from './Migrate/index'; +export * from './NameGen'; +export * from './ObjectArray'; +export * from './ObjectMatrix'; +export * from './Rectangle'; +export * from './Serialize/index'; +export * from './Tools'; +export * from './Tuples'; +export * from './Types'; +export * from './TypeStore'; +export * from './Reference'; +// @endindex diff --git a/packages/core/src/Sheets/Action/AddMergeAction.ts b/packages/core/src/Sheets/Action/AddMergeAction.ts new file mode 100644 index 0000000000..71ace2d371 --- /dev/null +++ b/packages/core/src/Sheets/Action/AddMergeAction.ts @@ -0,0 +1,64 @@ +import { addMerge } from '../Apply/AddMerge'; +import { RemoveMerge } from '../Apply/RemoveMerge'; +import { CONVERTOR_OPERATION } from '../../Const'; +import { WorkSheetConvertor } from '../../Convertor'; +import { WorkBook } from '../Domain'; +import { IRangeData } from '../../Interfaces'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers } from '../../Command/ActionObservers'; +import { IRemoveMergeActionData } from './RemoveMergeAction'; + +/** + * @internal + * Format of AddMergeActionData param + */ +export interface IAddMergeActionData extends IActionData { + rectangles: IRangeData[]; +} + +/** + * Set merged cell range + * + * @internal + */ +export class AddMergeAction extends ActionBase< + IAddMergeActionData, + IRemoveMergeActionData, + IRangeData[] +> { + constructor( + actionData: IAddMergeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.INSERT)], + }; + this._oldActionData = { + ...actionData, + rectangles: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.REMOVE)], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + RemoveMerge(worksheet.getMerges(), this._doActionData.rectangles); + } + + do(): IRangeData[] { + const worksheet = this.getWorkSheet(); + return addMerge(worksheet.getMerges(), this._doActionData.rectangles); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/ClearRangeAction.ts b/packages/core/src/Sheets/Action/ClearRangeAction.ts new file mode 100644 index 0000000000..2cef1ceae2 --- /dev/null +++ b/packages/core/src/Sheets/Action/ClearRangeAction.ts @@ -0,0 +1,100 @@ +import { SetRangeData, ClearRange } from '../Apply'; +import { ACTION_NAMES } from '../../Const'; +import { CONVERTOR_OPERATION } from '../../Const/CONST'; +import { WorkSheetConvertor } from '../../Convertor/WorkSheetConvertor'; +import { WorkBook } from '../Domain'; +import { ICellData, IOptionsData, IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { ISetRangeDataActionData } from './SetRangeDataAction'; + +/** + * @internal + */ +export interface IClearRangeActionData extends IActionData { + options: IOptionsData; + rangeData: IRangeData; +} + +/** + * Clearly specify a range of styles, content, comments, validation, filtering + * + * @internal + */ +export class ClearRangeAction extends ActionBase< + IClearRangeActionData, + ISetRangeDataActionData, + ObjectMatrixPrimitiveType +> { + constructor( + actionData: IClearRangeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + cellValue: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = ClearRange( + worksheet.getCellMatrix(), + this._doActionData.options, + this._doActionData.rangeData + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId, rangeData } = this._doActionData; + + this._oldActionData = { + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + sheetId, + rangeData, + cellValue: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.INSERT)], + }; + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const { rangeData, cellValue } = this._oldActionData; + const styles = this._workbook.getStyles(); + + if (worksheet) { + SetRangeData(worksheet.getCellMatrix(), cellValue, rangeData, styles); + // no need update current data + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/DeleteRangeAction.ts b/packages/core/src/Sheets/Action/DeleteRangeAction.ts new file mode 100644 index 0000000000..ddbd63a27f --- /dev/null +++ b/packages/core/src/Sheets/Action/DeleteRangeAction.ts @@ -0,0 +1,112 @@ +import { DeleteRange, InsertRange } from '../Apply'; +import { ACTION_NAMES } from '../../Const'; +import { CONVERTOR_OPERATION } from '../../Const/CONST'; +import { WorkSheetConvertor } from '../../Convertor/WorkSheetConvertor'; +import { WorkBook } from '../Domain'; +import { Dimension } from '../../Enum/Dimension'; +import { ICellData, IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IInsertRangeActionData } from './InsertRangeAction'; + +/** + * @internal + */ +export interface IDeleteRangeActionData extends IActionData { + shiftDimension: Dimension; + rangeData: IRangeData; +} + +/** + * Delete the specified range and move the right or lower range + * + * @internal + */ +export class DeleteRangeAction extends ActionBase< + IDeleteRangeActionData, + IInsertRangeActionData, + ObjectMatrixPrimitiveType +> { + constructor( + actionData: IDeleteRangeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + ...actionData, + cellValue: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.INSERT)], + }; + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = DeleteRange( + { + rowCount: worksheet.getConfig().rowCount, + columnCount: worksheet.getConfig().columnCount, + }, + worksheet.getCellMatrix(), + this._doActionData.shiftDimension, + this._doActionData.rangeData + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId, rangeData, shiftDimension } = this._doActionData; + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.INSERT_RANGE_ACTION, + shiftDimension, + rangeData, + cellValue: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.INSERT)], + }; + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const { shiftDimension, rangeData, cellValue } = this._oldActionData; + if (worksheet) { + InsertRange( + { + rowCount: worksheet.getConfig().rowCount, + columnCount: worksheet.getConfig().columnCount, + }, + worksheet.getCellMatrix(), + shiftDimension, + rangeData, + cellValue + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + // no need update current data + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/InsertColumnAction.ts b/packages/core/src/Sheets/Action/InsertColumnAction.ts new file mode 100644 index 0000000000..9cc3139e2f --- /dev/null +++ b/packages/core/src/Sheets/Action/InsertColumnAction.ts @@ -0,0 +1,83 @@ +import { RemoveColumn, InsertColumn } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IRemoveColumnAction } from './removeColumnAction'; + +/** + * @internal + */ +export interface IInsertColumnActionData extends IActionData { + columnIndex: number; + columnCount: number; +} + +/** + * Insert the column configuration of the specified column index + * + * @internal + */ +export class InsertColumnAction extends ActionBase< + IInsertColumnActionData, + IRemoveColumnAction +> { + constructor( + actionData: IInsertColumnActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + do(): void { + const worksheet = this.getWorkSheet(); + const columnManager = worksheet.getColumnManager(); + + InsertColumn( + this._doActionData.columnIndex, + this._doActionData.columnCount, + columnManager.getColumnData().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const columnManager = worksheet.getColumnManager(); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + + RemoveColumn( + this._oldActionData.columnIndex, + this._oldActionData.columnCount, + columnManager.getColumnData().toJSON() + ); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/InsertColumnDataAction.ts b/packages/core/src/Sheets/Action/InsertColumnDataAction.ts new file mode 100644 index 0000000000..3251bd253d --- /dev/null +++ b/packages/core/src/Sheets/Action/InsertColumnDataAction.ts @@ -0,0 +1,85 @@ +import { InsertDataColumn, RemoveColumnData } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ICellData } from '../../Interfaces'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IRemoveColumnDataAction } from './RemoveColumnDataAction'; +import { ObjectArray } from '../../Shared'; + +/** + * @internal + */ +export interface IInsertColumnDataActionData extends IActionData { + columnIndex: number; + columnData: ObjectMatrixPrimitiveType; +} + +/** + * Insert the column data of the specified column index + * + * @internal + */ +export class InsertColumnDataAction extends ActionBase< + IInsertColumnDataActionData, + IRemoveColumnDataAction +> { + constructor( + actionData: IInsertColumnDataActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + columnCount: ObjectArray.getMaxLength(actionData.columnData[0]), + convertor: [], + }; + this.validate(); + } + + do(): void { + const worksheet = this.getWorkSheet(); + + InsertDataColumn( + this._doActionData.columnIndex, + this._doActionData.columnData, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + RemoveColumnData( + this._oldActionData.columnIndex, + this._oldActionData.columnCount, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/InsertRangeAction.ts b/packages/core/src/Sheets/Action/InsertRangeAction.ts new file mode 100644 index 0000000000..256690c15d --- /dev/null +++ b/packages/core/src/Sheets/Action/InsertRangeAction.ts @@ -0,0 +1,107 @@ +import { DeleteRange, InsertRange } from '../Apply'; +import { ACTION_NAMES } from '../../Const'; +import { WorkBook } from '../Domain'; +import { Dimension } from '../../Enum/Dimension'; +import { ICellData, IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IDeleteRangeActionData } from './DeleteRangeAction'; + +/** + * @internal + */ +export interface IInsertRangeActionData extends IActionData { + shiftDimension: Dimension; + rangeData: IRangeData; + cellValue: ObjectMatrixPrimitiveType; +} + +/** + * Insert data into a range and move the range to the right or below + * + * @internal + */ +export class InsertRangeAction extends ActionBase< + IInsertRangeActionData, + IDeleteRangeActionData +> { + constructor( + actionData: IInsertRangeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + do(): void { + const worksheet = this.getWorkSheet(); + if (worksheet) { + InsertRange( + { + rowCount: worksheet.getLastRow(), + columnCount: worksheet.getLastColumn(), + }, + worksheet.getCellMatrix(), + this._doActionData.shiftDimension, + this._doActionData.rangeData, + this._doActionData.cellValue + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + } + + redo(): void { + this.do(); + // no need store + } + + undo(): void { + const { rangeData, sheetId, shiftDimension } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + if (worksheet) { + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + sheetId, + cellValue: DeleteRange( + { + rowCount: worksheet.getLastRow(), + columnCount: worksheet.getLastColumn(), + }, + worksheet.getCellMatrix(), + shiftDimension, + rangeData + ), + rangeData, + shiftDimension, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/InsertRowAction.ts b/packages/core/src/Sheets/Action/InsertRowAction.ts new file mode 100644 index 0000000000..acfbd69028 --- /dev/null +++ b/packages/core/src/Sheets/Action/InsertRowAction.ts @@ -0,0 +1,83 @@ +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { InsertRow, RemoveRow } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IRemoveRowActionData } from './RemoveRowAction'; + +/** + * @internal + */ +export interface IInsertRowActionData extends IActionData { + rowIndex: number; + rowCount: number; +} + +/** + * Insert the row configuration of the specified row index + * + * @internal + */ +export class InsertRowAction extends ActionBase< + IInsertRowActionData, + IRemoveRowActionData +> { + constructor( + actionData: IInsertRowActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + do(): void { + const worksheet = this.getWorkSheet(); + const rowManager = worksheet.getRowManager()!; + + InsertRow( + this._doActionData.rowIndex, + this._doActionData.rowCount, + rowManager.getRowData().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const rowManager = worksheet.getRowManager()!; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + + RemoveRow( + this._oldActionData.rowIndex, + this._oldActionData.rowCount, + rowManager.getRowData().toJSON() + ); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/InsertRowDataAction.ts b/packages/core/src/Sheets/Action/InsertRowDataAction.ts new file mode 100644 index 0000000000..3fcd90ce46 --- /dev/null +++ b/packages/core/src/Sheets/Action/InsertRowDataAction.ts @@ -0,0 +1,85 @@ +import { InsertDataRow, RemoveRowData } from '../Apply'; +import { ICellData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { WorkBook } from '../Domain'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IRemoveRowDataActionData } from './RemoveRowDataAction'; +import { ObjectArray } from '../../Shared'; + +/** + * @internal + */ +export interface IInsertRowDataActionData extends IActionData { + rowIndex: number; + rowData: ObjectMatrixPrimitiveType; +} + +/** + * Insert the row data of the specified row index + * + * @internal + */ +export class InsertRowDataAction extends ActionBase< + IInsertRowDataActionData, + IRemoveRowDataActionData +> { + constructor( + actionData: IInsertRowDataActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + rowCount: ObjectArray.getMaxLength(actionData.rowData[0]), + convertor: [], + }; + this.validate(); + } + + do(): void { + const worksheet = this.getWorkSheet(); + + InsertDataRow( + this._doActionData.rowIndex, + this._doActionData.rowData, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + RemoveRowData( + this._oldActionData.rowIndex, + this._oldActionData.rowCount, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/InsertSheetAction.ts b/packages/core/src/Sheets/Action/InsertSheetAction.ts new file mode 100644 index 0000000000..982709cd14 --- /dev/null +++ b/packages/core/src/Sheets/Action/InsertSheetAction.ts @@ -0,0 +1,66 @@ +import { InsertSheet, RemoveSheet } from '../Apply'; +import { IWorksheetConfig } from '../../Interfaces'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { WorkBook } from '../Domain'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IRemoveSheetActionData } from './RemoveSheetAction'; + +export interface IInsertSheetActionData extends IActionData { + index: number; + sheet: IWorksheetConfig; +} + +export class InsertSheetAction extends ActionBase< + IInsertSheetActionData, + IRemoveSheetActionData +> { + constructor( + actionData: IInsertSheetActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + sheetId: this.do(), + convertor: [], + }; + } + + redo(): string { + const workbook = this.getWorkBook(); + const result = InsertSheet( + workbook, + this._doActionData.index, + this._doActionData.sheet + ); + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + return result; + } + + undo(): void { + const workbook = this.getWorkBook(); + RemoveSheet(workbook, this._oldActionData.sheetId); + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + do(): string { + return this.redo(); + } + + validate(): boolean { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/core/src/Sheets/Action/RemoveColumnDataAction.ts b/packages/core/src/Sheets/Action/RemoveColumnDataAction.ts new file mode 100644 index 0000000000..c942740ca8 --- /dev/null +++ b/packages/core/src/Sheets/Action/RemoveColumnDataAction.ts @@ -0,0 +1,87 @@ +import { InsertDataColumn, RemoveColumnData } from '../Apply'; +import { CONVERTOR_OPERATION } from '../../Const'; +import { WorkSheetConvertor } from '../../Convertor'; +import { WorkBook } from '../Domain'; +import { ICellData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IInsertColumnDataActionData } from './InsertColumnDataAction'; + +/** + * @internal + */ +export interface IRemoveColumnDataAction extends IActionData { + columnIndex: number; + columnCount: number; +} + +/** + * Remove the column data of the specified column index and column count + * + * @internal + */ +export class RemoveColumnDataAction extends ActionBase< + IRemoveColumnDataAction, + IInsertColumnDataActionData +> { + constructor( + actionData: IRemoveColumnDataAction, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.REMOVE)], + }; + this._oldActionData = { + ...actionData, + columnData: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.INSERT)], + }; + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = RemoveColumnData( + this._doActionData.columnIndex, + this._doActionData.columnCount, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + InsertDataColumn( + this._oldActionData.columnIndex, + this._oldActionData.columnData, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/RemoveMergeAction.ts b/packages/core/src/Sheets/Action/RemoveMergeAction.ts new file mode 100644 index 0000000000..0570d54cde --- /dev/null +++ b/packages/core/src/Sheets/Action/RemoveMergeAction.ts @@ -0,0 +1,79 @@ +import { addMerge, RemoveMerge } from '../Apply'; +import { WorkBook } from '../Domain'; +import { IRangeData } from '../../Interfaces'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IAddMergeActionData } from './AddMergeAction'; + +/** + * @internal + */ +export interface IRemoveMergeActionData extends IActionData { + rectangles: IRangeData[]; +} + +/** + * Remove merged cells from a specified range + * + * @internal + */ +export class RemoveMergeAction extends ActionBase< + IRemoveMergeActionData, + IAddMergeActionData, + IRangeData[] +> { + constructor( + actionData: IRemoveMergeActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + rectangles: this.do(), + convertor: [], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + addMerge(worksheet.getMerges(), this._doActionData.rectangles); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + do(): IRangeData[] { + const worksheet = this.getWorkSheet(); + + const result = RemoveMerge( + worksheet.getMerges(), + this._doActionData.rectangles + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/RemoveRowAction.ts b/packages/core/src/Sheets/Action/RemoveRowAction.ts new file mode 100644 index 0000000000..543707ede5 --- /dev/null +++ b/packages/core/src/Sheets/Action/RemoveRowAction.ts @@ -0,0 +1,87 @@ +import { InsertRow, RemoveRow } from '../Apply'; +import { CONVERTOR_OPERATION } from '../../Const'; +import { WorkSheetConvertor } from '../../Convertor'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IInsertRowActionData } from './InsertRowAction'; + +/** + * @internal + */ +export interface IRemoveRowActionData extends IActionData { + rowIndex: number; + rowCount: number; +} + +/** + * Remove the row configuration of the specified row index and row count + * + * @internal + */ +export class RemoveRowAction extends ActionBase< + IRemoveRowActionData, + IInsertRowActionData +> { + constructor( + actionData: IRemoveRowActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.REMOVE)], + }; + this._oldActionData = { + ...actionData, + rowCount: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.REMOVE)], + }; + this.validate(); + } + + do(): number { + const worksheet = this.getWorkSheet(); + const rowManager = worksheet.getRowManager()!; + + const result = RemoveRow( + this._doActionData.rowIndex, + this._doActionData.rowCount, + rowManager.getRowData().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const rowManager = worksheet.getRowManager()!; + + InsertRow( + this._oldActionData.rowIndex, + this._oldActionData.rowCount, + rowManager.getRowData().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/RemoveRowDataAction.ts b/packages/core/src/Sheets/Action/RemoveRowDataAction.ts new file mode 100644 index 0000000000..8dd57e14a0 --- /dev/null +++ b/packages/core/src/Sheets/Action/RemoveRowDataAction.ts @@ -0,0 +1,85 @@ +import { InsertDataRow, RemoveRowData } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ICellData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IInsertRowDataActionData } from './InsertRowDataAction'; + +/** + * @internal + */ +export interface IRemoveRowDataActionData extends IActionData { + rowIndex: number; + rowCount: number; +} + +/** + * Remove the row data of the specified row index and row count + * + * @internal + */ +export class RemoveRowDataAction extends ActionBase< + IRemoveRowDataActionData, + IInsertRowDataActionData +> { + constructor( + actionData: IRemoveRowDataActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + rowData: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = RemoveRowData( + this._doActionData.rowIndex, + this._doActionData.rowCount, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + InsertDataRow( + this._oldActionData.rowIndex, + this._oldActionData.rowData, + worksheet.getCellMatrix().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/RemoveSheetAction.ts b/packages/core/src/Sheets/Action/RemoveSheetAction.ts new file mode 100644 index 0000000000..d258a38357 --- /dev/null +++ b/packages/core/src/Sheets/Action/RemoveSheetAction.ts @@ -0,0 +1,61 @@ +import { InsertSheet, RemoveSheet } from '../Apply'; +import { IWorksheetConfig } from '../../Interfaces'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { WorkBook } from '../Domain'; +import { IInsertSheetActionData } from './InsertSheetAction'; + +export interface IRemoveSheetActionData extends IActionData { + sheetId: string; +} + +export class RemoveSheetAction extends ActionBase< + IRemoveSheetActionData, + IInsertSheetActionData +> { + constructor( + actionData: IRemoveSheetActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + ...this.do(), + convertor: [], + }; + } + + redo(): { index: number; sheet: IWorksheetConfig } { + const workbook = this.getWorkBook(); + const result = RemoveSheet(workbook, this._doActionData.sheetId); + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + return result; + } + + undo(): void { + const workbook = this.getWorkBook(); + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + InsertSheet(workbook, this._oldActionData.index, this._oldActionData.sheet); + } + + do(): { index: number; sheet: IWorksheetConfig } { + return this.redo(); + } + + validate(): boolean { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/core/src/Sheets/Action/SetBorderAction.ts b/packages/core/src/Sheets/Action/SetBorderAction.ts new file mode 100644 index 0000000000..d71411b3c9 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetBorderAction.ts @@ -0,0 +1,81 @@ +import { ObjectMatrixPrimitiveType } from '../../Shared'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { SetBorder } from '../Apply'; +import { IStyleData } from '../../Interfaces'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + * Border style format + */ +export interface BorderStyleData extends IActionData { + styles: ObjectMatrixPrimitiveType; +} + +/** + * set border style + * + * @internal + */ +export class SetBorderAction extends ActionBase { + constructor( + actionData: BorderStyleData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + styles: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const workSheet = this.getWorkSheet(); + const matrix = workSheet.getCellMatrix(); + const context = workSheet.getContext(); + const workBook = context.getWorkBook(); + const styles = workBook.getStyles(); + + const result = SetBorder(matrix, styles, this._doActionData.styles); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const wokSheet = this.getWorkSheet(); + const matrix = wokSheet.getCellMatrix(); + const context = wokSheet.getContext(); + const workBook = context.getWorkBook(); + const styles = workBook.getStyles(); + + SetBorder(matrix, styles, this._oldActionData.styles); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetCollapseAllColumnGroupsAction.ts b/packages/core/src/Sheets/Action/SetCollapseAllColumnGroupsAction.ts new file mode 100644 index 0000000000..bafe4da3ef --- /dev/null +++ b/packages/core/src/Sheets/Action/SetCollapseAllColumnGroupsAction.ts @@ -0,0 +1,72 @@ +import { WorkBook } from '../Domain'; +import { GroupOpenType } from '../../Module/Group'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { SetCollapseAllColumnGroups } from '../Apply'; + +/** + * @internal + */ +export interface ISetCollapseAllColumnGroupsData extends IActionData {} + +/** + * Set to collapse all column groups + * + * @internal + */ +export class SetCollapseAllColumnGroupsAction extends ActionBase< + ISetCollapseAllColumnGroupsData, + ISetCollapseAllColumnGroupsData +> { + constructor( + actionData: ISetCollapseAllColumnGroupsData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + do(): void { + const workSheet = this.getWorkSheet(); + const structGroup = workSheet.getColumnStructGroup(); + + SetCollapseAllColumnGroups(structGroup, GroupOpenType.open); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + redo(): void { + this.do(); + } + + undo(): void { + const workSheet = this.getWorkSheet(); + const structGroup = workSheet.getColumnStructGroup(); + + SetCollapseAllColumnGroups(structGroup, GroupOpenType.close); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetCollapseAllRowGroupsAction.ts b/packages/core/src/Sheets/Action/SetCollapseAllRowGroupsAction.ts new file mode 100644 index 0000000000..5a2544ec6b --- /dev/null +++ b/packages/core/src/Sheets/Action/SetCollapseAllRowGroupsAction.ts @@ -0,0 +1,72 @@ +import { SetCollapseAllRowGroups } from '../Apply'; +import { WorkBook } from '../Domain'; +import { GroupOpenType } from '../../Module/Group'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetCollapseAllRowGroupsData extends IActionData {} + +/** + * Set to collapse all row groups + * + * @internal + */ +export class SetCollapseAllRowGroupsAction extends ActionBase< + ISetCollapseAllRowGroupsData, + ISetCollapseAllRowGroupsData +> { + constructor( + actionData: ISetCollapseAllRowGroupsData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + do(): void { + const workSheet = this.getWorkSheet(); + const rowStructGroup = workSheet.getRowStructGroup(); + + SetCollapseAllRowGroups(rowStructGroup, GroupOpenType.open); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + redo(): void { + this.do(); + } + + undo(): void { + const workSheet = this.getWorkSheet(); + const rowStructGroup = workSheet.getRowStructGroup(); + + SetCollapseAllRowGroups(rowStructGroup, GroupOpenType.close); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetColumnHideAction.ts b/packages/core/src/Sheets/Action/SetColumnHideAction.ts new file mode 100644 index 0000000000..9a40a24dc5 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetColumnHideAction.ts @@ -0,0 +1,81 @@ +import { SetColumnHide, SetColumnShow } from '../Apply'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { WorkBook } from '../Domain'; +import { ISetColumnShowActionData } from './SetColumnShowAction'; + +/** + * @internal + */ +export interface ISetColumnHideActionData extends IActionData { + columnIndex: number; + columnCount: number; +} + +/** + * Set column hiding based on specified column index and number of columns + * + * @internal + */ +export class SetColumnHideAction extends ActionBase< + ISetColumnHideActionData, + ISetColumnShowActionData +> { + constructor( + actionData: ISetColumnHideActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + SetColumnShow( + this._doActionData.columnIndex, + this._doActionData.columnCount, + worksheet.getColumnManager() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + do(): void { + const worksheet = this.getWorkSheet(); + + SetColumnHide( + this._doActionData.columnIndex, + this._doActionData.columnCount, + worksheet.getColumnManager() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetColumnShowAction.ts b/packages/core/src/Sheets/Action/SetColumnShowAction.ts new file mode 100644 index 0000000000..643095fd2a --- /dev/null +++ b/packages/core/src/Sheets/Action/SetColumnShowAction.ts @@ -0,0 +1,77 @@ +import { SetColumnHide, SetColumnShow } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetColumnShowActionData extends IActionData { + columnIndex: number; + columnCount: number; +} + +/** + * Set column display based on specified column index and number of columns + * + * @internal + */ +export class SetColumnShowAction extends ActionBase { + constructor( + actionData: ISetColumnShowActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + SetColumnHide( + this._doActionData.columnIndex, + this._doActionData.columnCount, + worksheet.getColumnManager() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + do(): void { + const worksheet = this.getWorkSheet(); + + SetColumnShow( + this._doActionData.columnIndex, + this._doActionData.columnCount, + worksheet.getColumnManager() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetColumnWidthAction.ts b/packages/core/src/Sheets/Action/SetColumnWidthAction.ts new file mode 100644 index 0000000000..a3f799703b --- /dev/null +++ b/packages/core/src/Sheets/Action/SetColumnWidthAction.ts @@ -0,0 +1,81 @@ +import { SetColumnWidth } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetColumnWidthActionData extends IActionData { + columnIndex: number; + columnWidth: number[]; +} + +/** + * Set the column width according to the specified column index + * + * @internal + */ +export class SetColumnWidthAction extends ActionBase { + constructor( + actionData: ISetColumnWidthActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + columnWidth: this.do(), + convertor: [], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): number[] { + const worksheet = this.getWorkSheet(); + + const result = SetColumnWidth( + this._oldActionData.columnIndex, + this._oldActionData.columnWidth, + worksheet.getColumnManager() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + + return result; + } + + do(): number[] { + const worksheet = this.getWorkSheet(); + + const result = SetColumnWidth( + this._doActionData.columnIndex, + this._doActionData.columnWidth, + worksheet.getColumnManager() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetFrozenColumnsAction.ts b/packages/core/src/Sheets/Action/SetFrozenColumnsAction.ts new file mode 100644 index 0000000000..23568627a5 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetFrozenColumnsAction.ts @@ -0,0 +1,70 @@ +import { SetFrozenColumns } from '../Apply'; +import { WorkBook } from '../Domain/WorkBook'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetFrozenColumnsActionData extends IActionData { + numColumns: number; +} + +/** + * @internal + */ +export class SetFrozenColumnsAction extends ActionBase { + constructor( + actionData: ISetFrozenColumnsActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + + this._oldActionData = { + ...actionData, + numColumns: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): number { + const worksheet = this.getWorkSheet(); + + const result = SetFrozenColumns(worksheet, this._doActionData.numColumns); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const { numColumns, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + SetFrozenColumns(worksheet, this._oldActionData.numColumns); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetFrozenRowsAction.ts b/packages/core/src/Sheets/Action/SetFrozenRowsAction.ts new file mode 100644 index 0000000000..8b08b8a0e1 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetFrozenRowsAction.ts @@ -0,0 +1,70 @@ +import { SetFrozenRows } from '../Apply'; +import { WorkBook } from '../Domain/WorkBook'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetFrozenRowsActionData extends IActionData { + numRows: number; +} + +/** + * @internal + */ +export class SetFrozenRowsAction extends ActionBase { + constructor( + actionData: ISetFrozenRowsActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + + this._oldActionData = { + ...actionData, + numRows: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): number { + const worksheet = this.getWorkSheet(); + + const result = SetFrozenRows(worksheet, this._doActionData.numRows); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const { numRows, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + SetFrozenRows(worksheet, this._oldActionData.numRows); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetHiddenGridlinesAction.ts b/packages/core/src/Sheets/Action/SetHiddenGridlinesAction.ts new file mode 100644 index 0000000000..25f52aba51 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetHiddenGridlinesAction.ts @@ -0,0 +1,73 @@ +import { SetHiddenGridlines } from '../Apply'; +import { WorkBook } from '../Domain/WorkBook'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetHiddenGridlinesActionData extends IActionData { + hideGridlines: boolean; +} + +/** + * @internal + */ +export class SetHiddenGridlinesAction extends ActionBase { + constructor( + actionData: ISetHiddenGridlinesActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + + this._oldActionData = { + ...actionData, + hideGridlines: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): boolean { + const worksheet = this.getWorkSheet(); + + const result = SetHiddenGridlines( + worksheet, + this._doActionData.hideGridlines + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const { hideGridlines, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + SetHiddenGridlines(worksheet, this._oldActionData.hideGridlines); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRangeDataAction.ts b/packages/core/src/Sheets/Action/SetRangeDataAction.ts new file mode 100644 index 0000000000..8a4d412ba7 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRangeDataAction.ts @@ -0,0 +1,112 @@ +import { SetRangeData } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { CONVERTOR_OPERATION } from '../../Const/CONST'; +import { WorkSheetConvertor } from '../../Convertor/WorkSheetConvertor'; +import { WorkBook } from '../Domain'; +import { ICellData, ICopyToOptionsData, IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRangeDataActionData extends IActionData { + cellValue: ObjectMatrixPrimitiveType; + rangeData: IRangeData; + options?: ICopyToOptionsData; +} + +/** + * Modify values in the range + * + * @internal + */ +export class SetRangeDataAction extends ActionBase< + ISetRangeDataActionData, + ISetRangeDataActionData, + ObjectMatrixPrimitiveType +> { + constructor( + actionData: ISetRangeDataActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + ...actionData, + cellValue: this.do(), + }; + + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + const styles = this._workbook.getStyles(); + + const result = SetRangeData( + worksheet.getCellMatrix(), + this._doActionData.cellValue, + this._doActionData.rangeData, + styles, + this._doActionData.options + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId, rangeData, options } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + sheetId, + cellValue: this.do(), + rangeData, + options, + }; + } + + undo(): void { + const { rangeData, sheetId, cellValue, options } = this._oldActionData; + const worksheet = this.getWorkSheet(); + const styles = this._workbook.getStyles(); + if (worksheet) { + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + sheetId, + cellValue: SetRangeData( + worksheet.getCellMatrix(), + cellValue, + rangeData, + styles + ), + rangeData, + options, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRangeFormatAction.ts b/packages/core/src/Sheets/Action/SetRangeFormatAction.ts new file mode 100644 index 0000000000..1fa63c4f0f --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRangeFormatAction.ts @@ -0,0 +1,104 @@ +import { SetRangeFormat } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { CONVERTOR_OPERATION } from '../../Const/CONST'; +import { WorkSheetConvertor } from '../../Convertor/WorkSheetConvertor'; +import { WorkBook } from '../Domain'; +import { IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRangeFormatActionData extends IActionData { + cellFormat: ObjectMatrixPrimitiveType; + rangeData: IRangeData; +} + +/** + * @internal + */ +export class SetRangeFormatAction extends ActionBase< + ISetRangeFormatActionData, + ISetRangeFormatActionData, + ObjectMatrixPrimitiveType +> { + constructor( + actionData: ISetRangeFormatActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + ...actionData, + cellFormat: this.do(), + }; + + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = SetRangeFormat( + worksheet.getCellMatrix(), + this._doActionData.cellFormat, + this._doActionData.rangeData + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId, rangeData } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_RANGE_FORMAT_ACTION, + sheetId, + cellFormat: this.do(), + rangeData, + }; + // this.do(); + } + + undo(): void { + const { rangeData, sheetId, cellFormat } = this._oldActionData; + const worksheet = this.getWorkSheet(); + if (worksheet) { + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_RANGE_FORMAT_ACTION, + sheetId, + cellFormat: SetRangeFormat( + worksheet.getCellMatrix(), + cellFormat, + rangeData + ), + rangeData, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + // SetRangeFormat(worksheet.getCellMatrix(), cellFormat, rangeData); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRangeFormulaAction.ts b/packages/core/src/Sheets/Action/SetRangeFormulaAction.ts new file mode 100644 index 0000000000..efa2ff6368 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRangeFormulaAction.ts @@ -0,0 +1,101 @@ +import { SetRangeFormula } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkBook } from '../Domain'; +import { IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRangeFormulaActionData extends IActionData { + cellFormula: ObjectMatrixPrimitiveType; + rangeData: IRangeData; +} + +/** + * @internal + */ +export class SetRangeFormulaAction extends ActionBase< + ISetRangeFormulaActionData, + ISetRangeFormulaActionData, + ObjectMatrixPrimitiveType +> { + constructor( + actionData: ISetRangeFormulaActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + cellFormula: this.do(), + convertor: [], + }; + + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = SetRangeFormula( + worksheet.getCellMatrix(), + this._doActionData.cellFormula, + this._doActionData.rangeData + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId, rangeData } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_RANGE_FORMULA_ACTION, + sheetId, + cellFormula: this.do(), + rangeData, + }; + } + + undo(): void { + const { rangeData, sheetId, cellFormula } = this._oldActionData; + const worksheet = this.getWorkSheet(); + if (worksheet) { + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_RANGE_FORMULA_ACTION, + sheetId, + cellFormula: SetRangeFormula( + worksheet.getCellMatrix(), + cellFormula, + rangeData + ), + rangeData, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRangeNoteAction.ts b/packages/core/src/Sheets/Action/SetRangeNoteAction.ts new file mode 100644 index 0000000000..1c62d92eb1 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRangeNoteAction.ts @@ -0,0 +1,100 @@ +import { SetRangeNote } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkBook } from '../Domain'; +import { IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRangeNoteActionData extends IActionData { + cellNote: ObjectMatrixPrimitiveType; + rangeData: IRangeData; +} + +/** + * @internal + */ +export class SetRangeNoteAction extends ActionBase< + ISetRangeNoteActionData, + ISetRangeNoteActionData, + ObjectMatrixPrimitiveType +> { + constructor( + actionData: ISetRangeNoteActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + cellNote: this.do(), + convertor: [], + }; + + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = SetRangeNote( + worksheet.getCellMatrix(), + this._doActionData.cellNote, + this._doActionData.rangeData + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId, rangeData } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_RANGE_NOTE_ACTION, + sheetId, + cellNote: this.do(), + rangeData, + }; + } + + undo(): void { + const { rangeData, sheetId, cellNote } = this._oldActionData; + const worksheet = this.getWorkSheet(); + if (worksheet) { + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_RANGE_NOTE_ACTION, + sheetId, + cellNote: SetRangeNote( + worksheet.getCellMatrix(), + cellNote, + rangeData + ), + rangeData, + }; + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRangeStyleAction.ts b/packages/core/src/Sheets/Action/SetRangeStyleAction.ts new file mode 100644 index 0000000000..3eb64a4833 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRangeStyleAction.ts @@ -0,0 +1,84 @@ +import { SetRangeStyle } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkBook } from '../Domain'; +import { IRangeData, IStyleData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +export interface ISetRangeStyleActionData extends IActionData { + value: ObjectMatrixPrimitiveType; // + rangeData: IRangeData; +} + +/** + * @internal + */ +export class SetRangeStyleAction extends ActionBase { + constructor( + actionData: ISetRangeStyleActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + // store previous data + this._oldActionData = { + ...actionData, + value: this.do(), + }; + + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const { value, rangeData } = this._doActionData; + const styles = this._workbook.getStyles(); + const cellMatrix = this.getWorkSheet()!.getCellMatrix(); + + const result = SetRangeStyle(cellMatrix, value, rangeData, styles); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo() { + // update pre data + const { sheetId, rangeData } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + sheetId, + value: this.do(), + rangeData, + }; + } + + undo() { + const { value, rangeData, sheetId } = this._oldActionData; + const styles = this._workbook.getStyles(); + const cellMatrix = this.getWorkSheet()!.getCellMatrix(); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + sheetId, + value: SetRangeStyle(cellMatrix, value, rangeData, styles), + rangeData, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRangeValueAction.ts b/packages/core/src/Sheets/Action/SetRangeValueAction.ts new file mode 100644 index 0000000000..a838f39c5b --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRangeValueAction.ts @@ -0,0 +1,102 @@ +import { SetRangeValue } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { CONVERTOR_OPERATION } from '../../Const/CONST'; +import { WorkSheetConvertor } from '../../Convertor/WorkSheetConvertor'; +import { WorkBook } from '../Domain'; +import { ICellV, IRangeData } from '../../Interfaces'; +import { ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRangeValueActionData extends IActionData { + cellValue: ObjectMatrixPrimitiveType; + rangeData: IRangeData; +} + +/** + * @internal + */ +export class SetRangeValueAction extends ActionBase< + ISetRangeValueActionData, + ISetRangeValueActionData, + ObjectMatrixPrimitiveType +> { + constructor( + actionData: ISetRangeValueActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + ...actionData, + cellValue: this.do(), + }; + + this.validate(); + } + + do(): ObjectMatrixPrimitiveType { + const worksheet = this.getWorkSheet(); + + const result = SetRangeValue( + worksheet.getCellMatrix(), + this._doActionData.cellValue, + this._doActionData.rangeData + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId, rangeData } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + sheetId, + cellValue: this.do(), + rangeData, + }; + } + + undo(): void { + const { rangeData, sheetId, cellValue } = this._oldActionData; + const worksheet = this.getWorkSheet(); + if (worksheet) { + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + sheetId, + cellValue: SetRangeValue( + worksheet.getCellMatrix(), + cellValue, + rangeData + ), + rangeData, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRightToLeftAction.ts b/packages/core/src/Sheets/Action/SetRightToLeftAction.ts new file mode 100644 index 0000000000..a7bbc87dd9 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRightToLeftAction.ts @@ -0,0 +1,69 @@ +import { SetRightToLeft } from '../Apply'; +import { WorkBook } from '../Domain/WorkBook'; +import { BooleanNumber } from '../../Enum'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRightToLeftActionData extends IActionData { + rightToLeft: BooleanNumber; +} + +/** + * @internal + */ +export class SetRightToLeftAction extends ActionBase { + constructor( + actionData: ISetRightToLeftActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + + this._oldActionData = { + ...actionData, + rightToLeft: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): BooleanNumber { + const worksheet = this.getWorkSheet(); + + const result = SetRightToLeft(worksheet, this._doActionData.rightToLeft); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const { rightToLeft, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + SetRightToLeft(worksheet, this._oldActionData.rightToLeft); + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRowHeightAction.ts b/packages/core/src/Sheets/Action/SetRowHeightAction.ts new file mode 100644 index 0000000000..5308ff939b --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRowHeightAction.ts @@ -0,0 +1,81 @@ +import { SetRowHeight } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRowHeightActionData extends IActionData { + rowIndex: number; + rowHeight: number[]; +} + +/** + * Set the row height according to the specified row index + * + * @internal + */ +export class SetRowHeightAction extends ActionBase { + constructor( + actionData: ISetRowHeightActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + rowHeight: this.do(), + convertor: [], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): number[] { + const worksheet = this.getWorkSheet(); + + const result = SetRowHeight( + this._oldActionData.rowIndex, + this._oldActionData.rowHeight, + worksheet.getRowManager() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + + return result; + } + + do(): number[] { + const worksheet = this.getWorkSheet(); + + const result = SetRowHeight( + this._doActionData.rowIndex, + this._doActionData.rowHeight, + worksheet.getRowManager() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRowHideAction.ts b/packages/core/src/Sheets/Action/SetRowHideAction.ts new file mode 100644 index 0000000000..00bd0f1ae3 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRowHideAction.ts @@ -0,0 +1,81 @@ +import { SetHideRow, SetShowRow } from '../Apply'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { WorkBook } from '../Domain'; +import { ISetRowShowActionData } from './SetRowShowAction'; + +/** + * @internal + */ +export interface ISetRowHideActionData extends IActionData { + rowIndex: number; + rowCount: number; +} + +/** + * Set row hiding based on specified row index and number of rows + * + * @internal + */ +export class SetRowHideAction extends ActionBase< + ISetRowHideActionData, + ISetRowShowActionData +> { + constructor( + actionData: ISetRowHideActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + SetShowRow( + this._oldActionData.rowIndex, + this._doActionData.rowCount, + worksheet.getRowManager() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + do(): void { + const worksheet = this.getWorkSheet(); + + SetHideRow( + this._doActionData.rowIndex, + this._doActionData.rowCount, + worksheet.getRowManager() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetRowShowAction.ts b/packages/core/src/Sheets/Action/SetRowShowAction.ts new file mode 100644 index 0000000000..e35c9581c2 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetRowShowAction.ts @@ -0,0 +1,75 @@ +import { SetHideRow, SetShowRow } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetRowShowActionData extends IActionData { + rowIndex: number; + rowCount: number; +} + +/** + * @internal + */ +export class SetRowShowAction extends ActionBase { + constructor( + actionData: ISetRowShowActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this.do(); + this._oldActionData = { + ...actionData, + convertor: [], + }; + this.validate(); + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + + SetHideRow( + this._oldActionData.rowIndex, + this._oldActionData.rowCount, + worksheet.getRowManager() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + do(): void { + const worksheet = this.getWorkSheet(); + + SetShowRow( + this._doActionData.rowIndex, + this._doActionData.rowCount, + worksheet.getRowManager() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetSelectionActivateAction.ts b/packages/core/src/Sheets/Action/SetSelectionActivateAction.ts new file mode 100644 index 0000000000..9c76269603 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetSelectionActivateAction.ts @@ -0,0 +1,123 @@ +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkBook } from '../Domain'; +import { IRangeData } from '../../Interfaces'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { SetSelectionActivate } from '../Apply'; +import { CONVERTOR_OPERATION } from '../../Const/CONST'; +import { WorkSheetConvertor } from '../../Convertor/WorkSheetConvertor'; + +/** + * @internal + */ +export interface ISetSelectionActivateActionData extends IActionData { + activeRangeList: IRangeData | IRangeData[]; + activeRange: IRangeData; + currentCell: IRangeData; +} + +/** + * @internal + */ +export interface ISetSelectionActivateServiceData { + activeRangeList: IRangeData | IRangeData[]; + activeRange: IRangeData; + currentCell: IRangeData; +} + +/** + * @internal + */ +export class SetSelectionActivateAction extends ActionBase< + ISetSelectionActivateActionData, + ISetSelectionActivateActionData, + ISetSelectionActivateServiceData +> { + constructor( + actionData: ISetSelectionActivateActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + + const { activeRangeList, activeRange, currentCell } = this.do(); + + this._oldActionData = { + ...actionData, + activeRangeList, + activeRange, + currentCell, + }; + + this.validate(); + } + + do(): ISetSelectionActivateServiceData { + const { activeRangeList, activeRange, currentCell } = this._doActionData; + const worksheet = this.getWorkSheet(); + + const result = SetSelectionActivate( + worksheet, + activeRangeList, + activeRange, + currentCell + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + const { activeRangeList, activeRange, currentCell } = this.do(); + this._oldActionData = { + actionName: ACTION_NAMES.SET_SELECTION_ACTION, + sheetId, + activeRangeList, + activeRange, + currentCell, + }; + } + + undo(): void { + const { activeRangeList, activeRange, currentCell, sheetId } = + this._oldActionData; + const worksheet = this.getWorkSheet(); + + const doData = SetSelectionActivate( + worksheet, + activeRangeList, + activeRange, + currentCell + ); + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_SELECTION_ACTION, + sheetId, + activeRangeList: doData.activeRangeList, + activeRange: doData.activeRange, + currentCell: doData.currentCell, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetSheetOrderAction.ts b/packages/core/src/Sheets/Action/SetSheetOrderAction.ts new file mode 100644 index 0000000000..4f83ab11a9 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetSheetOrderAction.ts @@ -0,0 +1,73 @@ +import { SetSheetOrder } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +export interface ISetSheetOrderActionData extends IActionData { + sheetId: string; + order: number; +} + +export class SetSheetOrderAction extends ActionBase< + ISetSheetOrderActionData, + ISetSheetOrderActionData +> { + constructor( + actionData: ISetSheetOrderActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + order: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): number { + return this.redo(); + } + + redo(): number { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const workbook = context.getWorkBook(); + const result = SetSheetOrder( + workbook, + this._doActionData.sheetId, + this._doActionData.order + ); + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + return result; + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const workbook = context.getWorkBook(); + SetSheetOrder( + workbook, + this._oldActionData.sheetId, + this._oldActionData.order + ); + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetTabColorAction.ts b/packages/core/src/Sheets/Action/SetTabColorAction.ts new file mode 100644 index 0000000000..5d3fbb3f4f --- /dev/null +++ b/packages/core/src/Sheets/Action/SetTabColorAction.ts @@ -0,0 +1,89 @@ +import { SetTabColor } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { CONVERTOR_OPERATION } from '../../Const/CONST'; +import { WorkSheetConvertor } from '../../Convertor/WorkSheetConvertor'; +import { WorkBook } from '../Domain'; +import { Nullable } from '../../Shared/Types'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetTabColorActionData extends IActionData { + color: Nullable; +} + +/** + * @internal + */ +export class SetTabColorAction extends ActionBase< + ISetTabColorActionData, + ISetTabColorActionData, + Nullable +> { + constructor( + actionData: ISetTabColorActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + ...actionData, + color: this.do(), + }; + + this.validate(); + } + + do(): Nullable { + const worksheet = this.getWorkSheet(); + + const result = SetTabColor(worksheet, this._doActionData.color); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.SET_TAB_COLOR_ACTION, + color: this.do(), + }; + } + + undo(): void { + const { color, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_TAB_COLOR_ACTION, + sheetId, + color: SetTabColor(worksheet, color), + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetWorkSheetActivateAction.ts b/packages/core/src/Sheets/Action/SetWorkSheetActivateAction.ts new file mode 100644 index 0000000000..d2b3f5e2ba --- /dev/null +++ b/packages/core/src/Sheets/Action/SetWorkSheetActivateAction.ts @@ -0,0 +1,104 @@ +import { SetWorkSheetActivate } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkBook } from '../Domain'; +import { ActionBase, ActionOperation, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { BooleanNumber } from '../../Enum'; + +/** + * @internal + */ +export interface ISetWorkSheetActivateActionData extends IActionData { + status: BooleanNumber; +} + +/** + * @internal + */ +export interface ISheetStatus { + oldSheetId: string; + status: BooleanNumber; +} + +/** + * @internal + */ +export class SetWorkSheetActivateAction extends ActionBase< + ISetWorkSheetActivateActionData, + ISetWorkSheetActivateActionData, + ISheetStatus +> { + constructor( + actionData: ISetWorkSheetActivateActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + const { oldSheetId, status } = this.do(); + this._oldActionData = { + ...actionData, + sheetId: oldSheetId, + status, + convertor: [], + }; + this.validate(); + this.removeOperation(ActionOperation.SERVER_ACTION); + } + + do(): ISheetStatus { + const { sheetId, status } = this._doActionData; + const worksheet = this._workbook.getSheetBySheetId(sheetId)!; + + const result = SetWorkSheetActivate(worksheet, status); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { oldSheetId, status } = this.do(); + this._oldActionData = { + actionName: ACTION_NAMES.SET_WORKSHEET_ACTIVATE_ACTION, + sheetId: oldSheetId, + status, + }; + } + + undo(): void { + const { sheetId } = this._oldActionData; + const oldStatus = this._oldActionData.status; + const worksheet = this._workbook.getSheetBySheetId(sheetId); + if (worksheet) { + const { oldSheetId, status } = SetWorkSheetActivate( + worksheet, + oldStatus + ); + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_WORKSHEET_ACTIVATE_ACTION, + sheetId: oldSheetId, + status, + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetWorkSheetHideAction.ts b/packages/core/src/Sheets/Action/SetWorkSheetHideAction.ts new file mode 100644 index 0000000000..38ef629c27 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetWorkSheetHideAction.ts @@ -0,0 +1,84 @@ +import { SetWorkSheetHideService } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { BooleanNumber } from '../../Enum'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { WorkBook } from '../Domain'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetWorkSheetHideActionData extends IActionData { + hidden: BooleanNumber; +} + +/** + * @internal + */ +export class SetWorkSheetHideAction extends ActionBase< + ISetWorkSheetHideActionData, + ISetWorkSheetHideActionData, + BooleanNumber +> { + constructor( + actionData: ISetWorkSheetHideActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + hidden: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): BooleanNumber { + const worksheet = this.getWorkSheet(); + + const result = SetWorkSheetHideService(worksheet, this._doActionData.hidden); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + const { sheetId } = this._doActionData; + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.HIDE_SHEET_ACTION, + hidden: this.do(), + }; + } + + undo(): void { + const { hidden, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + this._doActionData = { + actionName: ACTION_NAMES.HIDE_SHEET_ACTION, + sheetId, + hidden: SetWorkSheetHideService(worksheet, hidden), + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetWorkSheetNameAction.ts b/packages/core/src/Sheets/Action/SetWorkSheetNameAction.ts new file mode 100644 index 0000000000..319767e329 --- /dev/null +++ b/packages/core/src/Sheets/Action/SetWorkSheetNameAction.ts @@ -0,0 +1,86 @@ +import { SetWorkSheetName } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkBook } from '../Domain/WorkBook'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetWorkSheetNameActionData extends IActionData { + sheetName: string; +} + +/** + * @internal + */ +export class SetWorkSheetNameAction extends ActionBase< + ISetWorkSheetNameActionData, + ISetWorkSheetNameActionData, + string +> { + constructor( + actionData: ISetWorkSheetNameActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + + this._oldActionData = { + ...actionData, + sheetName: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): string { + const worksheet = this.getWorkSheet(); + + const result = SetWorkSheetName(worksheet, this._doActionData.sheetName); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.SET_WORKSHEET_NAME_ACTION, + sheetName: this.do(), + }; + } + + undo(): void { + const { sheetName, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_WORKSHEET_NAME_ACTION, + sheetId, + sheetName: SetWorkSheetName(worksheet, sheetName), + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/SetWorkSheetStatusAction.ts b/packages/core/src/Sheets/Action/SetWorkSheetStatusAction.ts new file mode 100644 index 0000000000..b823f273ea --- /dev/null +++ b/packages/core/src/Sheets/Action/SetWorkSheetStatusAction.ts @@ -0,0 +1,83 @@ +import { SetWorkSheetStatus } from '../Apply'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkBook } from '../Domain/WorkBook'; +import { BooleanNumber } from '../../Enum'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; + +/** + * @internal + */ +export interface ISetWorkSheetStatusActionData extends IActionData { + sheetStatus: BooleanNumber; +} + +/** + * @internal + */ +export class SetWorkSheetStatusAction extends ActionBase { + constructor( + actionData: ISetWorkSheetStatusActionData, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + + this._oldActionData = { + ...actionData, + sheetStatus: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): number { + const worksheet = this.getWorkSheet(); + + const result = SetWorkSheetStatus(worksheet, this._doActionData.sheetStatus); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.SET_WORKSHEET_STATUS_ACTION, + sheetStatus: this.do(), + }; + } + + undo(): void { + const { sheetStatus, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_WORKSHEET_STATUS_ACTION, + sheetId, + sheetStatus: SetWorkSheetStatus(worksheet, sheetStatus), + }; + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Action/index.ts b/packages/core/src/Sheets/Action/index.ts new file mode 100644 index 0000000000..7223662441 --- /dev/null +++ b/packages/core/src/Sheets/Action/index.ts @@ -0,0 +1,43 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './AddMergeAction'; +export * from './ClearRangeAction'; +export * from './DeleteRangeAction'; +export * from './InsertColumnAction'; +export * from './InsertColumnDataAction'; +export * from './InsertRangeAction'; +export * from './InsertRowAction'; +export * from './InsertRowDataAction'; +export * from './InsertSheetAction'; +export * from './removeColumnAction'; +export * from './RemoveColumnDataAction'; +export * from './RemoveMergeAction'; +export * from './RemoveRowAction'; +export * from './RemoveRowDataAction'; +export * from './RemoveSheetAction'; +export * from './SetBorderAction'; +export * from './SetCollapseAllColumnGroupsAction'; +export * from './SetCollapseAllRowGroupsAction'; +export * from './SetColumnHideAction'; +export * from './SetColumnShowAction'; +export * from './SetColumnWidthAction'; +export * from './SetFrozenColumnsAction'; +export * from './SetFrozenRowsAction'; +export * from './SetHiddenGridlinesAction'; +export * from './SetRangeDataAction'; +export * from './SetRangeFormatAction'; +export * from './SetRangeFormulaAction'; +export * from './SetRangeNoteAction'; +export * from './SetRangeStyleAction'; +export * from './SetRangeValueAction'; +export * from './SetRightToLeftAction'; +export * from './SetRowHeightAction'; +export * from './SetRowHideAction'; +export * from './SetRowShowAction'; +export * from './SetSelectionActivateAction'; +export * from './SetSheetOrderAction'; +export * from './SetTabColorAction'; +export * from './SetWorkSheetActivateAction'; +export * from './SetWorkSheetHideAction'; +export * from './SetWorkSheetNameAction'; +export * from './SetWorkSheetStatusAction'; +// @endindex diff --git a/packages/core/src/Sheets/Action/removeColumnAction.ts b/packages/core/src/Sheets/Action/removeColumnAction.ts new file mode 100644 index 0000000000..5d7a00f6a7 --- /dev/null +++ b/packages/core/src/Sheets/Action/removeColumnAction.ts @@ -0,0 +1,85 @@ +import { InsertColumn, RemoveColumn } from '../Apply'; +import { WorkBook } from '../Domain'; +import { ActionBase, IActionData } from '../../Command/ActionBase'; +import { ActionObservers, ActionType } from '../../Command/ActionObservers'; +import { IInsertColumnActionData } from './InsertColumnAction'; + +/** + * @internal + */ +export interface IRemoveColumnAction extends IActionData { + columnCount: number; + columnIndex: number; +} + +/** + * Remove the column configuration of the specified column index and column count + * + * @internal + */ +export class RemoveColumnAction extends ActionBase< + IRemoveColumnAction, + IInsertColumnActionData +> { + constructor( + actionData: IRemoveColumnAction, + workbook: WorkBook, + observers: ActionObservers + ) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [], + }; + this._oldActionData = { + ...actionData, + columnCount: this.do(), + convertor: [], + }; + this.validate(); + } + + do(): number { + const worksheet = this.getWorkSheet(); + const columnManager = worksheet.getColumnManager(); + + const result = RemoveColumn( + this._doActionData.columnIndex, + this._doActionData.columnCount, + columnManager.getColumnData().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.REDO, + data: this._doActionData, + action: this, + }); + + return result; + } + + redo(): void { + this.do(); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const columnManager = worksheet.getColumnManager(); + + InsertColumn( + this._oldActionData.columnIndex, + this._oldActionData.columnCount, + columnManager.getColumnData().toJSON() + ); + + this._observers.notifyObservers({ + type: ActionType.UNDO, + data: this._oldActionData, + action: this, + }); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/core/src/Sheets/Apply/AddMerge.ts b/packages/core/src/Sheets/Apply/AddMerge.ts new file mode 100644 index 0000000000..3b58660015 --- /dev/null +++ b/packages/core/src/Sheets/Apply/AddMerge.ts @@ -0,0 +1,19 @@ +import { Merges } from '../Domain/Merges'; +import { IRangeData } from '../../Interfaces'; + +/** + * + * @param merges + * @param rectangles + * @returns + * + * @internal + */ +export function addMerge(merges: Merges, rectangles: IRangeData[]): IRangeData[] { + let remove: IRangeData[] = []; + for (let i = 0; i < rectangles.length; i++) { + merges.add(rectangles[i]); + // remove = remove.concat(merges.add(rectangles[i])); + } + return remove; +} diff --git a/packages/core/src/Sheets/Apply/ClearRange.ts b/packages/core/src/Sheets/Apply/ClearRange.ts new file mode 100644 index 0000000000..b34a79cc1e --- /dev/null +++ b/packages/core/src/Sheets/Apply/ClearRange.ts @@ -0,0 +1,47 @@ +import { ICellData, IOptionsData, IRangeData } from '../../Interfaces'; +import { Tools } from '../../Shared'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param cellMatrix + * @param options + * @param rangeData + * @returns + * + * @internal + */ +export function ClearRange( + cellMatrix: ObjectMatrix, + options: IOptionsData, + rangeData: IRangeData +): ObjectMatrixPrimitiveType { + const { startRow, endRow, startColumn, endColumn } = rangeData; + + const rows = endRow - startRow + 1; + const columns = endColumn - startColumn + 1; + + const result = new ObjectMatrix(); + // build new data + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + // get current value + const value = cellMatrix.getValue(r, c); + result.setValue(r, c, Tools.deepClone(value as ICellData)); + if (value) { + if (options.formatOnly) { + delete value.s; + } + if (options.contentsOnly) { + value.v = ''; + value.m = ''; + } + // TODO more options + + cellMatrix.setValue(r, c, Tools.deepClone(value as ICellData)); + } + } + } + + return result.getData(); +} diff --git a/packages/core/src/Sheets/Apply/DeleteRange.ts b/packages/core/src/Sheets/Apply/DeleteRange.ts new file mode 100644 index 0000000000..9d43c40fa2 --- /dev/null +++ b/packages/core/src/Sheets/Apply/DeleteRange.ts @@ -0,0 +1,95 @@ +import { Dimension } from '../../Enum/Dimension'; +import { ICellData, IRangeData } from '../../Interfaces'; +import { Tools } from '../../Shared'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { Nullable } from '../../Shared/Types'; + +/** + * + * @param count + * @param cellMatrix + * @param shiftDimension + * @param rangeData + * @returns + * + * @internal + */ +export function DeleteRange( + count: { + rowCount: number; + columnCount: number; + }, + cellMatrix: ObjectMatrix, + shiftDimension: Dimension, + rangeData: IRangeData +): ObjectMatrixPrimitiveType { + const { startRow, endRow, startColumn, endColumn } = rangeData; + + const rows = endRow - startRow + 1; + const columns = endColumn - startColumn + 1; + + const lastEndRow = count.rowCount; + const lastEndColumn = count.columnCount; + + const result = new ObjectMatrix(); + if (shiftDimension === Dimension.ROWS) { + // build new data + for (let r = startRow; r <= lastEndRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + // store old value + if (r <= endRow) { + const cell: Nullable = cellMatrix.getValue(r, c); + result.setValue(r, c, cell as ICellData); + } + // get value blow current range + const value = cellMatrix.getValue(r + rows, c); + if (value) { + cellMatrix.setValue(r, c, Tools.deepClone(value as ICellData)); + } else { + // null means delete + const originValue = cellMatrix.getValue(r, c); + if (originValue) { + cellMatrix.deleteValue(r, c); + // Deleting data will cause the column number to change, so subtract 1 here in advance + c--; + } + } + } + } + } else if (shiftDimension === Dimension.COLUMNS) { + // build new data + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= lastEndColumn; c++) { + // store old value + if (c <= endColumn) { + const cell: Nullable = cellMatrix.getValue(r, c); + result.setValue(r, c, cell as ICellData); + } else { + for (let i = 0; i <= endColumn; i++) { + const cell: Nullable = cellMatrix.getValue(r, c); + result.setValue(r, c + i, cell as ICellData); + } + } + + // get value blow current range + + const value = cellMatrix.getValue(r, c + columns); + if (value) { + cellMatrix.setValue(r, c, Tools.deepClone(value as ICellData)); + } else { + // null means delete + const originValue = cellMatrix.getValue(r, c); + if (originValue) { + for (let i = 0; i <= endColumn; i++) { + cellMatrix.deleteValue(r, c); + } + break; + // Deleting data will cause the column number to change, so subtract 1 here in advance + } + } + } + } + } + + return result.getData(); +} diff --git a/packages/core/src/Sheets/Apply/HideSheet.ts b/packages/core/src/Sheets/Apply/HideSheet.ts new file mode 100644 index 0000000000..a23041db60 --- /dev/null +++ b/packages/core/src/Sheets/Apply/HideSheet.ts @@ -0,0 +1,27 @@ +import { WorkSheet } from '../Domain/WorkSheet'; +import { BooleanNumber } from '../../Enum'; + +/** + * + * @param worksheet + * @param hidden + * @returns + * + * @internal + */ +export function SetWorkSheetHideService( + worksheet: WorkSheet, + hidden: BooleanNumber +): BooleanNumber { + // get config + const config = worksheet.getConfig(); + + // store old hidden setting + const oldHidden = config.hidden; + + // set new hidden setting + config.hidden = hidden; + + // return old hidden setting to undo + return oldHidden; +} diff --git a/packages/core/src/Sheets/Apply/InsertColumn.ts b/packages/core/src/Sheets/Apply/InsertColumn.ts new file mode 100644 index 0000000000..6344cc4830 --- /dev/null +++ b/packages/core/src/Sheets/Apply/InsertColumn.ts @@ -0,0 +1,19 @@ +import { IColumnData } from '../../Interfaces'; +import { ObjectArray, ObjectArrayPrimitiveType } from '../../Shared/ObjectArray'; + +/** + * Inserts addData into columnData + * @param columnIndex + * @param columnData + * @param insertData + * + * @internal + */ +export function InsertColumn( + columnIndex: number = 0, + columnCount: number = 1, + primitiveData: ObjectArrayPrimitiveType +) { + const wrapper = new ObjectArray(primitiveData); + wrapper.inserts(columnIndex, new ObjectArray(columnCount)); +} diff --git a/packages/core/src/Sheets/Apply/InsertDataColumn.ts b/packages/core/src/Sheets/Apply/InsertDataColumn.ts new file mode 100644 index 0000000000..afbba2dd37 --- /dev/null +++ b/packages/core/src/Sheets/Apply/InsertDataColumn.ts @@ -0,0 +1,19 @@ +import { ICellData } from '../../Interfaces'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param columnIndex + * @param columnData + * @param cellData + * + * @internal + */ +export function InsertDataColumn( + columnIndex: number, + columnData: ObjectMatrixPrimitiveType, + primitiveData: ObjectMatrixPrimitiveType +) { + const wrapper = new ObjectMatrix(primitiveData); + wrapper.insertColumns(columnIndex, new ObjectMatrix(columnData)); +} diff --git a/packages/core/src/Sheets/Apply/InsertDataRow.ts b/packages/core/src/Sheets/Apply/InsertDataRow.ts new file mode 100644 index 0000000000..3ad8224a88 --- /dev/null +++ b/packages/core/src/Sheets/Apply/InsertDataRow.ts @@ -0,0 +1,19 @@ +import { ICellData } from '../../Interfaces'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param rowIndex + * @param addData + * @param cellData + * + * @internal + */ +export function InsertDataRow( + rowIndex: number, + rowData: ObjectMatrixPrimitiveType, + primitiveData: ObjectMatrixPrimitiveType +) { + const wrapper = new ObjectMatrix(primitiveData); + wrapper.insertRows(rowIndex, new ObjectMatrix(rowData)); +} diff --git a/packages/core/src/Sheets/Apply/InsertRange.ts b/packages/core/src/Sheets/Apply/InsertRange.ts new file mode 100644 index 0000000000..d31f2d0b4b --- /dev/null +++ b/packages/core/src/Sheets/Apply/InsertRange.ts @@ -0,0 +1,80 @@ +import { Dimension } from '../../Enum/Dimension'; +import { ICellData, IRangeData } from '../../Interfaces'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param count + * @param cellMatrix + * @param shiftDimension + * @param rangeData + * @param cellValue + * + * @internal + */ +export function InsertRange( + count: { + rowCount: number; + columnCount: number; + }, + cellMatrix: ObjectMatrix, + shiftDimension: Dimension, + rangeData: IRangeData, + cellValue: ObjectMatrixPrimitiveType +): void { + const { startRow, endRow, startColumn, endColumn } = rangeData; + + const rows = endRow - startRow + 1; + const columns = endColumn - startColumn + 1; + + const lastEndRow = count.rowCount; + const lastEndColumn = count.columnCount; + + if (shiftDimension === Dimension.ROWS) { + // build new data + for (let r = lastEndRow; r >= startRow; r--) { + for (let c = startColumn; c <= endColumn; c++) { + // get value blow current range + const value = cellMatrix.getValue(r, c); + + cellMatrix.setValue(r + rows, c, value as ICellData); + } + } + + // insert cell value from user + for (let r = endRow; r >= startRow; r--) { + for (let c = startColumn; c <= endColumn; c++) { + cellMatrix.setValue( + r, + c, + (cellValue as ICellData)[r - startRow][c - startColumn] + ); + } + } + } else if (shiftDimension === Dimension.COLUMNS) { + for (let r = startRow; r <= endRow; r++) { + for (let c = lastEndColumn; c >= startColumn; c--) { + // get value blow current range + const value = cellMatrix.getValue(r, c); + + cellMatrix.setValue(r, c + columns, value as ICellData); + } + } + + // insert cell value from user + // for (let r = endRow; r >= startRow; r--) { + // for (let c = startColumn; c <= endColumn; c++) { + // cellMatrix.setValue(r, c, (cellValue as ICellData)[r - startRow][c - startColumn]); + // } + // } + for (let r = startRow; r <= endRow; r++) { + for (let c = endColumn; c >= startColumn; c--) { + cellMatrix.setValue( + r, + c, + (cellValue as ICellData)[r - startRow][c - startColumn] + ); + } + } + } +} diff --git a/packages/core/src/Sheets/Apply/InsertRow.ts b/packages/core/src/Sheets/Apply/InsertRow.ts new file mode 100644 index 0000000000..45d70f0909 --- /dev/null +++ b/packages/core/src/Sheets/Apply/InsertRow.ts @@ -0,0 +1,19 @@ +import { IRowData } from '../../Interfaces'; +import { ObjectArray, ObjectArrayPrimitiveType } from '../../Shared/ObjectArray'; + +/** + * Inserts addData into rowData + * @param rowIndex + * @param rowData + * @param insertData + * + * @internal + */ +export function InsertRow( + rowIndex: number = 0, + rowCount: number = 1, + primitiveData: ObjectArrayPrimitiveType +) { + const wrapper = new ObjectArray(primitiveData); + wrapper.inserts(rowIndex, new ObjectArray(rowCount)); +} diff --git a/packages/core/src/Sheets/Apply/InsertSheet.ts b/packages/core/src/Sheets/Apply/InsertSheet.ts new file mode 100644 index 0000000000..aabf133083 --- /dev/null +++ b/packages/core/src/Sheets/Apply/InsertSheet.ts @@ -0,0 +1,22 @@ +import { WorkBook, WorkSheet } from '../Domain'; +import { IWorksheetConfig } from '../../Interfaces'; + +export function InsertSheet( + workbook: WorkBook, + index: number, + worksheetConfig: IWorksheetConfig +): string { + const iSheets = workbook._getWorksheets(); + const config = workbook.getConfig(); + const { sheets, sheetOrder } = config; + if (sheets[worksheetConfig.id]) { + throw new Error(`Insert Sheet fail ${worksheetConfig.id} is already exist`); + } + sheets[worksheetConfig.id] = worksheetConfig; + sheetOrder.splice(index, 0, worksheetConfig.id); + iSheets.set( + worksheetConfig.id, + new WorkSheet(workbook.getContext(), worksheetConfig) + ); + return worksheetConfig.id; +} diff --git a/packages/core/src/Sheets/Apply/RemoveColumn.ts b/packages/core/src/Sheets/Apply/RemoveColumn.ts new file mode 100644 index 0000000000..a9699eed03 --- /dev/null +++ b/packages/core/src/Sheets/Apply/RemoveColumn.ts @@ -0,0 +1,28 @@ +import { IColumnData } from '../../Interfaces'; +import { ObjectArray, ObjectArrayPrimitiveType } from '../../Shared'; + +/** + * Deletes the specified number of columns in columnData + * @param columnIndex + * @param columnCount + * @param columnData + * + * @internal + */ +export function RemoveColumn( + columnIndex: number, + columnCount: number, + primitiveData: ObjectArrayPrimitiveType +): number { + const wrapper = new ObjectArray(primitiveData); + const result = new ObjectArray(); + const start = columnIndex; + const end = columnIndex + columnCount; + for (let i = start; i < end; i++) { + const splice = wrapper.splice(columnIndex, 1); + if (splice.getLength()) { + result.set(i, splice.first() as IColumnData); + } + } + return result.getLength(); +} diff --git a/packages/core/src/Sheets/Apply/RemoveColumnData.ts b/packages/core/src/Sheets/Apply/RemoveColumnData.ts new file mode 100644 index 0000000000..d2f18089ab --- /dev/null +++ b/packages/core/src/Sheets/Apply/RemoveColumnData.ts @@ -0,0 +1,21 @@ +import { ICellData } from '../../Interfaces'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param columnIndex + * @param columnCount + * @param cellData + * @returns + * + * @internal + */ +export function RemoveColumnData( + columnIndex: number, + columnCount: number, + primitiveData: ObjectMatrixPrimitiveType +): ObjectMatrixPrimitiveType { + return new ObjectMatrix(primitiveData) + .spliceColumns(columnIndex, columnCount) + .toJSON(); +} diff --git a/packages/core/src/Sheets/Apply/RemoveMerge.ts b/packages/core/src/Sheets/Apply/RemoveMerge.ts new file mode 100644 index 0000000000..e0686ce47b --- /dev/null +++ b/packages/core/src/Sheets/Apply/RemoveMerge.ts @@ -0,0 +1,18 @@ +import { Merges } from '../Domain/Merges'; +import { IRangeData } from '../../Interfaces'; + +/** + * + * @param merges + * @param rectangles + * @returns + * + * @internal + */ +export function RemoveMerge(merges: Merges, rectangles: IRangeData[]): IRangeData[] { + let remove: IRangeData[] = []; + for (let i = 0; i < rectangles.length; i++) { + remove = remove.concat(merges.remove(rectangles[i])); + } + return remove; +} diff --git a/packages/core/src/Sheets/Apply/RemoveRow.ts b/packages/core/src/Sheets/Apply/RemoveRow.ts new file mode 100644 index 0000000000..53e1fa14ab --- /dev/null +++ b/packages/core/src/Sheets/Apply/RemoveRow.ts @@ -0,0 +1,28 @@ +import { IRowData } from '../../Interfaces'; +import { ObjectArray, ObjectArrayPrimitiveType } from '../../Shared/ObjectArray'; + +/** + * Deletes the specified number of rows in rowData + * @param rowIndex + * @param rowCount + * @param rowData + * + * @internal + */ +export function RemoveRow( + rowIndex: number, + rowCount: number, + primitiveData: ObjectArrayPrimitiveType +): number { + const wrapper = new ObjectArray(primitiveData); + const result = new ObjectArray(); + const start = rowIndex; + const end = rowIndex + rowCount; + for (let i = start; i < end; i++) { + const splice = wrapper.splice(rowIndex, 1); + if (splice.getLength()) { + result.set(i, splice.first() as IRowData); + } + } + return result.getLength(); +} diff --git a/packages/core/src/Sheets/Apply/RemoveRowData.ts b/packages/core/src/Sheets/Apply/RemoveRowData.ts new file mode 100644 index 0000000000..a432fbead9 --- /dev/null +++ b/packages/core/src/Sheets/Apply/RemoveRowData.ts @@ -0,0 +1,20 @@ +import { ICellData } from '../../Interfaces'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param rowIndex + * @param rowCount + * @param cellData + * @returns + * + * @internal + */ +export function RemoveRowData( + rowIndex: number, + rowCount: number, + primitiveData: ObjectMatrixPrimitiveType +): ObjectMatrixPrimitiveType { + const wrapper = new ObjectMatrix(primitiveData); + return wrapper.spliceRows(rowIndex, rowCount).toJSON(); +} diff --git a/packages/core/src/Sheets/Apply/RemoveSheet.ts b/packages/core/src/Sheets/Apply/RemoveSheet.ts new file mode 100644 index 0000000000..3d6d850193 --- /dev/null +++ b/packages/core/src/Sheets/Apply/RemoveSheet.ts @@ -0,0 +1,26 @@ +import { WorkBook } from '../Domain'; +import { IWorksheetConfig } from '../../Interfaces'; + +export function RemoveSheet( + workbook: WorkBook, + sheetId: string +): { + index: number; + sheet: IWorksheetConfig; +} { + const iSheets = workbook._getWorksheets(); + const config = workbook.getConfig(); + const { sheets } = config; + if (sheets[sheetId] == null) { + throw new Error(`Remove Sheet fail ${sheetId} is not exist`); + } + const removeSheet = sheets[sheetId]; + const removeIndex = config.sheetOrder.findIndex((id) => id === sheetId); + delete sheets[sheetId]; + config.sheetOrder.splice(removeIndex, 1); + iSheets.delete(sheetId); + return { + index: removeIndex, + sheet: removeSheet as IWorksheetConfig, + }; +} diff --git a/packages/core/src/Sheets/Apply/SetBorder.ts b/packages/core/src/Sheets/Apply/SetBorder.ts new file mode 100644 index 0000000000..379be98138 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetBorder.ts @@ -0,0 +1,34 @@ +import { Styles } from '../Domain'; +import { ICellData, IStyleData } from '../../Interfaces'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared'; + +/** + * + * @param matrix + * @param globalStyles + * @param updateStyles + * @returns + * + * @internal + */ +export function SetBorder( + matrix: ObjectMatrix, + globalStyles: Styles, + primitiveStyles: ObjectMatrixPrimitiveType +): ObjectMatrixPrimitiveType { + const wrapper = new ObjectMatrix(primitiveStyles); + const result = new ObjectMatrix(); + wrapper.forEach((row, array) => { + array.forEach((column, value) => { + const cell = matrix.getValue(row, column); + if (cell) { + const old = globalStyles.get(cell.s); + if (old) { + result.setValue(row, column, old); + } + cell.s = globalStyles.setValue(value); + } + }); + }); + return result.toJSON(); +} diff --git a/packages/core/src/Sheets/Apply/SetCollapseAllColumnGroups.ts b/packages/core/src/Sheets/Apply/SetCollapseAllColumnGroups.ts new file mode 100644 index 0000000000..c9e4d8dfe9 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetCollapseAllColumnGroups.ts @@ -0,0 +1,15 @@ +import { GroupOpenType, StructGroup } from '../../Module/Group'; + +/** + * + * @param structGroup + * @param openType + * + * @internal + */ +export function SetCollapseAllColumnGroups( + structGroup: StructGroup, + openType: GroupOpenType +) { + structGroup.setAllGroupOpenType(openType); +} diff --git a/packages/core/src/Sheets/Apply/SetCollapseAllRowGroups.ts b/packages/core/src/Sheets/Apply/SetCollapseAllRowGroups.ts new file mode 100644 index 0000000000..8148b30e83 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetCollapseAllRowGroups.ts @@ -0,0 +1,15 @@ +import { GroupOpenType, StructGroup } from '../../Module/Group'; + +/** + * + * @param structGroup + * @param openType + * + * @internal + */ +export function SetCollapseAllRowGroups( + structGroup: StructGroup, + openType: GroupOpenType +) { + structGroup.setAllGroupOpenType(openType); +} diff --git a/packages/core/src/Sheets/Apply/SetColumnHide.ts b/packages/core/src/Sheets/Apply/SetColumnHide.ts new file mode 100644 index 0000000000..651ddfc74f --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetColumnHide.ts @@ -0,0 +1,20 @@ +import { ColumnManager } from '../Domain/ColumnManager'; + +/** + * + * @param columnIndex + * @param columnCount + * @param columnManager + * + * @internal + */ +export function SetColumnHide( + columnIndex: number = 0, + columnCount: number, + columnManager: ColumnManager +): void { + for (let i = columnIndex; i < columnIndex + columnCount; i++) { + const column = columnManager.getColumnOrCreate(i); + column.hd = 1; + } +} diff --git a/packages/core/src/Sheets/Apply/SetColumnShow.ts b/packages/core/src/Sheets/Apply/SetColumnShow.ts new file mode 100644 index 0000000000..aa05bb8c25 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetColumnShow.ts @@ -0,0 +1,22 @@ +import { ColumnManager } from '../Domain/ColumnManager'; + +/** + * + * @param columnIndex + * @param columnCount + * @param columnManager + * + * @internal + */ +export function SetColumnShow( + columnIndex: number = 0, + columnCount: number, + columnManager: ColumnManager +): void { + for (let i = columnIndex; i < columnIndex + columnCount; i++) { + const column = columnManager.getColumn(i); + if (column) { + column.hd = 0; + } + } +} diff --git a/packages/core/src/Sheets/Apply/SetColumnWidth.ts b/packages/core/src/Sheets/Apply/SetColumnWidth.ts new file mode 100644 index 0000000000..2e32bca852 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetColumnWidth.ts @@ -0,0 +1,24 @@ +import { ColumnManager } from '../Domain/ColumnManager'; + +/** + * + * @param columnIndex + * @param columnWidth + * @param columnManager + * @returns + * + * @internal + */ +export function SetColumnWidth( + columnIndex: number = 0, + columnWidth: number[], + columnManager: ColumnManager +) { + const result: number[] = []; + for (let i = columnIndex; i < columnIndex + columnWidth.length; i++) { + const column = columnManager.getColumnOrCreate(i); + result[i] = column.w; + column.w = columnWidth[i]; + } + return result; +} diff --git a/packages/core/src/Sheets/Apply/SetFrozenColumns.ts b/packages/core/src/Sheets/Apply/SetFrozenColumns.ts new file mode 100644 index 0000000000..a2bff634ec --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetFrozenColumns.ts @@ -0,0 +1,23 @@ +import { WorkSheet } from '../Domain/WorkSheet'; + +/** + * + * @param worksheet + * @param numColumns + * @returns + * + * @internal + */ +export function SetFrozenColumns(worksheet: WorkSheet, numColumns: number): number { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldStatus = config.freezeColumn; + + // set new sheet name + config.freezeColumn = numColumns; + + // return old sheet name to undo + return oldStatus; +} diff --git a/packages/core/src/Sheets/Apply/SetFrozenRows.ts b/packages/core/src/Sheets/Apply/SetFrozenRows.ts new file mode 100644 index 0000000000..c20ec87a20 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetFrozenRows.ts @@ -0,0 +1,23 @@ +import { WorkSheet } from '../Domain/WorkSheet'; + +/** + * + * @param worksheet + * @param numRows + * @returns + * + * @internal + */ +export function SetFrozenRows(worksheet: WorkSheet, numRows: number): number { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldStatus = config.freezeRow; + + // set new sheet name + config.freezeRow = numRows; + + // return old sheet name to undo + return oldStatus; +} diff --git a/packages/core/src/Sheets/Apply/SetHiddenGridlines.ts b/packages/core/src/Sheets/Apply/SetHiddenGridlines.ts new file mode 100644 index 0000000000..d4d239294e --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetHiddenGridlines.ts @@ -0,0 +1,30 @@ +import { WorkSheet } from '../Domain/WorkSheet'; + +/** + * + * @param worksheet + * @param hideGridlines + * @returns + * + * @internal + */ +export function SetHiddenGridlines( + worksheet: WorkSheet, + hideGridlines: boolean +): boolean { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldStatus = config.showGridlines === 0; + + // set new sheet name + if (hideGridlines) { + config.showGridlines = 0; + } else { + config.showGridlines = 1; + } + + // return old sheet name to undo + return oldStatus; +} diff --git a/packages/core/src/Sheets/Apply/SetHideRows.ts b/packages/core/src/Sheets/Apply/SetHideRows.ts new file mode 100644 index 0000000000..0d2fee4019 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetHideRows.ts @@ -0,0 +1,22 @@ +import { RowManager } from '../Domain/RowManager'; + +/** + * + * @param rowIndex + * @param rowCount + * @param rowManager + * + * @internal + */ +export function SetHideRow( + rowIndex: number = 0, + rowCount: number, + rowManager: RowManager +) { + for (let i = rowIndex; i < rowIndex + rowCount; i++) { + const row = rowManager.getRowOrCreate(i); + if (row) { + row.hd = 1; + } + } +} diff --git a/packages/core/src/Sheets/Apply/SetRangeData.ts b/packages/core/src/Sheets/Apply/SetRangeData.ts new file mode 100644 index 0000000000..df1c54d9da --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRangeData.ts @@ -0,0 +1,122 @@ +import { Styles } from '../Domain'; +import { + ICellData, + ICopyToOptionsData, + IRangeData, + IStyleData, +} from '../../Interfaces'; +import { Nullable, Tools } from '../../Shared'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; +import { mergeStyle, transformStyle } from './SetRangeStyle'; + +/** + * + * @param cellMatrix + * @param addMatrix + * @param rangeData + * @param options + * @returns + * + * @internal + */ +export function SetRangeData( + cellMatrix: ObjectMatrix, + addMatrix: ObjectMatrixPrimitiveType, + rangeData: IRangeData, + styles: Styles, + options?: ICopyToOptionsData +): ObjectMatrixPrimitiveType { + const target = new ObjectMatrix(addMatrix); + const result = new ObjectMatrix(); + + const { startRow, endRow, startColumn, endColumn } = rangeData; + + if (options) { + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + const value = target.getValue(r, c); + const cell = cellMatrix.getValue(r, c); + const newCell: ICellData = {}; + + if (options.contentsOnly) { + newCell.m = cell?.m; + newCell.v = cell?.v; + } + + if (options.formatOnly) { + // newCell.f = cell?.f; + newCell.v = cell?.v; + newCell.m = cell?.m; + } + + result.setValue(r, c, newCell || {}); + + cellMatrix.setValue(r, c, value || {}); + } + } + return result.getData(); + } + + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + const value = target.getValue(r, c); + const cell = cellMatrix.getValue(r, c) || {}; + + // set null, clear cell + if (!value) { + cellMatrix.setValue(r, c, value); + result.setValue(r, c, cell); + continue; + } + + // update style + + // use null to clear style + const old = cell.s ? styles.get(cell.s) : null; + + // store old data + const oldCellStyle = transformStyle( + old, + value?.s as Nullable + ); + const oldCellData = Tools.deepClone(cell); + oldCellData.s = oldCellStyle; + + result.setValue(r, c, oldCellData); + + if (old == null) { + // clear + delete cell.s; + } + + // set style + const merge = mergeStyle(old, value?.s as Nullable); + + // then remove null + merge && Tools.removeNull(merge); + + if (Tools.isEmptyObject(merge)) { + delete cell.s; + } else { + cell.s = styles.setValue(merge); + } + + // update other value + if (value.p) { + cell.p = value.p; + } + if (value.v) { + cell.v = value.v; + } + if (value.m) { + cell.m = value.m; + } + if (value.t) { + cell.t = value.t; + } + + cellMatrix.setValue(r, c, cell); + } + } + return result.getData(); +} diff --git a/packages/core/src/Sheets/Apply/SetRangeFormat.ts b/packages/core/src/Sheets/Apply/SetRangeFormat.ts new file mode 100644 index 0000000000..a22d112f39 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRangeFormat.ts @@ -0,0 +1,54 @@ +import { ICellData, IRangeData } from '../../Interfaces'; +import { Nullable, Tools } from '../../Shared'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * TODO remove tp Format Plugin + */ +/** + * + * @param cellMatrix + * @param addMatrix + * @param rangeData + * @returns + * + * @internal + */ +export function SetRangeFormat( + cellMatrix: ObjectMatrix, + addMatrix: ObjectMatrixPrimitiveType, + rangeData: IRangeData +): ObjectMatrixPrimitiveType { + const target = new ObjectMatrix(addMatrix); + const result = new ObjectMatrix(); + for (let i = rangeData.startRow; i <= rangeData.endRow; i++) { + for (let j = rangeData.startColumn; j <= rangeData.endColumn; j++) { + const targetValue = target.getValue(i, j); + + if (targetValue) { + const value = JSON.parse(targetValue); + + // store history value + const cell: Nullable = cellMatrix.getValue(i, j); + // result.setValue(i, j, (cell && JSON.stringify(cell.fm)) || ''); + + // update new value, cell may be undefined + const cellValue: ICellData = Tools.deepClone(cell || {}); + + // cellValue.fm = { + // f: value.f, + // t: value.t, + // }; + + cellMatrix.setValue(i, j, cellValue || {}); + } else { + const cell: Nullable = cellMatrix.getValue(i, j); + // result.setValue(i, j, (cell && JSON.stringify(cell.fm)) || ''); + const cellValue: ICellData = Tools.deepClone(cell || {}); + // delete cellValue.fm; + cellMatrix.setValue(i, j, cellValue || {}); + } + } + } + return result.getData(); +} diff --git a/packages/core/src/Sheets/Apply/SetRangeFormula.ts b/packages/core/src/Sheets/Apply/SetRangeFormula.ts new file mode 100644 index 0000000000..4b3ec2357a --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRangeFormula.ts @@ -0,0 +1,42 @@ +import { ICellData, IRangeData } from '../../Interfaces'; +import { Nullable, Tools } from '../../Shared'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * TODO remove tp Formula Plugin + */ + +/** + * + * @param cellMatrix + * @param addMatrix + * @param rangeData + * @returns + * + * @internal + */ +export function SetRangeFormula( + cellMatrix: ObjectMatrix, + addMatrix: ObjectMatrixPrimitiveType, + rangeData: IRangeData +): ObjectMatrixPrimitiveType { + const target = new ObjectMatrix(addMatrix); + const result = new ObjectMatrix(); + for (let i = rangeData.startRow; i <= rangeData.endRow; i++) { + for (let j = rangeData.startColumn; j <= rangeData.endColumn; j++) { + const value = target.getValue(i, j); + + // store history value + const cell: Nullable = cellMatrix.getValue(i, j); + // result.setValue(i, j, (cell && cell.f) || ''); + + // update new value, cell may be undefined + const cellValue = Tools.deepClone(cell || {}); + + cellValue.f = value; + + cellMatrix.setValue(i, j, cellValue || {}); + } + } + return result.getData(); +} diff --git a/packages/core/src/Sheets/Apply/SetRangeNote.ts b/packages/core/src/Sheets/Apply/SetRangeNote.ts new file mode 100644 index 0000000000..18dcab4cec --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRangeNote.ts @@ -0,0 +1,38 @@ +import { ICellData, IRangeData } from '../../Interfaces'; +import { Nullable, Tools } from '../../Shared'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param cellMatrix + * @param addMatrix + * @param rangeData + * @returns + * + * @internal + */ +export function SetRangeNote( + cellMatrix: ObjectMatrix, + addMatrix: ObjectMatrixPrimitiveType, + rangeData: IRangeData +): ObjectMatrixPrimitiveType { + const target = new ObjectMatrix(addMatrix); + const result = new ObjectMatrix(); + for (let i = rangeData.startRow; i <= rangeData.endRow; i++) { + for (let j = rangeData.startColumn; j <= rangeData.endColumn; j++) { + const value = target.getValue(i, j); + + // store history value + const cell: Nullable = cellMatrix.getValue(i, j); + // result.setValue(i, j, (cell && cell.n) || ''); + + // update new value, cell may be undefined + const cellValue = Tools.deepClone(cell || {}); + + cellValue.n = value; + + cellMatrix.setValue(i, j, cellValue || {}); + } + } + return result.getData(); +} diff --git a/packages/core/src/Sheets/Apply/SetRangeStyle.ts b/packages/core/src/Sheets/Apply/SetRangeStyle.ts new file mode 100644 index 0000000000..95cca5f8bc --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRangeStyle.ts @@ -0,0 +1,228 @@ +import { ITextStyle } from '../../Interfaces/IDocumentData'; +import { Styles } from '../Domain'; +import { IRangeData, ICellData, IDocumentData, ITextRun } from '../../Interfaces'; +import { IBorderData, IStyleData } from '../../Interfaces/IStyleData'; +import { + Nullable, + ObjectMatrix, + ObjectMatrixPrimitiveType, + Tools, +} from '../../Shared'; + +/** + * Set the style. The style sent by the user will be merged into the original style object. If you want to delete a style, you need to explicitly set it to null + * @param param0 + * @returns + *cellMatrix: , + addMatrix: , + rangeData: IRangeData + * @internal + */ +export function SetRangeStyle( + cellMatrix: ObjectMatrix, + value: ObjectMatrixPrimitiveType, + rangeData: IRangeData, + styles: Styles +): ObjectMatrixPrimitiveType { + const { startRow, endRow, startColumn, endColumn } = rangeData; + const result = new ObjectMatrix(); + for (let r = startRow; r <= endRow; r++) { + const rowResult = []; + for (let c = startColumn; c <= endColumn; c++) { + const cell = cellMatrix.getValue(r, c) || {}; + // use null to clear style + const old = cell.s ? styles.get(cell.s) : null; + + if (old == null) { + // clear + delete cell.s; + } + + // set style + const merge = mergeStyle( + old, + value[r - startRow][c - startColumn] as Nullable + ); + + // then remove null + merge && Tools.removeNull(merge); + + if (Tools.isEmptyObject(merge)) { + delete cell.s; + } else { + cell.s = styles.setValue(merge); + } + + // When the rich text sets the cell style, you need to modify the style of all rich text + // TODO redo/undo use setRangeData to undo rich text setting + if (cell.p) { + mergeRichTextStyle( + cell.p, + value[r - startRow][c - startColumn] as Nullable + ); + } + + cellMatrix.setValue(r, c, cell); + + // store old data + result.setValue( + r - startRow, + c - startColumn, + transformStyle( + old, + value[r - startRow][c - startColumn] as Nullable + ) + ); + } + + // result.push(rowResult); + } + return result.getData(); +} + +/** + * Convert old style data for storage + * @param style + */ +export function transformStyle( + oldStyle: Nullable, + newStyle: Nullable +): Nullable { + const backupStyle = transformNormalKey(oldStyle, newStyle); + return backupStyle; +} +/** + * Convert old style normal key for storage + * + * @param style + */ +export function transformNormalKey( + oldStyle: Nullable, + newStyle: Nullable +): Nullable { + // If there is no newly set style, directly store the historical style + if (!newStyle || !Object.keys(newStyle).length) { + return oldStyle; + } + const backupStyle = Tools.deepClone(oldStyle) || {}; + + for (const k in newStyle) { + if (k === 'bd') { + backupStyle[k] = transformBorders(backupStyle[k] || {}, newStyle[k]); + } + // 1. To modify the existing style,we need original setting to undo + // 2. Newly set the style, we need null to undo + else if (!(k in backupStyle)) { + backupStyle[k] = null; + } + } + return backupStyle; +} +/** + * Convert old style border for storage + * + + * @param style + */ +export function transformBorders( + oldBorders: IBorderData, + newBorders: Nullable +): IBorderData { + // If there is no newly set border, directly store the historical border + if (!newBorders || !Object.keys(newBorders).length) { + return oldBorders; + } + + for (const k in newBorders) { + // 1. To modify the existing border,we need original setting to undo + // 2. Newly set the border, we need null to undo + if (!(k in oldBorders)) { + oldBorders[k] = null; + } + } + + return oldBorders; +} + +/** + * merge new style to old style + * + * @param oldStyle + * @param newStyle + */ +export function mergeStyle( + oldStyle: Nullable, + newStyle: Nullable, + isRichText: boolean = false +): Nullable { + // clear style + if (!newStyle) return newStyle; + + const backupStyle = Tools.deepClone(oldStyle) || {}; + + for (const k in newStyle) { + if (isRichText && ['bd', 'tr', 'td', 'ht', 'vt', 'tb', 'pd'].includes(k)) { + continue; + } + // you can only choose one of the themeColor and rgbColor of the border setting + if (k in backupStyle && k === 'bd') { + backupStyle[k] = Object.assign(backupStyle[k], newStyle[k]); + } else { + backupStyle[k] = newStyle[k]; + + // overline/strikethrough/underline add color + if ('cl' in backupStyle) { + if (['ul', 'ol', 'st'].includes(k)) { + backupStyle[k].cl = backupStyle.cl; + } + } + } + } + return backupStyle; +} + +/** + * Find the text style of all paragraphs and modify it to the new style + * @param p + * @param newStyle + */ +export function mergeRichTextStyle( + p: IDocumentData, + newStyle: Nullable +) { + p.body?.blockElementOrder.forEach((blockId) => { + if (p.body?.blockElements[blockId].blockType === 0) { + const paragraph = p.body?.blockElements[blockId].paragraph; + paragraph?.elementOrder.forEach( + ({ elementId, paragraphElementType }) => { + if (!paragraph.elements[elementId].tr) { + paragraph.elements[elementId].tr = {}; + } + + const textRun = paragraph.elements[elementId].tr as ITextRun; + + if (!textRun.ts) { + textRun.ts = {}; + } + + const oldStyle = textRun.ts; + + const merge = mergeStyle( + oldStyle as Nullable, + newStyle, + true + ); + + // then remove null + merge && Tools.removeNull(merge); + + if (Tools.isEmptyObject(merge)) { + delete textRun.ts; + } else { + textRun.ts = merge as ITextStyle; + } + } + ); + } + }); +} diff --git a/packages/core/src/Sheets/Apply/SetRangeValue.ts b/packages/core/src/Sheets/Apply/SetRangeValue.ts new file mode 100644 index 0000000000..0651d0e20e --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRangeValue.ts @@ -0,0 +1,40 @@ +import { ICellData, ICellV, IRangeData } from '../../Interfaces'; +import { Nullable, Tools } from '../../Shared'; +import { ObjectMatrix, ObjectMatrixPrimitiveType } from '../../Shared/ObjectMatrix'; + +/** + * + * @param cellMatrix + * @param addMatrix + * @param rangeData + * @returns + * + * @internal + */ +export function SetRangeValue( + cellMatrix: ObjectMatrix, + addMatrix: ObjectMatrixPrimitiveType, + rangeData: IRangeData +): ObjectMatrixPrimitiveType { + const target = new ObjectMatrix(addMatrix); + const result = new ObjectMatrix(); + for (let i = rangeData.startRow; i <= rangeData.endRow; i++) { + for (let j = rangeData.startColumn; j <= rangeData.endColumn; j++) { + const value = target.getValue(i, j); + + // store history value + const cell: Nullable = cellMatrix.getValue(i, j); + result.setValue(i, j, (cell && cell.v) || ''); + + // update new value, cell may be undefined + const cellValue = Tools.deepClone(cell || {}); + cellValue.v = value; + // TODO:calculate m from cell format + // TODO:解析外移,性能监测 + cellValue.m = `${value}`; + + cellMatrix.setValue(i, j, cellValue || {}); + } + } + return result.getData(); +} diff --git a/packages/core/src/Sheets/Apply/SetRightToLeft.ts b/packages/core/src/Sheets/Apply/SetRightToLeft.ts new file mode 100644 index 0000000000..1083e1b602 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRightToLeft.ts @@ -0,0 +1,27 @@ +import { WorkSheet } from '../Domain/WorkSheet'; +import { BooleanNumber } from '../../Enum'; + +/** + * + * @param worksheet + * @param rightToLeft + * @returns + * + * @internal + */ +export function SetRightToLeft( + worksheet: WorkSheet, + rightToLeft: BooleanNumber +): BooleanNumber { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldState = config.rightToLeft; + + // set new sheet name + config.rightToLeft = rightToLeft; + + // return old sheet name to undo + return oldState; +} diff --git a/packages/core/src/Sheets/Apply/SetRowHeight.ts b/packages/core/src/Sheets/Apply/SetRowHeight.ts new file mode 100644 index 0000000000..cc599e6b5b --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetRowHeight.ts @@ -0,0 +1,24 @@ +import { RowManager } from '../Domain/RowManager'; + +/** + * + * @param rowIndex + * @param rowHeight + * @param rowManager + * @returns + * + * @internal + */ +export function SetRowHeight( + rowIndex: number = 0, + rowHeight: number[], + rowManager: RowManager +) { + const result: number[] = []; + for (let i = rowIndex; i < rowIndex + rowHeight.length; i++) { + const row = rowManager.getRowOrCreate(i); + result[i] = row.h; + row.h = rowHeight[i]; + } + return result; +} diff --git a/packages/core/src/Sheets/Apply/SetSelectionActivate.ts b/packages/core/src/Sheets/Apply/SetSelectionActivate.ts new file mode 100644 index 0000000000..d141a41e25 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetSelectionActivate.ts @@ -0,0 +1,35 @@ +import { ISetSelectionActivateServiceData } from '../Action'; +import { WorkSheet } from '../Domain/WorkSheet'; +import { IRangeData } from '../../Interfaces/IRangeData'; + +/** + * + * @param worksheet + * @param activeRangeList + * @param activeRange + * @param currentCell + * @returns + * + * @internal + */ +export function SetSelectionActivate( + worksheet: WorkSheet, + activeRangeList: IRangeData | IRangeData[], + activeRange: IRangeData, + currentCell: IRangeData +): ISetSelectionActivateServiceData { + const selectionIns = worksheet.getSelection(); + + const oldActiveRangeList = selectionIns.getActiveRangeList()?.getRangeList(); + const oldActiveRange = selectionIns.getActiveRange()?.getRangeData(); + const oldCurrentCell = selectionIns.getCurrentCell()?.getRangeData(); + + // set multiple ranges from domain + selectionIns.setRanges(activeRangeList, activeRange, currentCell); + + return { + activeRangeList: oldActiveRangeList, + activeRange: oldActiveRange, + currentCell: oldCurrentCell, + }; +} diff --git a/packages/core/src/Sheets/Apply/SetSheetOrder.ts b/packages/core/src/Sheets/Apply/SetSheetOrder.ts new file mode 100644 index 0000000000..35548a03f2 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetSheetOrder.ts @@ -0,0 +1,14 @@ +import { WorkBook } from '../Domain'; + +export function SetSheetOrder( + workbook: WorkBook, + sheetId: string, + order: number +): number { + const config = workbook.getConfig(); + const oldIndex = config.sheetOrder.findIndex((current) => current === sheetId); + const exclude = config.sheetOrder.filter((currentId) => currentId !== sheetId); + exclude.splice(order, 0, sheetId); + config.sheetOrder = exclude; + return oldIndex; +} diff --git a/packages/core/src/Sheets/Apply/SetShowRow.ts b/packages/core/src/Sheets/Apply/SetShowRow.ts new file mode 100644 index 0000000000..fb46efbc46 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetShowRow.ts @@ -0,0 +1,22 @@ +import { RowManager } from '../Domain/RowManager'; + +/** + * + * @param rowIndex + * @param rowCount + * @param rowManager + * + * @internal + */ +export function SetShowRow( + rowIndex: number = 0, + rowCount: number, + rowManager: RowManager +) { + for (let i = rowIndex; i < rowIndex + rowCount; i++) { + const row = rowManager.getRow(i); + if (row) { + row.hd = 0; + } + } +} diff --git a/packages/core/src/Sheets/Apply/SetTabColor.ts b/packages/core/src/Sheets/Apply/SetTabColor.ts new file mode 100644 index 0000000000..1acd23f3be --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetTabColor.ts @@ -0,0 +1,27 @@ +import { WorkSheet } from '../Domain/WorkSheet'; +import { Nullable } from '../../Shared/Types'; + +/** + * + * @param worksheet + * @param color + * @returns + * + * @internal + */ +export function SetTabColor( + worksheet: WorkSheet, + color: Nullable +): Nullable { + // get config + const config = worksheet.getConfig(); + + // store old tab color + const oldTabColor = config.tabColor; + + // set new tab color + config.tabColor = color!; + + // return old color to undo + return oldTabColor; +} diff --git a/packages/core/src/Sheets/Apply/SetWorkSheetActivate.ts b/packages/core/src/Sheets/Apply/SetWorkSheetActivate.ts new file mode 100644 index 0000000000..95998516ca --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetWorkSheetActivate.ts @@ -0,0 +1,45 @@ +import { ISheetStatus } from '../../Command'; +import { WorkSheet } from '../Domain/WorkSheet'; +import { BooleanNumber } from '../../Enum'; + +/** + * + * @param worksheet + * @param status + * @returns + * + * @internal + */ +export function SetWorkSheetActivate( + worksheet: WorkSheet, + status: BooleanNumber +): ISheetStatus { + // store old status + + const oldSheet = worksheet.getContext().getWorkBook().getActiveSheet(); + let oldSheetId = ''; + const oldStatus = BooleanNumber.FALSE; + if (oldSheet) { + oldSheetId = oldSheet.getSheetId(); + // oldSheet.getStatus(); + } + + const sheets = worksheet.getContext().getWorkBook().getSheets(); + const currentSheetId = worksheet.getSheetId(); + + sheets.forEach((sheet) => { + const sheetId = sheet.getSheetId(); + if (sheetId === currentSheetId) { + // set new status + // sheet.setStatus(status); + sheet.getConfig().status = BooleanNumber.TRUE; + } else { + // reset status + // sheet.setStatus(BooleanNumber.FALSE); + sheet.getConfig().status = BooleanNumber.FALSE; + } + }); + + // return old status to undo + return { oldSheetId, status: oldStatus }; +} diff --git a/packages/core/src/Sheets/Apply/SetWorkSheetName.ts b/packages/core/src/Sheets/Apply/SetWorkSheetName.ts new file mode 100644 index 0000000000..caa40c7285 --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetWorkSheetName.ts @@ -0,0 +1,23 @@ +import { WorkSheet } from '../Domain/WorkSheet'; + +/** + * + * @param worksheet + * @param sheetName + * @returns + * + * @internal + */ +export function SetWorkSheetName(worksheet: WorkSheet, sheetName: string): string { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldSheetName = config.name; + + // set new sheet name + config.name = sheetName; + + // return old sheet name to undo + return oldSheetName; +} diff --git a/packages/core/src/Sheets/Apply/SetWorkSheetStatus.ts b/packages/core/src/Sheets/Apply/SetWorkSheetStatus.ts new file mode 100644 index 0000000000..7460c588aa --- /dev/null +++ b/packages/core/src/Sheets/Apply/SetWorkSheetStatus.ts @@ -0,0 +1,27 @@ +import { WorkSheet } from '../Domain/WorkSheet'; +import { BooleanNumber } from '../../Enum'; + +/** + * + * @param worksheet + * @param sheetStatus + * @returns + * + * @internal + */ +export function SetWorkSheetStatus( + worksheet: WorkSheet, + sheetStatus: BooleanNumber +): BooleanNumber { + // get config + const config = worksheet.getConfig(); + + // store old sheet name + const oldStatus = config.status; + + // set new sheet name + config.status = sheetStatus; + + // return old sheet name to undo + return oldStatus; +} diff --git a/packages/core/src/Sheets/Apply/index.ts b/packages/core/src/Sheets/Apply/index.ts new file mode 100644 index 0000000000..42a148f619 --- /dev/null +++ b/packages/core/src/Sheets/Apply/index.ts @@ -0,0 +1,43 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './AddMerge'; +export * from './ClearRange'; +export * from './DeleteRange'; +export * from './HideSheet'; +export * from './InsertColumn'; +export * from './InsertDataColumn'; +export * from './InsertDataRow'; +export * from './InsertRange'; +export * from './InsertRow'; +export * from './InsertSheet'; +export * from './RemoveColumn'; +export * from './RemoveColumnData'; +export * from './RemoveMerge'; +export * from './RemoveRow'; +export * from './RemoveRowData'; +export * from './RemoveSheet'; +export * from './SetBorder'; +export * from './SetCollapseAllColumnGroups'; +export * from './SetCollapseAllRowGroups'; +export * from './SetColumnHide'; +export * from './SetColumnShow'; +export * from './SetColumnWidth'; +export * from './SetFrozenColumns'; +export * from './SetFrozenRows'; +export * from './SetHiddenGridlines'; +export * from './SetHideRows'; +export * from './SetRangeData'; +export * from './SetRangeFormat'; +export * from './SetRangeFormula'; +export * from './SetRangeNote'; +export * from './SetRangeStyle'; +export * from './SetRangeValue'; +export * from './SetRightToLeft'; +export * from './SetRowHeight'; +export * from './SetSelectionActivate'; +export * from './SetSheetOrder'; +export * from './SetShowRow'; +export * from './SetTabColor'; +export * from './SetWorkSheetActivate'; +export * from './SetWorkSheetName'; +export * from './SetWorkSheetStatus'; +// @endindex diff --git a/packages/core/src/Sheets/Domain/Color.ts b/packages/core/src/Sheets/Domain/Color.ts new file mode 100644 index 0000000000..c739be1e6e --- /dev/null +++ b/packages/core/src/Sheets/Domain/Color.ts @@ -0,0 +1,416 @@ +import { ThemeColorType, ColorType, ThemeColors } from '../../Enum'; +import { Nullable } from '../../Shared'; +import { ColorBuilder } from './ColorBuilder'; +import { THEME_COLOR_MAP } from '../../Const/THEME_COLOR_MAP'; + +export class Color { + static rgbColorToHexValue(color: RgbColor): string { + return `#${( + (1 << 24) + + (color.getRed() << 16) + + (color.getGreen() << 8) + + color.getBlue() + ) + .toString(16) + .slice(1)}`; + } + + static hexValueToRgbColor(hexValue: string): RgbColor { + if (hexValue) { + if (hexValue.indexOf('#') > -1) { + hexValue = hexValue.substring(1); + } + } else { + hexValue = '#000000'; + } + let r = +`0x${hexValue[0]}${hexValue[1]}`; + let g = +`0x${hexValue[2]}${hexValue[3]}`; + let b = +`0x${hexValue[4]}${hexValue[5]}`; + return new ColorBuilder().setRgbColor(`rgb(${r},${g},${b})`).asRgbColor(); + } + + static hexToRgbString(hex: string): Nullable { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b); + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + let string = null; + if (result) { + const r = parseInt(result[1], 16); + const g = parseInt(result[2], 16); + const b = parseInt(result[3], 16); + string = `rgba(${r},${g},${b})`; + } + return string; + } + + protected _builder: ColorBuilder; + + constructor(builder: ColorBuilder) { + this._builder = builder; + } + + asRgbColor(): RgbColor { + return this._builder.asRgbColor(); + } + + asThemeColor(): ThemeColor { + return this._builder.asThemeColor(); + } + + getColorType(): ColorType { + return this._builder.getColorType(); + } + + clone(): Color { + return new Color(this._builder); + } + + equals(color: Color): boolean { + return false; + } +} + +export class HLSColor { + private _saturation: number; + + private _hue: number; + + private _lightness: number; + + private _alpha: number; + + constructor(rgbColor: RgbColor) { + const red = rgbColor.getRed() / 255; + const green = rgbColor.getGreen() / 255; + const blue = rgbColor.getBlue() / 255; + const alpha = rgbColor.getAlpha() / 255; + + const min = Math.min(red, Math.min(green, blue)); + const max = Math.max(red, Math.max(green, blue)); + const delta = max - min; + + if (max === min) { + this._hue = 0; + this._saturation = 0; + this._lightness = max; + return; + } + + this._lightness = (min + max) / 2; + + if (this._lightness < 0.5) { + this._saturation = delta / (max + min); + } else { + this._saturation = delta / (2.0 - max - min); + } + + if (red === max) { + this._hue = (green - blue) / delta; + } + + if (green === max) { + this._hue = 2.0 + (blue - red) / delta; + } + + if (blue === max) { + this._hue = 4.0 + (red - green) / delta; + } + + this._hue *= 60; + + if (this._hue < 0) { + this._hue += 360; + } + + this._alpha = alpha; + } + + asRgbColor(): RgbColor { + const builder = new ColorBuilder(); + + if (this._saturation === 0) { + builder.setRgbColor( + `rgba(${this._lightness * 255},${this._lightness * 255},${ + this._lightness * 255 + },${this._alpha * 255})` + ); + return builder.asRgbColor(); + } + + let t1; + if (this._lightness < 0.5) { + t1 = this._lightness * (1.0 + this._saturation); + } else { + t1 = + this._lightness + + this._saturation - + this._lightness * this._saturation; + } + + const t2 = 2.0 * this._lightness - t1; + const hue = this._hue / 360; + + const tR = hue + 1.0 / 3.0; + const red = this.setColor(t1, t2, tR); + const green = this.setColor(t1, t2, hue); + const tB = hue - 1.0 / 3.0; + const blue = this.setColor(t1, t2, tB); + + builder.setRgbColor( + `rgba(${Math.round(red * 255)},${Math.round(green * 255)},${Math.round( + blue * 255 + )},${this._alpha * 255})` + ); + return builder.asRgbColor(); + } + + getLightness() { + return this._lightness; + } + + getHue() { + return this._hue; + } + + getSaturation() { + return this._saturation; + } + + getAlpha() { + return this._alpha; + } + + setColor(t1: number, t2: number, t3: number): number { + if (t3 < 0) { + t3 += 1.0; + } + + if (t3 > 1) { + t3 -= 1.0; + } + + let color: number; + + if (6.0 * t3 < 1) { + color = t2 + (t1 - t2) * 6.0 * t3; + } else if (2.0 * t3 < 1) { + color = t1; + } else if (3.0 * t3 < 2) { + color = t2 + (t1 - t2) * (2.0 / 3.0 - t3) * 6.0; + } else { + color = t2; + } + + return color; + } + + setLightness(lightness: number): void { + this._lightness = lightness; + } +} + +export class RgbColor extends Color { + static RGBA_EXTRACT: RegExp = new RegExp( + `\\s*rgba\\s*\\((\\s*\\d+\\s*),(\\s*\\d+\\s*),(\\s*\\d+\\s*),(\\s*\\d.\\d|\\d\\s*)\\)\\s*` + ); + + static RGB_EXTRACT: RegExp = new RegExp( + `\\s*rgb\\s*\\((\\s*\\d+\\s*),(\\s*\\d+\\s*),(\\s*\\d+\\s*)\\)\\s*` + ); + + private _cssString: string; + + private _red: number; + + private _green: number; + + private _blue: number; + + private _alpha: number; + + constructor(cssString: string, builder: ColorBuilder) { + super(builder); + + let match = cssString.match(RgbColor.RGBA_EXTRACT); + + if (match) { + const red = +match[1]; + const green = +match[2]; + const blue = +match[3]; + const alpha = +match[4]; + + this._cssString = cssString; + this._red = red; + this._green = green; + this._blue = blue; + this._alpha = alpha; + return; + } + + match = cssString.match(RgbColor.RGB_EXTRACT); + + if (match) { + const red = +match[1]; + const green = +match[2]; + const blue = +match[3]; + + this._cssString = cssString; + this._red = red; + this._green = green; + this._blue = blue; + this._alpha = 1; + return; + } + + throw new Error('Invalid rgba or rgb color'); + } + + asHexString(): string { + return Color.rgbColorToHexValue(this); + } + + getRed(): number { + return this._red; + } + + getGreen(): number { + return this._green; + } + + getBlue(): number { + return this._blue; + } + + getAlpha(): number { + return this._alpha; + } + + getColorType(): ColorType { + return ColorType.RGB; + } + + clone(): RgbColor { + return new RgbColor(this._cssString, this._builder); + } + + asThemeColor(): ThemeColor { + throw new Error('rgb color not support to themeColor'); + } + + equals(color: Color): boolean { + if (color instanceof RgbColor) { + return ( + color._red === this._red && + color._blue === this._blue && + color._green === this._green && + color._alpha === this._alpha + ); + } + return false; + } + + getCssString() { + return this._cssString; + } +} + +export class ThemeColor extends Color { + private static _cacheThemeColor = new Map< + ThemeColors, + Map + >(); + + private _themeColorType: ThemeColorType; + + private _themeTint: number; + + private _themeColors: ThemeColors; + + constructor( + theme: ThemeColorType, + themeTint: number, + themeColors: ThemeColors, + builder: ColorBuilder + ) { + super(builder); + this._themeColorType = theme; + this._themeTint = themeTint; + this._themeColors = themeColors; + } + + lumValue(tint: number, lum: number) { + if (tint == null) { + return lum; + } + + let value: number; + + if (tint < 0) { + value = lum * (1.0 + tint); + } else { + value = lum * (1.0 - tint) + (255 - 255 * (1.0 - tint)); + } + + return value; + } + + asRgbColor(): RgbColor { + const themeColors = THEME_COLOR_MAP.get(this._themeColors); + if (themeColors == null) { + throw new Error('not find themeColors type'); + } + + const hexValue = themeColors.get(this._themeColorType); + if (hexValue == null) { + throw new Error('not find themeColors value'); + } + + let themeCache; + if (ThemeColor._cacheThemeColor.has(this._themeColors)) { + themeCache = ThemeColor._cacheThemeColor.get(this._themeColors) as Map< + ThemeColorType, + RgbColor + >; + if (themeCache.has(this._themeColorType)) { + return themeCache.get(this._themeColorType) as RgbColor; + } + } else { + themeCache = new Map(); + ThemeColor._cacheThemeColor.set(this._themeColors, themeCache); + } + + const hlsColor = new HLSColor(Color.hexValueToRgbColor(hexValue)); + hlsColor.setLightness( + this.lumValue(this._themeTint, hlsColor.getLightness() * 255) / 255 + ); + const rgbColor = hlsColor.asRgbColor(); + themeCache.set(this._themeColorType, rgbColor); + + return rgbColor; + } + + clone(): ThemeColor { + return new ThemeColor( + this._themeColorType, + this._themeTint, + this._themeColors, + this._builder + ); + } + + equals(color: Color): boolean { + if (color instanceof ThemeColor) { + return color._themeColorType === this._themeColorType; + } + return false; + } + + getColorType(): ColorType { + return ColorType.THEME; + } + + getThemeColorType(): ThemeColorType { + return this._themeColorType; + } +} diff --git a/packages/core/src/Sheets/Domain/ColorBuilder.ts b/packages/core/src/Sheets/Domain/ColorBuilder.ts new file mode 100644 index 0000000000..48c8d63c46 --- /dev/null +++ b/packages/core/src/Sheets/Domain/ColorBuilder.ts @@ -0,0 +1,74 @@ +import { ColorType, ThemeColors, ThemeColorType } from '../../Enum'; +import { Nullable } from '../../Shared'; +import { Color, RgbColor, ThemeColor } from './Color'; + +export class ColorBuilder { + private _themeValue: ThemeColorType; + + private _themeColors: ThemeColors; + + private _themeTint: number; + + private _rgbValue: string; + + private _colorType: ColorType; + + constructor() { + this._colorType = ColorType.UNSUPPORTED; + this._themeColors = ThemeColors.OFFICE; + this._themeTint = 0; + } + + asRgbColor(): RgbColor { + return new RgbColor(this._rgbValue, this); + } + + asThemeColor(): ThemeColor { + return new ThemeColor( + this._themeValue, + this._themeTint, + this._themeColors, + this + ); + } + + build(): Nullable { + switch (this._colorType) { + case ColorType.THEME: { + return this.asThemeColor(); + } + case ColorType.RGB: { + return this.asRgbColor(); + } + case ColorType.UNSUPPORTED: { + throw Error('unsupported color type'); + } + } + } + + setRgbColor(cssString: string): ColorBuilder { + this._colorType = ColorType.RGB; + this._rgbValue = cssString; + return this; + } + + setThemeColors(value: ThemeColors) { + this._colorType = ColorType.THEME; + this._themeColors = value; + } + + setThemeTint(value: number) { + this._colorType = ColorType.THEME; + this._themeTint = value; + } + + setThemeColor(theme: ThemeColorType): ColorBuilder { + this._colorType = ColorType.THEME; + this._themeValue = theme; + return this; + } + + getColorType(): ColorType { + return this._colorType; + } +} diff --git a/packages/core/src/Sheets/Domain/ColumnManager.ts b/packages/core/src/Sheets/Domain/ColumnManager.ts new file mode 100644 index 0000000000..43b9c12897 --- /dev/null +++ b/packages/core/src/Sheets/Domain/ColumnManager.ts @@ -0,0 +1,136 @@ +import { Command } from '../../Command'; +import { ISetColumnWidthActionData } from '../Action'; +import { ACTION_NAMES } from '../../Const'; +import { BooleanNumber } from '../../Enum'; +import { IColumnData } from '../../Interfaces'; +import { Nullable } from '../../Shared'; +import { ObjectArray, ObjectArrayType } from '../../Shared/ObjectArray'; +import { WorkSheet } from './WorkSheet'; + +/** + * Manage configuration information of all columns, get column width, column length, set column width, etc. + */ +export class ColumnManager { + private _columnData: ObjectArray; + + private _workSheet: WorkSheet; + + constructor(workSheet: WorkSheet, data: ObjectArrayType>) { + this._workSheet = workSheet; + this._columnData = new ObjectArray(data); + } + + /** + * Get width and hidden status of columns in the sheet + * @returns + */ + getColumnData(): ObjectArray { + return this._columnData; + } + + getColumnDatas(columnPos: number, numColumns: number): ObjectArray { + const columnData = new ObjectArray(); + for (let i = columnPos; i < columnPos + numColumns; i++) { + const data = this.getColumnOrCreate(i); + columnData.push(data); + } + return columnData; + } + + /** + * Get count of column in the sheet + * @returns + */ + getSize(): number { + return this._columnData.getLength(); + } + + /** + * Get the width of column + * @param columnPos column index + * @returns + */ + getColumnWidth(columnPos: number): number; + /** + * Get the width of column + * @param columnPos column index + * @param numColumns column count + * @returns + */ + getColumnWidth(columnPos: number, numColumns: number): number; + getColumnWidth(...argument: any): number { + const { _workSheet } = this; + const { _columnData } = this; + const config = _workSheet.getConfig(); + let width: number = 0; + if (argument.length === 1) { + const column = _columnData.obtain(argument[0], { + hd: BooleanNumber.FALSE, + w: config.defaultColumnWidth, + }); + width = column.w; + } else if (argument.length === 2) { + for (let i = argument[0]; i < argument[0] + argument[1]; i++) { + const column = _columnData.obtain(i, { + hd: BooleanNumber.FALSE, + w: config.defaultColumnWidth, + }); + width += column.w; + } + } + + return width; + } + + /** + * set one or more column width + * @param columnIndex column index + * @param columnWidth column width Array + * @returns + */ + setColumnWidth(columnIndex: number, columnWidth: number[]): void { + const _context = this._workSheet.getContext(); + const _commandManager = this._workSheet.getCommandManager(); + const setColumnWidth: ISetColumnWidthActionData = { + sheetId: this._workSheet.getSheetId(), + actionName: ACTION_NAMES.SET_COLUMN_WIDTH_ACTION, + columnIndex, + columnWidth, + }; + const command = new Command(_context.getWorkBook(), setColumnWidth); + _commandManager.invoke(command); + } + + /** + * get given column data + * @param columnPos column index + * @returns + */ + getColumn(columnPos: number): Nullable { + const column = this._columnData.get(columnPos); + if (column) { + return column; + } + } + + /** + * get given column data or create a column data when it's null + * @param columnPos column index + * @returns + */ + getColumnOrCreate(columnPos: number): IColumnData { + const { _workSheet } = this; + const { _columnData } = this; + const config = _workSheet.getConfig(); + const column = _columnData.get(columnPos); + if (column) { + return column; + } + const create = { + w: config.defaultColumnWidth, + hd: BooleanNumber.FALSE, + }; + this._columnData.set(columnPos, create); + return create; + } +} diff --git a/packages/core/src/Sheets/Domain/Freeze.ts b/packages/core/src/Sheets/Domain/Freeze.ts new file mode 100644 index 0000000000..259f99851f --- /dev/null +++ b/packages/core/src/Sheets/Domain/Freeze.ts @@ -0,0 +1,44 @@ +import { Context } from '../../Basics'; +import { Command, CommandManager } from '../../Command'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { WorkSheet } from './WorkSheet'; + +export class Freeze { + private _commandManager: CommandManager; + + private _context: Context; + + private _worksheet: WorkSheet; + + constructor(workSheet: WorkSheet) { + this._context = workSheet.getContext(); + this._commandManager = this._context.getCommandManager(); + this._worksheet = workSheet; + } + + setFrozenColumns(columns: number): WorkSheet { + const { _context, _commandManager } = this; + const configure = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: this._worksheet.getSheetId(), + numColumns: columns, + }; + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + + return this._worksheet; + } + + setFrozenRows(rows: number): WorkSheet { + const { _context, _commandManager } = this; + const configure = { + actionName: ACTION_NAMES.SET_FROZEN_ROWS_ACTION, + sheetId: this._worksheet.getSheetId(), + numRows: rows, + }; + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + + return this._worksheet; + } +} diff --git a/packages/core/src/Sheets/Domain/Group.ts b/packages/core/src/Sheets/Domain/Group.ts new file mode 100644 index 0000000000..43fab8ca91 --- /dev/null +++ b/packages/core/src/Sheets/Domain/Group.ts @@ -0,0 +1,115 @@ +import { Nullable } from '../../Shared'; + +/** + * + * Access and modify spreadsheet groups. + * + * @remarks + * Groups are an association between an interval of contiguous rows or columns that can be expanded or collapsed as a unit to hide/show the rows or columns. Each group has a control toggle on the row or column directly before or after the group (depending on settings) that can expand or collapse the group as a whole. + +The depth of a group refers to the nested position of the group and how many larger groups contain the group. The collapsed state of a group refers to whether the group should remain collapsed or expanded after a parent group has been expanded. Additionally, at the time that a group is collapsed or expanded, the rows or columns within the group are hidden or set visible, though individual rows or columns can be hidden or set visible irrespective of the collapsed state. + + * @privateRemarks + * zh: 线段组 + */ +export class Group { + expand: boolean; + + begin: number; + + close: number; + + constructor(begin: number, close: number, expand: boolean) { + this.begin = begin; + this.close = close; + this.expand = expand; + } +} + +/** + * + * group at a depth + * + * @privateRemarks + * zh: 组层级 + */ +export class LayoutGroup { + depth: number; + + group: Group[]; + + constructor(depth: number, group: Group[] = []) { + this.depth = depth; + this.group = group; + } + + addGroup(group: Group) { + this.group.push(group); + } +} + +/** + * + * List of all groups + * + * @privateRemarks + * zh: 组列表 + */ +export class GroupList { + private _groups: LayoutGroup[]; + + /** + * GroupList + */ + constructor() { + this._groups = []; + } + + /** + * find a specific depth level group + * + * @privateRemarks + * zh: 查找指定深度层级分组 + * + * @param depth + * @returns + */ + getLayoutGroupByDepth(depth: number): Nullable { + for (let i = 0, len = this._groups.length; i < len; i++) { + const group = this._groups[i]; + if (group.depth === depth) { + return group; + } + } + return null; + } + + /** + * delete the specified group + * + * @privateRemarks + * zh: 删除指定分组 + * + * @param begin + * @param close + * @param depth + */ + remove(begin: number, close: number, depth: number) { + const layoutGroup = this.getLayoutGroupByDepth(depth); + // TODO ... + } + + addGroup(group: Group, depth: number) { + const layoutGroup = this.getLayoutGroupByDepth(depth); + if (layoutGroup) { + layoutGroup.addGroup(group); + } else { + // TODO depth = 1 ? + const newLayoutGroup = new LayoutGroup(1); + newLayoutGroup.addGroup(group); + this._groups.push(newLayoutGroup); + } + } + + // overlapping(src: Group): Group {} +} diff --git a/packages/core/src/Sheets/Domain/Merges.ts b/packages/core/src/Sheets/Domain/Merges.ts new file mode 100644 index 0000000000..314ae68481 --- /dev/null +++ b/packages/core/src/Sheets/Domain/Merges.ts @@ -0,0 +1,332 @@ +import { + IAddMergeActionData, + CommandManager, + InsertRowAction, + IRemoveMergeActionData, + Command, + RemoveRowAction, + DeleteRangeAction, +} from '../../Command'; +import { InsertColumnAction, RemoveColumnAction } from '../Action'; +import { ACTION_NAMES } from '../../Const'; +import { IRangeData } from '../../Interfaces'; +import { WorkSheet } from './WorkSheet'; +import { Nullable, Tools } from '../../Shared'; +import { Rectangle } from '../../Shared/Rectangle'; +import { Tuples } from '../../Shared/Tuples'; + +/** + * Manage merged cells + */ +export class Merges { + private _rectangleList: IRangeData[]; + + private _worksheet: WorkSheet; + + constructor(worksheet: WorkSheet, mergeData: any) { + this._worksheet = worksheet; + this._rectangleList = mergeData; + + CommandManager.getCommandInjectorObservers().add((inject) => { + let insertRowAction = inject.include(InsertRowAction); + let insertColumnAction = inject.include(InsertColumnAction); + let deleteRowAction = inject.include(RemoveRowAction); + let deleteColumnAction = inject.include(RemoveColumnAction); + let deleteRangeAction = inject.include(DeleteRangeAction); + + if (insertRowAction) { + const data = insertRowAction.getDoActionData(); + if (data.sheetId === this._worksheet.getSheetId()) { + const rectangleList = Tools.deepClone(this._rectangleList); + for (let i = 0; i < rectangleList.length; i++) { + const merge = rectangleList[i]; + const count = data.rowCount; + if (data.rowIndex > merge.endRow) { + continue; + } else if ( + data.rowIndex >= merge.startRow && + data.rowIndex <= merge.endRow + ) { + merge.endRow += count; + } else { + merge.startRow += count; + merge.endRow += count; + } + } + this.modifyMerge(this._rectangleList, rectangleList); + } + } + if (insertColumnAction) { + const data = insertColumnAction.getDoActionData(); + if (data.sheetId === this._worksheet.getSheetId()) { + const rectangleList = Tools.deepClone(this._rectangleList); + for (let i = 0; i < rectangleList.length; i++) { + const merge = rectangleList[i]; + const count = data.columnCount; + if (data.columnIndex > merge.endColumn) { + continue; + } else if ( + data.columnIndex >= merge.startColumn && + data.columnIndex <= merge.endColumn + ) { + merge.endColumn += count; + } else { + merge.startColumn += count; + merge.endColumn += count; + } + } + this.modifyMerge(this._rectangleList, rectangleList); + } + } + if (deleteRowAction) { + const data = deleteRowAction.getDoActionData(); + if (data.sheetId === this._worksheet.getSheetId()) { + if (data.sheetId === this._worksheet.getSheetId()) { + const rectangleList = Tools.deepClone(this._rectangleList); + for (let i = 0; i < rectangleList.length; i++) { + const merge = rectangleList[i]; + const count = data.rowCount; + if (data.rowIndex > merge.endRow) { + continue; + } else if ( + data.rowIndex >= merge.startRow && + data.rowIndex <= merge.endRow + ) { + merge.endRow -= count; + } else { + merge.startRow -= count; + merge.endRow -= count; + } + } + this.modifyMerge(this._rectangleList, rectangleList); + } + } + } + if (deleteColumnAction) { + const data = deleteColumnAction.getDoActionData(); + if (data.sheetId === this._worksheet.getSheetId()) { + if (data.sheetId === this._worksheet.getSheetId()) { + const rectangleList = Tools.deepClone(this._rectangleList); + for (let i = 0; i < rectangleList.length; i++) { + const merge = rectangleList[i]; + const count = data.columnCount; + if (data.columnIndex > merge.endColumn) { + continue; + } else if ( + data.columnIndex >= merge.startColumn && + data.columnIndex <= merge.endColumn + ) { + merge.endColumn -= count; + } else { + merge.startColumn -= count; + merge.endColumn -= count; + } + } + this.modifyMerge(this._rectangleList, rectangleList); + } + } + } + if (deleteRangeAction) { + const data = deleteRangeAction.getDoActionData(); + if (data.sheetId === this._worksheet.getSheetId()) { + const rectangleList = Tools.deepClone(this._rectangleList); + const hasMerge = this.getByRowColumn( + data.rangeData.startRow, + data.rangeData.endRow, + data.rangeData.startColumn, + data.rangeData.endColumn + ); + if (hasMerge) { + hasMerge.forEach((item) => { + const target = new Rectangle(item); + for (let i = 0; i < rectangleList.length; i++) { + const current = rectangleList[i]; + if (target.intersects(new Rectangle(current))) { + rectangleList.splice(i, 1); + } + } + }); + } + // 单元格上移 + if (data.shiftDimension) { + for (let i = 0; i < rectangleList.length; i++) { + const merge = rectangleList[i]; + if (merge.endRow >= data.rangeData.startRow) { + if (merge.endColumn < data.rangeData.startColumn) { + continue; + } else if ( + merge.startColumn > data.rangeData.endColumn + ) { + continue; + } else if ( + merge.startColumn >= + data.rangeData.startColumn && + merge.endColumn <= data.rangeData.endColumn + ) { + const count = + data.rangeData.endRow - + data.rangeData.startRow; + + merge.startRow -= count; + merge.endRow -= count; + } else { + return; + } + } + } + } else { + // 单元格左移 + for (let i = 0; i < rectangleList.length; i++) { + const merge = rectangleList[i]; + if (merge.startColumn > data.rangeData.endColumn) { + if (merge.endRow < data.rangeData.startRow) { + continue; + } else if (merge.startRow > data.rangeData.endRow) { + continue; + } else if ( + merge.startRow >= data.rangeData.startRow && + merge.endRow <= data.rangeData.endRow + ) { + const count = + data.rangeData.endColumn - + data.rangeData.startColumn; + + merge.startColumn -= count; + merge.endColumn -= count; + } else { + return; + } + } + } + } + + this.modifyMerge(this._rectangleList, rectangleList); + } + } + }); + } + + getMergeData(): IRangeData[] { + return this._rectangleList; + } + + getByRowColumn( + startRow: number, + endRow: number, + startColumn: number, + endColumn: number + ): Nullable; + getByRowColumn(row: number, column: number): Nullable; + getByRowColumn(...argument: any): Nullable { + const { _rectangleList } = this; + let target: Rectangle; + if ( + Tuples.checkup( + argument, + Tuples.NUMBER_TYPE, + Tuples.NUMBER_TYPE, + Tuples.NUMBER_TYPE, + Tuples.NUMBER_TYPE + ) + ) { + target = new Rectangle( + argument[0], + argument[2], + argument[1], + argument[3] + ); + } else if ( + Tuples.checkup(argument, Tuples.NUMBER_TYPE, Tuples.NUMBER_TYPE) + ) { + target = new Rectangle( + argument[0], + argument[1], + argument[0], + argument[1] + ); + } + const rectList = []; + for (let i = 0; i < _rectangleList.length; i++) { + const rectangle = _rectangleList[i]; + if (target!.intersects(new Rectangle(rectangle))) { + // return rectangle; + rectList.push(rectangle); + } + } + + return rectList.length ? rectList : null; + } + + remove(rectangle: IRangeData): IRangeData[] { + const { _rectangleList } = this; + const { length } = _rectangleList; + const target = new Rectangle(rectangle); + const result = []; + const remove = []; + for (let i = 0; i < length; i++) { + const current = _rectangleList[i]; + if (target.intersects(new Rectangle(current))) { + remove.push(current); + continue; + } + result.push(current); + } + this._rectangleList = result; + return remove; + } + + size() { + return this._rectangleList.length; + } + + add(rectangle: IRangeData): IRangeData[] { + const result = this.remove(rectangle); + this._rectangleList.push(rectangle); + return result; + } + + union(rectangle: IRangeData): IRangeData { + const { _rectangleList } = this; + const { length } = _rectangleList; + let target = new Rectangle(rectangle); + for (let i = 0; i < length; i++) { + const element = _rectangleList[i]; + target = target.union(new Rectangle(element)); + } + return target.getData(); + } + + getMergedRanges(rectangle: IRangeData): IRangeData[] { + const { _rectangleList } = this; + const { length } = _rectangleList; + const result = []; + for (let i = 0; i < length; i++) { + const item = _rectangleList[i]; + const target = new Rectangle(rectangle); + if (target.intersects(new Rectangle(item))) { + result.push(item); + } + } + return result; + } + + modifyMerge(originMerge: IRangeData[], currentMerge: IRangeData[]) { + const commandManager = this._worksheet.getCommandManager(); + const context = this._worksheet.getContext(); + + const removeMerge: IRemoveMergeActionData = { + actionName: ACTION_NAMES.REMOVE_MERGE_ACTION, + sheetId: this._worksheet.getSheetId(), + rectangles: originMerge, + }; + + const addMerge: IAddMergeActionData = { + actionName: ACTION_NAMES.ADD_MERGE_ACTION, + sheetId: this._worksheet.getSheetId(), + rectangles: currentMerge, + }; + + const command = new Command(context.getWorkBook(), removeMerge, addMerge); + commandManager.invoke(command); + } +} diff --git a/packages/core/src/Sheets/Domain/Range.ts b/packages/core/src/Sheets/Domain/Range.ts new file mode 100644 index 0000000000..16e8edee1f --- /dev/null +++ b/packages/core/src/Sheets/Domain/Range.ts @@ -0,0 +1,3850 @@ +// import { nanoid } from 'nanoid'; +import { Context } from '../../Basics'; +import { + Command, + CommandManager, + IActionData, + IAddMergeActionData, + IClearRangeActionData, + IDeleteRangeActionData, + IInsertRangeActionData, + IRemoveMergeActionData, + ISetRangeDataActionData, + ISetRangeFormulaActionData, + ISetRangeStyleActionData, + ISetRangeValueActionData, +} from '../../Command'; +import { DEFAULT_RANGE, DEFAULT_STYLES } from '../../Const'; +import { ACTION_NAMES } from '../../Const/ACTION_NAMES'; +import { + AutoFillSeries, + BooleanNumber, + BorderStyleTypes, + BorderType, + Dimension, + Direction, + FontItalic, + FontWeight, + HorizontalAlign, + TextDirection, + VerticalAlign, + WrapStrategy, +} from '../../Enum'; +import { CopyPasteType } from '../../Enum/CopyPasteType'; +import { + IBorderStyleData, + ICellData, + ICellDataMatrix, + ICellV, + IDocumentData, + IOptionData, + IOptionsData, + IRangeData, + IRangeType, + IStyleData, + ITextDecoration, +} from '../../Interfaces'; +import { + Nullable, + ObjectArrayPrimitiveType, + ObjectMatrix, + Tools, + Tuples, +} from '../../Shared'; +import { DropCell } from '../../Shared/DropCell'; +import { WorkSheet } from './WorkSheet'; + +/** + * getObjectValues options type + */ +type IValueOptionsType = { + /** + * set whether to include style + */ + isIncludeStyle?: boolean; +}; + +/** + * Access and modify spreadsheet ranges. + * + * @remarks + * A range can be a single cell in a sheet or a group of adjacent cells in a sheet. + * + * Reference from: https://developers.google.com/apps-script/reference/spreadsheet/range + * + * @beta + */ +export class Range { + private _commandManager: CommandManager; + + private _context: Context; + + private _rangeData: IRangeData; + + private _worksheet: WorkSheet; + + constructor(workSheet: WorkSheet, range: IRangeType) { + this._context = workSheet.getContext(); + this._commandManager = this._context.getCommandManager(); + + // Convert the range passed in by the user into a standard format + // this._rangeData = new TransformTool( + // this._context.getWorkBook() + // ).transformRangeType(range); + this._rangeData = this._context + .getWorkBook() + .transformRangeType(range).rangeData; + this._worksheet = workSheet; + + // The user entered an invalid range + if (Object.values(this._rangeData).includes(-1)) { + console.error('Invalid range,default set index -1'); + } + } + + /** + * get current range data + * + * @returns current range + */ + getRangeData(): IRangeData { + return this._rangeData; + } + + /** + * Returns the value of the top-left cell in the range. The value may be of type Number, Boolean, Date, or String depending on the value of the cell. Empty cells return an empty string. + * @returns The value in this cell + */ + getValue(): Nullable { + return this.getValues()[0][0]; + } + + /** + * Returns the rectangular grid of values for this range. + * + * Returns a two-dimensional array of values, indexed by row, then by column. The values may be of type Number, Boolean, Date, or String, depending on the value of the cell. Empty cells are represented by an empty string in the array. Remember that while a range index starts at 0, 0, same as the JavaScript array is indexed from [0][0]. + * + * In web apps, a Date value isn't a legal parameter. getValues() fails to return data to a web app if the range contains a cell with a Date value. Instead, transform all the values retrieved from the sheet to a supported JavaScript primitive like a Number, Boolean, or String. + * + * @returns A two-dimensional array of values. + */ + getValues(): Array>> { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const range: Array>> = []; + for (let r = startRow; r <= endRow; r++) { + const row: Array> = []; + for (let c = startColumn; c <= endColumn; c++) { + row.push(this.getMatrix().getValue(r, c) || null); + } + range.push(row); + } + return range; + } + + /** + * get range matrix + * + * @returns range matrix + */ + getMatrix(): ObjectMatrix { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const sheetMatrix = this._worksheet.getCellMatrix(); + const rangeMatrix = new ObjectMatrix(); + + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + rangeMatrix.setValue(r, c, sheetMatrix.getValue(r, c) || null); + } + } + + return rangeMatrix; + } + + /** + * get range matrix object + * + * @returns range matrix object + */ + getMatrixObject(): ObjectMatrix { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const sheetMatrix = this._worksheet.getCellMatrix(); + const rangeMatrix = new ObjectMatrix(); + + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + rangeMatrix.setValue( + r - startRow, + c - startColumn, + sheetMatrix.getValue(r, c) || {} + ); + } + } + + return rangeMatrix; + } + + /** + * Returns the displayed value of the top-left cell in the range. The value is a String. The displayed value takes into account date, time and currency formatting formatting, including formats applied automatically by the spreadsheet's Locale setting. Empty cells return an empty string. + * + * @returns The displayed value in this cell. + */ + getDisplayValue(): string | IDocumentData { + const value = this.getValue(); + if (value && value.p) { + return value.p; + } + if (value && value.m) { + return value.m; + } + return ''; + } + + /** + * Returns the rectangular grid of values for this range. + * + * Returns a two-dimensional array of displayed values, indexed by row, then by column. The values are String objects. The displayed value takes into account date, time and currency formatting, including formats applied automatically by the spreadsheet's Locale setting. Empty cells are represented by an empty string in the array. Remember that while a range index starts at 0, 0, same as the JavaScript array is indexed from [0][0]. + * + * @returns — A two-dimensional array of values. + */ + getDisplayValues(): Array> { + return this.getValues().map((row) => + row.map((value: Nullable) => { + if (value && value.p) { + return value.p; + } + if (value && value.m) { + return value.m; + } + return ''; + }) + ); + } + + /** + * Returns a string description of the range, in A1 notation. + * + * @returns The string description of the range in A1 notation. + */ + getA1Notation(): string { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + let start; + let end; + if (startColumn < endColumn) { + start = Tools.numToWord(startColumn + 1) + (startRow + 1); + end = Tools.numToWord(endColumn + 1) + (endRow + 1); + } else { + start = Tools.numToWord(endColumn + 1) + (endRow + 1); + end = Tools.numToWord(startColumn + 1) + (startRow + 1); + } + + if (start === end) return `${start}`; + return `${start}:${end}`; + } + + /** + * Returns the background color of the top-left cell in the range (for example, '#ffffff'). + * + * @returns — The color code of the background. + */ + getBackground(): string { + return this.getBackgrounds()[0][0]; + } + + /** + * Returns the background colors of the cells in the range (for example, '#ffffff'). + * + * @returns — A two-dimensional array of color codes of the backgrounds. + */ + getBackgrounds(): string[][] { + return this.getValues().map((row) => + row.map((cell: Nullable) => { + const styles = this._context.getWorkBook().getStyles(); + let rgbColor: string = DEFAULT_STYLES.bg?.rgb!; + + if (cell?.s) { + rgbColor = styles.get(cell?.s)?.bg?.rgb!; + } + + return rgbColor; + }) + ); + } + + /** + * Returns a given cell within a range. + * + * The row and column here are relative to the range + * e.g. "B2:D4", getCell(0,0) in this code returns the cell at B2 + * @returns — A range containing a single cell at the specified coordinates. + */ + getCell(row: number, column: number): Range { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const cell = { + startRow: startRow + row, + endRow: startRow + row, + startColumn: startColumn + column, + endColumn: startColumn + column, + }; + + return new Range(this._worksheet, cell); + } + + /** + * Returns the starting column position for this range + * + * @returns — The range's starting column position in the spreadsheet. + */ + getColumn(): number { + return this._rangeData.startColumn; + } + + /** + * Returns the data of the object structure, and can set whether to bring styles + */ + getObjectValue(options: IValueOptionsType = {}): ICellData { + return this.getObjectValues(options)[0][0]; + } + + /** + * Returns the data of the object structure, and can set whether to bring styles + * + * @param options set whether to include style + * @returns Returns a value in object format + */ + getObjectValues( + options: IValueOptionsType = {} + ): ObjectArrayPrimitiveType { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // get object values from sheet matrix, or use this.getMatrix() create a new matrix then this.getMatrix().getData() + const values = this._worksheet + .getCellMatrix() + .getFragments(startRow, endRow, startColumn, endColumn) + .getData(); + + if (options.isIncludeStyle) { + const style = this._context.getWorkBook().getStyles(); + for (let r = 0; r <= endRow - startRow; r++) { + for (let c = 0; c <= endColumn - startColumn; c++) { + // handle null + if (!values[r][c]) continue; + + const s = values[r][c].s; + + // make sure value has style + if (s) { + values[r][c].s = style.get(s); + } + } + } + } + + return values; + } + + /** + * Notify other Components + * TODO: 待触发更新测试 + */ + // notifyUpdated() { + // this._context.onAfterChangeRangeDataObservable.notifyObservers( + // this._worksheet + // ); + // } + + /** + * Returns the font color of the cell in the top-left corner of the range, in CSS notation + */ + getFontColor(): Nullable { + return this.getFontColors()[0][0]; + } + + /** + * Returns the font colors of the cells in the range in CSS notation (such as '#ffffff' or 'white'). + */ + getFontColors(): Array>> { + return this.getValues().map((row) => + row.map((cell: Nullable) => { + const styles = this._context.getWorkBook().getStyles(); + return ( + (cell?.s && styles.get(cell.s)?.cl?.rgb) || + DEFAULT_STYLES.cl?.rgb + ); + }) + ); + } + + /** + * Returns the font families of the cells in the range. + */ + getFontFamilies(): string[][] { + return this._getStyles('ff'); + } + + /** + * Returns the font family of the cell in the top-left corner of the range. + */ + getFontFamily(): string { + return this.getFontFamilies()[0][0]; + } + + /** + * Returns the underlines of the cells in the range. + */ + getUnderlines(): ITextDecoration[][] { + return this._getStyles('ul'); + } + + /** + * Returns the underline of the cells in the range. + */ + getUnderline(): ITextDecoration { + return this.getUnderlines()[0][0]; + } + + /** + * Returns the overlines of the cells in the range. + */ + getOverlines(): ITextDecoration[][] { + return this._getStyles('ol'); + } + + /** + * Returns the overline of the cells in the range. + */ + getOverline(): ITextDecoration { + return this.getOverlines()[0][0]; + } + + /** + * Returns the strikeThroughs of the cells in the range. + */ + getStrikeThroughs(): ITextDecoration[][] { + return this._getStyles('st'); + } + + /** + * Returns the strikeThrough of the cells in the range. + */ + getStrikeThrough(): ITextDecoration { + return this.getStrikeThroughs()[0][0]; + } + + /** + * + * @param arge Shorthand for the style that gets + * @returns style value + */ + private _getStyles(arge: string) { + return this.getValues().map((row) => + row.map((cell: Nullable) => { + const styles = this._context.getWorkBook().getStyles(); + return ( + (cell?.s && styles.get(cell.s)![arge]) || DEFAULT_STYLES[arge] + ); + }) + ); + } + + /** + * Returns the font size in point size of the cell in the top-left corner of the range. + */ + getFontSize(): number { + return this.getFontSizes()[0][0]; + } + + /** + * Returns the font sizes of the cells in the range. + */ + getFontSizes(): number[][] { + return this._getStyles('fs'); + } + + /** + * Returns the font style ('italic' or 'normal') of the cell in the top-left corner of the range. + */ + getFontStyle(): FontItalic { + return this.getFontStyles()[0][0]; + } + + /** + * Returns the font styles of the cells in the range. + */ + getFontStyles(): FontItalic[][] { + return this._getStyles('it'); + } + + /** + * Returns the font weight (normal/bold) of the cell in the top-left corner of the range. + */ + getFontWeight(): FontWeight { + return this.getFontWeights()[0][0]; + } + + /** + * Returns the font weights of the cells in the range. + */ + getFontWeights(): FontWeight[][] { + return this._getStyles('bl'); + } + + /** + * Returns the formula (A1 notation) for the top-left cell of the range, or an empty string if the cell is empty or doesn't contain a formula. + */ + // getFormula(): string { + // return this.getFormulas()[0][0]; + // } + + /** + * Returns the formulas (A1 notation) for the cells in the range. + */ + // getFormulas(): string[][] { + // return this.getValues().map((row) => + // row.map((cell: Nullable) => cell?.f || '') + // ); + // } + + /** + * Returns the grid ID of the range's parent sheet. + */ + getGridId(): string { + return this._worksheet.getSheetId(); + } + + /** + * Returns the height of the range. + */ + getHeight(): number { + const { _rangeData, _worksheet } = this; + const { startRow, endRow } = _rangeData; + let h = 0; + for (let i = 0; i <= endRow - startRow; i++) { + const hh = _worksheet.getRowHeight(i); + h += hh; + } + return h; + } + + /** + * Returns the horizontal alignment of the text (left/center/right) of the cell in the top-left corner of the range. + */ + getHorizontalAlignment(): HorizontalAlign { + return this.getHorizontalAlignments()[0][0]; + } + + /** + *Returns the horizontal alignments of the cells in the range. + */ + getHorizontalAlignments(): HorizontalAlign[][] { + return this._getStyles('ht'); + } + + /** + * Returns the end column position. + */ + getLastColumn(): number { + return this._rangeData.endColumn; + } + + /** + * Returns the end row position. + */ + getLastRow(): number { + return this._rangeData.endRow; + } + + /** + * Returns an array of Range objects representing merged cells that either are fully within the current range, or contain at least one cell in the current range. + */ + getMergedRanges(): Range[] { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + return this._worksheet + .getMerges() + .getMergedRanges({ startRow, endRow, startColumn, endColumn }) + .map((rangeData) => new Range(this._worksheet, rangeData)); + } + + /** + * Starting at the cell in the first column and row of the range, returns the next cell in the given direction that is the edge of a contiguous range of cells with data in them or the cell at the edge of the spreadsheet in that direction. + * @param direction + * @returns The data region edge cell or the cell at the edge of the spreadsheet. + */ + getNextDataCell(direction: Direction): Range { + const { _worksheet } = this; + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const maxRow = _worksheet.getMaxRows(); + const maxColumn = _worksheet.getMaxColumns(); + if (direction === Direction.BOTTOM) { + for (let i = 0; i < maxRow - startColumn; i++) { + const value = this._worksheet + .getCellMatrix() + .getValue(startRow + i, startColumn); + if (value) + _worksheet.getRange( + startRow + i, + startColumn, + startRow + i, + startColumn + ); + } + return _worksheet.getRange(maxRow, startColumn, maxRow, startColumn); + } + if (direction === Direction.TOP) { + for (let i = 0; i < startRow; i++) { + const value = this._worksheet + .getCellMatrix() + .getValue(startRow - i, startColumn); + if (value) + _worksheet.getRange( + startRow - i, + startColumn, + startRow - i, + startRow + ); + } + return _worksheet.getRange(0, startColumn, 0, startColumn); + } + if (direction === Direction.RIGHT) { + for (let i = 0; i < maxColumn - startColumn; i++) { + const value = this._worksheet + .getCellMatrix() + .getValue(startRow, startColumn + i); + if (value) + _worksheet.getRange( + startRow, + startColumn + i, + startRow, + startColumn + i + ); + } + return _worksheet.getRange(startRow, maxColumn, startRow, maxColumn); + } + if (direction === Direction.LEFT) { + for (let i = 0; i < maxRow - startColumn; i++) { + const value = this._worksheet + .getCellMatrix() + .getValue(startRow, startColumn - i); + if (value) + _worksheet.getRange( + startRow, + startColumn - i, + startRow, + startColumn - i + ); + } + return _worksheet.getRange(startRow, 0, startRow, 0); + } + return _worksheet.getRange(DEFAULT_RANGE); + } + + /** + * Returns the note associated with the given range. + */ + // getNote(): string { + // return this.getNotes()[0][0]; + // } + + /** + * Returns the notes associated with the cells in the range. + */ + // getNotes(): string[][] { + // return this.getValues().map((row) => + // row.map((cell: Nullable) => cell?.n || '') + // ); + // } + + /** + * Returns the number of columns in this range. + */ + getNumColumns(): number { + const { startColumn, endColumn } = this._rangeData; + return endColumn - startColumn + 1; + } + + /** + * Returns the number of rows in this range. + */ + getNumRows(): number { + const { startRow, endRow } = this._rangeData; + return endRow - startRow + 1; + } + + /** + * Get the number or date formatting of the top-left cell of the given range. + */ + // getNumberFormat(): string { + // return this.getNumberFormats()[0][0]; + // } + + /** + * Returns the number or date formats for the cells in the range. + */ + // getNumberFormats(): string[][] { + // return this.getValues().map((row) => + // row.map((cell: Nullable) => cell?.fm?.f || '') + // ); + // } + + /** + * Returns the Rich Text value for the top left cell of the range, or null if the cell value is not text. + */ + getRichTextValue(): Nullable { + return this.getRichTextValues()[0][0]; + } + + /** + * Returns the Rich Text values for the cells in the range. + */ + getRichTextValues(): Array>> { + return this.getValues().map((row) => + row.map((cell: Nullable) => cell?.p || '') + ); + } + + /** + * Returns the row position for this range. + */ + getRowIndex(): number { + return this._rangeData.startRow; + } + + /** + * Returns the sheet this range belongs to. + */ + getSheet(): WorkSheet { + return this._worksheet; + } + + /** + * Returns the text direction for the top left cell of the range. + */ + getTextDirection(): number { + return this.getTextDirections()[0][0]; + } + + /** + * Returns the text directions for the cells in the range. + */ + getTextDirections(): number[][] { + return this._getStyles('td'); + } + + /** + * Returns the text rotation settings for the top left cell of the range. + */ + getTextRotation(): number { + return this.getTextRotations()[0][0]; + } + + /** + * Returns the text rotation settings for the cells in the range. + */ + getTextRotations(): number[][] { + return this._getStyles('tr'); + } + + /** + * Returns the text style for the top left cell of the range. + */ + getTextStyle(): Nullable { + return this.getTextStyles()[0][0]; + } + + /** + * Returns the text styles for the cells in the range. + */ + getTextStyles(): Array>> { + return this.getValues().map((row) => + row.map((cell: Nullable) => { + const styles = this._context.getWorkBook().getStyles(); + if (cell && cell.s) { + return styles.get(cell.s); + } + return null; + }) + ); + } + + /** + * Returns the vertical alignment (top/middle/bottom) of the cell in the top-left corner of the range. + */ + getVerticalAlignment(): VerticalAlign { + return this.getVerticalAlignments()[0][0]; + } + + /** + * Returns the vertical alignments of the cells in the range. + */ + getVerticalAlignments(): VerticalAlign[][] { + return this._getStyles('vt'); + } + + /** + * Returns the width of the range in columns. + */ + getWidth(): number { + const { _rangeData, _worksheet } = this; + const { startColumn, endColumn } = _rangeData; + let w = 0; + for (let i = 0; i <= endColumn - startColumn; i++) { + w += _worksheet.getColumnWidth(i); + } + return w; + } + + /** + * Returns whether the text in the cell wraps. + */ + getWrap(): BooleanNumber { + return this.getWraps()[0][0]; + } + + /** + * Returns whether the text in the cells wrap. + */ + getWraps(): BooleanNumber[][] { + return this._getStyles('tb'); + } + + /** + * Returns the text wrapping strategies for the cells in the range. + */ + getWrapStrategies(): Array>> { + return this._getStyles('tb'); + } + + /** + * Returns the text wrapping strategy for the top left cell of the range. + */ + getWrapStrategy(): Nullable { + return this.getWrapStrategies()[0][0]; + } + + /** + * Returns true if the range is totally blank. + */ + isBlank(): boolean { + const data = this.getValues(); + return data.some((items) => items.some((item) => item?.m === '')); + } + + // isChecked() {} + // isEndColumnBounded() {} + // isEndRowBounded() {} + + /** + * Returns true if the cells in the current range overlap any merged cells. + */ + isPartOfMerge(): boolean { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const data = this._worksheet + .getMerges() + .getByRowColumn(startRow, endRow, startColumn, endColumn); + if (data) { + return true; + } + + return false; + } + + // isStartColumnBounded() {} + // isStartRowBounded() {} + + private _setStyle( + value: Nullable, + type: string + ) { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const styleObj = { [type]: value }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + private _setStyles( + values: Array>>, + type: string + ) { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const matrix = new ObjectMatrix(); + for (let r = 0; r < endRow - startRow + 1; r++) { + for (let c = 0; c < endColumn - startColumn + 1; c++) { + matrix.setValue(r, c, { [type]: values[r][c] }); + } + } + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: matrix.getData(), + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the background color of all cells in the range in CSS notation (such as '#ffffff' or 'white'). + * + * @param color Sets the background color of all cells in the range in CSS notation (such as '#ffffff' or 'white'). + * @returns This range, for chaining. + */ + setBackground(color: string): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + + const colorObj: IStyleData = { + bg: { + rgb: color, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + colorObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets a rectangular grid of background colors (must match dimensions of this range). The colors are in CSS notation (such as '#ffffff' or 'white'). + * + * @param color A two-dimensional array of colors in CSS notation (such as '#ffffff' or 'white'); null values reset the color. + * @returns This range, for chaining. + */ + setBackgrounds(color: string[][]): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const matrix = new ObjectMatrix(); + for (let r = 0; r < endRow - startRow + 1; r++) { + for (let c = 0; c < endColumn - startColumn + 1; c++) { + matrix.setValue(r, c, { + bg: { + rgb: color[r][c], + }, + }); + } + } + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: matrix.getData(), + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the background to the given color using RGB values (integers between 0 and 255 inclusive). + * + * @param red The red value in RGB notation. + * @param green The green value in RGB notation. + * @param blue The blue value in RGB notation. + * @returns This range, for chaining. + */ + setBackgroundRGB(red: number, green: number, blue: number): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const rgbString = `RGB(${red},${green},${blue})`; + + const colorObj: IStyleData = { + bg: { + rgb: rgbString, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + colorObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets a rectangular grid of font colors (must match dimensions of this range). The colors are in CSS notation (such as '#ffffff' or 'white'). + * + * @param colors A two-dimensional array of colors in CSS notation (such as '#ffffff' or 'white'); null values reset the color. + * @returns This range, for chaining. + */ + setFontColors(colors: string[][]): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const matrix = new ObjectMatrix(); + for (let r = 0; r < endRow - startRow + 1; r++) { + for (let c = 0; c < endColumn - startColumn + 1; c++) { + matrix.setValue(r, c, { cl: { rgb: colors[r][c] } }); + } + } + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: matrix.getData(), + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets a rectangular grid of font families (must match dimensions of this range). Examples of font families are "Arial" or "Helvetica". + * + * @param fontFamilies A two-dimensional array of font families; null values reset the font family. + * @returns This range, for chaining. + */ + setFontFamilies(fontFamilies: string[][]): Range { + this._setStyles(fontFamilies, 'ff'); + return this; + } + + /** + * Sets the font line style of the given range + * + * @param fontLine The font line style; a null value resets the font line style + * @returns This range, for chaining. + */ + setUnderline(fontLine: Nullable): Range { + this._setStyle(fontLine, 'ul'); + return this; + } + + /** + * Sets the font line style of the given range + * + * @param fontLine The font line style; a null value resets the font line style + * @returns This range, for chaining. + */ + setOverline(fontLine: Nullable): Range { + this._setStyle(fontLine, 'ol'); + return this; + } + + /** + * Sets a rectangular grid of line styles (must match dimensions of this range). + * + * @param fontLine The font line style; a null value resets the font line style + * @returns This range, for chaining. + */ + setStrikeThrough(fontLine: Nullable): Range { + this._setStyle(fontLine, 'st'); + return this; + } + + /** + * Sets the font line style of the given range + * + * @param fontLines A two-dimensional array of font line styles ; null values reset the font line style. + * @returns This range, for chaining. + */ + setUnderlines(fontLines: Array>>): Range { + this._setStyles(fontLines, 'ul'); + return this; + } + + /** + * Sets the font line style of the given range + * + * @param fontLines A two-dimensional array of font line styles ; null values reset the font line style. + * @returns This range, for chaining. + */ + setOverlines(fontLines: Array>>): Range { + this._setStyles(fontLines, 'ol'); + return this; + } + + /** + * Sets the font line style of the given range + * + * @param fontLines A two-dimensional array of font line styles ; null values reset the font line style. + * @returns This range, for chaining. + */ + setStrikeThroughs(fontLines: Array>>): Range { + this._setStyles(fontLines, 'st'); + return this; + } + + /** + * Sets a rectangular grid of font sizes (must match dimensions of this range). + * + * @param sizes A two-dimensional array of sizes. + * @returns This range, for chaining. + */ + setFontSizes(sizes: number[][]): Range { + this._setStyles(sizes, 'fs'); + return this; + } + + /** + * Set the font style for the given range ('italic' or 'normal'). + * + * @param fontStyle The font style, either 'italic' or 'normal'; a null value resets the font style. + * @returns This range, for chaining. + */ + setFontStyle(fontStyle: Nullable): Range { + this._setStyle(fontStyle, 'it'); + return this; + } + + /** + * Sets a rectangular grid of font styles (must match dimensions of this range). + * + * @param fontStyles A two-dimensional array of font styles, either 'italic' or 'normal'; null values reset the font style. + * @returns This range, for chaining. + */ + setFontStyles(fontStyles: Array>>): Range { + this._setStyles(fontStyles, 'it'); + return this; + } + + /** + * Set the font weight for the given range (normal/bold). + * + * @param fontWeight The font weight, either 'bold' or 'normal'; a null value resets the font weight. + * @returns This range, for chaining. + */ + setFontWeight(fontWeight: Nullable): Range { + this._setStyle(fontWeight, 'bl'); + return this; + } + + /** + * Sets a rectangular grid of font weights (must match dimensions of this range). + * + * @param fontWeights A two-dimensional array of font weights, either 'bold' or 'normal'; null values reset the font weight. + * @returns This range, for chaining. + */ + setFontWeights(fontWeights: Array>>): Range { + this._setStyles(fontWeights, 'bl'); + return this; + } + + /** + * Updates the formula for this range. + * + * @param formula A string representing the formula to set for the cell. + * @returns This range, for chaining. + */ + setFormula(formula: string): Range { + const { _rangeData, _context, _commandManager, _worksheet } = this; + const { startRow, startColumn, endRow, endColumn } = _rangeData; + const cellValue = new ObjectMatrix(); + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + cellValue.setValue(r, c, formula); + } + } + const setValue: ISetRangeFormulaActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_FORMULA_ACTION, + cellFormula: cellValue.getData(), + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + + return this; + } + + /** + * Sets a rectangular grid of formulas (must match dimensions of this range). + * + * @param formulas A two-dimensional string array of formulas. + * @returns This range, for chaining. + */ + setFormulas(formulas: string[][]): Range { + const { _rangeData, _context, _commandManager, _worksheet } = this; + + const { startRow, startColumn, endRow, endColumn } = _rangeData; + + const cellValue = new ObjectMatrix(); + for (let r = 0; r <= endRow - startRow; r++) { + for (let c = 0; c <= endColumn - startColumn; c++) { + cellValue.setValue(r + startRow, c + startColumn, formulas[r][c]); + } + } + + const setValue: ISetRangeFormulaActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_FORMULA_ACTION, + cellFormula: cellValue.getData(), + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + + return this; + } + + /** + * Sets a rectangular grid of horizontal alignments. + * + * @param alignments A two-dimensional array of alignments, either 'left', 'center' or 'normal'; a null value resets the alignment. + * @returns This range, for chaining. + */ + setHorizontalAlignments( + alignments: Array>> + ): Range { + this._setStyles(alignments, 'ht'); + return this; + } + + /** + * Sets the note to the given value. + * + * @param note The note value to set for the range; a null value removes the note. + * @returns This range, for chaining. + */ + // setNote(note: string): Range { + // const { _rangeData, _context, _commandManager, _worksheet } = this; + // const { startRow, startColumn, endRow, endColumn } = _rangeData; + // const cellValue = new ObjectMatrix(); + // for (let r = startRow; r <= endRow; r++) { + // for (let c = startColumn; c <= endColumn; c++) { + // cellValue.setValue(r, c, note); + // } + // } + // const setValue: ISetRangeNoteActionData = { + // sheetId: _worksheet.getSheetId(), + // actionName: ACTION_NAMES.SET_RANGE_NOTE_ACTION, + // cellNote: cellValue.getData(), + // rangeData: this._rangeData, + // }; + // const command = new Command(_context.getWorkBook(), setValue); + // _commandManager.invoke(command); + + // return this; + // } + + /** + * Sets the note to the given value. + * + * @param notes A two-dimensional array of notes; null values remove the note. + * @returns This range, for chaining. + */ + // setNotes(notes: string[][]): Range { + // const { _rangeData, _context, _commandManager, _worksheet } = this; + + // const { startRow, startColumn, endRow, endColumn } = _rangeData; + + // const cellValue = new ObjectMatrix(); + // for (let r = 0; r <= endRow - startRow; r++) { + // for (let c = 0; c <= endColumn - startColumn; c++) { + // cellValue.setValue(r + startRow, c + startColumn, notes[r][c]); + // } + // } + + // const setValue: ISetRangeNoteActionData = { + // sheetId: _worksheet.getSheetId(), + // actionName: ACTION_NAMES.SET_RANGE_NOTE_ACTION, + // cellNote: cellValue.getData(), + // rangeData: this._rangeData, + // }; + // const command = new Command(_context.getWorkBook(), setValue); + // _commandManager.invoke(command); + + // return this; + // } + + /** + * Sets the number or date format to the given formatting string. + * + * @param numberFormat A number format string. + * @returns This range, for chaining. + */ + // setNumberFormat(numberFormat: string): Range { + // const { _rangeData, _context, _commandManager, _worksheet } = this; + + // const { startRow, startColumn, endRow, endColumn } = _rangeData; + + // const cellValue = new ObjectMatrix(); + // for (let r = 0; r <= endRow - startRow; r++) { + // for (let c = 0; c <= endColumn - startColumn; c++) { + // const fm = { + // f: numberFormat, + // t: FormatType.NUMBER, + // }; + // cellValue.setValue(r + startRow, c + startColumn, JSON.stringify(fm)); + // } + // } + + // const setValue: ISetRangeFormatActionData = { + // sheetId: _worksheet.getSheetId(), + // actionName: ACTION_NAMES.SET_RANGE_FORMAT_ACTION, + // cellFormat: cellValue.getData(), + // rangeData: this._rangeData, + // }; + // const command = new Command(_context.getWorkBook(), setValue); + // _commandManager.invoke(command); + + // return this; + // } + + /** + * Sets a rectangular grid of number or date formats (must match dimensions of this range). + * + * @param numberFormats A two-dimensional array of number formats. + * @returns This range, for chaining. + */ + // setNumberFormats(numberFormats: string[][]): Range { + // const { _rangeData, _context, _commandManager, _worksheet } = this; + + // const { startRow, startColumn, endRow, endColumn } = _rangeData; + + // const cellValue = new ObjectMatrix(); + // for (let r = 0; r <= endRow - startRow; r++) { + // for (let c = 0; c <= endColumn - startColumn; c++) { + // const fm = { + // f: numberFormats[r][c], + // t: FormatType.NUMBER, + // }; + // cellValue.setValue(r + startRow, c + startColumn, JSON.stringify(fm)); + // } + // } + + // const setValue: ISetRangeFormatActionData = { + // sheetId: _worksheet.getSheetId(), + // actionName: ACTION_NAMES.SET_RANGE_FORMAT_ACTION, + // cellFormat: cellValue.getData(), + // rangeData: this._rangeData, + // }; + // const command = new Command(_context.getWorkBook(), setValue); + // _commandManager.invoke(command); + + // return this; + // } + + // setRichTextValue(value) {} + // setRichTextValues(values) {} + // setShowHyperlink(showHyperlink) {} + + /** + * Sets a rectangular grid of text directions. + * + * @param directions The desired text directions; if a specified direction is null it is inferred before setting. + * @returns This range, for chaining. + */ + setTextDirections(directions: number[][]): Range { + this._setStyles(directions, 'td'); + return this; + } + + /** + * Sets the text rotation settings for the cells in the range. + * + * @param degrees The desired angle between the standard orientation and the desired orientation. For left to right text, positive angles are in the counterclockwise direction. + * @returns This range, for chaining. + */ + setTextRotation(degrees: Nullable): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const styleObj: IStyleData = { + tr: { + v: 0, + a: Number(degrees), + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets a rectangular grid of text rotations. + * + * @param rotations The desired text rotation settings. + * @returns This range, for chaining. + */ + setTextRotations(rotations: number[][]): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const matrix = new ObjectMatrix(); + for (let r = 0; r < endRow - startRow + 1; r++) { + for (let c = 0; c < endColumn - startColumn + 1; c++) { + matrix.setValue(r, c, { + tr: { + v: 0, + a: rotations[r][c]!, + }, + }); + } + } + const a = matrix.getData(); + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: matrix.getData(), + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the text style for the cells in the range. + * + * @param style The desired text style. + * @returns This range, for chaining. + */ + setTextStyle(style: Nullable): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const stylesObj = { ...style }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + stylesObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets a rectangular grid of text styles. + * + * @param styles The desired text styles. + * @returns This range, for chaining. + */ + setTextStyles(styles: Array>>): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + const matrix = new ObjectMatrix(); + for (let r = 0; r < endRow - startRow + 1; r++) { + for (let c = 0; c < endColumn - startColumn + 1; c++) { + matrix.setValue(r, c, { ...styles[r][c] }); + } + } + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: matrix.getData(), + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets a rectangular grid of vertical alignments (must match dimensions of this range). + * + * @param alignments A two-dimensional array of alignments, either 'top', 'middle' or 'bottom'; a null value resets the alignment. + * @returns This range, for chaining. + */ + setVerticalAlignments(alignments: Array>>): Range { + this._setStyles(alignments, 'vt'); + return this; + } + + /** + * Sets whether or not to stack the text for the cells in the range. + * @param isVertical Whether or not to stack the text. + * @returns + */ + setVerticalText(isVertical: BooleanNumber): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const styleObj = { + tr: { + v: isVertical, + a: 90, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Set the cell wrap of the given range + * + * @param isWrapEnabled Whether to wrap text or not. + * @returns This range, for chaining. + */ + setWrap(isWrapEnabled: BooleanNumber): Range { + this._setStyle(isWrapEnabled, 'tb'); + return this; + } + + /** + * Sets a rectangular grid of wrap strategies. + * + * @param strategies The desired wrapping strategies. + * @returns This range, for chaining. + */ + setWrapStrategies(strategies: Array>>): Range { + this._setStyles(strategies, 'tb'); + return this; + } + + /** + * Sets a rectangular grid of word wrap policies (must match dimensions of this range). + * + * @param isWrapEnabled A two-dimensional array of wrap variables that determine whether to wrap text in a cell or not. + * @returns This range, for chaining. + */ + setWraps(isWrapEnabled: BooleanNumber[][]): Range { + this._setStyles(isWrapEnabled, 'tb'); + return this; + } + + /** + * Fills the destinationRange with data based on the data in this range. The new values are also determined by the specified series type. The destination range must contain this range and extend it in only one direction. For example, the following fills A1:A20 with a series of increasing numbers based on the current values in A1:A4: + * + * @param destination The range to be auto-filled with values. The destination range should contain this range and extend it in only one direction (upwards, downwards, left, or right). + * @param series The type of autoFill series that should be used to calculate new values. The effect of this series differs based on the type and amount of source data. + * @returns This range, for chaining. + */ + autoFill(destination: Range, series: AutoFillSeries): Range { + const { _worksheet, _context, _commandManager } = this; + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const { + startRow: Dsr, + endRow: Der, + startColumn: Dsc, + endColumn: Dec, + } = destination._rangeData; + let direction = Direction.BOTTOM; + let csLen: number = 0; + let asLen: number = 0; + + // 复制范围 + const copy_str_r = startRow; + const copy_end_r = endRow; + const copy_str_c = startColumn; + const copy_end_c = endColumn; + + // 应用范围 + const apply_str_r = Dsr; + const apply_end_r = Der; + const apply_str_c = Dsc; + const apply_end_c = Dec; + + const matrix = new ObjectMatrix(); + + if (apply_str_c === copy_str_c && apply_end_c === copy_end_c) { + if (apply_end_r > copy_end_r) { + direction = Direction.BOTTOM; + } else { + direction = Direction.TOP; + } + } else if (apply_end_c > copy_end_c) { + direction = Direction.RIGHT; + } else { + direction = Direction.LEFT; + } + + if (direction === Direction.BOTTOM || direction === Direction.TOP) { + // 列 + csLen = copy_end_r - copy_str_r + 1; + asLen = apply_end_r - apply_str_r + 1; + + for (let i = 0; i <= endColumn - startColumn; i++) { + const sourceRange = this.getColumnMatrix(i).toArray(); + + const copydata = DropCell.getCopyData( + sourceRange, + copy_str_r, + copy_end_r, + copy_str_c, + copy_end_c, + direction + ); + copydata.forEach((item) => { + const data = DropCell.getApplyData( + item, + csLen, + asLen, + series, + direction + ); + data.forEach((dataItem, j) => { + matrix.setValue(j, i, dataItem); + }); + }); + } + } else if (direction === Direction.RIGHT || direction === Direction.LEFT) { + asLen = apply_end_c - apply_str_c; + csLen = copy_end_c - copy_str_c; + // 行 + for (let i = 0; i <= endRow - startRow; i++) { + const sourceRange = this.getRowMatrix(i).toArray(); + + const copydata = DropCell.getCopyData( + sourceRange, + copy_str_r, + copy_end_r, + copy_str_c, + copy_end_c, + direction + ); + copydata.forEach((item) => { + const data = DropCell.getApplyData( + item, + csLen, + asLen, + series, + direction + ); + }); + } + } + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: matrix.getData(), + rangeData: destination._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + return this; + } + + // TODO + // autoFillToNeighbor(series: AutoFillSeries) {} + + /** + * Returns a copy of the range expanded in the four cardinal Directions to cover all adjacent cells with data in them. + * + * @returns This range, for chaining. + */ + getDataRegion(): Range; + /** + * Returns a copy of the range expanded Direction.UP and Direction.DOWN if the specified dimension is Dimension.ROWS, or Direction.NEXT and Direction.PREVIOUS if the dimension is Dimension.COLUMNS. + * + * @returns This range, for chaining. + */ + getDataRegion(dimension: Dimension): Range; + getDataRegion(...argument: any): Range { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + let numRows: number; + let numColumns: number; + + const data = this._worksheet.getCellMatrix(); + if (Tools.isNumber(argument[0])) { + const dimension = argument[0]; + + if (dimension === Dimension.COLUMNS) { + let start: number = startRow; + let end: number = startRow; + const rowMax = this._worksheet.getRowCount(); + for (let i = 1; i < startRow; i++) { + const element = data.getValue(startRow - i, startColumn); + if (!element) { + start = startRow - i + 1; + break; + } + } + const j = 0; + while (j < rowMax - startRow) { + const element = data.getValue(startRow + j, startColumn); + if (!element) { + end = startRow - j - 1; + break; + } + } + + numColumns = start - end; + numRows = 0; + return this._worksheet.getRange( + startRow, + start, + numRows, + numColumns + ); + } + if (dimension === Dimension.ROWS) { + let start: number = startRow; + let end: number = startRow; + const colMax = this._worksheet.getColumnCount(); + for (let i = 1; i < startRow; i++) { + const element = data.getValue(startRow, startColumn - i); + if (!element) { + start = startRow - i + 1; + break; + } + } + const j = 0; + while (j < colMax - startColumn) { + const element = data.getValue(startRow, startColumn + j); + if (!element) { + end = startRow - j - 1; + break; + } + } + + numColumns = 0; + numRows = start - end; + return this._worksheet.getRange( + start, + startColumn, + numRows, + numColumns + ); + } + } else { + let rowStart: number = startRow; + let rowEnd: number = startRow; + const rowMax = this._worksheet.getRowCount(); + for (let i = 1; i < startRow; i++) { + const element = data.getValue(startRow - i, startColumn); + if (!element) { + rowStart = startRow - i + 1; + break; + } + } + const rj = 0; + while (rj < rowMax - startRow) { + const element = data.getValue(startRow + rj, startColumn); + if (!element) { + rowEnd = startRow - rj - 1; + break; + } + } + + let columnStart: number = startRow; + let columnEnd: number = startRow; + const colMax = this._worksheet.getColumnCount(); + for (let i = 1; i < startRow; i++) { + const element = data.getValue(startRow, startColumn - i); + if (!element) { + columnStart = startRow - i + 1; + break; + } + } + const cj = 0; + while (cj < colMax - startColumn) { + const element = data.getValue(startRow, startColumn + cj); + if (!element) { + columnEnd = startRow - cj - 1; + break; + } + } + + numColumns = columnStart - columnEnd; + numColumns = rowStart - rowEnd; + numRows = startRow; + return this._worksheet.getRange( + rowStart, + columnStart, + numRows, + numColumns + ); + } + return this; + } + + /** + * Trims the whitespace (such as spaces, tabs, or new lines) in every cell in this range. Removes all whitespace from the start and end of each cell's text, and reduces any subsequence of remaining whitespace characters to a single space. + * + * @returns This range, for chaining. + */ + trimWhitespace(): Range { + const { _context, _worksheet, _commandManager } = this; + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const sheetMatrix = this._worksheet.getCellMatrix(); + const regx = /\s+/g; + + const cellValue = new ObjectMatrix(); + for (let r = 0; r <= endRow - startRow; r++) { + for (let c = 0; c <= endColumn - startColumn; c++) { + const value = sheetMatrix.getValue(r, c)!.m?.replace(regx, ''); + cellValue.setValue(r + startRow, c + startColumn, value || ''); + } + } + + const setValue: ISetRangeValueActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_VALUE_ACTION, + cellValue: cellValue.getData(), + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + + return this; + } + // uncheck + + /** + * Sets the specified range as the active range, with the top left cell in the range as the current cell. + * + * @returns This range, for chaining. + * @internal + */ + activate(): Range { + // const { _context, _commandManager } = this; + // The user entered an invalid range + if (this._rangeData?.startRow === -1) { + console.error('Invalid range,default set startRow -1'); + return this; + } + + this._worksheet.getSelection().setSelection({ selection: this }); + + // This range, for chaining + return this; + } + + /** + * Sets the specified cell as the current cell. + * + * If the specified cell is present in an existing range, then that range becomes the active range with the cell as the current cell. + * + * If the specified cell is not present in any existing range, then the existing selection is removed and the cell becomes the current cell and the active range. + * + * Note: The specified Range must consist of one cell, otherwise it throws an exception. + * + * @returns This range, for chaining. + */ + activateAsCurrentCell(): Range { + this._worksheet.getSelection().setCurrentCell(this); + return this; + } + + /** + * Whether the current range and the incoming range have an intersection + * + * @param range the incoming range + * @returns Intersect or not + */ + isIntersection(range: Range): boolean { + const currentStartRow = this._rangeData.startRow; + const currentEndRow = this._rangeData.endRow; + const currentStartColumn = this._rangeData.startColumn; + const currentEndColumn = this._rangeData.endColumn; + + const incomingStartRow = range.getRangeData().startRow; + const incomingEndRow = range.getRangeData().endRow; + const incomingStartColumn = range.getRangeData().startColumn; + const incomingEndColumn = range.getRangeData().endColumn; + + const zx = Math.abs( + currentStartColumn + + currentEndColumn - + incomingStartColumn - + incomingEndColumn + ); + const x = + Math.abs(currentStartColumn - currentEndColumn) + + Math.abs(incomingStartColumn - incomingEndColumn); + const zy = Math.abs( + currentStartRow + currentEndRow - incomingStartRow - incomingEndRow + ); + const y = + Math.abs(currentStartRow - currentEndRow) + + Math.abs(incomingStartRow - incomingEndRow); + if (zx <= x && zy <= y) { + return true; + } + return false; + } + + /** + * Sets the font color in CSS notation (such as '#ffffff' or 'white'). + * + * @param color The font color in CSS notation (such as '#ffffff' or 'white'); a null value resets the color. + * @returns + */ + setFontColor(color: string): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + + const colorObj: IStyleData = { + cl: { + rgb: color, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + colorObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Copies the data from a range of cells to another range of cells. Both the values and formatting are copied. + * + * @param destination A destination range to copy to; only the top-left cell position is relevant. + */ + copyTo(destination: Range): void; + + /** + * Copies the data from a range of cells to another range of cells.. + * + * @param destination A destination range to copy to; only the top-left cell position is relevant. + * @param copyPasteType A type that specifies how the range contents are pasted to the destination. + * @param transposed Whether the range should be pasted in its transposed orientation. + */ + copyTo( + destination: Range, + copyPasteType: CopyPasteType, + transposed: boolean + ): void; + + /** + * Copies the data from a range of cells to another range of cells. By default both the values and formatting are copied, but this can be overridden using advanced arguments. + * + * @param destination A destination range to copy to; only the top-left cell position is relevant. + * @param options A JavaScript object that specifies advanced parameters, as listed below. + */ + copyTo(destination: Range, options: IOptionData): void; + copyTo(...argument: any): void { + const { _context, _worksheet, _commandManager } = this; + const destination = argument[0]; + const [value, range] = this._handleCopyRange(this, destination); + const { startRow, startColumn, endRow, endColumn } = range; + + if (Tuples.checkup(argument, Range, Tuples.BOOLEAN_TYPE)) { + const copyPasteType = argument[1]; + const transposed = argument[2]; + + if (copyPasteType === CopyPasteType.PASTE_NORMAL) { + const cellValue = new ObjectMatrix(); + value.forEach((row, r) => + row.forEach((cell, c) => { + cell = cell as ICellData; + cellValue.setValue( + r + startRow, + c + startColumn, + cell || {} + ); + }) + ); + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: cellValue.getData(), + rangeData: range, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } else if (copyPasteType === CopyPasteType.PASTE_FORMAT) { + const styles = _context.getWorkBook().getStyles(); + + const stylesMatrix = new ObjectMatrix(); + const stylesArray = value.map((row, r) => + row.forEach((cell, c) => { + cell = cell as ICellData; + stylesMatrix.setValue( + r, + c, + cell.s ? styles.get(cell.s) : {} + ); + }) + ); + + // Organize the style format of the current range into the format of the destination range + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix.getData(), + rangeData: range, + }; + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + } else if (copyPasteType === CopyPasteType.PASTE_VALUES) { + const cellValue = new ObjectMatrix(); + value.forEach((row, r) => + row.forEach((cell, c) => { + cell = cell as ICellData; + cellValue.setValue( + r + startRow, + c + startColumn, + cell?.v || '' + ); + }) + ); + + const setValue: ISetRangeValueActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_VALUE_ACTION, + cellValue: cellValue.getData(), + rangeData: range, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } else if (copyPasteType === CopyPasteType.PASTE_COLUMN_WIDTHS) { + const rangeStartColumn = this._rangeData.startColumn; + for (let i = 0; i < endColumn - rangeStartColumn; i++) { + const width = _worksheet.getColumnWidth(rangeStartColumn + i); + _worksheet.setColumnWidth(rangeStartColumn + i, width); + } + } + } else if (Tuples.checkup(argument, Range, Tuples.OBJECT_TYPE)) { + const options = argument[1]; + + const cellValue = new ObjectMatrix(); + value.forEach((row, r) => + row.forEach((cell, c) => { + cell = cell as ICellData; + cellValue.setValue(r + startRow, c + startColumn, cell || {}); + }) + ); + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: cellValue.getData(), + rangeData: range, + options, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } else if (Tuples.checkup(argument, Range)) { + const cellValue = new ObjectMatrix(); + value.forEach((row, r) => + row.forEach((cell, c) => { + cell = cell as ICellData; + cellValue.setValue(r + startRow, c + startColumn, cell || {}); + }) + ); + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: cellValue.getData(), + rangeData: range, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } + } + + /** + * Copy the formatting of the range to the given location. If the destination is larger or smaller than the source range then the source is repeated or truncated accordingly. Note that this method copies the formatting only. + * + * @param sheetId The unique ID of the sheet within the spreadsheet, irrespective of position. + * @param startRow The start row of the target range. + * @param endRow The end row of the target range. + * @param startColumn The first column of the target range. + * @param endColumn The end column of the target range. + */ + copyFormatToRange( + sheetId: string, + startRow: number, + endRow: number, + startColumn: number, + endColumn: number + ): void; + /** + * Copy the formatting of the range to the given location. If the destination is larger or smaller than the source range then the source is repeated or truncated accordingly. Note that this method copies the formatting only. + * + * @param sheet The target sheet. + * @param startRow The start row of the target range. + * @param endRow The end row of the target range. + * @param startColumn The first column of the target range. + * @param endColumn The end column of the target range. + */ + copyFormatToRange( + sheet: WorkSheet, + startRow: number, + endRow: number, + startColumn: number, + endColumn: number + ): void; + copyFormatToRange(...argument: any): void { + const { _context, _commandManager } = this; + + const startRow = argument[1]; + const endRow = argument[2]; + const startColumn = argument[3]; + const endColumn = argument[4]; + + const sheetId = Tools.isAssignableFrom(argument[0], WorkSheet) + ? argument[0].getSheetId() + : argument[0]; + + const [value, range] = this._handleCopyRange(this, { + startRow, + endRow, + startColumn, + endColumn, + }); + + const styles = _context.getWorkBook().getStyles(); + + const stylesMatrix = new ObjectMatrix(); + value.map((row, r) => + row.map((cell, c) => { + cell = cell as ICellData; + stylesMatrix.setValue(r, c, cell.s ? styles.get(cell.s) : {}); + return cell.s ? styles.get(cell.s) : {}; + }) + ); + + // Organize the style format of the current range into the format of the destination range + const setStyle: ISetRangeStyleActionData = { + sheetId, + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix.getData(), + rangeData: range, + }; + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + } + + /** + * Copy the content of the range to the given location. If the destination is larger or smaller than the source range then the source is repeated or truncated accordingly.For a detailed description of the gridId parameter, see getGridId(). + * @param sheetId The unique ID of the sheet within the spreadsheet, irrespective of position. + * @param startRow The start row of the target range. + * @param endRow The end row of the target range. + * @param startColumn The first column of the target range. + * @param endColumn The end column of the target range. + * @returns + */ + copyValuesToRange( + sheetId: string, + startRow: number, + endRow: number, + startColumn: number, + endColumn: number + ): void; + /** + * Copy the content of the range to the given location. If the destination is larger or smaller than the source range then the source is repeated or truncated accordingly. + * @param sheet The target sheet. + * @param startRow The start row of the target range. + * @param endRow The end row of the target range. + * @param startColumn The first column of the target range. + * @param endColumn The end column of the target range. + * @returns + */ + copyValuesToRange( + sheet: WorkSheet, + startRow: number, + endRow: number, + startColumn: number, + endColumn: number + ): void; + copyValuesToRange(...argument: any): void { + const { _context, _commandManager } = this; + + const startRow = argument[1]; + const endRow = argument[2]; + const startColumn = argument[3]; + const endColumn = argument[4]; + + const sheetId = Tools.isAssignableFrom(argument[0], WorkSheet) + ? argument[0].getSheetId() + : argument[0]; + + const [value, range] = this._handleCopyRange(this, { + startRow, + endRow, + startColumn, + endColumn, + }); + + const cellValue = new ObjectMatrix(); + value.forEach((row, r) => + row.forEach((cell, c) => { + cell = cell as ICellData; + cellValue.setValue( + r + range.startRow, + c + range.startColumn, + cell?.v || '' + ); + }) + ); + + const setValue: ISetRangeValueActionData = { + sheetId, + actionName: ACTION_NAMES.SET_RANGE_VALUE_ACTION, + cellValue: cellValue.getData(), + rangeData: range, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } + + /** + * Cut and paste (both format and values) from this range to the target range. + * @param target A target range to copy this range to; only the top-left cell position is relevant. + * @returns + */ + moveTo(target: Range): void { + const { _context, _worksheet, _commandManager, _rangeData } = this; + + // clear current range + const options = { + formatOnly: true, + contentsOnly: true, + commentsOnly: true, + validationsOnly: true, + skipFilteredRows: true, + }; + const clearValue: IClearRangeActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.CLEAR_RANGE_ACTION, + options, + rangeData: _rangeData, + }; + + // get values from this range + const currentMatrix = this.getMatrix(); + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const { startRow: targetStartRow, startColumn: targetStartColumn } = + target.getRangeData(); + + const targetMatrix = new ObjectMatrix(); + + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + targetMatrix.setValue( + targetStartRow + (r - startRow), + targetStartColumn + (c - startColumn), + currentMatrix.getValue(r, c) || {} + ); + } + } + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: targetMatrix.getData(), + rangeData: { + startRow: targetStartRow, + endRow: targetStartRow + (endRow - startRow), + startColumn: targetStartColumn, + endColumn: targetStartColumn + (endColumn - startColumn), + }, + }; + + const command = new Command(_context.getWorkBook(), clearValue, setValue); + _commandManager.invoke(command); + } + + /** + * Returns a new range that is offset from this range by the given number of rows and columns (which can be negative). The new range is the same size as the original range. + * @param rowOffset The number of rows down from the range's top-left cell; negative values represent rows up from the range's top-left cell. + * @param columnOffset The number of columns right from the range's top-left cell; negative values represent columns left from the range's top-left cell. + * @returns Range — This range, for chaining. + */ + offset(rowOffset: number, columnOffset: number): Range; + + /** + * Returns a new range that is relative to the current range, whose upper left point is offset from the current range by the given rows and columns, and with the given height in cells. + * @param rowOffset The number of rows down from the range's top-left cell; negative values represent rows up from the range's top-left cell. + * @param columnOffset The number of columns right from the range's top-left cell; negative values represent columns left from the range's top-left cell. + * @param numRows The height in rows of the new range. + * @returns Range — This range, for chaining. + */ + offset(rowOffset: number, columnOffset: number, numRows: number): Range; + + /** + * Returns a new range that is relative to the current range, whose upper left point is offset from the current range by the given rows and columns, and with the given height and width in cells. + * @param rowOffset The number of rows down from the range's top-left cell; negative values represent rows up from the range's top-left cell. + * @param columnOffset The number of columns right from the range's top-left cell; negative values represent columns left from the range's top-left cell. + * @param numRows The height in rows of the new range. + * @param numColumns The width in columns of the new range. + * @returns Range — This range, for chaining. + */ + offset( + rowOffset: number, + columnOffset: number, + numRows: number, + numColumns: number + ): Range; + offset(...argument: any): Range { + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const rowOffset = argument[0]; + const columnOffset = argument[1]; + const numRows = argument[2]; + const numColumns = argument[3]; + + const offset = { + startRow: startRow + rowOffset, + endRow: endRow + rowOffset, + startColumn: startColumn + columnOffset, + endColumn: endColumn + columnOffset, + }; + + if (Tools.isNumber(numRows)) { + offset.endRow = offset.startRow + numRows - 1; + } + + if (Tools.isNumber(numColumns)) { + offset.endColumn = offset.endColumn + numColumns - 1; + } + + return new Range(this._worksheet, offset); + } + + /** + * Sets the border property with color and/or style. Valid values are true (on), false (off) and null (no change). For color, use Color in CSS notation (such as '#ffffff' or 'white'). + * + * @param top true for border, false for none, null for no change. + * @param left true for border, false for none, null for no change. + * @param bottom true for border, false for none, null for no change. + * @param right true for border, false for none, null for no change. + * @param vertical true for internal vertical borders, false for none, null for no change. + * @param horizontal true for internal horizontal borders, false for none, null for no change. + * @param color A color in CSS notation (such as '#ffffff' or 'white'), null for default color (black). + * @param style A style for the borders, null for default style (solid). + * @returns + */ + setBorder( + top: Nullable, + left: Nullable, + bottom: Nullable, + right: Nullable, + vertical: Nullable, + horizontal: Nullable + ): Range; + setBorder( + top: Nullable, + left: Nullable, + bottom: Nullable, + right: Nullable, + vertical: Nullable, + horizontal: Nullable, + color: string, + style: BorderStyleTypes + ): Range; + setBorder(...argument: any): Range { + const top = argument[0]; + const left = argument[1]; + const bottom = argument[2]; + const right = argument[3]; + + const vertical = argument[4]; + const horizontal = argument[5]; + + const color = argument[6] ? argument[6] : 'black'; + const style = argument[7] ? argument[7] : BorderStyleTypes.DASH_DOT; + + const { _context, _worksheet, _commandManager } = this; + const _workbook = _context.getWorkBook(); + + const rangeData = this._rangeData; + const _sheetId = _worksheet.getSheetId(); + + // Cells in the surrounding range may need to clear the border + const topRangeOut = { + startRow: rangeData.startRow - 1, + startColumn: rangeData.startColumn, + endRow: rangeData.startRow - 1, + endColumn: rangeData.endColumn, + }; + + const leftRangeOut = { + startRow: rangeData.startRow, + startColumn: rangeData.startColumn - 1, + endRow: rangeData.endRow, + endColumn: rangeData.startColumn - 1, + }; + + const bottomRangeOut = { + startRow: rangeData.endRow + 1, + startColumn: rangeData.startColumn, + endRow: rangeData.endRow + 1, + endColumn: rangeData.endColumn, + }; + + const rightRangeOut = { + startRow: rangeData.startRow, + startColumn: rangeData.endColumn + 1, + endRow: rangeData.endRow, + endColumn: rangeData.endColumn + 1, + }; + + // Cells in the upper, lower, left and right ranges + const topRange = { + startRow: rangeData.startRow, + startColumn: rangeData.startColumn, + endRow: rangeData.startRow, + endColumn: rangeData.endColumn, + }; + + const leftRange = { + startRow: rangeData.startRow, + startColumn: rangeData.startColumn, + endRow: rangeData.endRow, + endColumn: rangeData.startColumn, + }; + + const bottomRange = { + startRow: rangeData.endRow, + startColumn: rangeData.startColumn, + endRow: rangeData.endRow, + endColumn: rangeData.endColumn, + }; + + const rightRange = { + startRow: rangeData.startRow, + startColumn: rangeData.endColumn, + endRow: rangeData.endRow, + endColumn: rangeData.endColumn, + }; + + const tr = new Range(_worksheet, topRangeOut); + const lr = new Range(_worksheet, leftRangeOut); + const br = new Range(_worksheet, bottomRangeOut); + const rr = new Range(_worksheet, rightRangeOut); + const t = new Range(_worksheet, topRange); + const l = new Range(_worksheet, leftRange); + const b = new Range(_worksheet, bottomRange); + const r = new Range(_worksheet, rightRange); + + const mtr = new ObjectMatrix(); + const mlr = new ObjectMatrix(); + const mbr = new ObjectMatrix(); + const mrr = new ObjectMatrix(); + const mcr = new ObjectMatrix(); + + const actions: IActionData[] = []; + + const border: IBorderStyleData = { + s: style, + cl: { + rgb: color, + }, + }; + + if (top === true || top === false) { + // Probably to the border, there are no surrounding cells + if (tr.isValid()) { + // Clear the bottom border of the top range + tr.forEach((row, column) => { + mtr.setValue(row, column, { bd: { b: null } }); + }); + + const setBottomData: ISetRangeStyleActionData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: mtr.getArrayData(), + rangeData: topRangeOut, + }; + actions.push(setBottomData); + } + + // first row + t.forEach((row, column) => { + // update + if (top === true) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { t: Tools.deepClone(border) }, + }); + mcr.setValue(row, column, style); + } + // delete + else if (top === false) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { t: null }, + }); + mcr.setValue(row, column, style); + } + }); + } + if (bottom === true || bottom === false) { + // Probably to the border, there are no surrounding cells + if (br.isValid()) { + // Clear the top border of the lower range + br.forEach((row, column) => { + mbr.setValue(row, column, { bd: { t: null } }); + }); + + const setTopData: ISetRangeStyleActionData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: mbr.getArrayData(), + rangeData: bottomRangeOut, + }; + actions.push(setTopData); + } + + // the last row + b.forEach((row, column) => { + // update + if (bottom === true) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { b: Tools.deepClone(border) }, + }); + mcr.setValue(row, column, style); + } + // delete + else if (bottom === false) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { b: null }, + }); + mcr.setValue(row, column, style); + } + }); + } + if (left === true || left === false) { + // Probably to the border, there are no surrounding cells + if (lr.isValid()) { + // Clear the right border of the left range + lr.forEach((row, column) => { + mlr.setValue(row, column, { bd: { r: null } }); + }); + + const setRightData: ISetRangeStyleActionData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: mlr.getArrayData(), + rangeData: leftRangeOut, + }; + actions.push(setRightData); + } + + // first column + l.forEach((row, column) => { + // update + if (left === true) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { l: Tools.deepClone(border) }, + }); + mcr.setValue(row, column, style); + } + // delete + else if (left === false) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { l: null }, + }); + mcr.setValue(row, column, style); + } + }); + } + if (right === true || right === false) { + // Probably to the border, there are no surrounding cells + if (rr.isValid()) { + // Clear the left border of the right range + rr.forEach((row, column) => { + mrr.setValue(row, column, { bd: { l: null } }); + }); + + const setLeftData: ISetRangeStyleActionData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: mrr.getArrayData(), + rangeData: rightRangeOut, + }; + actions.push(setLeftData); + } + + // last column + r.forEach((row, column) => { + // update + if (right === true) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { r: Tools.deepClone(border) }, + }); + mcr.setValue(row, column, style); + } + // delete + else if (right === false) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { r: null }, + }); + mcr.setValue(row, column, style); + } + }); + } + + // inner vertical border + if (vertical === true || vertical === false) { + // current range + this.forEach((row, column) => { + // Set the right border except the last column + if (column !== rangeData.endColumn) { + // update + if (vertical === true) { + const style = Tools.deepMerge( + mcr.getValue(row, column) || {}, + { + bd: { r: Tools.deepClone(border) }, + } + ); + mcr.setValue(row, column, style); + } + // delete + else if (vertical === false) { + const style = Tools.deepMerge( + mcr.getValue(row, column) || {}, + { + bd: { r: null }, + } + ); + mcr.setValue(row, column, style); + } + } + + // Except for the first column, clear the left border + if (column !== rangeData.startColumn) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { l: null }, + }); + mcr.setValue(row, column, style); + } + }); + } + // inner horizontal border + if (horizontal === true || horizontal === false) { + // current range + this.forEach((row, column) => { + // Except for the last row, set the bottom border + if (row !== rangeData.endRow) { + // update + if (horizontal === true) { + const style = Tools.deepMerge( + mcr.getValue(row, column) || {}, + { + bd: { b: Tools.deepClone(border) }, + } + ); + mcr.setValue(row, column, style); + } + // delete + else if (horizontal === false) { + const style = Tools.deepMerge( + mcr.getValue(row, column) || {}, + { + bd: { b: null }, + } + ); + mcr.setValue(row, column, style); + } + } + + // Except for the first row, clear the top border + if (row !== rangeData.startRow) { + const style = Tools.deepMerge(mcr.getValue(row, column) || {}, { + bd: { t: null }, + }); + mcr.setValue(row, column, style); + } + }); + } + + const setCCData: ISetRangeStyleActionData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: mcr.getArrayData(), + rangeData: mcr.getDataRange(), + }; + actions.push(setCCData); + + const commandCC = new Command(_workbook, ...actions); + _commandManager.invoke(commandCC); + + return this; + } + + /** + * Set Border by tool bar + * @param type border type + * @param color color + * @param style size + */ + setBorderByType(type: BorderType, color: string, style: BorderStyleTypes) { + let top = null; + let left = null; + let bottom = null; + let right = null; + let vertical = null; + let horizontal = null; + + switch (type) { + case BorderType.TOP: + top = true; + break; + case BorderType.BOTTOM: + bottom = true; + break; + case BorderType.LEFT: + left = true; + break; + case BorderType.RIGHT: + right = true; + break; + case BorderType.NONE: + top = false; + left = false; + bottom = false; + right = false; + vertical = false; + horizontal = false; + break; + case BorderType.ALL: + top = true; + left = true; + bottom = true; + right = true; + vertical = true; + horizontal = true; + break; + case BorderType.OUTSIDE: + top = true; + left = true; + bottom = true; + right = true; + break; + case BorderType.INSIDE: + vertical = true; + horizontal = true; + break; + case BorderType.HORIZONTAL: + horizontal = true; + break; + case BorderType.VERTICAL: + vertical = true; + break; + + default: + break; + } + + this.setBorder(top, left, bottom, right, vertical, horizontal, color, style); + } + + /** + * Sets the font family, such as "Arial" or "Helvetica". + * + * @param fontFamily The font family to set; a null value resets the font family. + * @returns + */ + setFontFamily(fontFamily: Nullable): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const fontFamilyObj: IStyleData = { ff: fontFamily }; + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + fontFamilyObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the font size, with the size being the point size to use. + * + * @param size A font size in point size. + * @returns + */ + setFontSize(size: number): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const fontSizeObj = { fs: size }; + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + fontSizeObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Set the horizontal (left to right) alignment for the given range (left/center/right). + * + * @param alignment The alignment, either 'left', 'center' or 'normal'; a null value resets the alignment. + * @returns + */ + setHorizontalAlignment(alignment: Nullable): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const horizontalAlignmentObj = { ht: alignment }; + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + horizontalAlignmentObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Set the vertical (top to bottom) alignment for the given range (top/middle/bottom). + * + * @param alignment The alignment, either 'top', 'middle' or 'bottom'; a null value resets the alignment. + * @returns + */ + setVerticalAlignment(alignment: Nullable): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const verticalAlignmentObj = { vt: alignment }; + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + verticalAlignmentObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Set the vertical (top to bottom) alignment for the given range (top/middle/bottom). + * + * @param direction The desired text direction; if null the direction is inferred before setting. + * @returns + */ + setTextDirection(direction: Nullable): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const textDirectionObj: IStyleData = { td: direction }; + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + textDirectionObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the text wrapping strategy for the cells in the range. + * + * @param strategy The desired wrapping strategy. + * @returns + */ + setWrapStrategy(strategy: WrapStrategy): Range { + const { _context, _worksheet, _commandManager } = this; + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + // string converted to a two-dimensional array + const wrapStrategyObj = { tb: strategy }; + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + wrapStrategyObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: this._rangeData, + }; + + const command = new Command(_context.getWorkBook(), setStyle); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the value of the range. The value can be numeric, string, boolean or date. If it begins with '=' it is interpreted as a formula. + * @param value The value for the range. + * @returns This range, for chaining. + */ + setValue(value: ICellV): Range { + const { _rangeData, _context, _commandManager, _worksheet } = this; + const { startRow, startColumn, endRow, endColumn } = _rangeData; + const cellValue = new ObjectMatrix(); + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + cellValue.setValue(r, c, value); + } + } + const setValue: ISetRangeValueActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_VALUE_ACTION, + cellValue: cellValue.getData(), + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + + return this; + } + + /** + * Sets a rectangular grid of values (must match dimensions of this range). + * @param values A two-dimensional array of values. + * @returns Range — This range, for chaining. + */ + setValues(values: ICellV[][]): Range; + setValues(values: ObjectMatrix): Range; + setValues(...argument: any): Range { + const { _rangeData, _context, _commandManager, _worksheet } = this; + const values = argument[0]; + + if (Tuples.checkup(argument, Array)) { + const { startRow, startColumn, endRow, endColumn } = _rangeData; + + const cellValue = new ObjectMatrix(); + for (let r = 0; r <= endRow - startRow; r++) { + for (let c = 0; c <= endColumn - startColumn; c++) { + cellValue.setValue(r + startRow, c + startColumn, values[r][c]); + } + } + + const setValue: ISetRangeValueActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_VALUE_ACTION, + cellValue: cellValue.getData(), + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } else if (Tuples.checkup(argument, ObjectMatrix)) { + const setValue: ISetRangeValueActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_VALUE_ACTION, + cellValue: values, + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } + + return this; + } + + /** + * Sets cell data object for current range + * @param value Cell data object + * @returns + */ + setRangeData(value: ICellData): Range { + const { _rangeData, _context, _commandManager, _worksheet } = this; + const { startRow, startColumn, endRow, endColumn } = _rangeData; + const cellValue = new ObjectMatrix(); + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + cellValue.setValue(r, c, value); + } + } + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: cellValue.getData(), + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + + return this; + } + + /** + * Sets a rectangular grid of cell obejct data (must match dimensions of this range). + * @param values A two-dimensional array of cell object data. + * @returns Range — This range, for chaining. + */ + setRangeDatas(values: ICellData[][]): Range; + setRangeDatas(values: ObjectMatrix): Range; + setRangeDatas(...argument: any): Range { + const { _rangeData, _context, _commandManager, _worksheet } = this; + const values = argument[0]; + + if (Tuples.checkup(argument, Array)) { + const { startRow, startColumn, endRow, endColumn } = _rangeData; + + const cellValue = new ObjectMatrix(); + for (let r = 0; r <= endRow - startRow; r++) { + for (let c = 0; c <= endColumn - startColumn; c++) { + cellValue.setValue(r + startRow, c + startColumn, values[r][c]); + } + } + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: cellValue.getData(), + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } else if (Tuples.checkup(argument, ObjectMatrix)) { + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: values, + rangeData: this._rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } + + return this; + } + + /** + * Clears the range of contents, formats, and data validation rules. + * @returns This range, for chaining. + */ + clear(): Range; + /** + * Clears the range of contents, format, data validation rules, and/or comments, as specified with the given advanced options. By default all data is cleared. + * @param options A JavaScript object that specifies advanced parameters, as listed below. + * @returns This range, for chaining. + */ + clear(options: IOptionsData): Range; + clear(...argument: any): Range { + const { _context, _worksheet, _commandManager, _rangeData } = this; + + // default options + let options = { + formatOnly: true, + contentsOnly: true, + }; + if (Tuples.checkup(argument, Tuples.OBJECT_TYPE)) { + options = argument[0]; + } + + const setValue: IClearRangeActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.CLEAR_RANGE_ACTION, + options, + rangeData: _rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + + return this; + } + + /** + * Clears formatting for this range. + * + * This clears text formatting for the cell or cells in the range, but does not reset any number formatting rules. + * + * @returns Range This range, for chaining. + */ + clearFormat(): Range { + return this.clear({ formatOnly: true }); + } + + /** + * Clears the content of the range, leaving the formatting intact. + * + * @returns Range This range, for chaining. + */ + clearContent(): Range { + return this.clear({ contentsOnly: true }); + } + + /** + * Clears the note in the given cell or cells. + * + * @returns Range This range, for chaining. + */ + clearNote(): Range { + return this.clear({ commentsOnly: true }); + } + + /** + * Deletes this range of cells. Existing data in the sheet along the provided dimension is shifted towards the deleted range. + * + * solution: Clear the range to be deleted, and then set the new value of the cell content at the bottom using setValue + * @param {Dimension} shiftDimension The dimension along which to shift existing data. + * @returns void + */ + deleteCells(shiftDimension: Dimension): void { + const { _rangeData, _context, _commandManager, _worksheet } = this; + + const setValue: IDeleteRangeActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.DELETE_RANGE_ACTION, + shiftDimension, + rangeData: _rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } + + /** + * Inserts empty cells into this range. The new cells retain any formatting present in the cells previously occupying this range. Existing data in the sheet along the provided dimension is shifted away from the inserted range. + * @param {Dimension} shiftDimension The dimension along which to shift existing data. + * @returns Range This range, for chaining. + */ + insertCells(shiftDimension: Dimension): Range; + insertCells(shiftDimension: Dimension, destination: Range): Range; + insertCells(...argument: any): Range { + const shiftDimension = argument[0]; + const destination: Range = argument[1]; + const { _rangeData, _context, _commandManager, _worksheet } = this; + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + + let rangeData = this._rangeData; + + // TODO 1. 补充另一个方向 + // 2. 重载,传入数据,范围根据数据范围变化 + + // build blank values + let cellValue = new ObjectMatrix(); + if (destination) { + cellValue = destination.getMatrixObject(); + + const destinationRangeData = destination.getRangeData(); + + rangeData = { + startRow, + endRow: + startRow + + destinationRangeData.endRow - + destinationRangeData.startRow, + startColumn, + endColumn: + startRow + + destinationRangeData.endColumn - + destinationRangeData.startColumn, + }; + } else { + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + cellValue.setValue(r, c, { m: '', v: '' }); + } + } + } + + const insertValue: IInsertRangeActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.INSERT_RANGE_ACTION, + shiftDimension, + rangeData, + cellValue: cellValue.getData(), + }; + const command = new Command(_context.getWorkBook(), insertValue); + _commandManager.invoke(command); + return this; + } + + /** + * Merges the cells in the range together into a single block. + * + * @returns Range — This range, for chaining. + */ + merge(): Range { + const { _worksheet } = this; + const _context = _worksheet.getContext(); + const _sheetId = _worksheet.getSheetId(); + const _commandManager = _worksheet.getCommandManager(); + const dataRowInsert = { + actionName: ACTION_NAMES.ADD_MERGE_ACTION, + sheetId: _sheetId, + rectangles: [this._rangeData], + }; + const command = new Command(_context.getWorkBook(), dataRowInsert); + _commandManager.invoke(command); + + return this; + } + + /** + * Merge the cells in the range across the columns of the range. + * + * @returns Range — This range, for chaining. + */ + mergeAcross(): Range { + const { _worksheet } = this; + const _commandManager = _worksheet.getCommandManager(); + const _context = _worksheet.getContext(); + const _sheetId = _worksheet.getSheetId(); + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const rectangles = []; + + for (let r = startRow; r <= endRow; r++) { + const data = { + startRow: r, + endRow: r, + startColumn, + endColumn, + }; + rectangles.push(data); + } + const dataRowInsert: IAddMergeActionData = { + actionName: ACTION_NAMES.ADD_MERGE_ACTION, + sheetId: _sheetId, + rectangles, + }; + const command = new Command(_context.getWorkBook(), dataRowInsert); + _commandManager.invoke(command); + + return this; + } + + /** + * Merges the cells in the range together. + * + * @returns Range — This range, for chaining. + */ + mergeVertically(): Range { + const { _worksheet } = this; + const _commandManager = _worksheet.getCommandManager(); + const _context = _worksheet.getContext(); + const _sheetId = _worksheet.getSheetId(); + + const { startRow, endRow, startColumn, endColumn } = this._rangeData; + const rectangles = []; + + for (let c = startColumn; c <= endColumn; c++) { + const data = { + startRow, + endRow, + startColumn: c, + endColumn: c, + }; + rectangles.push(data); + } + const dataRowInsert: IAddMergeActionData = { + actionName: ACTION_NAMES.ADD_MERGE_ACTION, + sheetId: _sheetId, + rectangles, + }; + const command = new Command(_context.getWorkBook(), dataRowInsert); + _commandManager.invoke(command); + + return this; + } + + /** + * Break any multi-column cells in the range into individual cells again. + + Calling this function on a range is equivalent to selecting a range and clicking Format -> Merge -> Unmerge. + + @returns Range — This range, for chaining. + */ + breakApart(): Range { + // 您必须选中某个合并范围内的所有单元格才能执行合并或撤消合并。 + const { _worksheet } = this; + const _commandManager = _worksheet.getCommandManager(); + const _context = _worksheet.getContext(); + const _sheetId = _worksheet.getSheetId(); + + const rectangles = this._worksheet + .getMerges() + .getMergedRanges(this._rangeData); + + const dataRowInsert: IRemoveMergeActionData = { + actionName: ACTION_NAMES.REMOVE_MERGE_ACTION, + sheetId: _sheetId, + rectangles, + }; + const command = new Command(_context.getWorkBook(), dataRowInsert); + _commandManager.invoke(command); + + return this; + } + + /** + * Removes rows within this range that contain values that are duplicates of values in any previous row. Rows with identical values but different letter cases, formatting, or formulas are considered to be duplicates. This method also removes duplicates rows hidden from view (for example, due to a filter). Content outside of this range isn't removed. + * + * @returns Range The resulting range after removing duplicates. The size of the range is reduced by a row for every row removed. + */ + removeDuplicates(): Range; + + /** + * Removes rows within this range that contain values in the specified columns that are duplicates of values any previous row. Rows with identical values but different letter cases, formatting, or formulas are considered to be duplicates. This method also removes duplicates rows hidden from view (for example, due to a filter). Content outside of this range isn't removed. + * @param columnsToCompare The columns to analyze for duplicate values. If no columns are provided then all columns are analyzed for duplicates. + * @returns Range The resulting range after removing duplicates. The size of the range is reduced by a row for every row removed. + */ + removeDuplicates(columnsToCompare: number[]): Range; + removeDuplicates(...argument: any): Range { + const { _rangeData, _context, _commandManager, _worksheet } = this; + let columnsToCompare = []; + // set columns + if (Array.isArray(argument[0])) { + columnsToCompare = argument[0]; + } + + const newCellValue = this.getMatrix().getData(); + + const rowList: number[][] = []; + + for (let i = 0; i <= _rangeData.endColumn - _rangeData.startColumn; i++) { + const arr: number[] = []; + const obj: any = {}; + + const column = this.getColumnMatrix(i).getData(); + for (const key in column) { + // if (column.hasOwnProperty(key)) { + const value = column[key][i].m; + + if (!obj.hasOwnProperty(value)) { + obj[value!] = 1; + arr.push(i); + } + // } + } + + rowList.push(arr); + } + + const newRowList = Array.from(new Set(rowList.flat())); + + const newData = {}; + + newRowList.forEach((item, i) => { + newData[i] = newCellValue[item]; + }); + + const removeDatas: ISetRangeDataActionData[] = [ + { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: newData, + rangeData: _rangeData, + }, + ]; + const command = new Command(_context.getWorkBook(), ...removeDatas); + _commandManager.invoke(command); + return this; + } + + /** + * get row matrix + * @returns + */ + getRowMatrix(index: number) { + const { startColumn, endColumn } = this._rangeData; + + const sheetMatrix = this._worksheet.getCellMatrix(); + const rangeMatrix = new ObjectMatrix(); + + for (let r = index; r <= index; r++) { + for (let c = startColumn; c <= endColumn; c++) { + rangeMatrix.setValue(r, c, sheetMatrix.getValue(r, c) || {}); + } + } + + return rangeMatrix; + } + + /** + * get column matrix + * @returns + */ + getColumnMatrix(index: number) { + const { startRow, endRow } = this._rangeData; + + const sheetMatrix = this._worksheet.getCellMatrix(); + const rangeMatrix = new ObjectMatrix(); + + for (let r = startRow; r <= endRow; r++) { + for (let c = index; c <= index; c++) { + rangeMatrix.setValue(r, c, sheetMatrix.getValue(r, c) || {}); + } + } + + return rangeMatrix; + } + + /** + * en: + * + * The process of copying a range to another range is obtained by the following rules + * + * [The two-dimensional array composed of the content or format to be assigned] => [Target range] + * + * 1. 1 -> 1: 1 => 1 + * 2. N -> 1: N => N + * 3. 1 -> N: N => N + * 4. N1 -> N2: + * 1) N1 N2; If not, N1 => N1 (refer to office excel, different from google sheet) + * 2) N1> N2: N1 => N1 + * + * The above four cases can be combined and processed as + * + * Case 1, 1/2/4-2 merged into N1 => N1 + * Case 2, 3/4-1 merge into N1 * X => N2 or Case 1 + * + * In the end we only need to judge whether N2 is a multiple of N1 + * + * zh: + * + * 处理复制一个范围到另一个范围,由以下规则得到 + * + * [将要赋值的内容或者格式组成的二维数组] => [目标范围] + * + * 1. 1 -> 1 : 1 => 1 + * 2. N -> 1 : N => N + * 3. 1 -> N : N => N + * 4. N1 -> N2 : + * 1) N1 < N2 : 如果N2是N1的倍数X, N1 * X => N2; 如果不是,N1 => N1(参考office excel,和google sheet不同) + * 2) N1 > N2 : N1 => N1 + * + * 上面四种情况,可以合并处理为 + * 情况一, 1/2/4-2合并为 N1 => N1 + * 情况二, 3/4-1合并为 N1 * X => N2 或者情况一 + * + * 最终我们只需要判断N2是否是N1的倍数 + * + */ + private _handleCopyRange( + originRange: Range, + destinationRange: IRangeData + ): [ICellDataMatrix, IRangeData]; + private _handleCopyRange( + originRange: Range, + destinationRange: Range + ): [ICellDataMatrix, IRangeData]; + private _handleCopyRange(...argument: any): [ICellDataMatrix, IRangeData] { + const originRange = argument[0]; + let destinationRange = argument[1]; + destinationRange = Tuples.checkup(argument, Range, Range) + ? destinationRange.getRangeData() + : destinationRange; + + const cellData = originRange._worksheet.getCellMatrix(); + const { startRow, endRow, startColumn, endColumn } = + originRange.getRangeData(); + let { + startRow: dStartRow, + endRow: dEndRow, + startColumn: dStartColumn, + endColumn: dEndColumn, + } = destinationRange; + + const originRows = endRow - startRow + 1; + const originColumns = endColumn - startColumn + 1; + const destinationRows = dEndRow - dStartRow + 1; + const destinationColumns = dEndColumn - dStartColumn + 1; + + let value: ICellDataMatrix = []; + let range: IRangeData; + + // judge whether N2 is a multiple of N1 + if ( + destinationRows % originRows === 0 && + destinationColumns % originColumns === 0 + ) { + /** + * A1,B1 => A1,B1,C1,D1 + * A2,B2 A2,B2,C2,D2 + * A3,B3,C3,D3 + * A4,B4,C4,D4 + */ + for (let r = 0; r < destinationRows; r++) { + const row = []; + for (let c = 0; c < destinationColumns; c++) { + // Retrieve the corresponding cell data from the original range, {} as a fallback + const cell = + cellData.getValue( + (r + startRow) % originRows, + (c + startColumn) % originColumns + ) || {}; + row.push(cell); + } + value.push(row); + } + + range = destinationRange; + } else { + value = cellData + .getFragments(startRow, endRow, startColumn, endColumn) + .getData(); + + // Extend the destination to the same size as the original range + dEndRow += originRows - destinationRows; + dEndColumn += originColumns - destinationColumns; + + range = { + startRow: dStartRow, + endRow: dEndRow, + startColumn: dStartColumn, + endColumn: dEndColumn, + }; + } + + return [value, range]; + } + + forEach(action: (row: number, column: number) => void): void { + const { startRow, startColumn, endRow, endColumn } = this._rangeData; + for (let i = startRow; i <= endRow; i++) { + for (let j = startColumn; j <= endColumn; j++) { + action(i, j); + } + } + } + + /** + * Randomizes the order of the rows in the given range. + * TODO:待研究特性 + * 1. 公式内的范围也会变化 + * 2. 并不是所有范围都支持随机处理 + * @returns + */ + randomize() { + const { _context, _worksheet, _commandManager, _rangeData } = this; + const { startRow, startColumn } = _rangeData; + const cellValue = new ObjectMatrix(); + + const value = Tools.randSort(this.getMatrix().toArray()); + + value.forEach((row, r) => + row.forEach((cell, c) => { + cell = cell as ICellData; + cellValue.setValue(r + startRow, c + startColumn, cell || {}); + }) + ); + + const setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: cellValue.getData(), + rangeData: _rangeData, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + } + + // /** + // * Set key for cell + // */ + // setCellId(): string; + // setCellId(id: string): string; + // setCellId(...argument: any): string { + // let id = argument[0]; + + // // TODO rowCount > 10000 use 8, rountCount > 10 0000 ,12位 100 0000 ,16位,1000 0000, 20位 + // if (typeof id !== 'string') { + // id = nanoid(6); + // } + + // const { _context, _worksheet, _commandManager, _rangeData } = this; + + // const cellValue = new ObjectMatrix(); + // cellValue.setValue(0, 0, Object.assign(this.getValue() || {}, { id })); + + // const setValue: ISetRangeDataActionData = { + // sheetId: _worksheet.getSheetId(), + // actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + // cellValue: cellValue.getData(), + // rangeData: _rangeData, + // }; + // const command = new Command(_context.getWorkBook(), setValue); + // _commandManager.invoke(command); + + // return id; + // } + + /** + * Determine whether a range is legal + */ + isValid(): boolean { + if (Object.values(this._rangeData).includes(-1)) { + return false; + } + return true; + } + + // /** + // * Applies a default row banding theme to the range. By default, the banding has header and no footer color. + // */ + // applyRowBanding(): Nullable; + + // /** + // * Applies a specified row banding theme to the range. By default, the banding has header and no footer color. + // * @param bandingTheme + // */ + // applyRowBanding(bandingTheme: BandingTheme | IBandingProperties): Nullable; + + // /** + // * Applies a specified row banding theme to the range with specified header and footer settings. + // * @param bandingTheme + // * @param showHeader + // * @param showFooter + // */ + // applyRowBanding( + // bandingTheme: BandingTheme | IBandingProperties, + // showHeader: boolean, + // showFooter: boolean + // ): Nullable; + // applyRowBanding(...argument: any): Nullable { + + // // default argument + // const bandedRangeId = 'banded-range-' + Tools.generateRandomId(); + // const rangeData = this.getRangeData() + // let rowProperties:IBanding = { + // bandingTheme:argument[0] || BandingTheme.LIGHT_GRAY, + // showHeader:argument[1] || true, + // showFooter:argument[2] || false, + // } + + // const { _context, _commandManager } = this; + // // Check whether the incoming range has been set to alternate colors + // const bandings = this.getSheet().getBandings(); + // const isIntersection = + // bandings && + // bandings.find((banding: Banding) => + // banding.getRange().isIntersection(this) + // ); + // if (isIntersection) { + // console.error( + // 'You cannot add alternating background colors to a range that already has alternating background colors.' + // ); + // return + // } + + // // Organize action data + // const actionData: IAddBandingActionData = { + // actionName: ACTION_NAMES.ADD_BANDING_ACTION, + // bandedRange:{ + // bandedRangeId, + // rangeData, + // rowProperties + // }, + // sheetId: this.getSheet().getSheetId(), + // }; + + // // Execute action + // const command = new Command(_context.getWorkBook(), actionData); + // _commandManager.invoke(command); + + // return this.getSheet().getBandingById(bandedRangeId) + // } + // /** + // * Applies a default row banding theme to the range. By default, the banding has header and no footer color. + // */ + // applyRowBanding(): Nullable; + + // /** + // * Applies a specified row banding theme to the range. By default, the banding has header and no footer color. + // * @param bandingTheme + // */ + // applyRowBanding( + // bandingTheme: BandingTheme | IBandingProperties + // ): Nullable; + + // /** + // * Applies a specified row banding theme to the range with specified header and footer settings. + // * @param bandingTheme + // * @param showHeader + // * @param showFooter + // */ + // applyRowBanding( + // bandingTheme: BandingTheme | IBandingProperties, + // showHeader: boolean, + // showFooter: boolean + // ): Nullable; + // applyRowBanding(...argument: any): Nullable { + // // default argument + // const bandedRangeId = 'banded-range-' + Tools.generateRandomId(); + // const rangeData = this.getRangeData(); + // let rowProperties: IBanding = { + // bandingTheme: argument[0] || BandingTheme.LIGHT_GRAY, + // showHeader: argument[1] || true, + // showFooter: argument[2] || false, + // }; + + // const { _worksheet } = this; + // // Check whether the incoming range has been set to alternate colors + // const bandedRanges = _worksheet.getConfig().bandedRanges; + // const isIntersection = + // bandedRanges && + // bandedRanges.find((bandedRange: IBandedRange) => + // _worksheet.getRange(bandedRange.rangeData).isIntersection(this) + // ); + // if (isIntersection) { + // console.error( + // 'You cannot add alternating background colors to a range that already has alternating background colors.' + // ); + // return; + // } + + // return new Banding(this._worksheet).addRowBanding({ + // bandedRangeId, + // rangeData, + // rowProperties, + // }); + // } + + /** + * Creates a filter applied to the range. There can be at most one filter in a sheet. + * @returns The new filter. + */ + // createFilter(): Filter { + // return new Filter(this); + // } +} + +// Alternating Colors = bandingIns, range.applyRowBanding() bandingIns.setRowBanding(XXX) // bandingIns.remove(id) diff --git a/packages/core/src/Sheets/Domain/RangeList.ts b/packages/core/src/Sheets/Domain/RangeList.ts new file mode 100644 index 0000000000..d5653cc896 --- /dev/null +++ b/packages/core/src/Sheets/Domain/RangeList.ts @@ -0,0 +1,743 @@ +import { WorkSheet } from './index'; +import { Context } from '../../Basics'; +import { + Command, + CommandManager, + IClearRangeActionData, + ISetRangeDataActionData, + ISetRangeFormulaActionData, + ISetRangeStyleActionData, + ISetRangeValueActionData, +} from '../../Command'; + +import { ACTION_NAMES } from '../../Const'; +import { + BooleanNumber, + HorizontalAlign, + VerticalAlign, + WrapStrategy, +} from '../../Enum'; +import { + ICellData, + ICellV, + IOptionsData, + IRangeData, + IRangeType, + IStyleData, + ITextDecoration, +} from '../../Interfaces'; +import { Nullable, ObjectMatrix, Tools, Tuples } from '../../Shared'; +import { WorkBook } from './WorkBook'; +/** + * A collection of one or more Range instances in the same sheet. + * + * @remarks + * You can use this class to apply operations on collections of non-adjacent ranges or cells. + * + * Reference from: https://developers.google.com/apps-script/reference/spreadsheet/range-list + * + */ +export class RangeList { + private _rangeListId: string; + + private _commandManager: CommandManager; + + private _workBook: WorkBook; + + private _context: Context; + + private _worksheet: WorkSheet; + + private _rangeList: IRangeData[]; + + constructor(worksheet: WorkSheet, rangeList: IRangeType[]) { + this._context = worksheet.getContext(); + this._workBook = this._context.getWorkBook(); + this._commandManager = this._context.getCommandManager(); + this._worksheet = worksheet; + this._rangeList = []; + + // Convert the rangeList passed in by the user into a standard format + rangeList.forEach((range: IRangeType) => { + // this._rangeList.push( + // new TransformTool(this._workBook).transformRangeType(range) + // ); + this._rangeList.push( + this._context.getWorkBook().transformRangeType(range).rangeData + ); + }); + + // The user entered an invalid range + if (this._rangeList[0].startRow === -1) { + console.error('Invalid range,default set startRow -1'); + } + } + + getRangeList(): IRangeData[] { + return this._rangeList; + } + + /** + * Selects the list of Range instances. + * + * @returns The list of active ranges, for chaining. + */ + activate(): RangeList { + const { _context, _commandManager } = this; + // The user entered an invalid range + if (this._rangeList[0].startRow === -1) { + console.error('Invalid range,default set startRow -1'); + return this; + } + + this._worksheet.getSelection().setSelection({ selection: this._rangeList }); + + return this; + } + + /** + * Break all horizontally- or vertically-merged cells contained within the range list into individual cells again. + */ + // TODO + // breakApart() {} + + /** + * Sets the value for each Range in the range list. The value can be numeric, string, boolean or date. If it begins with '=' it is interpreted as a formula. + * + * @param value The value for the range list. + * @returns This range list, for chaining. + */ + setValue(value: string | number | boolean): RangeList { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + // collect action list + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + let cellValue = new ObjectMatrix(); + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + cellValue.setValue(startRow, startColumn, { + m: `${value}`, + v: value, + }); + } + } + + let setValue: ISetRangeDataActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_DATA_ACTION, + cellValue: cellValue.getData(), + rangeData: range, + }; + return setValue; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + /** + * Clears the range of contents, formats, and data validation rules for each Range in the range list. + * + * @returns This range list, for chaining. + */ + clear(): RangeList; + + /** + * Clears the range of contents, format, data validation rules, and comments, as specified with the given options. By default all data is cleared. + * + * @param options A JavaScript object that specifies advanced parameters, as listed IOptionsData. + * @returns This range list, for chaining. + */ + clear(options: IOptionsData): RangeList; + clear(...argument: any): RangeList { + const { _context, _worksheet, _commandManager, _rangeList } = this; + + // default options + let options = { + formatOnly: true, + contentsOnly: true, + commentsOnly: true, + validationsOnly: true, + skipFilteredRows: true, + }; + if (Tuples.checkup(argument, Tuples.OBJECT_TYPE)) { + options = argument[0]; + } + + // collect action list + const clearList = _rangeList.map((range) => { + let clearRange: IClearRangeActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.CLEAR_RANGE_ACTION, + options, + rangeData: range, + }; + return clearRange; + }); + + let command = new Command(_context.getWorkBook(), ...clearList); + _commandManager.invoke(command); + + return this; + } + + /** + * Clears text formatting for each Range in the range list. + * + * This clears text formatting for each range, but does not reset any number formatting rules. + * @returns This range list, for chaining. + */ + clearFormat(): RangeList { + return this.clear({ formatOnly: true }); + } + + /** + * Clears the content of each Range in the range list, leaving the formatting intact. + * + * @returns This range list, for chaining. + */ + clearContent(): RangeList { + return this.clear({ contentsOnly: true }); + } + + /** + * Clears the note for each Range in the range list. + * + * @returns This range list, for chaining. + */ + clearNote(): RangeList { + return this.clear({ commentsOnly: true }); + } + + /** + * set style + * + * @param value style value + * @param type style type + * @returns This range list, for chaining. + * @internal @preapproved + */ + private _setStyle( + value: Nullable, + type: string + ) { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + // string converted to a two-dimensional array + const styleObj: IStyleData = { [type]: value }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: range, + }; + + return setStyle; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the background color for each Range in the range list. + * + * @param color The background color code in CSS notation such as '#ffffff' or 'white'; a null value resets the color. + * @returns This range list, for chaining. + */ + setBackground(color: Nullable): RangeList { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + // string converted to a two-dimensional array + const styleObj = { + bg: { + rgb: color, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: range, + }; + + return setStyle; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the background to the given RGB color. + * + * @param red The red value in RGB notation. + * @param green The green value in RGB notation. + * @param blue The blue value in RGB notation. + * @returns This range list, for chaining. + */ + setBackgroundRGB(red: number, green: number, blue: number): RangeList { + const rgbString = `RGB(${red},${green},${blue})`; + + let { _rangeList, _context, _commandManager, _worksheet } = this; + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + // string converted to a two-dimensional array + const styleObj = { + bg: { + rgb: rgbString, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: range, + }; + + return setStyle; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + // TODO + // setBorder(top, left, bottom, right, vertical, horizontal) {} + // setBorder(top, left, bottom, right, vertical, horizontal, color, style) {} + + /** + * Sets the font color for each Range in the range list. Color is represented in in CSS notation; + * + * @param color The font color in CSS notation such as '#ffffff' or 'white'; a null value resets the color. + * @returns This range list, for chaining. + */ + setFontColor(color: Nullable): RangeList { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + // string converted to a two-dimensional array + const styleObj = { + cl: { + rgb: color, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: range, + }; + + return setStyle; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the font family for each Range in the range list. + * + * @param fontFamily The font family to set; a null value resets the font family. + * @returns This range list, for chaining. + */ + setFontFamily(fontFamily: Nullable): RangeList { + return this._setStyle(fontFamily, 'ff'); + } + // setFontLine(fontLine) {} + + /** + * Sets the underline for each Range in the range list. + * + * @param fontLine The underline to set; a null value resets the underline. + * @returns This range list, for chaining. + */ + setUnderline(fontLine: Nullable): RangeList { + const textDecoration: ITextDecoration = { + s: fontLine ? BooleanNumber.TRUE : BooleanNumber.FALSE, + }; + return this._setStyle(textDecoration, 'ul'); + } + + /** + * Sets the Overline for each Range in the range list. + * + * @param fontLine The Overline to set; a null value resets the Overline. + * @returns This range list, for chaining. + */ + setOverline(fontLine: Nullable): RangeList { + const textDecoration: ITextDecoration = { + s: fontLine ? BooleanNumber.TRUE : BooleanNumber.FALSE, + }; + return this._setStyle(textDecoration, 'ol'); + } + + /** + * Sets the StrikeThrough for each Range in the range list. + * + * @param fontLine The StrikeThrough to set; a null value resets the StrikeThrough. + * @returns This range list, for chaining. + */ + setStrikeThrough(fontLine: Nullable): RangeList { + const textDecoration: ITextDecoration = { + s: fontLine ? BooleanNumber.TRUE : BooleanNumber.FALSE, + }; + return this._setStyle(textDecoration, 'st'); + } + + /** + * Sets the font size (in points) for each Range in the range list. + * + * @param size A font point size. + * @returns This range list, for chaining. + */ + setFontSize(size: Nullable): RangeList { + return this._setStyle(size, 'fs'); + } + + /** + * Set the font style for each Range in the range list + * + * @param fontStyle The font style, either 'italic' or 'normal'; a null value resets the font style. + * @returns This range list, for chaining. + */ + setFontStyle(fontStyle: Nullable): RangeList { + const fontBoolean = fontStyle ? BooleanNumber.TRUE : BooleanNumber.FALSE; + return this._setStyle(fontBoolean, 'it'); + } + + /** + * Set the font style for each Range in the range list. + * + * @param fontWeight The font weight, either 'bold' or 'normal'; a null value resets the font weight. + * @returns This range list, for chaining. + */ + setFontWeight(fontWeight: Nullable): RangeList { + const fontBoolean = fontWeight ? BooleanNumber.TRUE : BooleanNumber.FALSE; + return this._setStyle(fontBoolean, 'bl'); + } + + /** + * Updates the formula for each Range in the range list. + * + * @param formula A string representing the formula to set. + * @returns This range list, for chaining. + */ + setFormula(formula: string): RangeList { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + let cellValue = new ObjectMatrix(); + for (let r = startRow; r <= endRow; r++) { + for (let c = startColumn; c <= endColumn; c++) { + cellValue.setValue(r, c, formula); + } + } + + let setValue: ISetRangeFormulaActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_FORMULA_ACTION, + cellFormula: cellValue.getData(), + rangeData: range, + }; + + return setValue; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + /** + * Set the horizontal alignment for each Range in the range list. + * + * @param alignment The alignment, either 'left', 'center' or 'right'; a null value resets the alignment. + * @returns This range list, for chaining. + */ + setHorizontalAlignment(alignment: Nullable): RangeList { + return this._setStyle(alignment, 'ht'); + } + + /** + * Sets the note text for each Range in the range list. + * + * @param note The note text to set; a null value removes the note. + * @returns This range list, for chaining. + */ + // setNote(note: Nullable): RangeList { + // let { _rangeList, _context, _commandManager, _worksheet } = this; + + // const setList = _rangeList.map((range) => { + // let { startRow, startColumn, endRow, endColumn } = range; + + // let cellValue = new ObjectMatrix(); + // for (let r = startRow; r <= endRow; r++) { + // for (let c = startColumn; c <= endColumn; c++) { + // cellValue.setValue(r, c, note); + // } + // } + + // let setValue: ISetRangeNoteActionData = { + // sheetId: _worksheet.getSheetId(), + // actionName: ACTION_NAMES.SET_RANGE_NOTE_ACTION, + // cellNote: cellValue.getData(), + // rangeData: range, + // }; + + // return setValue; + // }); + + // let command = new Command(_context.getWorkBook(), ...setList); + // _commandManager.invoke(command); + // return this; + // } + + /** + * Sets the note text for each Range in the range list. + * + * @param numberFormat A number format string. + * @returns This range list, for chaining. + */ + // setNumberFormat(numberFormat: string): RangeList { + // let { _rangeList, _context, _commandManager, _worksheet } = this; + + // const setList = _rangeList.map((range) => { + // let { startRow, startColumn, endRow, endColumn } = range; + + // let cellValue = new ObjectMatrix(); + // for (let r = 0; r <= endRow - startRow; r++) { + // for (let c = 0; c <= endColumn - startColumn; c++) { + // const fm = { + // f: numberFormat, + // t: FormatType.NUMBER, + // }; + // cellValue.setValue(r + startRow, c + startColumn, JSON.stringify(fm)); + // } + // } + + // let setValue: ISetRangeFormatActionData = { + // sheetId: _worksheet.getSheetId(), + // actionName: ACTION_NAMES.SET_RANGE_FORMAT_ACTION, + // cellFormat: cellValue.getData(), + // rangeData: range, + // }; + + // return setValue; + // }); + + // let command = new Command(_context.getWorkBook(), ...setList); + // _commandManager.invoke(command); + // return this; + // } + + /** + * Sets the note text for each Range in the range list. + */ + // TODO + // setShowHyperlink(showHyperlink) {} + + /** + * Sets the text direction for the cells in each Range in the range list. + * + * @param direction The desired text direction; if null the direction is inferred before setting. + * @returns This range list, for chaining. + */ + setTextDirection(direction: Nullable): RangeList { + return this._setStyle(direction, 'td'); + } + + /** + * Sets the text rotation settings for the cells in each Range in the range list. + * + * @param degrees The desired angle between the standard orientation and the desired orientation. For left to right text, positive angles are in the counterclockwise direction. + * @returns This range list, for chaining. + */ + setTextRotation(degrees: number): RangeList { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + const styleObj = { + tr: { + v: 0, + a: Number(degrees), + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: range, + }; + + return setStyle; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + /** + * Set the vertical alignment for each Range in the range list. + * + * @param alignment The alignment, either 'top', 'middle' or 'bottom'; a null value resets the alignment. + * @returns This range list, for chaining. + */ + setVerticalAlignment(alignment: Nullable): RangeList { + this._setStyle(alignment, 'vt'); + return this; + } + + /** + * Sets whether or not to stack the text for the cells for each Range in the range list. + * + * @param isVertical Whether or not to stack the text. + * @returns This range list, for chaining. + */ + setVerticalText(isVertical: BooleanNumber): RangeList { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + const styleObj = { + tr: { + v: isVertical, + a: 0, + }, + }; + + const stylesMatrix = Tools.fillObjectMatrix( + endRow - startRow + 1, + endColumn - startColumn + 1, + styleObj + ); + + const setStyle: ISetRangeStyleActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_STYLE_ACTION, + value: stylesMatrix, + rangeData: range, + }; + + return setStyle; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } + + /** + * Set text wrapping for each Range in the range list. + * + * @param isWrapEnabled Whether to wrap text or not. + * @returns This range list, for chaining. + */ + setWrap(isWrapEnabled: BooleanNumber): RangeList { + return this._setStyle(isWrapEnabled, 'tb'); + } + + /** + * Sets the text wrapping strategy for each Range in the range list. + * + * @param strategy The desired wrapping strategy. + * @returns This range list, for chaining. + */ + setWrapStrategy(strategy: WrapStrategy): RangeList { + return this._setStyle(strategy, 'tb'); + } + + /** + * Trims the whitespace (such as spaces, tabs, or new lines) in every cell in this range list. + * + * @returns This range list, for chaining. + */ + trimWhitespace(): RangeList { + let { _rangeList, _context, _commandManager, _worksheet } = this; + + const setList = _rangeList.map((range) => { + let { startRow, startColumn, endRow, endColumn } = range; + + const sheetMatrix = this._worksheet.getCellMatrix(); + const regx = /\s+/g; + let cellValue = new ObjectMatrix(); + for (let r = startRow; r < endRow + 1; r++) { + for (let c = startColumn; c < endColumn + 1; c++) { + let m = sheetMatrix.getValue(r, c)!.m!; + const value = m.replace(regx, ''); + cellValue.setValue(r, c, value || ''); + } + } + + let setValue: ISetRangeValueActionData = { + sheetId: _worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_RANGE_VALUE_ACTION, + cellValue: cellValue.getData(), + rangeData: range, + }; + + return setValue; + }); + + let command = new Command(_context.getWorkBook(), ...setList); + _commandManager.invoke(command); + return this; + } +} diff --git a/packages/core/src/Sheets/Domain/RowManager.ts b/packages/core/src/Sheets/Domain/RowManager.ts new file mode 100644 index 0000000000..67ef784bfd --- /dev/null +++ b/packages/core/src/Sheets/Domain/RowManager.ts @@ -0,0 +1,131 @@ +import { Command } from '../../Command'; +import { ISetRowHeightActionData } from '../Action'; +import { ACTION_NAMES } from '../../Const'; +import { BooleanNumber } from '../../Enum'; +import { IRowData } from '../../Interfaces'; +import { Nullable } from '../../Shared'; +import { ObjectArray, ObjectArrayType } from '../../Shared/ObjectArray'; +import { WorkSheet } from './WorkSheet'; + +/** + * Manage configuration information of all rows, get row height, row length, set row height, etc. + */ +export class RowManager { + private _rowData: ObjectArray; + + private _workSheet: WorkSheet; + + constructor(workSheet: WorkSheet, data: ObjectArrayType>) { + this._workSheet = workSheet; + this._rowData = new ObjectArray(data); + } + + /** + * Get height and hidden status of columns in the sheet + * @returns + */ + getRowData(): ObjectArray { + return this._rowData; + } + + getRowDatas(rowPos: number, numRows: number): ObjectArray { + const rowData = new ObjectArray(); + for (let i = rowPos; i < rowPos + numRows; i++) { + const data = this.getRowOrCreate(i); + rowData.push(data); + } + return rowData; + } + + /** + * Get the height of given row + * @param rowPos row index + * @returns + */ + getRowHeight(rowPos: number): number; + /** + * Get the height of more rows + * @param rowPos row index + * @param numRows row count + * @returns + */ + getRowHeight(rowPos: number, numRows: number): number; + getRowHeight(...argument: any): number { + const { _workSheet } = this; + const { _rowData } = this; + const config = _workSheet.getConfig(); + let height: number = 0; + if (argument.length === 1) { + const row = _rowData.obtain(argument[0], { + hd: BooleanNumber.FALSE, + h: config.defaultRowHeight, + }); + height = row.h; + } else if (argument.length === 2) { + for (let i = argument[0]; i < argument[0] + argument[1]; i++) { + const row = _rowData.obtain(i, { + hd: BooleanNumber.FALSE, + h: config.defaultRowHeight, + }); + height += row.h; + } + } + + return height; + } + + /** + * Set height of one or more rows + * @param rowIndex row index + * @param rowHeight row height array + * @returns + */ + setRowHeight(rowIndex: number, rowHeight: number[]) { + const _context = this._workSheet.getContext(); + const _commandManager = this._workSheet.getCommandManager(); + const setRowHeight: ISetRowHeightActionData = { + sheetId: this._workSheet.getSheetId(), + actionName: ACTION_NAMES.SET_ROW_HEIGHT_ACTION, + rowIndex, + rowHeight, + }; + const command = new Command(_context.getWorkBook(), setRowHeight); + _commandManager.invoke(command); + } + + /** + * Get row data of given row + * @param rowPos row index + * @returns + */ + getRow(rowPos: number): Nullable { + const { _rowData } = this; + return _rowData.get(rowPos); + } + + /** + * Get count of column in the sheet + * @returns + */ + getSize(): number { + return this._rowData.getLength(); + } + + /** + * Get given row data or create a row data when it's null + * @param rowPos row index + * @returns + */ + getRowOrCreate(rowPos: number): IRowData { + const { _workSheet } = this; + const { _rowData } = this; + const row = _rowData.get(rowPos); + if (row) { + return row; + } + const config = _workSheet.getConfig(); + const create = { hd: BooleanNumber.FALSE, h: config.defaultRowHeight }; + _rowData.set(rowPos, create); + return create; + } +} diff --git a/packages/core/src/Sheets/Domain/Selection.ts b/packages/core/src/Sheets/Domain/Selection.ts new file mode 100644 index 0000000000..044219f1bf --- /dev/null +++ b/packages/core/src/Sheets/Domain/Selection.ts @@ -0,0 +1,770 @@ +import { Context } from '../../Basics'; +import { Command, CommandManager } from '../../Command'; +import { ISetSelectionActivateActionData } from '../Action'; +import { ACTION_NAMES, DEFAULT_SELECTION } from '../../Const'; +import { Direction } from '../../Enum'; +import { IRangeData, IRangeType, ISelectionData } from '../../Interfaces'; +import { Nullable, Tools } from '../../Shared'; +import { Range } from './Range'; +import { RangeList } from './RangeList'; +import { WorkBook } from './WorkBook'; +import { WorkSheet } from './WorkSheet'; + +/** + * + * Access the current active selection in the active sheet. + * + * @remarks + * A selection is the set of cells the user has highlighted in the sheet, which can be non-adjacent ranges. One cell in the selection is the current cell, where the user's current focus is. The current cell is highlighted with a darker border in the UniverSheet UI. + * + * Current selection, each worksheet contains an array of selections, the last selection is the current active selection by default, or the selection with the active field set to true is the current active selection + * + * Reference: https://developers.google.com/apps-script/reference/spreadsheet/selection + * + * @privateRemarks + * zh: 当前选区,每个工作表包含一个选区数组,默认最后一个选区为当前激活选区,或者当前激活单元格所在选区为激活选区 + * + * @beta + */ +export class Selection { + private _commandManager: CommandManager; + + private _context: Context; + + private _workBook: WorkBook; + + private _workSheet: WorkSheet; + + private _activeRange: Range; + + private _currentCell: Range; + + private _activeRangeList: RangeList; + + constructor(worksheet: WorkSheet) { + this._workSheet = worksheet; + this._context = worksheet.getContext(); + this._commandManager = this._context.getCommandManager(); + this._workBook = worksheet.getContext().getWorkBook(); + + // set default selection + this._activeRangeList = this._workSheet.getRangeList([DEFAULT_SELECTION]); + this._activeRange = this._workSheet.getRange(DEFAULT_SELECTION); + this._currentCell = this._workSheet.getRange(DEFAULT_SELECTION); + } + + setWorkSheet(workSheet: WorkSheet): void { + this._workSheet = workSheet; + } + + /** + * Update the range of the selection + * + * @param param0 - selection data + * @returns + */ + setSelection({ selection, cell }: ISelectionData = {}): Range { + // if (!selection && !cell){ + // return console.error('Must provide selection or cell parameters'); + // } + + let activeRangeList: IRangeData[]; + + let activeRange: IRangeData; + + let currentCell: IRangeData; + + // selection is Range instance + if (selection instanceof Range) { + // get range data + activeRange = selection.getRangeData(); + + // The user entered an invalid range + if (activeRange.startRow === -1) { + console.error('Invalid selection, default set startRow -1'); + } + + // set range list + activeRangeList = [activeRange]; + + // get top-left cell in active range as current cell + currentCell = { + startRow: activeRange.startRow, + endRow: activeRange.startRow, + startColumn: activeRange.startColumn, + endColumn: activeRange.startColumn, + }; + } else if (selection && WorkBook.isIRangeType(selection)) { + // Convert the selection passed in by the user into a standard format + // activeRange = new TransformTool(this._workBook).transformRangeType( + // selection + // ); + activeRange = this._workBook.transformRangeType(selection).rangeData; + // The user entered an invalid range + if (activeRange.startRow === -1) { + console.error('Invalid selection, default set startRow -1'); + } + + // set range list + activeRangeList = [activeRange]; + + // get top-left cell in active range as current cell + currentCell = { + startRow: activeRange.startRow, + endRow: activeRange.startRow, + startColumn: activeRange.startColumn, + endColumn: activeRange.startColumn, + }; + } else if (selection instanceof Array) { + // Array is to prevent type detection + + // Convert the selection passed in by the user into a standard format + activeRangeList = selection.map((item: IRangeType) => { + // item = new TransformTool(this._workBook).transformRangeType(item); + const itemData: IRangeData = + this._workBook.transformRangeType(item).rangeData; + // The user entered an invalid range + if (itemData.startRow === -1) { + console.error('Invalid selection, default set startRow -1'); + } + + return itemData; + }); + + activeRange = activeRangeList[activeRangeList.length - 1]; + + currentCell = { + startRow: activeRange.startRow, + endRow: activeRange.startRow, + startColumn: activeRange.startColumn, + endColumn: activeRange.startColumn, + }; + } else { + // The current selection is taken by default + activeRangeList = this._activeRangeList.getRangeList(); + activeRange = this._activeRange.getRangeData(); + currentCell = this._currentCell.getRangeData(); + } + + // update current cell based on currentCell + if (cell) { + // currentCell = new TransformTool(this._workBook).transformRangeType(cell); + currentCell = this._workBook.transformRangeType(cell).rangeData; + const activeRangeData: Nullable = Selection.cellInRange( + activeRangeList, + currentCell + ); + + // if cell not in range, reset new active range and active range list based on current cell + if (!activeRangeData) { + activeRange = currentCell; + activeRangeList = [activeRange]; + } + } + + const { _context, _workSheet, _commandManager } = this; + const setSelection: ISetSelectionActivateActionData = { + sheetId: _workSheet.getSheetId(), + actionName: ACTION_NAMES.SET_SELECTION_ACTION, + activeRangeList, + activeRange, + currentCell, + }; + + const command = new Command(_context.getWorkBook(), setSelection); + _commandManager.invoke(command); + + // 设置后获取范围(以A1:B10形式) + const text = `${ + Tools.chatAtABC(activeRange.startColumn) + (activeRange.startRow + 1) + }:${Tools.chatAtABC(activeRange.endColumn)}${activeRange.endRow + 1}`; + this._context + .getObserverManager() + .getObserver('onAfterSetSelectionObservable') + ?.notifyObservers(text); + return this._activeRange; + } + + /** + * get selection from active range instance + * @returns + */ + getSelection(): IRangeData { + return Tools.deepClone(this._activeRange.getRangeData()); + } + + /** + * Returns the selected range in the active sheet, or null if there is no active range. If multiple ranges are selected this method returns only the last selected range. + * + * @returns + */ + getActiveRange(): Range { + return this._activeRange; + } + + /** + * Returns the list of active ranges in the active sheet or null if there are no active ranges. + * If there is a single range selected, this behaves as a getActiveRange() call. + * 已迁移到SelectionManager + * @returns + */ + getActiveRangeList(): RangeList { + return this._activeRangeList; + } + + /** + * Returns the active sheet in the spreadsheet. + * + * @returns + */ + getActiveSheet(): Nullable { + return this._workBook.getActiveSheet(); + } + + /** + * Returns the current (highlighted) cell that is selected in one of the active ranges or null if there is no current cell. + * + * @returns + */ + getCurrentCell(): Range { + return this._currentCell; + } + + /** + * set current cell by range data + * + * @remarks + * Provide actual methods for range.activateAsCurrentCell + * If the specified cell is not present in any existing range, then the existing selection is removed and the cell becomes the current cell and the active range. + * Note: The specified Range must consist of one cell, otherwise it throws an exception. + * + * @param range - range data + * @returns + */ + setCurrentCell(range: Range | IRangeData): Selection { + if (Tools.isAssignableFrom(range, Range)) { + range = range.getRangeData(); + } + + if (range?.startRow === -1) { + console.error('Invalid range,default set startRow -1'); + return this; + } + + if ( + !( + range.startRow === range.endRow && + range.startColumn === range.endColumn + ) + ) { + console.error('Exception: Range must have a single cell.'); + return this; + } + // 2. set current cell + // this._currentCell = this._workSheet.getRange(range); + // const activeRange = this._workSheet.getRange(range); + + // 3. change active range + const activeRangeList = this._activeRangeList.getRangeList(); + + // If the specified cell is present in an existing range, then that range becomes the active range with the cell as the current cell. + // const _range = Selection.cellInRange(rangeList, range); + + // if (_range) { + // this._activeRange = this._workSheet.getRange(_range); + // } else { + // this._activeRange = this._workSheet.getRange(range); + // this._activeRangeList = this._workSheet.getRangeList([range]); + // } + + const { _context, _workSheet, _commandManager } = this; + const setSelection: ISetSelectionActivateActionData = { + sheetId: _workSheet.getSheetId(), + actionName: ACTION_NAMES.SET_SELECTION_ACTION, + activeRangeList, + activeRange: range, + currentCell: range, + }; + + const command = new Command(_context.getWorkBook(), setSelection); + _commandManager.invoke(command); + + return this; + } + + /** + * set activeRangeList, avtiveRange, currentCell + * @param activeRangeList active range list + * @param activeRange last selected range + * @param currentCell current cell range + * @returns + */ + setRanges( + activeRangeList: IRangeData | IRangeData[], + activeRange: IRangeData, + currentCell: IRangeData + ): void { + // Construct selection instance + + this._activeRangeList = this._workSheet.getRangeList( + activeRangeList as IRangeType[] + ); + this._activeRange = this._workSheet.getRange(activeRange); + this._currentCell = this._workSheet.getRange(currentCell); + } + + /** + * move to next range by direction + * + * @remarks + * Starting from the current cell and active range and moving in the given direction, returns an adjusted range where the appropriate edge of the range has been shifted to cover the next data cell while still covering the current cell. If the active range is unbounded along the dimension of the direction, the original active range is returned. If there is no current cell or active range, null is returned. This is equivalent to selecting a range in the editor and hitting Ctrl+Shift+[arrow key]. + * + * @returns + */ + getNextDataRange(direction: Direction): Range { + const expandRangeData = this._expandRange(direction); + + this.setSelection({ selection: expandRangeData }); + + return this.getActiveRange(); + } + + /** + * expend data range by Ctrl+Shift+[arrow key] + * + * @returns + */ + private _expandRange(direction: Direction): IRangeData { + const lastColumn = this._workSheet.getColumnCount(); + const lastRow = this._workSheet.getRowCount(); + const range = this._activeRange.getRangeData(); + let expandRangeData = DEFAULT_SELECTION; + const currentCell = this.getCurrentCell().getRangeData(); + + if (direction === Direction.RIGHT) { + if (currentCell.startColumn === range.startColumn) { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, range.endColumn); + let endColumn; + if (cellValue && cellValue.v === undefined) { + for (let i = range.endColumn + 1; i <= lastColumn; i++) { + if (i === lastColumn) { + endColumn = lastColumn; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v !== undefined) { + endColumn = i; + break; + } + } + } else { + for (let i = range.endColumn + 1; i <= lastColumn; i++) { + if (i === lastColumn) { + endColumn = lastColumn; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v === undefined) { + const prevCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i - 1); + if (prevCellValue && prevCellValue.v !== undefined) { + endColumn = i - 1; + break; + } + } + } + } + expandRangeData = { + startRow: range.startRow, + endRow: range.endRow, + startColumn: range.startColumn, + endColumn: endColumn !== undefined ? endColumn : range.endColumn, + }; + } else { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, range.startColumn); + let startColumn; + if (cellValue && cellValue.v === undefined) { + for ( + let i = range.startColumn + 1; + i <= currentCell.startColumn; + i++ + ) { + if (i === currentCell.startColumn) { + startColumn = currentCell.startColumn; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v !== undefined) { + startColumn = i; + break; + } + } + } else { + for ( + let i = range.startColumn + 1; + i <= currentCell.startColumn; + i++ + ) { + if (i === currentCell.startColumn) { + startColumn = currentCell.startColumn; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v === undefined) { + const prevCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i - 1); + if (prevCellValue && prevCellValue.v !== undefined) { + startColumn = i - 1; + break; + } + } + } + } + expandRangeData = { + startRow: range.startRow, + endRow: range.endRow, + startColumn: startColumn || currentCell.startColumn, + endColumn: range.endColumn, + }; + } + } + if (direction === Direction.LEFT) { + let startColumn; + let endColumn; + if (currentCell.endColumn === range.endColumn) { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, range.startColumn); + if (cellValue && cellValue.v === undefined) { + for (let i = range.startColumn - 1; i >= 0; i--) { + if (i === 0) { + startColumn = 0; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v !== undefined) { + startColumn = i; + break; + } + } + } else { + for (let i = range.startColumn - 1; i >= 0; i--) { + if (i === 0) { + startColumn = 0; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v === undefined) { + const prevCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i - 1); + if (prevCellValue && prevCellValue.v !== undefined) { + startColumn = i - 1; + break; + } + } + } + } + } else { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, range.endColumn); + if (cellValue && cellValue.v === undefined) { + for ( + let i = range.endColumn - 1; + i >= currentCell.startColumn; + i-- + ) { + if (i === currentCell.startColumn) { + endColumn = i; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v !== undefined) { + endColumn = i; + break; + } + } + } else { + for ( + let i = range.endColumn - 1; + i >= currentCell.startColumn; + i-- + ) { + if (i === currentCell.startColumn) { + endColumn = i; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i); + if (curCellValue && curCellValue.v === undefined) { + const prevCellValue = this._workSheet + .getCellMatrix() + .getValue(currentCell.startRow, i - 1); + if (prevCellValue && prevCellValue.v !== undefined) { + endColumn = i - 1; + break; + } + } + } + } + } + expandRangeData = { + startRow: range.startRow, + endRow: range.endRow, + startColumn: + startColumn !== undefined ? startColumn : range.startColumn, + endColumn: endColumn !== undefined ? endColumn : range.endColumn, + }; + } + if (direction === Direction.TOP) { + let startRow; + let endRow; + if (currentCell.endRow === range.endRow) { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(range.startRow, currentCell.startColumn); + if (cellValue && cellValue.v === undefined) { + for (let i = range.endRow - 1; i >= 0; i--) { + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + if ( + (curCellValue && curCellValue.v !== undefined) || + i === 0 + ) { + startRow = i; + break; + } + } + } else { + for (let i = range.startRow - 1; i >= 0; i--) { + if (i === 0) { + startRow = 0; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + if (curCellValue && curCellValue.v === undefined) { + const prevCellValue = this._workSheet + .getCellMatrix() + .getValue(i - 1, currentCell.startColumn); + if (prevCellValue && prevCellValue.v !== undefined) { + startRow = i - 1; + break; + } + } + } + } + } else { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(range.endRow, currentCell.startColumn); + if (cellValue && cellValue.v === undefined) { + for (let i = range.endRow - 1; i >= currentCell.startRow; i--) { + if (i === currentCell.startRow) { + endRow = currentCell.startRow; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + // if (i <= range.startRow) { + // endRow = range.startRow; + // if (i == 0) { + // startRow = 0; + // break; + // } + // if (i == range.startRow) continue; + // if (curCellValue && curCellValue.v !== undefined) { + // startRow = i; + // break; + // } + // } else { + if (curCellValue && curCellValue.v !== undefined) { + endRow = i; + break; + } + // } + } + } else { + for (let i = range.endRow - 1; i >= currentCell.startRow; i--) { + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + + if (i === currentCell.startRow) { + endRow = currentCell.startRow; + break; + } else if ( + curCellValue && + curCellValue.v === undefined && + i !== range.endRow - 1 + ) { + const nextCellValue = this._workSheet + .getCellMatrix() + .getValue(i + 1, currentCell.startColumn); + if (nextCellValue && nextCellValue.v !== undefined) { + endRow = i + 1; + break; + } + } + } + } + } + expandRangeData = { + startRow: startRow !== undefined ? startRow : range.startRow, + endRow: endRow !== undefined ? endRow : range.endRow, + startColumn: range.startColumn, + endColumn: range.endColumn, + }; + } + if (direction === Direction.BOTTOM) { + let startRow; + let endRow; + if (currentCell.startRow === range.startRow) { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(range.endRow, currentCell.startColumn); + if (cellValue && cellValue.v === undefined) { + for (let i = range.endRow + 1; i <= lastRow; i++) { + if (i === lastRow) { + endRow = lastRow; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + if (curCellValue && curCellValue.v !== undefined) { + endRow = i; + break; + } + } + } else { + for (let i = range.endRow + 1; i <= lastRow; i++) { + if (i === lastRow) { + endRow = lastRow; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + if (curCellValue && curCellValue.v === undefined) { + const nextCellValue = this._workSheet + .getCellMatrix() + .getValue(i + 1, currentCell.startColumn); + if (nextCellValue && nextCellValue.v !== undefined) { + endRow = i + 1; + break; + } + } + } + } + } else { + const cellValue = this._workSheet + .getCellMatrix() + .getValue(range.startRow + 1, currentCell.startColumn); + if (cellValue && cellValue.v === undefined) { + for ( + let i = range.startRow + 1; + i <= currentCell.startRow; + i++ + ) { + if (i === currentCell.startRow) { + startRow = currentCell.startRow; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + if (curCellValue && curCellValue.v !== undefined) { + startRow = i; + break; + } + } + } else { + for ( + let i = range.startRow + 1; + i <= currentCell.startRow; + i++ + ) { + if (i === currentCell.startRow) { + startRow = currentCell.startRow; + break; + } + const curCellValue = this._workSheet + .getCellMatrix() + .getValue(i, currentCell.startColumn); + if ( + curCellValue && + curCellValue.v === undefined && + i !== range.startRow + 1 + ) { + const prevCellValue = this._workSheet + .getCellMatrix() + .getValue(i - 1, currentCell.startColumn); + if (prevCellValue && prevCellValue.v !== undefined) { + startRow = i - 1; + break; + } + } + } + } + } + expandRangeData = { + startRow: startRow !== undefined ? startRow : range.startRow, + endRow: endRow !== undefined ? endRow : range.endRow, + startColumn: range.startColumn, + endColumn: range.endColumn, + }; + } + return expandRangeData; + } + + /** + * Determine whether the cell is in the rangeList + * + * @param rangeList + * @param cell + * @returns + */ + static cellInRange( + rangeList: IRangeData[], + cell: IRangeData + ): Nullable { + for (const item of rangeList) { + if ( + item.startRow <= cell.startRow && + cell.endRow <= item.endRow && + item.startColumn <= cell.startColumn && + cell.endColumn <= item.endColumn + ) { + return item; + } + } + + return null; + } +} diff --git a/packages/core/src/Sheets/Domain/Styles.ts b/packages/core/src/Sheets/Domain/Styles.ts new file mode 100644 index 0000000000..41fbf6cdd1 --- /dev/null +++ b/packages/core/src/Sheets/Domain/Styles.ts @@ -0,0 +1,61 @@ +import { nanoid } from 'nanoid'; +import { IStyleData } from '../../Interfaces'; +import { IKeyType, Nullable, Tools } from '../../Shared'; + +/** + * Styles in a workbook, cells locate styles based on style IDs + */ +export class Styles { + private _styles: IKeyType>; + + constructor(styles: IKeyType> = {}) { + this._styles = styles; + } + + each( + callback: ( + value: [string, Nullable], + index: number, + array: Array<[string, Nullable]> + ) => void + ) { + Object.entries(this._styles).forEach(callback); + return this; + } + + search(data: IStyleData): string { + const { _styles } = this; + + for (const id in _styles) { + if (Tools.diffValue(_styles[id], data)) { + return id; + } + } + return '-1'; + } + + get(id: string | Nullable): Nullable { + id = String(id); + if (typeof id !== 'string') return id; + return this._styles[id]; + } + + add(data: IStyleData): string { + const id = nanoid(6); + this._styles[id] = data; + return id; + } + + setValue(data: Nullable): Nullable { + if (data == null) return; + const result = this.search(data); + if (result !== '-1') { + return result; + } + return this.add(data); + } + + toJSON(): IKeyType> { + return this._styles; + } +} diff --git a/packages/core/src/Sheets/Domain/WorkBook.ts b/packages/core/src/Sheets/Domain/WorkBook.ts new file mode 100644 index 0000000000..bcff42e0ef --- /dev/null +++ b/packages/core/src/Sheets/Domain/WorkBook.ts @@ -0,0 +1,874 @@ +import { nanoid } from 'nanoid'; +import { + ACTION_NAMES, + DEFAULT_RANGE_ARRAY, + DEFAULT_WORKBOOK, + DEFAULT_WORKSHEET, +} from '../../Const'; +import { BooleanNumber } from '../../Enum'; +import { Context } from '../../Basics'; +import { Command, CommandManager, ISetSheetOrderActionData } from '../../Command'; + +import { + IColumnStartEndData, + IGridRange, + IRangeData, + IRangeStringData, + IRangeType, + IRowStartEndData, + IWorkbookConfig, + IWorksheetConfig, +} from '../../Interfaces'; + +import { + AttributeValue, + Container, + Inject, + IOCContainer, + PostConstruct, +} from '../../IOC'; + +import { ServerBase } from '../../Server/ServerBase'; +import { NameGen, Nullable, Tools } from '../../Shared'; +import { Tuples } from '../../Shared/Tuples'; +import { Range } from './Range'; +import { RangeList } from './RangeList'; +import { Selection } from './Selection'; +import { Styles } from './Styles'; +import { WorkSheet } from './WorkSheet'; +import { IInsertSheetActionData, IRemoveSheetActionData } from '../Action'; + +/** + * Access and create Univer Sheets files + */ +export class WorkBook { + /** + * sheets list + * @private + */ + private _worksheets: Map; + + /** + * Common style + * @private + */ + private _styles: Styles; + + /** + * number format + * @private + */ + // private _formatManage: FormatManager; + + @Container() + private _container: IOCContainer; + + @AttributeValue() + private _config: IWorkbookConfig; + + private _bookId: string; + + @Inject('Server') + private _server: ServerBase; + + @Inject('Context') + private _context: Context; + + @Inject('CommandManager') + private _commandManager: CommandManager; + + /** + * Get Default Sheet + * @private + */ + private _getDefaultWorkSheet(): void { + const { _context, _config, _worksheets } = this; + const { sheets, sheetOrder } = _config; + for (let sheetId in sheets) { + let config = sheets[sheetId]; + config.name = NameGen.getSheetName(config.name); + const worksheet = new WorkSheet(_context, config); + this._container.inject(worksheet); + _worksheets.set(worksheet.getSheetId(), worksheet); + sheetOrder.push(worksheet.getSheetId()); + } + } + + /** + * Get Default Active Sheet + * @private + */ + private _setDefaultActiveSheet(): void { + if (this._worksheets.size > 0) { + this._worksheets.forEach((sheet) => { + sheet.setStatus(BooleanNumber.FALSE); + }); + this._worksheets[0].setStatus(BooleanNumber.TRUE); + } + } + + @PostConstruct() + private _initialize(): void { + // merge config + this._config = Tools.commonExtend(DEFAULT_WORKBOOK, this._config); + + const { styles } = this._config; + this._bookId = this._config.id ?? nanoid(6); + this._styles = new Styles(styles); + this._worksheets = new Map(); + // this._formatManage = new FormatManager(); + this._getDefaultWorkSheet(); + } + + /** + * Get the range array based on the range string and sheet id + * + * @privateRemarks + * zh: 根据范围字符串和sheet id取得范围数组 + * + * @param txt - range string + * @returns + * + * @internal + */ + private _getCellRange(txt: IRangeStringData): IGridRange { + let sheetTxt: string = ''; + let rangeTxt: string | string[] = ''; + if (txt.indexOf('!') > -1) { + const val = txt.split('!'); + sheetTxt = val[0]; + rangeTxt = val[1]; + sheetTxt = sheetTxt.replace(/\\'/g, "'").replace(/''/g, "'"); + if ( + sheetTxt.substring(0, 1) === "'" && + sheetTxt.substring(sheetTxt.length - 1, 1) === "'" + ) { + sheetTxt = sheetTxt.substring(1, sheetTxt.length - 1); + } + } else { + rangeTxt = txt; + } + if (rangeTxt.indexOf(':') === -1) { + const row = parseInt(rangeTxt.replace(/[^0-9]/g, ''), 10) - 1; + const col = Tools.ABCatNum(rangeTxt.replace(/[^A-Za-z]/g, '')); + + if (!Number.isNaN(row) && !Number.isNaN(col)) { + const item = { + sheetId: sheetTxt, + rangeData: { + startRow: row, + endRow: row, + startColumn: col, + endColumn: col, + }, + }; + return item; + } + return DEFAULT_RANGE_ARRAY; + } + rangeTxt = rangeTxt.split(':'); + + const row: IRowStartEndData = [0, 0]; + const col: IColumnStartEndData = [0, 0]; + const maxRow = + this.getSheetBySheetName(sheetTxt)?.getMaxRows() || + this.getActiveSheet()?.getMaxRows(); + const maxCol = + this.getSheetBySheetName(sheetTxt)?.getMaxColumns() || + this.getActiveSheet()?.getMaxColumns(); + row[0] = parseInt(rangeTxt[0].replace(/[^0-9]/g, ''), 10) - 1; + row[1] = parseInt(rangeTxt[1].replace(/[^0-9]/g, ''), 10) - 1; + + if (Number.isNaN(row[0])) { + row[0] = 0; + } + + if (Number.isNaN(row[1])) { + row[1] = maxRow!; + } + + if (row[0] > row[1]) { + return DEFAULT_RANGE_ARRAY; + } + col[0] = Tools.ABCatNum(rangeTxt[0].replace(/[^A-Za-z]/g, '')); + col[1] = Tools.ABCatNum(rangeTxt[1].replace(/[^A-Za-z]/g, '')); + if (Number.isNaN(col[0])) { + col[0] = 0; + } + if (Number.isNaN(col[1])) { + col[1] = maxCol!; + } + if (col[0] > col[1]) { + return DEFAULT_RANGE_ARRAY; + } + + const item = { + sheetId: this.getSheetBySheetName(sheetTxt)?.getSheetId() || '', + rangeData: { + startRow: row[0], + endRow: row[1], + startColumn: col[0], + endColumn: col[1], + }, + }; + return item; + } + + _getWorksheets(): Map { + return this._worksheets; + } + + nextSheet(start: number): Nullable { + if (start >= 0) { + const { sheetOrder } = this._config; + for (let i = start; i < sheetOrder.length; i++) { + const worksheet = this._worksheets.get(sheetOrder[i]); + if (worksheet && !worksheet.isSheetHidden()) { + return worksheet; + } + } + if (start < sheetOrder.length) { + for (let i = start - 1; i >= 0; i--) { + const worksheet = this._worksheets.get(sheetOrder[i]); + if (worksheet && !worksheet.isSheetHidden()) { + return worksheet; + } + } + } + return this._worksheets.get(sheetOrder[sheetOrder.length - 1]); + } + } + + setContext(context: Context): void { + this._context = context; + } + + setCommandManager(commandManager: CommandManager): void { + this._commandManager = commandManager; + } + + insertSheet(): Nullable; + insertSheet(index: number): Nullable; + insertSheet(name: string): Nullable; + insertSheet(data: Partial): Nullable; + insertSheet(sheet: WorkSheet): Nullable; + insertSheet(name: string, index: number): Nullable; + insertSheet(index: number, data: Partial): Nullable; + insertSheet(index: number, sheet: WorkSheet): Nullable; + insertSheet(...argument: any[]): Nullable { + const { _context, _commandManager } = this; + + const before = _context.getContextObserver('onBeforeInsertSheetObservable'); + const after = _context.getContextObserver('onAfterInsertSheetObservable'); + + // insert empty sheet + if (Tools.hasLength(argument, 0)) { + const worksheetConfig = { + name: NameGen.getSheetName(), + status: 0, + id: nanoid(6), + }; + const index = this.getSheetSize(); + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + + if (Tools.hasLength(argument, 1)) { + // insert clone worksheet instance + if (Tools.isAssignableFrom(argument[0], WorkSheet)) { + const sheet = argument[0]; + const index = this.getSheetSize(); + const worksheetConfig = sheet.getConfig(); + worksheetConfig.id = nanoid(6); + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + + // insert sheet to index + if (Tools.isNumber(argument[0])) { + const index = argument[0]; + const worksheetConfig = { + name: NameGen.getSheetName(), + status: 0, + id: nanoid(6), + }; + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + + // insert sheet by sheet name + if (Tools.isString(argument[0])) { + const name = argument[0]; + const index = this.getSheetSize(); + const worksheetConfig = { + status: 0, + name, + id: nanoid(6), + }; + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + + // insert sheet by sheet config + if (Tools.isPlainObject(argument[0])) { + const worksheetConfig = argument[0] as IWorksheetConfig; + const index = this.getSheetSize(); + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + } + + if (Tools.hasLength(argument, 2)) { + // insert sheet name to index + if (Tools.isString(argument[0])) { + const name = argument[0]; + const index = argument[1]; + const worksheetConfig = { + status: 0, + name, + id: nanoid(6), + }; + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + + if (Tools.isNumber(argument[0])) { + // insert clone worksheet instance to index + if (Tools.isAssignableFrom(argument[1], WorkSheet)) { + const index = argument[0]; + const sheet = argument[1]; + const worksheetConfig = sheet.getConfig(); + worksheetConfig.id = nanoid(6); + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + if (Tools.isPlainObject(argument[1])) { + const index = argument[0]; + const worksheetConfig = argument[1] as IWorksheetConfig; + before.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + _commandManager.invoke( + new Command(_context.getWorkBook(), { + actionName: ACTION_NAMES.INSERT_SHEET_ACTION, + sheetId: worksheetConfig.id, + index, + sheet: worksheetConfig as IWorksheetConfig, + } as IInsertSheetActionData) + ); + after.notifyObservers({ + index, + sheetId: worksheetConfig.id, + }); + return worksheetConfig.id; + } + } + } + } + + getActiveSpreadsheet(): WorkBook { + return this; + } + + getStyles(): Styles { + return this._styles; + } + + getContext(): Context { + return this._context; + } + + getConfig(): IWorkbookConfig { + // const { _config } = this; + // const sheets = {}; + // config中的sheets数据传递到WorkSheet后就交由WorkSheet管理,此处的config不再更新 + // sheetOrder.forEach((sheetId) => { + // const sheet = this._worksheets.get(sheetId) as WorkSheet; + // const config = sheet.getConfig(); + // sheets[config.id] = config; + // }); + // this._config.sheets = sheets; + // this._config.styles = this._styles.toJSON(); + return this._config; + } + + create(name: string, row: number, column: number): WorkSheet; + create(name: string): WorkSheet; + create(...argument: unknown[]): unknown { + if (Tools.hasLength(argument, 1)) { + const { _container, _context } = this; + const name = argument[0]; + const conf = { ...DEFAULT_WORKSHEET, name }; + const worksheet = _container.getInstance( + 'WorkSheet', + _context, + conf + ); + this.insertSheet(worksheet); + return worksheet; + } + if (Tools.hasLength(argument, 3)) { + const { _container, _context } = this; + const name = argument[0]; + const rowCount = argument[1]; + const columnCount = argument[2]; + const conf = { ...DEFAULT_WORKSHEET, name, rowCount, columnCount }; + const worksheet = _container.getInstance( + 'WorkSheet', + _context, + conf + ); + this.insertSheet(worksheet); + return worksheet; + } + } + + setServerBase(server: ServerBase): void { + this._server = server; + } + + setDefaultActiveSheet(): void { + this._setDefaultActiveSheet(); + } + + getServer(): ServerBase { + return this._server; + } + + getIndexBySheetId(sheetId: string): number { + const { sheetOrder } = this._config; + return sheetOrder.findIndex((id) => id === sheetId); + } + + getActiveSheet(): WorkSheet { + const { sheetOrder } = this._config; + const activeSheetId = sheetOrder.find((sheetId) => { + const worksheet = this._worksheets.get(sheetId) as WorkSheet; + return worksheet.getStatus() === BooleanNumber.TRUE; + }); + if (!activeSheetId) { + console.warn('No active sheet, get first sheet'); + return this._worksheets[0]; + } + return this._worksheets.get(activeSheetId) as WorkSheet; + } + + getActiveSheetIndex(): number { + const { sheetOrder } = this._config; + return sheetOrder.findIndex((sheetId) => { + const worksheet = this._worksheets.get(sheetId) as WorkSheet; + if (worksheet.getStatus() === 1) { + return true; + } + return false; + }); + } + + getActiveRange(): Nullable { + const workSheet = this.getActiveSheet(); + if (workSheet) { + const selection = workSheet.getSelection(); + return selection.getActiveRange(); + } + return null; + } + + setActiveRange(range: Range): Nullable { + const workSheet = this.getActiveSheet(); + if (workSheet) { + return workSheet.setActiveSelection(range); + } + return null; + } + + setActiveRangeList(rangeList: IRangeType[]): Nullable { + const workSheet = this.getActiveSheet(); + if (workSheet) { + const activeRangeList = workSheet.getRangeList(rangeList); + activeRangeList.activate(); + return activeRangeList.getRangeList(); + } + return null; + } + + getActiveRangeList(): Nullable { + const workSheet = this.getActiveSheet(); + if (workSheet) { + const selection = workSheet.getSelection(); + return selection.getActiveRangeList(); + } + return null; + } + + getSelection(): Nullable { + const workSheet = this.getActiveSheet(); + if (workSheet) { + return workSheet.getSelection(); + } + } + + getCurrentCell(): Nullable { + const selection = this.getSelection(); + if (selection) { + return selection.getCurrentCell(); + } + } + + getSheetSize(): number { + return this._config.sheetOrder.length; + } + + /** + * Sets the specified cell as the current cell. + * @param cell + */ + setCurrentCell(cell: Range): Range { + return cell.activateAsCurrentCell(); + } + + /** + * Applies all pending Sheets changes. + * + * @returns void + */ + flush(): void {} + + setSheetOrder(sheetId: string, order: number): void { + // const { _sheetOrder } = this; + // const exclude = _sheetOrder.filter((currentId) => currentId !== sheetId); + // exclude.splice(order, 0, sheetId); + // this._sheetOrder = exclude; + const { _context, _commandManager } = this; + const observer = _context.getContextObserver('onSheetOrderObservable'); + const config: ISetSheetOrderActionData = { + actionName: ACTION_NAMES.SET_SHEET_ORDER_ACTION, + sheetId, + order, + }; + const command = new Command(_context.getWorkBook(), config); + _commandManager.invoke(command); + observer.notifyObservers(); + } + + getSheets(): WorkSheet[] { + const { sheetOrder } = this._config; + return sheetOrder.map((sheetId) => + this._worksheets.get(sheetId) + ) as WorkSheet[]; + } + + getSheetIndex(sheet: WorkSheet): number { + const { sheetOrder } = this._config; + return sheetOrder.findIndex((sheetId) => { + if (sheet.getSheetId() === sheetId) { + return true; + } + return false; + }); + } + + removeSheetBySheetId(sheetId: string): void { + const { _commandManager, _config } = this; + const { sheetOrder } = _config; + const sheet = this.getSheetBySheetId(sheetId); + + if (sheetOrder.length > 1 && sheet != null) { + const index = this.getSheetIndex(sheet); + const before = this.getContext().getContextObserver( + 'onBeforeRemoveSheetObservable' + ); + const after = this.getContext().getContextObserver( + 'onAfterRemoveSheetObservable' + ); + + before.notifyObservers({ + index, + }); + + _commandManager.invoke( + new Command(this, { + actionName: ACTION_NAMES.REMOVE_SHEET_ACTION, + sheetId, + } as IRemoveSheetActionData) + ); + + const needSwitch = sheet.getStatus() === BooleanNumber.TRUE; + if (needSwitch) { + const nextSheet = this.nextSheet(index); + if (nextSheet) { + nextSheet.activate(); + } + } + after.notifyObservers({ index, sheetId }); + } + } + + getSheetBySheetName(name: string): Nullable { + const { sheetOrder } = this._config; + const sheetId = sheetOrder.find((sheetId) => { + const worksheet = this._worksheets.get(sheetId) as WorkSheet; + return worksheet.getName() === name; + }) as string; + return this._worksheets.get(sheetId); + } + + getSheetBySheetId(sheetId: string): Nullable { + return this._worksheets.get(sheetId); + } + + /** + * Sets the active sheet in a spreadsheet. The Google Sheets UI displays the chosen sheet unless the sheet belongs to a different spreadsheet. + * + * @param sheet - The new active sheet. + * @returns {@link WorkSheet } the sheet that has been made the new active sheet + */ + setActiveSheet(sheet: WorkSheet): WorkSheet; + /** + * Sets the active sheet in a spreadsheet, with the option to restore the most recent selection within that sheet. The Google Sheets UI displays the chosen sheet unless the sheet belongs to a different spreadsheet. + * @param sheet - The new active sheet. + * @param restoreSelection - If true, the most recent selection of the new active sheet becomes selected again as the new sheet becomes active; if false, the new sheet becomes active without changing the current selection. + * @returns {@link WorkSheet} - the new active sheet + */ + setActiveSheet(sheet: WorkSheet, restoreSelection: boolean): WorkSheet; + setActiveSheet(...argument: any): WorkSheet { + let restoreSelection = false; + const worksheet: WorkSheet = argument[0]; + if (Tuples.checkup(argument, WorkSheet, Tuples.BOOLEAN_TYPE)) { + restoreSelection = argument[1]; + } + + worksheet.activate(); + + // restore selection + if (restoreSelection) { + worksheet.setActiveSelection(); + } + + return worksheet; + } + + // getFormatManager(): FormatManager { + // return this._formatManage; + // } + + getCommandManager(): CommandManager { + return this._commandManager; + } + + getPluginMeta(name: string): T { + return this._config.pluginMeta[name]; + } + + setPluginMeta(name: string, value: T) { + if (!this._config.pluginMeta) { + this._config.pluginMeta = {}; + } + return (this._config.pluginMeta[name] = value); + } + + /** + * Creates a builder for a conditional formatting rule. + */ + // newConditionalFormatRule(): ConditionalFormatRuleBuilder { + // return new ConditionalFormatRuleBuilder(); + // } + + // newFilterCriteria(): FilterCriteriaBuilder { + // return new FilterCriteriaBuilder(); + // } + + static isIRangeType(range: IRangeType | IRangeType[]): Boolean { + return typeof range === 'string' || 'startRow' in range || 'row' in range; + } + + /** + * transform any range type to range data + * + * @remarks + * e.g., + * "A1:B1", "Sheet2!A1:B1" + * + * or + * + * { + * row:[0,1], + * column:[0,1] + * } + * + * or + * + * { + * startRow:0 , + * startColumn:0, + * endRow:1, + * endColumn:1, + * } + * + * to + * + * { + * startRow:0 , + * startColumn:0, + * endRow:1, + * endColumn:1, + * } + * + * IRangeType[] is to prevent type detection + * + * @param range support all range types + * + * @returns range data + */ + transformRangeType(range: IRangeType | IRangeType[]): IGridRange { + if (typeof range === 'string') { + const gridRange = this._getCellRange(range); + return gridRange; + } + if (typeof range !== 'string' && 'row' in range) { + return { + sheetId: '', + rangeData: { + startRow: range.row[0], + startColumn: range.column[0], + endRow: range.row[1], + endColumn: range.column[1], + }, + }; + // ref : https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-the-in-operator + } + if (typeof range !== 'string' && 'startRow' in range) { + return { sheetId: '', rangeData: range }; + } + + return DEFAULT_RANGE_ARRAY; + } + + /** + * + * @param rangeData + * @returns + */ + static rangeDataToRangeStringData(rangeData: IRangeData) { + const { startRow, endRow, startColumn, endColumn } = rangeData; + + return `${Tools.chatAtABC(startColumn) + (startRow + 1)}:${Tools.chatAtABC( + endColumn + )}${endRow + 1}`; + } + + load(config: IWorkbookConfig) { + // TODO: new Command + this._config = config; + } + + save(): IWorkbookConfig { + // TODO + return this._config; + } +} diff --git a/packages/core/src/Sheets/Domain/WorkSheet.ts b/packages/core/src/Sheets/Domain/WorkSheet.ts new file mode 100644 index 0000000000..ae47effa18 --- /dev/null +++ b/packages/core/src/Sheets/Domain/WorkSheet.ts @@ -0,0 +1,2239 @@ +import { nanoid } from 'nanoid'; +import { Context } from '../../Basics'; +import { + IInsertColumnDataActionData, + BorderStyleData, + Command, + CommandManager, + IActionData, + IClearRangeActionData, + IInsertRowActionData, + IInsertRowDataActionData, + IRemoveColumnDataAction, + IRemoveRowActionData, + IRemoveRowDataActionData, + ISetCollapseAllColumnGroupsData, + ISetCollapseAllRowGroupsData, + ISetColumnHideActionData, + ISetColumnShowActionData, + ISetRowHideActionData, + ISetRowShowActionData, + ISetTabColorActionData, + ISetWorkSheetActivateActionData, + ISetWorkSheetHideActionData, + ISetWorkSheetStatusActionData, +} from '../../Command'; +import { IInsertColumnActionData, IRemoveColumnAction } from '../Action'; +import { ACTION_NAMES, DEFAULT_WORKSHEET } from '../../Const'; +import { Direction, BooleanNumber, SheetTypes } from '../../Enum'; +import { + IBorderStyleData, + ICellData, + IOptionData, + IRangeData, + IRangeStringData, + IRangeType, + ISelectionData, + IStyleData, + IWorksheetConfig, +} from '../../Interfaces'; +import { StructGroup } from '../../Module/Group'; +import { OverGridImage } from '../../Module/Image'; +import { Protection } from '../../Module/Protection'; +import { Nullable, ObjectMatrix, Tools } from '../../Shared'; +import { ObjectArray } from '../../Shared/ObjectArray'; +import { Tuples } from '../../Shared/Tuples'; +import { ColumnManager } from './ColumnManager'; +import { Freeze } from './Freeze'; +import { Merges } from './Merges'; +import { Range } from './Range'; +import { RangeList } from './RangeList'; +import { RowManager } from './RowManager'; +import { Selection } from './Selection'; +import { WorkBook } from './WorkBook'; + +/** + * Access and modify spreadsheet sheets. + * + * @remarks + * Common operations are renaming a sheet and accessing range objects from the sheet. + * + * Reference from: https://developers.google.com/apps-script/reference/spreadsheet/sheet + * + * @beta + */ +export class WorkSheet { + protected _commandManager: CommandManager; + + protected _selection: Selection; + + protected _context: Context; + + protected _workbook: WorkBook; + + protected _config: IWorksheetConfig; + + protected _initialized: boolean; + + protected _merges: Merges; + + // protected _borderStyles: BorderStyles; + + protected _sheetId: string; + + protected _cellData: ObjectMatrix; + + protected _rowManager: RowManager; + + protected _protection: Protection; + + protected _columnManager: ColumnManager; + + protected _rowStatusGroup: StructGroup; + + protected _columnStatusGroup: StructGroup; + + protected _overGridImages: OverGridImage[]; + // protected _bandings: Array; + + private _setWorkbookObserver(): void {} + + private _initialize(): void { + this._selection = new Selection(this); + this._merges = new Merges(this, this._config.mergeData); + this._setWorkbookObserver(); + } + + constructor(context: Context); + constructor(context: Context, config: Partial); + constructor(...argument: any) { + if (Tools.hasLength(argument, 1)) { + const context = argument[0]; + const config = Tools.commonExtend({}, DEFAULT_WORKSHEET); + // const config = Tools.deepMerge({}, DEFAULT_WORKSHEET); + argument = [context, config]; + } + if (Tools.hasLength(argument, 2)) { + this._context = argument[0]; + // this._config = argument[1]; + this._config = Tools.commonExtend(DEFAULT_WORKSHEET, argument[1]); + // this._config = Tools.deepMerge({}, DEFAULT_WORKSHEET, argument[1]); + + const { columnData, rowData, cellData } = this._config; + this._sheetId = this._config.id ?? nanoid(6); + this._initialized = false; + // this._selection = new Selection(this); + // this._borderStyles = new BorderStyles(this); + this._cellData = new ObjectMatrix(cellData); + this._protection = new Protection(); + this._workbook = this._context.getWorkBook(); + this._commandManager = this._context.getCommandManager(); + // this._selection.setWorkSheet(this); + this._rowManager = new RowManager(this, rowData); + this._columnManager = new ColumnManager(this, columnData); + // overImage + this._overGridImages = new Array(); + // banding TODO init Banding instance from config.bandedRanges + // this._bandings = new Array(); + // group + this._rowStatusGroup = new StructGroup(); + this._columnStatusGroup = new StructGroup(); + this._initialize(); + } + } + + /** + * Returns WorkSheet Cell Data Matrix + * @returns + */ + getCellMatrix(): ObjectMatrix { + return this._cellData; + } + + /** + * Activates this sheet. Does not alter the sheet itself, only the parent's notion of the active sheet. + * @returns Sheet — The newly active sheet. + */ + activate(): WorkSheet { + if (this._config.status === BooleanNumber.TRUE) { + return this; + } + const { _context, _commandManager } = this; + const before = _context.getContextObserver( + 'onBeforeChangeActiveSheetObservable' + ); + const after = _context.getContextObserver( + 'onAfterChangeActiveSheetObservable' + ); + const setActive: ISetWorkSheetActivateActionData = { + sheetId: this._sheetId, + actionName: ACTION_NAMES.SET_WORKSHEET_ACTIVATE_ACTION, + status: BooleanNumber.TRUE, + }; + const command = new Command(_context.getWorkBook(), setActive); + before.notifyObservers({ sheet: this }); + _commandManager.invoke(command); + after.notifyObservers({ sheet: this }); + return this; + } + + /** + * Sets Update WorkSheet Configure + * @param config config of worksheet + * @returns current worksheet instance + */ + setConfig(config: Partial): WorkSheet { + this._config = Tools.commonExtend(DEFAULT_WORKSHEET, config); + // this._config = Tools.deepMerge(DEFAULT_WORKSHEET, config); + return this; + } + + /** + * Returns Row Manager + * @returns Row Manager + */ + getRowManager(): RowManager { + return this._rowManager; + } + + /** + * Returns the ID of the sheet represented by this object. + * @returns ID of the sheet + */ + getSheetId(): string { + return this._sheetId; + } + + /** + * Returns Column Manager + * @returns Column Manager + */ + getColumnManager(): ColumnManager { + return this._columnManager; + } + + /** + * Set the sheet name. + * + * @example + * Set new name: + * ``` + * worksheet.setName('NewSheet') + * ``` + * + * @param name - new sheet name + * @returns current worksheet instance + * + * @alpha + */ + setName(name: string): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const before = _context.getContextObserver( + 'onBeforeChangeSheetNameObservable' + ); + const after = _context.getContextObserver( + 'onAfterChangeSheetNameObservable' + ); + const configure = { + actionName: ACTION_NAMES.SET_WORKSHEET_NAME_ACTION, + sheetName: name, + sheetId: _sheetId, + }; + + const workbook = _context.getWorkBook(); + const sheets = workbook.getSheets(); + for (let i = 0; i < sheets.length; i++) { + const sheet = sheets[i]; + if (sheet !== this && sheet.getName() === name) { + console.warn('Duplicate name'); + return this; + } + } + + before.notifyObservers(); + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + after.notifyObservers({ name, sheet: this }); + return this; + } + + /** + * Returns the name of the sheet. + * @returns name of the sheet + */ + getName(): string { + return this._config.name; + } + + /** + * Returns WorkSheet Clone Object + * @returns WorkSheet Clone Object + */ + clone(): WorkSheet { + const { _config, _context } = this; + const copy = Tools.deepClone(_config); + return new WorkSheet(_context, copy); + } + + /** + * Returns Application Global Context + * @returns Global Context + */ + getContext(): Context { + return this._context; + } + + /** + * Returns WorkSheet Merges Manage + * + * @returns merge instance + */ + getMerges(): Merges { + return this._merges; + } + + /** + * Returns WorkSheet Command Manager + * @returns + */ + getCommandManager(): CommandManager { + return this._commandManager; + } + + /** + * Returns Sheet Data To Array + * @remarks {@link ICellData} data type of cell. + * @returns Sheet Data To Array + */ + getSheetData(): ICellData[][] { + // TODO: Whether to support multiple types, the length attribute will be used in other methods + // TODO: + // return this._cellData.toArray(); + return []; + } + + /** + * Returns User Selection Range + * @param range range types + * @returns range instance + */ + getRange(range: IRangeType): Range; + /** + * Returns User Selection Range + * @param row row index + * @param column column index + * @returns range instance + */ + getRange(row: number, column: number): Range; + /** + * Returns User Selection Range + * @param row row index + * @param column column index + * @param numRows row counr + * @returns range instance + */ + getRange(row: number, column: number, numRows: number): Range; + /** + * Returns User Selection Range + * @param row row index + * @param column column index + * @param numRows row counr + * @param numColumns column counr + * @returns range instance + */ + getRange( + row: number, + column: number, + numRows: number, + numColumns: number + ): Range; + /** + * Returns User Selection Range + * @param a1Notation One of the range types + * @remarks {@link IRangeStringData} e.g.,"A1:B2","sheet1!A1:B2","A1","1:1","A:A","AA1:BB2" + * @returns range instance + */ + getRange(a1Notation: IRangeStringData): Range; + getRange(...argument: any): Nullable { + if (Tools.hasLength(argument, 1)) { + return new Range(this, argument[0]); + } + if (Tools.hasLength(argument, 2)) { + return new Range(this, { + row: [argument[0], argument[0]], + column: [argument[1], argument[1]], + }); + } + if (Tools.hasLength(argument, 3)) { + return new Range(this, { + row: [argument[0], argument[2]], + column: [argument[1], argument[1]], + }); + } + if (Tools.hasLength(argument, 4)) { + return new Range(this, { + row: [argument[0], argument[2]], + column: [argument[1], argument[3]], + }); + } + } + + /** + * Returns User Multiple Selection Range List + * @param rangeList range types array + * @returns RangeList Instance + */ + getRangeList(rangeList: IRangeType[]): RangeList { + /** + * get range instance from range string or array + * const range = universheet.getRange('A1:B2') + * range.activate() // Activate the current range as the current selection + */ + return new RangeList(this, rangeList); + } + + /** + * Returns WorkSheet Status + * @returns WorkSheet Status + */ + getStatus() { + const { _config } = this; + return _config.status; + } + + /** + * Returns Protection + * @returns User Pretection + */ + getProtection(): Protection { + return this._protection; + } + + /** + * Sets WorkSheet Status + * @param status type of sheet status + * @returns WorkSheet Instance + */ + setStatus(status: BooleanNumber): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const configure: ISetWorkSheetStatusActionData = { + actionName: ACTION_NAMES.SET_WORKSHEET_STATUS_ACTION, + sheetId: _sheetId, + sheetStatus: status, + }; + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + return this; + } + + /** + * Return WorkSheetZoomRatio + * @return zoomRatio + */ + getZoomRatio(): number { + return this._config.zoomRatio || 1; + } + + /** + * Sets WorkSheetZoomRatio + * @param zoomRatio + */ + setZoomRatio(zoomRatio: number): void { + const { _context } = this; + const observer = _context.getContextObserver('onZoomRatioSheetObservable'); + this._config.zoomRatio = zoomRatio; + observer.notifyObservers({ zoomRatio }); + } + + /** + * Returns User Selection + * @returns User Selection + */ + getSelection(): Selection { + return this._selection; + } + + /** + * Returns Row StructGroup + * @returns Row StructGroup + */ + getRowStructGroup(): StructGroup { + return this._rowStatusGroup; + } + + /** + * Returns Column StructGroup + * @returns Column StructGroup + */ + getColumnStructGroup(): StructGroup { + return this._columnStatusGroup; + } + + /** + * Returns Copy WorkSheet + * @param name sheet name + * @returns Copy WorkSheet + */ + copy(name: string): WorkSheet { + const { _config, _context } = this; + const copy = Tools.deepClone(_config); + copy.name = name; + copy.status = BooleanNumber.FALSE; + copy.id = nanoid(); + return new WorkSheet(_context, copy); + } + + /** + * Returns WorkSheet Configures + * @returns WorkSheet Configures + */ + getConfig(): IWorksheetConfig { + // update merge data + this._config.mergeData = this._merges.getMergeData(); + return this._config; + } + + /** + * Inserts a blank row in a sheet at the specified location. + * @param rowPosition row index + * @returns WorkSheet Instance + */ + insertRows(rowPosition: number): WorkSheet; + /** + * Inserts a blank row in a sheet at the specified location. + * @param rowPosition row index + * @param numberRows row count + * @returns WorkSheet Instance + */ + insertRows(rowPosition: number, numberRows: number): WorkSheet; + insertRows(...argument: any): WorkSheet { + let rowIndex: number = 0; + let numRows: number = 1; + + if (Tools.hasLength(argument, 1)) { + rowIndex = argument[0]; + } + if (Tools.hasLength(argument, 2)) { + rowIndex = argument[0]; + numRows = argument[1]; + } + + const { _context, _commandManager, _sheetId } = this; + const insertRowData: IInsertRowDataActionData = { + actionName: ACTION_NAMES.INSERT_ROW_DATA_ACTION, + sheetId: _sheetId, + rowIndex, + rowData: new ObjectMatrix(new ObjectArray(numRows)).toJSON(), + }; + const insertRow: IInsertRowActionData = { + actionName: ACTION_NAMES.INSERT_ROW_ACTION, + sheetId: _sheetId, + rowIndex, + rowCount: numRows, + }; + const command = new Command( + _context.getWorkBook(), + insertRowData, + insertRow + ); + _commandManager.invoke(command); + return this; + } + + /** + * Inserts a number of rows after the given row position. + * @param afterPosition row index + * @returns WorkSheet Instance + */ + insertRowAfter(afterPosition: number): WorkSheet; + /** + * Inserts a number of rows after the given row position. + * @param afterPosition row index + * @param howMany row count + * @returns WorkSheet Instance + */ + insertRowAfter(afterPosition: number, howMany: number): WorkSheet; + insertRowAfter(...argument: any): WorkSheet { + let rowIndex: number = 0; + let numRows: number = 1; + + if (Tools.hasLength(argument, 1)) { + rowIndex = argument[0] + 1; + } + if (Tools.hasLength(argument, 2)) { + rowIndex = argument[0] + 1; + numRows = argument[1]; + } + + const { _context, _commandManager, _sheetId } = this; + const insertRowData = { + actionName: ACTION_NAMES.INSERT_ROW_DATA_ACTION, + sheetId: _sheetId, + rowIndex, + rowData: new ObjectMatrix(new ObjectArray(numRows)).toJSON(), + }; + const insertRow = { + actionName: ACTION_NAMES.INSERT_ROW_ACTION, + sheetId: _sheetId, + rowIndex, + rowCount: numRows, + }; + + const command = new Command( + _context.getWorkBook(), + insertRowData, + insertRow + ); + _commandManager.invoke(command); + return this; + } + + /** + * Inserts a number of rows before the given row position. + * @param beforePosition row index + * @returns WorkSheet Instance + */ + insertRowBefore(beforePosition: number): WorkSheet; + /** + * Inserts a number of rows before the given row position. + * @param beforePosition row index + * @param howMany row count + * @returns WorkSheet Instance + */ + insertRowBefore(beforePosition: number, howMany: number): WorkSheet; + insertRowBefore(...argument: any): WorkSheet { + let rowIndex: number = 0; + let numRows: number = 1; + + if (Tools.hasLength(argument, 1)) { + rowIndex = argument[0] - 1; + } + if (Tools.hasLength(argument, 2)) { + rowIndex = argument[0] - 1; + numRows = argument[1]; + } + + if (rowIndex < 0) { + rowIndex = 0; + } + + const { _context, _commandManager, _sheetId } = this; + const insertRowData = { + actionName: ACTION_NAMES.INSERT_ROW_DATA_ACTION, + sheetId: _sheetId, + rowIndex, + rowData: new ObjectMatrix(new ObjectArray(numRows)).toJSON(), + }; + const insertRow = { + actionName: ACTION_NAMES.INSERT_ROW_ACTION, + sheetId: _sheetId, + rowIndex, + rowCount: numRows, + }; + const command = new Command( + _context.getWorkBook(), + insertRowData, + insertRow + ); + _commandManager.invoke(command); + return this; + } + + /** + * Inserts a blank column in a sheet at the specified location. + * @param columnIndex column index + * @returns WorkSheet Instance + */ + insertColumns(columnIndex: number): WorkSheet; + /** + * Inserts a blank column in a sheet at the specified location. + * @param columnIndex column index + * @param numColumns column count + * @returns WorkSheet Instance + */ + insertColumns(columnIndex: number, numColumns: number): WorkSheet; + insertColumns(...argument: any): WorkSheet { + let columnIndex: number = 0; + let numColumns: number = 1; + + if (Tools.hasLength(argument, 1)) { + columnIndex = argument[0]; + } + if (Tools.hasLength(argument, 2)) { + columnIndex = argument[0]; + numColumns = argument[1]; + } + + const { _context, _commandManager, _sheetId } = this; + const columnData = new ObjectMatrix(); + this._cellData.forEach((index) => { + for (let i = columnIndex; i < columnIndex + numColumns; i++) { + columnData.setValue(index, i - columnIndex, {}); + } + }); + const insertColumnData = { + actionName: ACTION_NAMES.INSERT_COLUMN_DATA_ACTION, + sheetId: _sheetId, + columnIndex, + columnData: columnData.toJSON(), + }; + const insertColumn = { + actionName: ACTION_NAMES.INSERT_COLUMN_ACTION, + sheetId: _sheetId, + columnIndex, + columnCount: numColumns, + }; + const command = new Command( + _context.getWorkBook(), + insertColumn, + insertColumnData + ); + _commandManager.invoke(command); + + return this; + } + + /** + * Inserts a number of columns before the given row position. + * @param beforePosition column index + * @returns WorkSheet Instance + */ + insertColumnBefore(beforePosition: number): WorkSheet; + /** + * Inserts a number of columns before the given row position. + * @param beforePosition column index + * @param howMany column count + * @returns WorkSheet Instance + */ + insertColumnBefore(beforePosition: number, howMany: number): WorkSheet; + insertColumnBefore(...argument: any): WorkSheet { + let columnIndex: number = 0; + let numColumns: number = 1; + + if (Tools.hasLength(argument, 1)) { + columnIndex = argument[0] - 1; + } + if (Tools.hasLength(argument, 2)) { + columnIndex = argument[0] - 1; + numColumns = argument[1]; + } + + if (columnIndex < 0) { + columnIndex = 0; + } + + const { _context, _commandManager, _sheetId } = this; + const columnData = new ObjectMatrix(); + this._cellData.forEach((index) => { + for (let i = columnIndex; i < columnIndex + numColumns; i++) { + columnData.setValue(index, i - columnIndex, {}); + } + }); + const insertColumnData: IInsertColumnDataActionData = { + actionName: ACTION_NAMES.INSERT_COLUMN_DATA_ACTION, + sheetId: _sheetId, + columnIndex, + columnData: columnData.toJSON(), + }; + const insertColumn: IInsertColumnActionData = { + actionName: ACTION_NAMES.INSERT_COLUMN_ACTION, + sheetId: _sheetId, + columnIndex, + columnCount: numColumns, + }; + const command = new Command( + _context.getWorkBook(), + insertColumn, + insertColumnData + ); + _commandManager.invoke(command); + + return this; + } + + /** + * Inserts a number of columns after the given row position. + * @param afterPosition column index + * @returns WorkSheet Instance + */ + insertColumnAfter(afterPosition: number): WorkSheet; + /** + * Inserts a number of columns after the given row position. + * @param afterPosition column index + * @param howMany column count + * @returns WorkSheet Instance + */ + insertColumnAfter(afterPosition: number, howMany: number): WorkSheet; + insertColumnAfter(...argument: any): WorkSheet { + let columnIndex: number = 0; + let numColumns: number = 1; + + if (Tools.hasLength(argument, 1)) { + columnIndex = argument[0] + 1; + } + if (Tools.hasLength(argument, 2)) { + columnIndex = argument[0] + 1; + numColumns = argument[1]; + } + + const { _context, _commandManager, _sheetId } = this; + const columnData = new ObjectMatrix(); + this._cellData.forEach((index) => { + for (let i = columnIndex; i < columnIndex + numColumns; i++) { + columnData.setValue(index, i - columnIndex, {}); + } + }); + const insertColumnData: IInsertColumnDataActionData = { + actionName: ACTION_NAMES.INSERT_COLUMN_DATA_ACTION, + sheetId: _sheetId, + columnIndex, + columnData: columnData.toJSON(), + }; + const insertColumn: IInsertColumnActionData = { + actionName: ACTION_NAMES.INSERT_COLUMN_ACTION, + sheetId: _sheetId, + columnIndex, + columnCount: numColumns, + }; + const command = new Command( + _context.getWorkBook(), + insertColumnData, + insertColumn + ); + _commandManager.invoke(command); + + return this; + } + + /** + * Clears the sheet of content and formatting information. + * @returns WorkSheet Instance + */ + clear(): WorkSheet; + /** + * Clears the sheet of content and formatting information. + * @param options clear option + * @returns WorkSheet Instance + */ + clear(options: IOptionData): WorkSheet; + clear(...argument: any): WorkSheet { + const { _context, _commandManager } = this; + + // collect all cell as a Range + const _range = { + startRow: 0, + endRow: this.getConfig().rowCount - 1, + startColumn: 0, + endColumn: this.getConfig().columnCount - 1, + }; + + // default options + let options = { + formatOnly: true, + contentsOnly: true, + }; + + if (Tuples.checkup(argument, Tuples.OBJECT_TYPE)) { + options = argument[0]; + } + + const setValue: IClearRangeActionData = { + sheetId: this._sheetId, + actionName: ACTION_NAMES.CLEAR_RANGE_ACTION, + options, + rangeData: _range, + }; + const command = new Command(_context.getWorkBook(), setValue); + _commandManager.invoke(command); + return this; + } + + /** + * Sets the sheet tab color. + * @param color A color code in CSS notation (like '#ffffff' or 'white'), or null to reset the tab color. + * @returns WorkSheet This sheet, for chaining. + */ + setTabColor(color: Nullable): WorkSheet { + const { _context, _commandManager } = this; + const observer = _context.getContextObserver( + 'onSheetTabColorChangeObservable' + ); + const setTabColor: ISetTabColorActionData = { + sheetId: this._sheetId, + actionName: ACTION_NAMES.SET_TAB_COLOR_ACTION, + color, + }; + const command = new Command(_context.getWorkBook(), setTabColor); + _commandManager.invoke(command); + observer.notifyObservers(); + return this; + } + + /** + * Hides this sheet. Has no effect if the sheet is already hidden. If this method is called on the only visible sheet, it throws an exception. + * @returns Sheet — The current sheet. + */ + hideSheet(): WorkSheet { + const { _context, _workbook, _commandManager } = this; + if (!this._config.hidden) { + const observer = _context.getContextObserver('onHideSheetObservable'); + const setHiddenAction: ISetWorkSheetHideActionData = { + sheetId: this._sheetId, + actionName: ACTION_NAMES.HIDE_SHEET_ACTION, + hidden: BooleanNumber.TRUE, + }; + const command = new Command(_workbook, setHiddenAction); + _commandManager.invoke(command); + + const needSwitch = this.getStatus() === BooleanNumber.TRUE; + if (needSwitch) { + const index = _workbook.getSheetIndex(this); + const nextSheet = _workbook.nextSheet(index); + if (nextSheet) { + nextSheet.activate(); + } + } + + observer.notifyObservers({ sheet: this }); + } + return this; + } + + /** + * Makes the sheet visible. Has no effect if the sheet is already visible. + * @returns WorkSheet Instance + */ + showSheet(): WorkSheet { + if (!this._config.hidden) { + return this; + } + const { _context, _commandManager } = this; + const setHidden: ISetWorkSheetHideActionData = { + sheetId: this._sheetId, + actionName: ACTION_NAMES.HIDE_SHEET_ACTION, + hidden: BooleanNumber.FALSE, + }; + const command = new Command(_context.getWorkBook(), setHidden); + _commandManager.invoke(command); + _context + .getContextObserver('onShowSheetObservable') + .notifyObservers({ sheet: this }); + return this; + } + + /** + * Sets commandManager + * @param commandManager Command Manager + * @returns commandManager + */ + setCommandManager(commandManager: CommandManager): WorkSheet { + this._commandManager = commandManager; + return this; + } + + /** + * restore the active selection region for this sheet. + * @returns Range — the newly active range + */ + setActiveSelection(): Range; + /** + * restore the active selection region for this sheet. + * @param range Range Instance of Selection + * @returns Range — the newly active range + */ + setActiveSelection(range: Range): Range; + /** + * restore the active selection region for this sheet. + * @param a1Notation One of the range types, the range to set as active, as specified in A1 notation or R1C1 notation + * @returns Range — the newly active range + */ + setActiveSelection(a1Notation: IRangeStringData): Range; + setActiveSelection(...argument: any): Range { + const range = argument[0]; + + // just send range, we will handle Range instance or range string in Selection class + return range + ? this._selection.setSelection({ selection: range }) + : this._selection.setSelection(); + } + + /** + * Returns the number of frozen rows. + * @returns the number of frozen rows + */ + getFrozenRows(): number { + return this._config.freezeRow; + } + + /** + * Sets the width of the given column to fit its contents. + * @param columnPosition + */ + // todo 调用columnManage的API + // autoResizeColumn(columnPosition: number): WorkSheet { + // return this; + // } + + /** + * Returns the number of frozen columns. + * @returns the number of frozen columns + */ + getFrozenColumns(): number { + return this._config.freezeColumn; + } + + /** + * Returns the current number of columns in the sheet, regardless of content. + * @returns the current number of columns in the sheet, regardless of content + */ + getMaxColumns(): number { + const { _config } = this; + const { columnCount } = _config; + return columnCount; + } + + /** + * Returns the current number of rows in the sheet, regardless of content. + * @returns the current number of rows in the sheet, regardless of content + */ + getMaxRows(): number { + const { _config } = this; + const { rowCount } = _config; + return rowCount; + } + + /** + * Returns the type of the sheet. + * @returns the type of the sheet + */ + getType(): SheetTypes { + const { _config } = this; + const { type } = _config; + return type; + } + + /** + * Clears the sheet of contents, while preserving formatting information. + * @returns WorkSheet Instance + */ + clearContents(): WorkSheet { + const { _config } = this; + const { rowCount, columnCount } = _config; + this.getRange({ + startRow: 0, + endRow: rowCount, + startColumn: 0, + endColumn: columnCount, + }).clear({ + contentsOnly: true, + }); + return this; + } + + /** + * Clears the sheet of formatting, while preserving contents. + * @returns WorkSheet Instance + */ + clearFormats(): WorkSheet { + const { _config } = this; + const { rowCount, columnCount } = _config; + this.getRange({ + startRow: 0, + endRow: rowCount, + startColumn: 0, + endColumn: columnCount, + }).clear({ + formatOnly: true, + }); + return this; + } + + /** + * Clears the sheet of all notes. + * @returns WorkSheet Instance + */ + clearNotes(): WorkSheet { + const { _config } = this; + const { rowCount, columnCount } = _config; + this.getRange({ + startRow: 0, + endRow: rowCount, + startColumn: 0, + endColumn: columnCount, + }).clear({ + commentsOnly: true, + }); + return this; + } + + /** + * Deletes the column at the given column position. + * @param columnPosition column index + * @returns WorkSheet Instance + */ + deleteColumn(columnPosition: number): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const deleteColumnData: IRemoveColumnDataAction = { + actionName: ACTION_NAMES.REMOVE_COLUMN_DATA_ACTION, + sheetId: _sheetId, + columnCount: 1, + columnIndex: columnPosition, + }; + + const deleteColumn: IRemoveColumnAction = { + actionName: ACTION_NAMES.REMOVE_COLUMN_ACTION, + sheetId: _sheetId, + columnCount: 1, + columnIndex: columnPosition, + }; + + const command = new Command( + _context.getWorkBook(), + deleteColumnData, + deleteColumn + ); + _commandManager.invoke(command); + + return this; + } + + /** + * Deletes a number of columns starting at the given column position. + * @param columnPosition column index + * @param howMany column count + * @returns WorkSheet Instance + */ + deleteColumns(columnPosition: number, howMany: number): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const deleteColumnData: IRemoveColumnDataAction = { + actionName: ACTION_NAMES.REMOVE_COLUMN_DATA_ACTION, + sheetId: _sheetId, + columnCount: howMany, + columnIndex: columnPosition, + }; + + const deleteColumn: IRemoveColumnAction = { + actionName: ACTION_NAMES.REMOVE_COLUMN_ACTION, + sheetId: _sheetId, + columnCount: howMany, + columnIndex: columnPosition, + }; + + const command = new Command( + _context.getWorkBook(), + deleteColumnData, + deleteColumn + ); + _commandManager.invoke(command); + + return this; + } + + /** + * Deletes the row at the given row position. + * @param rowPosition row index + * @returns WorkSheet Instance + */ + deleteRow(rowPosition: number): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const dataRowDelete: IRemoveRowDataActionData = { + actionName: ACTION_NAMES.REMOVE_ROW_DATA_ACTION, + sheetId: _sheetId, + rowCount: 1, + rowIndex: rowPosition, + }; + + const rowDelete: IRemoveRowActionData = { + actionName: ACTION_NAMES.REMOVE_ROW_ACTION, + sheetId: _sheetId, + rowCount: 1, + rowIndex: rowPosition, + }; + + const command = new Command( + _context.getWorkBook(), + dataRowDelete, + rowDelete + ); + _commandManager.invoke(command); + + return this; + } + + /** + * Deletes a number of rows starting at the given row position. + * @param rowPosition row index + * @param howMany row count + * @returns WorkSheet Instance + */ + deleteRows(rowPosition: number, howMany: number): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + + const dataRowDelete: IRemoveRowDataActionData = { + actionName: ACTION_NAMES.REMOVE_ROW_DATA_ACTION, + sheetId: _sheetId, + rowCount: howMany, + rowIndex: rowPosition, + }; + + const rowDelete: IRemoveRowActionData = { + actionName: ACTION_NAMES.REMOVE_ROW_ACTION, + sheetId: _sheetId, + rowCount: howMany, + rowIndex: rowPosition, + }; + + const command = new Command( + _context.getWorkBook(), + dataRowDelete, + rowDelete + ); + _commandManager.invoke(command); + + return this; + } + + /** + * Returns Row Count + * @returns Row Count + */ + getRowCount(): number { + return this._config.rowCount; + } + + /** + * Returns Column Count + * @returns Column Count + */ + getColumnCount(): number { + return this._config.columnCount; + } + + /** + * Returns Border Styles + * @returns Border Styles + */ + // getBorderStyles(): BorderStyles { + // return this._borderStyles; + // } + + /** + * Sets the BorderStyles + * @param rangeData + * @param style + * @param directions + */ + setBorderStyle( + rangeData: IRangeData, + style: IBorderStyleData, + directions: Direction[] + ) { + const { _commandManager, _sheetId } = this; + + const context = this.getContext(); + const matrix = this.getCellMatrix(); + const workbook = context.getWorkBook(); + const styles = workbook.getStyles(); + + const tr = this.getRange({ + startRow: rangeData.startRow - 1, + startColumn: rangeData.startColumn, + endRow: rangeData.startRow - 1, + endColumn: rangeData.endColumn, + }); + const lr = this.getRange({ + startRow: rangeData.startRow, + startColumn: rangeData.startColumn - 1, + endRow: rangeData.startRow, + endColumn: rangeData.endColumn - 1, + }); + const br = this.getRange({ + startRow: rangeData.endRow + 1, + startColumn: rangeData.startColumn, + endRow: rangeData.endRow + 1, + endColumn: rangeData.endColumn, + }); + const rr = this.getRange({ + startRow: rangeData.startRow, + startColumn: rangeData.endColumn + 1, + endRow: rangeData.startRow, + endColumn: rangeData.endColumn + 1, + }); + const cc = this.getRange(rangeData); + + const mtr = new ObjectMatrix(); + const mlr = new ObjectMatrix(); + const mbr = new ObjectMatrix(); + const mrr = new ObjectMatrix(); + const mcr = new ObjectMatrix(); + + tr.forEach((row, column) => { + const cell = matrix.getValue(row, column); + if (cell) { + const cellStyle = styles.get(cell.s); + if (cellStyle) { + const copy: IStyleData = Tools.deepClone(cellStyle); + if (copy.bd) { + delete copy.bd.b; + } + mtr.setValue(row, column, copy); + } + } + }); + br.forEach((row, column) => { + const cell = matrix.getValue(row, column); + if (cell) { + const cellStyle = styles.get(cell.s); + if (cellStyle) { + const copy: IStyleData = Tools.deepClone(cellStyle); + if (copy.bd) { + delete copy.bd.t; + } + mbr.setValue(row, column, copy); + } + } + }); + lr.forEach((row, column) => { + const cell = matrix.getValue(row, column); + if (cell) { + const cellStyle = styles.get(cell.s); + if (cellStyle) { + const copy: IStyleData = Tools.deepClone(cellStyle); + if (copy.bd) { + delete copy.bd.r; + } + mlr.setValue(row, column, copy); + } + } + }); + rr.forEach((row, column) => { + const cell = matrix.getValue(row, column); + if (cell) { + const cellStyle = styles.get(cell.s); + if (cellStyle) { + const copy: IStyleData = Tools.deepClone(cellStyle); + if (copy.bd) { + delete copy.bd.l; + } + mrr.setValue(row, column, copy); + } + } + }); + cc.forEach((row, column) => { + mcr.setValue(row, column, style); + }); + + const actions: IActionData[] = []; + + if (directions.includes(Direction.TOP)) { + const setBottomData: BorderStyleData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_BORDER_ACTION, + styles: mbr.toJSON(), + }; + actions.push(setBottomData); + } + if (directions.includes(Direction.BOTTOM)) { + const setTopData: BorderStyleData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_BORDER_ACTION, + styles: mtr.toJSON(), + }; + actions.push(setTopData); + } + if (directions.includes(Direction.LEFT)) { + const setRightData: BorderStyleData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_BORDER_ACTION, + styles: mrr.toJSON(), + }; + actions.push(setRightData); + } + if (directions.includes(Direction.RIGHT)) { + const setLeftData: BorderStyleData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_BORDER_ACTION, + styles: mlr.toJSON(), + }; + actions.push(setLeftData); + } + const setCCData: BorderStyleData = { + sheetId: _sheetId, + actionName: ACTION_NAMES.SET_BORDER_ACTION, + styles: mcr.toJSON(), + }; + actions.push(setCCData); + + const commandCC = new Command(workbook, ...actions); + _commandManager.invoke(commandCC); + + return this; + } + + /** + * Sets the specified cell as the current cell. + * @param cell cell range + * @returns Range Instance + */ + setCurrentCell(cell: Range): Range { + return cell.activateAsCurrentCell(); + } + + /** + * Returns the active cell in this sheet. + * @returns the active cell in this sheet + */ + getActiveCell(): Range { + return this.getSelection().getCurrentCell(); + } + + /** + * Returns the selected range in the active sheet + * @returns the selected range in the active sheet + */ + getActiveRange(): Range { + return this.getSelection().getActiveRange(); + } + + /** + * Returns Array OverGridImage + * @returns Array OverGridImage + */ + getOverGridImages(): OverGridImage[] { + return this._overGridImages; + } + + // /** + // * Returns all the bandings that are applied to any cells in this range. + // */ + // getBandings(): Array { + // return this._bandings; + // } + + // /** + // * get banding by range + // * @param range + // * @returns + // */ + // getBandingByRange(range: IRangeData): Nullable { + // return this._bandings.find((banding: Banding, i) => + // Tools.diffValue(banding.getRange().getRangeData(), range) + // ); + // } + // /** + // * get banding by bandedRangeId + // * @param bandedRangeId + // * @returns + // */ + // getBandingById(bandedRangeId: string): Nullable { + // return ( + // this._bandings.find( + // (banding: Banding, i) => + // banding.getBandedRange().bandedRangeId === bandedRangeId + // ) || null + // ); + // } + + // /** + // * remove a banding in this sheet. + // * @param range + // */ + // removeBandingByRangeData(range: IRangeData): void { + // // remove from list + // const removedBanding = this._bandings.find((banding: Banding, i) => { + // if (Tools.diffValue(banding.getRange().getRangeData(), range)) { + // return banding; + // } + // }); + // if (!removedBanding) { + // console.warn('Banding does not exist'); + // return; + // } + + // // remove from plugin meta + // removedBanding.remove(); + // } + + // /** + // * Applies a default column banding theme to the range. By default, the banding has header and no footer color. + // * @param range range to be set + // */ + // addBanding(range: IRangeType): Nullable; + // /** + // * Applies a specified column banding theme to the range. By default, the banding has header and no footer color. + // * @param range range to be set + // * @param bandingTheme A color theme to apply to the columns in the range,or custom color setting. + // */ + // addBanding( + // range: IRangeType, + // bandingTheme: BandingTheme | IBandingProperties + // ): Nullable; + // /** + // * Applies a specified column banding theme to the range with specified header and footer settings. + // * @param range range to be set + // * @param bandingTheme A color theme to apply to the columns in the range,or custom color setting. + // * @param showHeader If true, the banding theme header color is applied to the first column. + // * @param showFooter If true, the banding theme footer color is applied to the last column. + // */ + // addBanding( + // range: IRangeType, + // bandingTheme: BandingTheme | IBandingProperties, + // showHeader: boolean, + // showFooter: boolean + // ): Nullable; + // addBanding(...argument: any): Nullable { + // const rangeData: IRangeType = argument[0], + // bandingTheme: BandingTheme = argument[1] || BandingTheme.LIGHT_GRAY, + // showHeader: boolean = argument[2] || true, + // showFooter: boolean = argument[3] || false; + + // return this.getRange(rangeData).applyRowBanding( + // bandingTheme, + // showHeader, + // showFooter + // ); + // } + + /** + * Returns the list of active ranges in the active sheet + * @returns the list of active ranges in the active sheet + */ + getActiveRangeList(): RangeList { + return this.getSelection().getActiveRangeList(); + } + + /** + * Gets the position of the sheet in its parent spreadsheet. Starts at 1. + * @returns Gets the position of the sheet in its parent spreadsheet. Starts at 1. + */ + getIndex(): Nullable { + const worksheets = this._context.getWorkBook().getSheets(); + const index = worksheets.findIndex( + (sheet) => sheet && sheet.getSheetId() === this._sheetId + ); + if (index > -1) return index + 1; + return null; + } + + /** + * Returns the Sheets that contains this sheet. + * @returns the Sheets that contains this sheet. + */ + getParent(): WorkBook { + return this._context.getWorkBook(); + } + + /** + * isSheetHidden + * @returns hidden status of sheet + */ + isSheetHidden(): BooleanNumber { + return this._config.hidden; + } + + /** + * Sets the specified range as the active range in the active sheet, with the top left cell in the range as the current cell. + * @param range active range types + * @return Active Range + */ + setActiveRange(range: ISelectionData): Range { + return this.getSelection().setSelection(range); + } + + /** + * Sets the specified list of ranges as the active ranges in the active sheet. The last range in the list is set as the active range. + * @param rangeList active range types array + * @return Active Range List + */ + setActiveRangeList(rangeList: ISelectionData): RangeList { + const selection = this.getSelection(); + selection.setSelection(rangeList); + return selection.getActiveRangeList(); + } + + /** + * Hides the rows in the given range. + * @param row row range + * @returns WorkSheet Instance + */ + hideRow(row: Range): WorkSheet { + const range = row.getRangeData(); + const index = range.startRow; + const count = range.endRow - range.startRow + 1; + const { _context, _commandManager, _sheetId } = this; + const hideRow: ISetRowHideActionData = { + actionName: ACTION_NAMES.SET_HIDE_ROW_ACTION, + sheetId: _sheetId, + rowCount: count, + rowIndex: index, + }; + const command = new Command(_context.getWorkBook(), hideRow); + _commandManager.invoke(command); + return this; + } + + /** + * Hides one or more consecutive rows starting at the given index. + * @param rowIndex row index + * @returns WorkSheet Instance + */ + hideRows(rowIndex: number): WorkSheet; + /** + * Hides one or more consecutive rows starting at the given index. + * @param rowIndex row index + * @param numRows row count + * @returns WorkSheet Instance + */ + hideRows(rowIndex: number, numRows: number): WorkSheet; + hideRows(...argument: any): WorkSheet { + const index = --argument[0]; + let count = 1; + if (argument[1]) { + count = argument[1]; + } + const { _context, _commandManager, _sheetId } = this; + const hideRow: ISetRowHideActionData = { + actionName: ACTION_NAMES.SET_HIDE_ROW_ACTION, + sheetId: _sheetId, + rowCount: count, + rowIndex: index, + }; + const command = new Command(_context.getWorkBook(), hideRow); + _commandManager.invoke(command); + return this; + } + + /** + * Hides the columns in the given range. + * @param column column range + * @returns WorkSheet Instance + */ + hideColumn(column: Range): WorkSheet { + const range = column.getRangeData(); + const index = range.startColumn; + const count = range.endColumn - range.startColumn + 1; + const { _context, _commandManager, _sheetId } = this; + const hideColumn: ISetColumnHideActionData = { + actionName: ACTION_NAMES.SET_HIDE_COLUMN_ACTION, + sheetId: _sheetId, + columnCount: count, + columnIndex: index, + }; + const command = new Command(_context.getWorkBook(), hideColumn); + _commandManager.invoke(command); + return this; + } + + /** + * Hides one or more consecutive columns starting at the given index. + * @param columnIndex column index + * @returns WorkSheet Instance + */ + hideColumns(columnIndex: number): WorkSheet; + /** + * Hides one or more consecutive columns starting at the given index. + * @param columnIndex column index + * @param numColumns column count + * @returns WorkSheet Instance + */ + hideColumns(columnIndex: number, numColumns: number): WorkSheet; + hideColumns(...argument: any): WorkSheet { + const index = argument[0]; + let count = 1; + if (argument[1]) { + count = argument[1]; + } + const { _context, _commandManager, _sheetId } = this; + const hideColumn: ISetColumnHideActionData = { + actionName: ACTION_NAMES.SET_HIDE_COLUMN_ACTION, + sheetId: _sheetId, + columnCount: count, + columnIndex: index, + }; + const command = new Command(_context.getWorkBook(), hideColumn); + _commandManager.invoke(command); + return this; + } + + /** + * Unhides the row in the given range. + * @param row row range + * @return WorkSheet Instance + */ + unhideRow(row: Range): WorkSheet { + const range = row.getRangeData(); + const index = range.startRow; + const count = range.endRow - range.startRow + 1; + const { _context, _commandManager, _sheetId } = this; + const unhideRow: ISetRowShowActionData = { + actionName: ACTION_NAMES.SET_SHOW_ROW_ACTION, + sheetId: _sheetId, + rowCount: count, + rowIndex: index, + }; + const command = new Command(_context.getWorkBook(), unhideRow); + _commandManager.invoke(command); + return this; + } + + /** + * Unhides the column in the given range. + * @param column column range + * @return WorkSheet Instance + */ + unhideColumn(column: Range): WorkSheet { + const range = column.getRangeData(); + const index = range.startColumn; + const count = range.endColumn - range.startColumn + 1; + const { _context, _commandManager, _sheetId } = this; + const unhideColumn: ISetColumnShowActionData = { + actionName: ACTION_NAMES.SET_SHOW_COLUMN_ACTION, + sheetId: _sheetId, + columnCount: count, + columnIndex: index, + }; + const command = new Command(_context.getWorkBook(), unhideColumn); + _commandManager.invoke(command); + return this; + } + + /** + * Unhides one or more consecutive columns starting at the given index. + * @param columnIndex column index + * @returns WorkSheet Instance + */ + showColumns(columnIndex: number): WorkSheet; + /** + * Unhides one or more consecutive columns starting at the given index. + * @param columnIndex column index + * @param numColumns column count + * @returns WorkSheet Instance + */ + showColumns(columnIndex: number, numColumns: number): WorkSheet; + showColumns(...argument: any): WorkSheet { + const index = argument[0]; + let count = 1; + if (argument[1]) { + count = argument[1]; + } + const { _context, _commandManager, _sheetId } = this; + const showColumn: ISetColumnShowActionData = { + actionName: ACTION_NAMES.SET_SHOW_COLUMN_ACTION, + sheetId: _sheetId, + columnCount: count, + columnIndex: index, + }; + const command = new Command(_context.getWorkBook(), showColumn); + _commandManager.invoke(command); + return this; + } + + /** + * Unhides one or more consecutive rows starting at the given index. + * @param rowIndex row index + * @returns WorkSheet Instance + */ + showRows(rowIndex: number): WorkSheet; + /** + * Unhides one or more consecutive rows starting at the given index. + * @param rowIndex row index + * @param numRows row count + * @returns WorkSheet Instance + */ + showRows(rowIndex: number, numRows: number): WorkSheet; + showRows(...argument: any): WorkSheet { + const index = --argument[0]; + let count = 1; + if (argument[1]) { + count = argument[1]; + } + const { _context, _commandManager, _sheetId } = this; + const showRow: ISetRowShowActionData = { + actionName: ACTION_NAMES.SET_SHOW_ROW_ACTION, + sheetId: _sheetId, + rowCount: count, + rowIndex: index, + }; + const command = new Command(_context.getWorkBook(), showRow); + _commandManager.invoke(command); + return this; + } + + /** + * Returns true if the sheet's gridlines are hidden; otherwise returns false. Gridlines are visible by default. + * @returns Gridlines Hidden Status + */ + hasHiddenGridlines(): Boolean { + const { _config } = this; + const { showGridlines } = _config; + if (showGridlines === 0) { + return true; + } + return false; + } + + /** + * Gets the sheet tab color, or null if the sheet tab has no color. + * @returns the sheet tab color or null + */ + getTabColor(): Nullable { + const { _config } = this; + const { tabColor } = _config; + return tabColor; + } + + /** + * Gets the width in pixels of the given column. + * @param columnPosition column index + * @returns Gets the width in pixels of the given column. + */ + getColumnWidth(columnPosition: number): number { + return this.getColumnManager().getColumnWidth(columnPosition); + } + + /** + * Gets the height in pixels of the given row. + * @param rowPosition row index + * @returns Gets the height in pixels of the given row. + */ + getRowHeight(rowPosition: number): number { + return this.getRowManager().getRowHeight(rowPosition); + } + + /** + * Sets the width of the given columns in pixels. + * @param columnPosition column index + * @param width column width + * @returns WorkSheet Instance + */ + setColumnWidth(columnPosition: number, width: number): WorkSheet; + /** + * Sets the width of the given columns in pixels. + * @param startColumn column index + * @param numColumns column count + * @param width column width + * @returns WorkSheet Instance + */ + setColumnWidth( + startColumn: number, + numColumns: number, + width: number + ): WorkSheet; + setColumnWidth(...argument: any): WorkSheet { + let columnIndex; + let columnWidth: number[] = []; + if (Tools.hasLength(argument, 2)) { + columnIndex = argument[0]; + columnWidth = [argument[1]]; + } + if (Tools.hasLength(argument, 3)) { + columnIndex = argument[0]; + for (let i = 0; i < argument[1]; i++) { + columnWidth.push(argument[2]); + } + } + this.getColumnManager().setColumnWidth(columnIndex, columnWidth); + return this; + } + + /** + * Sets the height of the given rows in pixels. + * @param startRow row index + * @param numRows row count + * @param height row height + * @returns WorkSheet Instance + */ + setRowHeights(startRow: number, numRows: number, height: number): WorkSheet { + const rowHeight = []; + for (let i = 0; i < numRows; i++) { + rowHeight.push(height); + } + this.getRowManager().setRowHeight(startRow, rowHeight); + return this; + } + + /** + * Sets the row height of the given row in pixels. + * @param rowPosition row index + * @param height row height + * @returns WorkSheet Instance + */ + setRowHeight(rowPosition: number, height: number): WorkSheet { + const rowHeight = [height]; + this.getRowManager().setRowHeight(rowPosition, rowHeight); + return this; + } + + /** + * Hides or reveals the sheet gridlines. + * @param hideGridlines hide gridlines status + * @returns WorkSheet Instance + */ + setHiddenGridlines(hideGridlines: boolean): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const configure = { + actionName: ACTION_NAMES.SET_HIDDEN_GRIDLINES_ACTION, + hideGridlines, + sheetId: _sheetId, + }; + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + return this; + } + + /** + * Freezes the given number of columns. If zero, no columns are frozen. + * @param columns freeze columns, 0 - columns + * @returns WorkSheet Instance + */ + setFrozenColumns(columns: number): WorkSheet { + const frozen = new Freeze(this); + return frozen.setFrozenColumns(columns); + } + + /** + * Freezes the given number of rows. If zero, no rows are frozen. + * @param rows freeze rows, 0 - rows + * @returns WorkSheet Instance + */ + setFrozenRows(rows: number): WorkSheet { + const frozen = new Freeze(this); + return frozen.setFrozenRows(rows); + } + + /** + * Returns true if this sheet layout is right-to-left. Returns false if the sheet uses the default left-to-right layout. + * @returns true if this sheet layout is right-to-left. Returns false if the sheet uses the default left-to-right layout. + */ + isRightToLeft(): BooleanNumber { + const { _config } = this; + const { rightToLeft } = _config; + return rightToLeft; + } + + /** + * Sets or unsets the sheet layout to right-to-left. + * @param rightToLeft rightToLeft status + * @returns WorkSheet Instance + */ + setRightToLeft(rightToLeft: BooleanNumber): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const configure = { + actionName: ACTION_NAMES.SET_RIGHT_TO_LEFT_ACTION, + rightToLeft, + sheetId: _sheetId, + }; + const command = new Command(_context.getWorkBook(), configure); + _commandManager.invoke(command); + + return this; + } + + // /** + // * Adds developer metadata with the specified key and value to the sheet. + // */ + // addDeveloperMetadata(key: string): WorkSheet; + // addDeveloperMetadata( + // key: string, + // visibility: DeveloperMetadataVisibility + // ): WorkSheet; + // addDeveloperMetadata(key: string, value: string): WorkSheet; + // addDeveloperMetadata( + // key: string, + // value: string, + // visibility: DeveloperMetadataVisibility + // ): WorkSheet; + // addDeveloperMetadata(...argument: any): WorkSheet { + // if (argument.length == 1) { + // this._config.metaData.push({ + // metadataKey: argument[0], + // }); + // } else if (argument.length == 2) { + // if (typeof argument[1] == 'string') { + // this._config.metaData.push({ + // metadataKey: argument[0], + // metadataValue: argument[1], + // }); + // } else { + // this._config.metaData.push({ + // metadataKey: argument[0], + // visibility: argument[1], + // }); + // } + // } else if (argument.length == 3) { + // this._config.metaData.push({ + // metadataKey: argument[0], + // metadataValue: argument[1], + // visibility: argument[2], + // }); + // } + + // return this; + // } + // /** + // * Get all developer metadata + // */ + // getDeveloperMetadata() { + // const { metaData } = this._config; + // return metaData; + // } + /** + * @typeParam T - plugin data structure + * @param name - plugin name + * @returns information stored by the plugin + */ + getPluginMeta(name: string): T { + return this._config.pluginMeta[name]; + } + + /** + * @typeParam T - plugin data structure + * @param name - plugin name + * @param value - plugin value + * @returns + */ + setPluginMeta(name: string, value: T) { + this._config.pluginMeta[name] = value; + } + + /** + * Copies the sheet to a given sheet, + * @param sheetIndex sheet index + * @returns WorkSheet Instance + */ + copyTo(sheetIndex: number): WorkSheet { + const worksheet = this._context.getWorkBook().getSheets()[sheetIndex]; + if (!worksheet) return this; + const config = Tools.deepClone(this._config); + config.sheetId = worksheet.getConfig().id; + worksheet.setConfig(config); + return worksheet; + } + + /** + * Returns the position of the last row that has content. + * @returns the position of the last row that has content. + */ + getLastRow(): number { + return this._cellData.getLength(); + } + + /** + * Returns the position of the last column that has content. + * @returns the position of the last column that has content. + */ + getLastColumn(): number { + return this._cellData.getRange().endColumn; + } + + /** + * Returns the rectangular grid of values for this range starting at the given coordinates. A -1 value given as the row or column position is equivalent to getting the very last row or column that has data in the sheet. + * @param startRow row start index + * @param startColumn column start index + * @param numRows row count + * @param numColumns column count + * @returns the rectangular grid of values for this range starting at the given coordinates. A -1 value given as the row or column position is equivalent to getting the very last row or column that has data in the sheet. + */ + getSheetValues( + startRow: number, + startColumn: number, + numRows: number, + numColumns: number + ): Array>> { + const range = new Range(this, { + startRow, + startColumn, + endRow: startRow + numRows - 1, + endColumn: startColumn + numColumns - 1, + }); + return range.getValues(); + } + + /** + * Returns a Range corresponding to the dimensions in which data is present. + * @returns a Range corresponding to the dimensions in which data is present. + */ + getDataRange(): Range { + const range = new Range(this, this._cellData.getRange()); + return range; + } + + /** + * Moves the rows selected by the given range to the position indicated by the destinationIndex. The rowSpec itself does not have to exactly represent an entire row or group of rows to move—it selects all rows that the range spans. + * @param rowSpec row range + * @param destinationIndex row index + * @returns WorkSheet Instance + */ + moveRows(rowSpec: Range, destinationIndex: number): WorkSheet { + const { startRow } = rowSpec.getRangeData(); + const { endRow } = rowSpec.getRangeData(); + const data = this._cellData.spliceRows(startRow, endRow - startRow + 1); + this._cellData.insertRows(destinationIndex, data); + return this; + } + + /** + * Moves the columns selected by the given range to the position indicated by the destinationIndex. The columnSpec itself does not have to exactly represent an entire column or group of columns to move—it selects all columns that the range spans. + * @param columnSpec column range + * @param destinationIndex column index + * @returns WorkSheet Instance + */ + moveColumns(columnSpec: Range, destinationIndex: number): WorkSheet { + const startRow = columnSpec.getRangeData().startColumn; + const endRow = columnSpec.getRangeData().endColumn; + const data = this._cellData.spliceColumns(startRow, endRow - startRow + 1); + this._cellData.insertColumns(destinationIndex, data); + return this; + } + + /** + * Collapse All Row + * @returns WorkSheet Instance + */ + collapseAllRowGroups(): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const collapseAllRowGroupsData: ISetCollapseAllRowGroupsData = { + actionName: ACTION_NAMES.SET_COLLAPSE_ALL_ROW_GROUPS_ACTION, + sheetId: _sheetId, + }; + const command = new Command( + _context.getWorkBook(), + collapseAllRowGroupsData + ); + _commandManager.invoke(command); + return this; + } + + /** + * Collapse All Column + * @returns WorkSheet Instance + */ + collapseAllColumnGroups(): WorkSheet { + const { _context, _commandManager, _sheetId } = this; + const collapseAllRowGroupsData: ISetCollapseAllColumnGroupsData = { + actionName: ACTION_NAMES.SET_COLLAPSE_ALL_COLUMN_GROUPS_ACTION, + sheetId: _sheetId, + }; + const command = new Command( + _context.getWorkBook(), + collapseAllRowGroupsData + ); + _commandManager.invoke(command); + return this; + } +} + +// /** +// * Set the top, bottom, left and right borders +// */ +// export class BorderStyles { +// private _workSheet: WorkSheet; + +// constructor(workSheet: WorkSheet) { +// this._workSheet = workSheet; +// } + +// setLStyle(row: number, column: number, style: IBorderStyleData): void { +// const workSheet = this._workSheet; +// const context = workSheet.getContext(); +// const workBook = context.getWorkBook(); +// const styles = workBook.getStyles(); +// const cellMatrix = workSheet.getCellMatrix(); +// const last = cellMatrix.getValue(row, column - 1); +// const next = cellMatrix.getValue(row, column); +// if (last) { +// const lastStyle = styles.get(last.s); +// if (lastStyle) { +// if (lastStyle.bd) { +// const copy = Tools.deepClone(style); +// delete copy.r; +// last.s = styles.add(copy); +// } +// } +// } +// if (next) { +// next.s = styles.add({ bd: { l: style } }); +// return; +// } +// const create = { s: styles.add({ bd: { l: style } }) }; +// cellMatrix.setValue(row, column, create); +// } + +// setTStyle(row: number, column: number, style: IBorderStyleData): void { +// const workSheet = this._workSheet; +// const context = workSheet.getContext(); +// const workBook = context.getWorkBook(); +// const styles = workBook.getStyles(); +// const cellMatrix = workSheet.getCellMatrix(); +// const last = cellMatrix.getValue(row - 1, column); +// const next = cellMatrix.getValue(row, column); +// if (last) { +// const lastStyle = styles.get(last.s); +// if (lastStyle) { +// if (lastStyle.bd) { +// const copy = Tools.deepClone(style); +// delete copy.b; +// last.s = styles.add(copy); +// } +// } +// } +// if (next) { +// next.s = styles.add({ bd: { t: style } }); +// return; +// } +// const create = { s: styles.add({ bd: { t: style } }) }; +// cellMatrix.setValue(row, column, create); +// } + +// setRStyle(row: number, column: number, style: IBorderStyleData): void { +// const workSheet = this._workSheet; +// const context = workSheet.getContext(); +// const workBook = context.getWorkBook(); +// const styles = workBook.getStyles(); +// const cellMatrix = workSheet.getCellMatrix(); +// const last = cellMatrix.getValue(row, column + 1); +// const next = cellMatrix.getValue(row, column); +// if (last) { +// const lastStyle = styles.get(last.s); +// if (lastStyle) { +// if (lastStyle.bd) { +// const copy = Tools.deepClone(style); +// delete copy.l; +// last.s = styles.add(copy); +// } +// } +// } +// if (next) { +// next.s = styles.add({ bd: { r: style } }); +// return; +// } +// const create = { s: styles.add({ bd: { r: style } }) }; +// cellMatrix.setValue(row, column, create); +// } + +// setBStyle(row: number, column: number, style: IBorderStyleData): void { +// const workSheet = this._workSheet; +// const context = workSheet.getContext(); +// const workBook = context.getWorkBook(); +// const styles = workBook.getStyles(); +// const cellMatrix = workSheet.getCellMatrix(); +// const last = cellMatrix.getValue(row + 1, column); +// const next = cellMatrix.getValue(row, column); +// if (last) { +// const lastStyle = styles.get(last.s); +// if (lastStyle) { +// if (lastStyle.bd) { +// const copy = Tools.deepClone(style); +// delete copy.t; +// last.s = styles.add(copy); +// } +// } +// } +// if (next) { +// next.s = styles.add({ bd: { b: style } }); +// return; +// } +// const create = { s: styles.add({ bd: { b: style } }) }; +// cellMatrix.setValue(row, column, create); +// } +// } diff --git a/packages/core/src/Sheets/Domain/index.ts b/packages/core/src/Sheets/Domain/index.ts new file mode 100644 index 0000000000..ba57694bb4 --- /dev/null +++ b/packages/core/src/Sheets/Domain/index.ts @@ -0,0 +1,15 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './Color'; +export * from './ColorBuilder'; +export * from './ColumnManager'; +export * from './Freeze'; +export * from './Group'; +export * from './Merges'; +export * from './Range'; +export * from './RangeList'; +export * from './RowManager'; +export * from './Selection'; +export * from './Styles'; +export * from './WorkBook'; +export * from './WorkSheet'; +// @endindex diff --git a/packages/core/src/Slides/index.ts b/packages/core/src/Slides/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000000..9d779b2066 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,29 @@ +// Copyright (c) DreamNum Company. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * UniverSheet core package + * + * @remarks + * The core defines basic data structures {@link IData} , APIs for modifying data {@link Domain}, + * easily extensible plugins {@link Plugin} and event listeners {@link Observer} + * + * @packageDocumentation + * + */ + +export * from './Basics'; +export * from './Command'; +export * from './Const'; +export * from './Convertor'; +export * from './Sheets/Domain'; +export * from './Enum'; +export * from './Interfaces'; +export * from './IOC'; +export * from './Module/Formula'; +export * from './Module/Group'; +export * from './Observer'; +export * from './Operation'; +export * from './Plugin'; +export * from './Server'; +export * from './Shared'; diff --git a/packages/core/src/tsdoc-metadata.json b/packages/core/src/tsdoc-metadata.json new file mode 100644 index 0000000000..1d96152afd --- /dev/null +++ b/packages/core/src/tsdoc-metadata.json @@ -0,0 +1,11 @@ +// This file is read by tools that parse documentation comments conforming to the TSDoc standard. +// It should be published with your NPM package. It should not be tracked by Git. +{ + "tsdocVersion": "0.12", + "toolPackages": [ + { + "packageName": "@microsoft/api-extractor", + "packageVersion": "7.25.0" + } + ] +} diff --git a/packages/core/src/type.d.ts b/packages/core/src/type.d.ts new file mode 100644 index 0000000000..9076dba048 --- /dev/null +++ b/packages/core/src/type.d.ts @@ -0,0 +1,8 @@ +export * from './index'; +declare module '@univer/core' {} + +declare global { + interface Navigator { + pointerEnabled?: any; + } +} diff --git a/packages/core/test/Basics/UniverSheet.test.ts b/packages/core/test/Basics/UniverSheet.test.ts new file mode 100644 index 0000000000..33c78b8e9d --- /dev/null +++ b/packages/core/test/Basics/UniverSheet.test.ts @@ -0,0 +1,87 @@ +/** + * @jest-environment jsdom + */ +import { Context, IOCContainer, UniverSheet, Plugin } from '../../src'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +describe('UniverSheet', () => { + let univerSheet: UniverSheet; + beforeAll(async () => { + univerSheet = UniverSheet.newInstance({ + id: 'workbook-01', + sheets: [ + { + name: 'first sheet', + }, + ], + }); + }); + afterAll(async () => {}); + + test('Test newInstance', () => { + let sheet = univerSheet.getWorkBook().getSheetBySheetName('first sheet'); + expect(sheet && sheet.getName()).toEqual('first sheet'); + }); + test('Test installPlugin/uninstallPlugin/getWorkBook/get context', () => { + class TestPlugin extends Plugin { + context: Context; + + constructor() { + super('testPlugin'); + } + + initialize(context: Context): void { + this.context = context; + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(ctx); + } + + onDestroy(): void {} + } + + // test installPlugin + univerSheet.installPlugin(new TestPlugin()); + + // test getWorkBook + let plugin = univerSheet + .getWorkBook() + .getContext() + .getPluginManager() + .getPluginByName('testPlugin'); + + plugin?.getPluginByName('testPlugin'); + expect(plugin && plugin.getPluginName()).toEqual('testPlugin'); + + // test uninstallPlugin + univerSheet.uninstallPlugin('testPlugin'); + + let uninstalledPlugin = univerSheet + .getWorkBook() + .getContext() + .getPluginManager() + .getPluginByName('testPlugin'); + expect(uninstalledPlugin).toEqual(undefined); + }); + + test('Test Context', () => { + // test get context + let context = univerSheet.context; + + let undoManager = context.getUndoManager(); + let observerManager = context.getObserverManager(); + let locale = context.getLocale(); + let pluginManager = context.getPluginManager(); + let hooksManager = context.getHooksManager(); + + expect(undoManager).not.toBeNull(); + expect(observerManager).not.toBeNull(); + expect(locale).not.toBeNull(); + expect(pluginManager).not.toBeNull(); + expect(hooksManager).not.toBeNull(); + }); +}); diff --git a/packages/core/test/Command/AddMergeAction.test.ts b/packages/core/test/Command/AddMergeAction.test.ts new file mode 100644 index 0000000000..f5077c6bf7 --- /dev/null +++ b/packages/core/test/Command/AddMergeAction.test.ts @@ -0,0 +1,95 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { AddMergeAction, CommandManager } from '../../src/Command'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test Create MergeCell', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'AddMergeAction'; + const rect = [{ startRow: 1, startColumn: 1, endRow: 5, endColumn: 5 }]; + + const configure = { actionName, sheetId, rectangles: rect }; + new AddMergeAction(configure, workbook, observers); + expect(worksheet.getMerges().size()).toEqual(1); +}); + +test('Test Undo MergeCell', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'AddMergeAction'; + const rect = [{ startRow: 1, startColumn: 1, endRow: 5, endColumn: 5 }]; + + const configure = { actionName, sheetId, rectangles: rect }; + const action = new AddMergeAction(configure, workbook, observers); + expect(worksheet.getMerges().size()).toEqual(1); + action.undo(); + expect(worksheet.getMerges().size()).toEqual(0); + action.redo(); + expect(worksheet.getMerges().size()).toEqual(1); +}); + +test('Test Cross MergeCell', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'AddMergeAction'; + + const rect1 = [{ startRow: 1, startColumn: 1, endRow: 5, endColumn: 5 }]; + const config1 = { actionName, sheetId, rectangles: rect1 }; + new AddMergeAction(config1, workbook, observers); + expect(worksheet.getMerges().size()).toEqual(1); + + const rect2 = [{ startRow: 2, startColumn: 2, endRow: 5, endColumn: 5 }]; + const config2 = { actionName, sheetId, rectangles: rect2 }; + new AddMergeAction(config2, workbook, observers); + expect(worksheet.getMerges().size()).toEqual(1); +}); + +test('Test Multiple MergeCell', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'AddMergeAction'; + + const rect1 = [{ startRow: 1, startColumn: 1, endRow: 5, endColumn: 5 }]; + const config1 = { actionName, sheetId, rectangles: rect1 }; + new AddMergeAction(config1, workbook, observers); + expect(worksheet.getMerges().size()).toEqual(1); + + const rect2 = [{ startRow: 10, startColumn: 10, endRow: 15, endColumn: 15 }]; + const config2 = { actionName, sheetId, rectangles: rect2 }; + new AddMergeAction(config2, workbook, observers); + expect(worksheet.getMerges().size()).toEqual(2); +}); diff --git a/packages/core/test/Command/InsertRowAction.test.ts b/packages/core/test/Command/InsertRowAction.test.ts new file mode 100644 index 0000000000..c084702b31 --- /dev/null +++ b/packages/core/test/Command/InsertRowAction.test.ts @@ -0,0 +1,55 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { CommandManager, InsertRowAction } from '../../src/Command'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Undo a new Row', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'InsertRowAction'; + const configure = { + actionName, + sheetId, + rowIndex: 0, + rowCount: 1, + }; + const action = new InsertRowAction(configure, workbook, observers); + expect(worksheet.getRowManager().getSize()).toEqual(1); + action.undo(); + expect(worksheet.getRowManager().getSize()).toEqual(0); + action.redo(); + expect(worksheet.getRowManager().getSize()).toEqual(1); +}); + +test('Insert a new Row', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'InsertRowAction'; + const configure = { + actionName, + sheetId, + rowIndex: 0, + rowCount: 1, + }; + new InsertRowAction(configure, workbook, observers); + expect(worksheet.getRowManager().getSize()).toEqual(1); +}); diff --git a/packages/core/test/Command/RemoveMergeAction.test.ts b/packages/core/test/Command/RemoveMergeAction.test.ts new file mode 100644 index 0000000000..bde5f1f1d1 --- /dev/null +++ b/packages/core/test/Command/RemoveMergeAction.test.ts @@ -0,0 +1,52 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { + AddMergeAction, + CommandManager, + RemoveMergeAction, +} from '../../src/Command'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; +import { IRangeData } from '../../src/Interfaces'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Remove a new merge', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + const observers = CommandManager.getActionObservers(); + + // add merge + const actionNameAdd = 'AddMergeAction'; + const rect: IRangeData[] = [ + { startRow: 1, startColumn: 1, endRow: 5, endColumn: 5 }, + ]; + + const configureAdd = { actionName: actionNameAdd, sheetId, rectangles: rect }; + const mergeAction = new AddMergeAction(configureAdd, workbook, observers); + + // remove merge + const actionNameRemove = 'RemoveMergeAction'; + const rectangles: IRangeData[] = [ + { startRow: 1, startColumn: 1, endRow: 5, endColumn: 5 }, + ]; + const configureRemove = { + actionName: actionNameRemove, + sheetId, + rectangles, + }; + const action = new RemoveMergeAction(configureRemove, workbook, observers); + expect(worksheet.getMerges().size()).toEqual(0); + + action.undo(); + expect(worksheet.getMerges().size()).toEqual(1); + action.redo(); + expect(worksheet.getMerges().size()).toEqual(0); +}); diff --git a/packages/core/test/Command/RemoveRowDataAction.test.ts b/packages/core/test/Command/RemoveRowDataAction.test.ts new file mode 100644 index 0000000000..b66391147f --- /dev/null +++ b/packages/core/test/Command/RemoveRowDataAction.test.ts @@ -0,0 +1,109 @@ +/** + * @jest-environment jsdom + */ +import { Context } from '../../src/Basics'; +import { CommandManager, RemoveRowDataAction } from '../../src/Command'; +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Insert a new Row', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const data = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, data); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'RemoveRowDataAction'; + const configure = { + actionName, + sheetId: data.sheetId, + rowIndex: 0, + rowCount: 1, + }; + const action = new RemoveRowDataAction(configure, workbook, observers); + expect(worksheet.getRowManager().getSize()).toEqual(2); + action.undo(); + action.redo(); +}); diff --git a/packages/core/test/Command/RowRemoveAction.test.ts b/packages/core/test/Command/RowRemoveAction.test.ts new file mode 100644 index 0000000000..5e2e4d0c15 --- /dev/null +++ b/packages/core/test/Command/RowRemoveAction.test.ts @@ -0,0 +1,63 @@ +/** + * @jest-environment jsdom + */ +import { + CommandManager, + Context, + RemoveRowAction, + WorkBook, + WorkSheet, +} from '../../src'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Remove a new column', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const rowData = { 0: { hd: 0, h: 50 } }; + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId, rowData }); + workbook.insertSheet(worksheet); + expect(worksheet.getRowManager().getSize()).toEqual(1); + + const observers = CommandManager.getActionObservers(); + const actionName = 'RemoveRowAction'; + const configure = { + actionName, + sheetId, + rowIndex: 0, + rowCount: 1, + }; + new RemoveRowAction(configure, workbook, observers); + expect(worksheet.getRowManager().getSize()).toEqual(0); +}); + +test('Remove Undo column', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const rowData = { 0: { hd: 0, h: 50 } }; + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId, rowData }); + workbook.insertSheet(worksheet); + expect(worksheet.getRowManager().getSize()).toEqual(1); + + const observers = CommandManager.getActionObservers(); + const actionName = 'RemoveRowAction'; + const configure = { + actionName, + sheetId, + rowIndex: 0, + rowCount: 1, + }; + const action = new RemoveRowAction(configure, workbook, observers); + expect(worksheet.getRowManager().getSize()).toEqual(0); + action.undo(); + expect(worksheet.getRowManager().getSize()).toEqual(1); + action.redo(); + expect(worksheet.getRowManager().getSize()).toEqual(0); +}); diff --git a/packages/core/test/Command/SetCollapseAllColumnGroupsAction.test.ts b/packages/core/test/Command/SetCollapseAllColumnGroupsAction.test.ts new file mode 100644 index 0000000000..e52dd88518 --- /dev/null +++ b/packages/core/test/Command/SetCollapseAllColumnGroupsAction.test.ts @@ -0,0 +1 @@ +test('', () => {}); diff --git a/packages/core/test/Command/SetCollapseAllRowGroupsAction.test.ts b/packages/core/test/Command/SetCollapseAllRowGroupsAction.test.ts new file mode 100644 index 0000000000..e52dd88518 --- /dev/null +++ b/packages/core/test/Command/SetCollapseAllRowGroupsAction.test.ts @@ -0,0 +1 @@ +test('', () => {}); diff --git a/packages/core/test/Command/SetHideColumnAction.test.ts b/packages/core/test/Command/SetHideColumnAction.test.ts new file mode 100644 index 0000000000..3f84c5b640 --- /dev/null +++ b/packages/core/test/Command/SetHideColumnAction.test.ts @@ -0,0 +1,52 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; +import { Context } from '../../src/Basics'; +import { + CommandManager, + SetColumnHideAction, + SetColumnShowAction, +} from '../../src/Command'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Set a hide Column', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'SetColumnHideAction'; + const configure = { actionName, sheetId, columnIndex: 1, columnCount: 1 }; + const action = new SetColumnHideAction(configure, workbook, observers); + + expect(worksheet.getColumnManager().getColumnOrCreate(1).hd).toEqual(true); + + action.undo(); + expect(worksheet.getColumnManager().getColumnOrCreate(1).hd).toEqual(false); + action.redo(); + expect(worksheet.getColumnManager().getColumnOrCreate(1).hd).toEqual(true); + + const actionNameShow = 'SetColumnShowAction'; + const configureShow = { + actionName: actionNameShow, + sheetId, + columnIndex: 1, + columnCount: 1, + }; + workbook.insertSheet(worksheet); + const actionShow = new SetColumnShowAction(configureShow, workbook, observers); + + expect(worksheet.getColumnManager().getColumnOrCreate(1).hd).toEqual(false); + + actionShow.undo(); + expect(worksheet.getColumnManager().getColumnOrCreate(1).hd).toEqual(true); + actionShow.redo(); + expect(worksheet.getColumnManager().getColumnOrCreate(1).hd).toEqual(false); +}); diff --git a/packages/core/test/Command/SetHideRowAction.test.ts b/packages/core/test/Command/SetHideRowAction.test.ts new file mode 100644 index 0000000000..a3fa4df9c3 --- /dev/null +++ b/packages/core/test/Command/SetHideRowAction.test.ts @@ -0,0 +1,51 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { + CommandManager, + SetRowHideAction, + SetRowShowAction, +} from '../../src/Command'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('SetRowHideAction', () => { + const observers = CommandManager.getActionObservers(); + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const actionName = 'SetRowHideAction'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const configure = { actionName, sheetId, rowIndex: 1, rowCount: 1 }; + const action = new SetRowHideAction(configure, workbook, observers); + + expect(worksheet.getRowManager().getRowOrCreate(1).hd).toEqual(true); + + action.undo(); + expect(worksheet.getRowManager().getRowOrCreate(1).hd).toEqual(false); + action.redo(); + expect(worksheet.getRowManager().getRowOrCreate(1).hd).toEqual(true); + + const actionNameShow = 'SetRowShowAction'; + const configureShow = { + actionName: actionNameShow, + sheetId, + rowIndex: 1, + rowCount: 1, + }; + const actionShow = new SetRowShowAction(configureShow, workbook, observers); + + expect(worksheet.getRowManager().getRowOrCreate(1).hd).toEqual(false); + + actionShow.undo(); + expect(worksheet.getRowManager().getRowOrCreate(1).hd).toEqual(true); + actionShow.redo(); + expect(worksheet.getRowManager().getRowOrCreate(1).hd).toEqual(false); +}); diff --git a/packages/core/test/Command/SetRangeDataAction.test.ts b/packages/core/test/Command/SetRangeDataAction.test.ts new file mode 100644 index 0000000000..1ae8d3ab83 --- /dev/null +++ b/packages/core/test/Command/SetRangeDataAction.test.ts @@ -0,0 +1,66 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { + SetRangeDataAction, + CommandManager, + ISetRangeDataActionData, +} from '../../src/Command'; +import { ICellData, ObjectMatrixType } from '../../src'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test SetRangeDataAction', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'SetRangeDataAction'; + + // mock data + const cellValue: ObjectMatrixType = { + 0: { + 0: { + v: 1, + m: '1', + }, + }, + }; + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 0, + }; + const configure: ISetRangeDataActionData = { + actionName, + sheetId, + cellValue, + rangeData, + }; + + const action = new SetRangeDataAction(configure, workbook, observers); + + const value = worksheet.getCellMatrix().getValue(0, 0); + expect(value && value.v).toEqual(1); + + action.undo(); + + const preValue = worksheet.getCellMatrix().getValue(0, 0); + + expect(preValue && preValue.v).toEqual(undefined); + + action.redo(); + + const nextValue = worksheet.getCellMatrix().getValue(0, 0); + expect(nextValue && nextValue.v).toEqual(1); +}); diff --git a/packages/core/test/Command/SetRangeValueAction.test.ts b/packages/core/test/Command/SetRangeValueAction.test.ts new file mode 100644 index 0000000000..e44ba4f75a --- /dev/null +++ b/packages/core/test/Command/SetRangeValueAction.test.ts @@ -0,0 +1,63 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { + SetRangeValueAction, + CommandManager, + ISetRangeValueActionData, +} from '../../src/Command'; +import { ICellV, ObjectMatrixPrimitiveType } from '../../src'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test SetRangeValueAction', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'SetRangeValueAction'; + + // mock data + const cellValue: ObjectMatrixPrimitiveType = { + 0: { + 0: 1, + }, + }; + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 0, + }; + const configure: ISetRangeValueActionData = { + actionName, + sheetId, + cellValue, + rangeData, + }; + + const action = new SetRangeValueAction(configure, workbook, observers); + + const value = worksheet.getCellMatrix().getValue(0, 0); + expect(value && value.v).toEqual(1); + + action.undo(); + + const preValue = worksheet.getCellMatrix().getValue(0, 0); + + expect(preValue && preValue.v).toEqual(''); + + action.redo(); + + const nextValue = worksheet.getCellMatrix().getValue(0, 0); + expect(nextValue && nextValue.v).toEqual(1); +}); diff --git a/packages/core/test/Command/SetShowColumnAction.test.ts b/packages/core/test/Command/SetShowColumnAction.test.ts new file mode 100644 index 0000000000..436cc8b885 --- /dev/null +++ b/packages/core/test/Command/SetShowColumnAction.test.ts @@ -0,0 +1,24 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { CommandManager, SetColumnShowAction } from '../../src/Command'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('SetColumnShowAction', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + + const observers = CommandManager.getActionObservers(); + const actionName = 'SetColumnShowAction'; + const configure = { actionName, sheetId, columnIndex: 1, columnCount: 1 }; + workbook.insertSheet(worksheet); + new SetColumnShowAction(configure, workbook, observers); +}); diff --git a/packages/core/test/Command/SetShowRowAction.test.ts b/packages/core/test/Command/SetShowRowAction.test.ts new file mode 100644 index 0000000000..e7e3f5121e --- /dev/null +++ b/packages/core/test/Command/SetShowRowAction.test.ts @@ -0,0 +1,24 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; +import { CommandManager, SetRowShowAction } from '../../src/Command'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('SetRowShowAction', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = 'SetRowShowAction'; + const configure = { actionName, sheetId, rowIndex: 1, rowCount: 1 }; + new SetRowShowAction(configure, workbook, observers); +}); diff --git a/packages/core/test/Command/SetWorkSheetHideAction.test.ts b/packages/core/test/Command/SetWorkSheetHideAction.test.ts new file mode 100644 index 0000000000..b517d17d49 --- /dev/null +++ b/packages/core/test/Command/SetWorkSheetHideAction.test.ts @@ -0,0 +1,35 @@ +/** + * @jest-environment jsdom + */ +import { Context } from '../../src/Basics'; +import { CommandManager, SetWorkSheetHideAction } from '../../src/Command'; +import { ACTION_NAMES } from '../../src/Const'; +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { BooleanNumber } from '../../src/Enum'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); +test('Set WorkSheet Hide', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet-01'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = ACTION_NAMES.HIDE_SHEET_ACTION; + const configure = { + actionName, + sheetId, + hidden: BooleanNumber.TRUE, + }; + const action = new SetWorkSheetHideAction(configure, workbook, observers); + expect(worksheet.isSheetHidden()).toEqual(1); + + action.undo(); + expect(worksheet.isSheetHidden()).toEqual(0); + action.redo(); + expect(worksheet.isSheetHidden()).toEqual(1); +}); diff --git a/packages/core/test/Command/SetWorkSheetNameAction.test.ts b/packages/core/test/Command/SetWorkSheetNameAction.test.ts new file mode 100644 index 0000000000..8a6906a4e6 --- /dev/null +++ b/packages/core/test/Command/SetWorkSheetNameAction.test.ts @@ -0,0 +1,37 @@ +/** + * @jest-environment jsdom + */ +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { Context } from '../../src/Basics'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; +import { CommandManager, SetWorkSheetNameAction } from '../../src/Command'; +import { ACTION_NAMES } from '../../src/Const'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Set WorkSheet Name', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const name = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId, name }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = ACTION_NAMES.SET_WORKSHEET_NAME_ACTION; + const sheetName = 'test'; + const configure = { + actionName, + sheetId, + sheetName, + }; + const action = new SetWorkSheetNameAction(configure, workbook, observers); + expect(worksheet.getName()).toEqual('test'); + + action.undo(); + expect(worksheet.getName()).toEqual('sheet1'); + action.redo(); + expect(worksheet.getName()).toEqual('test'); +}); diff --git a/packages/core/test/Command/SetWorkSheetStatusAction.test.ts b/packages/core/test/Command/SetWorkSheetStatusAction.test.ts new file mode 100644 index 0000000000..912d682544 --- /dev/null +++ b/packages/core/test/Command/SetWorkSheetStatusAction.test.ts @@ -0,0 +1,37 @@ +/** + * @jest-environment jsdom + */ +import { Context } from '../../src/Basics'; +import { CommandManager, SetWorkSheetStatusAction } from '../../src/Command'; +import { ACTION_NAMES } from '../../src/Const'; +import { WorkBook, WorkSheet } from '../../src/Sheets/Domain'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Set WorkSheet Status', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const status = 1; + const worksheet = new WorkSheet(context, { id: sheetId, status }); + workbook.insertSheet(worksheet); + + const observers = CommandManager.getActionObservers(); + const actionName = ACTION_NAMES.SET_WORKSHEET_STATUS_ACTION; + const newStatus = 0; + const configure = { + actionName, + sheetId, + sheetStatus: newStatus, + }; + const action = new SetWorkSheetStatusAction(configure, workbook, observers); + expect(worksheet.getStatus()).toEqual(0); + + action.undo(); + expect(worksheet.getStatus()).toEqual(1); + action.redo(); + expect(worksheet.getStatus()).toEqual(0); +}); diff --git a/packages/core/test/ContainerStartUp.ts b/packages/core/test/ContainerStartUp.ts new file mode 100644 index 0000000000..b60419146c --- /dev/null +++ b/packages/core/test/ContainerStartUp.ts @@ -0,0 +1,172 @@ +import { Context, Environment } from '../src/Basics'; +import { CommandManager, UndoManager } from '../src/Command'; +import { WorkBook, WorkSheet } from '../src/Sheets/Domain'; +import { BooleanNumber } from '../src/Enum'; +import { IWorksheetConfig } from '../src/Interfaces'; +import { IOCAttribute, IOCContainer } from '../src/IOC'; +import { HooksManager } from '../src/Observer/HooksManager'; +import { ObserverManager } from '../src/Observer/ObserverManager'; +import { PluginManager } from '../src/Plugin'; +import { ServerHttp, ServerSocket } from '../src/Server'; +import { Locale } from '../src/Shared'; + +export function IOCContainerStartUpReady(): IOCContainer { + const configure = { + value: { + id: '', + extensions: [], + sheetOrder: [], + socketEnable: BooleanNumber.FALSE, + socketUrl: '', + name: '', + timeZone: '', + appVersion: '', + theme: '', + skin: '', + locale: '', + creator: '', + styles: [], + sheets: [], + lastModifiedBy: '', + createdTime: '', + modifiedTime: '', + namedRanges: [], + }, + }; + const attribute = new IOCAttribute(configure); + const container = new IOCContainer(attribute); + container.addSingletonMapping('Environment', Environment); + container.addSingletonMapping('Server', ServerSocket); + container.addSingletonMapping('ServerSocket', ServerSocket); + container.addSingletonMapping('ServerHttp', ServerHttp); + container.addSingletonMapping('WorkBook', WorkBook); + container.addSingletonMapping('Locale', Locale); + container.addSingletonMapping('Context', Context); + container.addSingletonMapping('UndoManager', UndoManager); + container.addSingletonMapping('CommandManager', CommandManager); + container.addSingletonMapping('PluginManager', PluginManager); + container.addSingletonMapping('ObserverManager', ObserverManager); + container.addSingletonMapping('ObservableHooksManager', HooksManager); + container.addMapping('WorkSheet', WorkSheet); + return container; +} + +const defaultWorksheetConfigure = { + sheetId: 'sheet-01', + cellData: { + 0: { + 0: { + s: 1, + v: 1, + m: '1', + }, + 1: { + s: 1, + v: 2, + m: '2', + }, + }, + }, + defaultColumnWidth: 93, + defaultRowHeight: 27, + status: 1, +}; + +export function TestInit(worksheetConfig?: Partial) { + const configure = Object.assign(defaultWorksheetConfigure, worksheetConfig); + + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + // const worksheet = new WorkSheet(context, configure); + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + return { worksheet, workbook }; +} +export function TestInitTwoSheet() { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetOneConfigure = { + sheetId: 'sheet-01', + cellData: { + 0: { + 0: { + s: 1, + v: 1, + m: '1', + }, + 1: { + s: 1, + v: 2, + m: '2', + }, + }, + }, + defaultColumnWidth: 93, + defaultRowHeight: 27, + status: 1, + }; + + const sheetTwoConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: 1, + v: 1, + m: '1', + }, + 1: { + s: 1, + v: 2, + m: '2', + }, + }, + }, + defaultColumnWidth: 93, + defaultRowHeight: 27, + status: 0, + }; + + const worksheetOne = container.getInstance( + 'WorkSheet', + context, + sheetOneConfigure + ); + workbook.insertSheet(worksheetOne); + worksheetOne.setCommandManager(commandManager); + + const worksheetTwo = container.getInstance( + 'WorkSheet', + context, + sheetTwoConfigure + ); + workbook.insertSheet(worksheetTwo); + worksheetTwo.setCommandManager(commandManager); + + return { worksheetOne, worksheetTwo, workbook }; +} + +export function TestInitSheetInstance(worksheetConfig?: Partial) { + const configure = Object.assign(defaultWorksheetConfigure, worksheetConfig); + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + return { workbook, worksheet }; +} diff --git a/packages/core/test/Domain/Color.test.ts b/packages/core/test/Domain/Color.test.ts new file mode 100644 index 0000000000..0978b39851 --- /dev/null +++ b/packages/core/test/Domain/Color.test.ts @@ -0,0 +1,45 @@ +import { ThemeColorType } from '../../src/Enum'; +import { Color, RgbColor, ThemeColor } from '../../src/Sheets/Domain/Color'; +import { ColorBuilder } from '../../src/Sheets/Domain/ColorBuilder'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test Color RgbColor', () => { + const builder = new ColorBuilder(); + builder.setRgbColor('rgb(0,10,100)'); + const rgb = builder.build() as RgbColor; + expect(rgb.getRed()).toEqual(0); + expect(rgb.getGreen()).toEqual(10); + expect(rgb.getBlue()).toEqual(100); + expect(rgb.getAlpha()).toEqual(1); + + const hex = Color.rgbColorToHexValue(rgb); + const hRgb = Color.hexValueToRgbColor(hex); + console.log(hex); + console.log(hRgb); + + builder.setRgbColor('rgba(0,10,100, 0.2)'); + const rgba = builder.build() as RgbColor; + expect(rgba.getRed()).toEqual(0); + expect(rgba.getGreen()).toEqual(10); + expect(rgba.getBlue()).toEqual(100); + expect(rgba.getAlpha()).toEqual(0.2); +}); + +test('Test Color themeColor', () => { + const builder = new ColorBuilder(); + builder.setThemeColor(ThemeColorType.ACCENT1); + const color = builder.build() as ThemeColor; + expect(color.getThemeColorType()).toEqual(ThemeColorType.ACCENT1); +}); + +test('Test Color themeColor Office', () => { + const builder = new ColorBuilder(); + builder.setThemeColor(ThemeColorType.ACCENT1); + const color = builder.build() as ThemeColor; + const rgba = color.asRgbColor(); + expect(rgba.getRed()).toEqual(68); + expect(rgba.getGreen()).toEqual(114); + expect(rgba.getBlue()).toEqual(196); + expect(rgba.getAlpha()).toEqual(1); +}); diff --git a/packages/core/test/Domain/Merges.test.ts b/packages/core/test/Domain/Merges.test.ts new file mode 100644 index 0000000000..ada3139ba0 --- /dev/null +++ b/packages/core/test/Domain/Merges.test.ts @@ -0,0 +1,135 @@ +import { Merges } from '../../src'; +import { demo } from './Range.test'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test Merges getByRowColumn', () => { + let { worksheet } = demo(); + const merges = new Merges(worksheet, [ + { + startRow: 1, + startColumn: 1, + endRow: 5, + endColumn: 5, + }, + { + startRow: 6, + startColumn: 6, + endRow: 10, + endColumn: 10, + }, + ]); + expect(merges.getByRowColumn(2, 2)).not.toBeUndefined(); +}); + +test('Test Merges remove', () => { + let { worksheet } = demo(); + const merges = new Merges(worksheet, [ + { + startRow: 1, + startColumn: 1, + endRow: 5, + endColumn: 5, + }, + { + startRow: 6, + startColumn: 6, + endRow: 10, + endColumn: 10, + }, + ]); + expect( + merges.remove({ + startRow: 6, + startColumn: 6, + endRow: 10, + endColumn: 10, + }).length + ).toEqual(1); + expect(merges.size()).toEqual(1); +}); + +test('Test Merges size', () => { + let { worksheet } = demo(); + const merges = new Merges(worksheet, [ + { + startRow: 1, + startColumn: 1, + endRow: 5, + endColumn: 5, + }, + { + startRow: 6, + startColumn: 6, + endRow: 10, + endColumn: 10, + }, + ]); + expect(merges.size()).toEqual(2); +}); + +test('Test Merges add', () => { + let { worksheet } = demo(); + const merges = new Merges(worksheet, [ + { + startRow: 1, + startColumn: 1, + endRow: 5, + endColumn: 5, + }, + { + startRow: 6, + startColumn: 6, + endRow: 10, + endColumn: 10, + }, + ]); + expect(merges.size()).toEqual(2); + merges.add({ + startRow: 16, + startColumn: 16, + endRow: 20, + endColumn: 20, + }); + expect(merges.size()).toEqual(3); +}); + +test('Test Merges union', () => { + let { worksheet } = demo(); + const merges = new Merges(worksheet, [ + { + startRow: 0, + startColumn: 0, + endRow: 5, + endColumn: 5, + }, + ]); + const rect = { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 }; + expect(merges.union(rect)).toEqual(rect); +}); + +test('Test Merges getMergedRanges', () => { + let { worksheet } = demo(); + const merges = new Merges(worksheet, [ + { + startRow: 1, + startColumn: 1, + endRow: 5, + endColumn: 5, + }, + { + startRow: 6, + startColumn: 6, + endRow: 10, + endColumn: 10, + }, + ]); + expect( + merges.getMergedRanges({ + startRow: 6, + startColumn: 6, + endRow: 10, + endColumn: 10, + }) + ).not.toBeUndefined(); +}); diff --git a/packages/core/test/Domain/Range.test.ts b/packages/core/test/Domain/Range.test.ts new file mode 100644 index 0000000000..7923bb18da --- /dev/null +++ b/packages/core/test/Domain/Range.test.ts @@ -0,0 +1,6405 @@ +/** + * @jest-environment jsdom + */ +import { ObjectMatrix } from '../../src'; +import { Context } from '../../src/Basics/Context'; +import { Environment } from '../../src/Basics/Environment'; +import { CommandManager } from '../../src/Command/CommandManager'; +import { UndoManager } from '../../src/Command/UndoManager'; +import { WorkBook } from '../../src/Sheets/Domain/WorkBook'; +import { WorkSheet } from '../../src/Sheets/Domain/WorkSheet'; +import { + BooleanNumber, + BorderStyleTypes, + CopyPasteType, + Dimension, + Direction, + HorizontalAlign, + TextDirection, + ThemeColorType, + VerticalAlign, + WrapStrategy, +} from '../../src/Enum'; +import { ICellData, ICellV, IWorkbookConfig } from '../../src/Interfaces'; +import { IOCAttribute, IOCContainer } from '../../src/IOC/IOCContainer'; +import { HooksManager } from '../../src/Observer/HooksManager'; +import { ObserverManager } from '../../src/Observer/ObserverManager'; +import { PluginManager } from '../../src/Plugin/PluginManager'; +import { ServerHttp } from '../../src/Server/ServerHttp'; +import { ServerSocket } from '../../src/Server/ServerSocket'; +import { Locale } from '../../src/Shared/Locale'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +// TODO performance test, 1000 times +// TODO 修复Range单元测试报错 +// TODO 补充Range API单元格测试 +// TODO 优化:每一个测试用例都会重新初始化一个universheet实例,有的测试case是否可以合并到一个实例,减少测试执行时间 + +/** + * + Tips: The test debugger with the same name will always execute. Please keep a unique name when testing, such as'Test setFontColor' + */ + +export function IOCContainerStartUpReady( + workbookConfig?: Partial +): IOCContainer { + const configure = { + id: '', + extensions: [], + sheetOrder: [], + socketEnable: BooleanNumber.FALSE, + socketUrl: '', + name: '', + timeZone: '', + appVersion: '', + theme: '', + skin: '', + locale: '', + creator: '', + styles: {}, + sheets: [], + lastModifiedBy: '', + createdTime: '', + modifiedTime: '', + ...workbookConfig, + }; + const attribute = new IOCAttribute({ value: configure }); + const container = new IOCContainer(attribute); + container.addSingletonMapping('Environment', Environment); + container.addSingletonMapping('Server', ServerSocket); + container.addSingletonMapping('ServerSocket', ServerSocket); + container.addSingletonMapping('ServerHttp', ServerHttp); + container.addSingletonMapping('WorkBook', WorkBook); + container.addSingletonMapping('Locale', Locale); + container.addSingletonMapping('Context', Context); + container.addSingletonMapping('UndoManager', UndoManager); + container.addSingletonMapping('CommandManager', CommandManager); + container.addSingletonMapping('PluginManager', PluginManager); + container.addSingletonMapping('ObserverManager', ObserverManager); + container.addSingletonMapping('ObservableHooksManager', HooksManager); + container.addMapping('WorkSheet', WorkSheet); + return container; +} + +export function demo() { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: 1, + v: '1', + m: '1', + }, + 1: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + mergeData: [ + { + startRow: 1, + startColumn: 1, + endRow: 2, + endColumn: 2, + }, + ], + rowCount: 30, + columnCount: 10, + defaultColumnWidth: 93, + defaultRowHeight: 27, + status: 1, + }; + // const worksheet = new WorkSheet(context, configure); + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + return { worksheet, workbook }; +} + +test('Test setFontColor', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set new color + range.setFontColor('red'); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + const currentColor = currentStyle && currentStyle.cl?.rgb; + + expect(currentColor).toEqual('red'); + + // undo + worksheet.getCommandManager().undo(); + + // test previous color + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + const preColor = preStyle && preStyle.cl?.rgb; + expect(preColor).toEqual('blue'); +}); + +test('Test setFontFamily', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + ff: 'Arial', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + // const range = worksheet.getRangeData('B1:A10'); + const range = worksheet.getRange('A1'); + + // set A1 fontFamily + range.setFontFamily('Helvetica'); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ff: 'Helvetica', + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ff: 'Arial', + }); +}); + +test('Test setFontFamilies', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + ff: 'Arial', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + // const range = worksheet.getRangeData('B1:A10'); + const range = worksheet.getRange('A1'); + + // set A1 fontFamily + range.setFontFamilies([['Helvetica', 'Helvetica']]); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ff: 'Helvetica', + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ff: 'Arial', + }); +}); + +test('Test setUnderline', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + ff: 'Arial', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + // const range = worksheet.getRangeData('B1:A10'); + const range = worksheet.getRange('A1'); + + // set A1 fontFamily + range.setUnderline({ s: BooleanNumber.TRUE }); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + ff: 'Arial', + ul: { s: BooleanNumber.TRUE }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + ff: 'Arial', + }); +}); +test('Test setOverline', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + ff: 'Arial', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + // const range = worksheet.getRangeData('B1:A10'); + const range = worksheet.getRange('A1'); + + // set A1 fontFamily + range.setOverline({ s: BooleanNumber.TRUE }); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + ff: 'Arial', + ol: { s: BooleanNumber.TRUE }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + ff: 'Arial', + }); +}); +test('Test setStrikeThrough', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + ff: 'Arial', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + // const range = worksheet.getRangeData('B1:A10'); + const range = worksheet.getRange('A1'); + + // set A1 fontFamily + range.setStrikeThrough({ s: BooleanNumber.TRUE }); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + ff: 'Arial', + st: { s: BooleanNumber.TRUE }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + ff: 'Arial', + }); +}); +test('Test setFontSize', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set A1 fontSize + range.setFontSize(18); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 18, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }); +}); + +test('Test setHorizontalAlignment', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + ht: HorizontalAlign.CENTER, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set A1 horizontalAlignment + range.setHorizontalAlignment(HorizontalAlign.LEFT); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ht: HorizontalAlign.LEFT, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'blue', + }, + ht: HorizontalAlign.CENTER, + }); +}); + +test('Test setVerticalAlignment', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + vt: VerticalAlign.MIDDLE, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set A1 verticalAlignment + range.setVerticalAlignment(VerticalAlign.TOP); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + vt: VerticalAlign.TOP, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'blue', + }, + vt: VerticalAlign.MIDDLE, + }); +}); + +test('Test setTextDirection', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + td: TextDirection.LEFT_TO_RIGHT, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set A1 textDirection + range.setTextDirection(TextDirection.RIGHT_TO_LEFT); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + td: TextDirection.RIGHT_TO_LEFT, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'blue', + }, + td: TextDirection.LEFT_TO_RIGHT, + }); +}); + +test('Test setWrapStrategy', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set A1 wrapStrategy + range.setWrapStrategy(WrapStrategy.OVERFLOW); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + tb: WrapStrategy.OVERFLOW, + }); + + // set A1 wrapStrategy + range.setWrapStrategy(WrapStrategy.WRAP); + + const nextCell = worksheet.getCellMatrix().getValue(0, 0); + const nextStyle = workbook.getStyles().get(nextCell && nextCell.s); + + expect(nextStyle).toEqual({ + cl: { + rgb: 'blue', + }, + tb: WrapStrategy.WRAP, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'blue', + }, + tb: WrapStrategy.OVERFLOW, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test init style + const initValue = worksheet.getCellMatrix().getValue(0, 0); + const initStyle = workbook.getStyles().get(initValue && initValue.s); + expect(initStyle).toEqual({ + cl: { + rgb: 'blue', + }, + }); +}); + +test('Test setWrapStrategies', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set A1 wrapStrategy + range.setWrapStrategies([[WrapStrategy.WRAP, WrapStrategy.WRAP]]); + + const nextCell = worksheet.getCellMatrix().getValue(0, 0); + const nextStyle = workbook.getStyles().get(nextCell && nextCell.s); + + expect(nextStyle).toEqual({ + fs: 12, + tb: WrapStrategy.WRAP, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + fs: 12, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test init style + const initValue = worksheet.getCellMatrix().getValue(0, 0); + const initStyle = workbook.getStyles().get(initValue && initValue.s); + expect(initStyle).toEqual({ + fs: 12, + }); +}); + +test('Test setBorder', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bd: { + l: { + s: BorderStyleTypes.DOTTED, + cl: { + th: ThemeColorType.DARK1, + }, + }, + b: { + s: BorderStyleTypes.DOTTED, + cl: { + th: ThemeColorType.DARK1, + }, + }, + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set border + range.setBorder( + true, + true, + true, + true, + false, + false, + 'red', + BorderStyleTypes.DASH_DOT + ); + // dashDot + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + fs: 12, + bd: { + t: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + b: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + l: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + r: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test init style + const initValue = worksheet.getCellMatrix().getValue(0, 0); + const initStyle = workbook.getStyles().get(initValue && initValue.s); + expect(initStyle).toEqual({ + cl: { + rgb: 'blue', + }, + fs: 12, + bd: { + l: { + s: BorderStyleTypes.DOTTED, + cl: { + th: ThemeColorType.DARK1, + }, + }, + b: { + s: BorderStyleTypes.DOTTED, + cl: { + th: ThemeColorType.DARK1, + }, + }, + }, + }); + + worksheet.getCommandManager().redo(); + const redoCell = worksheet.getCellMatrix().getValue(0, 0); + const redoStyle = workbook.getStyles().get(redoCell && redoCell.s); + + expect(redoStyle).toEqual({ + cl: { + rgb: 'blue', + }, + fs: 12, + bd: { + t: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + b: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + l: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + r: { + s: BorderStyleTypes.DASH_DOT, + cl: { + rgb: 'red', + }, + }, + }, + }); +}); + +test('Test copyTo formatOnly', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // copy A1 to B1:D3 + range.copyTo(worksheet.getRange('B1:D3'), { formatOnly: true }); + + const currentCell = worksheet.getCellMatrix().getValue(0, 1); + + expect(currentCell).toEqual({ s: 1, v: 1, m: '1' }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous + const preCell = worksheet.getCellMatrix().getValue(0, 1); + expect(preCell).toEqual({ v: 2, m: '2' }); +}); + +test('Test copyTo contentsOnly', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // copy A1 style to B1:D3 + range.copyTo(worksheet.getRange('B1:D3'), { contentsOnly: true }); + + const currentCell = worksheet.getCellMatrix().getValue(0, 1); + + expect(currentCell).toEqual({ + v: 1, + s: 1, + m: '1', + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = worksheet.getCellMatrix().getValue(0, 1); + + expect(preCell).toEqual({ + v: 2, + m: '2', + }); + + // redo + worksheet.getCommandManager().redo(); + + // test next value + const nextCell = worksheet.getCellMatrix().getValue(0, 1); + expect(nextCell).toEqual({ + v: 1, + s: 1, + m: '1', + }); +}); + +test('Test copyTo CopyPasteType', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + columnData: { + 0: { + w: 30, + hd: 1, + }, + 1: { + w: 50, + hd: 1, + }, + }, + + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:A3'); + + const destination = worksheet.getRange('B1:B3'); + + // copy A1 style to B1:B3 + range.copyTo(destination, CopyPasteType.PASTE_COLUMN_WIDTHS, false); + + const width = worksheet.getColumnWidth(0); + + expect(width).toEqual(30); +}); + +test('Test copyFormatToRange by sheet', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // copy A1 style to B1:D3 + range.copyFormatToRange(worksheet, 0, 2, 1, 3); + + const currentCell = worksheet.getCellMatrix().getValue(0, 1); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + fs: 12, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 1); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'green', + }, + fs: 14, + }); +}); +test('Test copyFormatToRange by sheetId', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // copy A1 style to B1:D3 + range.copyFormatToRange(worksheet.getSheetId(), 0, 2, 1, 3); + + const currentCell = worksheet.getCellMatrix().getValue(0, 1); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + cl: { + rgb: 'blue', + }, + fs: 12, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 1); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + cl: { + rgb: 'green', + }, + fs: 14, + }); +}); + +test('Test copyValuesToRange by sheet', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // copy A1 values to B2:C3 + range.copyValuesToRange(worksheet, 1, 2, 1, 2); + + const currentCell = worksheet.getCellMatrix().getFragments(1, 2, 1, 2).getData(); + + expect(currentCell).toEqual({ + 0: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + }, + 1: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous B2:C3 values + const preCell = worksheet.getCellMatrix().getFragments(1, 2, 1, 2).getData(); + expect(preCell).toEqual({ + 0: { + 0: { m: '', v: '' }, + 1: { m: '', v: '' }, + }, + 1: { + 0: { m: '', v: '' }, + 1: { m: '', v: '' }, + }, + }); +}); + +test('Test copyValuesToRange by sheetId', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // copy A1 values to B2:C3 + range.copyValuesToRange(worksheet.getSheetId(), 1, 2, 1, 2); + + const currentCell = worksheet.getCellMatrix().getFragments(1, 2, 1, 2).getData(); + + expect(currentCell).toEqual({ + 0: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + }, + 1: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous B2:C3 values + const preCell = worksheet.getCellMatrix().getFragments(1, 2, 1, 2).getData(); + expect(preCell).toEqual({ + 0: { + 0: { m: '', v: '' }, + 1: { m: '', v: '' }, + }, + 1: { + 0: { m: '', v: '' }, + 1: { m: '', v: '' }, + }, + }); +}); +test('Test moveTo', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeA = worksheet.getRange('A1:B1'); + const rangeB = worksheet.getRange('A2:B2'); + + // copy A1:B1 move to A2:B2 + rangeA.moveTo(rangeB); + + // current A2:B2 values + const currentAValue = rangeA.getObjectValues(); + const currentBValue = rangeB.getObjectValues({ isIncludeStyle: true }); + + expect(currentAValue).toEqual({ + 0: { + 0: { m: '', v: '' }, + 1: { m: '', v: '' }, + }, + }); + expect(currentBValue).toEqual({ + 0: { + 0: { + s: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + v: 1, + m: '1', + }, + 1: { + s: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + v: 2, + m: '2', + }, + }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // previous A2:B2 values + const preAValue = rangeA.getObjectValues(); + const preBValue = rangeB.getObjectValues(); + + expect(preAValue).toEqual({ + 0: { + 0: { + s: 1, + v: 1, + m: '1', + }, + 1: { + s: 2, + v: 2, + m: '2', + }, + }, + }); + + expect(preBValue).toEqual({ + 0: { + 0: {}, + 1: {}, + }, + }); + + // redo + worksheet.getCommandManager().redo(); + + // next A2:B2 values + const nextAValue = rangeA.getObjectValues(); + const nextBValue = rangeB.getObjectValues(); + + expect(nextAValue).toEqual({ + 0: { + 0: { m: '', v: '' }, + 1: { m: '', v: '' }, + }, + }); + expect(nextBValue).toEqual({ + 0: { + 0: { + s: 1, + v: 1, + m: '1', + }, + 1: { + s: 2, + v: 2, + m: '2', + }, + }, + }); +}); +test('Test offset', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeA = worksheet.getRange('A1'); + + // A1 offset to B2:C3 + const rangeB = rangeA.offset(1, 1, 2, 2); + + expect(rangeB.getRangeData()).toEqual({ + startRow: 1, + endRow: 2, + startColumn: 1, + endColumn: 2, + }); +}); + +test('Test setValue', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set new value + range.setValue(3); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + + expect(currentCell).toEqual({ s: 1, v: 3, m: '3' }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = worksheet.getCellMatrix().getValue(0, 0); + expect(preCell).toEqual({ s: 1, v: 1, m: '1' }); +}); + +test('Test setValues ICellV', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set new value + range.setValues([[5, 6]]); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + + expect(currentCell).toEqual({ s: 1, v: 5, m: '5' }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = worksheet.getCellMatrix().getValue(0, 0); + expect(preCell).toEqual({ s: 1, v: 1, m: '1' }); +}); + +test('Test setValues ObjectMatrix', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + const matrix = new ObjectMatrix(); + + matrix.setValue(0, 0, 5); + matrix.setValue(0, 1, 6); + + // set new value + range.setValues(matrix); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + + expect(currentCell).toEqual({ s: 1, v: 5, m: '5' }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = worksheet.getCellMatrix().getValue(0, 0); + expect(preCell).toEqual({ s: 1, v: 1, m: '1' }); +}); + +test('Test clear', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // clear + range.clear({ formatOnly: true, contentsOnly: true }); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + expect(currentCell && currentCell.s).toEqual(undefined); + expect(currentCell && currentCell.v).toEqual(''); + + // undo + worksheet.getCommandManager().undo(); + + // test init style + const initValue = worksheet.getCellMatrix().getValue(0, 0); + const initStyle = workbook.getStyles().get(initValue && initValue.s); + expect(initStyle).toEqual({ + cl: { + rgb: 'blue', + }, + fs: 12, + }); + + expect(initValue && initValue.v).toEqual(1); +}); +test('Test clearFormat', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // clear style + range.clearFormat(); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + expect(currentCell && currentCell.s).toEqual(undefined); + expect(currentCell && currentCell.v).toEqual(1); + + // undo + worksheet.getCommandManager().undo(); + + // test init style + const initValue = worksheet.getCellMatrix().getValue(0, 0); + const initStyle = workbook.getStyles().get(initValue && initValue.s); + expect(initStyle).toEqual({ + cl: { + rgb: 'blue', + }, + fs: 12, + }); + + expect(initValue && initValue.v).toEqual(1); +}); +test('Test deleteCells', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 1: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 2: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + 3: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B2'); + + // Delete cells and shift up + range.deleteCells(Dimension.ROWS); + + // test current A1:B4 values + const currentCells = worksheet.getRange('A1:B4').getValues(); + + expect(currentCells).toEqual([ + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + [{}, {}], + [{}, {}], + ]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous A1:B4 value + const preCells = worksheet.getRange('A1:B4').getValues(); + + expect(preCells).toEqual([ + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + ]); + // redo + worksheet.getCommandManager().redo(); + + // test next A1:B4 value + const nextCells = worksheet.getRange('A1:B4').getValues(); + expect(nextCells).toEqual([ + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + [{}, {}], + [{}, {}], + ]); +}); + +test('Test insertCells', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 1: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 2: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + 3: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B2'); + + // insert blank cells and shift down + range.insertCells(Dimension.ROWS); + + // test current A1:B4 values + const currentCells = worksheet.getRange('A1:B4').getValues(); + + expect(currentCells).toEqual([ + [ + { v: '', m: '' }, + { v: '', m: '' }, + ], + [ + { v: '', m: '' }, + { v: '', m: '' }, + ], + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + ]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous A1:B4 value + const preCells = worksheet.getRange('A1:B4').getValues(); + + expect(preCells).toEqual([ + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + ], + ]); + // redo + worksheet.getCommandManager().redo(); + + // test next A1:B4 value + const nextCells = worksheet.getRange('A1:B4').getValues(); + + expect(nextCells).toEqual([ + [ + { v: '', m: '' }, + { v: '', m: '' }, + ], + [ + { v: '', m: '' }, + { v: '', m: '' }, + ], + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + [ + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + ]); +}); + +test('Test insertCells COLUMNS', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + }, + 1: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + }, + 2: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + 3: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B2'); + const destination = worksheet.getRange('A3:B4'); + + range.insertCells(Dimension.COLUMNS, destination); + const columnCells = worksheet.getRange('A1:D2').getValues(); + + expect(columnCells).toEqual([ + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + [ + { v: 1111, m: '1111' }, + { v: 1111, m: '1111' }, + { v: 1, m: '1' }, + { v: 1, m: '1' }, + ], + ]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous A1:B4 value + const preCells = worksheet.getRange('A1:D2').getValues(); + + expect(preCells).toEqual([ + [{ v: 1, m: '1' }, { v: 1, m: '1' }, {}, {}], + [{ v: 1, m: '1' }, { v: 1, m: '1' }, {}, {}], + ]); +}); + +test('Create a Merge', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 1: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 2: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + 3: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B2'); + + // merge range + range.merge(); + + // test current A1:B4 values + const merges = worksheet.getMerges(); + expect(merges.size() === 1); +}); + +test('Test getBackground', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 1: { + 0: { + v: 1, + m: '1', + }, + 1: { + v: 1, + m: '1', + }, + 2: { + v: 8, + m: '8', + }, + 3: { + v: 8, + m: '8', + }, + }, + 2: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + 3: { + 0: { + v: 1111, + m: '1111', + }, + 1: { + v: 1111, + m: '1111', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // get A1 background + const background = range.getBackground(); + + expect(background).toEqual('#ff0000'); +}); +test('Test getBackgrounds', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 backgrounds + const backgrounds = range.getBackgrounds(); + + expect(backgrounds).toEqual([['#ff0000', '#0000ff']]); +}); + +test('Test getCell', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get B1 Range instance + const cellValue = range.getCell(0, 1).getValue()?.m; + + expect(cellValue).toEqual('2'); +}); + +test('Test getColumn', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get the starting column position for this range. + const columnNumber = range.getColumn(); + + expect(columnNumber).toEqual(0); +}); + +test('Test getDisplayValue', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get the displayed value of the top-left cell in the range + const cellValue = range.getDisplayValue(); + + expect(cellValue).toEqual('1'); +}); + +test('Test getDisplayValues', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get the rectangular grid of values for this range. + const cellValue = range.getDisplayValues(); + + expect(cellValue).toEqual([['1', '2']]); +}); + +test('Test getFontColor', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 backgrounds + const backgrounds = range.getFontColor(); + + expect(backgrounds).toEqual('blue'); +}); + +test('Test getFontColors', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 backgrounds + const backgrounds = range.getFontColors(); + + expect(backgrounds).toEqual([['blue', 'green']]); +}); + +test('Test getFontFamily', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 backgrounds + const backgrounds = range.getFontFamily(); + + expect(backgrounds).toEqual('微软雅黑'); +}); + +test('Test getFontFamilies', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 backgrounds + const backgrounds = range.getFontFamilies(); + + expect(backgrounds).toEqual([['微软雅黑', 'Roboto']]); +}); + +test('Test getFontLines', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + ul: { s: BooleanNumber.TRUE }, + ol: { s: BooleanNumber.TRUE }, + st: { s: BooleanNumber.TRUE }, + }, + 2: { + ul: { s: BooleanNumber.TRUE }, + ol: { s: BooleanNumber.TRUE }, + st: { s: BooleanNumber.TRUE }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 FontLines + const underlines = range.getUnderlines(); + const overline = range.getOverlines(); + const strikethroughs = range.getStrikeThroughs(); + + expect(underlines).toEqual([ + [{ s: BooleanNumber.TRUE }, { s: BooleanNumber.TRUE }], + ]); + expect(overline).toEqual([ + [{ s: BooleanNumber.TRUE }, { s: BooleanNumber.TRUE }], + ]); + expect(strikethroughs).toEqual([ + [{ s: BooleanNumber.TRUE }, { s: BooleanNumber.TRUE }], + ]); +}); + +test('Test getFontLine', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + ul: { s: BooleanNumber.TRUE }, + ol: { s: BooleanNumber.TRUE }, + st: { s: BooleanNumber.TRUE }, + }, + 2: { + ul: { s: BooleanNumber.TRUE }, + ol: { s: BooleanNumber.TRUE }, + st: { s: BooleanNumber.TRUE }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // get A1 getFontLine + const underlines = range.getUnderline(); + const overline = range.getOverline(); + const strikethroughs = range.getStrikeThrough(); + + expect(underlines).toEqual({ s: BooleanNumber.TRUE }); + expect(overline).toEqual({ s: BooleanNumber.TRUE }); + expect(strikethroughs).toEqual({ s: BooleanNumber.TRUE }); +}); + +test('Test getFontSize', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 backgrounds + const backgrounds = range.getFontSize(); + + expect(backgrounds).toEqual(12); +}); + +test('Test getFontSizes', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getFontSizes + const fs = range.getFontSizes(); + + expect(fs).toEqual([[12, 14]]); +}); + +test('Test getFontWeight', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + bl: 1, + }, + 2: { + bl: 0, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // get A1:B1 getFontWeights + const A1 = range.getFontWeight(); + + expect(A1).toEqual(1); + + const range2 = worksheet.getRange('B1'); + + // get A1:B1 getFontWeight + const B1 = range2.getFontWeight(); + + expect(B1).toEqual(0); +}); +test('Test getFontWeights', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + bl: 1, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + bl: 0, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getFontWeights + const A1 = range.getFontWeights(); + + expect(A1).toEqual([[1, 0]]); +}); + +test('Test getHorizontalAlignment', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + ht: HorizontalAlign.LEFT, + }, + 2: { + ht: HorizontalAlign.CENTER, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // get A1:B1 getHorizontalAlignments + const A1 = range.getHorizontalAlignment(); + + expect(A1).toEqual(HorizontalAlign.LEFT); +}); + +test('Test getHorizontalAlignments', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + ht: HorizontalAlign.LEFT, + }, + 2: { + ht: HorizontalAlign.CENTER, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getHorizontalAlignments + const A1 = range.getHorizontalAlignments(); + + expect(A1).toEqual([[HorizontalAlign.LEFT, HorizontalAlign.CENTER]]); +}); + +test('Test getVerticalAlignments', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + vt: VerticalAlign.TOP, + }, + 2: { + vt: VerticalAlign.MIDDLE, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getVerticalAlignments + const A1 = range.getVerticalAlignments(); + + expect(A1).toEqual([[VerticalAlign.TOP, VerticalAlign.MIDDLE]]); +}); + +test('Test getVerticalAlignment', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + vt: VerticalAlign.TOP, + }, + 2: { + vt: VerticalAlign.MIDDLE, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getVerticalAlignments + const A1 = range.getVerticalAlignment(); + + expect(A1).toEqual(VerticalAlign.TOP); +}); + +test('Test getFontStyles', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + it: 1, + }, + 2: { + it: 0, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getFontStyles + const A1 = range.getFontStyles(); + + expect(A1).toEqual([[1, 0]]); +}); + +test('Test getFontStyle', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + it: 1, + }, + 2: { + it: 0, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getFontStyles + const A1 = range.getFontStyle(); + + expect(A1).toEqual(1); +}); + +test('Test getTextDirections', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + td: 1, + }, + 2: { + td: 2, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 getTextDirections + const A1 = range.getTextDirections(); + + expect(A1).toEqual([[1, 2]]); +}); + +test('Test getTextDirection', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + td: 1, + }, + 2: { + td: 2, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // get A1 getTextDirection + const A1 = range.getTextDirection(); + + expect(A1).toEqual(1); +}); + +test('Test getTextRotations', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + tr: { + a: 90, + }, + }, + 2: { + tr: { + a: -90, + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1 getTextRotations + const A1 = range.getTextRotations(); + + expect(A1).toEqual([ + [ + { + a: 90, + }, + { + a: -90, + }, + ], + ]); +}); + +test('Test getTextRotation', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + tr: { + a: 90, + }, + }, + 2: { + tr: { + a: -90, + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // get A1 getTextRotation + const A1 = range.getTextRotation(); + + expect(A1).toEqual({ a: 90 }); +}); + +test('Test getTextStyles', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + ff: '微软雅黑', + }, + 2: { + fs: 14, + ff: '宋体', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1 getTextStyles + const A1 = range.getTextStyles(); + + expect(A1).toEqual([ + [ + { + fs: 12, + ff: '微软雅黑', + }, + { + fs: 14, + ff: '宋体', + }, + ], + ]); +}); + +test('Test getTextStyle', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + ff: '微软雅黑', + }, + 2: { + fs: 14, + ff: '宋体', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1 getTextStyle + const A1 = range.getTextStyle(); + + expect(A1).toEqual({ + fs: 12, + ff: '微软雅黑', + }); +}); + +test('Test getSheet', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // get A1:B1 sheetId + const sheetId = range.getSheet().getSheetId(); + + expect(sheetId).toEqual('sheet'); +}); + +test('Test getNumColumns', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:C1'); + + // get A1:B1 sheetId + const column = range.getNumColumns(); + + expect(column).toEqual(3); +}); + +test('Test getNumRows', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B5'); + + // get A1:B1 sheetId + const rows = range.getNumRows(); + + expect(rows).toEqual(5); +}); + +test('Test getRowIndex', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + ff: '微软雅黑', + bg: { + rgb: '#ff0000', + }, + }, + 2: { + cl: { + rgb: 'green', + }, + fs: 14, + ff: 'Roboto', + bg: { + rgb: '#0000ff', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A5'); + + // get A1:B1 sheetId + const row = range.getRowIndex(); + + expect(row).toEqual(4); +}); + +test('Test getLastColumn', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + ff: '微软雅黑', + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('C5'); + + // get LastColumn + const LastColumn = range.getLastColumn(); + + expect(LastColumn).toEqual(2); +}); + +test('Test getWrapStrategies', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + tb: WrapStrategy.OVERFLOW, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + const wrapStrategies = range.getWrapStrategies(); + expect(wrapStrategies).toEqual([[1, 1]]); + + const wrapStrategy = range.getWrapStrategy(); + expect(wrapStrategy).toEqual(1); +}); + +test('Test getWraps/getWrap', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + tb: WrapStrategy.OVERFLOW, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + // get LastColumn + const wraps = range.getWraps(); + expect(wraps).toEqual([[1, 1]]); + + const wrap = range.getWrap(); + expect(wrap).toEqual(1); +}); + +test('Test setFontColors', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '2', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set new color + range.setFontColors([['green', 'yellow']]); + + const currentCell1 = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle1 = workbook.getStyles().get(currentCell1 && currentCell1.s); + const currentColor1 = currentStyle1 && currentStyle1.cl?.rgb; + + expect(currentColor1).toEqual('green'); + + const currentCell2 = worksheet.getCellMatrix().getValue(0, 1); + const currentStyle2 = workbook.getStyles().get(currentCell2 && currentCell2.s); + const currentColor2 = currentStyle2 && currentStyle2.cl?.rgb; + + expect(currentColor2).toEqual('yellow'); + + // undo + worksheet.getCommandManager().undo(); + + // test previous color + const preCell1 = worksheet.getCellMatrix().getValue(0, 0); + const preStyle1 = workbook.getStyles().get(preCell1 && preCell1.s); + const preColor1 = preStyle1 && preStyle1.cl?.rgb; + expect(preColor1).toEqual('blue'); + + const preCell2 = worksheet.getCellMatrix().getValue(0, 1); + const preStyle2 = workbook.getStyles().get(preCell2 && preCell2.s); + const preColor2 = preStyle2 && preStyle2.cl?.rgb; + expect(preColor2).toEqual('red'); +}); + +test('Test setBackground', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + bg: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + // set A1 fontSize + range.setBackground('red'); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + bg: { + rgb: 'red', + }, + ff: 'Arial', + fs: 12, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + bg: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }); + // redo + worksheet.getCommandManager().redo(); + + // test next style + const nextCell = worksheet.getCellMatrix().getValue(0, 0); + const nextStyle = workbook.getStyles().get(nextCell && nextCell.s); + expect(nextStyle).toEqual({ + bg: { + rgb: 'red', + }, + ff: 'Arial', + fs: 12, + }); +}); + +test('Test setBackgrounds', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + bg: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set A1 fontSize + range.setBackgrounds([['red', 'green']]); + + const currentCell = worksheet.getCellMatrix().getValue(0, 1); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + bg: { + rgb: 'green', + }, + ff: 'Arial', + fs: 12, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 1); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + bg: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }); + // redo + worksheet.getCommandManager().redo(); + + // test next style + const nextCell = worksheet.getCellMatrix().getValue(0, 1); + const nextStyle = workbook.getStyles().get(nextCell && nextCell.s); + expect(nextStyle).toEqual({ + bg: { + rgb: 'green', + }, + ff: 'Arial', + fs: 12, + }); +}); + +test('Test setBackgroundRGB', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + bg: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set A1 fontSize + range.setBackgroundRGB(0, 0, 0); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + bg: { + rgb: 'RGB(0,0,0)', + }, + ff: 'Arial', + fs: 12, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + bg: { + rgb: 'blue', + }, + ff: 'Arial', + fs: 12, + }); + // redo + worksheet.getCommandManager().redo(); + + // test next style + const nextCell = worksheet.getCellMatrix().getValue(0, 0); + const nextStyle = workbook.getStyles().get(nextCell && nextCell.s); + expect(nextStyle).toEqual({ + bg: { + rgb: 'RGB(0,0,0)', + }, + ff: 'Arial', + fs: 12, + }); +}); + +test('Test setUnderline', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set A1 fontSize + range.setUnderlines([[{ s: BooleanNumber.TRUE }]]); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + fs: 12, + ul: { s: BooleanNumber.TRUE }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + fs: 12, + }); +}); + +test('Test setOverline', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set A1 fontSize + range.setOverlines([[{ s: BooleanNumber.TRUE }]]); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + fs: 12, + ol: { s: BooleanNumber.TRUE }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + fs: 12, + }); +}); + +test('Test setStrikeThrough', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set A1 fontSize + range.setStrikeThroughs([[{ s: BooleanNumber.TRUE }]]); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + const currentStyle = workbook.getStyles().get(currentCell && currentCell.s); + + expect(currentStyle).toEqual({ + fs: 12, + st: { s: BooleanNumber.TRUE }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + fs: 12, + }); +}); + +test('Test setHorizontalAlignments', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setHorizontalAlignments + range.setHorizontalAlignments([[HorizontalAlign.CENTER, HorizontalAlign.LEFT]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + const B1 = worksheet.getCellMatrix().getValue(0, 1); + const B1Style = workbook.getStyles().get(B1 && B1.s); + + expect(A1Style).toEqual({ + fs: 12, + ht: HorizontalAlign.CENTER, + }); + + expect(B1Style).toEqual({ + ht: HorizontalAlign.LEFT, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preCell = worksheet.getCellMatrix().getValue(0, 0); + const preStyle = workbook.getStyles().get(preCell && preCell.s); + expect(preStyle).toEqual({ + fs: 12, + }); + + /** + * TODO :优化数据 + * 撤销后B1单元格为空 + * cellData: { + * 0: { + * 0: { + * s: 1, + * v: 1, + * m: '1', + * }, + * 1: {} + * }, + * }, + */ + const preB1 = worksheet.getCellMatrix().getValue(0, 1); + expect(preB1).toEqual({}); +}); + +test('Test isPartOfMerge', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + mergeData: [ + { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 1, + }, + ], + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 1, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeA = worksheet.getRange('A1:C3'); + + const isMergeA = rangeA.isPartOfMerge(); + + expect(isMergeA).toEqual(true); + + const rangeB = worksheet.getRange('A4:C6'); + + const isMergeB = rangeB.isPartOfMerge(); + + expect(isMergeB).toEqual(false); +}); + +test('Test getLastRow', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:C5'); + + const lastRow = range.getLastRow(); + + expect(lastRow).toEqual(4); +}); + +test('Test setFontSizes', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setFontSizes + range.setFontSizes([[16, 18]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + const B1 = worksheet.getCellMatrix().getValue(0, 1); + const B1Style = workbook.getStyles().get(B1 && B1.s); + + expect(A1Style).toEqual({ + fs: 16, + }); + + expect(B1Style).toEqual({ + fs: 18, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setTextDirections', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setTextDirections + range.setTextDirections([[0, 1]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + const B1 = worksheet.getCellMatrix().getValue(0, 1); + const B1Style = workbook.getStyles().get(B1 && B1.s); + + expect(A1Style).toEqual({ + fs: 12, + td: 0, + }); + + expect(B1Style).toEqual({ + fs: 12, + td: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setFontWeights', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setFontWeights + range.setFontWeights([[0, 1]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + const B1 = worksheet.getCellMatrix().getValue(0, 1); + const B1Style = workbook.getStyles().get(B1 && B1.s); + + expect(A1Style).toEqual({ + fs: 12, + bl: 0, + }); + + expect(B1Style).toEqual({ + fs: 12, + bl: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setFontWeight', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1'); + + // set A1 setFontWeights + range.setFontWeight(1); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + bl: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setFontStyles', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setFontStyles + range.setFontStyles([[0, 1]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + it: 0, + }); + + const B1 = worksheet.getCellMatrix().getValue(0, 1); + const B1Style = workbook.getStyles().get(B1 && B1.s); + + expect(B1Style).toEqual({ + fs: 12, + it: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); + const preB1 = worksheet.getCellMatrix().getValue(0, 1); + const preB1Style = workbook.getStyles().get(preB1 && preB1.s); + expect(preB1Style).toEqual({ + fs: 12, + }); +}); +test('Test setFontStyle', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1'); + + // set A1 setFontStyles + range.setFontStyle(1); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + it: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); +test('Test setVerticalAlignments', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setVerticalAlignments + range.setVerticalAlignments([[0, 1]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + vt: 0, + }); + const B1 = worksheet.getCellMatrix().getValue(0, 1); + const B1Style = workbook.getStyles().get(B1 && B1.s); + + expect(B1Style).toEqual({ + fs: 12, + vt: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); + + const preB1 = worksheet.getCellMatrix().getValue(0, 0); + const preB1Style = workbook.getStyles().get(preB1 && preB1.s); + expect(preB1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setTextRotations', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setTextRotations + range.setTextRotations([[0, 1]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + const B1 = worksheet.getCellMatrix().getValue(0, 1); + const B1Style = workbook.getStyles().get(B1 && B1.s); + + expect(A1Style).toEqual({ + fs: 12, + tr: { v: 0, a: 0 }, + }); + + expect(B1Style).toEqual({ + fs: 12, + tr: { v: 0, a: 1 }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setTextRotation', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setTextRotation + range.setTextRotation(1); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + tr: { + v: 0, + a: 1, + }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setTextStyle', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1'); + + // set A1 setTextStyle + range.setTextStyle({ + ff: '黑体', + fs: 18, + bl: 1, + }); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + ff: '黑体', + fs: 18, + bl: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setTextStyles', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + // set A1 setTextStyle + range.setTextStyles([ + [ + { + ff: '黑体', + fs: 18, + bl: 1, + }, + { + ff: '宋体', + fs: 24, + bl: 0, + }, + ], + ]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + ff: '黑体', + fs: 18, + bl: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setVerticalText', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1'); + + // set A1 setVerticalText + range.setVerticalText(1); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + tr: { + a: 90, + v: 1, + }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setWrap', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1'); + + // set A1 setWrap + range.setWrap(1); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + tb: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test setWraps', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1'); + + // set A1 setWraps + range.setWraps([[1, 1]]); + + const A1 = worksheet.getCellMatrix().getValue(0, 0); + const A1Style = workbook.getStyles().get(A1 && A1.s); + + expect(A1Style).toEqual({ + fs: 12, + tb: 1, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous style + const preA1 = worksheet.getCellMatrix().getValue(0, 0); + const preA1Style = workbook.getStyles().get(preA1 && preA1.s); + expect(preA1Style).toEqual({ + fs: 12, + }); +}); + +test('Test activate Range', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + const rangeData = range.activate().getRangeData(); + + const activeRange = worksheet.getSelection().getActiveRange().getRangeData(); + + expect(rangeData).toEqual(activeRange); +}); + +test('Test activateAsCurrentCell', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('B1'); + + const activeCurrentCell = range.activateAsCurrentCell().getRangeData(); + + const CurrentCell = worksheet.getSelection().getCurrentCell().getRangeData(); + + expect(activeCurrentCell).toEqual(CurrentCell); +}); + +test('Test isIntersection', () => { + let { worksheet, workbook } = demo(); + + const currentRange = worksheet.getRange('A1:C5'); + + const range = worksheet.getRange('B1:D3'); + + const isInter = currentRange.isIntersection(range); + + expect(isInter).toEqual(true); +}); +test('Test clearContent', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + range.clearContent(); + const data = range.getObjectValues(); + + expect(data).toEqual({ + 0: { + 0: { + s: 1, + v: '', + m: '', + }, + 1: { + s: 1, + v: '', + m: '', + }, + }, + }); +}); + +test('Test clearNote', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + range.clearNote(); + const data = range.getObjectValues(); + + expect(data).toEqual({ + 0: { + 0: { + s: 1, + v: '1', + m: '1', + }, + 1: { + s: 1, + v: 1, + m: '1', + }, + }, + }); +}); + +test('Test removeDuplicates', () => { + const container = IOCContainerStartUpReady({ + styles: {}, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 3, + m: '3', + }, + 1: { + s: '1', + v: 4, + m: '4', + }, + }, + 2: { + 0: { + s: '1', + v: 3, + m: '3', + }, + 1: { + s: '1', + v: 4, + m: '4', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B4'); + + range.removeDuplicates(); + const data = range.getObjectValues(); + expect(data).toEqual({ + 0: { + 0: { s: 1, v: 1, m: '1' }, + 1: { s: 1, v: 2, m: '2' }, + }, + 1: { + 0: { s: 1, v: 3, m: '3' }, + 1: { s: 1, v: 4, m: '4' }, + }, + 2: { 0: {}, 1: {} }, + 3: { 0: {}, 1: {} }, + }); +}); + +test('Test getColumnMatrix', () => { + const container = IOCContainerStartUpReady({ + styles: {}, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 3, + m: '3', + }, + 1: { + s: '1', + v: 4, + m: '4', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B2'); + + const A = range.getColumnMatrix(0).getData(); + const B = range.getRowMatrix(1).getData(); + + expect(A).toEqual({ + 0: { + 0: { s: 1, v: 1, m: '1' }, + }, + 1: { + 0: { s: 1, v: 3, m: '3' }, + }, + }); + + expect(B).toEqual({ + 1: { + 0: { s: 1, v: 3, m: '3' }, + 1: { s: 1, v: 4, m: '4' }, + }, + }); +}); + +test('Test isBlank', () => { + const container = IOCContainerStartUpReady({ + styles: {}, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 3, + m: '3', + }, + 1: { + s: '1', + v: 4, + m: '4', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1:B2'); + + const A = range.isBlank(); + + expect(A).toEqual(false); +}); + +test('Test getA1Notation', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange(0, 0, 1, 4); + + const A1 = range.getA1Notation(); + + expect(A1).toEqual('A1:E2'); +}); + +// test('Test getFormulas/getFormual', () => { +// const container = IOCContainerStartUpReady({ +// styles: {}, +// }); +// const context = container.getSingleton('Context'); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); + +// const configure = { +// sheetId: 'sheet', +// cellData: { +// 0: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// f: 'SUM(1,2)', +// }, +// 1: { +// s: 1, +// v: 2, +// m: '2', +// f: 'SUM(3,4)', +// }, +// }, +// }, +// status: 1, +// }; +// const worksheet = new WorkSheet(context, configure); +// workbook.insertSheet(worksheet); +// worksheet.setCommandManager(commandManager); + +// const range1 = worksheet.getRange('A1'); + +// const formula = range1.getFormula(); + +// expect(formula).toEqual('SUM(1,2)'); + +// const range2 = worksheet.getRange('A1:B1'); + +// const formulas = range2.getFormulas(); + +// expect(formulas).toEqual([['SUM(1,2)', 'SUM(3,4)']]); +// }); + +// test('Test autoFill bottom', () => { +// const container = IOCContainerStartUpReady({ +// styles: {}, +// }); +// const context = container.getSingleton('Context'); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); + +// const configure = { +// sheetId: 'sheet', +// cellData: { +// 0: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// fm: { +// f: 'g', +// t: FormatType.NUMBER, +// }, +// }, +// 1: { +// s: 1, +// v: 2, +// m: '2', +// }, +// }, +// }, +// status: 1, +// }; +// const worksheet = new WorkSheet(context, configure); +// workbook.insertSheet(worksheet); +// worksheet.setCommandManager(commandManager); + +// const range = worksheet.getRange('A1'); +// const sourceRange = worksheet.getRange('A1:A5'); +// range.autoFill(sourceRange, AutoFillSeries.DEFAULT_SERIES); + +// const A5 = worksheet.getCellMatrix().getValue(4, 0); + +// expect(A5).toEqual({ +// 0: { +// s: 1, +// v: 1, +// m: '1', +// fm: { +// f: 'g', +// t: FormatType.NUMBER, +// }, +// }, +// }); +// }); + +// test('Test autoFill right', () => { +// const container = IOCContainerStartUpReady({ +// styles: {}, +// }); +// const context = container.getSingleton('Context'); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); + +// const configure = { +// sheetId: 'sheet', +// cellData: { +// 0: { +// 0: { +// v: 1, +// m: '1', +// fm: { +// f: 'g', +// t: FormatType.NUMBER, +// }, +// }, +// 1: { +// v: 2, +// m: '2', +// fm: { +// f: 'g', +// t: FormatType.NUMBER, +// }, +// }, +// }, +// 1: { +// 0: { +// v: 2, +// m: '2', +// fm: { +// f: 'g', +// t: FormatType.NUMBER, +// }, +// }, +// }, +// }, +// status: 1, +// }; +// const worksheet = new WorkSheet(context, configure); +// workbook.insertSheet(worksheet); +// worksheet.setCommandManager(commandManager); + +// const range = worksheet.getRange('A1:B1'); +// const sourceRange = worksheet.getRange('A1:D1'); +// range.autoFill(sourceRange, AutoFillSeries.DEFAULT_SERIES); + +// const a = worksheet.getCellMatrix().getMatrix(); +// const D1 = worksheet.getCellMatrix().getValue(0, 3); + +// expect(D1).toEqual({ +// 3: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// fm: { +// f: 'g', +// t: FormatType.NUMBER, +// }, +// }, +// }, +// }); +// }); + +test('Test getGridId', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange(1, 1, 2, 5); + + const A1 = range.getGridId(); + + expect(A1).toEqual('sheet'); +}); + +// test('Test getFormulas/getFormual', () => { +// const container = IOCContainerStartUpReady({ +// styles: {}, +// }); +// const context = container.getSingleton('Context'); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); + +// const configure = { +// sheetId: 'sheet', +// cellData: { +// 0: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// n: '111', +// }, +// 1: { +// s: 1, +// v: 2, +// m: '2', +// n: '222', +// }, +// }, +// }, +// status: 1, +// }; +// const worksheet = new WorkSheet(context, configure); +// workbook.insertSheet(worksheet); +// worksheet.setCommandManager(commandManager); + +// const range1 = worksheet.getRange('A1'); + +// const note = range1.getNote(); + +// expect(note).toEqual('111'); + +// const range2 = worksheet.getRange('A1:B1'); + +// const notes = range2.getNotes(); + +// expect(notes).toEqual([['111', '222']]); +// }); + +// test('Test getNumberFormat/getNumberFormats', () => { +// const container = IOCContainerStartUpReady({ +// styles: {}, +// }); +// const context = container.getSingleton('Context'); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); + +// const configure = { +// sheetId: 'sheet', +// cellData: { +// 0: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// fm: { +// f: 'g', +// t: FormatType.NUMBER, +// }, +// }, +// 1: { +// s: 1, +// v: 2, +// m: '2', +// fm: { +// f: 'g', +// t: FormatType.TEXT, +// }, +// }, +// }, +// }, +// status: 1, +// }; +// const worksheet = new WorkSheet(context, configure); +// workbook.insertSheet(worksheet); +// worksheet.setCommandManager(commandManager); + +// const range1 = worksheet.getRange('A1:B1'); +// const formats = range1.getNumberFormats(); + +// const range2 = worksheet.getRange('A1'); +// const format = range2.getNumberFormat(); + +// expect(formats).toEqual([['g', 'g']]); +// expect(format).toEqual('g'); +// }); + +test('Test getHeight', () => { + let { worksheet } = demo(); + + const range = worksheet.getRange('A1:B2'); + + const h = range.getHeight(); + + // defaultRowHeight: 27, + expect(h).toEqual(27 * 2); +}); + +test('Test getWidth', () => { + let { worksheet } = demo(); + + const range = worksheet.getRange('A1:B2'); + + const w = range.getWidth(); + + // defaultColumnWidth: 93, + expect(w).toEqual(93 * 2); +}); + +test('Test trimWhitespace', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: '1 1', + m: '1 1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const range = worksheet.getRange('A1'); + + range.trimWhitespace(); + + const A1 = worksheet.getRange('A1').getValue()?.m; + expect(A1).toEqual('11'); +}); + +test('Test getNextDataCell', () => { + let { worksheet } = demo(); + + const range = worksheet.getRange('C3:D3'); + + const cellTop = range.getNextDataCell(Direction.TOP).getA1Notation(); + expect(cellTop).toEqual('C1'); + + const cellBottom = range.getNextDataCell(Direction.BOTTOM).getA1Notation(); + expect(cellBottom).toEqual('C31'); + + const cellLeft = range.getNextDataCell(Direction.LEFT).getA1Notation(); + expect(cellLeft).toEqual('A3'); + + const cellRight = range.getNextDataCell(Direction.RIGHT).getA1Notation(); + expect(cellRight).toEqual('K3'); +}); + +test('Test getDataRegion', () => { + let { worksheet } = demo(); + + const range = worksheet.getRange('C3:D3'); + + const cellColumns = range.getDataRegion(Dimension.COLUMNS).getA1Notation(); + expect(cellColumns).toEqual('B1:C3'); + + const cellRows = range.getDataRegion(Dimension.ROWS).getA1Notation(); + expect(cellRows).toEqual('A2:C3'); + + const cell = range.getDataRegion().getA1Notation(); + expect(cell).toEqual('B3:C3'); +}); + +test('Test getRichTextValues/getRichTextValue', () => { + let { worksheet } = demo(); + + const ranges = worksheet.getRange('A1:B1'); + const cells = ranges.getRichTextValues(); + expect(cells).toEqual([['', '']]); + + const range = worksheet.getRange('A1'); + const cell = range.getRichTextValue(); + expect(cell).toEqual(''); +}); + +test('Test setRangeData', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B1'); + + const newData: ICellData = { + s: { + bg: { + rgb: 'red', + }, + }, + v: 11, + m: '11', + }; + + range.setRangeData(newData); + const currentRangeValues = range.getValues(); + expect(currentRangeValues).toEqual([ + [ + { + v: 11, + m: '11', + s: '12345678', + }, + { + v: 11, + m: '11', + s: '12345678', + }, + ], + ]); + + // undo + worksheet.getCommandManager().undo(); + const preRangeValues = range.getValues(); + + expect(preRangeValues).toEqual([ + [ + { + s: '1', + v: '1', + m: '1', + }, + { + s: '1', + v: 1, + m: '1', + }, + ], + ]); + + // redo + worksheet.getCommandManager().redo(); + const nextRangeValues = range.getValues(); + + expect(nextRangeValues).toEqual([ + [ + { + v: 11, + m: '11', + s: '12345678', + }, + { + v: 11, + m: '11', + s: '12345678', + }, + ], + ]); +}); + +// test('Test setNumberFormat', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1'); + +// range.setNumberFormat('0.000'); +// const cell = range.getNumberFormat(); +// expect(cell).toEqual('0.000'); + +// // undo +// worksheet.getCommandManager().undo(); +// const uncell = range.getNumberFormat(); +// expect(uncell).toEqual(''); + +// // redo +// worksheet.getCommandManager().redo(); +// const recell = range.getNumberFormat(); +// expect(recell).toEqual('0.000'); +// }); + +// test('Test setNumberFormats', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1:B1'); + +// range.setNumberFormats([['0.000', '0.000']]); +// const cell = range.getNumberFormats(); +// expect(cell).toEqual([['0.000', '0.000']]); + +// // undo +// worksheet.getCommandManager().undo(); +// const undoValue = worksheet.getCellMatrix().getValue(0, 0); +// const uncell = range.getNumberFormats(); +// expect(uncell).toEqual([['', '']]); + +// // redo +// worksheet.getCommandManager().redo(); +// // const redoValue = worksheet.getCellMatrix().getValue(0, 0); +// const recell = range.getNumberFormats(); +// expect(recell).toEqual([['0.000', '0.000']]); +// }); + +// test('Test setFormulas', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1:B1'); + +// range.setFormulas([['=SUM(1,2)', '=SUM(1,2)']]); +// const cell = range.getFormulas(); +// expect(cell).toEqual([['=SUM(1,2)', '=SUM(1,2)']]); + +// // undo +// worksheet.getCommandManager().undo(); +// const uncell = range.getFormulas(); +// expect(uncell).toEqual([['', '']]); + +// // redo +// worksheet.getCommandManager().redo(); +// const recell = range.getFormulas(); +// expect(recell).toEqual([['=SUM(1,2)', '=SUM(1,2)']]); +// }); + +// test('Test setFormula', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1:B1'); + +// range.setFormula('=SUM(1,2)'); + +// const cell = range.getFormula(); +// expect(cell).toEqual('=SUM(1,2)'); + +// // undo +// worksheet.getCommandManager().undo(); +// const uncell = range.getFormula(); +// expect(uncell).toEqual(''); + +// // redo +// worksheet.getCommandManager().redo(); +// const recell = range.getFormula(); +// expect(recell).toEqual('=SUM(1,2)'); +// }); +// test('Test setNotes', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1:B1'); + +// range.setNotes([['note1', 'note2']]); + +// const cell = range.getNotes(); +// expect(cell).toEqual([['note1', 'note2']]); + +// // undo +// worksheet.getCommandManager().undo(); +// const uncell = range.getNotes(); +// expect(uncell).toEqual([['', '']]); + +// // redo +// worksheet.getCommandManager().redo(); +// const recell = range.getNotes(); +// expect(recell).toEqual([['note1', 'note2']]); +// }); +// test('Test setNote', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1:B1'); + +// range.setNote('note'); + +// const cell = range.getNote(); +// expect(cell).toEqual('note'); + +// // undo +// worksheet.getCommandManager().undo(); +// const uncell = range.getNote(); +// expect(uncell).toEqual(''); + +// // redo +// worksheet.getCommandManager().redo(); +// const recell = range.getNote(); +// expect(recell).toEqual('note'); +// }); + +test('Test randomize', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B5'); + + range.randomize(); + // TODO +}); + +test('Test getMergedRanges', () => { + let { worksheet } = demo(); + + const range = worksheet.getRange('A1:B3'); + + const merge = range.getMergedRanges(); + expect(merge).toEqual({ + endColumn: 2, + endRow: 2, + startColumn: 1, + startRow: 1, + }); +}); + +test('Test mergeAcross', () => { + let { worksheet } = demo(); + + const range = worksheet.getRange('A1:B3'); + + range.mergeAcross(); + + const merge = worksheet.getMerges(); + + expect(merge.getByRowColumn(1, 1)).not.toBeUndefined(); +}); + +test('Test mergeVertically', () => { + let { worksheet, workbook } = demo(); + + const range = worksheet.getRange('A1:B3'); + + range.mergeVertically(); + const merge = worksheet.getMerges(); + expect(merge.getByRowColumn(1, 1)).not.toBeUndefined(); +}); + +// test('Test createFilter', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1:B3'); + +// range.createFilter(); +// // TODO +// }); +// test('Test setCellId', () => { +// let { worksheet, workbook } = demo(); + +// const range = worksheet.getRange('A1'); + +// range.setCellId(); + +// let currentCell = worksheet.getCellMatrix().getValue(0, 0); + +// // use mock data +// expect(currentCell?.id).toHaveLength(8); + +// range.setCellId('cell-01'); + +// currentCell = worksheet.getCellMatrix().getValue(0, 0); + +// expect(currentCell).toEqual({ s: 1, v: '1', m: '1', id: 'cell-01' }); +// }); diff --git a/packages/core/test/Domain/RangeList.test.ts b/packages/core/test/Domain/RangeList.test.ts new file mode 100644 index 0000000000..13f4cc91c6 --- /dev/null +++ b/packages/core/test/Domain/RangeList.test.ts @@ -0,0 +1,690 @@ +/** + * @jest-environment jsdom + */ +import { Context } from '../../src/Basics/Context'; +import { WorkBook } from '../../src/Sheets/Domain/WorkBook'; +import { WorkSheet } from '../../src/Sheets/Domain/WorkSheet'; +import { BooleanNumber, WrapStrategy } from '../../src/Enum'; +import { TestInit } from '../ContainerStartUp'; +import { IOCContainerStartUpReady } from './Range.test'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +// TODO performance test, 1000 times +// TODO 修复RangeList单元测试报错 + +test('Test RangeList setValue', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + // set new value + rangeList.setValue(3); + + const currentCell = worksheet.getCellMatrix().getValue(0, 0); + + expect(currentCell).toEqual({ v: 3, m: '3' }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = worksheet.getCellMatrix().getValue(0, 0); + expect(preCell).toEqual({ s: 1, v: 1, m: '1' }); +}); + +test('Test RangeList clear', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + // clear + rangeList.clear({ formatOnly: true, contentsOnly: true }); + + // test current A1 B1 + const currentA = worksheet?.getCellMatrix()?.getValue(0, 0); + const currentB = worksheet?.getCellMatrix()?.getValue(0, 1); + + expect(currentA).toEqual({ v: '', m: '' }); + expect(currentB).toEqual({ v: '', m: '' }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous A1 B1 + const preA = worksheet?.getCellMatrix()?.getValue(0, 0); + const preB = worksheet?.getCellMatrix()?.getValue(0, 1); + + expect(preA).toEqual({ s: 1, v: 1, m: '1' }); + expect(preB).toEqual({ s: 1, v: 2, m: '2' }); +}); + +function demo() { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 111, + m: '1 11', + }, + 1: { + s: '1', + v: 222, + m: '2 22', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + return worksheet; +} + +test('Test RangeList setFontSize', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setFontSize(20); + + const cell = range.getFontSizes(); + + expect(cell).toEqual([[20, 20]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getFontSizes(); + expect(preCell).toEqual([[12, 12]]); +}); + +test('Test RangeList setFontFamily', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setFontFamily('宋体'); + + const cell = range.getFontFamilies(); + + expect(cell).toEqual([['宋体', '宋体']]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getFontFamilies(); + expect(preCell).toEqual([['', '']]); +}); + +test('Test RangeList setUnderline', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setUnderline(BooleanNumber.TRUE); + + const cell = range.getUnderlines(); + + expect(cell).toEqual([[{ s: BooleanNumber.TRUE }, { s: BooleanNumber.TRUE }]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getUnderlines(); + expect(preCell).toEqual([ + [{ s: BooleanNumber.FALSE }, { s: BooleanNumber.FALSE }], + ]); +}); + +test('Test RangeList setOverline', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setOverline(BooleanNumber.TRUE); + + const cell = range.getOverlines(); + + expect(cell).toEqual([[{ s: BooleanNumber.TRUE }, { s: BooleanNumber.TRUE }]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getOverlines(); + expect(preCell).toEqual([ + [{ s: BooleanNumber.FALSE }, { s: BooleanNumber.FALSE }], + ]); +}); + +test('Test RangeList setStrikeThrough', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setStrikeThrough(BooleanNumber.TRUE); + + const cell = range.getStrikeThroughs(); + + expect(cell).toEqual([[{ s: BooleanNumber.TRUE }, { s: BooleanNumber.TRUE }]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getStrikeThroughs(); + expect(preCell).toEqual([ + [{ s: BooleanNumber.FALSE }, { s: BooleanNumber.FALSE }], + ]); +}); + +test('Test RangeList setFontStyle', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setFontStyle(1); + + const cell = range.getFontStyles(); + + expect(cell).toEqual([[1, 1]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getFontStyles(); + expect(preCell).toEqual([[0, 0]]); +}); + +test('Test RangeList setFontWeight', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setFontWeight(1); + + const cell = range.getFontWeights(); + + expect(cell).toEqual([[1, 1]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getFontWeights(); + expect(preCell).toEqual([[0, 0]]); +}); + +test('Test RangeList setHorizontalAlignment', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setHorizontalAlignment(1); + + const cell = range.getHorizontalAlignments(); + + expect(cell).toEqual([[1, 1]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getHorizontalAlignments(); + expect(preCell).toEqual([[0, 0]]); +}); + +test('Test RangeList setTextDirection', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setTextDirection(1); + + const cell = range.getTextDirections(); + + expect(cell).toEqual([[1, 1]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getTextDirections(); + expect(preCell).toEqual([[0, 0]]); +}); + +test('Test RangeList setTextRotation', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setTextRotation(90); + + const cell = range.getTextRotations(); + + expect(cell).toEqual([ + [ + { + a: 90, + v: 0, + }, + { + a: 90, + v: 0, + }, + ], + ]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getTextRotations(); + expect(preCell).toEqual([ + [ + { + a: 0, + v: 0, + }, + { + a: 0, + v: 0, + }, + ], + ]); +}); + +test('Test RangeList setVerticalAlignment', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setVerticalAlignment(1); + + const cell = range.getVerticalAlignments(); + + expect(cell).toEqual([[1, 1]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getVerticalAlignments(); + expect(preCell).toEqual([[0, 0]]); +}); + +test('Test RangeList setWrapStrategy', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setWrapStrategy(WrapStrategy.CLIP); + + const cell = range.getWrapStrategies(); + + expect(cell).toEqual([[WrapStrategy.CLIP, WrapStrategy.CLIP]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getWrapStrategies(); + expect(preCell).toEqual([[WrapStrategy.UNSPECIFIED, WrapStrategy.UNSPECIFIED]]); +}); + +test('Test activate RangeList', () => { + const { workbook, worksheet } = TestInit(); + + const rangeList = worksheet.getRangeList(['A1:B1', 'A3:B4']); + + const rangeData = rangeList.activate(); + + const activeRange = worksheet.getSelection().getActiveRangeList(); + + expect(rangeData).toEqual(activeRange); +}); + +test('Test RangeList setFontColor', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setFontColor('#FFF'); + + const cell = range.getFontColors(); + + expect(cell).toEqual([['#FFF', '#FFF']]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getFontColors(); + expect(preCell).toEqual([['#000', '#000']]); +}); + +test('Test RangeList setWrap', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setWrap(1); + + const cell = range.getWraps(); + + expect(cell).toEqual([[1, 1]]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getWraps(); + expect(preCell).toEqual([[0, 0]]); +}); + +test('Test RangeList setVerticalText', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + const range = worksheet.getRange('A1:B1'); + + rangeList.setVerticalText(1); + + const cell = range.getTextRotations(); + + expect(cell).toEqual([ + [ + { + a: 90, + v: 1, + }, + { + a: 90, + v: 1, + }, + ], + ]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + const preCell = range.getTextRotations(); + expect(preCell).toEqual([ + [ + { + a: 0, + v: 0, + }, + { + a: 0, + v: 0, + }, + ], + ]); +}); + +// test('Test RangeList setFormula', () => { +// const worksheet = demo(); + +// const rangeList = worksheet.getRangeList(['A1', 'B1']); + +// const range = worksheet.getRange('A1:B1'); + +// rangeList.setFormula('=SUM(1,2)'); + +// const cell = range.getFormula(); + +// expect(cell).toEqual('=SUM(1,2)'); + +// // undo +// worksheet.getCommandManager().undo(); + +// // test previous value +// const preCell = range.getFormula(); +// expect(preCell).toEqual(''); +// }); + +// test('Test RangeList setNote', () => { +// const worksheet = demo(); + +// const rangeList = worksheet.getRangeList(['A1', 'B1']); + +// const range = worksheet.getRange('A1:B1'); + +// rangeList.setNote('1'); + +// const cell = range.getNote(); + +// expect(cell).toEqual('1'); + +// // undo +// worksheet.getCommandManager().undo(); + +// // test previous value +// const preCell = range.getNote(); +// expect(preCell).toEqual(''); +// }); + +// test('Test RangeList setNumberFormat', () => { +// const worksheet = demo(); + +// const rangeList = worksheet.getRangeList(['A1', 'B1']); + +// const range = worksheet.getRange('A1:B1'); + +// rangeList.setNumberFormat('#.##'); + +// const cell = range.getNumberFormat(); + +// expect(cell).toEqual('#.##'); + +// // undo +// worksheet.getCommandManager().undo(); + +// // test previous value +// const preCell = range.getNumberFormat(); +// expect(preCell).toEqual(''); +// }); + +test('Test RangeList setBackground', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + rangeList.setBackground('red'); + + const range = worksheet.getRange('A1:B1'); + const bg = range.getBackgrounds(); + expect(bg).toEqual([['red', 'red']]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + // defined background : #fff + const preRange = worksheet.getRange('A1:B1'); + const preBg = preRange.getBackgrounds(); + expect(preBg).toEqual([[undefined, undefined]]); +}); + +test('Test RangeList setBackgroundRGB', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + rangeList.setBackgroundRGB(0, 0, 0); + + const range = worksheet.getRange('A1:B1'); + const bg = range.getBackgrounds(); + expect(bg).toEqual([['RGB(0,0,0)', 'RGB(0,0,0)']]); + + // undo + worksheet.getCommandManager().undo(); + + // test previous value + // defined background : #fff + const preRange = worksheet.getRange('A1:B1'); + const preBg = preRange.getBackgrounds(); + expect(preBg).toEqual([[undefined, undefined]]); +}); + +test('Test RangeList trimWhitespace', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + rangeList.trimWhitespace(); + const range = worksheet.getRange('A1:B1'); + const value = range.getValues(); + expect(value).toEqual([ + [ + { s: 1, m: '111', v: '111' }, + { s: 1, m: '222', v: '222' }, + ], + ]); +}); + +test('Test RangeList clearFormat', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + rangeList.clearFormat(); + + // TODO +}); +test('Test RangeList clearContent', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + rangeList.clearContent(); + + // TODO +}); +test('Test RangeList clearNote', () => { + const worksheet = demo(); + + const rangeList = worksheet.getRangeList(['A1', 'B1']); + + rangeList.clearNote(); + + // TODO +}); diff --git a/packages/core/test/Domain/Request.test.ts b/packages/core/test/Domain/Request.test.ts new file mode 100644 index 0000000000..2b5ec54292 --- /dev/null +++ b/packages/core/test/Domain/Request.test.ts @@ -0,0 +1,36 @@ +const http = require('http'); + +export function post(action: string, send: object, callback: any) { + const data = JSON.stringify(send); + const options = { + hostname: 'http://127.0.0.1:3998', + port: '', // do not set 8080 + path: action, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length, + }, + }; + + const req = http.request(options, (res: any) => { + // A post variable is defined to temporarily store the information of the request body + let body = ''; + res.setEncoding('utf8'); + // Through the data event monitoring function of res, whenever the data of the request body is received, it is added to the post variable + res.on('data', (chunk: any) => { + body += chunk; + }); + // After the end event of res is triggered, the post is parsed into a real POST request format through JSON.parse, and then the passed callback function is called to process the data + res.on('end', () => { + const json = JSON.parse(body); + + callback(json); + }); + }); + req.on('error', (e: any) => { + console.log(`problem with request: ${e.message}`); + }); + req.write(data); + req.end(); +} diff --git a/packages/core/test/Domain/Selection.test.ts b/packages/core/test/Domain/Selection.test.ts new file mode 100644 index 0000000000..5c8256c0e7 --- /dev/null +++ b/packages/core/test/Domain/Selection.test.ts @@ -0,0 +1,5342 @@ +/** + * @jest-environment jsdom + */ +import { + BooleanNumber, + CommandManager, + Direction, + Environment, + HooksManager, + IOCAttribute, + IOCContainer, + IWorkbookConfig, + Locale, + Nullable, + ObserverManager, + PluginManager, + Range, + Selection, + ServerHttp, + ServerSocket, + UndoManager, +} from '../../src'; +import { Context } from '../../src/Basics/Context'; +import { WorkBook } from '../../src/Sheets/Domain/WorkBook'; +import { WorkSheet } from '../../src/Sheets/Domain/WorkSheet'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +export function IOCContainerStartUpReady( + workbookConfig?: Partial +): IOCContainer { + const configure = { + id: '', + extensions: [], + sheetOrder: [], + socketEnable: BooleanNumber.FALSE, + socketUrl: '', + name: '', + timeZone: '', + appVersion: '', + theme: '', + skin: '', + locale: '', + creator: '', + styles: {}, + sheets: [], + lastModifiedBy: '', + createdTime: '', + modifiedTime: '', + ...workbookConfig, + }; + const attribute = new IOCAttribute({ value: configure }); + const container = new IOCContainer(attribute); + container.addSingletonMapping('Environment', Environment); + container.addSingletonMapping('Server', ServerSocket); + container.addSingletonMapping('ServerSocket', ServerSocket); + container.addSingletonMapping('ServerHttp', ServerHttp); + container.addSingletonMapping('WorkBook', WorkBook); + container.addSingletonMapping('Locale', Locale); + container.addSingletonMapping('Context', Context); + container.addSingletonMapping('UndoManager', UndoManager); + container.addSingletonMapping('CommandManager', CommandManager); + container.addSingletonMapping('PluginManager', PluginManager); + container.addSingletonMapping('ObserverManager', ObserverManager); + container.addSingletonMapping('ObservableHooksManager', HooksManager); + container.addMapping('WorkSheet', WorkSheet); + return container; +} + +function demo() { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: 'a', + }, + 1: { + s: '1', + v: 1, + m: 'ab', + }, + 2: { + s: '1', + v: 1, + m: 'Abc', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: 'c', + }, + 1: { + s: '1', + v: 1, + m: 'cD', + }, + 2: { + s: '1', + v: 1, + m: 'cde', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + return { worksheet, workbook }; +} + +test('Test setSelection', () => { + const { workbook, worksheet } = demo(); + workbook.insertSheet(worksheet); + const commandManager = workbook.getCommandManager(); + worksheet.setCommandManager(commandManager); + worksheet.getSelection().setWorkSheet(worksheet); +}); + +test('Test getSelection', () => { + const { workbook, worksheet } = demo(); + workbook.insertSheet(worksheet); + const commandManager = workbook.getCommandManager(); + worksheet.setCommandManager(commandManager); + const rangeData = { + startRow: 1, + endRow: 3, + startColumn: 1, + endColumn: 3, + }; + worksheet.getSelection().setSelection({ selection: rangeData }); + const selection = worksheet.getSelection().getSelection(); + expect(selection).toEqual({ + startRow: 1, + endRow: 3, + startColumn: 1, + endColumn: 3, + }); +}); + +test('Test getActiveSheet', () => { + const { workbook, worksheet } = demo(); + workbook.insertSheet(worksheet); + const commandManager = workbook.getCommandManager(); + worksheet.setCommandManager(commandManager); + const sheet = worksheet.getSelection().getActiveSheet(); + if (!sheet) return; + expect(sheet.getSheetId()).toEqual('sheet'); +}); + +test('Test setSelection', () => { + const { workbook, worksheet } = demo(); + workbook.insertSheet(worksheet); + const commandManager = workbook.getCommandManager(); + worksheet.setCommandManager(commandManager); + + // set first selection + + const rangeData = { + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }; + + worksheet.getSelection().setSelection({ selection: rangeData }); + const range: Nullable = worksheet.getSelection().getActiveRange(); + + expect(range && range.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }); + + // set second selection + const nextData = { + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }; + worksheet.getSelection().setSelection({ selection: nextData }); + + const nextRange: Nullable = worksheet.getSelection().getActiveRange(); + expect(nextRange && nextRange.getRangeData()).toEqual({ + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }); + + // undo + worksheet.getCommandManager().undo(); + + const undoRange: Nullable = worksheet.getSelection().getActiveRange(); + expect(undoRange && undoRange.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }); + + // undo + worksheet.getCommandManager().redo(); + + const redoRange: Nullable = worksheet.getSelection().getActiveRange(); + expect(redoRange && redoRange.getRangeData()).toEqual({ + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }); + + const errorData = { + startRow: -1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }; + + worksheet.getSelection().setSelection({ selection: errorData }); + // test range type + const typeData = { + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }; + worksheet.getSelection().setSelection({ selection: typeData }); + + const typeRange: Nullable = worksheet.getSelection().getActiveRange(); + worksheet.getSelection().setSelection({ selection: typeRange }); + + const typeData1 = { + startRow: -1, + endRow: 2, + startColumn: 2, + endColumn: 2, + }; + worksheet.getSelection().setSelection({ selection: typeData1 }); + + const typeRange1: Nullable = worksheet.getSelection().getActiveRange(); + worksheet.getSelection().setSelection({ selection: typeRange1 }); + + // range array + const arrData = [ + { + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }, + { + startRow: 3, + endRow: 3, + startColumn: 3, + endColumn: 3, + }, + ]; + worksheet.getSelection().setSelection({ selection: arrData }); + + const arrRange: Nullable = worksheet.getSelection().getActiveRange(); + worksheet.getSelection().setSelection({ selection: arrRange }); + + const arrData1 = [ + { + startRow: -1, + endRow: 2, + startColumn: 2, + endColumn: 2, + }, + { + startRow: 3, + endRow: 3, + startColumn: 3, + endColumn: 3, + }, + ]; + worksheet.getSelection().setSelection({ selection: arrData1 }); + + const arrRange1: Nullable = worksheet.getSelection().getActiveRange(); + worksheet.getSelection().setSelection({ selection: arrRange1 }); + + // null type + worksheet.getSelection().setSelection(); + + // includes cell + const cellRangeData = { + startRow: 3, + endRow: 5, + startColumn: 3, + endColumn: 5, + }; + worksheet.getSelection().setSelection({ + selection: cellRangeData, + cell: { startRow: 3, endRow: 3, startColumn: 3, endColumn: 3 }, + }); + + worksheet.getSelection().setSelection({ + selection: cellRangeData, + cell: { startRow: 7, endRow: 7, startColumn: 3, endColumn: 3 }, + }); +}); + +test('Test setCurrentCell', () => { + const { workbook, worksheet } = demo(); + workbook.insertSheet(worksheet); + const commandManager = workbook.getCommandManager(); + worksheet.setCommandManager(commandManager); + + const prevRangeData = { + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }; + + worksheet.getSelection().setSelection({ selection: prevRangeData }); + worksheet.getSelection().getSelection(); + worksheet.getSelection().getActiveSheet(); + + const rangeData = { + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }; + + worksheet.getSelection().setCurrentCell(rangeData); + const range: Nullable = worksheet.getSelection().getCurrentCell(); + + expect(range && range.getRangeData()).toEqual({ + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }); + + // undo + worksheet.getCommandManager().undo(); + + const undoRange: Nullable = worksheet.getSelection().getCurrentCell(); + expect(undoRange && undoRange.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }); + + // redo + worksheet.getCommandManager().redo(); + + const redoRange: Nullable = worksheet.getSelection().getCurrentCell(); + expect(redoRange && redoRange.getRangeData()).toEqual({ + startRow: 2, + endRow: 2, + startColumn: 2, + endColumn: 2, + }); + // range?.startRow === -1 + const wrongData = { + startRow: -1, + endRow: 2, + startColumn: 2, + endColumn: 2, + }; + worksheet.getSelection().setCurrentCell(wrongData); + // range type + worksheet.getSelection().setCurrentCell(range); + // !(range.startRow === range.endRow && range.startColumn === range.endColumn) + const rangeData1 = { + startRow: 3, + endRow: 5, + startColumn: 3, + endColumn: 5, + }; + worksheet.getSelection().setCurrentCell(rangeData1); + // const rangeData2 = '123'; + // worksheet.getSelection().setCurrentCell(rangeData1); +}); + +test('Test cellInRange', () => { + const { workbook, worksheet } = demo(); + workbook.insertSheet(worksheet); + const commandManager = workbook.getCommandManager(); + worksheet.setCommandManager(commandManager); + const rangeList = [ + { + startRow: 2, + endRow: 4, + startColumn: 3, + endColumn: 5, + }, + ]; + + Selection.cellInRange(rangeList, { + startRow: 2, + endRow: 2, + startColumn: 3, + endColumn: 3, + }); + Selection.cellInRange(rangeList, { + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }); +}); + +test('Test getNextDataRange:top1', () => { + const { workbook, worksheet } = demo(); + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + worksheet.getConfig().cellData = configure.cellData; + workbook.insertSheet(worksheet); + const commandManager = workbook.getCommandManager(); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 1, + endRow: 3, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ selection: rangeData }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:top2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 1, + endRow: 3, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ selection: rangeData }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 1, + endRow: 2, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:top2.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 1, + endRow: 3, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ selection: rangeData }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:top2.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 2, + endRow: 3, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ selection: rangeData }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 2, + endRow: 2, + startColumn: 1, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:top2.3', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 2, + endRow: 2, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ selection: rangeData }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 2, + startColumn: 1, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:top2.4', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 1, + endRow: 5, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ selection: rangeData }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 1, + endRow: 3, + startColumn: 1, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:top2.5', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + v: 2, + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 2, + endRow: 5, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 5, + endRow: 5, + startColumn: 1, + endColumn: 3, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 5, + startColumn: 1, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:top3', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 4, + endRow: 5, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 5, + endRow: 5, + startColumn: 1, + endColumn: 1, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 2, + endRow: 5, + startColumn: 1, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:top4', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + v: '1', + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 5: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 4, + endRow: 5, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 5, + endRow: 5, + startColumn: 1, + endColumn: 3, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.TOP); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 3, + endRow: 5, + startColumn: 1, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:bottom1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + v: '1', + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + // worksheet.getConfig().rowCount = 3; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 1, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 2, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:bottom1.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().rowCount = 3; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 1, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 3, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:bottom2.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().rowCount = 3; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 1, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 3, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:bottom2.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().rowCount = 4; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 1, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 4, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:bottom3.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().rowCount = 4; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 4, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 3, + endRow: 3, + startColumn: 1, + endColumn: 1, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 2, + endRow: 4, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:bottom3.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().rowCount = 4; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 4, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 3, + endRow: 3, + startColumn: 1, + endColumn: 1, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 3, + endRow: 4, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:bottom4.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().rowCount = 4; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 4, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 3, + endRow: 3, + startColumn: 1, + endColumn: 1, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 1, + endRow: 4, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:bottom4.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 2: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + v: 3, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 3: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + 4: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().rowCount = 4; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 4, + startColumn: 1, + endColumn: 3, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 3, + endRow: 3, + startColumn: 1, + endColumn: 1, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.BOTTOM); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 3, + endRow: 4, + startColumn: 1, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:left1.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: 1, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 4, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 5, + endColumn: 5, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 3, + endColumn: 5, + }); + } +}); + +test('Test getNextDataRange:left1.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: 1, + m: '2', + }, + 3: { + s: 1, + m: '2', + }, + 4: { + s: 1, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 4, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 5, + endColumn: 5, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 5, + }); + } +}); +test('Test getNextDataRange:left2.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: 1, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 4, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 5, + endColumn: 5, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 5, + }); + } +}); +test('Test getNextDataRange:left2.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: '1', + v: 2, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 4, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 5, + endColumn: 5, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 5, + }); + } +}); +test('Test getNextDataRange:left3.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: '1', + v: 2, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: 1, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 4, + }); + } +}); + +test('Test getNextDataRange:left3.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: 1, + m: '2', + }, + 4: { + s: 1, + m: '2', + }, + 5: { + s: 1, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 2, + }); + } +}); + +test('Test getNextDataRange:left4.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 2, + m: '2', + }, + 3: { + s: 1, + m: '2', + v: 2, + }, + 4: { + s: 1, + m: '2', + }, + 5: { + s: 1, + m: '2', + v: 3, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 3, + }); + } +}); + +test('Test getNextDataRange:left4.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: 1, + m: 2, + }, + 3: { + s: 1, + m: '2', + }, + 4: { + s: 1, + m: '2', + }, + 5: { + s: 1, + m: '2', + v: 3, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 5, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.LEFT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 2, + endColumn: 2, + }); + } +}); + +test('Test getNextDataRange:right1.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: 1, + m: '2', + }, + 3: { + s: 1, + m: '2', + }, + 4: { + s: '1', + v: 2, + m: '2', + }, + 5: { + s: 1, + m: '2', + v: 3, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 2, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 4, + }); + } +}); + +test('Test getNextDataRange:right1.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: 1, + m: '2', + }, + 3: { + s: 1, + m: '2', + }, + 4: { + s: 1, + m: '2', + }, + 5: { + s: 1, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().columnCount = 5; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 2, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 5, + }); + } +}); +test('Test getNextDataRange:right2.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: 1, + m: '2', + v: 2, + }, + 3: { + s: 1, + m: '2', + v: 2, + }, + 4: { + s: 1, + m: '2', + }, + 5: { + s: 1, + m: '2', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().columnCount = 5; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 2, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 3, + }); + } +}); +test('Test getNextDataRange:right2.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: 1, + m: '2', + v: 2, + }, + 3: { + s: 1, + m: '2', + v: 2, + }, + 4: { + s: 1, + m: '2', + v: 2, + }, + 5: { + s: 1, + m: '2', + v: 2, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().columnCount = 5; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 2, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 5, + }); + } +}); +test('Test getNextDataRange:right3.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: 1, + m: '2', + v: 2, + }, + 3: { + s: 1, + m: '2', + v: 2, + }, + 4: { + s: 1, + m: '2', + v: 2, + }, + 5: { + s: 1, + m: '2', + v: 2, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().columnCount = 5; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 4, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 3, + endColumn: 3, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 1, + endColumn: 4, + }); + } +}); +test('Test getNextDataRange:right3.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: 1, + m: '1', + }, + 1: { + s: 1, + m: '2', + }, + 2: { + s: 1, + m: '2', + }, + 3: { + s: 1, + m: '2', + v: 2, + }, + 4: { + s: 1, + m: '2', + v: 2, + }, + 5: { + s: 1, + m: '2', + v: 2, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().columnCount = 5; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 4, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 3, + endColumn: 3, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 3, + endColumn: 4, + }); + } +}); + +test('Test getNextDataRange:right4.1', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: 1, + m: '1', + v: 1, + }, + 1: { + s: 1, + m: '2', + v: 2, + }, + 2: { + s: 1, + m: '2', + }, + 3: { + s: 1, + m: '2', + v: 2, + }, + 4: { + s: 1, + m: '2', + v: 2, + }, + 5: { + s: 1, + m: '2', + v: 2, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().columnCount = 5; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 4, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 3, + endColumn: 3, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 1, + endColumn: 4, + }); + } +}); +test('Test getNextDataRange:right4.2', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: 1, + m: '1', + v: 1, + }, + 1: { + s: 1, + m: '2', + v: 2, + }, + 2: { + s: 1, + m: '2', + v: 1, + }, + 3: { + s: 1, + m: '2', + v: 2, + }, + 4: { + s: 1, + m: '2', + v: 2, + }, + 5: { + s: 1, + m: '2', + v: 2, + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.getConfig().columnCount = 5; + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const rangeData = { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 4, + }; + + worksheet.getSelection().setSelection({ + selection: rangeData, + cell: { + startRow: 0, + endRow: 0, + startColumn: 3, + endColumn: 3, + }, + }); + const activeRange = worksheet.getSelection().getNextDataRange(Direction.RIGHT); + if (activeRange) { + expect(activeRange.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 3, + endColumn: 4, + }); + } +}); diff --git a/packages/core/test/Domain/Styles.test.ts b/packages/core/test/Domain/Styles.test.ts new file mode 100644 index 0000000000..0b04745c02 --- /dev/null +++ b/packages/core/test/Domain/Styles.test.ts @@ -0,0 +1,88 @@ +import { nanoid } from 'nanoid'; +import { Styles } from '../../src'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test Styles', () => { + const styles = new Styles({ + 1: { + cl: { + rgb: 'red', + }, + }, + }); + const index = styles.setValue({ + cl: { + rgb: 'blue', + }, + }); + styles.setValue({ + cl: { + rgb: 'green', + }, + }); + expect(styles.get(index)).toEqual({ + cl: { + rgb: 'blue', + }, + }); +}); + +test('Test each', () => { + const map = { + [nanoid(6)]: { cl: { rgb: 'red' } }, + }; + const styles = new Styles(map); + styles.each(([id, val]) => { + if (val && val.cl) { + expect(val.cl.rgb).toEqual('red'); + } + }); +}); + +test('Test search', () => { + const map = { + [nanoid(6)]: { cl: { rgb: 'red' } }, + }; + const styles = new Styles(map); + const find = styles.search({ cl: { rgb: 'red' } }); + expect(find).not.toBeUndefined(); +}); + +test('Test get', () => { + const map = { + [nanoid(6)]: { cl: { rgb: 'red' } }, + }; + const styles = new Styles(map); + // const find1 = styles.get({ cl: { rgb: 'red' } }); + // expect(find1).not.toBeUndefined(); + const find2 = styles.get('0'); + expect(find2).not.toBeUndefined(); +}); + +test('Test add', () => { + const map = {}; + const styles = new Styles(map); + + styles.add({ cl: { rgb: 'red' } }); + styles.add({ tr: { a: 10 } }); + + const find1 = styles.search({ cl: { rgb: 'red' } }); + expect(find1).not.toBeUndefined(); + + const find2 = styles.search({ tr: { a: 10 } }); + expect(find2).not.toBeUndefined(); +}); + +test('Test setValue', () => { + const map = {}; + const styles = new Styles(map); + styles.add({ cl: { rgb: 'red' } }); + styles.add({ tr: { a: 10 } }); + + const result1 = styles.setValue({ tr: { a: 20 } }); + expect(result1).not.toBeUndefined(); + + const result2 = styles.setValue(); + expect(result2).toBeUndefined(); +}); diff --git a/packages/core/test/Domain/WorkBook.test.ts b/packages/core/test/Domain/WorkBook.test.ts new file mode 100644 index 0000000000..8dc3063218 --- /dev/null +++ b/packages/core/test/Domain/WorkBook.test.ts @@ -0,0 +1,995 @@ +/** + * @jest-environment jsdom + */ +import { CommandManager, ServerHttp } from '../../src'; +import { Context } from '../../src/Basics/Context'; +import { Range } from '../../src/Sheets/Domain/Range'; +import { WorkBook } from '../../src/Sheets/Domain/WorkBook'; +import { WorkSheet } from '../../src/Sheets/Domain/WorkSheet'; +import { BooleanNumber } from '../../src/Enum'; +import { IOCContainerStartUpReady } from './Range.test'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test _getDefaultWorkSheet', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + expect(workbook.getSheets().length).toEqual(2); +}); + +test('Test _setServerBase', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + workbook.setServerBase(new ServerHttp()); + expect(workbook.getServer()).not.toBeUndefined(); +}); + +test('Test setDefaultActiveSheet', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + workbook.setDefaultActiveSheet(); + expect(workbook.getSheets()[0].getStatus()).toEqual(BooleanNumber.TRUE); +}); + +test('Test setContext', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + workbook.setContext(context); + expect(workbook.getContext()).toBe(context); +}); + +test('Test setCommandManager', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + const manager = container.getSingleton('CommandManager'); + workbook.setCommandManager(manager); + expect(workbook.getCommandManager()).toBe(manager); +}); + +test('Test flush', () => { + const container = IOCContainerStartUpReady({}); + const workbook = container.getSingleton('WorkBook'); + workbook.flush(); +}); + +test('Test activateSheet', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet-01', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: BooleanNumber.TRUE, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: BooleanNumber.FALSE, + }; + const secondWorksheet = new WorkSheet(context, secondConfigure); + workbook.insertSheet(secondWorksheet); + secondWorksheet.setCommandManager(commandManager); + + // activate sheet + workbook.setActiveSheet(secondWorksheet); + + // test current sheet status state + const currentStatus = secondWorksheet.getStatus(); + expect(currentStatus).toEqual(BooleanNumber.TRUE); + + // undo + secondWorksheet.getCommandManager().undo(); + + // test previous sheet status state + const preStatus = secondWorksheet.getStatus(); + expect(preStatus).toEqual(BooleanNumber.FALSE); +}); + +test('Test setActiveRange', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const oneWorksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + oneWorksheet.setCommandManager(commandManager); + workbook.insertSheet(oneWorksheet); + + const range = oneWorksheet.getRange({ + startRow: 0, + startColumn: 0, + endRow: 10, + endColumn: 10, + }); + + workbook.setActiveRange(range); + const activeRange1 = workbook.getActiveRange(); + expect(activeRange1 as Range).toEqual(null); + + workbook.setActiveSheet(oneWorksheet); + workbook.setActiveRange(range); + const activeRange2 = workbook.getActiveRange(); + expect((activeRange2 as Range).getRangeData()).toEqual(range.getRangeData()); +}); + +test('Test getIndexBySheetId', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const oneWorksheet = new WorkSheet(context, secondConfigure); + oneWorksheet.setCommandManager(commandManager); + workbook.insertSheet(oneWorksheet); + + expect(workbook.getIndexBySheetId('sheet-02')).toEqual(0); +}); + +test('Test insertSheet', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + workbook.setContext(context); + + const oneConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const oneWorksheet = new WorkSheet(context, oneConfigure); + oneWorksheet.setCommandManager(commandManager); + workbook.insertSheet(oneWorksheet); + + expect(workbook.getCommandManager()).not.toEqual(null); + expect(workbook.getContext()).not.toEqual(null); + expect(workbook.getSheets().length).toEqual(1); + expect(workbook.getActiveSpreadsheet()).toEqual(workbook); + + workbook.create('Test1'); + expect(workbook.getSheets().length).toEqual(2); + + workbook.insertSheet(1); + expect(workbook.getSheets().length).toEqual(3); + + workbook.insertSheet('sheet2'); + expect(workbook.getSheets().length).toEqual(4); + + workbook.insertSheet({ name: 'sheet3' }); + expect(workbook.getSheets().length).toEqual(5); + + workbook.insertSheet('sheet4', 1); + expect(workbook.getSheets().length).toEqual(6); + + workbook.insertSheet(1, { name: 'sheet5' }); + expect(workbook.getSheets().length).toEqual(7); + + const towConfigure = { + sheetId: 'sheet6', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const towWorksheet = new WorkSheet(context, towConfigure); + oneWorksheet.setCommandManager(commandManager); + + workbook.insertSheet(1, towWorksheet); + expect(workbook.getSheets().length).toEqual(8); +}); + +test('Test create', () => { + const container = IOCContainerStartUpReady({}); + const workbook = container.getSingleton('WorkBook'); + const worksheet = workbook.create('sheet1', 100, 100); + expect(worksheet).not.toBeUndefined(); +}); + +test('Test getActiveRangeList', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const oneWorksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + oneWorksheet.setCommandManager(commandManager); + workbook.insertSheet(oneWorksheet); + + expect(workbook.getActiveRangeList()).toBe(null); + workbook.setDefaultActiveSheet(); + expect(workbook.getActiveRangeList()).toBe( + oneWorksheet.getSelection().getActiveRangeList() + ); +}); + +test('Test getSelection', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + workbook.setContext(context); + expect(workbook.getSelection()).not.toEqual(null); +}); + +test('Test getCurrentCell', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet); + + const cell = worksheet.getRange({ + startRow: 10, + startColumn: 10, + endRow: 10, + endColumn: 10, + }); + workbook.setCurrentCell(cell); + + expect((workbook.getCurrentCell() as Range).getRangeData()).toEqual( + cell.getRangeData() + ); +}); + +test('Test setSheetOrder', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet); + workbook.setSheetOrder('sheet-02', 3); + + expect(workbook.getSheets()[3]).toEqual(worksheet); +}); + +test('Test removeSheetBySheetId', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet); + + expect(workbook.getSheets().length).toEqual(1); + workbook.removeSheetBySheetId('sheet-02'); + expect(workbook.getSheets().length).toEqual(0); +}); + +test('Test getSheetBySheetName', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + name: 'sheet-luck', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet); + + const find = workbook.getSheetBySheetName('sheet-luck'); + expect(worksheet).toEqual(find); +}); + +// test('Test newFilterCriteria', () => { +// const container = IOCContainerStartUpReady({ +// styles: { +// 1: { +// cl: { +// rgb: 'blue', +// }, +// }, +// 2: { +// cl: { +// rgb: 'red', +// }, +// }, +// }, +// }); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); +// const context = container.getSingleton('Context'); +// workbook.setContext(context); + +// const secondConfigure = { +// sheetId: 'sheet-02', +// name: 'sheet-luck', +// cellData: { +// 0: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// }, +// }, +// }, +// status: 0, +// }; +// const worksheet = container.getInstance( +// 'WorkSheet', +// context, +// secondConfigure +// ); +// worksheet.setCommandManager(commandManager); +// workbook.insertSheet(worksheet); +// workbook.setActiveSheet(worksheet); + +// const filterCriteria = workbook.newFilterCriteria(); +// expect(filterCriteria).not.toBeNull(); +// }); + +test('Test setActiveRangeList', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + workbook.setActiveRangeList([ + { startRow: 1, startColumn: 1, endRow: 2, endColumn: 2 }, + { startRow: 3, startColumn: 3, endRow: 4, endColumn: 4 }, + ]); + workbook.setDefaultActiveSheet(); + workbook.setActiveRangeList([ + { startRow: 1, startColumn: 1, endRow: 2, endColumn: 2 }, + { startRow: 3, startColumn: 3, endRow: 4, endColumn: 4 }, + ]); +}); + +test('Test getSheetBySheetId', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + name: 'sheet-luck', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet); + + const find = workbook.getSheetBySheetId('sheet-02'); + expect(worksheet).toEqual(find); +}); + +test('Test setActiveSheet', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + name: 'sheet-luck', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet); + + expect(workbook.getActiveSheet()).toEqual(worksheet); +}); + +// test('Test getFormatManager', () => { +// const container = IOCContainerStartUpReady({ +// styles: { +// 1: { +// cl: { +// rgb: 'blue', +// }, +// }, +// 2: { +// cl: { +// rgb: 'red', +// }, +// }, +// }, +// }); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); +// const context = container.getSingleton('Context'); +// workbook.setContext(context); + +// const secondConfigure = { +// sheetId: 'sheet-02', +// name: 'sheet-luck', +// cellData: { +// 0: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// }, +// }, +// }, +// status: 0, +// }; +// const worksheet = container.getInstance( +// 'WorkSheet', +// context, +// secondConfigure +// ); +// worksheet.setCommandManager(commandManager); +// workbook.insertSheet(worksheet); +// workbook.setActiveSheet(worksheet); + +// const formatManager = workbook.getFormatManager(); +// expect(formatManager).not.toBeNull(); +// }); + +// test('Test newConditionalFormatRule', () => { +// const container = IOCContainerStartUpReady({ +// styles: { +// 1: { +// cl: { +// rgb: 'blue', +// }, +// }, +// 2: { +// cl: { +// rgb: 'red', +// }, +// }, +// }, +// }); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); +// const context = container.getSingleton('Context'); +// workbook.setContext(context); + +// const secondConfigure = { +// sheetId: 'sheet-02', +// name: 'sheet-luck', +// cellData: { +// 0: { +// 0: { +// s: 1, +// v: 1, +// m: '1', +// }, +// }, +// }, +// status: 0, +// }; +// const worksheet = container.getInstance( +// 'WorkSheet', +// context, +// secondConfigure +// ); +// worksheet.setCommandManager(commandManager); +// workbook.insertSheet(worksheet); +// workbook.setActiveSheet(worksheet); + +// const conditionalFormatRule = workbook.newConditionalFormatRule(); +// expect(conditionalFormatRule).not.toBeNull(); +// }); + +test('Test getCommandManager', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + workbook.setContext(context); + + const secondConfigure = { + sheetId: 'sheet-02', + name: 'sheet-luck', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet); + + expect(workbook.getCommandManager()).not.toBeNull(); +}); + +// test('Test newFilterCriteria', () => { +// const container = IOCContainerStartUpReady({ +// sheets: [{ sheetId: 'sheet1' }, { sheetId: 'sheet2' }], +// }); +// const workbook = container.getSingleton('WorkBook'); +// workbook.setPluginMeta('test', {}); +// expect(workbook.newFilterCriteria()).not.toBeUndefined(); +// }); + +test('Test getPluginMeta/setPluginMeta', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + workbook.setPluginMeta('test', {}); + expect(workbook.getPluginMeta('test')).not.toBeUndefined(); +}); + +// test('Test newConditionalFormatRule', () => { +// const container = IOCContainerStartUpReady({ +// sheets: [{ sheetId: 'sheet1' }, { sheetId: 'sheet2' }], +// }); +// const workbook = container.getSingleton('WorkBook'); +// workbook.setPluginMeta('test', {}); +// expect(workbook.newConditionalFormatRule()).not.toBeUndefined(); +// }); + +test('Test getConfig', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + expect(workbook.getConfig()).not.toBeUndefined(); +}); + +test('Test setActiveSheet', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + const context = container.getSingleton('Context'); + + const secondConfigure = { + sheetId: 'sheet-02', + name: 'sheet-luck', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + secondConfigure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + workbook.setActiveSheet(worksheet, true); +}); + +test('Test setSheetOrder', () => { + const container = IOCContainerStartUpReady({ + sheets: { sheet1: { id: 'sheet1' }, sheet2: { id: 'sheet2' } }, + }); + const workbook = container.getSingleton('WorkBook'); + workbook.setSheetOrder('sheet1', 1); +}); + +test('Test isIRangeType', () => { + const rangeData = { startRow: 1, endRow: 1, startColumn: 1, endColumn: 1 }; + expect(WorkBook.isIRangeType(rangeData)).toEqual(true); +}); + +test('Test rangeDataToRangeStringData', () => { + const rangeData = { startRow: 1, endRow: 1, startColumn: 1, endColumn: 1 }; + expect(WorkBook.rangeDataToRangeStringData(rangeData)).toEqual('B2:B2'); +}); + +test('Test rangeDataToRangeStringData', () => { + const container = IOCContainerStartUpReady({ + sheets: { + sheet1: { + id: 'sheet1', + status: BooleanNumber.TRUE, + rowCount: 30, + columnCount: 10, + }, + }, + }); + const workbook = container.getSingleton('WorkBook'); + expect(workbook.transformRangeType('A1:B1').rangeData).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 1, + }); + expect(workbook.transformRangeType('Sheet2!A1:B1').sheetId).toEqual('Sheet2'); + expect(workbook.transformRangeType('A:A').rangeData).toEqual({ + startRow: 0, + endRow: 30, + startColumn: 0, + endColumn: 0, + }); + expect(workbook.transformRangeType('1:1').rangeData).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 10, + }); +}); diff --git a/packages/core/test/Domain/WorkSheet.test.ts b/packages/core/test/Domain/WorkSheet.test.ts new file mode 100644 index 0000000000..caa96b89a6 --- /dev/null +++ b/packages/core/test/Domain/WorkSheet.test.ts @@ -0,0 +1,3692 @@ +/** + * @jest-environment jsdom + */ +import { BooleanNumber, Range } from '../../src'; +import { Context } from '../../src/Basics/Context'; +import { WorkBook } from '../../src/Sheets/Domain/WorkBook'; +import { WorkSheet } from '../../src/Sheets/Domain/WorkSheet'; +import { IOCContainerStartUpReady } from './Range.test'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test activate', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + }, + 2: { + cl: { + rgb: 'red', + }, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet-01', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const test = new WorkSheet(context); + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const secondWorksheet = new WorkSheet(context, secondConfigure); + workbook.insertSheet(secondWorksheet); + secondWorksheet.setCommandManager(commandManager); + expect(secondWorksheet.getSheetId()).toEqual('sheet-02'); + + // activate sheet + secondWorksheet.activate(); + + // test current sheet status state + const currentStatus = secondWorksheet.getStatus(); + expect(currentStatus).toEqual(BooleanNumber.TRUE); + + // undo + secondWorksheet.getCommandManager().undo(); + + // test previous sheet status state + const preStatus = secondWorksheet.getStatus(); + expect(preStatus).toEqual(BooleanNumber.FALSE); + + // redo + secondWorksheet.getCommandManager().redo(); + + // test next sheet status state + const nextStatus = secondWorksheet.getStatus(); + expect(nextStatus).toEqual(BooleanNumber.TRUE); + secondWorksheet.activate(); +}); + +test('Test getCellMatrix', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + 3: {}, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.getCellMatrix()).not.toBeUndefined(); +}); + +test('Test clone', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.clone()).not.toBeUndefined(); +}); + +test('Test SetName', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const sheetName = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId, name: sheetName }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.getName()).toEqual('sheet'); + + worksheet.setName('luckSheet'); + expect(worksheet.getName()).toEqual('luckSheet'); +}); + +test('Test setStatus', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const sheetName = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId, name: sheetName }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + worksheet.setStatus(BooleanNumber.FALSE); + expect(worksheet.getStatus()).toEqual(BooleanNumber.FALSE); +}); + +test('Test Clear', () => { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + cl: { + rgb: 'blue', + }, + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + }, + 1: { + 0: { + s: '1', + v: 3, + m: '3', + }, + 1: { + s: '1', + v: 4, + m: '4', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + // clear sheet + worksheet.clear(); + worksheet.clear({ + formatOnly: true, + }); + + // test current sheet + const currentV = worksheet.getCellMatrix().getData(); + expect(currentV).toEqual({ + 0: { + 0: { + v: '', + m: '', + }, + 1: { + v: '', + m: '', + }, + }, + 1: { + 0: { + v: '', + m: '', + }, + 1: { + v: '', + m: '', + }, + }, + }); + + // undo + worksheet.getCommandManager().undo(); + + // test previous A1 color + const preV = worksheet.getCellMatrix()?.getData(); + // expect(preV).toEqual({ + // 0: { + // 0: { + // s: '1', + // v: 1, + // m: '1', + // }, + // 1: { + // s: '1', + // v: 2, + // m: '2', + // }, + // }, + // 1: { + // 0: { + // s: '1', + // v: 3, + // m: '3', + // }, + // 1: { + // s: '1', + // v: 4, + // m: '4', + // }, + // }, + // }); +}); + +test('Test SetTabColor', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId, tabColor: 'red' }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + // clear sheet + worksheet.setTabColor('blue'); + + // test current sheet tab color + const currentTabColor = worksheet.getConfig().tabColor; + expect(currentTabColor).toEqual('blue'); + + // undo + worksheet.getCommandManager().undo(); + + // test previous sheet tab color + const preTabColor = worksheet.getConfig().tabColor; + expect(preTabColor).toEqual('red'); + + // redo + worksheet.getCommandManager().redo(); + + // test next sheet tab color + const nextTabColor = worksheet.getConfig().tabColor; + expect(nextTabColor).toEqual('blue'); +}); + +test('Test ClearContents', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const cellData = { 0: { 0: { v: 'lucksheet', m: 'lucksheet' } } }; + const worksheet = new WorkSheet(context, { id: sheetId, cellData }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + const cellMatrix = worksheet.getCellMatrix(); + const cell = cellMatrix.getValue(0, 0); + if (cell) { + expect(cell.v).toEqual('lucksheet'); + worksheet.clearContents(); + expect(cell.v).toEqual(''); + } +}); + +test('Test ClearNotes', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const cellData = { 0: { 0: { v: 'lucksheet', m: 'lucksheet' } } }; + const worksheet = new WorkSheet(context, { id: sheetId, cellData }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + const cellMatrix = worksheet.getCellMatrix(); + const cell = cellMatrix.getValue(0, 0); + if (cell) { + expect(cell.v).toEqual('lucksheet'); + worksheet.clearNotes(); + } +}); + +test('Test ClearFormats', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const cellData = { 0: { 0: { v: 'lucksheet', m: 'lucksheet' } } }; + const worksheet = new WorkSheet(context, { id: sheetId, cellData }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + const cellMatrix = worksheet.getCellMatrix(); + const cell = cellMatrix.getValue(0, 0); + if (cell) { + expect(cell.v).toEqual('lucksheet'); + worksheet.clearFormats(); + } +}); + +test('Test DeleteRow', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const cellData = { + 0: { + 0: { v: 'lucksheet1', m: 'lucksheet1' }, + 1: { v: 'lucksheet1', m: 'lucksheet1' }, + 2: { v: 'lucksheet1', m: 'lucksheet1' }, + }, + 1: { + 0: { v: 'lucksheet1', m: 'lucksheet1' }, + 1: { v: 'lucksheet1', m: 'lucksheet1' }, + 2: { v: 'lucksheet1', m: 'lucksheet1' }, + }, + 2: { + 0: { v: 'lucksheet1', m: 'lucksheet1' }, + 1: { v: 'lucksheet1', m: 'lucksheet1' }, + 2: { v: 'lucksheet1', m: 'lucksheet1' }, + }, + 3: { + 0: { v: 'lucksheet1', m: 'lucksheet1' }, + 1: { v: 'lucksheet1', m: 'lucksheet1' }, + 2: { v: 'lucksheet1', m: 'lucksheet1' }, + }, + }; + const rowData = { + 0: { hd: 0, h: 10 }, + 1: { hd: 0, h: 10 }, + 2: { hd: 0, h: 10 }, + 3: { hd: 0, h: 10 }, + }; + const configure = { sheetId, cellData, rowData }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.deleteRow(1); + worksheet.deleteRows(1, 1); +}); + +test('Test SetCurrentCell/getActiveCell/getActiveRangeList', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.setCommandManager(commandManager); + worksheet.setCurrentCell( + new Range(worksheet, { + startRow: 0, + endRow: 1, + startColumn: 0, + endColumn: 1, + }) + ); + expect(worksheet.getActiveCell().getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 0, + }); + expect(worksheet.getActiveRangeList().getRangeList().length).toEqual(1); +}); + +// test('Test BorderStyle', () => { +// const container = IOCContainerStartUpReady(); +// const context = container.getSingleton('Context'); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); + +// const configure = { +// sheetId: 'sheet', +// cellData: { +// 0: { +// 0: { +// s: '1', +// v: 1, +// m: '1', +// }, +// 1: { +// s: '1', +// v: 2, +// m: '2', +// }, +// }, +// 1: { +// 0: { +// s: '1', +// v: 3, +// m: '3', +// }, +// 1: { +// s: '1', +// v: 4, +// m: '4', +// }, +// }, +// }, +// status: 1, +// }; +// const worksheet = new WorkSheet(context, configure); +// workbook.insertSheet(worksheet); +// worksheet.setCommandManager(commandManager); +// const borderStyles = worksheet.getBorderStyles(); + +// // console.log(JSON.stringify(workbook.getStyles())); +// borderStyles.setRStyle(0, 0, { +// cl: { rgb: 'rgb(0,0,0,)' }, +// s: BorderStyleTypes.DASH_DOT, +// }); +// // console.log(JSON.stringify(workbook.getStyles())); +// borderStyles.setLStyle(0, 1, { +// cl: { rgb: 'rgb(0,0,0,)' }, +// s: BorderStyleTypes.DASH_DOT, +// }); +// // console.log(JSON.stringify(workbook.getStyles())); +// }); + +test('Test Copy', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.copy('demo')).not.toBeUndefined(); +}); + +test('Test getRange', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const a = worksheet + .getRange({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }) + .getRangeData(); + + const b = worksheet.getRange(1, 1).getRangeData(); + const c = worksheet.getRange(1, 1, 2).getRangeData(); + const d = worksheet.getRange('A1:B1').getRangeData(); + + expect(a).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }); + expect(b).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }); + expect(c).toEqual({ + startRow: 1, + endRow: 2, + startColumn: 1, + endColumn: 1, + }); + expect(d).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 1, + }); +}); + +test('Test getRangeList', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const a = worksheet.getRangeList(['A1:B1', 'A2:B2']).getRangeList(); + // console.log(a); +}); + +test('Test getStatus/setStatus', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const sheetName = 'sheet'; + const worksheet = container.getInstance('WorkSheet', context, { + sheetId, + name: sheetName, + }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.getStatus()).toEqual(BooleanNumber.FALSE); + worksheet.setStatus(BooleanNumber.TRUE); + expect(worksheet.getStatus()).toEqual(BooleanNumber.TRUE); +}); + +test('Test hideSheet/showSheet', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + // hide sheet + worksheet.hideSheet(); + + // test current sheet hidden state + const currentHidden = worksheet.getConfig().hidden; + expect(currentHidden).toEqual(1); + + worksheet.showSheet(); + const currentHidden1 = worksheet.getConfig().hidden; + expect(currentHidden1).toEqual(0); + const sheetId1 = 'sheet1'; + const worksheet1 = new WorkSheet(context, { id: sheetId1, hidden: 1 }); + worksheet1.setCommandManager(commandManager); + workbook.insertSheet(worksheet1); + worksheet1.hideSheet(); + const sheetId2 = 'sheet2'; + const worksheet2 = new WorkSheet(context, { id: sheetId2, hidden: 0 }); + worksheet2.setCommandManager(commandManager); + workbook.insertSheet(worksheet2); + worksheet2.showSheet(); +}); + +test('Test hideRow/hideRows', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + worksheet.hideRows(2, 1); + worksheet.hideRow( + new Range(worksheet, { + startRow: 0, + startColumn: 0, + endRow: 0, + endColumn: 0, + }) + ); +}); + +test('Test showColumns/hideColumns', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + '2': { + w: 200, + hd: 1, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + worksheet.hideColumn( + new Range(worksheet, { + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 0, + }) + ); + expect(worksheet.getConfig().columnData['0'].hd).toEqual(true); + worksheet.hideColumns(0, 1); + expect(worksheet.getConfig().columnData['0'].hd).toEqual(true); + worksheet.showColumns(0, 1); + expect(worksheet.getConfig().columnData['0'].hd).toEqual(false); +}); + +test('Test showRows/hideRows', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + worksheet.hideRows(1, 1); + worksheet.showRows(1, 1); +}); + +test('Test unhideRow/unhideColumn', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + worksheet.hideRows(1, 1); + const range = worksheet.getRange('A1:A2'); + worksheet.unhideRow(range); + worksheet.hideColumns(1, 1); + const range1 = worksheet.getRange('A1:B1'); + worksheet.unhideColumn(range1); +}); + +test('Test hasHiddenGridlines/setHiddenGridlines', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + // hide sheet + worksheet.setHiddenGridlines(true); + expect(worksheet.hasHiddenGridlines()).toEqual(true); + worksheet.getCommandManager().undo(); + expect(worksheet.hasHiddenGridlines()).toEqual(false); + worksheet.getCommandManager().redo(); + expect(worksheet.hasHiddenGridlines()).toEqual(true); +}); + +test('Test getTabColor', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + // hide sheet + expect(worksheet.getTabColor()).toEqual('red'); +}); + +test('Test setColumnWidth/getColumnWidth', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.setColumnWidth(0, 1, 50); + expect(worksheet.getColumnWidth(0)).toEqual(50); + expect(worksheet.getColumnManager().getColumnWidth(0, 2)).toEqual(250); + expect(worksheet.getColumnManager().getSize()).toEqual(2); + expect(worksheet.getColumnManager().getColumnData().getSizeOf()).toEqual(2); + worksheet.getCommandManager().undo(); + expect(worksheet.getColumnWidth(0)).toEqual(100); + worksheet.getCommandManager().redo(); + expect(worksheet.getColumnWidth(0)).toEqual(50); + worksheet.setColumnWidth(0, 30); + expect(worksheet.getColumnWidth(0)).toEqual(30); +}); + +test('Test setRowHeights/getRowHeight', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.setRowHeights(0, 2, 30); + expect(worksheet.getRowHeight(0)).toEqual(30); + expect(worksheet.getRowHeight(1)).toEqual(30); + expect(worksheet.getRowManager().getRowHeight(0, 2)).toEqual(60); + worksheet.getCommandManager().undo(); + expect(worksheet.getRowHeight(0)).toEqual(50); + expect(worksheet.getRowHeight(1)).toEqual(60); + worksheet.getCommandManager().redo(); + expect(worksheet.getRowHeight(0)).toEqual(30); + expect(worksheet.getRowHeight(1)).toEqual(30); + worksheet.setRowHeight(0, 10); + expect(worksheet.getRowHeight(0)).toEqual(10); +}); + +test('Test setActiveSelection', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + worksheet.setActiveSelection('A1:B1'); +}); + +test('Test getFrozenRows/getFrozenColumns/getMaxColumns/getMaxRows/getType/getRowCount/getColumnCount', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.getFrozenColumns()).toEqual(-1); + expect(worksheet.getFrozenRows()).toEqual(-1); + expect(worksheet.getMaxColumns()).toEqual(100); + expect(worksheet.getMaxRows()).toEqual(1000); + expect(worksheet.getType()).toEqual(0); + expect(worksheet.getRowCount()).toEqual(1000); + expect(worksheet.getColumnCount()).toEqual(100); +}); + +// test('Test getBorderStyles/setBorderStyles', () => { +// const container = IOCContainerStartUpReady(); +// const context = container.getSingleton('Context'); +// const workbook = container.getSingleton('WorkBook'); +// const commandManager = workbook.getCommandManager(); + +// const configure = { +// sheetId: 'sheet', +// cellData: { +// 0: { +// 0: { +// s: '1', +// v: 1, +// m: '1', +// }, +// 1: { +// s: '1', +// v: 2, +// m: '2', +// }, +// 2: { +// s: '1', +// v: 3, +// m: '3', +// }, +// }, +// 1: { +// 0: { +// s: '1', +// v: 4, +// m: '4', +// }, +// 1: { +// s: '1', +// v: 5, +// m: '5', +// }, +// 2: { +// s: '1', +// v: 6, +// m: '6', +// }, +// }, +// 2: { +// 0: { +// s: '1', +// v: 7, +// m: '7', +// }, +// 1: { +// s: '1', +// v: 8, +// m: '8', +// }, +// 2: { +// s: '1', +// v: 9, +// m: '9', +// }, +// }, +// }, +// rowData: { +// '0': { +// h: 50, +// hd: 0, +// }, +// '1': { +// h: 60, +// hd: 1, +// }, +// }, +// columnData: { +// '0': { +// w: 100, +// hd: 0, +// }, +// '1': { +// w: 200, +// hd: 1, +// }, +// }, +// status: 1, +// tabColor: 'red', +// }; +// const worksheet = new WorkSheet(context, configure); +// worksheet.setCommandManager(commandManager); +// workbook.insertSheet(worksheet); +// worksheet.setBorderStyle( +// { startRow: 0, endRow: 1, startColumn: 0, endColumn: 1 }, +// { s: 0, cl: { rgb: 'red' } }, +// [1] +// ); +// worksheet.getBorderStyles(); +// }); + +test('Test getIndex/getParent', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.getIndex()).toEqual(1); + worksheet.getParent(); +}); + +test('Test isSheetHidden', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + expect(worksheet.isSheetHidden()).toEqual(0); +}); + +test('Test setFrozenColumns', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + worksheet.setFrozenColumns(3); + expect(worksheet.getFrozenColumns()).toEqual(3); + worksheet.getCommandManager().undo(); + expect(worksheet.getFrozenColumns()).toEqual(-1); + worksheet.getCommandManager().redo(); + expect(worksheet.getFrozenColumns()).toEqual(3); +}); + +test('Test setFrozenRows', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + worksheet.setFrozenRows(3); + expect(worksheet.getFrozenRows()).toEqual(3); + worksheet.getCommandManager().undo(); + expect(worksheet.getFrozenRows()).toEqual(-1); + worksheet.getCommandManager().redo(); + expect(worksheet.getFrozenRows()).toEqual(3); +}); + +test('Test isRightToLeft/setRightToLeft', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + worksheet.setRightToLeft(BooleanNumber.TRUE); + expect(worksheet.isRightToLeft()).toEqual(BooleanNumber.TRUE); + worksheet.getCommandManager().undo(); + expect(worksheet.isRightToLeft()).toEqual(BooleanNumber.FALSE); + worksheet.getCommandManager().redo(); + expect(worksheet.isRightToLeft()).toEqual(BooleanNumber.TRUE); +}); + +test('Test getPluginMeta/setPluginMeta', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const sheetId = 'sheet'; + const worksheet = new WorkSheet(context, { id: sheetId }); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + worksheet.setPluginMeta('name', 'univer'); + expect(worksheet.getPluginMeta('name')).toEqual('univer'); +}); + +test('Test getLastRow/getLastColumn', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + worksheet.insertRows(3, 100); + expect(worksheet.getLastRow()).toEqual(103); +}); + +test('Test copyTo', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet-01', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const secondConfigure = { + sheetId: 'sheet-02', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 0, + }; + const secondWorksheet = new WorkSheet(context, secondConfigure); + workbook.insertSheet(secondWorksheet); + secondWorksheet.setCommandManager(commandManager); + worksheet.copyTo(5); + worksheet.copyTo(1); + expect(secondWorksheet.getStatus()).toEqual(1); +}); + +test('Test getSheetValues', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + const values = worksheet.getSheetValues(1, 1, 2, 2); +}); + +test('Test getSheetValues', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + + const values = worksheet.getDataRange(); +}); + +test('Test InsertRows', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.insertRows(1); + worksheet.insertRows(1, 1); + worksheet.getCommandManager().undo(); + worksheet.getCommandManager().redo(); +}); + +test('Test insertrowsafter/insertrowsbefore', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.insertRowAfter(1); + worksheet.insertRowAfter(1, 1); +}); + +test('Test insertrowsbefore', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.insertRowBefore(1); + worksheet.insertRowBefore(1, 1); + worksheet.insertRowBefore(0); +}); + +test('Test moveRows', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + const range = worksheet.getRange('A1:B1'); + worksheet.moveRows(range, 2); +}); + +test('Test insertColumns', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.insertColumns(1); + expect(worksheet.getLastColumn()).toEqual(3); + worksheet.getCommandManager().undo(); + worksheet.getCommandManager().redo(); + worksheet.insertColumns(1, 1); + expect(worksheet.getLastColumn()).toEqual(4); +}); +test('Test insertColumnsBefore', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.insertColumnBefore(0); + expect(worksheet.getLastColumn()).toEqual(3); + worksheet.insertColumnBefore(1, 1); + expect(worksheet.getLastColumn()).toEqual(4); +}); + +test('Test insertColumnsAfter', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.insertColumnAfter(0); + expect(worksheet.getLastColumn()).toEqual(3); + worksheet.insertColumnAfter(1, 1); + expect(worksheet.getLastColumn()).toEqual(4); +}); + +test('Test moveColumns', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + const range = worksheet.getRange('A1'); + worksheet.moveColumns(range, 2); +}); + +test('Test deleteColumns/deleteColumn', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + workbook.insertSheet(worksheet); + worksheet.deleteColumn(1); + const range = worksheet.getRange('A1'); + worksheet.deleteColumns(1, 1); + const data = worksheet.getConfig().cellData; + worksheet.getCommandManager().undo(); + worksheet.getCommandManager().redo(); +}); + +test('Test getContext', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + + expect(worksheet.getContext()).toEqual(context); +}); + +test('Test getMerges', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + + expect(worksheet.getMerges()).not.toBeUndefined(); +}); + +test('Test getSheetData', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + + expect(worksheet.getSheetData()).not.toBeUndefined(); +}); + +test('Test getProtection', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = new WorkSheet(context, configure); + worksheet.setCommandManager(commandManager); + + expect(worksheet.getProtection()).not.toBeUndefined(); +}); + +test('Test getSelection', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + worksheet.setCommandManager(commandManager); + + expect(worksheet.getSelection()).not.toBeUndefined(); +}); + +test('Test setActiveRange', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + const range = worksheet.getRange({ + startRow: 0, + endRow: 1, + startColumn: 0, + endColumn: 1, + }); + worksheet.setActiveRange({ + selection: range, + }); + expect(worksheet.getActiveRange().getRangeData()).toEqual({ + startRow: 0, + endRow: 1, + startColumn: 0, + endColumn: 1, + }); +}); + +test('Test setActiveRangeList', () => { + const container = IOCContainerStartUpReady(); + const workbook = container.getSingleton('WorkBook'); + const context = container.getSingleton('Context'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + 1: { + s: '1', + v: 2, + m: '2', + }, + 2: { + s: '1', + v: 3, + m: '3', + }, + }, + 1: { + 0: { + s: '1', + v: 4, + m: '4', + }, + 1: { + s: '1', + v: 5, + m: '5', + }, + 2: { + s: '1', + v: 6, + m: '6', + }, + }, + 2: { + 0: { + s: '1', + v: 7, + m: '7', + }, + 1: { + s: '1', + v: 8, + m: '8', + }, + 2: { + s: '1', + v: 9, + m: '9', + }, + }, + }, + rowData: { + '0': { + h: 50, + hd: 0, + }, + '1': { + h: 60, + hd: 1, + }, + }, + columnData: { + '0': { + w: 100, + hd: 0, + }, + '1': { + w: 200, + hd: 1, + }, + }, + status: 1, + tabColor: 'red', + }; + const worksheet = container.getInstance( + 'WorkSheet', + context, + configure + ); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + worksheet.setActiveRangeList({ + selection: [ + { + startRow: 2, + endRow: 3, + startColumn: 2, + endColumn: 3, + }, + { + startRow: 0, + endRow: 1, + startColumn: 0, + endColumn: 1, + }, + ], + }); + + expect(worksheet.getActiveRangeList().getRangeList().length).toEqual(2); +}); diff --git a/packages/core/test/IOC/IOCContainer.test.ts b/packages/core/test/IOC/IOCContainer.test.ts new file mode 100644 index 0000000000..f48a0246b4 --- /dev/null +++ b/packages/core/test/IOC/IOCContainer.test.ts @@ -0,0 +1,164 @@ +import { Environment } from '../../src/Basics/Environment'; +import { + IOCAttribute, + IOCContainer, + Inject, + PreDestroy, + PostConstruct, +} from '../../src/IOC/IOCContainer'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test inject', () => { + const container = new IOCContainer(); + container.addSingletonMapping('Environment', Environment); + class Test1 { + @Inject('Environment') + environment1: Environment; + + @Inject({ + mapping: 'Environment', + }) + environment2: Environment; + } + const text1 = new Test1(); + container.inject(text1); + expect(text1.environment1).not.toBeUndefined(); + expect(text1.environment2).not.toBeUndefined(); +}); + +test('Test getAttribute', () => { + const attribute = new IOCAttribute({}); + const container = new IOCContainer(attribute); + expect(container.getAttribute()).toEqual(attribute); +}); + +test('Test getInstance', () => { + const container = new IOCContainer(); + container.addMapping('Environment', Environment); + expect(container.getInstance('Environment')).not.toBeUndefined(); +}); + +test('Test getSingleton', () => { + const container = new IOCContainer(); + container.addSingletonMapping('Environment', Environment); + const singleton1 = container.getSingleton('Environment'); + const singleton2 = container.getSingleton('Environment'); + expect(singleton1).toEqual(singleton2); +}); + +test('Test getBaseClassByInstance', () => { + const container = new IOCContainer(); + container.addSingletonMapping('Environment', Environment); + const singleton1 = container.getSingleton('Environment'); + expect(container.getBaseClassByInstance(singleton1)).toEqual('Environment'); +}); + +test('Test removeMapping', () => { + const container = new IOCContainer(); + class Mapping { + @Inject('Environment') + environment: Environment; + } + container.addSingletonMapping('Environment', Environment); + container.addSingletonMapping('Mapping', Mapping); + const singleton1 = container.getSingleton('Mapping'); + expect(singleton1).not.toBeUndefined(); + container.removeMapping('Mapping'); + const singleton2 = container.getSingleton('Mapping'); + expect(singleton2).toBeUndefined(); +}); + +test('Test addMapping', () => { + const container = new IOCContainer(); + container.addMapping('Environment', Environment); + const singleton1 = container.getInstance('Environment'); + const singleton2 = container.getInstance('Environment'); + expect(singleton1).not.toBeUndefined(); + expect(singleton1).not.toBe(singleton2); +}); + +test('Test addSingletonMapping', () => { + const container = new IOCContainer(); + container.addSingletonMapping('Environment', Environment); + const singleton1 = container.getInstance('Environment'); + const singleton2 = container.getInstance('Environment'); + expect(singleton1).not.toBeUndefined(); + expect(singleton1).not.toBe(singleton2); +}); + +test('Test addClass', () => { + const container = new IOCContainer(); + container.addClass(Environment); + const singleton1 = container.getInstance('Environment'); + const singleton2 = container.getInstance('Environment'); + expect(singleton1).not.toBeUndefined(); + expect(singleton1).not.toBe(singleton2); +}); + +test('Test removeClass', () => { + const container = new IOCContainer(); + container.addClass(Environment); + const singleton1 = container.getInstance('Environment'); + expect(singleton1).not.toBeUndefined(); + container.removeClass(Environment); + const singleton2 = container.getInstance('Environment'); + expect(singleton2).toBeUndefined(); +}); + +test('Test addSingletonClass', () => { + const container = new IOCContainer(); + container.addSingletonClass(Environment); + const singleton1 = container.getInstance('Environment'); + const singleton2 = container.getInstance('Environment'); + expect(singleton1).not.toBeUndefined(); + expect(singleton1).not.toBe(singleton2); +}); + +test('Test Loop', () => { + const container = new IOCContainer(); + class Test2 { + @Inject('Test1') + test: Object; + } + class Test1 { + @Inject('Test2') + test: Object; + } + container.addClass(Test1); + container.addClass(Test2); + try { + container.getInstance('Test1'); + } catch (e) { + expect(e).not.toBeUndefined(); + } +}); + +test('Test PostConstruct', () => { + const drink = jest.fn(); + const container = new IOCContainer(); + class Test1 { + @PostConstruct() + init() { + drink(); + } + } + container.addClass(Test1); + container.getInstance('Test1'); + expect(drink).toHaveBeenCalled(); +}); + +test('Test PreDestroy', () => { + const drink = jest.fn(); + const container = new IOCContainer(); + class Test1 { + @PreDestroy() + des() { + drink(); + } + } + container.addClass(Test1); + container.getSingleton('Test1'); + container.removeClass(Test1); + expect(drink).toHaveBeenCalled(); +}); diff --git a/packages/core/test/Module/Freeze/Action/SetFrozenCancelAction.test.ts b/packages/core/test/Module/Freeze/Action/SetFrozenCancelAction.test.ts new file mode 100644 index 0000000000..2522b3e7fc --- /dev/null +++ b/packages/core/test/Module/Freeze/Action/SetFrozenCancelAction.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Frozen', () => {}); diff --git a/packages/core/test/Module/Freeze/Action/SetFrozenColumnsAction.test.ts b/packages/core/test/Module/Freeze/Action/SetFrozenColumnsAction.test.ts new file mode 100644 index 0000000000..2522b3e7fc --- /dev/null +++ b/packages/core/test/Module/Freeze/Action/SetFrozenColumnsAction.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Frozen', () => {}); diff --git a/packages/core/test/Module/Freeze/Action/SetFrozenRowsAction.test.ts b/packages/core/test/Module/Freeze/Action/SetFrozenRowsAction.test.ts new file mode 100644 index 0000000000..2522b3e7fc --- /dev/null +++ b/packages/core/test/Module/Freeze/Action/SetFrozenRowsAction.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Frozen', () => {}); diff --git a/packages/core/test/Module/Freeze/Service/SetFrozenCancelService.test.ts b/packages/core/test/Module/Freeze/Service/SetFrozenCancelService.test.ts new file mode 100644 index 0000000000..2522b3e7fc --- /dev/null +++ b/packages/core/test/Module/Freeze/Service/SetFrozenCancelService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Frozen', () => {}); diff --git a/packages/core/test/Module/Freeze/Service/SetFrozenColumnsService.test.ts b/packages/core/test/Module/Freeze/Service/SetFrozenColumnsService.test.ts new file mode 100644 index 0000000000..2522b3e7fc --- /dev/null +++ b/packages/core/test/Module/Freeze/Service/SetFrozenColumnsService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Frozen', () => {}); diff --git a/packages/core/test/Module/Freeze/Service/SetFrozenRowsService.test.ts b/packages/core/test/Module/Freeze/Service/SetFrozenRowsService.test.ts new file mode 100644 index 0000000000..2522b3e7fc --- /dev/null +++ b/packages/core/test/Module/Freeze/Service/SetFrozenRowsService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Frozen', () => {}); diff --git a/packages/core/test/Module/NamedRange/Action/AddNamedRangeAction.test.ts b/packages/core/test/Module/NamedRange/Action/AddNamedRangeAction.test.ts new file mode 100644 index 0000000000..87ec9efef7 --- /dev/null +++ b/packages/core/test/Module/NamedRange/Action/AddNamedRangeAction.test.ts @@ -0,0 +1,46 @@ +/** + * @jest-environment jsdom + */ +import { ACTION_NAMES } from '../../../../src'; +import { Context } from '../../../../src/Basics'; +import { ActionObservers } from '../../../../src/Command'; +import { WorkBook, WorkSheet } from '../../../../src/Sheets/Domain'; +import { AddNamedRangeAction } from '../../../../src/Module/NamedRange/Action'; +import { INamedRange } from '../../../../src/Module/NamedRange/INamedRange'; +import { IOCContainerStartUpReady } from '../../../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Add NamedRange Action Test', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const rangeData = { + startRow: 0, + endRow: 10, + startColumn: 0, + endColumn: 10, + }; + const namedRange: INamedRange = { + name: 'named range 1', + namedRangeId: 'named-range-1', + range: { + sheetId: 'sheet1', + rangeData, + }, + }; + new AddNamedRangeAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.ADD_NAMED_RANGE_ACTION, + namedRange, + }, + workbook, + new ActionObservers() + ); +}); diff --git a/packages/core/test/Module/NamedRange/Action/DeleteNamedRangeAction.test.ts b/packages/core/test/Module/NamedRange/Action/DeleteNamedRangeAction.test.ts new file mode 100644 index 0000000000..b685a98184 --- /dev/null +++ b/packages/core/test/Module/NamedRange/Action/DeleteNamedRangeAction.test.ts @@ -0,0 +1,62 @@ +/** + * @jest-environment jsdom + */ +import { ACTION_NAMES } from '../../../../src'; +import { Context } from '../../../../src/Basics'; +import { ActionObservers } from '../../../../src/Command'; +import { WorkBook, WorkSheet } from '../../../../src/Sheets/Domain'; +import { + AddNamedRangeAction, + DeleteNamedRangeAction, +} from '../../../../src/Module/NamedRange/Action'; +import { INamedRange } from '../../../../src/Module/NamedRange/INamedRange'; +import { IOCContainerStartUpReady } from '../../../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Delete NamedRange Action Test', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const rangeData = { + startRow: 0, + endRow: 10, + startColumn: 0, + endColumn: 10, + }; + const namedRange: INamedRange = { + name: 'named range 1', + namedRangeId: 'named-range-1', + range: { + sheetId: 'sheet1', + rangeData, + }, + }; + + // add + new AddNamedRangeAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.ADD_NAMED_RANGE_ACTION, + namedRange, + }, + workbook, + new ActionObservers() + ); + + // remove + new DeleteNamedRangeAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.DELETE_NAMED_RANGE_ACTION, + namedRangeId: 'named-range-1', + }, + workbook, + new ActionObservers() + ); +}); diff --git a/packages/core/test/Module/NamedRange/Action/SetNamedRangeAction.test.ts b/packages/core/test/Module/NamedRange/Action/SetNamedRangeAction.test.ts new file mode 100644 index 0000000000..508f9ed64a --- /dev/null +++ b/packages/core/test/Module/NamedRange/Action/SetNamedRangeAction.test.ts @@ -0,0 +1,68 @@ +/** + * @jest-environment jsdom + */ +import { ACTION_NAMES } from '../../../../src'; +import { Context } from '../../../../src/Basics'; +import { ActionObservers } from '../../../../src/Command'; +import { WorkBook, WorkSheet } from '../../../../src/Sheets/Domain'; +import { + AddNamedRangeAction, + SetNamedRangeAction, +} from '../../../../src/Module/NamedRange/Action'; +import { INamedRange } from '../../../../src/Module/NamedRange/INamedRange'; +import { IOCContainerStartUpReady } from '../../../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Set NamedRange Action Test', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const rangeData = { + startRow: 0, + endRow: 10, + startColumn: 0, + endColumn: 10, + }; + const namedRange: INamedRange = { + name: 'named range 1', + namedRangeId: 'named-range-1', + range: { + sheetId: 'sheet1', + rangeData, + }, + }; + new AddNamedRangeAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.ADD_NAMED_RANGE_ACTION, + namedRange, + }, + workbook, + new ActionObservers() + ); + + const newNamedRange: INamedRange = { + name: 'new named range 1', + namedRangeId: 'named-range-1', + range: { + sheetId: 'sheet1', + rangeData, + }, + }; + + new SetNamedRangeAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_NAMED_RANGE_ACTION, + namedRange: newNamedRange, + }, + workbook, + new ActionObservers() + ); +}); diff --git a/packages/core/test/Module/NamedRange/NamedRange.test.ts b/packages/core/test/Module/NamedRange/NamedRange.test.ts new file mode 100644 index 0000000000..6b9cac7647 --- /dev/null +++ b/packages/core/test/Module/NamedRange/NamedRange.test.ts @@ -0,0 +1,117 @@ +/** + * @jest-environment jsdom + */ +import { Context, WorkBook, WorkSheet } from '../../../src'; +import { + DEFAULT_NAMED_RANGE, + INamedRange, +} from '../../../src/Module/NamedRange/INamedRange'; +import { NamedRange } from '../../../src/Module/NamedRange/NamedRange'; +import { IOCContainerStartUpReady } from '../../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +export const namedRange: INamedRange = DEFAULT_NAMED_RANGE; + +export const newNamedRange = { ...DEFAULT_NAMED_RANGE, name: 'namedRange02' }; + +test('Test Named Range', () => { + // const { workbook, worksheet } = TestInitSheetInstance(); + + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: '1', + v: 1, + m: '1', + }, + }, + }, + status: 1, + }; + const worksheet = new WorkSheet(context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + const name = DEFAULT_NAMED_RANGE.name; + const range = worksheet.getRange(DEFAULT_NAMED_RANGE.range.rangeData); + const namedRangeIns = new NamedRange(worksheet); + + // add + namedRangeIns.addNamedRange(namedRange); + + expect(workbook.getConfig().namedRanges).toEqual([namedRange]); + + worksheet.getCommandManager().undo(); + + expect(workbook.getConfig().namedRanges).toEqual([]); + + worksheet.getCommandManager().redo(); + + expect(workbook.getConfig().namedRanges).toEqual([namedRange]); + + // set + namedRangeIns.setNamedRange(newNamedRange); + + expect(workbook.getConfig().namedRanges).toEqual([newNamedRange]); + + worksheet.getCommandManager().undo(); + + expect(workbook.getConfig().namedRanges).toEqual([namedRange]); + + worksheet.getCommandManager().redo(); + + expect(workbook.getConfig().namedRanges).toEqual([newNamedRange]); + + // remove + namedRangeIns.remove(namedRange.namedRangeId); + + expect(workbook.getConfig().namedRanges).toEqual([]); + + worksheet.getCommandManager().undo(); + + expect(workbook.getConfig().namedRanges).toEqual([newNamedRange]); + + worksheet.getCommandManager().redo(); + + expect(workbook.getConfig().namedRanges).toEqual([]); + + // // getName + // expect(namedRange.getName()).toEqual(name); + + // // setName + // namedRange.setName('namedRange02'); + + // expect(namedRange.getName()).toEqual('namedRange02'); + + // // getRange + // expect(namedRange.getRange().getRangeData()).toEqual( + // DEFAULT_NAMED_RANGE.range.rangeData + // ); + + // // setRange + // const newRange = worksheet.getRange({ + // startRow: 1, + // endRow: 1, + // startColumn: 1, + // endColumn: 1, + // }); + // namedRange.setRange(newRange); + + // expect(namedRange.getRange().getRangeData()).toEqual({ + // startRow: 1, + // endRow: 1, + // startColumn: 1, + // endColumn: 1, + // }); + + // // getNamedRangeId + // expect(namedRange.getNamedRangeId()).toEqual(DEFAULT_NAMED_RANGE.namedRangeId); +}); diff --git a/packages/core/test/Module/Protection/Action/AddAllowedAction.test.ts b/packages/core/test/Module/Protection/Action/AddAllowedAction.test.ts new file mode 100644 index 0000000000..8b04b35851 --- /dev/null +++ b/packages/core/test/Module/Protection/Action/AddAllowedAction.test.ts @@ -0,0 +1,37 @@ +/** + * @jest-environment jsdom + */ +import { IOCContainerStartUpReady } from '../../../ContainerStartUp'; +import { AddAllowedAction } from '../../../../src/Module/Protection/Action'; +import { Allowed } from '../../../../src/Module/Protection'; +import { Context } from '../../../../src/Basics'; +import { WorkBook, WorkSheet, Range } from '../../../../src/Sheets/Domain'; +import { ActionObservers } from '../../../../src/Command'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Add Allowed Action Test', () => { + const container = IOCContainerStartUpReady(); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + + const sheetId = 'sheet1'; + const worksheet = new WorkSheet(context, { id: sheetId }); + workbook.insertSheet(worksheet); + + const allowedRange = new Range(worksheet, { + startRow: 0, + startColumn: 0, + endRow: 0, + endColumn: 0, + }); + const addAllowedAction = new AddAllowedAction( + { + sheetId: worksheet.getSheetId(), + actionName: 'addAllowedAction', + allowed: new Allowed('123456', allowedRange), + }, + workbook, + new ActionObservers() + ); +}); diff --git a/packages/core/test/Module/Protection/Action/AddUnlockAction.test.ts b/packages/core/test/Module/Protection/Action/AddUnlockAction.test.ts new file mode 100644 index 0000000000..32eba82b34 --- /dev/null +++ b/packages/core/test/Module/Protection/Action/AddUnlockAction.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Protection', () => {}); diff --git a/packages/core/test/Module/Protection/Action/SetProtectionAction.test.ts b/packages/core/test/Module/Protection/Action/SetProtectionAction.test.ts new file mode 100644 index 0000000000..32eba82b34 --- /dev/null +++ b/packages/core/test/Module/Protection/Action/SetProtectionAction.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Protection', () => {}); diff --git a/packages/core/test/Module/Protection/Service/AddAllowedService.test.ts b/packages/core/test/Module/Protection/Service/AddAllowedService.test.ts new file mode 100644 index 0000000000..32eba82b34 --- /dev/null +++ b/packages/core/test/Module/Protection/Service/AddAllowedService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Protection', () => {}); diff --git a/packages/core/test/Module/Protection/Service/AddUnlockService.test.ts b/packages/core/test/Module/Protection/Service/AddUnlockService.test.ts new file mode 100644 index 0000000000..32eba82b34 --- /dev/null +++ b/packages/core/test/Module/Protection/Service/AddUnlockService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Protection', () => {}); diff --git a/packages/core/test/Module/Protection/Service/RemoveAllowedService.test.ts b/packages/core/test/Module/Protection/Service/RemoveAllowedService.test.ts new file mode 100644 index 0000000000..32eba82b34 --- /dev/null +++ b/packages/core/test/Module/Protection/Service/RemoveAllowedService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Protection', () => {}); diff --git a/packages/core/test/Module/Protection/Service/RemoveUnlockService.test.ts b/packages/core/test/Module/Protection/Service/RemoveUnlockService.test.ts new file mode 100644 index 0000000000..32eba82b34 --- /dev/null +++ b/packages/core/test/Module/Protection/Service/RemoveUnlockService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Protection', () => {}); diff --git a/packages/core/test/Module/Protection/Service/SetProtectionService.test.ts b/packages/core/test/Module/Protection/Service/SetProtectionService.test.ts new file mode 100644 index 0000000000..32eba82b34 --- /dev/null +++ b/packages/core/test/Module/Protection/Service/SetProtectionService.test.ts @@ -0,0 +1,5 @@ +/** + * @jest-environment jsdom + */ + +test('Protection', () => {}); diff --git a/packages/core/test/Observer/Observer.test.ts b/packages/core/test/Observer/Observer.test.ts new file mode 100644 index 0000000000..1c0a050405 --- /dev/null +++ b/packages/core/test/Observer/Observer.test.ts @@ -0,0 +1,13 @@ +import { EventState, Observable, Observer } from '../../src'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Observable add', () => { + const observable = new Observable((observer: Observer) => {}); + observable.add((eventData, eventState: EventState) => {}, false, false); +}); + +test('Observable addOnce', () => { + const observable = new Observable((observer: Observer) => {}); + observable.addOnce((eventData, eventState: EventState) => {}); +}); diff --git a/packages/core/test/Plugin/Plugin.test.st.ts b/packages/core/test/Plugin/Plugin.test.st.ts new file mode 100644 index 0000000000..bb9ef32e2e --- /dev/null +++ b/packages/core/test/Plugin/Plugin.test.st.ts @@ -0,0 +1,97 @@ +/** + * @jest-environment jsdom + */ +import { Context } from '../../src/Basics'; +import { Plugin, PluginManager } from '../../src/Plugin'; +import { IOCContainer } from '../../src/IOC'; +import { IOCContainerStartUpReady } from '../ContainerStartUp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test getContext', () => { + const container = IOCContainerStartUpReady(); + const manager: PluginManager = container.getSingleton('PluginManager'); + class Test extends Plugin { + constructor() { + super('test'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const test = new Test(); + manager.install(test); + expect(test.getContext()).not.toBeUndefined(); + manager.uninstall('test'); +}); + +test('Test getName', () => { + const container = IOCContainerStartUpReady(); + class Test extends Plugin { + constructor() { + super('test'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const test = new Test(); + container.inject(test); + expect(test.getPluginName()).toEqual('test'); +}); + +test('Test getPluginByName', () => { + const container = IOCContainerStartUpReady(); + class Test extends Plugin { + constructor() { + super('test'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const test = new Test(); + container.inject(test); + expect(test.getPluginByName('test1')).toBeUndefined(); +}); + +test('Test addObserve', () => { + const container = IOCContainerStartUpReady(); + class Test extends Plugin { + constructor() { + super('test'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const test = new Test(); + container.inject(test); + test.pushToObserve('abs1'); + test.pushToObserve('abs2'); + expect(test.getObserver('abs1')).not.toBeNull(); +}); + +test('Test removeObserve', () => { + const container = IOCContainerStartUpReady(); + class Test extends Plugin { + constructor() { + super('test'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const test = new Test(); + container.inject(test); + test.pushToObserve('abs1'); + test.pushToObserve('abs2'); + expect(test.getObserver('abs1')).not.toBeNull(); + test.deleteObserve('abs1'); + expect(test.getObserver('abs1')).toBeNull(); +}); diff --git a/packages/core/test/Plugin/PluginManager.test.ts b/packages/core/test/Plugin/PluginManager.test.ts new file mode 100644 index 0000000000..3b20affcae --- /dev/null +++ b/packages/core/test/Plugin/PluginManager.test.ts @@ -0,0 +1,86 @@ +/** + * @jest-environment jsdom + */ +import { IOCContainerStartUpReady } from '../ContainerStartUp'; +import { Context, PluginManager, Plugin, IOCContainer } from '../../src'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test _initialize', () => { + class MyPlugin extends Plugin { + constructor() { + super('plugin'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const container = IOCContainerStartUpReady(); + const context: Context = container.getSingleton('Context'); + const manager2: PluginManager = container.getInstance( + 'PluginManager', + context, + [] + ); + const manager1: PluginManager = container.getInstance('PluginManager', context, [ + new MyPlugin(), + ]); + manager1.setContext(context); + manager2.setContext(context); +}); + +test('Test install', () => { + const container = IOCContainerStartUpReady(); + const context: Context = container.getSingleton('Context'); + const manager: PluginManager = container.getInstance('PluginManager', context); + class MyPlugin extends Plugin { + constructor() { + super('plugin'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const plugin = new MyPlugin(); + manager.install(plugin); +}); + +test('Test getPluginByName', () => { + const container = IOCContainerStartUpReady(); + const context: Context = container.getSingleton('Context'); + const manager: PluginManager = container.getInstance('PluginManager', context); + class MyPlugin extends Plugin { + constructor() { + super('plugin'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const plugin = new MyPlugin(); + manager.install(plugin); + expect(manager.getPluginByName('plugin')).not.toBeUndefined(); +}); + +test('Test uninstall', () => { + const container = IOCContainerStartUpReady(); + const context: Context = container.getSingleton('Context'); + const manager: PluginManager = container.getInstance('PluginManager', context); + class MyPlugin extends Plugin { + constructor() { + super('plugin'); + } + + onMounted(context: Context): void {} + + onMapping(container: IOCContainer): void {} + } + const plugin = new MyPlugin(); + manager.install(plugin); + expect(manager.getPluginByName('plugin')).not.toBeUndefined(); + manager.uninstall('plugin'); + expect(manager.getPluginByName('plugin')).toBeUndefined(); +}); diff --git a/packages/core/test/Server/IOHttp.test.ts b/packages/core/test/Server/IOHttp.test.ts new file mode 100644 index 0000000000..a4a38192d3 --- /dev/null +++ b/packages/core/test/Server/IOHttp.test.ts @@ -0,0 +1,70 @@ +/** + * @jest-environment jsdom + */ +import { IOHttp, IOHttpRequestType } from '../../src/Shared/IOHttp'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +const child_process = require('child_process'); +const os = require('os'); + +function osType() { + if (os.type() === 'Windows_NT') { + // windows + return 'windows'; + } + if (os.type() === 'Darwin') { + // mac + return 'mac'; + } + if (os.type() === 'Linux') { + // Linux + return 'linux'; + } + // Prompt not supported + return ''; +} + +describe('IOHttp', () => { + afterAll(async () => { + // stop http server + if (osType() === 'windows') { + child_process.exec( + 'for /f "tokens=5" %a in (\'netstat -aon ^| find "0.0.0.0:3998" ^| find "LISTENING"\') do taskkill /f /pid %a' + ); + } else if (osType() === 'mac') { + // https://stackoverflow.com/a/36917850 + child_process.exec( + "kill -9 `lsof -i TCP:3998 | awk '/LISTEN/{print $2}'`" + ); + } + }); + + test('IOHttp Get Test', (done) => { + const service = { + actionName: 'setStyle', + type: 'color', + value: 'red', + }; + IOHttp({ + contentType: 'application/json', + url: 'http://127.0.0.1:3998/action/', + data: service, + type: IOHttpRequestType.POST, + success: (json: any) => { + expect(json.message.actionName).toEqual(service.actionName); + done(); + }, + }); + // IOHttp({ + // contentType: 'application/json', + // url: 'http://universheet.lashuju.com/action/', + // data: service, + // type: IOHttpRequestType.POST, + // success: (json: any) => { + // expect(json.message.actionName).toEqual(service.actionName); + // done(); + // }, + // }); + }); +}); diff --git a/packages/core/test/Server/server.js b/packages/core/test/Server/server.js new file mode 100644 index 0000000000..041d58ce89 --- /dev/null +++ b/packages/core/test/Server/server.js @@ -0,0 +1,64 @@ +const http = require('http'); + +const server = http.createServer(); +server.on('request', (req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader( + 'Access-Control-Allow-Headers', + 'Content-Type,Content-Length, Authorization, Accept,X-Requested-With' + ); + res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS'); + if (req.url === '/') { + const responseBody = { + index: 1, + }; + + res.statusCode = 200; + res.write(JSON.stringify(responseBody)); + res.end(); + } else if (req.url.indexOf('/action') === 0 && req.method === 'POST') { + const chunks = []; + req.on('data', (chunk) => chunks.push(chunk)); + req.on('end', () => { + // buffer => string => object + const dataStr = Buffer.concat(chunks).toString(); + const data = JSON.parse(JSONize(dataStr)); + + const json = { + message: { + actionName: data.actionName, + }, + code: 0, + }; + res.statusCode = 200; + res.write(JSON.stringify(json)); + res.end(); + }); + } +}); + +server.listen(3998, () => { + console.log('Node server running on port 3998...'); +}); + +const JSONize = (str) => + // https://stackoverflow.com/questions/14432165/uncaught-syntaxerror-unexpected-token-with-json-parse/50730876 + str + .replace(/\\n/g, '\\n') + .replace(/\\'/g, "\\'") + .replace(/\\"/g, '\\"') + .replace(/\\&/g, '\\&') + .replace(/\\r/g, '\\r') + .replace(/\\t/g, '\\t') + .replace(/\\b/g, '\\b') + .replace(/\\f/g, '\\f') + // remove non-printable and other non-valid JSON chars + .replace(/[\u0000-\u0019]+/g, '') + + // https://stackoverflow.com/questions/9036429/convert-object-string-to-json + // wrap keys without quote with valid double quote + .replace(/([\w]+)\s*:/g, (_, $1) => `"${$1}":`) + // replacing single quote wrapped ones to double quote + .replace(/'([^']+)'/g, (_, $1) => `"${$1}"`) + // '{ "value": "red", }' to '{ "value": "red" }' + .replace(/,(?=\s*})/g, ''); diff --git a/packages/core/test/Shared/LRUMap.test.ts b/packages/core/test/Shared/LRUMap.test.ts new file mode 100644 index 0000000000..2408b672da --- /dev/null +++ b/packages/core/test/Shared/LRUMap.test.ts @@ -0,0 +1,134 @@ +import { LRUMap } from '../../src/Shared'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('LRU Test Key', () => { + let c = new LRUMap(4, [ + ['adam', 29], + ['john', 26], + ['angela', 24], + ['bob', 48], + ]); + let kit = c.keys(); + expect(kit.next().value).toEqual('adam'); + expect(kit.next().value).toEqual('john'); + expect(kit.next().value).toEqual('angela'); + expect(kit.next().value).toEqual('bob'); + expect(kit.next().done).toBeTruthy(); +}); + +test('LRU Test Value', () => { + let c = new LRUMap(4, [ + ['adam', 29], + ['john', 26], + ['angela', 24], + ['bob', 48], + ]); + let kit = c.values(); + expect(kit.next().value).toEqual(29); + expect(kit.next().value).toEqual(26); + expect(kit.next().value).toEqual(24); + expect(kit.next().value).toEqual(48); + expect(kit.next().done).toBeTruthy(); +}); + +test('LRU Test Clear', () => { + let c = new LRUMap(4); + c.set('adam', 29); + c.set('john', 26); + expect(c.size).toEqual(2); + c.clear(); + expect(c.size).toEqual(0); + expect(c.oldest).toEqual(undefined); + expect(c.newest).toEqual(undefined); +}); + +test('LRU Test Delete', () => { + let c = new LRUMap([ + ['adam', 29], + ['john', 26], + ['angela', 24], + ['bob', 48], + ]); + expect(c.get('adam')).toEqual(29); + c.delete('adam'); + expect(c.size).toEqual(3); + expect(c.get('adam')).toEqual(undefined); + c.delete('angela'); + expect(c.size).toEqual(2); + c.delete('bob'); + expect(c.size).toEqual(1); + c.delete('john'); + expect(c.size).toEqual(0); + expect(c.get('john')).toEqual(undefined); + expect(c.oldest).toEqual(undefined); + expect(c.newest).toEqual(undefined); +}); + +test('LRU Test Set', () => { + let c = new LRUMap(4); + c.set('a', 1); + c.set('a', 2); + c.set('a', 3); + c.set('a', 4); + expect(c.size).toEqual(1); + expect(c.newest).toEqual(c.oldest); + expect(c.newest?.toJSON()).toEqual({ key: 'a', value: 4 }); + + c.set('a', 5); + expect(c.size).toEqual(1); + expect(c.newest).toEqual(c.oldest); + expect(c.newest?.toJSON()).toEqual({ key: 'a', value: 5 }); + + c.set('b', 6); + expect(c.size).toEqual(2); + expect(c.newest !== c.oldest).toBeTruthy(); + + expect(c.newest?.toJSON()).toEqual({ key: 'b', value: 6 }); + expect(c.oldest?.toJSON()).toEqual({ key: 'a', value: 5 }); + + c.shift(); + expect(c.size).toEqual(1); + c.shift(); + expect(c.size).toEqual(0); +}); + +test('LRU Set And Get', () => { + let c = new LRUMap(4); + expect(c.size).toEqual(0); + expect(c.limit).toEqual(4); + expect(c.oldest).toEqual(undefined); + expect(c.newest).toEqual(undefined); + + c.set('adam', 29).set('john', 26).set('angela', 24).set('bob', 48); + expect(c.toString()).toEqual('adam:29 < john:26 < angela:24 < bob:48'); + expect(c.size).toEqual(4); + + expect(c.get('adam')).toEqual(29); + expect(c.get('john')).toEqual(26); + expect(c.get('angela')).toEqual(24); + expect(c.get('bob')).toEqual(48); + expect(c.toString()).toEqual('adam:29 < john:26 < angela:24 < bob:48'); + + expect(c.get('angela')).toEqual(24); + expect(c.toString()).toEqual('adam:29 < john:26 < bob:48 < angela:24'); + + c.set('ygwie', 81); + expect(c.toString()).toEqual('john:26 < bob:48 < angela:24 < ygwie:81'); + expect(c.size).toEqual(4); + expect(c.get('adam')).toEqual(undefined); + + c.set('john', 11); + expect(c.toString()).toEqual('bob:48 < angela:24 < ygwie:81 < john:11'); + expect(c.get('john')).toEqual(11); + + let expectedKeys = ['bob', 'angela', 'ygwie', 'john']; + c.forEach((v, k) => { + //sys.sets(k+': '+v); + expect(k).toEqual(expectedKeys.shift()); + }); + + let currentSize = c.size; + expect(c.delete('john') !== undefined).toBeTruthy(); + expect(currentSize - 1).toEqual(c.size); +}); diff --git a/packages/core/test/Shared/Locale.test.ts b/packages/core/test/Shared/Locale.test.ts new file mode 100644 index 0000000000..b28048056f --- /dev/null +++ b/packages/core/test/Shared/Locale.test.ts @@ -0,0 +1,37 @@ +/** + * @jest-environment jsdom + */ + +import { LocaleType, UniverSheet } from '../../src'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +// mock data +const zh = { + type: '表格', +}; +const en = { + type: 'table', +}; + +test('Test Locale', () => { + const univerSheet = UniverSheet.newInstance({ + id: 'workbook-01', + sheets: [ + { + name: 'first sheet', + }, + ], + locale: LocaleType.ZH, + }); + + const locale = univerSheet.context.getLocale(); + + locale.load({ zh, en }); + + expect(locale.get('type')).toEqual('表格'); + + locale.change(LocaleType.EN); + + expect(locale.get('type')).toEqual('table'); +}); diff --git a/packages/core/test/Shared/Logger.test.ts b/packages/core/test/Shared/Logger.test.ts new file mode 100644 index 0000000000..aac7e70155 --- /dev/null +++ b/packages/core/test/Shared/Logger.test.ts @@ -0,0 +1,22 @@ +/** + * @jest-environment jsdom + */ +import { Logger } from '../../src'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('test info', () => { + Logger.info('info'); +}); + +test('test error', () => { + Logger.error('error'); +}); + +test('test warn', () => { + Logger.error('warn'); +}); + +test('test br', () => { + Logger.error('br'); +}); diff --git a/packages/core/test/Shared/Migrate.test.ts b/packages/core/test/Shared/Migrate.test.ts new file mode 100644 index 0000000000..22e07c86c0 --- /dev/null +++ b/packages/core/test/Shared/Migrate.test.ts @@ -0,0 +1,6061 @@ +import { migrate } from '../../src/Shared'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('migrate', () => { + // univer 2.0 config + const sheetCell = { + name: 'Cell', + config: { + merge: { + '13_5': { + r: 13, + c: 5, + rs: 3, + cs: 1, + }, + '13_7': { + r: 13, + c: 7, + rs: 3, + cs: 2, + }, + '14_2': { + r: 14, + c: 2, + rs: 1, + cs: 2, + }, + '15_10': { + r: 15, + c: 10, + rs: 4, + cs: 3, + }, + }, + borderInfo: [ + { + rangeType: 'cell', + value: { + row_index: 3, + col_index: 3, + l: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 3, + col_index: 4, + l: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 10, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 3, + col_index: 5, + l: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 3, + col_index: 6, + l: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 3, + col_index: 7, + l: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 3, + col_index: 8, + l: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 5, + col_index: 2, + l: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 5, + col_index: 3, + l: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 5, + col_index: 4, + l: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 5, + col_index: 5, + l: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 5, + col_index: 6, + l: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 5, + col_index: 7, + l: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 1, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 5, + col_index: 8, + l: { + style: 2, + color: 'rgb(255, 0, 0)', + }, + r: { + style: 2, + color: 'rgb(255, 0, 0)', + }, + t: { + style: 2, + color: 'rgb(255, 0, 0)', + }, + b: { + style: 2, + color: 'rgb(255, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 7, + col_index: 2, + l: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + r: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + t: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + b: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 7, + col_index: 3, + l: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + r: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + t: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + b: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 7, + col_index: 5, + l: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + t: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 7, + col_index: 6, + r: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + t: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 7, + col_index: 8, + r: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 7, + col_index: 9, + l: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + b: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 8, + col_index: 2, + l: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + r: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + t: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + b: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 8, + col_index: 3, + l: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + r: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + t: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + b: { + style: 9, + color: 'rgb(0, 0, 255)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 8, + col_index: 5, + l: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + b: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 8, + col_index: 6, + r: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + b: { + style: 2, + color: 'rgb(154, 205, 50)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 8, + col_index: 8, + r: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 8, + col_index: 9, + l: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + t: { + style: 9, + color: 'rgb(0, 0, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 10, + col_index: 2, + l: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + t: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + b: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 10, + col_index: 3, + r: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + t: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + b: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 10, + col_index: 5, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + t: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 10, + col_index: 6, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + t: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 10, + col_index: 7, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + t: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 11, + col_index: 2, + l: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + t: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + b: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 11, + col_index: 3, + r: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + t: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + b: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 11, + col_index: 5, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 11, + col_index: 6, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 11, + col_index: 7, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 12, + col_index: 2, + l: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + t: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + b: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 12, + col_index: 3, + r: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + t: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + b: { + style: 1, + color: 'rgb(144, 238, 144)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 12, + col_index: 5, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + b: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 12, + col_index: 6, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + b: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 12, + col_index: 7, + l: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + r: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + b: { + style: 1, + color: 'rgb(205, 205, 0)', + }, + }, + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '2', + color: '#000', + range: [ + { + row: [3, 3], + column: [3, 4], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '2', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [3, 4], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '4', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [4, 4], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '3', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [4, 4], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '3', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [3, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '2', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [3, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '2', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [5, 5], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '2', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [8, 8], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '2', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [3, 8], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '4', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [7, 7], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '1', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [8, 8], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '5', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [6, 6], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '6', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [5, 5], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '3', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [4, 4], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '3', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [4, 4], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '2', + color: '#ff0000', + range: [ + { + row: [3, 3], + column: [3, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '2', + color: '#ff0000', + range: [ + { + row: [5, 5], + column: [2, 9], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '9', + color: '#ff0000', + range: [ + { + row: [5, 5], + column: [5, 5], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '8', + color: '#ff0000', + range: [ + { + row: [5, 5], + column: [6, 6], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '13', + color: '#ff0000', + range: [ + { + row: [5, 5], + column: [7, 7], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '13', + color: '#ff0000', + range: [ + { + row: [6, 6], + column: [11, 11], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '10', + color: '#ff0000', + range: [ + { + row: [5, 5], + column: [4, 4], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '11', + color: '#ff0000', + range: [ + { + row: [5, 5], + column: [3, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '11', + color: '#ff0000', + range: [ + { + row: [7, 8], + column: [2, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-all', + style: '3', + color: '#0000ff', + range: [ + { + row: [7, 8], + column: [2, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-inside', + style: '3', + color: '#0000ff', + range: [ + { + row: [7, 8], + column: [8, 9], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-inside', + style: '9', + color: '#0000ff', + range: [ + { + row: [7, 8], + column: [8, 9], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-inside', + style: '2', + color: '#0000ff', + range: [ + { + row: [7, 8], + column: [8, 9], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-inside', + style: '9', + color: '#0000ff', + range: [ + { + row: [7, 8], + column: [8, 9], + }, + ], + }, + { + rangeType: 'cell', + value: { + row_index: 10, + col_index: 10, + l: { + color: '#ff0000', + style: '13', + }, + r: { + color: '#ff0000', + style: '13', + }, + t: { + color: '#ff0000', + style: '13', + }, + b: { + color: '#ff0000', + style: '13', + }, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 6, + col_index: 11, + l: null, + r: null, + t: null, + b: null, + }, + }, + { + rangeType: 'cell', + value: { + row_index: 10, + col_index: 10, + l: null, + r: null, + t: null, + b: null, + }, + }, + { + rangeType: 'range', + borderType: 'border-outside', + style: '13', + color: '#00ff00', + range: [ + { + row: [10, 12], + column: [2, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-horizontal', + style: '13', + color: '#00ff00', + range: [ + { + row: [10, 12], + column: [2, 3], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-vertical', + style: '13', + color: '#ff9900', + range: [ + { + row: [10, 12], + column: [5, 7], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-outside', + style: '13', + color: '#ff9900', + range: [ + { + row: [10, 12], + column: [5, 7], + }, + ], + }, + { + rangeType: 'range', + borderType: 'border-none', + style: '1', + color: '#ff9900', + range: [ + { + row: [19, 19], + column: [6, 6], + }, + ], + }, + ], + rowlen: { + '0': 20, + '1': 20, + '2': 20, + '3': 20, + '4': 20, + '5': 20, + '6': 20, + '7': 20, + '8': 20, + '9': 20, + '10': 20, + '11': 20, + '12': 20, + '13': 20, + '14': 20, + '15': 20, + '16': 20, + '17': 31, + '18': 20, + '19': 20, + '20': 20, + '21': 20, + '22': 20, + '23': 20, + '24': 20, + '25': 79, + '26': 20, + '27': 20, + '28': 80, + '29': 36, + }, + columnlen: { + '0': 131, + '2': 153, + '3': 128, + '4': 136, + '5': 122, + '6': 138, + '7': 131, + '8': 128, + '9': 140, + '10': 144, + }, + rowhidden: { + '30': 0, + '31': 0, + }, + customHeight: { + '29': 1, + }, + customWidth: { + '2': 1, + }, + }, + index: '0', + zoomRatio: 1, + order: '0', + column: 18, + row: 36, + status: 1, + celldata: [ + { + r: 0, + c: 0, + v: { + customKey: { a: 1 }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + v: 1, + ct: { fa: 'General', t: 'n' }, + m: '1', + }, + }, + { + r: 0, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + v: 2, + ct: { fa: 'General', t: 'n' }, + m: '2', + }, + }, + { + r: 0, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + v: 3, + ct: { fa: 'General', t: 'n' }, + m: '3', + }, + }, + { + r: 0, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + v: 0, + ct: { fa: 'General', t: 'n' }, + m: '0', + f: '=Formula!D3+Formula!D4', + }, + }, + { + r: 0, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 0, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 0, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 0, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 0, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 0, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 0, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 0, + v: { + v: 'Background', + ct: { fa: 'General', t: 'g' }, + m: 'Background', + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 2, + v: { + bg: 'rgb(30, 144, 255)', + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 4, + v: { + bg: 'rgb(0, 255, 0)', + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 1, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 2, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 0, + v: { + v: 'Border', + ct: { fa: 'General', t: 'g' }, + m: 'Border', + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 3, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 4, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + ct: { + fa: 'General', + t: 'inlineStr', + s: [ + { + ff: 'Arial', + fc: 'rgb(255, 0, 0)', + fs: 12, + cl: 0, + un: 0, + bl: 0, + it: 0, + v: 'Inline', + }, + { + ff: 'Arial', + fc: '#000000', + fs: 12, + cl: 0, + un: 0, + bl: 0, + it: 0, + v: ' ', + }, + { + ff: 'Arial', + fc: '#000000', + fs: 16, + cl: 1, + un: 0, + bl: 0, + it: 1, + v: 'Style', + }, + { + ff: 'Arial', + fc: '#000000', + fs: 12, + cl: 0, + un: 0, + bl: 0, + it: 0, + v: ' ', + }, + { + ff: 'Arial', + fc: '#000000', + fs: 12, + cl: 0, + un: 0, + bl: 1, + it: 0, + v: 'Cell', + }, + ], + }, + }, + }, + { + r: 5, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 5, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 6, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 7, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 8, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 9, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 10, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 11, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 12, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + mc: { r: 13, c: 5, rs: 3, cs: 1 }, + }, + }, + { + r: 13, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + mc: { r: 13, c: 7, rs: 3, cs: 2 }, + }, + }, + { r: 13, c: 8, v: { mc: { r: 13, c: 7 } } }, + { + r: 13, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 13, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 14, + c: 0, + v: { + v: 'Span', + ct: { fa: 'General', t: 'g' }, + m: 'Span', + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 14, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 14, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + mc: { r: 14, c: 2, rs: 1, cs: 2 }, + }, + }, + { r: 14, c: 3, v: { mc: { r: 14, c: 2 } } }, + { + r: 14, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { r: 14, c: 5, v: { mc: { r: 13, c: 5 } } }, + { + r: 14, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { r: 14, c: 7, v: { mc: { r: 13, c: 7 } } }, + { r: 14, c: 8, v: { mc: { r: 13, c: 7 } } }, + { + r: 14, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 14, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 15, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 15, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 15, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 15, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 15, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { r: 15, c: 5, v: { mc: { r: 13, c: 5 } } }, + { + r: 15, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { r: 15, c: 7, v: { mc: { r: 13, c: 7 } } }, + { r: 15, c: 8, v: { mc: { r: 13, c: 7 } } }, + { + r: 15, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 15, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + mc: { r: 15, c: 10, rs: 4, cs: 3 }, + }, + }, + { r: 15, c: 11, v: { mc: { r: 15, c: 10 } } }, + { r: 15, c: 12, v: { mc: { r: 15, c: 10 } } }, + { + r: 16, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 16, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + mc: { r: 15, c: 10 }, + }, + }, + { r: 16, c: 11, v: { mc: { r: 15, c: 10 } } }, + { r: 16, c: 12, v: { mc: { r: 15, c: 10 } } }, + { + r: 17, + c: 0, + v: { + v: 'Font', + ct: { fa: 'General', t: 'g' }, + m: 'Font', + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 17, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 17, + c: 2, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: '11', + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 17, + c: 3, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 13, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 17, + c: 4, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 17, + c: 5, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 13, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 17, + c: 6, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: 'rgb(255, 215, 0)', + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 17, + c: 7, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(67, 110, 238)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 17, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 17, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 17, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + mc: { r: 15, c: 10 }, + }, + }, + { r: 17, c: 11, v: { mc: { r: 15, c: 10 } } }, + { r: 17, c: 12, v: { mc: { r: 15, c: 10 } } }, + { + r: 18, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 18, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + mc: { r: 15, c: 10 }, + }, + }, + { r: 18, c: 11, v: { mc: { r: 15, c: 10 } } }, + { r: 18, c: 12, v: { mc: { r: 15, c: 10 } } }, + { + r: 19, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 19, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 19, + c: 2, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: 'rgb(67, 110, 238)', + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(255, 215, 0)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 19, + c: 3, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: '10', + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 19, + c: 4, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 1, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: 'UniverSheet', + }, + }, + { + r: 19, + c: 5, + v: { + v: 'UniverSheet', + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + cl: 1, + m: 'UniverSheet', + }, + }, + { + r: 19, + c: 6, + v: { + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '1', + vt: '0', + cl: 1, + }, + }, + { + r: 19, + c: 7, + v: { + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 19, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 19, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 19, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 20, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 21, + c: 0, + v: { + v: 'Format', + ct: { fa: 'General', t: 'g' }, + m: 'Format', + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 21, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 21, + c: 2, + v: { + ct: { fa: '##0.00', t: 'n' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: '0.25', + v: 0.25, + }, + }, + { + r: 21, + c: 3, + v: { + ct: { fa: '$#,##0.00_);($#,##0.00)', t: 'n' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: '$0.25 ', + v: 0.25, + }, + }, + { + r: 21, + c: 4, + v: { + ct: { fa: '"$" 0.00', t: 'n' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: '$ 0.25', + v: 0.25, + }, + }, + { + r: 21, + c: 5, + v: { + ct: { fa: '0%', t: 'n' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: '25%', + v: 0.25, + }, + }, + { + r: 21, + c: 6, + v: { + ct: { fa: '# ?/?', t: 'n' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: ' 1/4', + v: 0.25, + }, + }, + { + r: 21, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 21, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 21, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 21, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 22, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 23, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 23, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 23, + c: 2, + v: { + ct: { fa: '0.00E+00', t: 'n' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + m: '2.50E-01', + v: 0.25, + }, + }, + { + r: 23, + c: 3, + v: { + v: 0.25, + ct: { fa: '0.00', t: 'n' }, + m: '0.25', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 23, + c: 4, + v: { + ct: { fa: 'AM/PM h:mm:ss', t: 'd' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + v: 44032, + m: 'AM 12:00:00', + }, + }, + { + r: 23, + c: 5, + v: { + ct: { fa: 'yyyy/MM/dd', t: 'd' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + v: 44032, + m: '2020/07/20', + }, + }, + { + r: 23, + c: 6, + v: { + ct: { fa: 'yyyy"年"M"月"d"日"', t: 'd' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + v: 44032, + m: '2020年7月20日', + }, + }, + { + r: 23, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 23, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 23, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 23, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 24, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 25, + c: 0, + v: { + v: 'Alignment', + ct: { fa: 'General', t: 'g' }, + m: 'Alignment', + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 25, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 25, + c: 2, + v: { + v: 'Top Left', + ct: { fa: 'General', t: 'g' }, + m: 'Top Left', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '1', + vt: '1', + }, + }, + { + r: 25, + c: 3, + v: { + v: 'Top Center', + ct: { fa: 'General', t: 'g' }, + m: 'Top Center', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '0', + vt: '1', + }, + }, + { + r: 25, + c: 4, + v: { + v: 'Top Right', + ct: { fa: 'General', t: 'g' }, + m: 'Top Right', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '2', + vt: '1', + }, + }, + { + r: 25, + c: 5, + v: { + v: 'Center Left', + ct: { fa: 'General', t: 'g' }, + m: 'Center Left', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '1', + vt: '0', + }, + }, + { + r: 25, + c: 6, + v: { + v: 'Center Center', + ct: { fa: 'General', t: 'g' }, + m: 'Center Center', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '0', + vt: '0', + }, + }, + { + r: 25, + c: 7, + v: { + v: 'Center Right', + ct: { fa: 'General', t: 'g' }, + m: 'Center Right', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '2', + vt: '0', + }, + }, + { + r: 25, + c: 8, + v: { + v: 'Bottom Left', + ct: { fa: 'General', t: 'g' }, + m: 'Bottom Left', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '1', + vt: '2', + }, + }, + { + r: 25, + c: 9, + v: { + v: 'Bottom Center', + ct: { fa: 'General', t: 'g' }, + m: 'Bottom Center', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '0', + vt: '2', + }, + }, + { + r: 25, + c: 10, + v: { + v: 'Bottom Right', + ct: { fa: 'General', t: 'g' }, + m: 'Bottom Right', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: '2', + vt: '2', + }, + }, + { + r: 26, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 26, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 27, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 0, + v: { + v: 'WordWrap', + ct: { fa: 'General', t: 'g' }, + m: 'WordWrap', + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 2, + v: { + v: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + ct: { fa: 'General', t: 'g' }, + m: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + tb: '2', + }, + }, + { + r: 28, + c: 3, + v: { + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + tb: '1', + }, + }, + { + r: 28, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 28, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + { + r: 29, + c: 0, + v: { + ct: { + fa: 'General', + t: 'inlineStr', + s: [ + { + ff: '"times new roman"', + fc: 'rgb(51, 51, 51)', + fs: '12', + cl: 0, + un: 0, + bl: 1, + it: 0, + v: 'TextRotate', + }, + ], + }, + ht: '1', + vt: '0', + }, + }, + { + r: 29, + c: 2, + v: { + ct: { fa: 'General', t: 'g' }, + v: 'I am UniverSheet text rotate style', + m: 'I am UniverSheet text rotate style', + tr: '1', + tb: '2', + ht: '1', + fs: '12', + }, + }, + { + r: 29, + c: 3, + v: { + ct: { fa: 'General', t: 'g' }, + v: 'I am UniverSheet text rotate style', + m: 'I am UniverSheet text rotate style', + tr: '2', + tb: '2', + ht: '0', + fs: '12', + }, + }, + { + r: 29, + c: 4, + v: { + ct: { fa: 'General', t: 'g' }, + v: 'I am UniverSheet text rotate style', + m: 'I am UniverSheet text rotate style', + tr: '4', + tb: '2', + ht: '1', + fs: '12', + vt: '2', + }, + }, + { + r: 29, + c: 5, + v: { + ct: { fa: 'General', t: 'g' }, + v: 'I am UniverSheet text rotate style', + m: 'I am UniverSheet text rotate style', + tr: '5', + tb: '2', + ht: '1', + fs: '12', + }, + }, + { + r: 29, + c: 6, + v: { + ct: { fa: 'General', t: 'g' }, + v: 'I am UniverSheet text rotate style', + m: 'I am UniverSheet text rotate style', + tr: '1', + tb: '1', + ht: '1', + fs: '12', + vt: '0', + }, + }, + { + r: 30, + c: 0, + v: { ct: { fa: 'General', t: 'g' }, v: 'hidden1', m: 'hidden1' }, + }, + { + r: 31, + c: 0, + v: { m: 'hidden2', ct: { fa: 'General', t: 'g' }, v: 'hidden2' }, + }, + { + r: 33, + c: 0, + v: { + ct: { fa: 'General', t: 'g' }, + bg: null, + bl: 1, + it: 0, + ff: 0, + fs: 11, + fc: 'rgb(51, 51, 51)', + ht: 1, + vt: 1, + }, + }, + ], + ch_width: 2361, + rh_height: 936, + universheet_select_save: [ + { + left: 741, + width: 138, + top: 796, + height: 19, + left_move: 741, + width_move: 138, + top_move: 796, + height_move: 19, + row: [33, 33], + column: [6, 6], + row_focus: 33, + column_focus: 6, + }, + ], + calcChain: [ + { + r: 0, + c: 3, + index: '0', + func: [true, 3, '=Formula!A1+Formula!B1'], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + ], + scrollLeft: 0, + scrollTop: 0, + }; + + const options = { + container: 'universheet', + lang: 'zh', + data: [sheetCell], + }; + + // Transition to 3.0 configuration + const univerData = migrate(options); +}); diff --git a/packages/core/test/Shared/NameGen.test.ts b/packages/core/test/Shared/NameGen.test.ts new file mode 100644 index 0000000000..354ef8c89d --- /dev/null +++ b/packages/core/test/Shared/NameGen.test.ts @@ -0,0 +1,8 @@ +import { NameGen } from '../../src'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('NameGen getSheetName', () => { + const name = NameGen.getSheetName(); + expect(name).not.toBeUndefined(); +}); diff --git a/packages/core/test/Shared/ObjectArray.test.ts b/packages/core/test/Shared/ObjectArray.test.ts new file mode 100644 index 0000000000..21de9a4d8f --- /dev/null +++ b/packages/core/test/Shared/ObjectArray.test.ts @@ -0,0 +1,448 @@ +import { ObjectArray } from '../../src/Shared/ObjectArray'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('object array create', () => { + const array = new ObjectArray({ 1: 'UniverSheet1' }); + expect(array.getLength()).toEqual(2); + array.push({ 2: 'UniverSheet2' }); + array.push({ 3: 'UniverSheet3' }); + expect(array.getLength()).toEqual(4); +}); + +test('object array pop', () => { + const array = new ObjectArray(); + expect(array.pop()).toBeNull(); + array.push('univerSheet1'); + array.push('univerSheet2'); + array.push('univerSheet3'); + array.pop(); + expect(array.get(0)).toEqual('univerSheet1'); + expect(array.get(1)).toEqual('univerSheet2'); + expect(array.get(2)).toBeUndefined(); + expect(array.getLength()).toEqual(2); +}); + +test('object array shift', () => { + const array = new ObjectArray(); + expect(array.shift()).toBeNull(); + + array.push('univerSheet1'); + array.push('univerSheet2'); + array.push('univerSheet3'); + array.shift(); + + expect(array.get(0)).toEqual('univerSheet2'); + expect(array.get(1)).toEqual('univerSheet3'); + expect(array.get(2)).toBeUndefined(); + expect(array.getLength()).toEqual(2); +}); + +test('object array inserts', () => { + const array1 = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + }); + const array2 = new ObjectArray({ + 0: 'univerSheet3', + 1: 'univerSheet4', + 2: 'univerSheet5', + }); + array1.inserts(0, array2); + expect(array1.get(0)).toEqual('univerSheet3'); + expect(array1.get(1)).toEqual('univerSheet4'); + expect(array1.get(2)).toEqual('univerSheet5'); + expect(array1.get(3)).toEqual('univerSheet0'); + expect(array1.get(4)).toEqual('univerSheet1'); + expect(array1.get(5)).toEqual('univerSheet2'); + expect(array1.getLength()).toEqual(6); + + const array3 = new ObjectArray(); + array3.inserts(3, array2); + expect(array3.getLength()).toEqual(array2.getLength()); +}); + +test('object array insert', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + }); + array.insert(1, 'univerSheetNew'); + expect(array.get(0)).toEqual('univerSheet0'); + expect(array.get(1)).toEqual('univerSheetNew'); + expect(array.get(2)).toEqual('univerSheet1'); + expect(array.get(3)).toEqual('univerSheet2'); + expect(array.get(4)).toEqual('univerSheet3'); + expect(array.get(5)).toEqual('univerSheet4'); + expect(array.get(6)).toEqual('univerSheet5'); + expect(array.getLength()).toEqual(7); +}); + +test('object array splice', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + 6: 'univerSheet6', + 7: 'univerSheet7', + 8: 'univerSheet8', + 9: 'univerSheet9', + 10: 'univerSheet10', + }); + expect(array.getLength()).toEqual(11); + + const splice1 = array.splice(0, 2); + expect(array.get(0)).toEqual('univerSheet2'); + expect(array.get(1)).toEqual('univerSheet3'); + expect(array.get(2)).toEqual('univerSheet4'); + expect(array.get(3)).toEqual('univerSheet5'); + expect(array.get(4)).toEqual('univerSheet6'); + expect(array.get(5)).toEqual('univerSheet7'); + expect(array.get(6)).toEqual('univerSheet8'); + expect(array.get(7)).toEqual('univerSheet9'); + expect(array.get(8)).toEqual('univerSheet10'); + expect(array.getLength()).toEqual(9); + expect(splice1.getLength()).toEqual(2); + + let splice2 = array.splice(2, 3); + expect(array.get(0)).toEqual('univerSheet2'); + expect(array.get(1)).toEqual('univerSheet3'); + expect(array.get(2)).toEqual('univerSheet7'); + expect(array.get(3)).toEqual('univerSheet8'); + expect(array.get(4)).toEqual('univerSheet9'); + expect(array.get(5)).toEqual('univerSheet10'); + expect(array.getLength()).toEqual(6); + expect(splice2.getLength()).toEqual(3); + + expect(new ObjectArray().splice(0, 10)).toEqual(new ObjectArray()); +}); + +test('object array unshift', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + array.unshift('univerSheet4'); + expect(array.get(0)).toEqual('univerSheet4'); + expect(array.get(1)).toEqual('univerSheet0'); + expect(array.get(2)).toEqual('univerSheet1'); + expect(array.get(3)).toEqual('univerSheet2'); + expect(array.get(4)).toEqual('univerSheet3'); + expect(array.getLength()).toEqual(5); +}); + +test('object array set', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + array.set(0, 'univerSheetNew0'); + array.set(1, 'univerSheetNew1'); + array.set(2, 'univerSheetNew2'); + array.set(10, 'univerSheetNew10'); + expect(array.get(0)).toEqual('univerSheetNew0'); + expect(array.get(1)).toEqual('univerSheetNew1'); + expect(array.get(2)).toEqual('univerSheetNew2'); + expect(array.get(10)).toEqual('univerSheetNew10'); + expect(array.getLength()).toEqual(11); +}); + +test('object array last', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + expect(array.last()).toEqual('univerSheet3'); +}); + +test('object array push', () => { + const array = new ObjectArray(); + array.push('univerSheet0'); + array.push('univerSheet1'); + array.push('univerSheet2'); + expect(array.get(0)).toEqual('univerSheet0'); + expect(array.get(1)).toEqual('univerSheet1'); + expect(array.get(2)).toEqual('univerSheet2'); + expect(array.getLength()).toEqual(3); +}); + +test('object array includes', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + }); + expect(array.includes('univerSheet0')).toBeTruthy(); + expect(array.includes('univerSheet10')).toBeFalsy(); +}); + +test('object array slice', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + }); + let slice = array.slice(1, 4); + expect(slice.get(0)).toEqual('univerSheet1'); + expect(slice.get(1)).toEqual('univerSheet2'); + expect(slice.get(2)).toEqual('univerSheet3'); + expect(slice.getLength()).toEqual(3); + + expect(new ObjectArray().slice(0, 10)).toEqual(new ObjectArray()); +}); + +test('object array concat', () => { + const array1 = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + }); + const array2 = new ObjectArray({ + 0: 'univerSheet3', + 1: 'univerSheet4', + 2: 'univerSheet5', + }); + const array3 = array1.concat(array2); + expect(array3.get(0)).toEqual('univerSheet0'); + expect(array3.get(1)).toEqual('univerSheet1'); + expect(array3.get(2)).toEqual('univerSheet2'); + expect(array3.get(3)).toEqual('univerSheet3'); + expect(array3.get(4)).toEqual('univerSheet4'); + expect(array3.get(5)).toEqual('univerSheet5'); + expect(array3.getLength()).toEqual(6); +}); + +test('object array delete', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + array.delete(0); + expect(array.get(0)).toEqual('univerSheet1'); + expect(array.get(1)).toEqual('univerSheet2'); + expect(array.get(2)).toEqual('univerSheet3'); + expect(array.getLength()).toEqual(3); +}); + +test('object array find', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + const find1 = array.find((k, v) => v === 'univerSheet1'); + const find2 = array.find((k, v) => v === 'univerSheet100'); + expect(find1).toEqual('univerSheet1'); + expect(find2).toEqual(null); +}); + +test('object array findIndex', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + const find1 = array.findIndex((k, v) => v === 'univerSheet1'); + const find2 = array.findIndex((k, v) => v === 'test1'); + expect(find1).toEqual(1); + expect(find2).toEqual(-1); +}); + +test('object array map', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + const find = array.map((v) => `${v}-new`); + expect(find.get(0)).toEqual('univerSheet0-new'); + expect(find.get(1)).toEqual('univerSheet1-new'); + expect(find.get(2)).toEqual('univerSheet2-new'); + expect(find.get(3)).toEqual('univerSheet3-new'); + expect(find.getLength()).toEqual(4); +}); + +test('object array filter', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + const find = array.filter((k, v) => k > 1); + expect(find.get(0)).toEqual('univerSheet2'); + expect(find.get(1)).toEqual('univerSheet3'); + expect(find.getLength()).toEqual(2); +}); + +test('object array iterator', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + for (let item of array) { + console.log(item); + } +}); + +test('object array forEach', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + }); + array.forEach((k, v) => { + if (v === 'univerSheet2') return false; + }); +}); + +test('object array bigData', () => { + console.time(); + const array = new ObjectArray(1000000); + for (let i = 0; i < 1000000; i++) { + array.set(i, i); + } + console.timeEnd(); +}); + +test('native array bigData', () => { + console.time(); + const array = new Array(1000000); + for (let i = 0; i < 1000000; i++) { + array.push(i); + } + console.timeEnd(); +}); + +test('object array delete bug 001', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + 6: 'univerSheet6', + 7: 'univerSheet7', + 8: 'univerSheet8', + 9: 'univerSheet9', + 10: 'univerSheet10', + }); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + array.delete(array.getLength() - 1); + expect(array.getLength()).toEqual(0); +}); + +test('object array delete bug 002', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + 6: 'univerSheet6', + 7: 'univerSheet7', + 8: 'univerSheet8', + 9: 'univerSheet9', + 10: 'univerSheet10', + }); + array.delete(8); + expect(array.get(8)).toEqual('univerSheet9'); +}); + +test('object array clone', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + 6: 'univerSheet6', + 7: 'univerSheet7', + 8: 'univerSheet8', + 9: 'univerSheet9', + 10: 'univerSheet10', + }); + expect(array.clone()).toEqual(array); + expect( + array.clone((v) => (v === 'univerSheet0' ? 'univerSheet100' : v)).get(0) + ).toEqual('univerSheet100'); +}); + +test('object array clear', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + 6: 'univerSheet6', + 7: 'univerSheet7', + 8: 'univerSheet8', + 9: 'univerSheet9', + 10: 'univerSheet10', + }); + expect(array.getSizeOf()).toEqual(11); + array.clear(); + expect(array.getSizeOf()).toEqual(0); +}); + +test('object array forOf', () => { + const array = new ObjectArray({ + 0: 'univerSheet0', + 1: 'univerSheet1', + 2: 'univerSheet2', + 3: 'univerSheet3', + 4: 'univerSheet4', + 5: 'univerSheet5', + 6: 'univerSheet6', + 7: 'univerSheet7', + 8: 'univerSheet8', + 9: 'univerSheet9', + 10: 'univerSheet10', + }); + const iterator = array[Symbol.iterator]()[Symbol.iterator](); + for (let val of array) { + console.log(val); + } +}); diff --git a/packages/core/test/Shared/ObjectMatrix.test.ts b/packages/core/test/Shared/ObjectMatrix.test.ts new file mode 100644 index 0000000000..491bef4905 --- /dev/null +++ b/packages/core/test/Shared/ObjectMatrix.test.ts @@ -0,0 +1,257 @@ +import { ObjectMatrix } from '../../src/Shared/ObjectMatrix'; +import { ObjectArray } from '../../src/Shared/ObjectArray'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test ObjectMatrix getLength', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getLength()).toEqual(3); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getLength()).toEqual(4); +}); + +test('Test ObjectMatrix getRow', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getRow(0)).not.toBeUndefined(); + expect(matrix.getRow(1)).not.toBeUndefined(); + expect(matrix.getRow(2)).not.toBeUndefined(); + expect(matrix.getRow(3)).toBeUndefined(); +}); + +test('Test ObjectMatrix insertRow', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getRow(0)).not.toBeUndefined(); + expect(matrix.getRow(1)).not.toBeUndefined(); + expect(matrix.getRow(2)).not.toBeUndefined(); + expect(matrix.getRow(3)).toBeUndefined(); +}); + +test('Test ObjectMatrix getRowOrCreate', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getRowOrCreate(0)).not.toBeUndefined(); + expect(matrix.getRowOrCreate(1)).not.toBeUndefined(); + expect(matrix.getRowOrCreate(2)).not.toBeUndefined(); + expect(matrix.getRowOrCreate(3)).not.toBeUndefined(); +}); + +test('Test ObjectMatrix swapRow', () => { + const matrix = new ObjectMatrix(); + const srcRow = new ObjectArray({ 0: 0, 1: 1 }); + const disRow = new ObjectArray({ 0: 0, 1: 1 }); + matrix.insertRow(0, srcRow); + matrix.insertRow(1, disRow); + expect(matrix.getRow(0) === srcRow).toBeTruthy(); + expect(matrix.getRow(1) === disRow).toBeTruthy(); + matrix.swapRow(0, 1); + expect(matrix.getRow(1) === srcRow).toBeTruthy(); + expect(matrix.getRow(0) === disRow).toBeTruthy(); +}); + +test('Test ObjectMatrix toArray', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.toArray().length).toEqual(3); +}); + +test('Test ObjectMatrix getValue', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getValue(0, 0)).toEqual(0); +}); + +test('Test ObjectMatrix DeleteValue', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getValue(0, 0)).toEqual(0); + matrix.deleteValue(0, 0); + expect(matrix.getValue(0, 0)).toEqual(1); +}); + +test('Test ObjectMatrix spliceRows', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); +}); + +test('Test ObjectMatrix insertRow', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getLength()).toEqual(3); +}); + +test('Test ObjectMatrix forEach', () => { + const matrix = new ObjectMatrix>(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.forEach((k, v) => k <= 1); +}); + +test('Test ObjectMatrix BigData', () => { + console.time(); + const matrix = new ObjectMatrix>(); + for (let i = 0; i < 100000; i++) { + matrix.insertRow(i, new ObjectArray()); + } + console.timeEnd(); +}); + +test('Test rest', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getLength()).toEqual(6); + matrix.reset(); + expect(matrix.getLength()).toEqual(0); +}); + +test('Test hasValue', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.hasValue()).toEqual(true); + matrix.reset(); + expect(matrix.hasValue()).toEqual(false); +}); + +test('Test getMatrix', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getMatrix()).not.toBeUndefined(); +}); + +test('Test setValue', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + matrix.setValue(0, 0, 10); + expect(matrix.getValue(0, 0)).toEqual(10); +}); + +test('Test pushRow', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getLength()).toEqual(6); + matrix.pushRow(new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getLength()).toEqual(7); +}); + +test('Test insertRows', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + + const matrix1 = new ObjectMatrix(); + matrix1.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix1.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix1.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix1.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix1.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix1.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + + matrix.insertRows(0, matrix1); + expect(matrix.getLength()).toEqual(12); +}); + +test('Test getFragments', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + + const fragment = matrix.getFragments(0, 1, 0, 1); + expect(fragment).not.toBeUndefined(); +}); + +test('Test getSizeOf', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getSizeOf()).toEqual(6); +}); + +test('Test getRange', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getRange()).not.toBeUndefined(); +}); + +test('Test toJSON', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.toJSON()).not.toBeUndefined(); +}); + +test('Test getData', () => { + const matrix = new ObjectMatrix(); + matrix.insertRow(0, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(1, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(2, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(3, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(4, new ObjectArray({ 0: 0, 1: 1 })); + matrix.insertRow(5, new ObjectArray({ 0: 0, 1: 1 })); + expect(matrix.getData()).not.toBeUndefined(); +}); diff --git a/packages/core/test/Shared/RectTree.test.ts b/packages/core/test/Shared/RectTree.test.ts new file mode 100644 index 0000000000..9d53ebec84 --- /dev/null +++ b/packages/core/test/Shared/RectTree.test.ts @@ -0,0 +1,255 @@ +import { RectTree } from '../../src/Shared/RectTree/RectTree'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('Test RectTree insert', () => { + const tree = new RectTree(); + const node1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 10, + maxX: 10, + }); + const node2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 20, + maxX: 20, + }); + const node3 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 5, + maxX: 5, + }); + tree.insert(node1); + tree.insert(node2); + tree.insert(node3); +}); + +test('Test RectTree load', () => { + const tree = new RectTree(); + const node1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 10, + maxX: 10, + }); + const node2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 20, + maxX: 20, + }); + tree.load([node1, node2]); + + const find1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 50, + maxX: 50, + }); + + const res1 = tree.search(find1); + expect(res1.length).toEqual(2); +}); + +test('Test RectTree search', () => { + const tree = new RectTree(); + const node1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 10, + maxX: 10, + }); + const node2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 20, + maxX: 20, + }); + tree.insert(node1); + tree.insert(node2); + + const find1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 5, + maxX: 5, + }); + const find2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 30, + maxX: 30, + }); + const find3 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 30, + maxX: 30, + }); + + const res1 = tree.search(find1); + const res2 = tree.search(find2); + const res3 = tree.search(find3); + + expect(res1.length).toEqual(1); + expect(res2.length).toEqual(1); + expect(res3.length).toEqual(2); + + expect(res1[0]).toEqual(node1); + expect(res2[0]).toEqual(node2); + expect(res3[0]).toEqual(node1); + expect(res3[1]).toEqual(node2); +}); + +test('Test RectTree clear', () => { + const tree = new RectTree(); + const node1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 10, + maxX: 10, + }); + const node2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 20, + maxX: 20, + }); + tree.load([node1, node2]); + + const find1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 50, + maxX: 50, + }); + const res1 = tree.search(find1); + expect(res1.length).toEqual(2); + + tree.clear(); + + const find2 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 50, + maxX: 50, + }); + const res2 = tree.search(find2); + expect(res2.length).toEqual(0); +}); + +test('Test RectTree collides', () => { + const tree = new RectTree(); + const node1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 10, + maxX: 10, + }); + const node2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 20, + maxX: 20, + }); + tree.insert(node1); + tree.insert(node2); + + const find1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 5, + maxX: 5, + }); + const find2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 30, + maxX: 30, + }); + const find3 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 30, + maxX: 30, + }); + const find4 = new RectTree.Node({ + minY: 100, + minX: 100, + maxY: 130, + maxX: 130, + }); + + const res1 = tree.collides(find1); + const res2 = tree.collides(find2); + const res3 = tree.collides(find3); + const res4 = tree.collides(find4); + + expect(res1).toEqual(true); + expect(res2).toEqual(true); + expect(res3).toEqual(true); + expect(res4).toEqual(false); +}); + +test('Test RectTree first', () => { + const tree = new RectTree(); + const node1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 10, + maxX: 10, + }); + const node2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 20, + maxX: 20, + }); + tree.insert(node1); + tree.insert(node2); + + const find1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 5, + maxX: 5, + }); + const res1 = tree.first(find1); + expect(res1).toEqual(node1); +}); + +test('Test RectTree Remove', () => { + const tree = new RectTree(); + const node1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 10, + maxX: 10, + }); + const node2 = new RectTree.Node({ + minY: 11, + minX: 11, + maxY: 20, + maxX: 20, + }); + tree.insert(node1); + tree.insert(node2); + + const find1 = new RectTree.Node({ + minY: 0, + minX: 0, + maxY: 50, + maxX: 50, + }); + + const res1 = tree.search(find1); + expect(res1.length).toEqual(2); + + tree.remove(node1); + + const res2 = tree.search(find1); + expect(res2.length).toEqual(1); +}); diff --git a/packages/core/test/Shared/Tools.test.ts b/packages/core/test/Shared/Tools.test.ts new file mode 100644 index 0000000000..2efe16a424 --- /dev/null +++ b/packages/core/test/Shared/Tools.test.ts @@ -0,0 +1,372 @@ +/** + * @jest-environment jsdom + */ +import { Tools } from '../../src/Shared/Tools'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('sheet Utils stringat', () => { + expect(Tools.stringAt(2)).toEqual('C'); + expect(Tools.stringAt(30)).toEqual('undefinedE'); +}); + +test('sheet Utils getEnvironment', () => { + expect(Tools.getEnvironment().app_version).toEqual('4.0'); +}); + +test('sheet Utils indexAt', () => { + expect(Tools.indexAt('CE')).toEqual(82); +}); + +test('sheet Utils deleteBlank', () => { + expect(Tools.deleteBlank('a b')).toEqual('ab'); + expect(Tools.deleteBlank()).toEqual(undefined); +}); + +test('sheet Utils getSystemType', () => { + Object.defineProperty(global.navigator, 'platform', { + value: 'Mac68K', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Mac'); + Object.defineProperty(global.navigator, 'platform', { + value: 'X11', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Unix'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Linux', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Linux'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Windows 2000', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Windows 2000'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Windows XP', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Windows XP'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Windows 2003', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Windows 2003'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Windows Vista', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Windows Vista'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Windows 7', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Windows 7'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Windows 10', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Windows 10'); + Object.defineProperty(global.navigator, 'platform', { + value: 'Win32', + configurable: true, + }); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Windows 11', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Windows 11'); + Object.defineProperty(global.navigator, 'userAgent', { + value: '', + configurable: true, + }); + expect(Tools.getSystemType()).toEqual('Unknown system'); +}); + +test('sheet Utils getBrowserType', () => { + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Opera', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('Opera'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Trident rv:11.0', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('IE11'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Edge', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('Edge'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Firefox', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('FF'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Safari', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('Safari'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Safari Chrome', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('Chrome'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'compatible; MSIE 7.0;', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('IE7'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'compatible; MSIE 8.0;', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('IE8'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'compatible; MSIE 9.0;', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('IE9'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'compatible; MSIE 10.0;', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('IE10'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'MSIE7', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('Unknown browser'); + Object.defineProperty(global.navigator, 'userAgent', { + value: 'compatible; MSIE 5.0;', + configurable: true, + }); + expect(Tools.getBrowserType()).toEqual('0'); +}); + +test('sheet Utils number check', () => { + expect(Tools.isNumber([])).toBeFalsy(); + expect(Tools.isNumber({})).toBeFalsy(); + expect(Tools.isNumber(() => {})).toBeFalsy(); + expect(Tools.isNumber(undefined)).toBeFalsy(); + expect(Tools.isNumber(null)).toBeFalsy(); + expect(Tools.isNumber(0)).toBeTruthy(); + expect(Tools.isNumber(1212)).toBeTruthy(); + expect(Tools.isNumber(false)).toBeFalsy(); +}); + +test('sheet Utils blank check', () => { + expect(Tools.isBlank([])).toBeFalsy(); + expect(Tools.isBlank({})).toBeFalsy(); + expect(Tools.isBlank(() => {})).toBeFalsy(); + expect(Tools.isBlank(0)).toBeFalsy(); + expect(Tools.isBlank(1212)).toBeFalsy(); + expect(Tools.isBlank(false)).toBeFalsy(); + expect(Tools.isBlank(undefined)).toBeTruthy(); + expect(Tools.isBlank(null)).toBeTruthy(); + expect(Tools.isBlank('')).toBeTruthy(); + expect(Tools.isBlank(' ')).toBeTruthy(); + expect(Tools.isBlank(' ')).toBeTruthy(); +}); + +test('sheet Utils plain object', () => { + class GC {} + expect(Tools.isPlainObject(new GC())).toBeFalsy(); + expect(Tools.isPlainObject({})).toBeTruthy(); + // eslint-disable-next-line no-new-object + expect(Tools.isPlainObject(new Object())).toBeTruthy(); +}); + +test('sheet Utils deepMerge', () => { + const object = { + zh: 'zh', + en: 'en', + array: [1, 2, 3], + object: { key: 'key' }, + }; + const clone = Tools.deepMerge({}, object); + expect(clone).toEqual(object); + expect(clone.array === object.array).toBeFalsy(); + expect(clone.object === object.object).toBeFalsy(); + const arr = [[[1]], [[2]]]; + const arrClone = Tools.deepMerge([], arr); + expect(arrClone === arr).toBeFalsy(); + const arr1 = [{ a: [1, 2] }]; + const arr1Clone = Tools.deepMerge([], arr1); + expect(arr1Clone === arr1).toBeFalsy(); +}); + +test('sheet Utils deepClone', () => { + const object = { + zh: 'zh', + en: 'en', + array: [1, 2, 3], + object: { key: 'key' }, + }; + const clone = Tools.deepClone(object); + expect(clone).toEqual(object); + expect(clone.array === object.array).toBeFalsy(); + expect(clone.object === object.object).toBeFalsy(); + const reg = /test/; + expect(Tools.deepClone(reg).test('test')).toEqual(true); + expect(Tools.deepClone(new Date('1993')).getDate()).toEqual(1); +}); + +test('sheet Utils diffValue', () => { + expect(Tools.diffValue(null, undefined)).toBeFalsy(); + expect(Tools.diffValue([], {})).toBeFalsy(); + expect(Tools.diffValue(1, '')).toBeFalsy(); + expect(Tools.diffValue(1, 1)).toBeTruthy(); + expect(Tools.diffValue(/s/, /s/)).toBeTruthy(); + expect(Tools.diffValue('s', 's')).toBeTruthy(); + expect( + Tools.diffValue(new Date('2020/11/11'), new Date('2020/11/11')) + ).toBeTruthy(); + expect(Tools.diffValue({ a: 1 }, { a: 1 })).toBeTruthy(); + expect( + Tools.diffValue({ a: { b: 2, c: [1, 2, 3] } }, { a: { b: 2, c: [1, 2, 3] } }) + ).toBeTruthy(); + expect( + Tools.diffValue({ a: { b: 2, c: [1, 2, 3] } }, { a: { b: 2, c: [1, 2, 4] } }) + ).toBeFalsy(); + expect(Tools.diffValue([1, 2, 3, 5], [1, 2, 3, 5])).toBeTruthy(); + expect(Tools.diffValue([1, 2, 3, 5], [1, 2, 8, 5])).toBeFalsy(); + expect( + Tools.diffValue( + [1, { b: 2, c: [1, 2, 3] }, 3, 5], + [1, { b: 2, c: [1, 2, 3] }, 3, 5] + ) + ).toBeTruthy(); +}); + +test('sheet Utils isBoolean', () => { + const a = true; + expect(Tools.isBoolean(true)).toEqual(true); +}); + +test('sheet Utils isFunction', () => { + const a = () => {}; + expect(Tools.isFunction(a)).toEqual(true); +}); + +test('sheet Utils hasLength', () => { + const a = 'abc'; + expect(Tools.hasLength(a)).toEqual(true); + const b = 'abc'; + expect(Tools.hasLength(b, 5)).toEqual(false); +}); + +test('sheet Utils capitalize', () => { + const a = 'abc'; + expect(Tools.capitalize(a)).toEqual('Abc'); +}); + +test('sheet Utils camelCase', () => { + const a = 'ab-c'; + expect(Tools.camelCase(a)).toEqual('abC'); +}); + +test('sheet Utils ABCatNum', () => { + const a = ''; + expect(Tools.ABCatNum(a)).toEqual(NaN); + const b = '`'; + expect(Tools.ABCatNum(b)).toEqual(NaN); +}); + +test('sheet Utils chatAtABC', () => { + expect(Tools.chatAtABC(2)).toEqual('C'); +}); + +test('sheet Utils itCount', () => { + Tools.itCount(2)(); +}); + +test('sheet Utils isAndroid', () => { + Object.defineProperty(global.navigator, 'userAgent', { value: 'Android' }); + expect(Tools.isAndroid()).toEqual(true); +}); + +test('sheet Utils isTablet', () => { + Object.defineProperty(global.navigator, 'userAgent', { value: 'ipad' }); + expect(Tools.isTablet()).toEqual(true); +}); + +test('sheet Utils isWeChat', () => { + Object.defineProperty(global.navigator, 'userAgent', { + value: 'micromessenger', + }); + expect(Tools.isWeChat()).toEqual(true); +}); + +test('sheet Utils isIphone', () => { + Object.defineProperty(global.navigator, 'userAgent', { + value: 'iPhone', + }); + expect(Tools.isIPhone()).toEqual(true); +}); + +test('sheet Utils isIEBrowser', () => { + Object.defineProperty(global.navigator, 'userAgent', { + value: 'User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1;', + }); + expect(Tools.isIEBrowser()).toEqual(true); +}); + +test('sheet Utils isMobile', () => { + Object.defineProperty(global.navigator, 'userAgent', { + value: 'Android', + }); + expect(Tools.isMobile()).toEqual(true); + Object.defineProperty(document.body, 'clientWidth', { + value: '300', + }); + Object.defineProperty(document.body, 'clientHeight', { + value: '400', + }); + expect(Tools.isMobile()).toEqual(true); +}); + +test('sheet Utils fillTwoDimensionalArray', () => { + const a = Tools.fillTwoDimensionalArray(3, 3, '3'); + expect(a.length).toEqual(3); +}); + +test('sheet Utils diffArrays', () => { + expect(Tools.diffValue([1], [1, 2])).toEqual(false); +}); + +test('sheet Utils getLanguage', () => { + Object.defineProperty(global, 'navigator', { + value: null, + configurable: true, + }); + expect(Tools.getLanguage()).toEqual('en-US'); +}); diff --git a/packages/core/test/Shared/Tuples.test.ts b/packages/core/test/Shared/Tuples.test.ts new file mode 100644 index 0000000000..115ceaa8d9 --- /dev/null +++ b/packages/core/test/Shared/Tuples.test.ts @@ -0,0 +1,27 @@ +import { Tuples } from '../../src/Shared/Tuples'; + +jest.mock('nanoid', () => ({ nanoid: () => '12345678' })); + +test('checkup', () => { + const test = (...argument: any) => + Tuples.checkup( + argument, + Tuples.BOOLEAN_TYPE, + Tuples.STRING_TYPE, + Tuples.NUMBER_TYPE, + Tuples.FUNCTION_TYPE + ); + expect(test(false, '1', 1, () => {})).toEqual(true); + const test1 = (...argument: any) => + Tuples.checkup(argument, Tuples.BOOLEAN_TYPE); + expect(test1('1')).toEqual(false); + const test2 = (...argument: any) => Tuples.checkup(argument, Tuples.STRING_TYPE); + expect(test2(1)).toEqual(false); + const test3 = (...argument: any) => Tuples.checkup(argument, Tuples.NUMBER_TYPE); + expect(test3('1')).toEqual(false); + const test4 = (...argument: any) => + Tuples.checkup(argument, Tuples.FUNCTION_TYPE); + expect(test4('1')).toEqual(false); + const test5 = (...argument: any) => Tuples.checkup(argument, Tuples.OBJECT_TYPE); + expect(test5('1')).toEqual(false); +}); diff --git a/packages/core/test/globalSetup.js b/packages/core/test/globalSetup.js new file mode 100644 index 0000000000..66a3337f37 --- /dev/null +++ b/packages/core/test/globalSetup.js @@ -0,0 +1,11 @@ +const { setup: setupDevServer } = require('jest-dev-server'); + +module.exports = async function globalSetup() { + // await setupDevServer({ + // command: `node test/Server/server.js`, + // launchTimeout: 10000, + // port: 3998, + // usedPortAction: 'kill', + // }); + // Your global setup +}; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000000..43fada229b --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + "downlevelIteration": true, + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports true + "jsx": "preserve", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "suppressImplicitAnyIndexErrors": true, + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*"] +} diff --git a/packages/core/tsconfig.ref.json b/packages/core/tsconfig.ref.json new file mode 100644 index 0000000000..b321caa9ce --- /dev/null +++ b/packages/core/tsconfig.ref.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "exclude": ["test/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/core/vite.config.ts b/packages/core/vite.config.ts new file mode 100644 index 0000000000..a3be243a70 --- /dev/null +++ b/packages/core/vite.config.ts @@ -0,0 +1,51 @@ +import { defineConfig } from 'vite'; +import { name, version } from './package.json'; +import path from 'path'; +import createExternal from 'vite-plugin-external'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheet', + formats: ['es', 'umd'], + fileName: 'universheet-core', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3102, + open: true, // // Automatically open the app in the browser on server start. + }, + + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + + plugins: [ + createExternal({ + externals: { + 'es6-proxy-polyfill': 'es6-proxy-polyfill', + 'reflect-metadata': 'reflect-metadata', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-alternating-colors/README-zh.md b/packages/sheets-plugin-alternating-colors/README-zh.md new file mode 100644 index 0000000000..61ce3b90aa --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet AlternatingColors 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-alternating-colors +``` + +### 使用 + +```js +impport {AlternatingColors} from '@univer/sheets-plugin-alternating-colors' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new AlternatingColors()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-alternating-colors/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-alternating-colors dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-alternating-colors/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-alternating-colors build +``` diff --git a/packages/sheets-plugin-alternating-colors/README.md b/packages/sheets-plugin-alternating-colors/README.md new file mode 100644 index 0000000000..370664509d --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin AlternatingColors + +### Installation + +```bash +npm i @univer/sheets-plugin-alternating-colors +``` + +### Usage + +```js +impport {AlternatingColors} from '@univer/sheets-plugin-alternating-colors' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new AlternatingColors()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-alternating-colors/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-alternating-colors dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-alternating-colors/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-alternating-colors build +``` diff --git a/packages/sheets-plugin-alternating-colors/index.html b/packages/sheets-plugin-alternating-colors/index.html new file mode 100644 index 0000000000..5d5495993a --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-alternating-colors/jest.config.js b/packages/sheets-plugin-alternating-colors/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-alternating-colors/package.json b/packages/sheets-plugin-alternating-colors/package.json new file mode 100644 index 0000000000..e22be49279 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/package.json @@ -0,0 +1,58 @@ +{ + "name": "@univer/sheets-plugin-alternating-colors", + "version": "0.0.1", + "description": "UniverSheet normal plugin-alternating-colors", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/universheet-plugin-alternating-colors.umd.js", + "module": "./lib/universheet-plugin-alternating-colors.es.js", + "types": "./lib/universheet-plugin-alternating-colors.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/sheets-plugin-protection": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/AlternatingColorsPlugin.tsx b/packages/sheets-plugin-alternating-colors/src/AlternatingColorsPlugin.tsx new file mode 100644 index 0000000000..92b4bc5866 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/AlternatingColorsPlugin.tsx @@ -0,0 +1,126 @@ +import { IMainProps, ISlotElement, ISlotProps, IToolBarItemProps } from '@univer/base-component'; +import { Command, Context, IOCContainer, UniverSheet, Nullable, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { Banding } from './Banding'; +import { IAddBandingActionData } from './Command'; +import { ACTION_NAMES } from './Command/ACTION_NAMES'; +import { ALTERNATING_COLORS_PLUGIN_NAME } from './Const/PLUGIN_NAME'; +import { DEFAULT_PROPERTIES_BANDED_RANGE, IBandedRange } from './IBandedRange'; +import { IConfig } from './IData/IAlternatingColors'; +import { en, zh } from './Locale'; +import { AlternatingColorsButton } from './UI/AlternatingColorsButton'; +import { AlternatingColorsSide } from './UI/AlternatingColorsSide/AlternatingColorsSide'; + +export interface IPluginConfig { + value: IBandedRange[]; +} + +export class AlternatingColorsPlugin extends Plugin { + spreadsheetPlugin: Nullable; + + protected _config: IPluginConfig; + + constructor(config?: IPluginConfig) { + super(ALTERNATING_COLORS_PLUGIN_NAME); + this._config = config || { value: [] }; + } + + static create(config?: IPluginConfig) { + return new AlternatingColorsPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = { + // context, + }; + + const item: IToolBarItemProps = { + locale: ALTERNATING_COLORS_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + + // get spreadsheet plugin + this.spreadsheetPlugin = context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + + // extend toolbar + this.spreadsheetPlugin?.addButton(item); + + const panelItem: ISlotProps = { + name: ALTERNATING_COLORS_PLUGIN_NAME, + type: ISlotElement.JSX, + label: , + }; + + // extend sider + this.spreadsheetPlugin?.addSider(panelItem); + + // extend main area + const mainItem: IMainProps = { + name: ALTERNATING_COLORS_PLUGIN_NAME, + type: ISlotElement.JSX, + content:
        middle alternating
        , + }; + + this.spreadsheetPlugin?.addMain(mainItem); + this.spreadsheetPlugin?.showMainByName(ALTERNATING_COLORS_PLUGIN_NAME, true); + } + + /** + * {@inheritDoc Plugin.onMapping} + */ + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} + + getBandings(): Banding[] { + return this._config.value.map((element) => new Banding(this, element)); + } + + getBandingById(bandedRangeId: string): Nullable { + const bandingData = this._config.value.find((element) => element.bandedRangeId === bandedRangeId); + + return bandingData && new Banding(this, bandingData); + } + + getBandingData(): IBandedRange[] { + return this._config.value; + } + + /** + * add banding by setting + */ + addRowBanding(bandedRange: IBandedRange) { + const context = this.getContext(); + const _commandManager = context.getCommandManager(); + const _worksheet = context.getWorkBook().getActiveSheet()!; + + // Organize action data + const actionData: IAddBandingActionData = { + actionName: ACTION_NAMES.ADD_BANDING_ACTION, + bandedRange, + sheetId: _worksheet.getSheetId(), + }; + + // Execute action + const command = new Command(context.getWorkBook(), actionData); + _commandManager.invoke(command); + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/Apply/AddBanding.ts b/packages/sheets-plugin-alternating-colors/src/Apply/AddBanding.ts new file mode 100644 index 0000000000..0cda02476e --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Apply/AddBanding.ts @@ -0,0 +1,7 @@ +import { AlternatingColorsPlugin } from '../AlternatingColorsPlugin'; +import { IBandedRange } from '../IBandedRange'; + +export function AddBanding(plugin: AlternatingColorsPlugin, bandedRange: IBandedRange): void { + let data = plugin.getBandingData(); + data.push(bandedRange); +} diff --git a/packages/sheets-plugin-alternating-colors/src/Apply/DeleteBanding.ts b/packages/sheets-plugin-alternating-colors/src/Apply/DeleteBanding.ts new file mode 100644 index 0000000000..eb81760e3e --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Apply/DeleteBanding.ts @@ -0,0 +1,15 @@ +import { AlternatingColorsPlugin } from '../AlternatingColorsPlugin'; +import { IBandedRange } from '../IBandedRange'; + +export function DeleteBanding(plugin: AlternatingColorsPlugin, bandedRangeId: string): IBandedRange { + let data = plugin.getBandingData(); + const deleteBanding = data.find((bandedRange: IBandedRange, i) => { + if (bandedRange.bandedRangeId === bandedRangeId) { + // remove + data.splice(i, 1); + return bandedRange; + } + return null; + })!; + return deleteBanding; +} diff --git a/packages/sheets-plugin-alternating-colors/src/Apply/SetBanding.ts b/packages/sheets-plugin-alternating-colors/src/Apply/SetBanding.ts new file mode 100644 index 0000000000..a6a4bbbf43 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Apply/SetBanding.ts @@ -0,0 +1,18 @@ +import { Tools } from '@univer/core'; +import { AlternatingColorsPlugin } from '../AlternatingColorsPlugin'; +import { DEFAULT_PROPERTIES_BANDED_RANGE, IBandedRange } from '../IBandedRange'; + +export function SetBanding(plugin: AlternatingColorsPlugin, bandedRange: IBandedRange): IBandedRange { + let data = plugin.getBandingData(); + let result: IBandedRange = DEFAULT_PROPERTIES_BANDED_RANGE; + for (let i = 0; i < data.length; i++) { + const banding = data[i]; + if (banding.bandedRangeId === bandedRange.bandedRangeId) { + result = Tools.deepClone(banding); + // edit + data[i] = bandedRange; + } + } + + return result; +} diff --git a/packages/sheets-plugin-alternating-colors/src/Apply/index.ts b/packages/sheets-plugin-alternating-colors/src/Apply/index.ts new file mode 100644 index 0000000000..d05e02ad07 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Apply/index.ts @@ -0,0 +1,5 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './AddBanding'; +export * from './DeleteBanding'; +export * from './SetBanding'; +// @endindex diff --git a/packages/sheets-plugin-alternating-colors/src/BANDING_THEME_COLOR.ts b/packages/sheets-plugin-alternating-colors/src/BANDING_THEME_COLOR.ts new file mode 100644 index 0000000000..a470fc2f4a --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/BANDING_THEME_COLOR.ts @@ -0,0 +1,179 @@ +/** + * Banding theme color presets map + */ +export const BANDING_THEME_COLOR_MAP = { + light_gray: { + headerColor: '#bfbdbe', + firstBandColor: '#ffffff', + secondBandColor: '#f8f3f7', + footerColor: '#dde2de', + }, + light_cyan: { + headerColor: '#4bd4e7', + firstBandColor: '#ffffff', + secondBandColor: '#eaf7ff', + footerColor: '#aae9f8', + }, + green: { + headerColor: '#5ed593', + firstBandColor: '#ffffff', + secondBandColor: '#e5fbee', + footerColor: '#a5efcc', + }, + yellow: { + headerColor: '#f6cb4b', + firstBandColor: '#ffffff', + secondBandColor: '#fff9e7', + footerColor: '#ffebac', + }, + light_orange: { + headerColor: '#f96420', + firstBandColor: '#ffffff', + secondBandColor: '#ffe5d9', + footerColor: '#ffcfba', + }, + light_blue: { + headerColor: '#5599fc', + firstBandColor: '#ffffff', + secondBandColor: '#ecf2fe', + footerColor: '#afcbfa', + }, + teal: { + headerColor: '#22a69b', + firstBandColor: '#ffffff', + secondBandColor: '#dff2f8', + footerColor: '#8dd4d0', + }, + grey: { + headerColor: '#7a939a', + firstBandColor: '#ffffff', + secondBandColor: '#f0eff7', + footerColor: '#bdcad0', + }, + light_brown: { + headerColor: '#d7a270', + firstBandColor: '#ffffff', + secondBandColor: '#fdf3f1', + footerColor: '#ead2b6', + }, + light_green: { + headerColor: '#89c54b', + firstBandColor: '#ffffff', + secondBandColor: '#f1f7e9', + footerColor: '#c5e3a7', + }, + indigo: { + headerColor: '#8f88f0', + firstBandColor: '#ffffff', + secondBandColor: '#f0e5ff', + footerColor: '#c6c4f6', + }, + pink: { + headerColor: '#fd1664', + firstBandColor: '#ffffff', + secondBandColor: '#feddee', + footerColor: '#f98ab5', + }, + purple: { + headerColor: '#da96d3', + firstBandColor: '#ffffff', + secondBandColor: '#fce8fb', + footerColor: '#f2caee', + }, + darken_red: { + headerColor: '#b49191', + firstBandColor: '#ffffff', + secondBandColor: '#f5ebe8', + footerColor: '#d8c3c3', + }, + darken_green: { + headerColor: '#91b493', + firstBandColor: '#ffffff', + secondBandColor: '#f0fbf0', + footerColor: '#b4cfb6', + }, + darken_orange: { + headerColor: '#b4a891', + firstBandColor: '#ffffff', + secondBandColor: '#f8f6f1', + footerColor: '#d3cab8', + }, + darken_blue: { + headerColor: '#91abb4', + firstBandColor: '#ffffff', + secondBandColor: '#eff7fa', + footerColor: '#b7cbd3', + }, + darken_yellow: { + headerColor: '#b7ba82', + firstBandColor: '#ffffff', + secondBandColor: '#fafbeb', + footerColor: '#dadcb4', + }, + red: { + headerColor: '#df3e3e', + firstBandColor: '#ffffff', + secondBandColor: '#fde9e9', + footerColor: '#f89292', + }, + orange: { + headerColor: '#f2711c', + firstBandColor: '#ffffff', + secondBandColor: '#fef0d7', + footerColor: '#fbb335', + }, + green_yellow: { + headerColor: '#b5cc18', + firstBandColor: '#ffffff', + secondBandColor: '#f9fbd4', + footerColor: '#e2ed2a', + }, + cyan: { + headerColor: '#00b5ad', + firstBandColor: '#ffffff', + secondBandColor: '#ccfaf9', + footerColor: '#00e4df', + }, + blue: { + headerColor: '#2185d0', + firstBandColor: '#ffffff', + secondBandColor: '#d8f3fc', + footerColor: '#3cc4f0', + }, + brown: { + headerColor: '#a5673f', + firstBandColor: '#ffffff', + secondBandColor: '#f6ede5', + footerColor: '#d3a47c', + }, +}; + +/** + * Banding theme color presets list + */ +export const BANDING_THEME_COLOR_LIST = [ + 'light_gray', + 'light_cyan', + 'green', + 'yellow', + 'light_orange', + 'light_blue', + 'teal', + 'grey', + 'light_brown', + 'light_green', + 'indigo', + 'pink', + 'purple', + 'darken_red', + 'darken_green', + 'darken_orange', + 'darken_blue', + 'darken_yellow', + 'red', + 'orange', + 'green_yellow', + 'cyan', + 'blue', + 'brown', +]; diff --git a/packages/sheets-plugin-alternating-colors/src/Banding.ts b/packages/sheets-plugin-alternating-colors/src/Banding.ts new file mode 100644 index 0000000000..6e8096a8f5 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Banding.ts @@ -0,0 +1,273 @@ +import { Command, CommandManager, Context, Plugin, WorkSheet } from '@univer/core'; + +import { IAddBandingActionData, IDeleteBandingActionData, ISetBandingActionData } from './Command'; +import { ACTION_NAMES } from './Command/ACTION_NAMES'; +import { IBandedRange } from './IBandedRange'; + +/** + * Access and modify bandings + * + * @remarks + * The color patterns applied to rows or columns of a range. Each banding consists of a range and a set of colors for rows, columns, headers, and footers. + * + * Reference: {@link https://developers.google.com/apps-script/reference/spreadsheet/banding | Google Sheets Banding} + */ +export class Banding { + protected readonly _bandedRange: IBandedRange; + + protected readonly _plugin: Plugin; + + private _worksheet: WorkSheet; + + private _commandManager: CommandManager; + + private _context: Context; + + // private _range: Range; + + constructor(plugin: Plugin, bandedRange: IBandedRange) { + this._plugin = plugin; + this._bandedRange = bandedRange; + this._context = this.getPlugin().getContext(); + this._commandManager = this._context.getCommandManager(); + this._worksheet = this._context.getWorkBook().getActiveSheet()!; + } + + getPlugin(): Plugin { + return this._plugin; + } + + // private _getRowBandingProperties(): IBandingProperties { + // const { rowProperties } = this._bandedRange; + // return typeof rowProperties.bandingTheme === 'string' + // ? BANDING_THEME_COLOR_MAP[rowProperties.bandingTheme] + // : rowProperties.bandingTheme; + // } + + // private _setRowProperties(property: string, color: string): Banding { + // const { _context, _commandManager, _bandedRange } = this; + // const { bandedRangeId, rangeData } = this._bandedRange; + + // const rowProperties: IBanding = { + // bandingTheme: Object.assign({}, this._getRowBandingProperties(), { + // [property]: color, + // }), + // showHeader: _bandedRange.rowProperties.showHeader, + // showFooter: _bandedRange.rowProperties.showFooter, + // }; + + // // Organize action data + // const actionData: ISetBandingActionData = { + // actionName: ACTION_NAMES.SET_BANDING_ACTION, + // bandedRange: { + // bandedRangeId, + // rangeData, + // rowProperties, + // }, + // sheetId: this._worksheet.getSheetId(), + // }; + + // // Execute action + // const command = new Command(_context.getWorkBook(), actionData); + // _commandManager.invoke(command); + + // return this; + // } + + /** + * get bandedRange + */ + // getBandedRange(): IBandedRange { + // return this._bandedRange; + // } + // /** + // * set bandedRange + // */ + // setBandedRange(bandedRange: IBandedRange): Banding { + // this._bandedRange = bandedRange; + // return this; + // } + + /** + * add banding by setting + */ + addRowBanding(): Banding { + const { _context, _commandManager, _bandedRange } = this; + + // Organize action data + const actionData: IAddBandingActionData = { + actionName: ACTION_NAMES.ADD_BANDING_ACTION, + bandedRange: _bandedRange, + sheetId: this._worksheet.getSheetId(), + }; + + // Execute action + const command = new Command(_context.getWorkBook(), actionData); + _commandManager.invoke(command); + return this; + } + + /** + * edit banding by setting + */ + setRowBanding(bandedRange: IBandedRange): Banding { + const { _context, _commandManager } = this; + + // Organize action data + const actionData: ISetBandingActionData = { + actionName: ACTION_NAMES.SET_BANDING_ACTION, + bandedRange, + sheetId: this._worksheet.getSheetId(), + }; + + // Execute action + const command = new Command(_context.getWorkBook(), actionData); + _commandManager.invoke(command); + + return this; + } + + /** + * Copies this banding to another range. + * @param range The range to copy this banding to. + * @returns Banding — The new banding. + */ + // copyTo(range: Range): Nullable { + // const { bandingTheme, showHeader, showFooter } = + // this._bandedRange.rowProperties; + + // return range.applyRowBanding(bandingTheme, showHeader, showFooter); + // } + + /** + * Removes this banding. + */ + remove(): void { + const { _context, _commandManager, _bandedRange } = this; + // Organize action data + const actionData: IDeleteBandingActionData = { + sheetId: this._worksheet.getSheetId(), + actionName: ACTION_NAMES.DELETE_BANDING_ACTION, + bandedRangeId: _bandedRange.bandedRangeId, + }; + + // Execute action + const command = new Command(_context.getWorkBook(), actionData); + _commandManager.invoke(command); + } + + /** + * get range instance + * @returns + */ + // getRange(): Range { + // return this._range; + // } + + /** + * Sets the range for this banding. + * @param range The new range for this banding. + * @returns Banding — This banding, for chaining. + */ + // setRange(range: Range): Banding { + // const { _context, _commandManager } = this; + // const { bandedRangeId, rowProperties } = this._bandedRange; + + // // Organize action data + // const actionData: ISetBandingActionData = { + // actionName: ACTION_NAMES.SET_BANDING_ACTION, + // bandedRange: { + // bandedRangeId, + // rangeData: range.getRangeData(), + // rowProperties, + // }, + // sheetId: this._worksheet.getSheetId(), + // }; + + // // Execute action + // const command = new Command(_context.getWorkBook(), actionData); + // _commandManager.invoke(command); + + // return this; + // } + + /** + * Returns the first row color that is alternating or null if no color is set. + * + * @returns String — The color code in CSS notation (such as '#ffffff' or 'white'), or null if no color is set. + */ + // getFirstRowColor(): string { + // return this._getRowBandingProperties().firstBandColor; + // } + + /** + * Returns the second row color that is alternating or null if no color is set. + * + * @returns String — The color code in CSS notation (such as '#ffffff' or 'white'), or null if no color is set. + */ + // getSecondRowColor(): string { + // return this._getRowBandingProperties().secondBandColor; + // } + + /** + * Returns the color of the header row or null if no color is set. + * + * @returns String — The color code in CSS notation (such as '#ffffff' or 'white'), or null if no color is set. + */ + // getHeaderRowColor(): string { + // return this._getRowBandingProperties().headerColor; + // } + + /** + * Returns the color of the last row, or null if no color is set. + * + * @returns String — The color code in CSS notation (such as '#ffffff' or 'white'), or null if no color is set. + */ + // getFooterRowColor(): string { + // return this._getRowBandingProperties().footerColor; + // } + + /** + * Sets the first row color that is alternating. + * + * @param color The color code in CSS notation (such as '#ffffff' or 'white'), or null to clear the color. + * + * @returns Banding — This banding, for chaining. + */ + // setFirstRowColor(color: string): Banding { + // return this._setRowProperties('firstBandColor', color); + // } + + /** + * Sets the second row color that is alternating. + * + * @param color The color code in CSS notation (such as '#ffffff' or 'white'), or null to clear the color. + * + * @returns Banding — This banding, for chaining. + */ + // setSecondRowColor(color: string): Banding { + // return this._setRowProperties('secondBandColor', color); + // } + + /** + * Sets the color of the header row. + * + * @param color The color code in CSS notation (such as '#ffffff' or 'white'), or null to clear the color. + * + * @returns Banding — This banding, for chaining. + */ + // setHeaderRowColor(color: string): Banding { + // return this._setRowProperties('headerColor', color); + // } + + /** + * Sets the color of the last row. + * + * @param color The color code in CSS notation (such as '#ffffff' or 'white'), or null to clear the color. + * + * @returns Banding — This banding, for chaining. + */ + // setFooterRowColor(color: string): Banding { + // return this._setRowProperties('footerColor', color); + // } +} diff --git a/packages/sheets-plugin-alternating-colors/src/BandingTheme.ts b/packages/sheets-plugin-alternating-colors/src/BandingTheme.ts new file mode 100644 index 0000000000..518ac233db --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/BandingTheme.ts @@ -0,0 +1,36 @@ +/** + * An enumeration of banding themes. + * + * + * @remarks + * Each theme consists of several complementary colors that are applied to different cells based on the banding settings. + * + * Reference: https://developers.google.com/apps-script/reference/spreadsheet/banding-theme + * + */ +export enum BandingTheme { + LIGHT_GRAY = 'light_gray', + LIGHT_CYAN = 'light_cyan', + GREEN = 'green', + YELLOW = 'yellow', + LIGHT_ORANGE = 'light_orange', + LIGHT_BLUE = 'light_blue', + TEAL = 'teal', + GREY = 'grey', + LIGHT_BROWN = 'light_brown', + LIGHT_GREEN = 'light_green', + INDIGO = 'indigo', + PINK = 'pink', + PURPLE = 'purple', + DARKEN_RED = 'darken_red', + DARKEN_GREEN = 'darken_green', + DARKEN_ORANGE = 'darken_orange', + DARKEN_BLUE = 'darken_blue', + DARKEN_YELLOW = 'darken_yellow', + RED = 'red', + ORANGE = 'orange', + GREEN_YELLOW = 'green_yellow', + CYAN = 'cyan', + BLUE = 'blue', + BROWN = 'brown', +} diff --git a/packages/sheets-plugin-alternating-colors/src/Command/ACTION_NAMES.ts b/packages/sheets-plugin-alternating-colors/src/Command/ACTION_NAMES.ts new file mode 100644 index 0000000000..3c3aebf936 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Command/ACTION_NAMES.ts @@ -0,0 +1,5 @@ +export enum ACTION_NAMES { + ADD_BANDING_ACTION = 'AddBandingAction', + SET_BANDING_ACTION = 'SetBandingAction', + DELETE_BANDING_ACTION = 'DeleteBandingAction', +} diff --git a/packages/sheets-plugin-alternating-colors/src/Command/Action/AddBandingAction.ts b/packages/sheets-plugin-alternating-colors/src/Command/Action/AddBandingAction.ts new file mode 100644 index 0000000000..cbd542ead4 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Command/Action/AddBandingAction.ts @@ -0,0 +1,66 @@ +import { ActionBase, ActionObservers, CONVERTOR_OPERATION, IActionData, WorkBook, WorkSheetConvertor } from '@univer/core'; +import { AlternatingColorsPlugin } from '../../AlternatingColorsPlugin'; +import { AddBanding } from '../../Apply/AddBanding'; +import { DeleteBanding } from '../../Apply/DeleteBanding'; +import { ALTERNATING_COLORS_PLUGIN_NAME } from '../../Const'; +import { IBandedRange } from '../../IBandedRange'; +import { ACTION_NAMES } from '../ACTION_NAMES'; +import { IDeleteBandingActionData } from './DeleteBandingAction'; + +export interface IAddBandingActionData extends IActionData { + bandedRange: IBandedRange; +} + +export class AddBandingAction extends ActionBase { + constructor(actionData: IAddBandingActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this._oldActionData = { + actionName: ACTION_NAMES.DELETE_BANDING_ACTION, + sheetId: actionData.sheetId, + bandedRangeId: actionData.bandedRange.bandedRangeId, + }; + this.validate(); + } + + do(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + const plugin = manager.getPluginByName(ALTERNATING_COLORS_PLUGIN_NAME); + + const { bandedRange } = this._doActionData; + this._observers.onActionDoObserver.notifyObservers(this); + AddBanding(plugin, bandedRange); + } + + redo(): void { + this.do(); + // no need store + } + + undo(): void { + const { bandedRangeId, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + const plugin = manager.getPluginByName(ALTERNATING_COLORS_PLUGIN_NAME); + this._observers.onActionUndoObserver.notifyObservers(this); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.ADD_BANDING_ACTION, + sheetId, + bandedRange: DeleteBanding(plugin, bandedRangeId), + }; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/Command/Action/DeleteBandingAction.ts b/packages/sheets-plugin-alternating-colors/src/Command/Action/DeleteBandingAction.ts new file mode 100644 index 0000000000..0b2e87d183 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Command/Action/DeleteBandingAction.ts @@ -0,0 +1,65 @@ +import { ActionBase, ActionObservers, CONVERTOR_OPERATION, IActionData, WorkBook, WorkSheetConvertor } from '@univer/core'; +import { AlternatingColorsPlugin } from '../../AlternatingColorsPlugin'; +import { AddBanding } from '../../Apply/AddBanding'; +import { DeleteBanding } from '../../Apply/DeleteBanding'; +import { ALTERNATING_COLORS_PLUGIN_NAME } from '../../Const'; +import { IBandedRange } from '../../IBandedRange'; +import { ACTION_NAMES } from '../ACTION_NAMES'; +import { IAddBandingActionData } from './AddBandingAction'; + +export interface IDeleteBandingActionData extends IActionData { + bandedRangeId: string; +} + +export class DeleteBandingAction extends ActionBase { + constructor(actionData: IDeleteBandingActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.ADD_BANDING_ACTION, + sheetId: actionData.sheetId, + bandedRange: this.do(), + }; + this.validate(); + } + + do(): IBandedRange { + const { bandedRangeId } = this._doActionData; + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + const plugin = manager.getPluginByName(ALTERNATING_COLORS_PLUGIN_NAME); + this._observers.onActionDoObserver.notifyObservers(this); + return DeleteBanding(plugin, bandedRangeId); + } + + redo(): void { + // update pre data + const { sheetId } = this._doActionData; + + this._oldActionData = { + sheetId, + actionName: ACTION_NAMES.ADD_BANDING_ACTION, + bandedRange: this.do(), + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.INSERT)], + }; + } + + undo(): void { + const { bandedRange } = this._oldActionData; + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + const plugin = manager.getPluginByName(ALTERNATING_COLORS_PLUGIN_NAME); + this._observers.onActionUndoObserver.notifyObservers(this); + AddBanding(plugin, bandedRange); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/Command/Action/SetBandingAction.ts b/packages/sheets-plugin-alternating-colors/src/Command/Action/SetBandingAction.ts new file mode 100644 index 0000000000..c660c20eeb --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Command/Action/SetBandingAction.ts @@ -0,0 +1,67 @@ +import { ActionBase, ActionObservers, CONVERTOR_OPERATION, IActionData, WorkBook, WorkSheetConvertor } from '@univer/core'; +import { AlternatingColorsPlugin } from '../../AlternatingColorsPlugin'; +import { SetBanding } from '../../Apply/SetBanding'; +import { ALTERNATING_COLORS_PLUGIN_NAME } from '../../Const'; +import { IBandedRange } from '../../IBandedRange'; +import { ACTION_NAMES } from '../ACTION_NAMES'; + +export interface ISetBandingActionData extends IActionData { + bandedRange: IBandedRange; +} + +export class SetBandingAction extends ActionBase { + constructor(actionData: ISetBandingActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_BANDING_ACTION, + sheetId: actionData.sheetId, + bandedRange: this.do(), + }; + this.validate(); + } + + do(): IBandedRange { + const { bandedRange } = this._doActionData; + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + const plugin = manager.getPluginByName(ALTERNATING_COLORS_PLUGIN_NAME); + this._observers.onActionDoObserver.notifyObservers(this); + return SetBanding(plugin, bandedRange); + } + + redo(): void { + // update pre data + const { bandedRange, sheetId } = this._doActionData; + this._oldActionData = { + actionName: ACTION_NAMES.SET_BANDING_ACTION, + sheetId, + bandedRange: this.do(), + }; + } + + undo(): void { + const { bandedRange, sheetId } = this._oldActionData; + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + const plugin = manager.getPluginByName(ALTERNATING_COLORS_PLUGIN_NAME); + this._observers.onActionUndoObserver.notifyObservers(this); + + // update current data + this._doActionData = { + actionName: ACTION_NAMES.SET_BANDING_ACTION, + sheetId, + bandedRange: SetBanding(plugin, bandedRange), + }; + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/Command/RegisterAction.ts b/packages/sheets-plugin-alternating-colors/src/Command/RegisterAction.ts new file mode 100644 index 0000000000..062d626fec --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Command/RegisterAction.ts @@ -0,0 +1,9 @@ +import { CommandManager } from '@univer/core'; +import { AddBandingAction } from './Action/AddBandingAction'; +import { DeleteBandingAction } from './Action/DeleteBandingAction'; +import { SetBandingAction } from './Action/SetBandingAction'; +import { ACTION_NAMES } from './ACTION_NAMES'; + +CommandManager.register(ACTION_NAMES.DELETE_BANDING_ACTION, DeleteBandingAction); +CommandManager.register(ACTION_NAMES.SET_BANDING_ACTION, SetBandingAction); +CommandManager.register(ACTION_NAMES.ADD_BANDING_ACTION, AddBandingAction); diff --git a/packages/sheets-plugin-alternating-colors/src/Command/index.ts b/packages/sheets-plugin-alternating-colors/src/Command/index.ts new file mode 100644 index 0000000000..1be3fa2cdb --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Command/index.ts @@ -0,0 +1,7 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +import './RegisterAction'; + +export * from './Action/AddBandingAction'; +export * from './Action/DeleteBandingAction'; +export * from './Action/SetBandingAction'; +// @endindex diff --git a/packages/sheets-plugin-alternating-colors/src/Const/CUSTOM_COLOR_MAP.ts b/packages/sheets-plugin-alternating-colors/src/Const/CUSTOM_COLOR_MAP.ts new file mode 100644 index 0000000000..a0e76a87ab --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Const/CUSTOM_COLOR_MAP.ts @@ -0,0 +1,6 @@ +export const CUSTOM_COLOR_MAP = { + headerColor: 'Header', + firstBandColor: 'Color 1', + secondBandColor: 'Color 2', + footerColor: 'Footer', +}; diff --git a/packages/sheets-plugin-alternating-colors/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-alternating-colors/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..fcc91c5cbe --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const ALTERNATING_COLORS_PLUGIN_NAME = 'alternatingColors'; diff --git a/packages/sheets-plugin-alternating-colors/src/Const/index.ts b/packages/sheets-plugin-alternating-colors/src/Const/index.ts new file mode 100644 index 0000000000..0749b6a9c9 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Const/index.ts @@ -0,0 +1,4 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './CUSTOM_COLOR_MAP'; +export * from './PLUGIN_NAME'; +// @endindex diff --git a/packages/sheets-plugin-alternating-colors/src/IBandedRange.ts b/packages/sheets-plugin-alternating-colors/src/IBandedRange.ts new file mode 100644 index 0000000000..c476536947 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/IBandedRange.ts @@ -0,0 +1,87 @@ +import { IRangeData } from '@univer/core'; +import { BandingTheme } from './BandingTheme'; +/** + * Properties referring a single dimension (either row or column). + * + * @remarks + * If both BandedRange.row_properties and BandedRange.column_properties are set, the fill colors are applied to cells according to the following rules: + * + * 1. headerColor and footerColor take priority over band colors. + * 2. firstBandColor takes priority over secondBandColor . + * 3. rowProperties takes priority over columnProperties . + * + * For example, the first row color takes priority over the first column color, but the first column color takes priority over the second row color. Similarly, the row header takes priority over the column header in the top left cell, but the column header takes priority over the first row color if the row header is not set. + */ +export interface IBandingProperties { + headerColor: string; + + firstBandColor: string; + + secondBandColor: string; + + footerColor: string; +} + +/** + * Properties of Banding + */ +export interface IBanding { + bandingTheme: BandingTheme | IBandingProperties; + showHeader: boolean; + showFooter: boolean; +} + +/** + * A banded (alternating colors) range in a sheet. + */ +export interface IBandedRange { + bandedRangeId: string; + sheetId: string; + rangeData: IRangeData; + rowProperties: IBanding; +} + +/** + * Default banded range + */ +export const DEFAULT_THEME_BANDED_RANGE: IBandedRange = { + bandedRangeId: 'banded-range-01', + sheetId: 'sheet-01', + rangeData: { + startRow: 0, + endRow: 1, + startColumn: 0, + endColumn: 1, + }, + rowProperties: { + bandingTheme: BandingTheme.BLUE, + showHeader: true, + showFooter: false, + }, +}; +/** + * Default banded range + */ +export const DEFAULT_PROPERTIES_BANDED_RANGE: IBandedRange = { + bandedRangeId: 'banded-range-01', + sheetId: 'sheet-01', + rangeData: { + startRow: 0, + endRow: 1, + startColumn: 0, + endColumn: 1, + }, + rowProperties: { + bandingTheme: { + headerColor: 'red', + + firstBandColor: 'lightgray', + + secondBandColor: 'lightgreen', + + footerColor: 'yellow', + }, + showHeader: true, + showFooter: false, + }, +}; diff --git a/packages/sheets-plugin-alternating-colors/src/IData/IAlternatingColors.ts b/packages/sheets-plugin-alternating-colors/src/IData/IAlternatingColors.ts new file mode 100644 index 0000000000..bd94a82b2c --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/IData/IAlternatingColors.ts @@ -0,0 +1,6 @@ +export type IConfig = { + // context: Context; +}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-alternating-colors/src/IData/index.ts b/packages/sheets-plugin-alternating-colors/src/IData/index.ts new file mode 100644 index 0000000000..0c0a2b4735 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/IData/index.ts @@ -0,0 +1,3 @@ +// @index('./*.ts', f => `export * from '${f.path}'`) +export * from './IAlternatingColors'; +// @endindex diff --git a/packages/sheets-plugin-alternating-colors/src/Locale/en.ts b/packages/sheets-plugin-alternating-colors/src/Locale/en.ts new file mode 100644 index 0000000000..57831f4df9 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Locale/en.ts @@ -0,0 +1,14 @@ +export default { + alternatingColors: { + alternatingColorsLabel: 'Alternating Colors', + applyToRange: 'Apply to range', + styles: 'Styles', + defaultStyles: 'Default styles', + customStyles: 'Custom styles', + removeAlternatingColors: 'Remove alternating colors', + headerColor: 'Header', + firstBandColor: 'Color 1', + secondBandColor: 'Color 2', + footerColor: 'Footer', + }, +}; diff --git a/packages/sheets-plugin-alternating-colors/src/Locale/index.ts b/packages/sheets-plugin-alternating-colors/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-alternating-colors/src/Locale/zh.ts b/packages/sheets-plugin-alternating-colors/src/Locale/zh.ts new file mode 100644 index 0000000000..2cf59e269f --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Locale/zh.ts @@ -0,0 +1,14 @@ +export default { + alternatingColors: { + alternatingColorsLabel: '交替颜色', + applyToRange: '应用范围', + styles: '样式', + defaultStyles: '默认样式', + customStyles: '自定义样式', + removeAlternatingColors: '移除交替颜色', + headerColor: '头部', + firstBandColor: '颜色 1', + secondBandColor: '颜色 2', + footerColor: '尾部', + }, +}; diff --git a/packages/sheets-plugin-alternating-colors/src/Types/index.d.ts b/packages/sheets-plugin-alternating-colors/src/Types/index.d.ts new file mode 100644 index 0000000000..806f320cb7 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-alternating-colors' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsButton.tsx b/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsButton.tsx new file mode 100644 index 0000000000..b9070c8523 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsButton.tsx @@ -0,0 +1,104 @@ +import { Component, IToolBarItemProps } from '@univer/base-component'; + +import { Context, Nullable, Observer, PLUGIN_NAMES, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; + +import { ALTERNATING_COLORS_PLUGIN_NAME } from '../Const'; +import { IProps } from '../IData/IAlternatingColors'; + +// Types for state +interface IState { + alternatingColors: IToolBarItemProps; +} + +export class AlternatingColorsButton extends Component { + private _localeObserver: Nullable>; + + initialize(props: IProps) { + const OrderIcon = this.getComponentRender().renderFunction('OrderIcon'); + + // this.SingleButton = renderComponent.renderFunction('SingleButton'); + + const alternatingColors: IToolBarItemProps = { + locale: ALTERNATING_COLORS_PLUGIN_NAME, + type: 'single', + label: , + show: true, + onClick: (item, context: Context) => { + context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.showSiderByName(ALTERNATING_COLORS_PLUGIN_NAME, true); + }, + }; + + this.state = { + alternatingColors, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.alternatingColors; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}Label`); + item.tooltipRight = locale.get(`${item.locale}RightLabel`); + + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + + return { + alternatingColors: item, + }; + }); + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const { alternatingColors } = state; + // Set Provider for entire Container + const SingleButton = this.getComponentRender().renderFunction('SingleButton'); + return ( + + ); + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsSide/AlternatingColorsSide.tsx b/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsSide/AlternatingColorsSide.tsx new file mode 100644 index 0000000000..67f05bab0c --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsSide/AlternatingColorsSide.tsx @@ -0,0 +1,485 @@ +import { closest, Component, createRef } from '@univer/base-component'; +import { IKeyValue, IRangeStringData, Nullable, Observer, PluginManager, PLUGIN_NAMES, Tools, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; + +import { AlternatingColorsPlugin } from '../../AlternatingColorsPlugin'; +import { BandingTheme } from '../../BandingTheme'; +import { BANDING_THEME_COLOR_LIST, BANDING_THEME_COLOR_MAP } from '../../BANDING_THEME_COLOR'; + +import { ALTERNATING_COLORS_PLUGIN_NAME } from '../../Const'; +import { IBandedRange, IBandingProperties } from '../../IBandedRange'; +import styles from './index.module.less'; + +/** + * type for props + */ +type IConfig = { + bandedRange: IBandedRange; + alternatingColorsPlugin: AlternatingColorsPlugin; +}; +type IPanelProps = { + config: IConfig; + style?: JSX.CSSProperties; +}; + +// Types for state +interface IState { + bandedRange: IBandedRange; + customStyles: Array; + colorSelect: IBandingProperties; + currentStyle: ICurrentStyle; + panelBodyHeight: string | number; + alternatingColors: IKeyValue; + rangeStringData: IRangeStringData; + showHeader: boolean; + showFooter: boolean; +} + +interface ICurrentStyle { + type?: 'theme' | 'custom'; + index?: number; +} + +export class AlternatingColorsSide extends Component { + private _localeObserver: Nullable>; + + alternatingColorsPlugin: AlternatingColorsPlugin; + + panelContainerRef = createRef(); + + panelBodyRef = createRef(); + + initialize(props: IPanelProps) { + const { bandedRange, alternatingColorsPlugin } = props.config; + + this.alternatingColorsPlugin = alternatingColorsPlugin; + + let colorSelect: IBandingProperties; + let currentStyle: ICurrentStyle = {}; + let customStyles: Array = ['add']; + + // get colorSelect from user setting + const banding = bandedRange.rowProperties; + if (banding) { + // string means BandingThemeType + if (typeof banding.bandingTheme === 'string') { + colorSelect = BANDING_THEME_COLOR_MAP[banding.bandingTheme]; + + currentStyle.type = 'theme'; + + // find index in theme color list + currentStyle.index = BANDING_THEME_COLOR_LIST.findIndex((theme) => banding.bandingTheme === theme); + } + // or a object as custom colors + else { + colorSelect = banding.bandingTheme; + + // we don't store all custom styles to server, just init colorSelect as customStyles + customStyles.push(colorSelect); + + // active current custom color + currentStyle = { + type: 'custom', + index: 1, + }; + } + } + // init default colorSelect + else { + currentStyle = { + type: 'theme', + index: 0, + }; + + colorSelect = BANDING_THEME_COLOR_MAP[BandingTheme.GREY]; + } + + this.state = { + bandedRange, + customStyles, + colorSelect, + currentStyle, + panelBodyHeight: 0, + alternatingColors: {}, + rangeStringData: WorkBook.rangeDataToRangeStringData(bandedRange.rangeData), + showHeader: banding.showHeader, + showFooter: banding.showFooter, + }; + + this.alternatingColorsPlugin.addRowBanding(bandedRange); + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * set scroll height + */ + componentDidMount() { + const dom = this.panelContainerRef.current?.base; + if (dom) { + const panelHeaderHeight = dom.querySelector(`.${styles.panelHeader}`)?.clientHeight; + const panelFooterHeight = dom.querySelector(`.${styles.panelFooter}`)?.clientHeight; + + // this.panelBodyRef.current?.styles.height = panelBodyHeight // not working + if (panelHeaderHeight !== undefined && panelFooterHeight !== undefined) { + this.setState({ + panelBodyHeight: `calc(100% - ${panelHeaderHeight + panelFooterHeight}px)`, + }); + } + } + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + + this.setState((prevState: IState) => { + const alternatingColors: IKeyValue = locale.getObject(ALTERNATING_COLORS_PLUGIN_NAME); + alternatingColors.button = locale.getObject(`button`); + return { + alternatingColors, + }; + }); + } + + /** + * close panel + */ + handleClosePanel() { + const manage: PluginManager = this._context.getPluginManager(); + + manage.getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.showSiderByName(ALTERNATING_COLORS_PLUGIN_NAME, false); + } + + /** + * choose a theme + */ + handleDefaultStyleClick(e: MouseEvent) { + // get current active item index in item list + const currentItem = closest(e.target as HTMLElement, `.${styles.themeColorList} .${styles.themeColorItem}`); + + // click item gap + if (!currentItem) { + return; + } + + const dom = this.panelContainerRef.current?.base; + const nodes = Array.prototype.slice.call(dom.querySelectorAll(`.${styles.themeColorList} .${styles.themeColorItem}`)); + const index = nodes.indexOf(currentItem); + + this.setState((preState: IState) => { + let { colorSelect, currentStyle } = preState; + currentStyle = { + type: 'theme', + index, + }; + + // update current select + colorSelect = Tools.deepClone(BANDING_THEME_COLOR_MAP[BANDING_THEME_COLOR_LIST[index]]); + + return { + colorSelect, + currentStyle, + }; + }); + + this.setBanding(); + } + + /** + * choose a custom style + */ + handleCustomStyleClick(e: MouseEvent) { + // get current active item index in item list + const currentItem = closest(e.target as HTMLElement, `.${styles.customColorList} .${styles.themeColorItem}`); + + // add button don't need active + if (currentItem?.textContent === '+') { + this.addCustomItem(); + return; + } + const dom = this.panelContainerRef.current?.base; + const nodes = Array.prototype.slice.call(dom.querySelectorAll(`.${styles.customColorList} .${styles.themeColorItem}`)); + const index = nodes.indexOf(currentItem); + this.setState((preState: IState) => { + let { customStyles, colorSelect, currentStyle } = preState; + currentStyle = { + type: 'custom', + index, + }; + + // update current select + colorSelect = customStyles[index] as IBandingProperties; + + return { + colorSelect, + currentStyle, + }; + }); + + this.setBanding(); + } + + /** + * add new custom color item + */ + addCustomItem(colorBanding?: IBandingProperties) { + this.setState((preState: IState) => { + let { customStyles, colorSelect, currentStyle } = preState; + + // click default style,then change color,we need changed color to set custom style + colorBanding ? customStyles.push(Tools.deepClone(colorBanding)) : customStyles.push(Tools.deepClone(colorSelect)); + + // limit max length as 12 + if (customStyles.length === 13) { + // remove add button + customStyles.shift(); + } + + // update current item as active + currentStyle = { + type: 'custom', + index: customStyles.length - 1, + }; + + // update current select + colorSelect = customStyles[customStyles.length - 1] as IBandingProperties; + + return { + colorSelect, + currentStyle, + }; + }); + + this.setBanding(); + } + + /** + * choose custom color + */ + handleColorSelect(key: string, color: string) { + let { customStyles, colorSelect, currentStyle } = this.state; + + const changeColor = {}; + changeColor[key] = color; + colorSelect = Object.assign(colorSelect, changeColor); + + // change color by theme color + if (currentStyle.type === 'theme') { + this.addCustomItem(colorSelect); + } else { + this.setState({ + colorSelect, + }); + } + + this.setBanding(); + } + + /** + * cancel choose custom color + */ + handleColorCancel() { + // console.log('handleColorCancel', e, i, a); + } + + // remove banding + setBanding() { + const { currentStyle, colorSelect, bandedRange } = this.state; + let bandingTheme; + if (currentStyle.type === 'theme') { + bandingTheme = BANDING_THEME_COLOR_LIST[`${currentStyle.index}`]; + } else { + bandingTheme = colorSelect; + } + + const newBandedRange = Tools.deepMerge(bandedRange, { + rowProperties: { + bandingTheme, + }, + }); + + // 1. 每设置一次,就调用一次API + + // 确定时将结果状态保存到bandedRange + this.alternatingColorsPlugin.getBandingById(bandedRange.bandedRangeId)?.setRowBanding(newBandedRange); + } + + handleAlternatingColorsCancel() { + const { bandedRange } = this.state; + + // 取消的话恢复到bandedRange + this.alternatingColorsPlugin.getBandingById(bandedRange.bandedRangeId)?.setRowBanding(bandedRange); + + this.handleClosePanel(); + } + + handleAlternatingColorsConfirm() { + this.setBanding(); + this.handleClosePanel(); + } + + /** + * Render the component's HTML + * + * @returns + */ + render(props: IPanelProps, state: IState) { + const { customStyles, colorSelect, currentStyle, panelBodyHeight, alternatingColors, rangeStringData, showHeader, showFooter } = state; + const { style } = props; + // Set Provider for entire Container + const SiderModal = this.getComponentRender().renderFunction('SiderModal'); + const Button = this.getComponentRender().renderFunction('Button'); + const CellRangeModal = this.getComponentRender().renderFunction('CellRangeModal'); + const Checkbox = this.getComponentRender().renderFunction('Checkbox'); + const ColorPickerCircleButton = this.getComponentRender().renderFunction('ColorPickerCircleButton'); + const DeleteIcon = this.getComponentRender().renderFunction('DeleteIcon'); + return ( + +
        + + + + {alternatingColors.removeAlternatingColors} +
        + + } + pluginName={ALTERNATING_COLORS_PLUGIN_NAME} + style={{ ...style }} + > +
        +
        + {/*
        */} +
        +

        {alternatingColors.applyToRange}

        +
        + +
        +
        +
        +

        {alternatingColors.styles}

        +
        + this.setState({ showHeader: !showHeader })}> + this.setState({ showFooter: !showFooter })}> +
        + +

        {alternatingColors.defaultStyles}

        +
        + {BANDING_THEME_COLOR_LIST.map((theme, index) => { + // theme color list + const colors = BANDING_THEME_COLOR_MAP[theme]; + return ( +
        +
        +
        +
        +
        +
        +
        +
        + ); + })} +
        +

        {alternatingColors.customStyles}

        +
        +
        + {customStyles.map((colors, index) => + typeof colors === 'string' ? ( + // add custom color button +
        +
        +
        +
        +
        +
        + ) : ( + // custom color list +
        +
        +
        +
        +
        +
        +
        +
        + ) + )} +
        + +
        + {Object.keys(colorSelect).map((key) => ( +
        + + {alternatingColors[key]} + + + + +
        + ))} +
        +
        +
        + +
        + + +
        +
        +
        + + ); + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsSide/index.module.less b/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsSide/index.module.less new file mode 100644 index 0000000000..f0a2e88661 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/UI/AlternatingColorsSide/index.module.less @@ -0,0 +1,136 @@ +@import '@univer/style-universheet/assets/css/skin/base.module.less'; +.alternating-colors-side { + .section { + padding-top: @padding-base; + h3 { + margin-bottom: @margin-base; + } + } + + .panel-body { + padding: 0 @padding-base; + position: relative; + + .panel-body-inner { + position: absolute; + overflow-y: auto; + height: 100%; + .styles-checkbox { + display: flex; + > label { + margin: 0 @margin-base @margin-base 0; + } + } + + .theme-color-list { + display: flex; + flex-wrap: wrap; + } + + .custom-color-list { + display: flex; + flex-wrap: wrap; + border: 1px solid var(--border-color-base); + border-radius: var(--border-radius-base); + .custom-style-add { + padding: @padding-base; + display: flex; + justify-content: center; + align-items: center; + + height: 4em; + line-height: 4em; + :hover { + cursor: pointer; + } + } + } + + // 6 items per line + .theme-color-item { + width: 15.5%; + padding: 0.1em; + border: 2px solid transparent; + box-sizing: border-box; + + :hover { + cursor: pointer; + } + .theme-color-inner-item { + border: 1px solid var(--border-color-base); + box-sizing: border-box; + + &:hover { + border-color: var(--border-color-base-hover); + } + > div { + width: 100%; + line-height: 1em; + text-align: center; + color: @gray-8; + } + } + } + .theme-color-item-active { + border-color: #726efe; + } + + .color-select { + display: flex; + flex-direction: column; + .color-select-item { + width: 100%; + display: flex; + + .color-select-banner { + width: 65%; + display: flex; + justify-content: center; + align-items: center; + height: 2em; + border-radius: var(--border-radius-base); + } + + .color-select-button { + &:hover { + cursor: pointer; + background-color: @gray-4; + } + } + } + } + + .bottom-buttons { + float: right; + button { + padding: 0 1em; + margin-left: 1em; + } + } + } + } + .panel-footer { + .section { + display: flex; + justify-content: center; + border-top: 1px solid var(--border-color-base); + padding: 0.9em @padding-base; + margin: 0.9em @padding-base; + &:hover { + cursor: pointer; + background-color: @gray-4; + } + .icon-container { + font-size: 1.6em; + :hover { + cursor: pointer; + } + } + .text-container { + line-height: 1.8em; + display: inline-block; + height: 1.8em; + } + } + } +} diff --git a/packages/sheets-plugin-alternating-colors/src/View/Event.ts b/packages/sheets-plugin-alternating-colors/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-alternating-colors/src/View/ObserverList.ts b/packages/sheets-plugin-alternating-colors/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-alternating-colors/src/View/Render.ts b/packages/sheets-plugin-alternating-colors/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-alternating-colors/src/View/index.ts b/packages/sheets-plugin-alternating-colors/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-alternating-colors/src/index.ts b/packages/sheets-plugin-alternating-colors/src/index.ts new file mode 100644 index 0000000000..495f93bd9b --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/index.ts @@ -0,0 +1,3 @@ +import { AlternatingColorsPlugin } from './AlternatingColorsPlugin'; + +export { AlternatingColorsPlugin }; diff --git a/packages/sheets-plugin-alternating-colors/src/main.tsx b/packages/sheets-plugin-alternating-colors/src/main.tsx new file mode 100644 index 0000000000..b385f9fcc4 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/main.tsx @@ -0,0 +1,47 @@ +import { defaultWorkbookData, UniverSheet } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { UniverComponentSheet } from '@univer/style-universheet'; +import { AlternatingColorsPlugin } from './AlternatingColorsPlugin'; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); +univerSheetUp.installPlugin(new UniverComponentSheet()); +univerSheetUp.installPlugin(new SpreadsheetPlugin(uiDefaultConfigUp)); + +univerSheetUp.installPlugin(new AlternatingColorsPlugin({ value: [] })); diff --git a/packages/sheets-plugin-alternating-colors/src/preact.d.ts b/packages/sheets-plugin-alternating-colors/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-alternating-colors/test/Banding.test.ts b/packages/sheets-plugin-alternating-colors/test/Banding.test.ts new file mode 100644 index 0000000000..893dcb7911 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/test/Banding.test.ts @@ -0,0 +1,70 @@ +/** + * @jest-environment jsdom + */ +import { UniverSheet } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { AlternatingColorsPlugin } from '../src'; +// TODO 补充单元测试 +test('Test Banding', () => { + const univerSheet = UniverSheet.newInstance({ + locale: 'zh', + creator: '', + name: '', + skin: 'default', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + styles: { + 1: { + color: 'blue', + fontFamily: 'Arial', + fontSize: 12, + background: '#ff0000', + }, + 2: { + color: 'green', + fontSize: 14, + background: '#0000ff', + }, + }, + sheets: [ + { + cellData: { + 0: { + 0: { + s: 1, + v: 1, + m: 1, + }, + 1: { + s: 2, + v: 2, + m: 2, + }, + }, + }, + }, + ], + }); + + const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + // layout: 'auto', + layout: { + outerLeft: false, + outerRight: true, + innerLeft: false, + innerRight: false, + toolBar: true, + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, + }; + univerSheet.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); + univerSheet.installPlugin(new AlternatingColorsPlugin()); +}); diff --git a/packages/sheets-plugin-alternating-colors/test/Banding/Action/AddBandingAction.test.ts b/packages/sheets-plugin-alternating-colors/test/Banding/Action/AddBandingAction.test.ts new file mode 100644 index 0000000000..5b41ff4a0b --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/test/Banding/Action/AddBandingAction.test.ts @@ -0,0 +1,22 @@ +/** + * @jest-environment jsdom + */ +import { ACTION_NAMES } from '../../../../src'; +import { ActionObservers } from '../../../../src/Command'; +import { AddBandingAction } from '../../../../src/Module/Banding/Action'; +import { TestInit } from '../../../ContainerStartUp'; +import { bandedRange } from '../Banding.test'; + +test('Add Banding Action Test', () => { + const { workbook, worksheet } = TestInit(); + + new AddBandingAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.ADD_BANDING_ACTION, + bandedRange, + }, + workbook, + new ActionObservers() + ); +}); diff --git a/packages/sheets-plugin-alternating-colors/test/Banding/Action/DeleteBandingAction.test.ts b/packages/sheets-plugin-alternating-colors/test/Banding/Action/DeleteBandingAction.test.ts new file mode 100644 index 0000000000..51334b0d3c --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/test/Banding/Action/DeleteBandingAction.test.ts @@ -0,0 +1,21 @@ +/** + * @jest-environment jsdom + */ +import { ACTION_NAMES } from '../../../../src'; +import { ActionObservers } from '../../../../src/Command'; +import { DeleteBandingAction } from '../../../../src/Module/Banding/Action'; +import { TestInit } from '../../../ContainerStartUp'; +import { bandedRange } from '../Banding.test'; + +test('Delete Banding Action Test', () => { + const { workbook, worksheet } = TestInit({ bandedRanges: [bandedRange] }); + new DeleteBandingAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.DELETE_BANDING_ACTION, + bandedRangeId: bandedRange.bandedRangeId, + }, + workbook, + new ActionObservers() + ); +}); diff --git a/packages/sheets-plugin-alternating-colors/test/Banding/Action/SetBandingAction.test.ts b/packages/sheets-plugin-alternating-colors/test/Banding/Action/SetBandingAction.test.ts new file mode 100644 index 0000000000..92fcd858bb --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/test/Banding/Action/SetBandingAction.test.ts @@ -0,0 +1,22 @@ +/** + * @jest-environment jsdom + */ +import { ACTION_NAMES } from '../../../../src'; +import { ActionObservers } from '../../../../src/Command'; +import { SetBandingAction } from '../../../../src/Module/Banding/Action'; +import { TestInit } from '../../../ContainerStartUp'; +import { bandedRange, newBandedRange } from '../Banding.test'; + +test('Set Banding Action Test', () => { + const { workbook, worksheet } = TestInit({ bandedRanges: [bandedRange] }); + + new SetBandingAction( + { + sheetId: worksheet.getSheetId(), + actionName: ACTION_NAMES.SET_BANDING_ACTION, + bandedRange: newBandedRange, + }, + workbook, + new ActionObservers() + ); +}); diff --git a/packages/sheets-plugin-alternating-colors/test/Banding/Banding.test.ts b/packages/sheets-plugin-alternating-colors/test/Banding/Banding.test.ts new file mode 100644 index 0000000000..5a29d56f18 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/test/Banding/Banding.test.ts @@ -0,0 +1,66 @@ +/** + * @jest-environment jsdom + */ +import { + Banding, + BandingTheme, + DEFAULT_BANDED_RANGE, + IBandedRange, +} from '../../../src'; +import { TestInit } from '../../ContainerStartUp'; + +export const bandedRange = DEFAULT_BANDED_RANGE; + +export const newBandedRange: IBandedRange = { + ...DEFAULT_BANDED_RANGE, + rowProperties: { + bandingTheme: BandingTheme.BROWN, + showHeader: true, + showFooter: false, + }, +}; + +test('Test Banding', () => { + const { workbook, worksheet } = TestInit(); + + const banding = new Banding(worksheet); + + // add + banding.addRowBanding(bandedRange); + + expect(worksheet.getConfig().bandedRanges).toEqual([bandedRange]); + + worksheet.getCommandManager().undo(); + + expect(worksheet.getConfig().bandedRanges).toEqual([]); + + worksheet.getCommandManager().redo(); + + expect(worksheet.getConfig().bandedRanges).toEqual([bandedRange]); + + // set + banding.setRowBanding(newBandedRange); + + expect(worksheet.getConfig().bandedRanges).toEqual([newBandedRange]); + + worksheet.getCommandManager().undo(); + + expect(worksheet.getConfig().bandedRanges).toEqual([bandedRange]); + + worksheet.getCommandManager().redo(); + + expect(worksheet.getConfig().bandedRanges).toEqual([newBandedRange]); + + // remove + banding.remove(newBandedRange.bandedRangeId); + + expect(worksheet.getConfig().bandedRanges).toEqual([]); + + worksheet.getCommandManager().undo(); + + expect(worksheet.getConfig().bandedRanges).toEqual([newBandedRange]); + + worksheet.getCommandManager().redo(); + + expect(worksheet.getConfig().bandedRanges).toEqual([]); +}); diff --git a/packages/sheets-plugin-alternating-colors/tsconfig.json b/packages/sheets-plugin-alternating-colors/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-alternating-colors/tsconfig.ref.json b/packages/sheets-plugin-alternating-colors/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-alternating-colors/tsconfig.test.json b/packages/sheets-plugin-alternating-colors/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-alternating-colors/vite.config.ts b/packages/sheets-plugin-alternating-colors/vite.config.ts new file mode 100644 index 0000000000..b5419bdd04 --- /dev/null +++ b/packages/sheets-plugin-alternating-colors/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginAlternatingColors', + formats: ['es', 'umd'], + fileName: 'univer-sheets--plugin-alternating-colors', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-clipboard/README-zh.md b/packages/sheets-plugin-clipboard/README-zh.md new file mode 100644 index 0000000000..81fc2b8417 --- /dev/null +++ b/packages/sheets-plugin-clipboard/README-zh.md @@ -0,0 +1,98 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet 官方 UI 组件库 + +## 特性 + +- 纯 tsx 组件,可方便集成到 vue/react +- 按需引入,不用担心影响打包体积 + +## 用法 + +### 安装 + +```bash +npm i @univer/style-universheet +``` + +### 使用 + +```js +impport baseUI from '@univer/style-universheet' +universheet.install(baseUI) +universheet.create({ + plugins:['style-universheet'] +}) +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 7 + +### 安装 + +``` +npm i +``` + +### 开发 + +``` +# 当前目录 ./packages/style-universheet/ +npm run dev + +# 或者项目根目录 ./ +npm run dev -w @univer/style-universheet +``` + +### 打包 + +``` +# 当前目录 ./packages/style-universheet/ +npm run build + +# 或者根目录 ./ +npm run build -w @univer/style-universheet +``` + +## 组件库文档 + +使用[Docusaurus 2](https://docusaurus.io/)构建 + +### 安装 + +```console +# 根目录 +npm i +``` + +### 开发 + +```console +# 当前目录 +npm run start + +# 或者根目录 +npm run start -w @univer/style-universheet +``` + +此命令启动本地开发服务器并打开浏览器窗口。 大多数更改都会实时刷新,而无需重新启动服务器。 + +### 打包 + +```console +# 当前目录 +npm run build:doc + +# 或者根目录 +npm run build:doc -w @univer/style-universheet +``` + +此命令将静态内容生成到 `build` 目录中,并且可以使用任何静态内容托管服务来提供。 diff --git a/packages/sheets-plugin-clipboard/README.md b/packages/sheets-plugin-clipboard/README.md new file mode 100644 index 0000000000..7c47b06298 --- /dev/null +++ b/packages/sheets-plugin-clipboard/README.md @@ -0,0 +1,98 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet official UI component library + +## Features + +- Pure tsx components, which can be easily integrated into vue/react +- Import on demand, don’t worry about affecting the package size + +## Usage + +### Install + +```bash +npm i @univer/style-universheet +``` + +### Import + +```js +impport baseUI from'@univer/style-universheet' +universheet.install(baseUI) +universheet.create({ + plugins:['style-universheet'] +}) +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 7 + +### Installation + +``` +npm i +``` + +### Development + +``` +# Current directory ./packages/style-universheet/ +npm run dev + +# Or project root directory ./ +npm run dev -w @univer/style-universheet +``` + +### Package + +``` +# Current directory ./packages/style-universheet/ +npm run build + +# Or root directory ./ +npm run build -w @univer/style-universheet +``` + +## Doc Website + +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +### Installation + +```console +# root folder +npm i +``` + +### Development + +```console +# style-universheet folder +npm run start + +# or root folder +npm run start -w @univer/style-universheet +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### Build + +```console +# style-universheet folder +npm run build:doc + +# or root folder +npm run build:doc -w @univer/style-universheet +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. diff --git a/packages/sheets-plugin-clipboard/index.html b/packages/sheets-plugin-clipboard/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-clipboard/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-clipboard/jest.config.js b/packages/sheets-plugin-clipboard/jest.config.js new file mode 100644 index 0000000000..ef2425ea8f --- /dev/null +++ b/packages/sheets-plugin-clipboard/jest.config.js @@ -0,0 +1,16 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + coverageReporters: ['json', 'html'], + collectCoverage: true, + testEnvironment: 'jsdom', + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.(js|jsx)$': 'babel-jest', + }, + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-clipboard/package.json b/packages/sheets-plugin-clipboard/package.json new file mode 100644 index 0000000000..25ae0a69ac --- /dev/null +++ b/packages/sheets-plugin-clipboard/package.json @@ -0,0 +1,52 @@ +{ + "name": "@univer/sheets-plugin-clipboard", + "version": "0.0.1", + "description": "UniverSheet normal plugin UI manager", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "2.0.1", + "@types/jest": "27.0.3", + "@vitejs/plugin-legacy": "1.5.1", + "jest": "29.0.0", + "jest-environment-jsdom": "^29.0.3", + "less": "4.1.1", + "ts-jest": "29.0.1", + "typescript": "4.7.2", + "vite": "2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-conditional-format": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "nanoid": "^4.0.0", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-clipboard/src/Action/AddFilterAction.ts b/packages/sheets-plugin-clipboard/src/Action/AddFilterAction.ts new file mode 100644 index 0000000000..f5b0847fed --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Action/AddFilterAction.ts @@ -0,0 +1,46 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { IFilter } from '../Domain'; +import { ACTION_NAMES, FILTER_PLUGIN } from '../Const'; +import { AddFilter, RemoveFilter } from '../Apply'; +import { IRemoveFilterActionData } from './RemoveFilterAction'; + +export interface IAddFilterActionData extends IActionData { + filter: Nullable; +} + +export class AddFilterAction extends ActionBase { + constructor(actionData: IAddFilterActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + sheetId: this._doActionData.sheetId, + actionName: ACTION_NAMES.ADD_FILTER_ACTION, + }; + this.do(); + this.validate(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilter(manager.getRequirePluginByName(FILTER_PLUGIN), this._doActionData.sheetId, this._doActionData.filter); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + RemoveFilter(manager.getRequirePluginByName(FILTER_PLUGIN), this._oldActionData.sheetId); + } + + do(): void { + this.redo(); + } + + validate(): boolean { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/sheets-plugin-clipboard/src/Action/AddFilterCriteriaAction.ts b/packages/sheets-plugin-clipboard/src/Action/AddFilterCriteriaAction.ts new file mode 100644 index 0000000000..d81ef1080c --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Action/AddFilterCriteriaAction.ts @@ -0,0 +1,49 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { IFilterCriteriaColumn } from '../Domain/FilterCriteriaColumn'; +import { ACTION_NAMES } from '../Const/ACTION_NAME'; +import { FILTER_PLUGIN } from '../Const'; +import { AddFilterCriteria, RemoveFilterCriteria } from '../Apply'; +import { IRemoveFilterCriteriaAction } from './RemoveFilterCriteriaAction'; + +export interface IAddFilterCriteriaActionData extends IActionData { + columnPosition: number; + criteriaColumn: Nullable; +} + +export class AddFilterCriteriaAction extends ActionBase { + constructor(actionData: IAddFilterCriteriaActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + columnPosition: actionData.columnPosition, + sheetId: actionData.sheetId, + actionName: ACTION_NAMES.ADD_FILTER_CRITERIA_ACTION, + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN), this._doActionData.sheetId, this._doActionData.criteriaColumn); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + RemoveFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN), this._oldActionData.sheetId, this._oldActionData.columnPosition); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-clipboard/src/Action/RemoveFilterAction.ts b/packages/sheets-plugin-clipboard/src/Action/RemoveFilterAction.ts new file mode 100644 index 0000000000..d671a1c5de --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Action/RemoveFilterAction.ts @@ -0,0 +1,44 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { AddFilter, RemoveFilter } from '../Apply'; +import { ACTION_NAMES, FILTER_PLUGIN } from '../Const'; +import { IFilter } from '../Domain'; +import { IAddFilterActionData } from './AddFilterAction'; + +export interface IRemoveFilterActionData extends IActionData {} + +export class RemoveFilterAction extends ActionBase { + constructor(actionData: IRemoveFilterActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + sheetId: this._doActionData.sheetId, + actionName: ACTION_NAMES.REMOVE_FILTER_ACTION, + filter: this.do(), + }; + this.validate(); + } + + redo(): Nullable { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + return RemoveFilter(manager.getRequirePluginByName(FILTER_PLUGIN), this._doActionData.sheetId); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilter(manager.getRequirePluginByName(FILTER_PLUGIN), this._doActionData.sheetId, this._oldActionData.filter); + } + + do(): Nullable { + return this.redo(); + } + + validate(): boolean { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/sheets-plugin-clipboard/src/Action/RemoveFilterCriteriaAction.ts b/packages/sheets-plugin-clipboard/src/Action/RemoveFilterCriteriaAction.ts new file mode 100644 index 0000000000..7e5b4d5c88 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Action/RemoveFilterCriteriaAction.ts @@ -0,0 +1,47 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { AddFilterCriteria, RemoveFilterCriteria } from '../Apply'; +import { ACTION_NAMES, FILTER_PLUGIN } from '../Const'; +import { IFilterCriteriaColumn } from '../Domain'; +import { IAddFilterCriteriaActionData } from './AddFilterCriteriaAction'; + +export interface IRemoveFilterCriteriaAction extends IActionData { + columnPosition: number; +} + +export class RemoveFilterCriteriaAction extends ActionBase { + constructor(actionData: IRemoveFilterCriteriaAction, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + actionName: ACTION_NAMES.ADD_FILTER_CRITERIA_ACTION, + sheetId: actionData.sheetId, + columnPosition: actionData.columnPosition, + criteriaColumn: this.do(), + }; + this.validate(); + } + + do(): Nullable { + return this.redo(); + } + + redo(): Nullable { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + return RemoveFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN), this._oldActionData.sheetId, this._doActionData.columnPosition); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN), this._doActionData.sheetId, this._oldActionData.criteriaColumn); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-clipboard/src/Action/index.ts b/packages/sheets-plugin-clipboard/src/Action/index.ts new file mode 100644 index 0000000000..58d3e905df --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Action/index.ts @@ -0,0 +1,4 @@ +export * from './IAction'; +export * from './IActionSet'; +export * from './ActionFilter'; +export * from './SetFilterCriteriaAction'; diff --git a/packages/sheets-plugin-clipboard/src/Apply/AddFilter.ts b/packages/sheets-plugin-clipboard/src/Apply/AddFilter.ts new file mode 100644 index 0000000000..d74e3bd208 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Apply/AddFilter.ts @@ -0,0 +1,10 @@ +import { Nullable } from '@univer/core'; +import { Filter, IFilter } from '../Domain'; +import { ClipboardPlugin } from '../ClipboardPlugin'; + +export function AddFilter(plugin: ClipboardPlugin, sheetId: string, filter: Nullable) { + const filterList = plugin.getFilterList(); + if (filter) { + filterList.addFilter(sheetId, Filter.fromSequence(filter).withContext(plugin.getContext())); + } +} diff --git a/packages/sheets-plugin-clipboard/src/Apply/AddFilterCriteria.ts b/packages/sheets-plugin-clipboard/src/Apply/AddFilterCriteria.ts new file mode 100644 index 0000000000..e095c564d8 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Apply/AddFilterCriteria.ts @@ -0,0 +1,12 @@ +import { Nullable } from '@univer/core'; +import { FilterCriteriaColumn, IFilterCriteriaColumn } from '../Domain'; +import { ClipboardPlugin } from '../ClipboardPlugin'; + +export function AddFilterCriteria(plugin: ClipboardPlugin, sheetId: string, criteriaColumn: Nullable) { + const filterList = plugin.getFilterList(); + const filterModel = filterList.getFilter(sheetId); + const criteriaModel = filterModel.getGroupModel(); + if (criteriaColumn) { + criteriaModel[criteriaColumn.column] = FilterCriteriaColumn.fromSequence(criteriaColumn).withContext(plugin.getContext()); + } +} diff --git a/packages/sheets-plugin-clipboard/src/Apply/RemoveFilter.ts b/packages/sheets-plugin-clipboard/src/Apply/RemoveFilter.ts new file mode 100644 index 0000000000..4accac003b --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Apply/RemoveFilter.ts @@ -0,0 +1,11 @@ +import { Nullable } from '@univer/core'; +import { ClipboardPlugin } from '../ClipboardPlugin'; +import { IFilter } from '../Domain'; + +export function RemoveFilter(plugin: ClipboardPlugin, sheetId: string): Nullable { + const filterList = plugin.getFilterList(); + const filterModel = filterList.getGroupModel(); + const filter = filterModel[sheetId]; + delete filterModel[sheetId]; + return filter.toSequence(); +} diff --git a/packages/sheets-plugin-clipboard/src/Apply/RemoveFilterCriteria.ts b/packages/sheets-plugin-clipboard/src/Apply/RemoveFilterCriteria.ts new file mode 100644 index 0000000000..22f83627e9 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Apply/RemoveFilterCriteria.ts @@ -0,0 +1,12 @@ +import { Nullable } from '@univer/core'; +import { ClipboardPlugin } from '../ClipboardPlugin'; +import { IFilterCriteriaColumn } from '../Domain'; + +export function RemoveFilterCriteria(plugin: ClipboardPlugin, sheetId: string, column: number): Nullable { + const filterList = plugin.getFilterList(); + const filterModel = filterList.getFilter(sheetId); + const criteriaModel = filterModel.getGroupModel(); + const criteria = criteriaModel[column]; + delete criteriaModel[column]; + return criteria.toSequence(); +} diff --git a/packages/sheets-plugin-clipboard/src/Apply/index.ts b/packages/sheets-plugin-clipboard/src/Apply/index.ts new file mode 100644 index 0000000000..c11e36e997 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Apply/index.ts @@ -0,0 +1,4 @@ +export * from './AddFilterCriteria'; +export * from './RemoveFilterCriteria'; +export * from './AddFilter'; +export * from './RemoveFilter'; diff --git a/packages/sheets-plugin-clipboard/src/ClipboardPlugin.tsx b/packages/sheets-plugin-clipboard/src/ClipboardPlugin.tsx new file mode 100644 index 0000000000..b5121d770e --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/ClipboardPlugin.tsx @@ -0,0 +1,45 @@ +import { Context, Plugin } from '@univer/core'; +import { en, zh } from './Locale'; +import { CLIPBOARD_PLUGIN } from './Const'; +import { Copy, UniverCopy, Paste, UniverPaste } from './Domain'; + +interface CopyResolver { + name: string; + resolver: Copy; +} + +interface PasteResolver { + name: string; + resolver: Paste; +} + +export class ClipboardPlugin extends Plugin { + private _copyResolvers: CopyResolver[] = []; + + private _pasteResolvers: PasteResolver[] = []; + + constructor() { + super(CLIPBOARD_PLUGIN); + } + + onMounted(context: Context): void { + context.getLocale().load({ + en, + zh, + }); + this.installPasteResolver({ name: 'univerPaste', resolver: new UniverPaste(context) }); + this.installCopyResolver({ name: 'univerCopy', resolver: new UniverCopy(context) }); + } + + installCopyResolver(resolver: CopyResolver) { + const index = this._copyResolvers.findIndex((item) => item.name === resolver.name); + if (index > -1) return false; + this._copyResolvers.push(resolver); + } + + installPasteResolver(resolver: PasteResolver) { + const index = this._pasteResolvers.findIndex((item) => item.name === resolver.name); + if (index > -1) return false; + this._pasteResolvers.push(resolver); + } +} diff --git a/packages/sheets-plugin-clipboard/src/Const/ACTION_NAME.ts b/packages/sheets-plugin-clipboard/src/Const/ACTION_NAME.ts new file mode 100644 index 0000000000..356873ce46 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Const/ACTION_NAME.ts @@ -0,0 +1,9 @@ +export enum ACTION_NAMES { + ADD_FILTER_ACTION = 'AddFilterAction', + REMOVE_FILTER_ACTION = 'RemoveFilterAction', + + ADD_FILTER_CRITERIA_ACTION = 'AddFilterCriteriaAction', + REMOVE_FILTER_CRITERIA_ACTION = 'RemoveFilterCriteriaAction', + + SET_FILTER_CRITERIA_ACTION = 'setFilterCriteriaAction', +} diff --git a/packages/sheets-plugin-clipboard/src/Const/FILTER_CRITERIA_DEFINED.ts b/packages/sheets-plugin-clipboard/src/Const/FILTER_CRITERIA_DEFINED.ts new file mode 100644 index 0000000000..3e7d4f73ae --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Const/FILTER_CRITERIA_DEFINED.ts @@ -0,0 +1,7 @@ +export const FilterCriteriaDefined = { + CELL_EMPTY: 'whenCellEmpty', + CELL_NOT_EMPTY: 'whenCellNotEmpty', + DATE_AFTER: 'whenDateAfter', + DATE_BEFORE: 'whenDateBefore', + DATE_EQUAL_TO: 'whenDateEqualTo', +}; diff --git a/packages/sheets-plugin-clipboard/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-clipboard/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..e4aebe4304 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const CLIPBOARD_PLUGIN = 'ClipboardPlugin'; diff --git a/packages/sheets-plugin-clipboard/src/Const/index.ts b/packages/sheets-plugin-clipboard/src/Const/index.ts new file mode 100644 index 0000000000..5681768dab --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Const/index.ts @@ -0,0 +1,3 @@ +export * from './FILTER_CRITERIA_DEFINED'; +export * from './ACTION_NAME'; +export * from './PLUGIN_NAME'; diff --git a/packages/sheets-plugin-clipboard/src/Domain/Clipboard.ts b/packages/sheets-plugin-clipboard/src/Domain/Clipboard.ts new file mode 100644 index 0000000000..41ad96385f --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Domain/Clipboard.ts @@ -0,0 +1,94 @@ +import { PasteType } from './Paste'; + +export type ClipboardType = { + type?: 'text/html' | 'text/plain' | 'image/png'; + data: string; +}; + +export class Clipboard { + static clipboard = window.navigator.clipboard; + + static textArea: HTMLTextAreaElement; + + static async writeText(text: string, e?: ClipboardEvent) { + if (Clipboard.clipboard) { + return Clipboard.clipboard.writeText(text).then( + () => true, + (err) => console.dir(err) + ); + } + + e?.clipboardData?.setData('text/html', text); + } + + static async write(data: ClipboardType, e?: ClipboardEvent) { + if (Clipboard.clipboard) { + const blob = new Blob([data.data], { type: data.type ?? 'text/html' }); + const clipboardData = [new ClipboardItem({ 'text/html': blob })]; + + return Clipboard.clipboard.write(clipboardData).then( + () => true, + () => false + ); + } + + e?.clipboardData?.setData(data.type ?? 'text/html', data.data); + } + + static async readText() { + if (Clipboard.clipboard) { + return Clipboard.clipboard.readText().then( + (text) => text, + () => null + ); + } + if (!Clipboard.textArea) return null; + return new Promise((resolve, reject) => { + resolve(Clipboard.textArea.value); + }) + .then((text) => text) + .then(() => null); + } + + static async read(e?: ClipboardEvent): Promise | null> { + if (Clipboard.clipboard) { + const clipboardItems = await Clipboard.clipboard.read(); + const Promises: Array> = []; + for (const clipboardItem of clipboardItems) { + for (const type of clipboardItem.types) { + const blob = await clipboardItem.getType(type); + const reader = new FileReader(); + if (type === 'image/png') { + reader.readAsDataURL(blob); + } else { + reader.readAsText(blob); + } + const promise: Promise = new Promise((resolve: (value: PasteType) => void, reject) => { + reader.onload = (e) => { + const item: PasteType = { type, result: reader.result }; + resolve(item); + }; + }).then( + (res) => res, + () => null + ); + Promises.push(promise); + } + } + return Promise.all(Promises); + } + + const result = []; + const clipboardData = e?.clipboardData; + const types = clipboardData?.types; + console.dir(types); + if (!types) return null; + for (let i = 0; i < types.length; i++) { + result.push({ + type: types[i], + result: clipboardData.getData(types[i]), + }); + } + return result; + } +} diff --git a/packages/sheets-plugin-clipboard/src/Domain/Copy.ts b/packages/sheets-plugin-clipboard/src/Domain/Copy.ts new file mode 100644 index 0000000000..e39afa542f --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Domain/Copy.ts @@ -0,0 +1,459 @@ +import { handleJsonToDom, handleStyleToString } from '@univer/base-component'; +import { Context, PLUGIN_NAMES, Tools } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { SelectionControl } from '@univer/base-sheets/src/Controller/Selection/SelectionController'; +import { RightMenuProps } from '@univer/base-sheets/src/Model/Domain/RightMenuModel'; +import { SelectionModel } from '@univer/base-sheets/src/Model/Domain/SelectionModel'; +import { Clipboard } from './Clipboard'; + +export abstract class Copy { + private _context: Context; + + constructor(context: Context, copyList: RightMenuProps[]) { + this._context = context; + const spreadSheetPlugin = this._context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + spreadSheetPlugin?.addRightMenu(copyList); + + const manager = this._context.getObserverManager(); + manager.requiredObserver('onSpreadsheetKeyCopyObservable', PLUGIN_NAMES.SPREADSHEET).add((e) => { + this.copy(e); + }); + } + + getContext() { + return this._context; + } + + copy(e: Event) { + e.preventDefault(); + } +} + +export class UniverCopy extends Copy { + constructor(context: Context) { + const copyList = [ + { + locale: ['rightClick.copy'], + onClick: (...arg: any) => this.copyTo(...arg), + }, + { + locale: ['rightClick.copyAs'], + icon: 'RightIcon', + children: [ + { + locale: ['Json', 'rightClick.firstLineTitle'], + onClick: (...arg: any) => this.copyJsonHead(...arg), + }, + { + locale: ['Json', 'rightClick.untitled'], + onClick: (...arg: any) => this.copyJsonNoHead(...arg), + }, + { + locale: ['rightClick.array1'], + onClick: (...arg: any) => this.copyArray1(...arg), + }, + { + locale: ['rightClick.array2'], + onClick: (...arg: any) => this.copyArray2(...arg), + }, + { + locale: [ + 'rightClick.array3', + { + type: 'input', + min: 1, + format: 'number', + placeholder: 'rightClick.row', + locale: 'rightClick.row', + onKeyUp: (e: KeyboardEvent) => this.copyArrayMore(e), + }, + '×', + { + type: 'input', + format: 'number', + min: 1, + placeholder: 'rightClick.column', + locale: 'rightClick.column', + onKeyUp: (e: KeyboardEvent) => this.copyArrayMore(e), + }, + ], + onKeyUp: (...arg: any) => { + if (arg[0].key !== 'Enter') return; + arg[1].ref.hideSelect(); + arg[1].ref.getParent().hideSelect(); + }, + }, + // { + // locale: ['rightClick.diagonal'], + // }, + // { + // locale: ['rightClick.antiDiagonal'], + // }, + // { + // locale: ['rightClick.diagonalOffset', { type: 'input', format: 'number' }, 'rightClick.column'], + // }, + // { + // locale: ['rightClick.boolean'], + // }, + ], + }, + ]; + super(context, copyList); + } + + private _getSheetInfo() { + const sheet = this.getContext().getWorkBook().getActiveSheet(); + const spreadSheetPlugin = this.getContext().getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + const spreadsheet = spreadSheetPlugin?.getMainComponent(); + const controls = spreadSheetPlugin?.getSelectionManager().getCurrentControls(); + const selections: any = controls?.map((control: SelectionControl) => { + const model: SelectionModel = control.model; + return { + startRow: model.startRow, + startColumn: model.startColumn, + endRow: model.endRow, + endColumn: model.endColumn, + }; + }); + return { sheet, spreadsheet, selections }; + } + + private _getRangeInfo() { + const { sheet, selections } = this._getSheetInfo(); + if (!selections.length) return; + const range = sheet.getRange(selections[0]); + const rangeData = range.getValues(); + if (!rangeData.length) return; + return { range, rangeData }; + } + + private _getCopyContent() { + const { sheet, spreadsheet, selections } = this._getSheetInfo(); + if (selections.length > 1) return; + + const rowManager: any = sheet.getRowManager().getRowData(); + const colManager: any = sheet.getColumnManager().getColumnData(); + let rowIndexArr: number[] = []; + let colIndexArr: number[] = []; + + for (let s = 0; s < selections.length; s++) { + let range = selections[s]; + + let r1 = range.startRow; + let r2 = range.endRow; + let c1 = range.startColumn; + let c2 = range.endColumn; + + for (let copyR = r1; copyR <= r2; copyR++) { + const rowItem = rowManager.get(copyR); + if (rowItem && rowItem.hd) { + continue; + } + + if (!rowIndexArr.includes(copyR)) { + rowIndexArr.push(copyR); + } + + for (let copyC = c1; copyC <= c2; copyC++) { + const colItem = colManager.get(copyC); + if (colItem && colItem.hd) { + continue; + } + + if (!colIndexArr.includes(copyC)) { + colIndexArr.push(copyC); + } + } + } + } + + let cpData = ''; + let colGroup = ''; + + for (let i = 0; i < rowIndexArr.length; i++) { + let r = rowIndexArr[i]; + cpData += ''; + + for (let j = 0; j < colIndexArr.length; j++) { + let c = colIndexArr[j]; + + let cellValue = sheet.getRange(r, c).getValue(); + let column = ''; + let style = ''; + let span = ''; + + if (r === rowIndexArr[0]) { + const colItem = colManager.get(c); + if (colItem && colItem.w) { + ``; + } else { + colGroup += ''; + } + } + + const rowItem = rowManager.get(r); + if (rowItem && rowItem.h) { + style += `height:${rowItem.h}px;`; + } else { + style += 'height:19px;'; + } + + const colItem = colManager.get(c); + if (colItem && colItem.w) { + style += `width:${colItem.w}px;`; + } else { + style += `width:72px;`; + } + + if (cellValue && cellValue.s) { + const cellStyle = this.getContext().getWorkBook().getStyles().get(cellValue.s); + if (cellStyle) { + style += handleStyleToString(cellStyle); + } + } + + const cellInfo = spreadsheet?.getCellByIndex(r, c); + if (cellInfo?.isMerged || (!cellInfo?.isMerged && cellInfo?.isMergedMainCell)) { + if (cellInfo.isMergedMainCell) { + span = `rowSpan="${cellInfo.mergeInfo.endRow - cellInfo.mergeInfo.startRow + 1}" colSpan="${ + cellInfo.mergeInfo.endColumn - cellInfo.mergeInfo.startColumn + 1 + }"`; + } else { + continue; + } + } + + if (style.includes('data-rotate')) { + let rotate = style.split(';').find((item) => item.includes('data-rotate')); + const match = rotate?.match(/\d+/g); + let angle = 0; + let ver = 0; + if (match?.length) { + angle = +match[0]; + ver = +match[1] ?? 0; + } + column += ``; + } else { + column = ``; + } + + let c_value; + + if (cellValue) { + if (cellValue.p) { + c_value = handleJsonToDom(cellValue.p); + } else { + c_value = sheet.getRange(r, c).getDisplayValue(); + if (c_value == null) { + c_value = ''; + } + } + } else { + c_value = ''; + } + + column += c_value; + + column += ''; + cpData += column; + } + + cpData += ''; + } + let cpTable = `${colGroup}${cpData}
        `; + return cpTable; + } + + async copy(e: ClipboardEvent) { + const table = this._getCopyContent(); + if (table) { + Clipboard.write( + { + data: table, + }, + e + ); + } + } + + async copyTo(...arg: any) { + arg[1].ref.hideSelect(); + + const table = this._getCopyContent(); + if (table) { + const isWrite = await Clipboard.write({ + data: table, + }); + } + } + + //复制为json格式字符串,首行为标题 + async copyJsonHead(...arg: any) { + arg[1].ref.hideSelect(); + arg[1].ref.getParent().hideSelect(); + + const Range = this._getRangeInfo(); + if (!Range) return; + const { range, rangeData } = Range; + + let arr = []; + if (rangeData.length === 1) { + let obj = {}; + for (let i = 0; i < rangeData[0].length; i++) { + const value = range.getValues()[0][i]?.v; + if (typeof value === 'string' || typeof value === 'number') { + obj[value] = ''; + } else { + obj[''] = ''; + } + } + arr.push(obj); + } else { + for (let r = 1; r < rangeData.length; r++) { + let obj = {}; + for (let c = 0; c < rangeData[0].length; c++) { + const title = range.getValues()[0][c]?.v; + const value = range.getValues()[r][c]?.v; + if (typeof title === 'string' || typeof title === 'number') { + obj[title] = value; + } else { + obj[''] = value; + } + } + arr.push(obj); + } + } + + const isWrite = await Clipboard.writeText(JSON.stringify(arr)); + } + + //复制为json格式字符串,无标题,采用ABCD作为标题 + async copyJsonNoHead(...arg: any) { + arg[1].ref.hideSelect(); + arg[1].ref.getParent().hideSelect(); + + const Range = this._getRangeInfo(); + if (!Range) return; + const { range, rangeData } = Range; + + let arr = []; + for (let r = 0; r < rangeData.length; r++) { + let obj = {}; + for (let c = 0; c < rangeData[0].length; c++) { + const value = range.getValues()[r][c]?.v; + obj[Tools.chatAtABC(c + range.getRangeData().startColumn)] = value; + } + arr.push(obj); + } + + const isWrite = await Clipboard.writeText(JSON.stringify(arr)); + } + + //复制为一维数组 + async copyArray1(...arg: any) { + arg[1].ref.hideSelect(); + arg[1].ref.getParent().hideSelect(); + + const Range = this._getRangeInfo(); + if (!Range) return; + const { range, rangeData } = Range; + + let arr = []; + for (let r = 0; r < rangeData.length; r++) { + for (let c = 0; c < rangeData[0].length; c++) { + const value = range.getValues()[r][c]?.v; + arr.push(value); + } + } + + const isWrite = await Clipboard.writeText(JSON.stringify(arr)); + } + + //复制为二维数组 + async copyArray2(...arg: any) { + arg[1].ref.hideSelect(); + arg[1].ref.getParent().hideSelect(); + + const Range = this._getRangeInfo(); + if (!Range) return; + const { range, rangeData } = Range; + + let arr = []; + for (let r = 0; r < rangeData.length; r++) { + for (let c = 0; c < rangeData[0].length; c++) { + const value = range.getValues()[r][c]?.v; + arr.push(value); + } + } + + const isWrite = await Clipboard.writeText(JSON.stringify(arr)); + } + + //复制为多维数组 + async copyArrayMore(e: KeyboardEvent) { + if (e.key !== 'Enter') return; + const Range = this._getRangeInfo(); + if (!Range) return; + const { range, rangeData } = Range; + + let arr = []; + for (let r = 0; r < rangeData.length; r++) { + for (let c = 0; c < rangeData[0].length; c++) { + arr.push(rangeData[r][c]); + } + } + + let row = '0'; + let column = '0'; + const target = e.target as HTMLInputElement; + const textContent = target?.previousSibling?.textContent; + if (!textContent) return; + if (textContent.length > 1) { + row = target.value; + const columnItem = target.parentNode?.querySelectorAll('input')[1]; + column = columnItem!.value; + } else { + column = target.value; + const columnItem = target.parentNode?.querySelectorAll('input')[0]; + row = columnItem!.value; + } + + if (row === '' && column === '') { + const isWrite = await Clipboard.writeText(JSON.stringify(arr)); + return; + } + + if (row === '') { + row = '1'; + } else { + row = `${parseInt(row)}`; + } + + if (column === '') { + column = '1'; + } else { + column = `${parseInt(column)}`; + } + + if (+row < 1 || +column < 1) { + return; + } + + let arrLen = arr.length; + let i = 0; + let ret = []; + for (let r = 0; r < +row; r++) { + let a = []; + for (let c = 0; c < +column; c++) { + a.push(arr[i++]); + if (i >= arrLen) { + const isWrite = await Clipboard.writeText(JSON.stringify(ret)); + return; + } + } + ret.push(a); + } + + const isWrite = await Clipboard.writeText(JSON.stringify(ret)); + } +} diff --git a/packages/sheets-plugin-clipboard/src/Domain/Paste.ts b/packages/sheets-plugin-clipboard/src/Domain/Paste.ts new file mode 100644 index 0000000000..612e9ec204 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Domain/Paste.ts @@ -0,0 +1,236 @@ +import { handleDomToJson, handleStringToStyle } from '@univer/base-component'; +import { SelectionControl } from '@univer/base-sheets/src/Controller/Selection/SelectionController'; +import { Context, ICellData, PLUGIN_NAMES, Tools } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { SelectionModel } from '@univer/base-sheets/src/Model/Domain/SelectionModel'; +import { RightMenuProps } from '@univer/base-sheets/src/Model/Domain/RightMenuModel'; +import { Clipboard } from './Clipboard'; + +export interface PasteType { + type: string; + result: string | ArrayBuffer | null; +} + +export abstract class Paste { + private _context: Context; + + constructor(context: Context, pasteList: RightMenuProps[]) { + this._context = context; + const spreadSheetPlugin = this._context.getPluginManager().getRequirePluginByName(PLUGIN_NAMES.SPREADSHEET); + spreadSheetPlugin.addRightMenu(pasteList); + + // spreadSheetPlugin.getCellEditorControl().isEditMode + const manager = this._context.getObserverManager(); + manager.requiredObserver('onSpreadsheetKeyPasteObservable', PLUGIN_NAMES.SPREADSHEET).add((e) => { + this.paste(e); + }); + } + + getContext() { + return this._context; + } + + paste(e: Event) {} +} + +export class UniverPaste extends Paste { + constructor(context: Context) { + const pasteList = [ + { + locale: ['rightClick.paste'], + onClick: (...arg: any) => { + arg[1].ref.hideSelect(); + this.pasteTo(); + }, + border: true, + }, + ]; + super(context, pasteList); + } + + async pasteResolver(e?: ClipboardEvent) { + const file: Array | null = await Clipboard.read(e); + if (!file) return []; + const HtmlIndex = file.findIndex((item: PasteType | null, index: number) => item && item.type === 'text/html'); + const PlainIndex = file.findIndex((item: PasteType | null, index: number) => item && item.type === 'text/plain'); + let data: any[] = []; + const content = document.createElement('DIV'); + + if (HtmlIndex > -1) { + const html = file[HtmlIndex]?.result as string; + content.innerHTML = html; + data = new Array(content.querySelectorAll('table tr').length); + if (!data.length) return []; + let colLen = 0; + const trs = content.querySelectorAll('table tr'); + const firstTds = trs[0].querySelectorAll('td'); + firstTds.forEach((item: HTMLTableCellElement) => { + let colSpan = 0; + const attr = item.getAttribute('colSpan'); + if (attr !== null) { + colSpan = +attr; + } else { + colSpan = 1; + } + colLen += colSpan; + }); + for (let i = 0; i < data.length; i++) { + data[i] = new Array(colLen); + } + let r = 0; + trs.forEach((item: any) => { + let c = 0; + item.querySelectorAll('td').forEach((td: HTMLTableCellElement) => { + let cell: ICellData = {}; + if (td.querySelectorAll('span').length || td.querySelectorAll('font').length) { + const spanStyle = handleDomToJson(td); + if (typeof spanStyle !== 'string') { + cell.p = spanStyle; + } + } + let txt = td.innerText; + if (txt.trim().length === 0) { + cell.v = null; + cell.m = null; + } else { + // Todo,处理格式 + cell.v = txt; + cell.m = txt; + } + const style = handleStringToStyle(td); + if (Tools.isPlainObject(style)) { + cell.s = style; + } + while (c < colLen && data[r][c] != null) { + c++; + } + if (c === colLen) { + return; + } + if (data[r][c] == null) { + data[r][c] = cell; + let rowSpan = Number(td.getAttribute('rowSpan')); + let colSpan = Number(td.getAttribute('colSpan')); + if (Number.isNaN(rowSpan)) { + rowSpan = 1; + } + if (Number.isNaN(colSpan)) { + colSpan = 1; + } + if (rowSpan > 1 || colSpan > 1) { + let first = { rs: rowSpan - 1, cs: colSpan - 1, r, c }; + data[r][c].mc = first; + for (let rp = 0; rp < rowSpan; rp++) { + for (let cp = 0; cp < colSpan; cp++) { + if (rp === 0 && cp === 0) { + continue; + } + data[r + rp][c + cp] = { mc: null }; + } + } + } + } + c++; + }); + r++; + }); + } else if (PlainIndex > -1) { + const html = file[PlainIndex]?.result as string; + content.innerHTML = html; + let dataChe = html.replace(/\r/g, ''); + let che = dataChe.split('\n'); + let colCheLen = che[0].split('\t').length; + for (let i = 0; i < che.length; i++) { + if (che[i].split('\t').length < colCheLen) { + continue; + } + data.push(che[i].split('\t')); + } + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < data[i].length; j++) { + if (data[i][j].length) { + data[i][j] = { + v: data[i][j], + m: data[i][j], + }; + } else { + data[i][j] = null; + } + } + } + } + + return data; + } + + async pasteTo(e?: ClipboardEvent) { + const data = await this.pasteResolver(e); + if (data.length === 0) return; + const sheet = this.getContext().getWorkBook().getActiveSheet(); + if (!sheet) return; + const spreadSheetPlugin = this.getContext().getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + if (!spreadSheetPlugin) return; + const spreadsheet = spreadSheetPlugin?.getMainComponent(); + if (!spreadsheet) return; + const controls = spreadSheetPlugin?.getSelectionManager().getCurrentControls(); + const selections: any = controls?.map((control: SelectionControl) => { + const model: SelectionModel = control.model; + return { + startRow: model.startRow, + startColumn: model.startColumn, + endRow: model.endRow, + endColumn: model.endColumn, + }; + }); + + if (!selections.length) { + return; + } + + if (selections.length > 1) { + return; + } + + const selection = selections[0]; + let copyH = data.length; + let copyC = data[0].length; + + let minH = selection.startRow; //应用范围首尾行 + let maxH = minH + copyH - 1; + let minC = selection.startColumn; //应用范围首尾列 + let maxC = minC + copyC - 1; + + const isMerge = sheet.getMerges().getByRowColumn(minH, maxH, minC, maxC); + if (isMerge) { + return; + } + + // Todo: 若应用范围超过最大行或最大列,增加行列 + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < data[i].length; j++) { + if (data[i][j] && typeof data[i][j] === 'object' && 'mc' in data[i][j]) { + if (data[i][j].mc) { + const mc = data[i][j].mc; + const startRow = mc.r + minH; + const endRow = startRow + mc.rs; + const startColumn = mc.c + minC; + const endColumn = startColumn + mc.cs; + + sheet.getMerges().add({ startRow, endRow, startColumn, endColumn }); + delete data[i][j].mc; + } else { + data[i][j] = null; + } + } + } + } + + sheet.getRange(minH, minC, maxH, maxC).setRangeDatas(data); + spreadSheetPlugin.getCanvasView().updateToSheet(spreadSheetPlugin.getContext().getWorkBook().getActiveSheet()!); + spreadsheet.makeDirty(true); + } + + paste(e: ClipboardEvent) { + this.pasteTo(e); + } +} diff --git a/packages/sheets-plugin-clipboard/src/Domain/index.ts b/packages/sheets-plugin-clipboard/src/Domain/index.ts new file mode 100644 index 0000000000..414c62bc48 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Domain/index.ts @@ -0,0 +1,3 @@ +export * from './Clipboard'; +export * from './Copy'; +export * from './Paste'; diff --git a/packages/sheets-plugin-clipboard/src/Enum/BooleanCriteria.ts b/packages/sheets-plugin-clipboard/src/Enum/BooleanCriteria.ts new file mode 100644 index 0000000000..b9e1f08f61 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Enum/BooleanCriteria.ts @@ -0,0 +1,73 @@ +export enum BooleanCriteria { + // The criteria is met when a cell is empty. + CELL_EMPTY = 'CELL_EMPTY', + + // The criteria is met when a cell is not empty. + CELL_NOT_EMPTY = 'CELL_NOT_EMPTY', + + // The criteria is met when a date is after the given value. + DATE_AFTER = 'DATE_AFTER', + + // The criteria is met when a date is before the given value. + DATE_BEFORE = 'DATE_BEFORE', + + // The criteria is met when a date is equal to the given value. + DATE_EQUAL_TO = 'DATE_EQUAL_TO', + + // The criteria is met when a date is not equal to the given value. + DATE_NOT_EQUAL_TO = 'DATE_NOT_EQUAL_TO', + + // The criteria is met when a date is after the relative date value. + DATE_AFTER_RELATIVE = 'DATE_AFTER_RELATIVE', + + // The criteria is met when a date is before the relative date value. + DATE_BEFORE_RELATIVE = 'DATE_BEFORE_RELATIVE', + + // The criteria is met when a date is equal to the relative date value. + DATE_EQUAL_TO_RELATIVE = 'DATE_EQUAL_TO_RELATIVE', + + // The criteria is met when a number that is between the given values. + NUMBER_BETWEEN = 'NUMBER_BETWEEN', + + // The criteria is met when a number that is equal to the given value. + NUMBER_EQUAL_TO = 'NUMBER_EQUAL_TO', + + // The criteria is met when a number that is greater than the given value. + NUMBER_GREATER_THAN = 'NUMBER_GREATER_THAN', + + // The criteria is met when a number that is greater than or equal to the given value. + NUMBER_GREATER_THAN_OR_EQUAL_TO = 'NUMBER_GREATER_THAN_OR_EQUAL_TO', + + // The criteria is met when a number that is less than the given value. + NUMBER_LESS_THAN = 'NUMBER_LESS_THAN', + + // The criteria is met when a number that is less than or equal to the given value. + NUMBER_LESS_THAN_OR_EQUAL_TO = 'NUMBER_LESS_THAN_OR_EQUAL_TO', + + // The criteria is met when a number that is not between the given values. + NUMBER_NOT_BETWEEN = 'NUMBER_NOT_BETWEEN', + + // The criteria is met when a number that is not equal to the given value. + NUMBER_NOT_EQUAL_TO = 'NUMBER_NOT_EQUAL_TO', + + // The criteria is met when the input contains the given value. + TEXT_CONTAINS = 'TEXT_CONTAINS', + + // The criteria is met when the input does not contain the given value. + TEXT_DOES_NOT_CONTAIN = 'TEXT_DOES_NOT_CONTAIN', + + // The criteria is met when the input is equal to the given value. + TEXT_EQUAL_TO = 'TEXT_EQUAL_TO', + + // The criteria is met when the input is not equal to the given value. + TEXT_NOT_EQUAL_TO = 'TEXT_NOT_EQUAL_TO', + + // The criteria is met when the input begins with the given value. + TEXT_STARTS_WITH = 'TEXT_STARTS_WITH', + + // The criteria is met when the input ends with the given value. + TEXT_ENDS_WITH = 'TEXT_ENDS_WITH', + + // The criteria is met when the input makes the given formula evaluate to true. + CUSTOM_FORMULA = 'CUSTOM_FORMULA', +} diff --git a/packages/sheets-plugin-clipboard/src/IData.ts b/packages/sheets-plugin-clipboard/src/IData.ts new file mode 100644 index 0000000000..0f2d37c2c7 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/IData.ts @@ -0,0 +1,9 @@ +import { Context } from '@univer/core'; +import { ClipboardPlugin } from './ClipboardPlugin'; + +export type IConfig = { + context: Context; +}; + +// Types for props +export type IProps = { config: IConfig; super: ClipboardPlugin }; diff --git a/packages/sheets-plugin-clipboard/src/IData/FilterType.ts b/packages/sheets-plugin-clipboard/src/IData/FilterType.ts new file mode 100644 index 0000000000..38f3f82801 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/IData/FilterType.ts @@ -0,0 +1,24 @@ +import { IColor, IRangeData, Nullable } from '@univer/core'; + +export type IFilterType = { + range: IRangeData; + sortSpecs: {}; + filterSpecs: IFilterSpecsType[]; +}; + +export type IFilterSpecsType = { + filterCriteria: Nullable; + // Union field reference can be only one of the following: + columnIndex: number; + dataSourceColumnReference: {}; + // End of list of possible types for union field reference. +}; + +type IFilterCriteria = { + hiddenValues: string[]; + condition: BooleanCondition; + visibleBackgroundColor: IColor; + // visibleBackgroundColorStyle: IColorStyle; + visibleForegroundColor: IColor; + // visibleForegroundColorStyle: IColorStyle; +}; diff --git a/packages/sheets-plugin-clipboard/src/IData/index.ts b/packages/sheets-plugin-clipboard/src/IData/index.ts new file mode 100644 index 0000000000..1d99946701 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/IData/index.ts @@ -0,0 +1 @@ +export * from './FilterType'; diff --git a/packages/sheets-plugin-clipboard/src/Locale/en.ts b/packages/sheets-plugin-clipboard/src/Locale/en.ts new file mode 100644 index 0000000000..f7752ee54b --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Locale/en.ts @@ -0,0 +1,58 @@ +export default { + // toolbar: { + filterLabel: 'Filter', + filter: { + filter: 'create filter', + + sortByAsc: 'Sort A-Z', + sortByDesc: 'Sort Z-A', + filterByColor: 'Filter by color', + filterByCondition: 'Filter by condition', + filterByValues: 'Filter by values', + + filiterInputNone: 'None', + + filiterInputTip: 'Enter filter value', + filiterRangeStartTip: 'Value for formula', + filiterRangeEndTip: 'Value for formula', + + filterValueByAllBtn: 'Check all', + filterValueByClearBtn: 'Clear', + filterValueByInverseBtn: 'Inverse', + filterValueByTip: 'filter By Values', + filterConform: 'Confirm', + filterCancel: 'Cancel', + clearFilter: 'Clear filter', + + conditionNone: 'None', + conditionCellIsNull: 'Is empty', + conditionCellNotNull: 'Is not empty', + conditionCellTextContain: 'Text contains', + conditionCellTextNotContain: 'Text does not contain', + conditionCellTextStart: 'Text starts with', + conditionCellTextEnd: 'Text ends with', + conditionCellTextEqual: 'Text is exactly', + conditionCellDateEqual: 'Date is', + conditionCellDateBefore: 'Date is before', + conditionCellDateAfter: 'Date is after', + conditionCellGreater: 'Greater than', + conditionCellGreaterEqual: 'Greater than or equal to', + conditionCellLess: 'Less than', + conditionCellLessEqual: 'Less than or equal to', + conditionCellEqual: 'Is equal to', + conditionCellNotEqual: 'Is not equal to', + conditionCellBetween: 'Is between', + conditionCellNotBetween: 'Is not between', + + filiterMoreDataTip: 'Big amount of data! please wait', + filiterMonthText: 'Month', + filiterYearText: 'Year', + filiterByColorTip: 'Filter by cell color', + filiterByTextColorTip: 'Filter by font color', + filterContainerOneColorTip: 'This column contains only one color', + filterDateFormatTip: 'Date format', + + valueBlank: '(Null)', + mergeError: 'There are merged cells in the filter selection, this operation cannot be performed!', + }, +}; diff --git a/packages/sheets-plugin-clipboard/src/Locale/index.ts b/packages/sheets-plugin-clipboard/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-clipboard/src/Locale/zh.ts b/packages/sheets-plugin-clipboard/src/Locale/zh.ts new file mode 100644 index 0000000000..c1b0d86277 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/Locale/zh.ts @@ -0,0 +1,57 @@ +export default { + filterLabel: '筛选', + filter: { + filter: '筛选', + clearFilter: '清除筛选', + + sortByAsc: '以A-Z升序排列', + sortByDesc: '以Z-A降序排列', + filterByColor: '按颜色筛选', + filterByCondition: '按条件过滤', + filterByValues: '按值过滤', + + filiterInputNone: '无', + + filiterInputTip: '输入筛选值', + filiterRangeStartTip: '范围开始', + filiterRangeEndTip: '范围结束', + + filterValueByAllBtn: '全选', + filterValueByClearBtn: '清除', + filterValueByInverseBtn: '反选', + filterValueByTip: '按照值进行筛选', + filterConform: '确 认', + filterCancel: '取 消', + + conditionNone: '无', + conditionCellIsNull: '单元格为空', + conditionCellNotNull: '单元格有数据', + conditionCellTextContain: '文本包含', + conditionCellTextNotContain: '文本不包含', + conditionCellTextStart: '文本开头为', + conditionCellTextEnd: '文本结尾为', + conditionCellTextEqual: '文本等于', + conditionCellDateEqual: '日期等于', + conditionCellDateBefore: '日期早于', + conditionCellDateAfter: '日期晚于', + conditionCellGreater: '大于', + conditionCellGreaterEqual: '大于等于', + conditionCellLess: '小于', + conditionCellLessEqual: '小于等于', + conditionCellEqual: '等于', + conditionCellNotEqual: '不等于', + conditionCellBetween: '介于', + conditionCellNotBetween: '不在其中', + + filiterMoreDataTip: '数据量大!请稍后', + filiterMonthText: '月', + filiterYearText: '年', + filiterByColorTip: '按单元格颜色筛选', + filiterByTextColorTip: '按单元格字体颜色筛选', + filterContainerOneColorTip: '本列仅包含一种颜色', + filterDateFormatTip: '日期格式', + + valueBlank: '(空白)', + mergeError: '筛选选区有合并单元格,无法执行此操作!', + }, +}; diff --git a/packages/sheets-plugin-clipboard/src/UI/FilterButton.tsx b/packages/sheets-plugin-clipboard/src/UI/FilterButton.tsx new file mode 100644 index 0000000000..a78937ce56 --- /dev/null +++ b/packages/sheets-plugin-clipboard/src/UI/FilterButton.tsx @@ -0,0 +1,102 @@ +import { BaseSelectProps, Component, IToolBarItemProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { IProps } from '../IData'; +import { ClipboardPlugin } from '../ClipboardPlugin'; + +interface IState { + filter: IToolBarItemProps; + isFilter: boolean; + filterPlugin: ClipboardPlugin | null; +} + +export class FilterButton extends Component { + protected _localeObserver: Nullable>; + + initialize(props: IProps) { + const FilterRankIcon = this.getComponentRender().renderFunction('FilterRankIcon'); + const NextIcon = this.getComponentRender().renderFunction('NextIcon'); + const FilterIcon = this.getComponentRender().renderFunction('FilterIcon'); + const CleanIcon = this.getComponentRender().renderFunction('CleanIcon'); + + this.state = { + filter: { + locale: 'filter', + type: 'select', + label: , + icon: , + show: true, + children: [ + { + locale: 'filter.filter', + icon: , + onClick: () => {}, + }, + { + locale: 'filter.clearFilter', + icon: , + onClick: () => {}, + }, + ], + }, + filterPlugin: null, + isFilter: false, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this.getContext() + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this.getContext().getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.filter; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}Label`); + + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + + return { + filter: item, + }; + }); + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const { filter } = state; + const Select = this.getComponentRender().renderFunction('Select'); + return + ); + } +} diff --git a/packages/sheets-plugin-comment/src/View/Event.ts b/packages/sheets-plugin-comment/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-comment/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-comment/src/View/ObserverList.ts b/packages/sheets-plugin-comment/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-comment/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-comment/src/View/Render.ts b/packages/sheets-plugin-comment/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-comment/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-comment/src/View/index.ts b/packages/sheets-plugin-comment/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-comment/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-comment/src/index.ts b/packages/sheets-plugin-comment/src/index.ts new file mode 100644 index 0000000000..14daeb9654 --- /dev/null +++ b/packages/sheets-plugin-comment/src/index.ts @@ -0,0 +1,3 @@ +import { CommentPlugin } from './CommentPlugin'; + +export { CommentPlugin }; diff --git a/packages/sheets-plugin-comment/src/main.tsx b/packages/sheets-plugin-comment/src/main.tsx new file mode 100644 index 0000000000..551ab3debf --- /dev/null +++ b/packages/sheets-plugin-comment/src/main.tsx @@ -0,0 +1,60 @@ +import { UniverSheet } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { FilterPlugin } from '@univer/sheets-plugin-filter'; +import { CommentPlugin } from './CommentPlugin'; + +const defaultWorkbookData = { + locale: 'zh', + creator: '', + name: '', + skin: 'default', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +univerSheetUp.installPlugin(new FilterPlugin()); +univerSheetUp.installPlugin(new CommentPlugin()); diff --git a/packages/sheets-plugin-comment/src/preact.d.ts b/packages/sheets-plugin-comment/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-comment/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-comment/tsconfig.json b/packages/sheets-plugin-comment/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-comment/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-comment/tsconfig.ref.json b/packages/sheets-plugin-comment/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-comment/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-comment/tsconfig.test.json b/packages/sheets-plugin-comment/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-comment/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-comment/vite.config.ts b/packages/sheets-plugin-comment/vite.config.ts new file mode 100644 index 0000000000..894f4f3abe --- /dev/null +++ b/packages/sheets-plugin-comment/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginComment', + formats: ['es', 'umd'], + fileName: 'univer-sheets--plugin-comment', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-conditional-format/README-zh.md b/packages/sheets-plugin-conditional-format/README-zh.md new file mode 100644 index 0000000000..0cf99fe684 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet ConditionalFormat 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-conditional-format +``` + +### 使用 + +```js +impport {ConditionalFormat} from '@univer/sheets-plugin-conditional-format' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new ConditionalFormat()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-conditional-format/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-conditional-format dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-conditional-format/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-conditional-format build +``` diff --git a/packages/sheets-plugin-conditional-format/README.md b/packages/sheets-plugin-conditional-format/README.md new file mode 100644 index 0000000000..001bc6402f --- /dev/null +++ b/packages/sheets-plugin-conditional-format/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin ConditionalFormat + +### Installation + +```bash +npm i @univer/sheets-plugin-conditional-format +``` + +### Usage + +```js +impport {ConditionalFormat} from '@univer/sheets-plugin-conditional-format' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new ConditionalFormat()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-conditional-format/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-conditional-format dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-conditional-format/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-conditional-format build +``` diff --git a/packages/sheets-plugin-conditional-format/index.html b/packages/sheets-plugin-conditional-format/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-conditional-format/jest.config.js b/packages/sheets-plugin-conditional-format/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-conditional-format/package.json b/packages/sheets-plugin-conditional-format/package.json new file mode 100644 index 0000000000..10ca4c1cce --- /dev/null +++ b/packages/sheets-plugin-conditional-format/package.json @@ -0,0 +1,57 @@ +{ + "name": "@univer/sheets-plugin-conditional-format", + "version": "0.0.1", + "description": "UniverSheet normal plugin conditionalFormat", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-conditional-format/src/Action/ActionConditionalFormat.ts b/packages/sheets-plugin-conditional-format/src/Action/ActionConditionalFormat.ts new file mode 100644 index 0000000000..c4dfe4af21 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Action/ActionConditionalFormat.ts @@ -0,0 +1 @@ +export class ActionConditionalFormat {} diff --git a/packages/sheets-plugin-conditional-format/src/Action/IAction.ts b/packages/sheets-plugin-conditional-format/src/Action/IAction.ts new file mode 100644 index 0000000000..9b031a3c3c --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Action/IAction.ts @@ -0,0 +1 @@ +export class IAction {} diff --git a/packages/sheets-plugin-conditional-format/src/Action/IActionSet.ts b/packages/sheets-plugin-conditional-format/src/Action/IActionSet.ts new file mode 100644 index 0000000000..f8e2a8f7e6 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Action/IActionSet.ts @@ -0,0 +1 @@ +export class IActionSet {} diff --git a/packages/sheets-plugin-conditional-format/src/Action/index.ts b/packages/sheets-plugin-conditional-format/src/Action/index.ts new file mode 100644 index 0000000000..c53eec62bc --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Action/index.ts @@ -0,0 +1,8 @@ +/** + * en: Modify localData, conflict handling, generate data transfer protocol, submit for background execution + * + * zh: 修改localData、冲突处理、生成数据传输协议、提交后台执行 + */ +export * from './ActionConditionalFormat'; +export * from './IAction'; +export * from './IActionSet'; diff --git a/packages/sheets-plugin-conditional-format/src/Assets/CFcolorGradation.png b/packages/sheets-plugin-conditional-format/src/Assets/CFcolorGradation.png new file mode 100644 index 0000000000..b094ab2739 Binary files /dev/null and b/packages/sheets-plugin-conditional-format/src/Assets/CFcolorGradation.png differ diff --git a/packages/sheets-plugin-conditional-format/src/Assets/CFdataBar.png b/packages/sheets-plugin-conditional-format/src/Assets/CFdataBar.png new file mode 100644 index 0000000000..157c0d5b0e Binary files /dev/null and b/packages/sheets-plugin-conditional-format/src/Assets/CFdataBar.png differ diff --git a/packages/sheets-plugin-conditional-format/src/Assets/CFicons.png b/packages/sheets-plugin-conditional-format/src/Assets/CFicons.png new file mode 100644 index 0000000000..131a526dd6 Binary files /dev/null and b/packages/sheets-plugin-conditional-format/src/Assets/CFicons.png differ diff --git a/packages/sheets-plugin-conditional-format/src/Base/Cache.ts b/packages/sheets-plugin-conditional-format/src/Base/Cache.ts new file mode 100644 index 0000000000..002f98640e --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Base/Cache.ts @@ -0,0 +1 @@ +export class Cache {} diff --git a/packages/sheets-plugin-conditional-format/src/Base/State.ts b/packages/sheets-plugin-conditional-format/src/Base/State.ts new file mode 100644 index 0000000000..f277253fef --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Base/State.ts @@ -0,0 +1 @@ +export class State {} diff --git a/packages/sheets-plugin-conditional-format/src/Base/index.ts b/packages/sheets-plugin-conditional-format/src/Base/index.ts new file mode 100644 index 0000000000..6c6e3b5d96 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Base/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './State'; diff --git a/packages/sheets-plugin-conditional-format/src/ConditionalFormatPlugin.tsx b/packages/sheets-plugin-conditional-format/src/ConditionalFormatPlugin.tsx new file mode 100644 index 0000000000..752e78f982 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/ConditionalFormatPlugin.tsx @@ -0,0 +1,59 @@ +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { Context, IOCContainer, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { en, zh } from './Locale'; +import { ConditionalFormatButton } from './UI/ConditionalFormatButton'; + +import { IConfig } from './IData/IConditionalFormat'; +import { CONDITIONAL_FORMAT_PLUGIN_NAME } from './Const/PLUGIN_NAME'; + +type IPluginConfig = {}; + +export class ConditionalFormatPlugin extends Plugin { + spreadsheetPlugin: any; + + constructor(config?: IPluginConfig) { + super(CONDITIONAL_FORMAT_PLUGIN_NAME); + } + + static create(config?: IPluginConfig) { + return new ConditionalFormatPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = {}; + + const item: IToolBarItemProps = { + locale: CONDITIONAL_FORMAT_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + // get spreadsheet plugin + this.spreadsheetPlugin = context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + + // extend comment + this.spreadsheetPlugin?.addButton(item); + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} +} diff --git a/packages/sheets-plugin-conditional-format/src/ConditionalFormatRule.ts b/packages/sheets-plugin-conditional-format/src/ConditionalFormatRule.ts new file mode 100644 index 0000000000..ca04030c84 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/ConditionalFormatRule.ts @@ -0,0 +1,44 @@ +import { IRangeData, Nullable } from '@univer/core'; +import { ConditionalFormatRuleBuilder } from './ConditionalFormatRuleBuilder'; +import { BooleanRule, GradientRule } from './IData/IConditionalFormatRule'; + +export class ConditionalFormatRule { + private _ranges: IRangeData[]; + + private _rule: BooleanRule | GradientRule; + + constructor(ranges: IRangeData[], rule: BooleanRule | GradientRule) { + this._ranges = ranges; + this._rule = rule; + } + + /** + * Returns a rule builder preset with this rule's settings. + */ + copy(): ConditionalFormatRuleBuilder { + return new ConditionalFormatRuleBuilder(this._ranges, this._rule); + } + + /** + * Retrieves the rule's BooleanCondition information if this rule uses boolean condition criteria. Otherwise returns null. + */ + getBooleanCondition(): Nullable { + if ('minpoint' in this._rule) return null; + return this._rule; + } + + /** + * Retrieves the rule's GradientCondition information, if this rule uses gradient condition criteria. Otherwise returns null. + */ + getGradientCondition(): Nullable { + if ('condition' in this._rule) return null; + return this._rule; + } + + /** + * Retrieves the ranges to which this conditional format rule is applied. + */ + getRanges(): IRangeData[] { + return this._ranges; + } +} diff --git a/packages/sheets-plugin-conditional-format/src/ConditionalFormatRuleBuilder.ts b/packages/sheets-plugin-conditional-format/src/ConditionalFormatRuleBuilder.ts new file mode 100644 index 0000000000..6e54d35537 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/ConditionalFormatRuleBuilder.ts @@ -0,0 +1,698 @@ +import { ConditionType, InterpolationPointType, Range, IRangeData, IStyleData, Nullable, RelativeDate } from '@univer/core'; +import { ConditionalFormatRule } from './ConditionalFormatRule'; +import { BooleanCondition, BooleanRule, ConditionValue, GradientRule, InterpolationPoint } from './IData/IConditionalFormatRule'; + +export class ConditionalFormatRuleBuilder { + private _ranges: IRangeData[]; + + private _booleanRule: BooleanRule; + + private _gradientRule: GradientRule; + + private _format: IStyleData; + + private _condition: BooleanCondition; + + private _maxPoint: InterpolationPoint; + + private _minPoint: InterpolationPoint; + + private _midPoint: Nullable; + + /** + * get condition of date format + */ + private _getDate = (type: ConditionType, date: RelativeDate | Date) => { + let value: ConditionValue; + if (typeof date === 'object') { + value = { + userEnteredValue: (+date).toString(), + }; + } else { + value = { + relativeDate: date, + }; + } + this._condition = { + type, + values: [value], + }; + }; + + private _setBooleanRule() { + this._booleanRule = { + condition: this._condition, + format: this._format, + }; + } + + /** + * get condition of text format + */ + private _getText(type: ConditionType, value: string) { + this._condition = { + type, + values: [ + { + userEnteredValue: value, + }, + ], + }; + } + + /** + * get condition of number format + */ + private _getNumber(type: ConditionType, value: number[]) { + const values: ConditionValue[] = []; + for (let i = 0; i < value.length; i++) { + values.push({ + userEnteredValue: value[i].toString(), + }); + } + this._condition = { + type, + values, + }; + } + + constructor(ranges?: IRangeData[], rule?: BooleanRule | GradientRule) { + this._ranges = ranges ?? []; + if (rule) { + if ('condition' in rule) { + this._booleanRule = rule; + } else { + this._gradientRule = rule; + } + } + } + + /** + * Constructs a conditional format rule from the settings applied to the builder. + */ + build(): ConditionalFormatRule { + return new ConditionalFormatRule(this._ranges, this._booleanRule ?? this._gradientRule); + } + + /** + * Sets the background color for the conditional format rule's format. Passing in null removes the background color format setting from the rule. + */ + setBackground(color: Nullable): ConditionalFormatRuleBuilder { + const bgColor = { + rgb: color, + }; + this._format.bg = bgColor; + this._setBooleanRule(); + + return this; + } + + /** + * + * Sets text bolding for the conditional format rule's format. If bold is true, the rule bolds text if the condition is met; if false, the rule removes any existing bolding if the condition is met. Passing in null removes the bold format setting from the rule. + */ + setBold(bold: Nullable): ConditionalFormatRuleBuilder { + if (bold === null) { + delete this._format.bl; + } else if (bold) { + this._format.bl = 1; + } else { + this._format.bl = 0; + } + this._setBooleanRule(); + + return this; + } + + /** + * Sets the font color for the conditional format rule's format. Passing in null removes the font color format setting from the rule. + */ + setFontColor(color: Nullable): ConditionalFormatRuleBuilder { + const fontColor = { + rgb: color, + }; + this._format.cl = fontColor; + this._setBooleanRule(); + + return this; + } + + /** + * Sets text italics for the conditional format rule's format. If italic is true, the rule italicises text if the condition is met; if false, the rule removes any existing italicization if the condition is met. Passing in null removes the italic format setting from the rule. + */ + setItalic(italic: Nullable): ConditionalFormatRuleBuilder { + if (italic === null) { + delete this._format.it; + } else if (italic) { + this._format.it = 1; + } else { + this._format.it = 0; + } + this._setBooleanRule(); + + return this; + } + + /** + * Sets one or more ranges to which this conditional format rule is applied. This operation replaces any existing ranges. Setting an empty array clears any existing ranges. A rule must have at least one range. + */ + setRanges(ranges: Range[]): ConditionalFormatRuleBuilder { + const range = []; + for (let i = 0; i < ranges.length; i++) { + range.push(ranges[i].getRangeData()); + } + this._ranges = range; + + return this; + } + + /** + * Sets text strikethrough for the conditional format rule's format. If strikethrough is true, the rule strikesthrough text if the condition is met; if false, the rule removes any existing strikethrough formatting if the condition is met. Passing in null removes the strikethrough format setting from the rule. + */ + setStrikethrough(strikethrough: Nullable): ConditionalFormatRuleBuilder { + if (!this._format.st) { + this._format.st = { s: 1 }; + } + + if (strikethrough === null) { + delete this._format.st; + } else if (strikethrough) { + this._format.st.s = 1; + } else { + this._format.st.s = 0; + } + this._setBooleanRule(); + + return this; + } + + /** + * Sets text underlining for the conditional format rule's format. If underline is true, the rule underlines text if the condition is met; if false, the rule removes any existing underlines if the condition is met. Passing in null removes the underline format setting from the rule. + */ + setUnderline(underline: Nullable): ConditionalFormatRuleBuilder { + if (!this._format.ul) { + this._format.ul = { s: 1 }; + } + + if (underline === null) { + delete this._format.st; + } else if (underline) { + this._format.ul.s = 1; + } else { + this._format.ul.s = 0; + } + this._setBooleanRule(); + + return this; + } + + /** + * Sets the conditional format rule to trigger when the cell is empty. + */ + whenCellEmpty(): ConditionalFormatRuleBuilder { + const type = ConditionType.BLANK; + this._condition = { + type, + }; + + return this; + } + + /** + * Sets the conditional format rule to trigger when the cell is not empty. + */ + whenCellNotEmpty(): ConditionalFormatRuleBuilder { + const type = ConditionType.NOT_BLANK; + this._condition = { + type, + }; + + return this; + } + + /** + * Sets the conditional format rule to trigger when a date is after the given value. + */ + whenDateAfter(date: RelativeDate | Date): ConditionalFormatRuleBuilder { + this._getDate(ConditionType.DATE_AFTER, date); + return this; + } + + /** + * Sets the conditional format rule to trigger when a date is before the given date. + */ + whenDateBefore(date: RelativeDate | Date): ConditionalFormatRuleBuilder { + this._getDate(ConditionType.DATE_BEFORE, date); + return this; + } + + /** + * Sets the conditional format rule to trigger when a date is equal to the given relative date. + */ + whenDateEqualTo(date: RelativeDate | Date): ConditionalFormatRuleBuilder { + this._getDate(ConditionType.DATE_EQ, date); + return this; + } + + /** + * Sets the conditional format rule to trigger when a number falls between, or is either of, two specified values. + */ + whenNumberBetween(start: number, end: number): ConditionalFormatRuleBuilder { + let value: number[]; + if (start === end) { + value = [start]; + } else if (start > end) { + value = [end, start]; + } else { + value = [start, end]; + } + + this._getNumber(ConditionType.NUMBER_BETWEEN, value); + return this; + } + + /** + * Sets the conditional format rule to trigger when a number is equal to the given value. + */ + whenNumberEqualTo(number: number): ConditionalFormatRuleBuilder { + this._getNumber(ConditionType.NUMBER_EQ, [number]); + return this; + } + + /** + * Sets the conditional format rule to trigger when a number is greater than the given value. + */ + whenNumberGreaterThan(number: number): ConditionalFormatRuleBuilder { + this._getNumber(ConditionType.NUMNUMBER_BETWEENR_GREATER, [number]); + return this; + } + + /** + * Sets the conditional format rule to trigger when a number is greater than or equal to the given value. + */ + whenNumberGreaterThanOrEqualTo(number: number): ConditionalFormatRuleBuilder { + this._getNumber(ConditionType.NUMBER_GREATER_THAN_EQ, [number]); + return this; + } + + /** + * Sets the conditional conditional format rule to trigger when a number less than the given value. + */ + whenNumberLessThan(number: number): ConditionalFormatRuleBuilder { + this._getNumber(ConditionType.NUMBER_LESS, [number]); + return this; + } + + /** + * Sets the conditional format rule to trigger when a number less than or equal to the given value. + */ + whenNumberLessThanOrEqualTo(number: number): ConditionalFormatRuleBuilder { + this._getNumber(ConditionType.NUMBER_LESS_THAN_EQ, [number]); + return this; + } + + /** + * Sets the conditional format rule to trigger when a number does not fall between, and is neither of, two specified values. + */ + whenNumberNotBetween(start: number, end: number): ConditionalFormatRuleBuilder { + let value: number[]; + if (start === end) { + value = [start]; + } else if (start > end) { + value = [end, start]; + } else { + value = [start, end]; + } + + this._getNumber(ConditionType.NUMBER_NOT_BETWEEN, value); + return this; + } + + /** + * Sets the conditional format rule to trigger when a number is not equal to the given value. + */ + whenNumberNotEqualTo(number: number): ConditionalFormatRuleBuilder { + this._getNumber(ConditionType.NUMBER_NOT_EQ, [number]); + return this; + } + + /** + * Sets the conditional format rule to trigger when that the given formula evaluates to true. + */ + whenFormulaSatisfied(formula: string): ConditionalFormatRuleBuilder { + this._getText(ConditionType.CUSTOM_FORMULA, formula); + return this; + } + + /** + * Sets the conditional format rule to trigger when that the input contains the given value. + */ + whenTextContains(text: string): ConditionalFormatRuleBuilder { + this._getText(ConditionType.TEXT_CONTAINS, text); + return this; + } + + /** + * Sets the conditional format rule to trigger when that the input does not contain the given value. + */ + whenTextDoesNotContain(text: string): ConditionalFormatRuleBuilder { + this._getText(ConditionType.TEXT_NOT_CONTAINS, text); + return this; + } + + /** + * Sets the conditional format rule to trigger when that the input ends with the given value. + */ + whenTextEndsWith(text: string): ConditionalFormatRuleBuilder { + this._getText(ConditionType.TEXT_ENDS_WITH, text); + return this; + } + + /** + * Sets the conditional format rule to trigger when that the input is equal to the given value. + */ + whenTextEqualTo(text: string): ConditionalFormatRuleBuilder { + this._getText(ConditionType.TEXT_EQ, text); + return this; + } + + /** + * Sets the conditional format rule to trigger when that the input starts with the given value. + */ + whenTextStartsWith(text: string): ConditionalFormatRuleBuilder { + this._getText(ConditionType.TEXT_STARTS_WITH, text); + return this; + } + + /** + * Sets the conditional format rule to criteria defined by BooleanCriteria values, typically taken from the criteria and arguments of an existing rule. + */ + withCriteria(criteria: ConditionType, args: ConditionValue[]): ConditionalFormatRuleBuilder { + this._condition = { + type: criteria, + values: args, + }; + return this; + } + + /** + * Clears the conditional format rule's gradient maxpoint value, and instead uses the maximum value in the rule's ranges. Also sets the gradient's maxpoint color to the input color. + */ + setGradientMaxpoint(color: string): ConditionalFormatRuleBuilder { + this._maxPoint = { + colorStyle: { + rgb: color, + }, + }; + + return this; + } + + /** + * Sets the conditional format rule's gradient maxpoint fields. + */ + setGradientMaxpointWithValue(color: string, type: InterpolationPointType, value: string): ConditionalFormatRuleBuilder { + this._maxPoint = { + colorStyle: { + rgb: color, + }, + type, + value, + }; + + return this; + } + + /** + * Sets the conditional format rule's gradient midpoint fields. Clears all of the midpoint fields if the passed in interpolation type is null. + */ + setGradientMidpointWithValue(color: string, type: Nullable, value: string): ConditionalFormatRuleBuilder { + if (!type) { + this._midPoint = null; + } else { + this._midPoint = { + colorStyle: { + rgb: color, + }, + type, + value, + }; + } + + return this; + } + + /** + * Clears the conditional format rule's gradient minpoint value, and instead uses the minimum value in the rule's ranges. Also sets the gradient's minpoint color to the input color. + */ + setGradientMinpoint(color: string): ConditionalFormatRuleBuilder { + this._minPoint = { + colorStyle: { + rgb: color, + }, + }; + + return this; + } + + /** + * Sets the conditional format rule's gradient minpoint fields. + */ + setGradientMinpointWithValue(color: string, type: InterpolationPointType, value: string): ConditionalFormatRuleBuilder { + this._minPoint = { + colorStyle: { + rgb: color, + }, + type, + value, + }; + + return this; + } +} + +// /** +// * Creates a builder for a conditional formatting rule. +// */ +// newConditionalFormatRule(): ConditionalFormatRuleBuilder { +// return new ConditionalFormatRuleBuilder(); +// } + +// /** +// * Get all conditional format rules in this sheet. +// */ +// getConditionalFormatRules(): ConditionalFormatRule[] { +// const condition = this._config.conditionalFormats; +// const rules = []; +// for (let i = 0; i < condition.length; i++) { +// rules.push( +// new ConditionalFormatRule(condition[i].ranges, condition[i].rule) +// ); +// } +// return rules; +// } + +// /** +// * Replaces all currently existing conditional format rules in the sheet with the input rules. Rules are evaluated in their input order. +// */ +// setConditionalFormatRules(rules: ConditionalFormatRule[]) { +// let ruleRange: IRangeData[] = []; +// for (let i = 0; i < rules.length; i++) { +// const rule = rules[i]; +// const rangeList = rule.getRanges(); +// const booleanConditon = rule.getBooleanCondition(); +// const gradientCondition = rule.getGradientCondition(); + +// if (booleanConditon) { +// ruleRange = this._setBooleanCondtionRange( +// ruleRange, +// rangeList, +// booleanConditon +// ); +// } else if (gradientCondition) { +// ruleRange = this._setGradientContionRange( +// ruleRange, +// rangeList, +// gradientCondition +// ); +// } +// } +// } + +// private _setBooleanCondtionRange = ( +// ranges: IRangeData[], +// rangeList: IRangeData[], +// booleanRule: BooleanRule +// ) => { +// const map = new Map([ +// [ +// ConditionType.CONDITION_TYPE_UNSPECIFIED, +// (cell: Nullable, values?: ConditionValue[]) => true, +// ], +// [ConditionType.BLANK, (cell) => !cell || cell.m === ''], +// [ConditionType.NOT_BLANK, (cell) => !!cell && !!cell.m], +// [ +// ConditionType.DATE_AFTER, +// (cell, values) => { +// values = values!; +// if (cell && cell.fm && cell.fm.t === FormatType.DATE) { +// if ('relativeDate' in values[0]) { +// } else { +// if (cell.v && cell.v > values[0].userEnteredValue!) { +// return true; +// } +// } +// } +// return false; +// }, +// ], +// [ +// ConditionType.DATE_BEFORE, +// (cell, values) => { +// values = values!; +// if (cell && cell.fm && cell.fm.t === FormatType.DATE) { +// if ('relativeDate' in values[0]) { +// } else { +// if (cell.v && cell.v < values[0].userEnteredValue!) { +// return true; +// } +// } +// } +// return false; +// }, +// ], +// [ +// ConditionType.DATE_EQ, +// (cell, values) => { +// values = values!; +// if (cell && cell.fm && cell.fm.t === FormatType.DATE) { +// if ('relativeDate' in values[0]) { +// } else { +// if (cell.v == values[0].userEnteredValue!) { +// return true; +// } +// } +// } +// return false; +// }, +// ], +// [ +// ConditionType.NUMBER_BETWEEN, +// (cell, values) => +// cell && +// cell.v && +// values && +// cell.v > values[0].userEnteredValue! && +// cell.v < values[1].userEnteredValue!, +// ], +// [ +// ConditionType.NUMBER_EQ, +// (cell, values) => +// cell && values && cell.v == values[0].userEnteredValue!, +// ], +// [ +// ConditionType.NUMNUMBER_BETWEENR_GREATER, +// (cell, values) => +// cell && cell.v && values && cell.v > values[0].userEnteredValue!, +// ], +// [ +// ConditionType.NUMBER_GREATER_THAN_EQ, +// (cell, values) => +// cell && +// cell.v && +// values && +// cell.v >= values[0].userEnteredValue!, +// ], +// [ +// ConditionType.NUMBER_GREATER_THAN_EQ, +// (cell, values) => +// values && +// cell && +// cell.v && +// cell.v >= values[0].userEnteredValue!, +// ], +// [ +// ConditionType.NUMBER_LESS, +// (cell, values) => +// values && cell && cell.v && cell.v < values[0].userEnteredValue!, +// ], +// [ +// ConditionType.NUMBER_LESS_THAN_EQ, +// (cell, values) => +// values && +// cell && +// cell.v && +// cell.v <= values[0].userEnteredValue!, +// ], +// [ +// ConditionType.NUMBER_NOT_BETWEEN, +// (cell, values) => +// values && +// cell && +// cell.v && +// (cell.v < values[0].userEnteredValue! || +// cell.v > values[1].userEnteredValue!), +// ], +// [ +// ConditionType.NUMBER_NOT_EQ, +// (cell, values) => +// values && cell && cell.v != values[0].userEnteredValue!, +// ], +// [ +// ConditionType.CUSTOM_FORMULA, +// (cell, values) => +// values && cell && cell.f == values[0].userEnteredValue!, +// ], +// [ +// ConditionType.TEXT_CONTAINS, +// (cell, values) => +// values && cell && cell.m?.includes(values[0].userEnteredValue!), +// ], +// [ +// ConditionType.TEXT_NOT_CONTAINS, +// (cell, values) => +// values && +// (!cell || +// !cell.m || +// !cell.m.includes(values[0].userEnteredValue!)), +// ], +// [ +// ConditionType.TEXT_ENDS_WITH, +// (cell, values) => +// values && +// cell && +// cell.m && +// new RegExp(values[0].userEnteredValue! + '$').test(cell.m), +// ], +// ]); + +// for (let i = 0; i < rangeList.length; i++) { +// const range = new Range(this, rangeList[i]); +// const matrix = range.getMatrix(); +// range.forEach((row, column) => { +// const cell = matrix.getValue(row, column); +// const condition = map.get(booleanRule.condition.type)!( +// cell, +// booleanRule.condition.values +// ); +// if (condition) { +// ranges.push({ +// startRow: row, +// endRow: row, +// startColumn: column, +// endColumn: column, +// }); +// } +// }); +// } + +// return ranges; +// }; + +// private _setGradientContionRange = ( +// ranges: IRangeData[], +// rangeList: IRangeData[], +// booleanRule: GradientRule +// ) => { +// return ranges; +// }; diff --git a/packages/sheets-plugin-conditional-format/src/Conflict/ConflictConditionalFormat.ts b/packages/sheets-plugin-conditional-format/src/Conflict/ConflictConditionalFormat.ts new file mode 100644 index 0000000000..b313ae8535 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Conflict/ConflictConditionalFormat.ts @@ -0,0 +1 @@ +export class ConflictConditionalFormat {} diff --git a/packages/sheets-plugin-conditional-format/src/Conflict/IConflict.ts b/packages/sheets-plugin-conditional-format/src/Conflict/IConflict.ts new file mode 100644 index 0000000000..02b0979966 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Conflict/IConflict.ts @@ -0,0 +1 @@ +export class IConflict {} diff --git a/packages/sheets-plugin-conditional-format/src/Conflict/index.ts b/packages/sheets-plugin-conditional-format/src/Conflict/index.ts new file mode 100644 index 0000000000..013fea666d --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Conflict/index.ts @@ -0,0 +1,7 @@ +/** + * en: Handling action conflicts + * + * zh: 处理action冲突 + */ +export * from './IConflict'; +export * from './ConflictConditionalFormat'; diff --git a/packages/sheets-plugin-conditional-format/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-conditional-format/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..52c3c7173a --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const CONDITIONAL_FORMAT_PLUGIN_NAME = 'conditionalFormat'; diff --git a/packages/sheets-plugin-conditional-format/src/Const/index.ts b/packages/sheets-plugin-conditional-format/src/Const/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-conditional-format/src/Domain/ConditionalFormat.ts b/packages/sheets-plugin-conditional-format/src/Domain/ConditionalFormat.ts new file mode 100644 index 0000000000..1977a14391 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Domain/ConditionalFormat.ts @@ -0,0 +1 @@ +export class ConditionalFormat {} diff --git a/packages/sheets-plugin-conditional-format/src/Domain/index.ts b/packages/sheets-plugin-conditional-format/src/Domain/index.ts new file mode 100644 index 0000000000..d120ca01fb --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Domain/index.ts @@ -0,0 +1 @@ +export * from './ConditionalFormat'; diff --git a/packages/sheets-plugin-conditional-format/src/Enum/index.ts b/packages/sheets-plugin-conditional-format/src/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-conditional-format/src/IData/IConditionalFormat.ts b/packages/sheets-plugin-conditional-format/src/IData/IConditionalFormat.ts new file mode 100644 index 0000000000..a809ef3796 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/IData/IConditionalFormat.ts @@ -0,0 +1,4 @@ +export type IConfig = {}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-conditional-format/src/IData/IConditionalFormatRule.ts b/packages/sheets-plugin-conditional-format/src/IData/IConditionalFormatRule.ts new file mode 100644 index 0000000000..57f0335362 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/IData/IConditionalFormatRule.ts @@ -0,0 +1,52 @@ +import { ConditionType, IColor, IColorStyle, InterpolationPointType, IRangeData, IStyleData, RelativeDate } from '@univer/core'; + +/** + * The value of the condition. + */ +export type ConditionValue = { + relativeDate?: RelativeDate; + userEnteredValue?: string; +}; + +/** + * A condition that can evaluate to true or false. BooleanConditions are used by conditional formatting, data validation, and the criteria in filters. + */ +export type BooleanCondition = { + type: ConditionType; + values?: ConditionValue[]; +}; + +/** + * A rule that may or may not match, depending on the condition. + */ +export type BooleanRule = { + condition: BooleanCondition; + format: IStyleData; +}; + +/** + * A single interpolation point on a gradient conditional format. These pin the gradient color scale according to the color, type and value chosen. + */ +export type InterpolationPoint = { + color?: IColor; + colorStyle?: IColorStyle; + type?: InterpolationPointType; + value?: string; +}; + +/** + * A rule that applies a gradient color scale format, based on the interpolation points listed. The format of a cell will vary based on its contents as compared to the values of the interpolation points. + */ +export type GradientRule = { + minpoint: InterpolationPoint; + midpoint?: InterpolationPoint; + maxpoint: InterpolationPoint; +}; + +/** + * A rule describing a conditional format. + */ +export interface IConditionalFormatRule { + ranges: IRangeData[]; + rule: BooleanRule | GradientRule; +} diff --git a/packages/sheets-plugin-conditional-format/src/IData/index.ts b/packages/sheets-plugin-conditional-format/src/IData/index.ts new file mode 100644 index 0000000000..b1a7550367 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/IData/index.ts @@ -0,0 +1,2 @@ +export * from './IConditionalFormat'; +export * from './IConditionalFormatRule'; diff --git a/packages/sheets-plugin-conditional-format/src/Locale/en.ts b/packages/sheets-plugin-conditional-format/src/Locale/en.ts new file mode 100644 index 0000000000..da7db551a6 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Locale/en.ts @@ -0,0 +1,222 @@ +export default { + conditionalformat: { + conditionalFormatLabel: 'Conditional Format', + conditionalformat_greaterThan: 'Conditionalformat-GreaterThan', + conditionalformat_greaterThan_title: 'Format cells greater than', + conditionalformat_lessThan: 'Conditionalformat-LessThan', + conditionalformat_lessThan_title: 'Format cells smaller than', + conditionalformat_betweenness: 'Conditionalformat-Betweenness', + conditionalformat_betweenness_title: 'Format cells with values between', + conditionalformat_equal: 'Conditionalformat-Equal', + conditionalformat_equal_title: 'Format cells equal to', + conditionalformat_textContains: 'Conditionalformat-TextContains', + conditionalformat_textContains_title: 'Format cells containing the following text', + conditionalformat_occurrenceDate: 'Conditionalformat-OccurrenceDate', + conditionalformat_occurrenceDate_title: 'Format cells containing the following dates', + conditionalformat_duplicateValue: 'Conditionalformat-DuplicateValue', + conditionalformat_duplicateValue_title: 'Format cells containing the following types of values', + conditionalformat_top10: 'Conditionalformat-Top10', + conditionalformat_top10_percent: 'Conditionalformat-Top10%', + conditionalformat_top10_title: 'Format the cells with the highest value', + conditionalformat_last10: 'Conditionalformat-Last10', + conditionalformat_last10_percent: 'Conditionalformat-Last10%', + conditionalformat_last10_title: 'Format the cells with the smallest value', + conditionalformat_AboveAverage: 'Conditionalformat-AboveAverage', + conditionalformat_AboveAverage_title: 'Format cells above average', + conditionalformat_SubAverage: 'Conditionalformat-SubAverage', + conditionalformat_SubAverage_title: 'Format cells below average', + rule: 'Rule', + newRule: 'New rule', + editRule: 'Edit rule', + deleteRule: 'Delete rule', + deleteCellRule: 'Delete cell rule', + deleteSheetRule: 'Delete sheet rule', + manageRules: 'Management rules', + showRules: 'Show its formatting rules', + highlightCellRules: 'Highlight cell rules', + itemSelectionRules: 'Item selection rules', + conditionalformatManageRules: 'Conditional Formatting Rule Manager', + format: 'Format', + setFormat: 'Set format', + setAs: 'Set as', + setAsByArea: 'For the selected area, set to', + applyRange: 'Apply range', + selectRange: 'Select application range', + selectRange_percent: 'Percentage of selected range', + selectRange_average: 'Average value of selected range', + selectRange_value: 'Value in the selected range', + pleaseSelectRange: 'Please select application range', + selectDataRange: 'Select data range', + selectCell: 'select cell', + pleaseSelectCell: 'Please select cell', + pleaseSelectADate: 'Please select a date', + pleaseEnterInteger: 'Please enter an integer between 1 and 1000', + onlySingleCell: 'Only a single cell can be referenced', + conditionValueCanOnly: 'The condition value can only be a number or a single cell', + ruleTypeItem1: 'Format all cells based on their respective values', + ruleTypeItem2: 'Only format cells that contain', + ruleTypeItem2_title: 'Only for cells that meet the following conditions', + ruleTypeItem3: 'Format only the top or bottom numbers', + ruleTypeItem3_title: 'Is the value in the following ranking', + ruleTypeItem4: 'Format only values above or below the average', + ruleTypeItem4_title: 'Is a value that satisfies the following conditions', + ruleTypeItem5: 'Format only unique or repeated values', + ruleTypeItem6: 'Use formulas to determine which cells to format', + formula: 'Formula', + textColor: 'Text color', + cellColor: 'Cell color', + confirm: 'Confirm', + confirmColor: 'Confirm color', + cancel: 'Cancel', + close: 'Close', + clearColorSelect: 'Clear color select', + sheet: 'Sheet', + currentSheet: 'Current Sheet', + dataBar: 'data bar', + dataBarColor: 'data bar color', + gradientDataBar_1: 'Blue-white gradient data bar', + gradientDataBar_2: 'Green-white gradient data bar', + gradientDataBar_3: 'Red-white gradient data bar', + gradientDataBar_4: 'Orange-white gradient stripes', + gradientDataBar_5: 'Light blue-white gradient stripes', + gradientDataBar_6: 'Purple-white gradient data bar', + solidColorDataBar_1: 'Blue data bar', + solidColorDataBar_2: 'Green data bar', + solidColorDataBar_3: 'Red data bar', + solidColorDataBar_4: 'Orange data bar', + solidColorDataBar_5: 'Light blue data bar', + solidColorDataBar_6: 'Purple data bar', + colorGradation: 'color gradation', + colorGradation_1: 'Green-yellow-red color gradation', + colorGradation_2: 'Red-yellow-green color gradation', + colorGradation_3: 'Green-white-red color gradation', + colorGradation_4: 'Red-white-green color gradation', + colorGradation_5: 'Blue-white-red color gradation', + colorGradation_6: 'Red-white-blue color gradation', + colorGradation_7: 'White-red color gradation', + colorGradation_8: 'Red-white color gradation', + colorGradation_9: 'Green-white color gradation', + colorGradation_10: 'White-green color gradation', + colorGradation_11: 'Green-yellow color gradation', + colorGradation_12: 'Yellow-green color gradation', + icons: 'icons', + pleaseSelectIcon: 'Please click to select a group of icons:', + cellValue: 'Cell value', + specificText: 'Specific text', + occurrence: { + text: 'Date', + example: 'YTD', + }, + greaterThan: { + text: 'Greater than', + example: '>', + }, + lessThan: { + text: 'Less than', + example: '<', + }, + between: { + text: 'Between', + example: '[]', + }, + equal: { + text: 'Equal', + example: '=', + }, + in: 'In', + between2: '', + contain: 'Contain', + textContains: { + text: 'Text contains', + example: '()', + }, + duplicateValue: { + text: 'Duplicate value', + example: '##', + }, + uniqueValue: 'Unique value', + top: 'Top', + top10: { + text: 'Top 10', + example: 'Top 10', + }, + top10_percent: { + text: 'Top 10%', + example: 'Top 10%', + }, + last: 'Last', + last10: { + text: 'Last 10', + example: 'Last 10', + }, + last10_percent: { + text: 'Last 10%', + example: 'Last 10%', + }, + oneself: '', + above: 'Above', + aboveAverage: { + text: 'Above average', + example: 'Above', + }, + below: 'Below', + belowAverage: { + text: 'Below average', + example: 'Below', + }, + all: 'All', + yesterday: 'YTD', + today: 'Today', + tomorrow: 'Tomorrow', + lastWeek: 'Last week', + thisWeek: 'This week', + lastMonth: 'Last month', + thisMonth: 'This month', + lastYear: 'Last year', + thisYear: 'This year', + last7days: 'Last 7 days', + last30days: 'Last 30 days', + next7days: 'Next 7 days', + next30days: 'Next 30 days', + next60days: 'Next 60 days', + chooseRuleType: 'Choose rule type', + editRuleDescription: 'Edit rule description', + newFormatRule: 'New format rule', + editFormatRule: 'Edit format rule', + formatStyle: 'Style', + fillType: 'Fill', + color: 'Color', + twocolor: 'Two-color', + tricolor: 'Tricolor', + multicolor: 'Multi color', + grayColor: 'Gray color', + gradient: 'Gradient', + solid: 'Solid', + maxValue: 'Max value', + medianValue: 'Median value', + minValue: 'Min value', + direction: 'Direction', + threeWayArrow: 'Three-way arrow', + fourWayArrow: 'Four-way arrow', + fiveWayArrow: 'Five-way arrow', + threeTriangles: 'Three triangles', + shape: 'Shape', + threeColorTrafficLight: 'Three-color traffic light', + fourColorTrafficLight: 'Four-color traffic light', + threeSigns: 'Three signs', + greenRedBlackGradient: 'Green-red-black gradient', + rimless: 'Rimless', + bordered: 'Bordered', + mark: 'Mark', + threeSymbols: 'Three symbols', + tricolorFlag: 'Tricolor flag', + circled: 'Circled', + noCircle: 'No circle', + grade: 'Grade', + grade4: '4 Grade', + grade5: '5 Grade', + threeStars: '3 Stars', + fiveQuadrantDiagram: 'Five-quadrant diagram', + fiveBoxes: '5 Boxes', + }, +}; diff --git a/packages/sheets-plugin-conditional-format/src/Locale/index.ts b/packages/sheets-plugin-conditional-format/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-conditional-format/src/Locale/zh.ts b/packages/sheets-plugin-conditional-format/src/Locale/zh.ts new file mode 100644 index 0000000000..1f8129f04d --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Locale/zh.ts @@ -0,0 +1,223 @@ +export default { + conditionalformat: { + conditionalFormatLabel: '条件格式', + conditionalformat_greaterThan: '条件格式——大于', + conditionalformat_greaterThan_title: '为大于以下值的单元格设置格式', + conditionalformat_lessThan: '条件格式——小于', + conditionalformat_lessThan_title: '为小于以下值的单元格设置格式', + conditionalformat_betweenness: '条件格式——介于', + conditionalformat_betweenness_title: '为介于以下值的单元格设置格式', + conditionalformat_equal: '条件格式——等于', + conditionalformat_equal_title: '为等于以下值的单元格设置格式', + conditionalformat_textContains: '条件格式——文本包含', + conditionalformat_textContains_title: '为包含以下文本的单元格设置格式', + conditionalformat_occurrenceDate: '条件格式——发生日期', + conditionalformat_occurrenceDate_title: '为包含以下日期的单元格设置格式', + conditionalformat_duplicateValue: '条件格式——重复值', + conditionalformat_duplicateValue_title: '为包含以下类型值的单元格设置格式', + conditionalformat_top10: '条件格式——前 10 项', + conditionalformat_top10_percent: '条件格式——前 10%', + conditionalformat_top10_title: '为值最大的那些单元格设置格式', + conditionalformat_last10: '条件格式——最后 10 项', + conditionalformat_last10_percent: '条件格式——最后 10%', + conditionalformat_last10_title: '为值最小的那些单元格设置格式', + conditionalformat_AboveAverage: '条件格式——高于平均值', + conditionalformat_AboveAverage_title: '为高于平均值的单元格设置格式', + conditionalformat_SubAverage: '条件格式——低于平均值', + conditionalformat_SubAverage_title: '为低于平均值的单元格设置格式', + rule: '规则', + newRule: '新建规则', + editRule: '编辑规则', + deleteRule: '删除规则', + deleteCellRule: '清除所选单元格的规则', + deleteSheetRule: '清除整个工作表的规则', + manageRules: '管理规则', + showRules: '显示其格式规则', + highlightCellRules: '突出显示单元格规则', + itemSelectionRules: '项目选取规则', + conditionalformatManageRules: '条件格式规则管理器', + format: '格式', + setFormat: '设置格式', + setAs: '设置为', + setAsByArea: '针对选定区域,设置为', + applyRange: '应用范围', + selectRange: '点击选择应用范围', + selectRange_percent: '所选范围的百分比', + selectRange_average: '选定范围的平均值', + selectRange_value: '选定范围中的数值', + pleaseSelectRange: '请选择应用范围', + selectDataRange: '点击选择数据范围', + selectCell: '选择单元格', + pleaseSelectCell: '请选择单元格', + pleaseSelectADate: '请选择日期', + pleaseEnterInteger: '请输入一个介于 1 和 1000 之间的整数', + onlySingleCell: '只能对单个单元格进行引用', + conditionValueCanOnly: '条件值只能是数字或者单个单元格', + ruleTypeItem1: '基于各自值设置所有单元格的格式', + ruleTypeItem2: '只为包含以下内容的单元格设置格式', + ruleTypeItem2_title: '只为满足以下条件的单元格', + ruleTypeItem3: '仅对排名靠前或靠后的数值设置格式', + ruleTypeItem3_title: '为以下排名内的值', + ruleTypeItem4: '仅对高于或低于平均值的数值设置格式', + ruleTypeItem4_title: '为满足以下条件的值', + ruleTypeItem5: '仅对唯一值或重复值设置格式', + ruleTypeItem6: '使用公式确定要设置格式的单元格', + formula: '公式', + textColor: '文本颜色', + cellColor: '单元格颜色', + confirm: '确定', + confirmColor: '确定颜色', + cancel: '取消', + close: '关闭', + clearColorSelect: '清除颜色选择', + sheet: '表', + currentSheet: '当前工作表', + dataBar: '数据条', + dataBarColor: '数据条颜色', + gradientDataBar_1: '蓝-白渐变数据条', + gradientDataBar_2: '绿-白渐变数据条', + gradientDataBar_3: '红-白渐变数据条', + gradientDataBar_4: '橙-白渐变数据条', + gradientDataBar_5: '浅蓝-白渐变数据条', + gradientDataBar_6: '紫-白渐变数据条', + solidColorDataBar_1: '蓝色数据条', + solidColorDataBar_2: '绿色数据条', + solidColorDataBar_3: '红色数据条', + solidColorDataBar_4: '橙色数据条', + solidColorDataBar_5: '浅蓝色数据条', + solidColorDataBar_6: '紫色数据条', + colorGradation: '色阶', + colorGradation_1: '绿-黄-红色阶', + colorGradation_2: '红-黄-绿色阶', + colorGradation_3: '绿-白-红色阶', + colorGradation_4: '红-白-绿色阶', + colorGradation_5: '蓝-白-红色阶', + colorGradation_6: '红-白-蓝色阶', + colorGradation_7: '白-红色阶', + colorGradation_8: '红-白色阶', + colorGradation_9: '绿-白色阶', + colorGradation_10: '白-绿色阶', + colorGradation_11: '绿-黄色阶', + colorGradation_12: '黄-绿色阶', + icons: '图标集', + pleaseSelectIcon: '请点击选择一组图标:', + cellValue: '单元格值', + specificText: '特定文本', + occurrence: { + text: '发生日期', + example: '昨天', + }, + greaterThan: { + text: '大于', + example: '>', + }, + lessThan: { + text: '小于', + example: '<', + }, + between: { + text: '介于', + example: '[]', + }, + equal: { + text: '等于', + example: '=', + }, + in: '和', + to: '到', + between2: '之间', + contain: '包含', + textContains: { + text: '文本包含', + example: '()', + }, + duplicateValue: { + text: '重复值', + example: '##', + }, + uniqueValue: '唯一值', + top: '前', + top10: { + text: '前 10 项', + example: '前 10 项', + }, + top10_percent: { + text: '前 10%', + example: '前 10%', + }, + last: '后', + last10: { + text: '后 10 项', + example: '后 10 项', + }, + last10_percent: { + text: '后 10%', + example: '后 10%', + }, + oneself: '个', + above: '高于', + aboveAverage: { + text: '高于平均值', + example: '高于', + }, + below: '低于', + belowAverage: { + text: '低于平均值', + example: '低于', + }, + all: '全部', + yesterday: '昨天', + today: '今天', + tomorrow: '明天', + lastWeek: '上周', + thisWeek: '本周', + lastMonth: '上月', + thisMonth: '本月', + lastYear: '去年', + thisYear: '本年', + last7days: '最近7天', + last30days: '最近30天', + next7days: '未来7天', + next30days: '未来30天', + next60days: '未来60天', + chooseRuleType: '选择规则类型', + editRuleDescription: '编辑规则说明', + newFormatRule: '新建格式规则', + editFormatRule: '编辑格式规则', + formatStyle: '格式样式', + fillType: '填充类型', + color: '颜色', + twocolor: '双色', + tricolor: '三色', + multicolor: '彩色', + grayColor: '灰色', + gradient: '渐变', + solid: '实心', + maxValue: '最大值', + medianValue: '中间值', + minValue: '最小值', + direction: '方向', + threeWayArrow: '三向箭头', + fourWayArrow: '四向箭头', + fiveWayArrow: '五向箭头', + threeTriangles: '3个三角形', + shape: '形状', + threeColorTrafficLight: '三色交通灯', + fourColorTrafficLight: '四色交通灯', + threeSigns: '三标志', + greenRedBlackGradient: '绿-红-黑渐变', + rimless: '无边框', + bordered: '有边框', + mark: '标记', + threeSymbols: '三个符号', + tricolorFlag: '三色旗', + circled: '有圆圈', + noCircle: '无圆圈', + grade: '等级', + grade4: '四等级', + grade5: '五等级', + threeStars: '3个星形', + fiveQuadrantDiagram: '五象限图', + fiveBoxes: '5个框', + }, +}; diff --git a/packages/sheets-plugin-conditional-format/src/ProxyData/IProxyData.ts b/packages/sheets-plugin-conditional-format/src/ProxyData/IProxyData.ts new file mode 100644 index 0000000000..0bd8a3cf07 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/ProxyData/IProxyData.ts @@ -0,0 +1 @@ +export class IProxyData {} diff --git a/packages/sheets-plugin-conditional-format/src/ProxyData/IProxyDataSet.ts b/packages/sheets-plugin-conditional-format/src/ProxyData/IProxyDataSet.ts new file mode 100644 index 0000000000..9bf75cd768 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/ProxyData/IProxyDataSet.ts @@ -0,0 +1 @@ +export class IProxyDataSet {} diff --git a/packages/sheets-plugin-conditional-format/src/ProxyData/ProxyDataConditionalFormat.ts b/packages/sheets-plugin-conditional-format/src/ProxyData/ProxyDataConditionalFormat.ts new file mode 100644 index 0000000000..51a28ba60d --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/ProxyData/ProxyDataConditionalFormat.ts @@ -0,0 +1 @@ +export class IProxyDataConditionalFormat {} diff --git a/packages/sheets-plugin-conditional-format/src/ProxyData/index.ts b/packages/sheets-plugin-conditional-format/src/ProxyData/index.ts new file mode 100644 index 0000000000..d91b8ae927 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/ProxyData/index.ts @@ -0,0 +1,8 @@ +/** + * en: Transfer to background data protocol + * + * zh: 传输到后台数据协议 + */ +export * from './IProxyData'; +export * from './IProxyDataSet'; +export * from './ProxyDataConditionalFormat'; diff --git a/packages/sheets-plugin-conditional-format/src/Service/ConditionalFormatService.ts b/packages/sheets-plugin-conditional-format/src/Service/ConditionalFormatService.ts new file mode 100644 index 0000000000..a33b1cda48 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Service/ConditionalFormatService.ts @@ -0,0 +1 @@ +export class ConditionalFormatService {} diff --git a/packages/sheets-plugin-conditional-format/src/Service/index.ts b/packages/sheets-plugin-conditional-format/src/Service/index.ts new file mode 100644 index 0000000000..61a48f2689 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Service/index.ts @@ -0,0 +1,7 @@ +/** + * + * en: Operate and register localData + * + * zh: 操作、注册localData + */ +export * from './ConditionalFormatService'; diff --git a/packages/sheets-plugin-conditional-format/src/Shared/index.ts b/packages/sheets-plugin-conditional-format/src/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-conditional-format/src/Types/index.d.ts b/packages/sheets-plugin-conditional-format/src/Types/index.d.ts new file mode 100644 index 0000000000..b5bef1766f --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-conditional-format' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-conditional-format/src/UI/ConditionContent.tsx b/packages/sheets-plugin-conditional-format/src/UI/ConditionContent.tsx new file mode 100644 index 0000000000..315f3a5b3d --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/UI/ConditionContent.tsx @@ -0,0 +1,347 @@ +import { BaseComponentRender, BaseComponentSheet, Component } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; + +import styles from './index.module.less'; + +type ConditionContentProps = { + config?: any; + type: string; + genre: string; +}; +type ConditionContentState = { + condition: ConditionProps; + setting: Record; + duplicateOption: LabelProps[]; +}; +type LabelProps = { + locale?: string; + label?: string; + value?: string; +}; +type ConditionProps = { + title: string; + subtitle: string; + to?: string; + prefix?: string; + suffix?: string; +}; + +export class ConditionContent extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize() { + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + greater: { + title: { + locale: 'conditionalformat.conditionalformat_greaterThan', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_greaterThan_title', + }, + }, + less: { + title: { + locale: 'conditionalformat.conditionalformat_lessThan', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_lessThan_title', + }, + }, + between: { + title: { + locale: 'conditionalformat.conditionalformat_betweenness', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_betweenness_title', + }, + to: { + locale: 'conditionalformat.to', + }, + }, + equal: { + title: { + locale: 'conditionalformat.conditionalformat_equal', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_equal_title', + }, + }, + text: { + title: { + locale: 'conditionalformat.conditionalformat_textContains', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_textContains_title', + }, + }, + date: { + title: { + locale: 'conditionalformat.conditionalformat_occurrenceDate', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_occurrenceDate_title', + }, + }, + duplicate: { + title: { + locale: 'conditionalformat.conditionalformat_duplicateValue', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_duplicateValue_title', + }, + }, + top10: { + title: { + locale: 'conditionalformat.conditionalformat_top10', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_top10_title', + }, + prefix: { + locale: 'conditionalformat.top', + }, + suffix: { + locale: 'conditionalformat.oneself', + }, + }, + top10_percent: { + title: { + locale: 'conditionalformat.conditionalformat_top10_percent', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_top10_title', + }, + prefix: { + locale: 'conditionalformat.top', + }, + }, + last10: { + title: { + locale: 'conditionalformat.conditionalformat_last10', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_last10_title', + }, + prefix: { + locale: 'conditionalformat.last', + }, + suffix: { + locale: 'conditionalformat.oneself', + }, + }, + last10_percent: { + title: { + locale: 'conditionalformat.conditionalformat_last10_percent', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_last10_title', + }, + prefix: { + locale: 'conditionalformat.last', + }, + }, + above: { + title: { + locale: 'conditionalformat.conditionalformat_AboveAverage', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_AboveAverage_title', + }, + }, + below: { + title: { + locale: 'conditionalformat.conditionalformat_SubAverage', + }, + subtitle: { + locale: 'conditionalformat.conditionalformat_SubAverage_title', + }, + }, + setting: { + setAs: { + locale: 'conditionalformat.setAs', + label: '', + }, + setAreaAs: { + locale: 'conditionalformat.setAsByArea', + label: '', + }, + textColor: { locale: 'conditionalformat.textColor' }, + cellColor: { locale: 'conditionalformat.cellColor' }, + }, + condition: { + title: '', + subtitle: '', + to: '', + prefix: '', + suffix: '', + }, + duplicateOption: [ + { + locale: 'conditionalformat.duplicateValue.text', + value: '', + label: '', + }, + { + locale: 'conditionalformat.uniqueValue', + value: '', + label: '', + }, + ], + }; + } + + setLocale() { + // setLocale for diffrent type of modal + const locale = this._context.getLocale(); + const genre = this.props.genre; + + function getLabel(obj: object) { + Object.keys(obj).forEach((item) => { + obj[item].label = locale.get(obj[item].locale); + }); + } + + this.setState((prevState) => { + let { setting, condition, duplicateOption } = prevState; + // set for condition + let obj = prevState[genre]; + getLabel(obj); + getLabel(setting); + condition.title = obj.title.label; + condition.subtitle = obj.subtitle.label; + if (obj.to) condition.to = obj.to.label; + if (obj.prefix) condition.prefix = obj.prefix.label; + if (obj.suffix) condition.suffix = obj.suffix.label; + + // set for duplicateOption + duplicateOption.forEach((item) => { + if (item) { + item.label = locale.get(item.locale || ''); + } + }); + + return { + // render modal's Locale + condition, + duplicateOption, + }; + }); + } + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + setValue = (value: object, fn?: () => void) => { + this.setState( + (prevState) => ({ + ...value, + }), + fn + ); + }; + + pickTextColor = () => {}; + + pickCellColor = () => {}; + + cancelColor = () => {}; + + // render different type of modal's rangepicker + getCellRange = () => { + const CellRangeModal = this.Render.renderFunction('CellRangeModal'); + const { type } = this.props; + const { condition, duplicateOption, setting } = this.state; + if (type === 'text') { + return ( + <> + +

        {setting.setAs.label}

        + + ); + } + if (type === 'between') { + return ( + <> +
        + + {condition.to} + +
        +

        {setting.setAs.label}

        + + ); + } + if (type === 'select') { + return ( + <> + +

        {setting.setAs.label}

        + + ); + } + if (type === 'input') { + const Input = this.Render.renderFunction('Input'); + return ( + <> +
        + {condition.prefix} + + {condition.suffix ? condition.suffix : '%'} +
        +

        {setting.setAs.label}

        + + ); + } + if (type === 'none') { + return

        {setting.setAreaAs.label}:

        ; + } + }; + + render(props: ConditionContentProps, state: ConditionContentState) { + const Checkbox = this.Render.renderFunction('Checkbox'); + const ColorPicker = this.Render.renderFunction('ColorPicker'); + const { condition, setting } = state; + return ( +
        +

        {condition.title}

        +
        +

        {condition.subtitle}:

        + {this.getCellRange()} +
        +
        +
        + {setting.textColor.label} + +
        +
        + {setting.cellColor.label} + +
        +
        +
        + ); + } +} diff --git a/packages/sheets-plugin-conditional-format/src/UI/ConditionalFormatButton.tsx b/packages/sheets-plugin-conditional-format/src/UI/ConditionalFormatButton.tsx new file mode 100644 index 0000000000..2d1875617a --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/UI/ConditionalFormatButton.tsx @@ -0,0 +1,397 @@ +import { BaseComponentRender, BaseComponentSheet, BaseSelectProps, Component, ModalProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; + +import { IProps } from '../IData/IConditionalFormat'; +import { ConditionContent } from './ConditionContent'; +import { IconContent } from './IconContent'; +import { ImageList } from './ImageList'; +import { RuleContent } from './RuleContent'; +import { RuleManage } from './RuleManage'; + +// Types for state +interface IState { + conditionalFormat: ConditionButtonProps; + modalData: ModalProps[]; +} + +type ConditionButtonProps = { + locale?: string; + iconName?: string; + label?: string | JSX.Element; + value?: string; + icon?: JSX.Element | string | null | undefined; + type?: string; + tooltip?: string; + border?: boolean; + show?: boolean; + children?: ConditionButtonProps[]; + className?: string; + onClick?: (...arg: any[]) => void; + style?: JSX.CSSProperties; + genre?: string; +}; + +export class ConditionalFormatButton extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize(props: IProps) { + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + const ConditionalFormatIcon = this.Render.renderFunction('ConditionalFormatIcon'); + const NextIcon = this.Render.renderFunction('NextIcon'); + const RightIcon = this.Render.renderFunction('RightIcon'); + + // super(props); + + this.state = { + conditionalFormat: { + locale: 'conditionalformat.conditionalFormatLabel', + type: 'select', + label: , + icon: , + show: true, + children: [ + { + locale: 'conditionalformat.highlightCellRules', + icon: , + onClick: this.showContentModal, + children: [ + { + locale: 'conditionalformat.greaterThan.text', + iconName: 'conditionalformat.greaterThan.example', + type: 'text', + genre: 'greater', + }, + { + locale: 'conditionalformat.lessThan.text', + iconName: 'conditionalformat.lessThan.example', + type: 'text', + genre: 'less', + }, + { + locale: 'conditionalformat.between.text', + iconName: 'conditionalformat.between.example', + type: 'between', + genre: 'between', + }, + { + locale: 'conditionalformat.equal.text', + iconName: 'conditionalformat.equal.example', + type: 'text', + genre: 'equal', + }, + { + locale: 'conditionalformat.textContains.text', + iconName: 'conditionalformat.textContains.example', + type: 'text', + genre: 'text', + }, + { + locale: 'conditionalformat.occurrence.text', + iconName: 'conditionalformat.occurrence.example', + type: 'date', + genre: 'date', + }, + { + locale: 'conditionalformat.duplicateValue.text', + iconName: 'conditionalformat.duplicateValue.example', + type: 'select', + genre: 'duplicate', + }, + ], + }, + { + locale: 'conditionalformat.itemSelectionRules', + icon: , + onClick: this.showContentModal, + children: [ + { + locale: 'conditionalformat.top10.text', + iconName: 'conditionalformat.top10.example', + type: 'input', + genre: 'top10', + }, + { + locale: 'conditionalformat.top10_percent.text', + iconName: 'conditionalformat.top10_percent.example', + type: 'input', + genre: 'top10_percent', + }, + { + locale: 'conditionalformat.last10.text', + iconName: 'conditionalformat.last10.example', + type: 'input', + genre: 'last10', + }, + { + locale: 'conditionalformat.last10_percent.text', + iconName: 'conditionalformat.last10_percent.example', + type: 'input', + genre: 'last10_percent', + }, + { + locale: 'conditionalformat.aboveAverage.text', + iconName: 'conditionalformat.aboveAverage.example', + type: 'none', + genre: 'above', + }, + { + locale: 'conditionalformat.belowAverage.text', + iconName: 'conditionalformat.belowAverage.example', + type: 'none', + genre: 'below', + }, + ], + }, + { + locale: 'conditionalformat.dataBar', + icon: , + children: [ + { + label: , + className: 'univer-image-li', + }, + ], + }, + { + locale: 'conditionalformat.colorGradation', + icon: , + children: [ + { + label: , + className: 'univer-image-li', + }, + ], + }, + { + locale: 'conditionalformat.icons', + onClick: this.showIconModal, + }, + { + locale: 'conditionalformat.newRule', + onClick: this.showRuleModal, + border: true, + }, + { + locale: 'conditionalformat.deleteRule', + icon: , + children: [ + { + locale: 'conditionalformat.deleteSheetRule', + }, + ], + }, + { + locale: 'conditionalformat.manageRules', + onClick: this.showManageModal, + }, + ], + }, + modalData: [ + { + show: false, + group: [ + { + locale: 'button.confirm', + type: 'primary', + }, + { + locale: 'button.cancel', + }, + ], + children: , + onCancel: () => { + this.showModal(0, false); + }, + }, + { + locale: 'conditionalformat.icons', + show: false, + group: [ + { + locale: 'button.cancel', + }, + ], + children: , + onCancel: () => { + this.showModal(1, false); + }, + }, + { + locale: 'conditionalformat.newFormatRule', + show: false, + group: [ + { + locale: 'button.confirm', + type: 'primary', + }, + { + locale: 'button.cancel', + }, + ], + children: , + onCancel: () => { + this.showModal(2, false); + }, + }, + { + locale: 'conditionalformat.conditionalformatManageRules', + show: false, + group: [ + { + locale: 'button.confirm', + type: 'primary', + }, + { + locale: 'button.cancel', + }, + ], + children: , + onCancel: () => { + this.showModal(3, false); + }, + }, + ], + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.conditionalFormat; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}`); + + // set current Locale string for select + item.children?.forEach((ele: any) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + if (ele.children) { + ele.children.forEach((node: any) => { + if (node.locale) { + node.label = locale.get(node.locale); + } + if (node.iconName) { + node.icon = locale.get(node.iconName); + } + }); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + // set current Locale for modal + let { modalData } = prevState; + modalData.forEach((ele) => { + ele.title = ele.locale ? locale.get(ele.locale) : ''; + if (ele.group && ele.group.length) { + ele.group.forEach((node) => { + node.label = locale.get(node.locale!); + }); + } + }); + + return { + conditionalFormat: item, + modalData, + }; + }); + } + + // showmodal + showModal = (index: number, isShow: boolean) => { + this.setState((prevState) => { + let { modalData } = prevState; + + modalData[index].show = isShow; + return { + modalData, + }; + }); + }; + + // show different type of modal + showContentModal = (...a: any[]) => { + const item = a[2].item; + this.setState((prevState) => { + let { modalData } = prevState; + modalData[0].children = ; + modalData[0].show = true; + + return { + modalData, + }; + }); + }; + + showRuleModal = () => { + this.showModal(2, true); + }; + + showManageModal = () => { + this.showModal(3, true); + }; + + showIconModal = () => { + this.showModal(1, true); + }; + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const Select = this.Render.renderFunction('Select'); + const Modal = this.Render.renderFunction('Modal'); + const { conditionalFormat, modalData } = state; + // Set Provider for entire Container + return ( + <> + + {fillType.map((item) => ( + + ))} + +
        +
        + {all.color.label}: +
        + + ); + } + if (formatType === 'colorGradation') { + return ( + <> +
        + {all.fillType.label} + +
        +
        + {all.color.label}: +
        +
        + {all.color.label}: +
        + + ); + } + if (formatType === 'icons') { + return ( + <> +
        + {all.fillType.label} +
        + + ); + } + }; + + // render content for different type + getEditContent = () => { + const { type } = this.props; + if (type === 'contain') { + const CellRangeModal = this.Render.renderFunction('CellRangeModal'); + + const { cellOption, mathOption, textOptions, contain, containType } = this.state; + return ( + <> +

        {contain.title.label}

        +
        + + {containType === 'cellValue' ? ( + <> + + + + ) : ( + '' + )} + {containType === 'specificText' ? ( + <> + + + + ) : ( + '' + )} + {containType === 'occurrence' ? ( + <> + + + ) : ( + '' + )} +
        + + ); + } + if (type === 'rank') { + const { rankOption, rank } = this.state; + const Checkbox = this.Render.renderFunction('Checkbox'); + + const Input = this.Render.renderFunction('Input'); + return ( + <> +

        {rank.title.label}

        +
        + + + {rank.subtitle.label} +
        + + ); + } + if (type === 'average') { + const { averageOption, average } = this.state; + return ( + <> +

        {average.title.label}

        +
        + + {average.subtitle.label} +
        + + ); + } + if (type === 'duplicate') { + const { duplicateOption, duplicate } = this.state; + return ( + <> +

        {duplicate.title.label}

        +
        + + {duplicate.subtitle.label} +
        + + ); + } + if (type === 'formula') { + const { formula } = this.state; + const CellRangeModal = this.Render.renderFunction('CellRangeModal'); + + return ( + <> +

        {formula.title.label}

        +
        + +
        + + ); + } + }; + + pickTextColor = () => {}; + + pickCellColor = () => {}; + + cancelColor = () => {}; + + render(props: RuleEditProps, state: RuleEditState) { + const ColorPicker = this.Render.renderFunction('ColorPicker'); + const Checkbox = this.Render.renderFunction('Checkbox'); + const { type } = props; + const { all, formatStyle, setting } = state; + + if (type === 'all') { + return ( +
        +

        {all.title.label}

        +
        + {all.formatStyle.label}: + +
        + {this.getAllTypeContent()} +
        + ); + } + + return ( +
        +
        {this.getEditContent()}
        +

        {setting.setAs.label}

        +
        +
        + {setting.textColor.label} + +
        +
        + {setting.cellColor.label} + +
        +
        +
        + ); + } +} diff --git a/packages/sheets-plugin-conditional-format/src/UI/RuleManage.tsx b/packages/sheets-plugin-conditional-format/src/UI/RuleManage.tsx new file mode 100644 index 0000000000..47a2ebba59 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/UI/RuleManage.tsx @@ -0,0 +1,136 @@ +import { BaseComponentRender, BaseComponentSheet, Component } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import styles from './index.module.less'; + +type RuleManageProps = { + config?: any; +}; +type RuleManageState = { + title: LabelProps; + buttons: LabelProps[]; + info: LabelProps[]; +}; +type LabelProps = { + name?: string; + label?: string; + value?: string; + active?: boolean; +}; + +export class RuleManage extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize() { + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + title: { + name: 'conditionalformat.showRules', + }, + buttons: [ + { + name: 'conditionalformat.newRule', + }, + { + name: 'conditionalformat.editRule', + }, + { + name: 'conditionalformat.deleteRule', + }, + ], + info: [ + { + name: 'conditionalformat.rule', + }, + { + name: 'conditionalformat.format', + }, + { + name: 'conditionalformat.applyRange', + }, + ], + }; + } + + setLocale() { + // setLocale for diffrent type of modal + const locale = this._context.getLocale(); + // set Locale for array + function getArrayLabel(arr: LabelProps[]) { + return arr.map((item) => { + item.label = locale.get(item.name || ''); + return item; + }); + } + + this.setState((prevState) => { + let { title, buttons, info } = prevState; + buttons = getArrayLabel(buttons); + info = getArrayLabel(info); + + title.label = locale.get(title.name || ''); + + return { title, buttons, info }; + }); + } + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + setValue = (value: object, fn?: () => void) => { + this.setState( + (prevState) => ({ + ...value, + }), + fn + ); + }; + + render(props: RuleManageProps, state: RuleManageState) { + const { title, buttons, info } = state; + const Button = this.Render.renderFunction('Button'); + + return ( +
        +
        + {title.label} + +
        +
        +
        + {buttons.map((item) => ( + + ))} +
        +
        +
        + {info.map((item) => ( + {item.label} + ))} +
        +
        +
        +
        +
        +
        +
        + ); + } +} diff --git a/packages/sheets-plugin-conditional-format/src/UI/index.module.less b/packages/sheets-plugin-conditional-format/src/UI/index.module.less new file mode 100644 index 0000000000..21d9b71c53 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/UI/index.module.less @@ -0,0 +1,272 @@ +@import '@univer/style-universheet/assets/css/skin/default.module.less'; + +.condition-content { + font-size: 12px; + + .condition-title { + font-size: 16px; + margin-bottom: 1.3em; + } + + .condition-subtitle { + p { + margin: 5px 0; + } + + .set-bold { + font-weight: bold; + } + + .cell-range-modal { + width: 50%; + } + + .condition-between { + display: flex; + justify-content: space-between; + align-items: center; + + .cell-range-modal { + width: 45%; + } + } + + select { + width: 50%; + height: 2.2em; + } + + .condition-input { + input { + width: 20%; + margin: 0 0.5em; + } + } + } + + .color-box { + border: 1px solid @gray-5; + padding: 0.4rem 0.8em; + + .color-item { + height: 2em; + line-height: 2em; + margin-bottom: 1em; + } + } +} + +.rule-content { + font-size: 12px; + + .rule-title { + margin: 0.5rem 0; + } + + .rule-box { + border: 1px solid @gray-5; + + &:first-child { + margin-bottom: 1em; + } + + &:last-child { + padding: 1em; + } + + .rule-item { + padding: 0.4rem 0.5em; + cursor: pointer; + + &-active { + background-color: var(--primary-color); + color: var(--bg); + } + } + + .rule-edit { + .rule-edit-title { + font-weight: bold; + margin-bottom: 0.4em; + } + + select { + height: 2.2em; + } + + .rule-edit-item { + margin-bottom: 0.4em; + + span { + &:first-child { + margin-right: 2em; + } + } + } + + .rule-edit-content { + display: flex; + align-items: center; + margin-bottom: 1em; + + select { + margin-right: 0.5em; + } + + input { + width: auto; + height: 2.2em; + margin-right: 0.5em; + } + } + + .color-box { + border: 1px solid @gray-5; + padding: 0.4rem 0.8em; + + .color-item { + height: 2em; + line-height: 2em; + margin-bottom: 1em; + } + } + } + } +} + +.rule-manage { + font-size: 12px; + + > div:first-child { + margin-bottom: 0.5em; + } + + .rule-box { + border: 1px solid @gray-5; + + .rule-btn { + padding: 0.2rem 0.4em; + border-bottom: 1px solid @gray-5; + + button { + margin-right: 1em; + font-size: 12px; + height: auto; + } + } + + .rule-list { + .list-title { + height: 2.2em; + padding: 0 1em; + border-bottom: 1px solid @gray-5; + display: flex; + align-items: center; + + span { + &:nth-of-type(1) { + width: 30%; + } + + &:nth-of-type(2) { + width: 20%; + } + + &:nth-of-type(3) { + width: 45%; + } + } + } + + .list-box { + height: 12em; + overflow-y: scroll; + + .list-item { + height: 2em; + padding: 0.2rem 1em; + border-bottom: 1px solid @gray-5; + + &-active { + background-color: var(--primary-color); + } + } + } + } + } +} + +.icons-content { + padding: 1em; + border: 1px solid @gray-14; + font-size: 14px; + + p { + margin-bottom: 1em; + } + + .icon-title { + height: 1.8em; + line-height: 1.8em; + padding: 0 1em; + background-color: @gray-16; + } + + .icon-list { + width: 300px; + padding: 5px 0; + display: flex; + flex-wrap: wrap; + + .icon-item { + width: 50%; + + > div { + width: 145px; + height: 30px; + padding: 5px 10px; + background-color: var(--bg); + cursor: pointer; + + &:hover { + background: @gray-16; + } + + div { + height: 100%; + background: url('../Assets/CFicons.png'); + background-size: 256px; + } + } + } + } +} + +.cols-menuitem:hover, +.image-li:hover { + background-color: transparent !important; +} + +.image-content { + width: 136px; + padding: 5px; + display: flex; + flex-wrap: wrap; + justify-content: space-around; + + > div { + width: 38px; + height: 36px; + padding: 5px; + + &:hover { + background-color: @gray-14; + } + + div { + width: 100%; + height: 100%; + background-repeat: no-repeat; + } + } +} diff --git a/packages/sheets-plugin-conditional-format/src/View/Event.ts b/packages/sheets-plugin-conditional-format/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-conditional-format/src/View/ObserverList.ts b/packages/sheets-plugin-conditional-format/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-conditional-format/src/View/Render.ts b/packages/sheets-plugin-conditional-format/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-conditional-format/src/View/index.ts b/packages/sheets-plugin-conditional-format/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-conditional-format/src/index.ts b/packages/sheets-plugin-conditional-format/src/index.ts new file mode 100644 index 0000000000..99447c2538 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/index.ts @@ -0,0 +1,5 @@ +import { ConditionalFormatPlugin } from './ConditionalFormatPlugin'; + +export * from './IData'; + +export { ConditionalFormatPlugin }; diff --git a/packages/sheets-plugin-conditional-format/src/main.tsx b/packages/sheets-plugin-conditional-format/src/main.tsx new file mode 100644 index 0000000000..9bd8b88039 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/main.tsx @@ -0,0 +1,66 @@ +import { UniverSheet } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { ConditionalFormatPlugin } from './ConditionalFormatPlugin'; + +const defaultWorkbookData = { + locale: 'zh', + creator: '', + name: '', + skin: 'default', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +// univerSheetUp.installPlugin(new FilterPlugin()); +univerSheetUp.installPlugin(new ConditionalFormatPlugin()); + +// const univerSheetDown = UniverSheet.newInstance({workBookData:defaultWorkbookDataDown}); + +// univerSheetDown.installPlugin(new StyleUniverSheet(uiDefaultConfigDown)); + +// univerSheetDown.installPlugin(new FilterPlugin()); +// univerSheetDown.installPlugin(new ConditionalFormat()); diff --git a/packages/sheets-plugin-conditional-format/src/preact.d.ts b/packages/sheets-plugin-conditional-format/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-conditional-format/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-conditional-format/tsconfig.json b/packages/sheets-plugin-conditional-format/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-conditional-format/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-conditional-format/tsconfig.ref.json b/packages/sheets-plugin-conditional-format/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-conditional-format/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-conditional-format/tsconfig.test.json b/packages/sheets-plugin-conditional-format/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-conditional-format/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-conditional-format/vite.config.ts b/packages/sheets-plugin-conditional-format/vite.config.ts new file mode 100644 index 0000000000..a05619ea2e --- /dev/null +++ b/packages/sheets-plugin-conditional-format/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginConditionalFormat', + formats: ['es', 'umd'], + fileName: 'univer-sheets--plugin-conditional-format', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-data-validation/README-zh.md b/packages/sheets-plugin-data-validation/README-zh.md new file mode 100644 index 0000000000..5461b23c2a --- /dev/null +++ b/packages/sheets-plugin-data-validation/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet DataValidation 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-data-validation +``` + +### 使用 + +```js +impport {DataValidation} from '@univer/sheets-plugin-data-validation' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new DataValidation()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-data-validation/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-data-validation dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-data-validation/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-data-validation build +``` diff --git a/packages/sheets-plugin-data-validation/README.md b/packages/sheets-plugin-data-validation/README.md new file mode 100644 index 0000000000..40aaf969f1 --- /dev/null +++ b/packages/sheets-plugin-data-validation/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin DataValidation + +### Installation + +```bash +npm i @univer/sheets-plugin-data-validation +``` + +### Usage + +```js +impport {DataValidation} from '@univer/sheets-plugin-data-validation' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new DataValidation()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-data-validation/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-data-validation dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-data-validation/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-data-validation build +``` diff --git a/packages/sheets-plugin-data-validation/index.html b/packages/sheets-plugin-data-validation/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-data-validation/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-data-validation/jest.config.js b/packages/sheets-plugin-data-validation/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-data-validation/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-data-validation/package.json b/packages/sheets-plugin-data-validation/package.json new file mode 100644 index 0000000000..9237b6275a --- /dev/null +++ b/packages/sheets-plugin-data-validation/package.json @@ -0,0 +1,57 @@ +{ + "name": "@univer/sheets-plugin-data-validation", + "version": "0.0.1", + "description": "UniverSheet normal plugin dataValidation", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-data-validation/src/Action/ActionDataValidation.ts b/packages/sheets-plugin-data-validation/src/Action/ActionDataValidation.ts new file mode 100644 index 0000000000..9b4e657cb2 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Action/ActionDataValidation.ts @@ -0,0 +1 @@ +export class ActionDataValidation {} diff --git a/packages/sheets-plugin-data-validation/src/Action/IAction.ts b/packages/sheets-plugin-data-validation/src/Action/IAction.ts new file mode 100644 index 0000000000..9b031a3c3c --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Action/IAction.ts @@ -0,0 +1 @@ +export class IAction {} diff --git a/packages/sheets-plugin-data-validation/src/Action/IActionSet.ts b/packages/sheets-plugin-data-validation/src/Action/IActionSet.ts new file mode 100644 index 0000000000..f8e2a8f7e6 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Action/IActionSet.ts @@ -0,0 +1 @@ +export class IActionSet {} diff --git a/packages/sheets-plugin-data-validation/src/Action/index.ts b/packages/sheets-plugin-data-validation/src/Action/index.ts new file mode 100644 index 0000000000..f663343de7 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Action/index.ts @@ -0,0 +1,8 @@ +/** + * en: Modify localData, conflict handling, generate data transfer protocol, submit for background execution + * + * zh: 修改localData、冲突处理、生成数据传输协议、提交后台执行 + */ +export * from './ActionDataValidation'; +export * from './IAction'; +export * from './IActionSet'; diff --git a/packages/sheets-plugin-data-validation/src/Base/Cache.ts b/packages/sheets-plugin-data-validation/src/Base/Cache.ts new file mode 100644 index 0000000000..002f98640e --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Base/Cache.ts @@ -0,0 +1 @@ +export class Cache {} diff --git a/packages/sheets-plugin-data-validation/src/Base/State.ts b/packages/sheets-plugin-data-validation/src/Base/State.ts new file mode 100644 index 0000000000..f277253fef --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Base/State.ts @@ -0,0 +1 @@ +export class State {} diff --git a/packages/sheets-plugin-data-validation/src/Base/index.ts b/packages/sheets-plugin-data-validation/src/Base/index.ts new file mode 100644 index 0000000000..6c6e3b5d96 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Base/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './State'; diff --git a/packages/sheets-plugin-data-validation/src/Conflict/ConflictDataValidation.ts b/packages/sheets-plugin-data-validation/src/Conflict/ConflictDataValidation.ts new file mode 100644 index 0000000000..6050bd9517 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Conflict/ConflictDataValidation.ts @@ -0,0 +1 @@ +export class ConflictDataValidation {} diff --git a/packages/sheets-plugin-data-validation/src/Conflict/IConflict.ts b/packages/sheets-plugin-data-validation/src/Conflict/IConflict.ts new file mode 100644 index 0000000000..02b0979966 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Conflict/IConflict.ts @@ -0,0 +1 @@ +export class IConflict {} diff --git a/packages/sheets-plugin-data-validation/src/Conflict/index.ts b/packages/sheets-plugin-data-validation/src/Conflict/index.ts new file mode 100644 index 0000000000..2b07cbb313 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Conflict/index.ts @@ -0,0 +1,7 @@ +/** + * en: Handling action conflicts + * + * zh: 处理action冲突 + */ +export * from './IConflict'; +export * from './ConflictDataValidation'; diff --git a/packages/sheets-plugin-data-validation/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-data-validation/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..e37ef4e61e --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const DATA_VALIDATION_PLUGIN_NAME = 'dataValidation'; diff --git a/packages/sheets-plugin-data-validation/src/Const/index.ts b/packages/sheets-plugin-data-validation/src/Const/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-data-validation/src/DataValidationPlugin.tsx b/packages/sheets-plugin-data-validation/src/DataValidationPlugin.tsx new file mode 100644 index 0000000000..fd0da501da --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/DataValidationPlugin.tsx @@ -0,0 +1,59 @@ +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { Context, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { en, zh } from './Locale'; +import { DataValidationButton } from './UI/DataValidationButton'; + +import { IConfig } from './IData/IDataValidation'; +import { DATA_VALIDATION_PLUGIN_NAME } from './Const/PLUGIN_NAME'; + +type IPluginConfig = {}; + +export class DataValidationPlugin extends Plugin { + spreadsheetPlugin: any; + + constructor(config?: IPluginConfig) { + super(DATA_VALIDATION_PLUGIN_NAME); + } + + static create(config?: IPluginConfig) { + return new DataValidationPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = {}; + + const item: IToolBarItemProps = { + locale: DATA_VALIDATION_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + // get spreadsheet plugin + this.spreadsheetPlugin = context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + + // extend comment + this.spreadsheetPlugin?.addButton(item); + } + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} + + onMapping(): void {} +} diff --git a/packages/sheets-plugin-data-validation/src/Domain/DataValidation.ts b/packages/sheets-plugin-data-validation/src/Domain/DataValidation.ts new file mode 100644 index 0000000000..02e0042089 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Domain/DataValidation.ts @@ -0,0 +1 @@ +export class DataValidation {} diff --git a/packages/sheets-plugin-data-validation/src/Domain/index.ts b/packages/sheets-plugin-data-validation/src/Domain/index.ts new file mode 100644 index 0000000000..b57127a3ac --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Domain/index.ts @@ -0,0 +1 @@ +export * from './DataValidation'; diff --git a/packages/sheets-plugin-data-validation/src/Enum/index.ts b/packages/sheets-plugin-data-validation/src/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-data-validation/src/IData/IDataValidation.ts b/packages/sheets-plugin-data-validation/src/IData/IDataValidation.ts new file mode 100644 index 0000000000..a809ef3796 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/IData/IDataValidation.ts @@ -0,0 +1,4 @@ +export type IConfig = {}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-data-validation/src/IData/index.ts b/packages/sheets-plugin-data-validation/src/IData/index.ts new file mode 100644 index 0000000000..87f5d98e31 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/IData/index.ts @@ -0,0 +1 @@ +export * from './IDataValidation'; diff --git a/packages/sheets-plugin-data-validation/src/Locale/en.ts b/packages/sheets-plugin-data-validation/src/Locale/en.ts new file mode 100644 index 0000000000..41e56c4e40 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Locale/en.ts @@ -0,0 +1,53 @@ +export default { + dataValidation: { + dataValidationLabel: 'DataValidation', + cellRange: 'Cell range', + selectCellRange: 'Click to select a cell range', + selectCellRange2: 'Please select a range of cells', + validationCondition: 'Validation condition', + allowMultiSelect: 'Allow multiple selection', + dropdown: 'drop-down list', + checkbox: 'Checkbox', + number: 'Number', + number_integer: 'Number-integer', + number_decimal: 'Number-decimal', + text_content: 'Text-content', + text_length: 'Text-length', + date: 'Date', + validity: 'Effectiveness', + placeholder1: 'Please enter the options, separated by commas, such as 1,2,3,4,5', + placeholder2: 'Please enter content', + placeholder3: 'Numeric value, such as 10', + placeholder4: 'Please enter the specified text', + placeholder5: 'Please enter the prompt displayed when the cell is selected', + selected: 'Selected', + notSelected: 'Not selected', + between: 'Between', + notBetween: 'Not between', + equal: 'Equal', + notEqualTo: 'Not equal to', + moreThanThe: 'More than the', + lessThan: 'Less than', + greaterOrEqualTo: 'Greater or equal to', + lessThanOrEqualTo: 'Less than or equal to', + include: 'Include', + exclude: 'Exclude', + earlierThan: 'Earlier than', + noEarlierThan: 'No earlier than', + laterThan: 'Later than', + noLaterThan: 'No later than', + identificationNumber: 'Identification number', + phoneNumber: 'Phone number', + remote: 'Automatic remote acquisition option', + prohibitInput: 'Prohibit input when input data is invalid', + hintShow: 'Show prompt when the cell is selected', + deleteValidation: 'Delete validation', + tooltipInfo1: 'The drop-down list option cannot be empty', + tooltipInfo2: 'Checkbox content cannot be empty', + tooltipInfo3: 'The value entered is not a numeric type', + tooltipInfo4: 'The value 2 cannot be less than the value 1', + tooltipInfo5: 'The text content cannot be empty', + tooltipInfo6: 'The value entered is not a date type', + tooltipInfo7: 'Date 2 cannot be less than date 1', + }, +}; diff --git a/packages/sheets-plugin-data-validation/src/Locale/index.ts b/packages/sheets-plugin-data-validation/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-data-validation/src/Locale/zh.ts b/packages/sheets-plugin-data-validation/src/Locale/zh.ts new file mode 100644 index 0000000000..afc1032cd7 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Locale/zh.ts @@ -0,0 +1,53 @@ +export default { + dataValidation: { + dataValidationLabel: '数据验证', + cellRange: '单元格范围', + selectCellRange: '点击选择单元格范围', + selectCellRange2: '请选择单元格范围', + validationCondition: '验证条件', + allowMultiSelect: '是否允许多选', + dropdown: '下拉列表', + checkbox: '复选框', + number: '数字', + number_integer: '数字-整数', + number_decimal: '数字-小数', + text_content: '文本-内容', + text_length: '文本-长度', + date: '日期', + validity: '有效性', + placeholder1: '请输入选项,以英文逗号分隔,如1,2,3,4,5', + placeholder2: '请输入内容', + placeholder3: '数值,如10', + placeholder4: '请输入指定的文本', + placeholder5: '请输入选中单元格时显示的提示语', + selected: '选择时', + notSelected: '未选择', + between: '介于', + notBetween: '不介于', + equal: '等于', + notEqualTo: '不等于', + moreThanThe: '大于', + lessThan: '小于', + greaterOrEqualTo: '大于等于', + lessThanOrEqualTo: '小于等于', + include: '包括', + exclude: '不包括', + earlierThan: '早于', + noEarlierThan: '不早于', + laterThan: '晚于', + noLaterThan: '不晚于', + identificationNumber: '身份证号码', + phoneNumber: '手机号', + remote: '自动远程获取选项', + prohibitInput: '输入数据无效时禁止输入', + hintShow: '选中单元格时显示提示语', + deleteValidation: '删除验证', + tooltipInfo1: '下拉列表选项不可为空', + tooltipInfo2: '复选框内容不可为空', + tooltipInfo3: '输入的值不是数值类型', + tooltipInfo4: '数值2不能小于数值1', + tooltipInfo5: '文本内容不能为空', + tooltipInfo6: '输入的值不是日期类型', + tooltipInfo7: '日期2不能小于日期1', + }, +}; diff --git a/packages/sheets-plugin-data-validation/src/ProxyData/IProxyData.ts b/packages/sheets-plugin-data-validation/src/ProxyData/IProxyData.ts new file mode 100644 index 0000000000..0bd8a3cf07 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/ProxyData/IProxyData.ts @@ -0,0 +1 @@ +export class IProxyData {} diff --git a/packages/sheets-plugin-data-validation/src/ProxyData/IProxyDataSet.ts b/packages/sheets-plugin-data-validation/src/ProxyData/IProxyDataSet.ts new file mode 100644 index 0000000000..9bf75cd768 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/ProxyData/IProxyDataSet.ts @@ -0,0 +1 @@ +export class IProxyDataSet {} diff --git a/packages/sheets-plugin-data-validation/src/ProxyData/ProxyDataDataValidation.ts b/packages/sheets-plugin-data-validation/src/ProxyData/ProxyDataDataValidation.ts new file mode 100644 index 0000000000..a9f218e898 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/ProxyData/ProxyDataDataValidation.ts @@ -0,0 +1 @@ +export class IProxyDataDataValidation {} diff --git a/packages/sheets-plugin-data-validation/src/ProxyData/index.ts b/packages/sheets-plugin-data-validation/src/ProxyData/index.ts new file mode 100644 index 0000000000..49bf042465 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/ProxyData/index.ts @@ -0,0 +1,8 @@ +/** + * en: Transfer to background data protocol + * + * zh: 传输到后台数据协议 + */ +export * from './IProxyData'; +export * from './IProxyDataSet'; +export * from './ProxyDataDataValidation'; diff --git a/packages/sheets-plugin-data-validation/src/Service/DataValidationService.ts b/packages/sheets-plugin-data-validation/src/Service/DataValidationService.ts new file mode 100644 index 0000000000..667f58cd4d --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Service/DataValidationService.ts @@ -0,0 +1 @@ +export class DataValidationService {} diff --git a/packages/sheets-plugin-data-validation/src/Service/index.ts b/packages/sheets-plugin-data-validation/src/Service/index.ts new file mode 100644 index 0000000000..1865ba231f --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Service/index.ts @@ -0,0 +1,7 @@ +/** + * + * en: Operate and register localData + * + * zh: 操作、注册localData + */ +export * from './DataValidationService'; diff --git a/packages/sheets-plugin-data-validation/src/Shared/index.ts b/packages/sheets-plugin-data-validation/src/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-data-validation/src/Types/index.d.ts b/packages/sheets-plugin-data-validation/src/Types/index.d.ts new file mode 100644 index 0000000000..6343673e17 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-data-validation' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-data-validation/src/UI/DataValidationButton.tsx b/packages/sheets-plugin-data-validation/src/UI/DataValidationButton.tsx new file mode 100644 index 0000000000..88d6d097c1 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/UI/DataValidationButton.tsx @@ -0,0 +1,170 @@ +import { BaseComponentRender, BaseComponentSheet, Component, IToolBarItemProps, ModalProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { IProps } from '../IData/IDataValidation'; +import { DataValidationContent } from './DataValidationContent'; +import styles from './index.module.less'; + +// Types for state +interface IState { + dataValidation: IToolBarItemProps; + modalData: ModalProps[]; + tip: LabelProps[]; +} + +type LabelProps = { + locale?: string; + label?: string; +}; + +export class DataValidationButton extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize(props: IProps) { + // super(props); + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + const CheckIcon = this.Render.renderFunction('CheckIcon'); + + const dataValidationState: IToolBarItemProps = { + locale: 'dataValidation', + type: 'single', + label: , + show: true, + }; + + this.state = { + dataValidation: dataValidationState, + tip: [ + { + locale: 'dataValidation.tooltipInfo1', + }, + ], + modalData: [ + { + locale: 'dataValidation.dataValidationLabel', + show: false, + group: [ + { + locale: 'button.confirm', + type: 'primary', + }, + { + locale: 'dataValidation.deleteValidation', + }, + { + locale: 'button.cancel', + }, + ], + children: , + onCancel: () => { + this.showModal(0, false); + }, + }, + { + show: false, + // children:

        {this.state.tip[0].label}

        , + group: [ + { + locale: 'button.close', + }, + ], + onCancel: () => { + this.showModal(1, false); + }, + }, + ], + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.dataValidation; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}`); + + let { modalData, tip } = prevState; + modalData.forEach((ele) => { + ele.title = ele.locale ? locale.get(ele.locale) : ''; + if (ele.group && ele.group.length) { + ele.group.forEach((node) => { + node.label = locale.get(node.locale!); + }); + } + }); + + // tip.forEach((ele) => { + // ele.label = Locale.get(ele.locale!); + // }); + return { + dataValidation: item, + modalData, + // tip, + }; + }); + } + + showModal = (index: number, isShow: boolean) => { + this.setState((prevState) => { + let { modalData } = prevState; + + modalData[index].show = isShow; + return { + modalData, + }; + }); + }; + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const Button = this.Render.renderFunction('Button'); + const Modal = this.Render.renderFunction('Modal'); + const { dataValidation, modalData } = state; + // Set Provider for entire Container + return ( +
        + + {modalData.map((item, index) => { + if (!item.show) return; + return ; + })} +
        + ); + } +} diff --git a/packages/sheets-plugin-data-validation/src/UI/DataValidationContent.tsx b/packages/sheets-plugin-data-validation/src/UI/DataValidationContent.tsx new file mode 100644 index 0000000000..cc7821739e --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/UI/DataValidationContent.tsx @@ -0,0 +1,441 @@ +import { BaseComponentRender, BaseComponentSheet, Component } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import styles from './index.module.less'; + +type DataValidationProps = { + config?: any; +}; +type DataValidationState = { + range: LabelProps; + validation: LabelProps; + options: LabelProps[]; + checkGroups: LabelProps[]; + placeholder: LabelProps[]; + checkValue: string[]; + selected: LabelProps; + notSelected: LabelProps; + betweenOptions: LabelProps[]; + textOptions: LabelProps[]; + validityOptions: LabelProps[]; + dateOptions: LabelProps[]; +}; +type LabelProps = { + name?: string; + label?: string; + value?: string; + checked?: boolean; +}; + +export class DataValidationContent extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize() { + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + range: { + name: 'dataValidation.cellRange', + }, + validation: { + name: 'dataValidation.validationCondition', + }, + options: [ + { + name: 'dataValidation.dropdown', + value: '1', + }, + { + name: 'dataValidation.checkbox', + value: '2', + }, + { + name: 'dataValidation.number', + value: '3', + }, + { + name: 'dataValidation.number_integer', + value: '3', + }, + { + name: 'dataValidation.number_decimal', + value: '3', + }, + { + name: 'dataValidation.text_content', + value: '4', + }, + { + name: 'dataValidation.text_length', + value: '3', + }, + { + name: 'dataValidation.date', + value: '5', + }, + { + name: 'dataValidation.validity', + value: '6', + }, + ], + checkGroups: [ + { + name: 'dataValidation.remote', + value: '1', + checked: false, + }, + { + name: 'dataValidation.prohibitInput', + value: '2', + }, + { + name: 'dataValidation.hintShow', + value: '3', + }, + ], + placeholder: [ + { + name: 'dataValidation.placeholder5', + label: '', + }, + { + name: 'dataValidation.placeholder1', + label: '', + }, + { + name: 'dataValidation.placeholder2', + label: '', + }, + { + name: 'dataValidation.placeholder4', + label: '', + }, + ], + selected: { + name: 'dataValidation.selected', + label: '', + }, + notSelected: { + name: 'dataValidation.notSelected', + label: '', + }, + betweenOptions: [ + { + name: 'dataValidation.between', + label: '', + value: '', + }, + { + name: 'dataValidation.notBetween', + label: '', + value: '', + }, + { + name: 'dataValidation.equal', + label: '', + value: '', + }, + { + name: 'dataValidation.notEqualTo', + label: '', + value: '', + }, + { + name: 'dataValidation.moreThanThe', + label: '', + value: '', + }, + { + name: 'dataValidation.lessThan', + label: '', + value: '', + }, + { + name: 'dataValidation.greaterOrEqualTo', + label: '', + value: '', + }, + { + name: 'dataValidation.lessThanOrEqualTo', + label: '', + value: '', + }, + ], + textOptions: [ + { + name: 'dataValidation.include', + label: '', + value: '', + }, + { + name: 'dataValidation.exclude', + label: '', + value: '', + }, + { + name: 'dataValidation.equal', + label: '', + value: '', + }, + ], + dateOptions: [ + { + name: 'dataValidation.between', + label: '', + value: '', + }, + { + name: 'dataValidation.notBetween', + label: '', + value: '', + }, + { + name: 'dataValidation.equal', + label: '', + value: '', + }, + { + name: 'dataValidation.notEqualTo', + label: '', + value: '', + }, + { + name: 'dataValidation.earlierThan', + label: '', + value: '', + }, + { + name: 'dataValidation.noEarlierThan', + label: '', + value: '', + }, + { + name: 'dataValidation.laterThan', + label: '', + value: '', + }, + { + name: 'dataValidation.noLaterThan', + label: '', + value: '', + }, + ], + validityOptions: [ + { + name: 'dataValidation.identificationNumber', + label: '', + value: '', + }, + { + name: 'dataValidation.phoneNumber', + label: '', + value: '', + }, + ], + selectValue: '1', + checkValue: [''], + }; + } + + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState) => { + let { range, validation, options, checkGroups, placeholder, selected, notSelected, betweenOptions, textOptions, validityOptions, dateOptions } = prevState; + range.label = locale.get(range.name); + validation.label = locale.get(validation.name); + options.forEach((item) => { + item.label = locale.get(item.name || ''); + }); + checkGroups.forEach((item) => { + item.label = locale.get(item.name); + }); + placeholder.forEach((item) => { + item.label = locale.get(item.name); + }); + selected.label = locale.get(selected.name); + notSelected.label = locale.get(notSelected.name); + betweenOptions.forEach((item) => { + item.label = locale.get(item.name); + }); + textOptions.forEach((item) => { + item.label = locale.get(item.name); + }); + validityOptions.forEach((item) => { + item.label = locale.get(item.name); + }); + dateOptions.forEach((item) => { + item.label = locale.get(item.name); + }); + + return { + range, + validation, + options, + checkGroups, + betweenOptions, + textOptions, + validityOptions, + dateOptions, + }; + }); + } + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + setValue = (value: object, fn?: () => void) => { + this.setState( + (prevState) => ({ + ...value, + }), + fn + ); + }; + + handleSelect = (e: Event) => { + const target = e.target as HTMLSelectElement; + this.setValue({ selectValue: target.value }); + }; + + // 不同验证条件显示样式 + getSelectInfo() { + const CellRangeModal = this.Render.renderFunction('CellRangeModal'); + const Input = this.Render.renderFunction('Input'); + const { selectValue, placeholder, selected, notSelected, betweenOptions, textOptions, validityOptions, dateOptions } = this.state; + if (selectValue === '1') { + return ; + } + if (selectValue === '2') { + return ( +
        +
        + {selected.label} —— + +
        +
        + {notSelected.label} —— + +
        +
        + ); + } + if (selectValue === '3') { + return ( +
        + +
        + + -- + +
        +
        + ); + } + if (selectValue === '4') { + return ( +
        + + +
        + ); + } + if (selectValue === '5') { + return ( +
        + +
        + + - + +
        +
        + ); + } + if (selectValue === '6') { + return ( +
        + +
        + ); + } + } + + checkboxHandle = (value: string[]) => { + let { checkGroups } = this.state; + checkGroups.forEach((item) => { + item.checked = false; + if (value.includes(item.value)) { + item.checked = true; + } + }); + this.setValue({ checkValue: value, checkGroups }); + }; + + // 选中时显示input + getCheckInfo() { + const Input = this.Render.renderFunction('Input'); + const { checkValue, placeholder } = this.state; + if (checkValue.includes('3')) { + return ; + } + } + + render(props: DataValidationProps, state: DataValidationState) { + const CellRangeModal = this.Render.renderFunction('CellRangeModal'); + const CheckboxGroup = this.Render.renderFunction('CheckboxGroup'); + const { range, validation, options, checkGroups, placeholder } = state; + return ( +
        +
        +

        {range.label}

        + +
        +
        +

        {validation.label}

        + + {this.getSelectInfo()} +
        +
        + + {this.getCheckInfo()} +
        +
        + ); + } +} diff --git a/packages/sheets-plugin-data-validation/src/UI/index.module.less b/packages/sheets-plugin-data-validation/src/UI/index.module.less new file mode 100644 index 0000000000..3f089332f4 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/UI/index.module.less @@ -0,0 +1,111 @@ +@import '@univer/style-universheet/assets/css/skin/default.module.less'; + +.single-button { + .data-validation { + .box-item { + &:first-child { + padding-top: 0; + } + + &:last-child { + border-bottom: none; + padding-bottom: 0; + } + + padding: 1em; + border-bottom: 1px solid @gray-5; + + .title { + font-weight: bold; + margin-bottom: 1em; + } + + .cell-range-modal { + height: 30px; + } + + select { + width: 100%; + height: 30px; + border-color: @gray-5; + outline-style: none; + + & + .cell-range-modal { + margin-top: 1em; + } + } + + .checkbox-group { + flex-direction: column; + + label { + margin: 0.5em; + font-size: 12px; + + .checkbox-inner { + width: 14px; + height: 14px; + } + } + } + + .data-checkbox { + margin-top: 1em; + font-size: 12px; + + div { + span { + margin: 0.3em; + } + + &:first-child { + margin-bottom: 0.5em; + } + + input { + width: auto; + } + } + } + + .data-between { + select { + margin-top: 1em; + } + + div { + margin-top: 1em; + display: flex; + justify-content: space-between; + + input { + width: 47%; + } + } + } + + .data-text { + select { + margin: 1rem 0; + } + } + + .data-date { + select { + margin: 1rem 0; + } + + div { + display: flex; + justify-content: space-around; + } + } + + .data-validity { + select { + margin-top: 1em; + } + } + } + } +} diff --git a/packages/sheets-plugin-data-validation/src/View/Event.ts b/packages/sheets-plugin-data-validation/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-data-validation/src/View/ObserverList.ts b/packages/sheets-plugin-data-validation/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-data-validation/src/View/Render.ts b/packages/sheets-plugin-data-validation/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-data-validation/src/View/index.ts b/packages/sheets-plugin-data-validation/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-data-validation/src/index.ts b/packages/sheets-plugin-data-validation/src/index.ts new file mode 100644 index 0000000000..080a288139 --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/index.ts @@ -0,0 +1,3 @@ +import { DataValidationPlugin } from './DataValidationPlugin'; + +export { DataValidationPlugin }; diff --git a/packages/sheets-plugin-data-validation/src/main.tsx b/packages/sheets-plugin-data-validation/src/main.tsx new file mode 100644 index 0000000000..091dab113b --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/main.tsx @@ -0,0 +1,45 @@ +import { UniverSheet, defaultWorkbookData } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { DataValidationPlugin } from './DataValidationPlugin'; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +univerSheetUp.installPlugin(new DataValidationPlugin()); diff --git a/packages/sheets-plugin-data-validation/src/preact.d.ts b/packages/sheets-plugin-data-validation/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-data-validation/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-data-validation/tsconfig.json b/packages/sheets-plugin-data-validation/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-data-validation/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-data-validation/tsconfig.ref.json b/packages/sheets-plugin-data-validation/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-data-validation/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-data-validation/tsconfig.test.json b/packages/sheets-plugin-data-validation/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-data-validation/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-data-validation/vite.config.ts b/packages/sheets-plugin-data-validation/vite.config.ts new file mode 100644 index 0000000000..10c992db0d --- /dev/null +++ b/packages/sheets-plugin-data-validation/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginDataValidation', + formats: ['es', 'umd'], + fileName: 'univer-sheets--plugin-data-validation', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-filter/README-zh.md b/packages/sheets-plugin-filter/README-zh.md new file mode 100644 index 0000000000..81fc2b8417 --- /dev/null +++ b/packages/sheets-plugin-filter/README-zh.md @@ -0,0 +1,98 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet 官方 UI 组件库 + +## 特性 + +- 纯 tsx 组件,可方便集成到 vue/react +- 按需引入,不用担心影响打包体积 + +## 用法 + +### 安装 + +```bash +npm i @univer/style-universheet +``` + +### 使用 + +```js +impport baseUI from '@univer/style-universheet' +universheet.install(baseUI) +universheet.create({ + plugins:['style-universheet'] +}) +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 7 + +### 安装 + +``` +npm i +``` + +### 开发 + +``` +# 当前目录 ./packages/style-universheet/ +npm run dev + +# 或者项目根目录 ./ +npm run dev -w @univer/style-universheet +``` + +### 打包 + +``` +# 当前目录 ./packages/style-universheet/ +npm run build + +# 或者根目录 ./ +npm run build -w @univer/style-universheet +``` + +## 组件库文档 + +使用[Docusaurus 2](https://docusaurus.io/)构建 + +### 安装 + +```console +# 根目录 +npm i +``` + +### 开发 + +```console +# 当前目录 +npm run start + +# 或者根目录 +npm run start -w @univer/style-universheet +``` + +此命令启动本地开发服务器并打开浏览器窗口。 大多数更改都会实时刷新,而无需重新启动服务器。 + +### 打包 + +```console +# 当前目录 +npm run build:doc + +# 或者根目录 +npm run build:doc -w @univer/style-universheet +``` + +此命令将静态内容生成到 `build` 目录中,并且可以使用任何静态内容托管服务来提供。 diff --git a/packages/sheets-plugin-filter/README.md b/packages/sheets-plugin-filter/README.md new file mode 100644 index 0000000000..7c47b06298 --- /dev/null +++ b/packages/sheets-plugin-filter/README.md @@ -0,0 +1,98 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet official UI component library + +## Features + +- Pure tsx components, which can be easily integrated into vue/react +- Import on demand, don’t worry about affecting the package size + +## Usage + +### Install + +```bash +npm i @univer/style-universheet +``` + +### Import + +```js +impport baseUI from'@univer/style-universheet' +universheet.install(baseUI) +universheet.create({ + plugins:['style-universheet'] +}) +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 7 + +### Installation + +``` +npm i +``` + +### Development + +``` +# Current directory ./packages/style-universheet/ +npm run dev + +# Or project root directory ./ +npm run dev -w @univer/style-universheet +``` + +### Package + +``` +# Current directory ./packages/style-universheet/ +npm run build + +# Or root directory ./ +npm run build -w @univer/style-universheet +``` + +## Doc Website + +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +### Installation + +```console +# root folder +npm i +``` + +### Development + +```console +# style-universheet folder +npm run start + +# or root folder +npm run start -w @univer/style-universheet +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### Build + +```console +# style-universheet folder +npm run build:doc + +# or root folder +npm run build:doc -w @univer/style-universheet +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. diff --git a/packages/sheets-plugin-filter/index.html b/packages/sheets-plugin-filter/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-filter/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-filter/jest.config.js b/packages/sheets-plugin-filter/jest.config.js new file mode 100644 index 0000000000..ef2425ea8f --- /dev/null +++ b/packages/sheets-plugin-filter/jest.config.js @@ -0,0 +1,16 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + coverageReporters: ['json', 'html'], + collectCoverage: true, + testEnvironment: 'jsdom', + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.(js|jsx)$': 'babel-jest', + }, + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-filter/package.json b/packages/sheets-plugin-filter/package.json new file mode 100644 index 0000000000..8e8b2e2c2b --- /dev/null +++ b/packages/sheets-plugin-filter/package.json @@ -0,0 +1,52 @@ +{ + "name": "@univer/sheets-plugin-filter", + "version": "0.0.1", + "description": "UniverSheet normal plugin UI manager", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "2.0.1", + "@types/jest": "27.0.3", + "@vitejs/plugin-legacy": "1.5.1", + "jest": "29.0.0", + "jest-environment-jsdom": "^29.0.3", + "less": "4.1.1", + "ts-jest": "29.0.1", + "typescript": "4.7.2", + "vite": "2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-conditional-format": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "nanoid": "^4.0.0", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-filter/src/Action/AddFilterAction.ts b/packages/sheets-plugin-filter/src/Action/AddFilterAction.ts new file mode 100644 index 0000000000..ee7a591fef --- /dev/null +++ b/packages/sheets-plugin-filter/src/Action/AddFilterAction.ts @@ -0,0 +1,46 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { IFilter } from '../Domain'; +import { ACTION_NAMES, FILTER_PLUGIN_NAME } from '../Const'; +import { AddFilter, RemoveFilter } from '../Apply'; +import { IRemoveFilterActionData } from './RemoveFilterAction'; + +export interface IAddFilterActionData extends IActionData { + filter: Nullable; +} + +export class AddFilterAction extends ActionBase { + constructor(actionData: IAddFilterActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + sheetId: this._doActionData.sheetId, + actionName: ACTION_NAMES.ADD_FILTER_ACTION, + }; + this.do(); + this.validate(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilter(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._doActionData.sheetId, this._doActionData.filter); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + RemoveFilter(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._oldActionData.sheetId); + } + + do(): void { + this.redo(); + } + + validate(): boolean { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/sheets-plugin-filter/src/Action/AddFilterCriteriaAction.ts b/packages/sheets-plugin-filter/src/Action/AddFilterCriteriaAction.ts new file mode 100644 index 0000000000..4b70b7564c --- /dev/null +++ b/packages/sheets-plugin-filter/src/Action/AddFilterCriteriaAction.ts @@ -0,0 +1,49 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { IFilterCriteriaColumn } from '../Domain/FilterCriteriaColumn'; +import { ACTION_NAMES } from '../Const/ACTION_NAME'; +import { FILTER_PLUGIN_NAME } from '../Const'; +import { AddFilterCriteria, RemoveFilterCriteria } from '../Apply'; +import { IRemoveFilterCriteriaAction } from './RemoveFilterCriteriaAction'; + +export interface IAddFilterCriteriaActionData extends IActionData { + columnPosition: number; + criteriaColumn: Nullable; +} + +export class AddFilterCriteriaAction extends ActionBase { + constructor(actionData: IAddFilterCriteriaActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + columnPosition: actionData.columnPosition, + sheetId: actionData.sheetId, + actionName: ACTION_NAMES.ADD_FILTER_CRITERIA_ACTION, + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._doActionData.sheetId, this._doActionData.criteriaColumn); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + RemoveFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._oldActionData.sheetId, this._oldActionData.columnPosition); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-filter/src/Action/RemoveFilterAction.ts b/packages/sheets-plugin-filter/src/Action/RemoveFilterAction.ts new file mode 100644 index 0000000000..6dc29dbe33 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Action/RemoveFilterAction.ts @@ -0,0 +1,44 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { AddFilter, RemoveFilter } from '../Apply'; +import { ACTION_NAMES, FILTER_PLUGIN_NAME } from '../Const'; +import { IFilter } from '../Domain'; +import { IAddFilterActionData } from './AddFilterAction'; + +export interface IRemoveFilterActionData extends IActionData {} + +export class RemoveFilterAction extends ActionBase { + constructor(actionData: IRemoveFilterActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + sheetId: this._doActionData.sheetId, + actionName: ACTION_NAMES.REMOVE_FILTER_ACTION, + filter: this.do(), + }; + this.validate(); + } + + redo(): Nullable { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + return RemoveFilter(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._doActionData.sheetId); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilter(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._doActionData.sheetId, this._oldActionData.filter); + } + + do(): Nullable { + return this.redo(); + } + + validate(): boolean { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/sheets-plugin-filter/src/Action/RemoveFilterCriteriaAction.ts b/packages/sheets-plugin-filter/src/Action/RemoveFilterCriteriaAction.ts new file mode 100644 index 0000000000..1dbd0a1e99 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Action/RemoveFilterCriteriaAction.ts @@ -0,0 +1,47 @@ +import { ActionBase, ActionObservers, IActionData, Nullable, WorkBook } from '@univer/core'; +import { AddFilterCriteria, RemoveFilterCriteria } from '../Apply'; +import { ACTION_NAMES, FILTER_PLUGIN_NAME } from '../Const'; +import { IFilterCriteriaColumn } from '../Domain'; +import { IAddFilterCriteriaActionData } from './AddFilterCriteriaAction'; + +export interface IRemoveFilterCriteriaAction extends IActionData { + columnPosition: number; +} + +export class RemoveFilterCriteriaAction extends ActionBase { + constructor(actionData: IRemoveFilterCriteriaAction, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + }; + this._oldActionData = { + actionName: ACTION_NAMES.ADD_FILTER_CRITERIA_ACTION, + sheetId: actionData.sheetId, + columnPosition: actionData.columnPosition, + criteriaColumn: this.do(), + }; + this.validate(); + } + + do(): Nullable { + return this.redo(); + } + + redo(): Nullable { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + return RemoveFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._oldActionData.sheetId, this._doActionData.columnPosition); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + const context = worksheet.getContext(); + const manager = context.getPluginManager(); + AddFilterCriteria(manager.getRequirePluginByName(FILTER_PLUGIN_NAME), this._doActionData.sheetId, this._oldActionData.criteriaColumn); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-filter/src/Action/index.ts b/packages/sheets-plugin-filter/src/Action/index.ts new file mode 100644 index 0000000000..58d3e905df --- /dev/null +++ b/packages/sheets-plugin-filter/src/Action/index.ts @@ -0,0 +1,4 @@ +export * from './IAction'; +export * from './IActionSet'; +export * from './ActionFilter'; +export * from './SetFilterCriteriaAction'; diff --git a/packages/sheets-plugin-filter/src/Apply/AddFilter.ts b/packages/sheets-plugin-filter/src/Apply/AddFilter.ts new file mode 100644 index 0000000000..e355eacd6e --- /dev/null +++ b/packages/sheets-plugin-filter/src/Apply/AddFilter.ts @@ -0,0 +1,10 @@ +import { Nullable } from '@univer/core'; +import { Filter, IFilter } from '../Domain'; +import { FilterPlugin } from '../FilterPlugin'; + +export function AddFilter(plugin: FilterPlugin, sheetId: string, filter: Nullable) { + const filterList = plugin.getFilterList(); + if (filter) { + filterList.addFilter(sheetId, Filter.fromSequence(filter).withContext(plugin.getContext())); + } +} diff --git a/packages/sheets-plugin-filter/src/Apply/AddFilterCriteria.ts b/packages/sheets-plugin-filter/src/Apply/AddFilterCriteria.ts new file mode 100644 index 0000000000..0453e9817c --- /dev/null +++ b/packages/sheets-plugin-filter/src/Apply/AddFilterCriteria.ts @@ -0,0 +1,12 @@ +import { Nullable } from '@univer/core'; +import { FilterCriteriaColumn, IFilterCriteriaColumn } from '../Domain'; +import { FilterPlugin } from '../FilterPlugin'; + +export function AddFilterCriteria(plugin: FilterPlugin, sheetId: string, criteriaColumn: Nullable) { + const filterList = plugin.getFilterList(); + const filterModel = filterList.getFilter(sheetId); + const criteriaModel = filterModel.getGroupModel(); + if (criteriaColumn) { + criteriaModel[criteriaColumn.column] = FilterCriteriaColumn.fromSequence(criteriaColumn).withContext(plugin.getContext()); + } +} diff --git a/packages/sheets-plugin-filter/src/Apply/RemoveFilter.ts b/packages/sheets-plugin-filter/src/Apply/RemoveFilter.ts new file mode 100644 index 0000000000..5164907bfa --- /dev/null +++ b/packages/sheets-plugin-filter/src/Apply/RemoveFilter.ts @@ -0,0 +1,11 @@ +import { Nullable } from '@univer/core'; +import { FilterPlugin } from '../FilterPlugin'; +import { IFilter } from '../Domain'; + +export function RemoveFilter(plugin: FilterPlugin, sheetId: string): Nullable { + const filterList = plugin.getFilterList(); + const filterModel = filterList.getGroupModel(); + const filter = filterModel[sheetId]; + delete filterModel[sheetId]; + return filter.toSequence(); +} diff --git a/packages/sheets-plugin-filter/src/Apply/RemoveFilterCriteria.ts b/packages/sheets-plugin-filter/src/Apply/RemoveFilterCriteria.ts new file mode 100644 index 0000000000..c42899edbf --- /dev/null +++ b/packages/sheets-plugin-filter/src/Apply/RemoveFilterCriteria.ts @@ -0,0 +1,12 @@ +import { Nullable } from '@univer/core'; +import { FilterPlugin } from '../FilterPlugin'; +import { IFilterCriteriaColumn } from '../Domain'; + +export function RemoveFilterCriteria(plugin: FilterPlugin, sheetId: string, column: number): Nullable { + const filterList = plugin.getFilterList(); + const filterModel = filterList.getFilter(sheetId); + const criteriaModel = filterModel.getGroupModel(); + const criteria = criteriaModel[column]; + delete criteriaModel[column]; + return criteria.toSequence(); +} diff --git a/packages/sheets-plugin-filter/src/Apply/index.ts b/packages/sheets-plugin-filter/src/Apply/index.ts new file mode 100644 index 0000000000..c11e36e997 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Apply/index.ts @@ -0,0 +1,4 @@ +export * from './AddFilterCriteria'; +export * from './RemoveFilterCriteria'; +export * from './AddFilter'; +export * from './RemoveFilter'; diff --git a/packages/sheets-plugin-filter/src/Const/ACTION_NAME.ts b/packages/sheets-plugin-filter/src/Const/ACTION_NAME.ts new file mode 100644 index 0000000000..356873ce46 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Const/ACTION_NAME.ts @@ -0,0 +1,9 @@ +export enum ACTION_NAMES { + ADD_FILTER_ACTION = 'AddFilterAction', + REMOVE_FILTER_ACTION = 'RemoveFilterAction', + + ADD_FILTER_CRITERIA_ACTION = 'AddFilterCriteriaAction', + REMOVE_FILTER_CRITERIA_ACTION = 'RemoveFilterCriteriaAction', + + SET_FILTER_CRITERIA_ACTION = 'setFilterCriteriaAction', +} diff --git a/packages/sheets-plugin-filter/src/Const/FILTER_CRITERIA_DEFINED.ts b/packages/sheets-plugin-filter/src/Const/FILTER_CRITERIA_DEFINED.ts new file mode 100644 index 0000000000..3e7d4f73ae --- /dev/null +++ b/packages/sheets-plugin-filter/src/Const/FILTER_CRITERIA_DEFINED.ts @@ -0,0 +1,7 @@ +export const FilterCriteriaDefined = { + CELL_EMPTY: 'whenCellEmpty', + CELL_NOT_EMPTY: 'whenCellNotEmpty', + DATE_AFTER: 'whenDateAfter', + DATE_BEFORE: 'whenDateBefore', + DATE_EQUAL_TO: 'whenDateEqualTo', +}; diff --git a/packages/sheets-plugin-filter/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-filter/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..d151ea986e --- /dev/null +++ b/packages/sheets-plugin-filter/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const FILTER_PLUGIN_NAME = 'filter'; diff --git a/packages/sheets-plugin-filter/src/Const/index.ts b/packages/sheets-plugin-filter/src/Const/index.ts new file mode 100644 index 0000000000..5681768dab --- /dev/null +++ b/packages/sheets-plugin-filter/src/Const/index.ts @@ -0,0 +1,3 @@ +export * from './FILTER_CRITERIA_DEFINED'; +export * from './ACTION_NAME'; +export * from './PLUGIN_NAME'; diff --git a/packages/sheets-plugin-filter/src/Domain/Filter.ts b/packages/sheets-plugin-filter/src/Domain/Filter.ts new file mode 100644 index 0000000000..0886371ddd --- /dev/null +++ b/packages/sheets-plugin-filter/src/Domain/Filter.ts @@ -0,0 +1,96 @@ +import { Command, Context, IRangeData, GroupModel, Nullable, Sequence, Serializer } from '@univer/core'; +import { ACTION_NAMES } from '../Const'; +import { FilterCriteria } from './FilterCriteria'; +import { FilterCriteriaColumn, IFilterCriteriaColumn } from './FilterCriteriaColumn'; + +export interface IFilter extends Sequence { + range: IRangeData; + sheetId: string; + criteriaColumns: { + [column: number]: IFilterCriteriaColumn; + }; +} + +export class Filter extends Serializer implements GroupModel<{ [column: number]: FilterCriteriaColumn }>, Context.WithContext { + static newInstance(sequence: IFilter): Filter { + const filter = new Filter(sequence.sheetId, sequence.range); + const criteriaColumns = {}; + for (let column in sequence.criteriaColumns) { + criteriaColumns[column] = FilterCriteriaColumn.fromSequence(sequence.criteriaColumns[column]); + } + filter._criteriaColumns = criteriaColumns; + return filter; + } + + private _context: Context; + + private _range: IRangeData; + + private _sheetId: string; + + private _criteriaColumns: { + [column: number]: FilterCriteriaColumn; + }; + + constructor(sheetId: string, range: IRangeData) { + super(); + this._sheetId = sheetId; + this._range = range; + this._criteriaColumns = {}; + } + + withContext(context: Context): Filter { + this._context = context; + return this; + } + + getContext(): Context { + return this._context; + } + + getColumnFilterCriteria(columnPosition: number): Nullable { + const criteriaColumn = this._criteriaColumns[columnPosition]; + if (criteriaColumn) { + return criteriaColumn.getCriteria(); + } + return null; + } + + getGroupModel(): { [column: number]: FilterCriteriaColumn } { + return this._criteriaColumns; + } + + setColumnFilterCriteria(columnPosition: number, criteriaColumn: FilterCriteriaColumn): void { + const context = this.getContext(); + const commandManager = context.getCommandManager(); + const workbook = context.getWorkBook(); + const worksheet = workbook.getActiveSheet(); + if (worksheet) { + const configure = { + actionName: ACTION_NAMES.ADD_FILTER_CRITERIA_ACTION, + sheetId: worksheet.getSheetId(), + columnPosition, + criteriaColumn: criteriaColumn.toSequence(), + }; + const command = new Command(workbook, configure); + commandManager.invoke(command); + } + } + + toSequence(): IFilter { + const criteriaColumns: { + [column: number]: IFilterCriteriaColumn; + } = {}; + + for (let column in this._criteriaColumns) { + criteriaColumns[column] = this._criteriaColumns[column].toSequence(); + } + + return { + ...this.toSequence(), + sheetId: this._sheetId, + range: this._range, + criteriaColumns, + }; + } +} diff --git a/packages/sheets-plugin-filter/src/Domain/FilterCriteria.ts b/packages/sheets-plugin-filter/src/Domain/FilterCriteria.ts new file mode 100644 index 0000000000..9991f1ced8 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Domain/FilterCriteria.ts @@ -0,0 +1,393 @@ +import { BooleanCriteria, Color, Context, RelativeDate, Sequence, Serializer, Tools } from '@univer/core'; +import { ConditionValue } from '../../../sheets-plugin-conditional-format'; +import { FilterCriteriaBuilder } from './FilterCriteriaBuilder'; + +export interface IFilterCriteria extends Sequence { + whenNumberGreaterThanOrEqualTo: number; + + criteriaType: BooleanCriteria; + + criteriaValues: ConditionValue[]; + + hiddenValues: string[]; + + visibleValues: string[]; + + visibleForegroundColor: Color; + + visibleBackgroundColor: Color; + + whenNumberEqualToAny: number[]; + + whenNumberGreaterThan: number; + + whenNumberBetweenEnd: number; + + whenNumberEqualTo: number; + + whenDateEqualToAny: Date[]; + + whenDateNotEqualTo: Date; + + whenFormulaSatisfied: string; + + whenDateNotEqualToAny: Date[]; + + whenNumberBetweenStart: number; + + whenDateAfter: Date; + + whenNumberNotBetweenStart: number; + + whenNumberNotBetweenEnd: number; + + whenNumberNotEqualTo: number; + + whenNumberNotEqualToAny: number[]; + + whenTextContains: string; + + whenTextDoesNotContain: string; + + whenTextEndsWith: string; + + whenTextEqualTo: string; + + whenTextEqualToAny: string[]; + + whenTextNotEqualTo: string; + + whenTextNotEqualToAny: string[]; + + whenTextStartsWith: string; + + whenDateAfterRelativeDate: RelativeDate; + + whenDateBefore: Date; + + whenDateBeforeRelativeDate: RelativeDate; + + whenDateEqualToDate: Date; + + whenDateEqualToRelativeDate: RelativeDate; + + whenNumberLessThan: number; + + whenNumberLessThanOrEqualTo: number; +} + +export class FilterCriteria extends Serializer implements Context.WithContext { + static newInstance(sequence: IFilterCriteria): FilterCriteria { + const criteria = new FilterCriteria(); + criteria._criteriaType = sequence.criteriaType; + criteria._whenNumberGreaterThanOrEqualTo = sequence.whenNumberGreaterThanOrEqualTo; + criteria._whenDateNotEqualToAny = sequence.whenDateNotEqualToAny; + criteria._whenNumberBetweenStart = sequence.whenNumberBetweenStart; + criteria._whenNumberBetweenEnd = sequence.whenNumberBetweenEnd; + criteria._criteriaValues = sequence.criteriaValues; + criteria._hiddenValues = sequence.hiddenValues; + criteria._visibleValues = sequence.visibleValues; + criteria._visibleForegroundColor = sequence.visibleForegroundColor; + criteria._visibleBackgroundColor = sequence.visibleBackgroundColor; + criteria._whenDateEqualToDate = sequence.whenDateEqualToDate; + criteria._whenDateEqualToRelativeDate = sequence.whenDateEqualToRelativeDate; + criteria._whenDateAfterRelativeDate = sequence.whenDateAfterRelativeDate; + criteria._whenDateBeforeRelativeDate = sequence.whenDateBeforeRelativeDate; + criteria._whenNumberEqualToAny = sequence.whenNumberEqualToAny; + criteria._whenNumberGreaterThan = sequence.whenNumberGreaterThan; + criteria._whenDateAfter = sequence.whenDateAfter; + criteria._whenDateBefore = sequence.whenDateBefore; + criteria._whenDateNotEqualTo = sequence.whenDateNotEqualTo; + criteria._whenFormulaSatisfied = sequence.whenFormulaSatisfied; + criteria._whenDateEqualToAny = sequence.whenDateEqualToAny; + criteria._whenNumberEqualTo = sequence.whenNumberEqualTo; + criteria._whenNumberNotEqualTo = sequence.whenNumberNotEqualTo; + criteria._whenNumberNotEqualToAny = sequence.whenNumberNotEqualToAny; + criteria._whenTextContains = sequence.whenTextContains; + criteria._whenTextDoesNotContain = sequence.whenTextDoesNotContain; + criteria._whenTextEndsWith = sequence.whenTextEndsWith; + criteria._whenTextEqualTo = sequence.whenTextEqualTo; + criteria._whenTextEqualToAny = sequence.whenTextEqualToAny; + criteria._whenTextNotEqualTo = sequence.whenTextNotEqualTo; + criteria._whenTextNotEqualToAny = sequence.whenTextNotEqualToAny; + criteria._whenTextStartsWith = sequence.whenTextStartsWith; + criteria._whenNumberNotBetweenStart = sequence.whenNumberNotBetweenStart; + criteria._whenNumberNotBetweenEnd = sequence.whenNumberNotBetweenEnd; + criteria._whenNumberLessThan = sequence.whenNumberLessThan; + criteria._whenNumberLessThanOrEqualTo = sequence.whenNumberLessThanOrEqualTo; + return criteria; + } + + private _context: Context; + + private _whenNumberGreaterThanOrEqualTo: number; + + private _criteriaType: BooleanCriteria; + + private _criteriaValues: ConditionValue[]; + + private _hiddenValues: string[]; + + private _visibleValues: string[]; + + private _visibleForegroundColor: Color; + + private _visibleBackgroundColor: Color; + + private _whenNumberBetweenEnd: number; + + private _whenNumberEqualTo: number; + + private _whenNumberEqualToAny: number[]; + + private _whenNumberGreaterThan: number; + + private _whenDateEqualToAny: Date[]; + + private _whenDateNotEqualTo: Date; + + private _whenFormulaSatisfied: string; + + private _whenDateNotEqualToAny: Date[]; + + private _whenNumberBetweenStart: number; + + private _whenDateAfter: Date; + + private _whenNumberNotBetweenStart: number; + + private _whenNumberNotBetweenEnd: number; + + private _whenNumberNotEqualTo: number; + + private _whenNumberNotEqualToAny: number[]; + + private _whenTextContains: string; + + private _whenTextDoesNotContain: string; + + private _whenTextEndsWith: string; + + private _whenTextEqualTo: string; + + private _whenTextEqualToAny: string[]; + + private _whenTextNotEqualTo: string; + + private _whenTextNotEqualToAny: string[]; + + private _whenTextStartsWith: string; + + private _whenDateAfterRelativeDate: RelativeDate; + + private _whenDateBefore: Date; + + private _whenDateBeforeRelativeDate: RelativeDate; + + private _whenDateEqualToDate: Date; + + private _whenDateEqualToRelativeDate: RelativeDate; + + private _whenNumberLessThan: number; + + private _whenNumberLessThanOrEqualTo: number; + + copy(): FilterCriteriaBuilder { + return FilterCriteriaBuilder.newInstance(this.toSequence()); + } + + toSequence(): IFilterCriteria { + return { + className: Tools.getClassName(this), + visibleForegroundColor: this._visibleForegroundColor, + visibleBackgroundColor: this._visibleBackgroundColor, + whenNumberEqualToAny: this._whenNumberEqualToAny, + whenNumberGreaterThan: this._whenNumberGreaterThan, + whenNumberBetweenEnd: this._whenNumberBetweenEnd, + whenNumberEqualTo: this._whenNumberEqualTo, + whenDateEqualToAny: this._whenDateEqualToAny, + whenDateNotEqualTo: this._whenDateNotEqualTo, + whenFormulaSatisfied: this._whenFormulaSatisfied, + whenDateNotEqualToAny: this._whenDateNotEqualToAny, + whenNumberBetweenStart: this._whenNumberBetweenStart, + whenNumberGreaterThanOrEqualTo: 0, + criteriaType: this._criteriaType, + criteriaValues: this._criteriaValues, + hiddenValues: this._hiddenValues, + visibleValues: this._visibleValues, + whenDateAfter: this._whenDateAfter, + whenNumberNotBetweenStart: this._whenNumberNotBetweenStart, + whenNumberNotBetweenEnd: this._whenNumberNotBetweenEnd, + whenNumberNotEqualTo: this._whenNumberNotEqualTo, + whenNumberNotEqualToAny: this._whenNumberNotEqualToAny, + whenTextContains: this._whenTextContains, + whenTextDoesNotContain: this._whenTextDoesNotContain, + whenTextEndsWith: this._whenTextEndsWith, + whenTextEqualTo: this._whenTextEqualTo, + whenTextEqualToAny: this._whenTextEqualToAny, + whenTextNotEqualTo: this._whenTextNotEqualTo, + whenTextNotEqualToAny: this._whenTextNotEqualToAny, + whenTextStartsWith: this._whenTextStartsWith, + whenDateAfterRelativeDate: this._whenDateAfterRelativeDate, + whenDateBefore: this._whenDateBefore, + whenDateBeforeRelativeDate: this._whenDateBeforeRelativeDate, + whenDateEqualToDate: this._whenDateEqualToDate, + whenDateEqualToRelativeDate: this._whenDateEqualToRelativeDate, + whenNumberLessThan: this._whenNumberLessThan, + whenNumberLessThanOrEqualTo: this._whenNumberLessThanOrEqualTo, + }; + } + + withContext(context: Context): FilterCriteria { + this._context = context; + return this; + } + + getContext(): Context { + return this._context; + } + + getWhenNumberGreaterThanOrEqualTo(): number { + return this._whenNumberGreaterThanOrEqualTo; + } + + getCriteriaType(): BooleanCriteria { + return this._criteriaType; + } + + getCriteriaValues(): ConditionValue[] { + return this._criteriaValues; + } + + getHiddenValues(): string[] { + return this._hiddenValues; + } + + getVisibleValues(): string[] { + return this._visibleValues; + } + + getVisibleForegroundColor(): Color { + return this._visibleForegroundColor; + } + + getVisibleBackgroundColor(): Color { + return this._visibleBackgroundColor; + } + + getWhenNumberEqualToAny(): number[] { + return this._whenNumberEqualToAny; + } + + getWhenNumberGreaterThan(): number { + return this._whenNumberGreaterThan; + } + + getWhenNumberBetweenEnd(): number { + return this._whenNumberBetweenEnd; + } + + getWhenNumberEqualTo(): number { + return this._whenNumberEqualTo; + } + + getWhenDateEqualToAny(): Date[] { + return this._whenDateEqualToAny; + } + + getWhenDateNotEqualTo(): Date { + return this._whenDateNotEqualTo; + } + + getWhenFormulaSatisfied(): string { + return this._whenFormulaSatisfied; + } + + getWhenDateNotEqualToAny(): Date[] { + return this._whenDateNotEqualToAny; + } + + getWhenNumberBetweenStart(): number { + return this._whenNumberBetweenStart; + } + + getWhenDateAfter(): Date { + return this._whenDateAfter; + } + + getWhenNumberNotBetweenStart(): number { + return this._whenNumberNotBetweenStart; + } + + getWhenNumberNotBetweenEnd(): number { + return this._whenNumberNotBetweenEnd; + } + + getWhenNumberNotEqualTo(): number { + return this._whenNumberNotEqualTo; + } + + getWhenNumberNotEqualToAny(): number[] { + return this._whenNumberNotEqualToAny; + } + + getWhenTextContains(): string { + return this._whenTextContains; + } + + getWhenTextDoesNotContain(): string { + return this._whenTextDoesNotContain; + } + + getWhenTextEndsWith(): string { + return this._whenTextEndsWith; + } + + getWhenTextEqualTo(): string { + return this._whenTextEqualTo; + } + + getWhenTextEqualToAny(): string[] { + return this._whenTextEqualToAny; + } + + getWhenTextNotEqualTo(): string { + return this._whenTextNotEqualTo; + } + + getWhenTextNotEqualToAny(): string[] { + return this._whenTextNotEqualToAny; + } + + getWhenTextStartsWith(): string { + return this._whenTextStartsWith; + } + + getWhenDateAfterRelativeDate(): RelativeDate { + return this._whenDateAfterRelativeDate; + } + + getWhenDateBefore(): Date { + return this._whenDateBefore; + } + + getWhenDateBeforeRelativeDate(): RelativeDate { + return this._whenDateBeforeRelativeDate; + } + + getWhenDateEqualToDate(): Date { + return this._whenDateEqualToDate; + } + + getWhenDateEqualToRelativeDate(): RelativeDate { + return this._whenDateEqualToRelativeDate; + } + + getWhenNumberLessThan(): number { + return this._whenNumberLessThan; + } + + getWhenNumberLessThanOrEqualTo(): number { + return this._whenNumberLessThanOrEqualTo; + } +} diff --git a/packages/sheets-plugin-filter/src/Domain/FilterCriteriaBuilder.ts b/packages/sheets-plugin-filter/src/Domain/FilterCriteriaBuilder.ts new file mode 100644 index 0000000000..450ea98d69 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Domain/FilterCriteriaBuilder.ts @@ -0,0 +1,569 @@ +import { BooleanCriteria, Color, Context, RelativeDate, Serializer, Tools, Tuples } from '@univer/core'; +import { ConditionValue } from '../../../sheets-plugin-conditional-format'; +import { FilterCriteria, IFilterCriteria } from './FilterCriteria'; + +export class FilterCriteriaBuilder extends Serializer implements Context.WithContext { + static newInstance(sequence: IFilterCriteria): FilterCriteriaBuilder { + const builder = new FilterCriteriaBuilder(); + builder._criteriaType = sequence.criteriaType; + builder._whenNumberGreaterThanOrEqualTo = sequence.whenNumberGreaterThanOrEqualTo; + builder._whenDateNotEqualToAny = sequence.whenDateNotEqualToAny; + builder._whenNumberBetweenStart = sequence.whenNumberBetweenStart; + builder._whenNumberBetweenEnd = sequence.whenNumberBetweenEnd; + builder._criteriaValues = sequence.criteriaValues; + builder._hiddenValues = sequence.hiddenValues; + builder._visibleValues = sequence.visibleValues; + builder._visibleForegroundColor = sequence.visibleForegroundColor; + builder._visibleBackgroundColor = sequence.visibleBackgroundColor; + builder._whenDateEqualToDate = sequence.whenDateEqualToDate; + builder._whenDateEqualToRelativeDate = sequence.whenDateEqualToRelativeDate; + builder._whenDateAfterRelativeDate = sequence.whenDateAfterRelativeDate; + builder._whenDateBeforeRelativeDate = sequence.whenDateBeforeRelativeDate; + builder._whenNumberEqualToAny = sequence.whenNumberEqualToAny; + builder._whenNumberGreaterThan = sequence.whenNumberGreaterThan; + builder._whenDateAfter = sequence.whenDateAfter; + builder._whenDateBefore = sequence.whenDateBefore; + builder._whenDateNotEqualTo = sequence.whenDateNotEqualTo; + builder._whenFormulaSatisfied = sequence.whenFormulaSatisfied; + builder._whenDateEqualToAny = sequence.whenDateEqualToAny; + builder._whenNumberEqualTo = sequence.whenNumberEqualTo; + builder._whenNumberNotEqualTo = sequence.whenNumberNotEqualTo; + builder._whenNumberNotEqualToAny = sequence.whenNumberNotEqualToAny; + builder._whenTextContains = sequence.whenTextContains; + builder._whenTextDoesNotContain = sequence.whenTextDoesNotContain; + builder._whenTextEndsWith = sequence.whenTextEndsWith; + builder._whenTextEqualTo = sequence.whenTextEqualTo; + builder._whenTextEqualToAny = sequence.whenTextEqualToAny; + builder._whenTextNotEqualTo = sequence.whenTextNotEqualTo; + builder._whenTextNotEqualToAny = sequence.whenTextNotEqualToAny; + builder._whenTextStartsWith = sequence.whenTextStartsWith; + builder._whenNumberNotBetweenStart = sequence.whenNumberNotBetweenStart; + builder._whenNumberNotBetweenEnd = sequence.whenNumberNotBetweenEnd; + builder._whenNumberLessThan = sequence.whenNumberLessThan; + builder._whenNumberLessThanOrEqualTo = sequence.whenNumberLessThanOrEqualTo; + return builder; + } + + private _context: Context; + + private _whenNumberGreaterThanOrEqualTo: number; + + private _criteriaType: BooleanCriteria; + + private _criteriaValues: ConditionValue[]; + + private _hiddenValues: string[]; + + private _visibleValues: string[]; + + private _visibleForegroundColor: Color; + + private _visibleBackgroundColor: Color; + + private _whenNumberEqualToAny: number[]; + + private _whenNumberGreaterThan: number; + + private _whenNumberBetweenEnd: number; + + private _whenNumberEqualTo: number; + + private _whenDateEqualToAny: Date[]; + + private _whenDateNotEqualTo: Date; + + private _whenFormulaSatisfied: string; + + private _whenDateNotEqualToAny: Date[]; + + private _whenNumberBetweenStart: number; + + private _whenDateAfter: Date; + + private _whenNumberNotBetweenStart: number; + + private _whenNumberNotBetweenEnd: number; + + private _whenNumberNotEqualTo: number; + + private _whenNumberNotEqualToAny: number[]; + + private _whenTextContains: string; + + private _whenTextDoesNotContain: string; + + private _whenTextEndsWith: string; + + private _whenTextEqualTo: string; + + private _whenTextEqualToAny: string[]; + + private _whenTextNotEqualTo: string; + + private _whenTextNotEqualToAny: string[]; + + private _whenTextStartsWith: string; + + private _whenDateAfterRelativeDate: RelativeDate; + + private _whenDateBefore: Date; + + private _whenDateBeforeRelativeDate: RelativeDate; + + private _whenDateEqualToDate: Date; + + private _whenDateEqualToRelativeDate: RelativeDate; + + private _whenNumberLessThan: number; + + private _whenNumberLessThanOrEqualTo: number; + + // Constructs a filter criteria from the settings supplied to the builder. + build(): FilterCriteria { + return FilterCriteria.newInstance(this.toSequence()).withContext(this._context); + } + + // Creates a builder for a filter criteria based on this filter criteria's settings. + copy(): FilterCriteriaBuilder { + return FilterCriteriaBuilder.newInstance(this.toSequence()).withContext(this._context); + } + + withContext(context: Context): FilterCriteriaBuilder { + this._context = context; + return this; + } + + toSequence(): IFilterCriteria { + return { + className: Tools.getClassName(this), + visibleForegroundColor: this._visibleForegroundColor, + visibleBackgroundColor: this._visibleBackgroundColor, + whenNumberEqualToAny: this._whenNumberEqualToAny, + whenNumberGreaterThan: this._whenNumberGreaterThan, + whenNumberBetweenEnd: this._whenNumberBetweenEnd, + whenNumberEqualTo: this._whenNumberEqualTo, + whenDateEqualToAny: this._whenDateEqualToAny, + whenDateNotEqualTo: this._whenDateNotEqualTo, + whenFormulaSatisfied: this._whenFormulaSatisfied, + whenDateNotEqualToAny: this._whenDateNotEqualToAny, + whenNumberBetweenStart: this._whenNumberBetweenStart, + whenNumberGreaterThanOrEqualTo: 0, + criteriaType: this._criteriaType, + criteriaValues: this._criteriaValues, + hiddenValues: this._hiddenValues, + visibleValues: this._visibleValues, + whenDateAfter: this._whenDateAfter, + whenNumberNotBetweenStart: this._whenNumberNotBetweenStart, + whenNumberNotBetweenEnd: this._whenNumberNotBetweenEnd, + whenNumberNotEqualTo: this._whenNumberNotEqualTo, + whenNumberNotEqualToAny: this._whenNumberNotEqualToAny, + whenTextContains: this._whenTextContains, + whenTextDoesNotContain: this._whenTextDoesNotContain, + whenTextEndsWith: this._whenTextEndsWith, + whenTextEqualTo: this._whenTextEqualTo, + whenTextEqualToAny: this._whenTextEqualToAny, + whenTextNotEqualTo: this._whenTextNotEqualTo, + whenTextNotEqualToAny: this._whenTextNotEqualToAny, + whenTextStartsWith: this._whenTextStartsWith, + whenDateAfterRelativeDate: this._whenDateAfterRelativeDate, + whenDateBefore: this._whenDateBefore, + whenDateBeforeRelativeDate: this._whenDateBeforeRelativeDate, + whenDateEqualToDate: this._whenDateEqualToDate, + whenDateEqualToRelativeDate: this._whenDateEqualToRelativeDate, + whenNumberLessThan: this._whenNumberLessThan, + whenNumberLessThanOrEqualTo: this._whenNumberLessThanOrEqualTo, + }; + } + + getContext(): Context { + return this._context; + } + + getWhenNumberGreaterThanOrEqualTo(): number { + return this._whenNumberGreaterThanOrEqualTo; + } + + getCriteriaType(): BooleanCriteria { + return this._criteriaType; + } + + getCriteriaValues(): ConditionValue[] { + return this._criteriaValues; + } + + getHiddenValues(): string[] { + return this._hiddenValues; + } + + getVisibleValues(): string[] { + return this._visibleValues; + } + + getVisibleForegroundColor(): Color { + return this._visibleForegroundColor; + } + + getVisibleBackgroundColor(): Color { + return this._visibleBackgroundColor; + } + + getWhenNumberEqualToAny(): number[] { + return this._whenNumberEqualToAny; + } + + getWhenNumberGreaterThan(): number { + return this._whenNumberGreaterThan; + } + + getWhenNumberBetweenEnd(): number { + return this._whenNumberBetweenEnd; + } + + getWhenNumberEqualTo(): number { + return this._whenNumberEqualTo; + } + + getWhenDateEqualToAny(): Date[] { + return this._whenDateEqualToAny; + } + + getWhenDateNotEqualTo(): Date { + return this._whenDateNotEqualTo; + } + + getWhenFormulaSatisfied(): string { + return this._whenFormulaSatisfied; + } + + getWhenDateNotEqualToAny(): Date[] { + return this._whenDateNotEqualToAny; + } + + getWhenNumberBetweenStart(): number { + return this._whenNumberBetweenStart; + } + + getWhenDateAfter(): Date { + return this._whenDateAfter; + } + + getWhenNumberNotBetweenStart(): number { + return this._whenNumberNotBetweenStart; + } + + getWhenNumberNotBetweenEnd(): number { + return this._whenNumberNotBetweenEnd; + } + + getWhenNumberNotEqualTo(): number { + return this._whenNumberNotEqualTo; + } + + getWhenNumberNotEqualToAny(): number[] { + return this._whenNumberNotEqualToAny; + } + + getWhenTextContains(): string { + return this._whenTextContains; + } + + getWhenTextDoesNotContain(): string { + return this._whenTextDoesNotContain; + } + + getWhenTextEndsWith(): string { + return this._whenTextEndsWith; + } + + getWhenTextEqualTo(): string { + return this._whenTextEqualTo; + } + + getWhenTextEqualToAny(): string[] { + return this._whenTextEqualToAny; + } + + getWhenTextNotEqualTo(): string { + return this._whenTextNotEqualTo; + } + + getWhenTextNotEqualToAny(): string[] { + return this._whenTextNotEqualToAny; + } + + getWhenTextStartsWith(): string { + return this._whenTextStartsWith; + } + + getWhenDateAfterRelativeDate(): RelativeDate { + return this._whenDateAfterRelativeDate; + } + + getWhenDateBefore(): Date { + return this._whenDateBefore; + } + + getWhenDateBeforeRelativeDate(): RelativeDate { + return this._whenDateBeforeRelativeDate; + } + + getWhenDateEqualToDate(): Date { + return this._whenDateEqualToDate; + } + + getWhenDateEqualToRelativeDate(): RelativeDate { + return this._whenDateEqualToRelativeDate; + } + + getWhenNumberLessThan(): number { + return this._whenNumberLessThan; + } + + getWhenNumberLessThanOrEqualTo(): number { + return this._whenNumberLessThanOrEqualTo; + } + + // Sets the values to hide. + setHiddenValues(values: string[]): FilterCriteriaBuilder { + this._hiddenValues = values; + return this; + } + + // Sets the background color used as a filter criteria. + setVisibleBackgroundColor(visibleBackgroundColor: Color) { + this._visibleBackgroundColor = visibleBackgroundColor; + } + + // Sets the foreground color used as a filter criteria. + setVisibleForegroundColor(visibleForegroundColor: Color) { + this._visibleForegroundColor = visibleForegroundColor; + } + + // Sets the values to show. + setVisibleValues(values: string[]): FilterCriteriaBuilder { + this._visibleValues = values; + return this; + } + + // Sets the filter criteria to show cells where the cell is empty. + whenCellEmpty(): FilterCriteriaBuilder { + this._criteriaType = BooleanCriteria.CELL_EMPTY; + return this; + } + + // Sets the filter criteria to show cells where the cell is not empty. + whenCellNotEmpty(): FilterCriteriaBuilder { + this._criteriaType = BooleanCriteria.CELL_NOT_EMPTY; + return this; + } + + // Sets the filter criteria to show cells where the cell date is after the specified value. + whenDateAfter(date: Date): FilterCriteriaBuilder; + // Sets the filter criteria to show cells where the cell date is after the specified relative date. + whenDateAfter(date: RelativeDate): FilterCriteriaBuilder; + whenDateAfter(...argument: any): FilterCriteriaBuilder { + if (Tuples.checkup(argument, Date)) { + this._whenDateAfter = argument[0]; + this._criteriaType = BooleanCriteria.DATE_AFTER; + } else { + this._whenDateAfterRelativeDate = argument[0]; + this._criteriaType = BooleanCriteria.DATE_AFTER_RELATIVE; + } + return this; + } + + // Sets the filter criteria to show cells where the cell date is before the specified date. + whenDateBefore(date: Date): FilterCriteriaBuilder; + // Sets the filter criteria to show cells where a cell date is before the specified relative date. + whenDateBefore(date: RelativeDate): FilterCriteriaBuilder; + whenDateBefore(...argument: any): FilterCriteriaBuilder { + if (Tuples.checkup(argument, Date)) { + this._whenDateBefore = argument[0]; + this._criteriaType = BooleanCriteria.DATE_BEFORE; + } else { + this._whenDateBeforeRelativeDate = argument[0]; + this._criteriaType = BooleanCriteria.DATE_BEFORE_RELATIVE; + } + + return this; + } + + // Sets the filter criteria to show cells where a cell date is equal to the specified date. + whenDateEqualTo(date: Date): FilterCriteriaBuilder; + // Sets the filter criteria to show cells where a cell date is equal to the specified relative date. + whenDateEqualTo(date: RelativeDate): FilterCriteriaBuilder; + whenDateEqualTo(...argument: any): FilterCriteriaBuilder { + if (Tuples.checkup(argument, Date)) { + this._whenDateEqualToDate = argument[0]; + this._criteriaType = BooleanCriteria.DATE_EQUAL_TO; + } else { + this._whenDateEqualToRelativeDate = argument[0]; + this._criteriaType = BooleanCriteria.DATE_EQUAL_TO_RELATIVE; + } + + return this; + } + + // Sets the filter criteria to show cells where the cell date is equal to any of the specified dates. + whenDateEqualToAny(dates: Date[]): FilterCriteriaBuilder { + this._whenDateEqualToAny = dates; + this._criteriaType = BooleanCriteria.DATE_EQUAL_TO; + return this; + } + + // Sets the filter criteria to show cells where the cell date is not equal to the specified date. + whenDateNotEqualTo(date: Date): FilterCriteriaBuilder { + this._whenDateNotEqualTo = date; + this._criteriaType = BooleanCriteria.DATE_NOT_EQUAL_TO; + return this; + } + + // Sets the filter criteria to show cells where the cell date is not equal to any of the specified dates. + whenDateNotEqualToAny(dates: Date[]): FilterCriteriaBuilder { + this._whenDateNotEqualToAny = dates; + this._criteriaType = BooleanCriteria.DATE_NOT_EQUAL_TO; + return this; + } + + // Sets the filter criteria to show cells where the specified formula (such as =B:B { + static newInstance(sequence: IFilterCriteriaColumn): FilterCriteriaColumn { + const criteriaColumn = new FilterCriteriaColumn(); + criteriaColumn._column = sequence.column; + criteriaColumn._criteria = FilterCriteria.fromSequence(sequence.criteria); + return criteriaColumn; + } + + private _criteria: FilterCriteria; + + private _column: number; + + private _context: Context; + + withContext(context: Context): FilterCriteriaColumn { + this._context = context; + return this; + } + + getColumn(): number { + return this._column; + } + + getContext(): Context { + return this._context; + } + + getCriteria(): FilterCriteria { + return this._criteria; + } + + toSequence(): IFilterCriteriaColumn { + return { + ...this.toSequence(), + criteria: this._criteria.toSequence(), + column: this._column, + }; + } +} diff --git a/packages/sheets-plugin-filter/src/Domain/FilterList.ts b/packages/sheets-plugin-filter/src/Domain/FilterList.ts new file mode 100644 index 0000000000..157434440b --- /dev/null +++ b/packages/sheets-plugin-filter/src/Domain/FilterList.ts @@ -0,0 +1,59 @@ +import { GroupModel, Sequence, Serializer, Tools } from '@univer/core'; +import { Filter, IFilter } from './Filter'; + +export interface IFilterList extends Sequence { + filters?: { + [sheetId: string]: IFilter; + }; +} + +export class FilterList extends Serializer implements GroupModel<{ [sheetId: string]: Filter }> { + static newInstance(sequence: IFilterList): FilterList { + const listModel = new FilterList(); + const filters = sequence.filters ?? {}; + for (let key in filters) { + listModel._filters[key] = Filter.fromSequence(filters[key]); + } + return listModel; + } + + private _filters: { + [sheetId: string]: Filter; + }; + + constructor() { + super(); + this._filters = {}; + } + + addFilter(sheetId: string, filter: Filter): void { + this._filters[sheetId] = filter; + } + + getFilter(sheetId: string): Filter { + return this._filters[sheetId]; + } + + removeFilter(sheetId: string): void { + delete this._filters[sheetId]; + } + + getGroupModel(): { [sheetId: string]: Filter } { + return this._filters; + } + + toSequence(): IFilterList { + let filters: { + [sheetId: string]: IFilter; + } = {}; + + for (let key in this._filters) { + filters[key] = this._filters[key].toSequence(); + } + + return { + className: Tools.getClassName(this), + filters, + }; + } +} diff --git a/packages/sheets-plugin-filter/src/Domain/index.ts b/packages/sheets-plugin-filter/src/Domain/index.ts new file mode 100644 index 0000000000..a0b0a6c6ba --- /dev/null +++ b/packages/sheets-plugin-filter/src/Domain/index.ts @@ -0,0 +1,5 @@ +export * from './Filter'; +export * from './FilterCriteria'; +export * from './FilterCriteriaBuilder'; +export * from './FilterCriteriaColumn'; +export * from './FilterList'; diff --git a/packages/sheets-plugin-filter/src/Enum/BooleanCriteria.ts b/packages/sheets-plugin-filter/src/Enum/BooleanCriteria.ts new file mode 100644 index 0000000000..b9e1f08f61 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Enum/BooleanCriteria.ts @@ -0,0 +1,73 @@ +export enum BooleanCriteria { + // The criteria is met when a cell is empty. + CELL_EMPTY = 'CELL_EMPTY', + + // The criteria is met when a cell is not empty. + CELL_NOT_EMPTY = 'CELL_NOT_EMPTY', + + // The criteria is met when a date is after the given value. + DATE_AFTER = 'DATE_AFTER', + + // The criteria is met when a date is before the given value. + DATE_BEFORE = 'DATE_BEFORE', + + // The criteria is met when a date is equal to the given value. + DATE_EQUAL_TO = 'DATE_EQUAL_TO', + + // The criteria is met when a date is not equal to the given value. + DATE_NOT_EQUAL_TO = 'DATE_NOT_EQUAL_TO', + + // The criteria is met when a date is after the relative date value. + DATE_AFTER_RELATIVE = 'DATE_AFTER_RELATIVE', + + // The criteria is met when a date is before the relative date value. + DATE_BEFORE_RELATIVE = 'DATE_BEFORE_RELATIVE', + + // The criteria is met when a date is equal to the relative date value. + DATE_EQUAL_TO_RELATIVE = 'DATE_EQUAL_TO_RELATIVE', + + // The criteria is met when a number that is between the given values. + NUMBER_BETWEEN = 'NUMBER_BETWEEN', + + // The criteria is met when a number that is equal to the given value. + NUMBER_EQUAL_TO = 'NUMBER_EQUAL_TO', + + // The criteria is met when a number that is greater than the given value. + NUMBER_GREATER_THAN = 'NUMBER_GREATER_THAN', + + // The criteria is met when a number that is greater than or equal to the given value. + NUMBER_GREATER_THAN_OR_EQUAL_TO = 'NUMBER_GREATER_THAN_OR_EQUAL_TO', + + // The criteria is met when a number that is less than the given value. + NUMBER_LESS_THAN = 'NUMBER_LESS_THAN', + + // The criteria is met when a number that is less than or equal to the given value. + NUMBER_LESS_THAN_OR_EQUAL_TO = 'NUMBER_LESS_THAN_OR_EQUAL_TO', + + // The criteria is met when a number that is not between the given values. + NUMBER_NOT_BETWEEN = 'NUMBER_NOT_BETWEEN', + + // The criteria is met when a number that is not equal to the given value. + NUMBER_NOT_EQUAL_TO = 'NUMBER_NOT_EQUAL_TO', + + // The criteria is met when the input contains the given value. + TEXT_CONTAINS = 'TEXT_CONTAINS', + + // The criteria is met when the input does not contain the given value. + TEXT_DOES_NOT_CONTAIN = 'TEXT_DOES_NOT_CONTAIN', + + // The criteria is met when the input is equal to the given value. + TEXT_EQUAL_TO = 'TEXT_EQUAL_TO', + + // The criteria is met when the input is not equal to the given value. + TEXT_NOT_EQUAL_TO = 'TEXT_NOT_EQUAL_TO', + + // The criteria is met when the input begins with the given value. + TEXT_STARTS_WITH = 'TEXT_STARTS_WITH', + + // The criteria is met when the input ends with the given value. + TEXT_ENDS_WITH = 'TEXT_ENDS_WITH', + + // The criteria is met when the input makes the given formula evaluate to true. + CUSTOM_FORMULA = 'CUSTOM_FORMULA', +} diff --git a/packages/sheets-plugin-filter/src/FilterPlugin.tsx b/packages/sheets-plugin-filter/src/FilterPlugin.tsx new file mode 100644 index 0000000000..5ba8944246 --- /dev/null +++ b/packages/sheets-plugin-filter/src/FilterPlugin.tsx @@ -0,0 +1,68 @@ +import { ISlotElement } from '@univer/base-component'; +import { Command, Context, IRangeData, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { en, zh } from './Locale'; +import { IConfig } from './IData'; +import { FilterButton } from './UI/FilterButton'; +import { ACTION_NAMES, FILTER_PLUGIN_NAME } from './Const'; +import { Filter, FilterList, IFilterList } from './Domain'; + +export class FilterPlugin extends Plugin { + private _filterList: FilterList; + + constructor(config?: IFilterList) { + super(FILTER_PLUGIN_NAME); + this._filterList = FilterList.fromSequence(config || {}); + } + + static create(config?: IFilterList) { + return new FilterPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + onMounted(context: Context): void { + const config: IConfig = { + context, + }; + context.getLocale().load({ + en, + zh, + }); + context + .getPluginManager() + .getRequirePluginByName(PLUGIN_NAMES.SPREADSHEET) + .addButton({ + locale: FILTER_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }); + } + + addFilter(filter: Filter): void { + const context = this.getContext(); + const commandManager = context.getCommandManager(); + const workbook = context.getWorkBook(); + const worksheet = workbook.getActiveSheet(); + if (worksheet) { + const configure = { + actionName: ACTION_NAMES.ADD_FILTER_ACTION, + filter: filter.toSequence(), + sheetId: worksheet.getSheetId(), + }; + const command = new Command(workbook, configure); + commandManager.invoke(command); + } + } + + newFilter(sheetId: string, range: IRangeData): Filter { + return new Filter(sheetId, range).withContext(this.getContext()); + } + + getFilterList(): FilterList { + return this._filterList; + } +} diff --git a/packages/sheets-plugin-filter/src/IData.ts b/packages/sheets-plugin-filter/src/IData.ts new file mode 100644 index 0000000000..2d99558aa0 --- /dev/null +++ b/packages/sheets-plugin-filter/src/IData.ts @@ -0,0 +1,9 @@ +import { Context } from '@univer/core'; +import { FilterPlugin } from './FilterPlugin'; + +export type IConfig = { + context: Context; +}; + +// Types for props +export type IProps = { config: IConfig; super: FilterPlugin }; diff --git a/packages/sheets-plugin-filter/src/IData/FilterType.ts b/packages/sheets-plugin-filter/src/IData/FilterType.ts new file mode 100644 index 0000000000..38f3f82801 --- /dev/null +++ b/packages/sheets-plugin-filter/src/IData/FilterType.ts @@ -0,0 +1,24 @@ +import { IColor, IRangeData, Nullable } from '@univer/core'; + +export type IFilterType = { + range: IRangeData; + sortSpecs: {}; + filterSpecs: IFilterSpecsType[]; +}; + +export type IFilterSpecsType = { + filterCriteria: Nullable; + // Union field reference can be only one of the following: + columnIndex: number; + dataSourceColumnReference: {}; + // End of list of possible types for union field reference. +}; + +type IFilterCriteria = { + hiddenValues: string[]; + condition: BooleanCondition; + visibleBackgroundColor: IColor; + // visibleBackgroundColorStyle: IColorStyle; + visibleForegroundColor: IColor; + // visibleForegroundColorStyle: IColorStyle; +}; diff --git a/packages/sheets-plugin-filter/src/IData/index.ts b/packages/sheets-plugin-filter/src/IData/index.ts new file mode 100644 index 0000000000..1d99946701 --- /dev/null +++ b/packages/sheets-plugin-filter/src/IData/index.ts @@ -0,0 +1 @@ +export * from './FilterType'; diff --git a/packages/sheets-plugin-filter/src/Locale/en.ts b/packages/sheets-plugin-filter/src/Locale/en.ts new file mode 100644 index 0000000000..f7752ee54b --- /dev/null +++ b/packages/sheets-plugin-filter/src/Locale/en.ts @@ -0,0 +1,58 @@ +export default { + // toolbar: { + filterLabel: 'Filter', + filter: { + filter: 'create filter', + + sortByAsc: 'Sort A-Z', + sortByDesc: 'Sort Z-A', + filterByColor: 'Filter by color', + filterByCondition: 'Filter by condition', + filterByValues: 'Filter by values', + + filiterInputNone: 'None', + + filiterInputTip: 'Enter filter value', + filiterRangeStartTip: 'Value for formula', + filiterRangeEndTip: 'Value for formula', + + filterValueByAllBtn: 'Check all', + filterValueByClearBtn: 'Clear', + filterValueByInverseBtn: 'Inverse', + filterValueByTip: 'filter By Values', + filterConform: 'Confirm', + filterCancel: 'Cancel', + clearFilter: 'Clear filter', + + conditionNone: 'None', + conditionCellIsNull: 'Is empty', + conditionCellNotNull: 'Is not empty', + conditionCellTextContain: 'Text contains', + conditionCellTextNotContain: 'Text does not contain', + conditionCellTextStart: 'Text starts with', + conditionCellTextEnd: 'Text ends with', + conditionCellTextEqual: 'Text is exactly', + conditionCellDateEqual: 'Date is', + conditionCellDateBefore: 'Date is before', + conditionCellDateAfter: 'Date is after', + conditionCellGreater: 'Greater than', + conditionCellGreaterEqual: 'Greater than or equal to', + conditionCellLess: 'Less than', + conditionCellLessEqual: 'Less than or equal to', + conditionCellEqual: 'Is equal to', + conditionCellNotEqual: 'Is not equal to', + conditionCellBetween: 'Is between', + conditionCellNotBetween: 'Is not between', + + filiterMoreDataTip: 'Big amount of data! please wait', + filiterMonthText: 'Month', + filiterYearText: 'Year', + filiterByColorTip: 'Filter by cell color', + filiterByTextColorTip: 'Filter by font color', + filterContainerOneColorTip: 'This column contains only one color', + filterDateFormatTip: 'Date format', + + valueBlank: '(Null)', + mergeError: 'There are merged cells in the filter selection, this operation cannot be performed!', + }, +}; diff --git a/packages/sheets-plugin-filter/src/Locale/index.ts b/packages/sheets-plugin-filter/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-filter/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-filter/src/Locale/zh.ts b/packages/sheets-plugin-filter/src/Locale/zh.ts new file mode 100644 index 0000000000..c1b0d86277 --- /dev/null +++ b/packages/sheets-plugin-filter/src/Locale/zh.ts @@ -0,0 +1,57 @@ +export default { + filterLabel: '筛选', + filter: { + filter: '筛选', + clearFilter: '清除筛选', + + sortByAsc: '以A-Z升序排列', + sortByDesc: '以Z-A降序排列', + filterByColor: '按颜色筛选', + filterByCondition: '按条件过滤', + filterByValues: '按值过滤', + + filiterInputNone: '无', + + filiterInputTip: '输入筛选值', + filiterRangeStartTip: '范围开始', + filiterRangeEndTip: '范围结束', + + filterValueByAllBtn: '全选', + filterValueByClearBtn: '清除', + filterValueByInverseBtn: '反选', + filterValueByTip: '按照值进行筛选', + filterConform: '确 认', + filterCancel: '取 消', + + conditionNone: '无', + conditionCellIsNull: '单元格为空', + conditionCellNotNull: '单元格有数据', + conditionCellTextContain: '文本包含', + conditionCellTextNotContain: '文本不包含', + conditionCellTextStart: '文本开头为', + conditionCellTextEnd: '文本结尾为', + conditionCellTextEqual: '文本等于', + conditionCellDateEqual: '日期等于', + conditionCellDateBefore: '日期早于', + conditionCellDateAfter: '日期晚于', + conditionCellGreater: '大于', + conditionCellGreaterEqual: '大于等于', + conditionCellLess: '小于', + conditionCellLessEqual: '小于等于', + conditionCellEqual: '等于', + conditionCellNotEqual: '不等于', + conditionCellBetween: '介于', + conditionCellNotBetween: '不在其中', + + filiterMoreDataTip: '数据量大!请稍后', + filiterMonthText: '月', + filiterYearText: '年', + filiterByColorTip: '按单元格颜色筛选', + filiterByTextColorTip: '按单元格字体颜色筛选', + filterContainerOneColorTip: '本列仅包含一种颜色', + filterDateFormatTip: '日期格式', + + valueBlank: '(空白)', + mergeError: '筛选选区有合并单元格,无法执行此操作!', + }, +}; diff --git a/packages/sheets-plugin-filter/src/UI/FilterButton.tsx b/packages/sheets-plugin-filter/src/UI/FilterButton.tsx new file mode 100644 index 0000000000..dc825392d1 --- /dev/null +++ b/packages/sheets-plugin-filter/src/UI/FilterButton.tsx @@ -0,0 +1,102 @@ +import { BaseSelectProps, Component, IToolBarItemProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { IProps } from '../IData'; +import { FilterPlugin } from '../FilterPlugin'; + +interface IState { + filter: IToolBarItemProps; + isFilter: boolean; + filterPlugin: FilterPlugin | null; +} + +export class FilterButton extends Component { + protected _localeObserver: Nullable>; + + initialize(props: IProps) { + const FilterRankIcon = this.getComponentRender().renderFunction('FilterRankIcon'); + const NextIcon = this.getComponentRender().renderFunction('NextIcon'); + const FilterIcon = this.getComponentRender().renderFunction('FilterIcon'); + const CleanIcon = this.getComponentRender().renderFunction('CleanIcon'); + + this.state = { + filter: { + locale: 'filter', + type: 'select', + label: , + icon: , + show: true, + children: [ + { + locale: 'filter.filter', + icon: , + onClick: () => {}, + }, + { + locale: 'filter.clearFilter', + icon: , + onClick: () => {}, + }, + ], + }, + filterPlugin: null, + isFilter: false, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this.getContext() + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this.getContext().getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.filter; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}Label`); + + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + + return { + filter: item, + }; + }); + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const { filter } = state; + const Select = this.getComponentRender().renderFunction('Select'); + return + {modalData.map((item) => { + if (!item.show) return; + return ( + + {item.children} + + ); + })} + + ); + } +} diff --git a/packages/sheets-plugin-find/src/UI/LocationContent.tsx b/packages/sheets-plugin-find/src/UI/LocationContent.tsx new file mode 100644 index 0000000000..78a06cba32 --- /dev/null +++ b/packages/sheets-plugin-find/src/UI/LocationContent.tsx @@ -0,0 +1,223 @@ +import { BaseComponentRender, BaseComponentSheet, Component } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import styles from './index.module.less'; + +type LocationProps = { + config: any; +}; +type LocationState = { + radioGroup: LabelProps[]; + active: string | number; +}; +type LabelProps = { + locale: string; + label?: string; + value: string; + disabled?: boolean; + children?: LabelProps[]; + checked?: boolean; +}; + +export class LocationContent extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize() { + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + this.state = { + radioGroup: [ + { + locale: 'find.locationConstant', + children: [ + { + locale: 'find.date', + disabled: true, + checked: true, + value: '1', + }, + { + locale: 'find.number', + checked: true, + value: '2', + }, + { + locale: 'find.string', + checked: true, + value: '3', + }, + { + locale: 'find.locationBool', + checked: true, + disabled: true, + value: '4', + }, + { + locale: 'find.error', + checked: true, + value: '5', + }, + ], + value: '1', + }, + { + locale: 'find.locationFormula', + children: [ + { + locale: 'find.date', + checked: true, + disabled: true, + value: '1', + }, + { + locale: 'find.number', + disabled: true, + checked: true, + value: '2', + }, + { + locale: 'find.string', + disabled: true, + checked: true, + value: '3', + }, + { + locale: 'find.locationBool', + checked: true, + disabled: true, + value: '4', + }, + { + locale: 'find.error', + checked: true, + disabled: true, + value: '5', + }, + ], + value: '2', + }, + { + locale: 'find.locationNull', + value: '3', + }, + { + locale: 'find.locationCondition', + value: '4', + }, + { + locale: 'find.locationRowSpan', + value: '5', + }, + { + locale: 'find.locationColumnSpan', + value: '6', + }, + ], + active: '1', + }; + } + + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState) => { + let { radioGroup } = prevState; + radioGroup.forEach((item) => { + item.label = locale.get(item.locale); + if (item.children) { + item.children.forEach((ele) => { + ele.label = locale.get(ele.locale); + }); + } + }); + + return { + radioGroup, + }; + }); + } + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + setValue = (value: object, fn?: () => void) => { + this.setState( + (prevState) => ({ + ...value, + }), + fn + ); + }; + + handleChange = (value: string) => { + let { radioGroup } = this.state; + radioGroup.forEach((item) => { + if (item.children) { + item.children.forEach((ele) => { + ele.disabled = true; + }); + } + }); + + radioGroup.forEach((item) => { + if (item.value === value) { + if (item.children) { + item.children.forEach((ele) => { + ele.disabled = false; + }); + } + this.setValue({ active: value, radioGroup }); + } + }); + }; + + handleCheckbox = (value: string[], locale: string) => { + let { radioGroup } = this.state; + let index = radioGroup.findIndex((item) => item.locale === locale); + radioGroup[index].children!.forEach((ele) => { + if (value.includes(ele.value)) { + ele.checked = true; + } else { + ele.checked = false; + } + }); + this.setValue({ radioGroup }); + }; + + render(props: LocationProps, state: LocationState) { + const CheckboxGroup = this.Render.renderFunction('CheckboxGroup'); + const Radio = this.Render.renderFunction('Radio'); + const RadioGroup = this.Render.renderFunction('RadioGroup'); + const { radioGroup, active } = state; + return ( +
        + + {radioGroup.map((item) => { + if (item.children) { + return ( + + this.handleCheckbox(value, item.locale)} /> + + ); + } + return ; + })} + +
        + ); + } +} diff --git a/packages/sheets-plugin-find/src/UI/SearchContent.tsx b/packages/sheets-plugin-find/src/UI/SearchContent.tsx new file mode 100644 index 0000000000..eaef949abd --- /dev/null +++ b/packages/sheets-plugin-find/src/UI/SearchContent.tsx @@ -0,0 +1,282 @@ +import { BaseComponentRender, BaseComponentSheet, Component } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +// import { Button, CheckboxGroup, Input, Tab, TabPane } from '@univer/style-universheet'; +import styles from './index.module.less'; +import { FindPlugin } from '../FindPlugin'; + +interface SearchProps { + config: any; + activeKey?: string; +} +interface ContentProps extends SearchProps { + keys?: string; +} +type SearchState = { + tab: LabelProps[]; +}; +type ContentState = { + content: LabelProps[]; + match: LabelProps[]; + buttons: LabelProps[]; +}; +type LabelProps = { + name: string; + label?: string; + value?: string; + key?: string; + onClick?: () => void; +}; + +export class SearchContent extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize() { + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + this.state = { + tab: [ + { + name: 'find.find', + key: 'find', + }, + { + name: 'find.replace', + key: 'replace', + }, + ], + }; + } + + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState) => { + let { tab } = prevState; + + tab.forEach((item) => { + item.label = locale.get(item.name); + }); + + return { + tab, + }; + }); + } + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + setValue = (value: object, fn?: () => void) => { + this.setState( + (prevState) => ({ + ...value, + }), + fn + ); + }; + + render(props: SearchProps, state: SearchState) { + const Tab = this.Render.renderFunction('Tab'); + const TabPane = this.Render.renderFunction('TabPane'); + const { tab } = state; + const { config, activeKey } = props; + return ( + + {tab.map((item, index) => ( + + + + ))} + + ); + } +} + +// tab content +class Content extends Component { + Render: BaseComponentRender; + + private _localeObserver: Nullable>; + + private _searchText: string | RegExp; + + private _matchList: string[] = []; // match condition list + + initialize(props: ContentProps) { + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + // super(props); + this._context = props.config.context; + this.state = { + content: [ + { + name: 'find.findTextbox', + }, + { + name: 'find.replaceTextbox', + }, + ], + buttons: [ + { + name: 'find.allFindBtn', + key: 'find', + onClick: () => this.findText(true), + }, + { + name: 'find.findBtn', + key: 'find', + onClick: () => this.findText(), + }, + { + name: 'find.allReplaceBtn', + key: 'replace', + }, + { + name: 'find.replaceBtn', + key: 'replace', + }, + ], + match: [ + { + name: 'find.regexTextbox', + value: 'regex', + }, + { + name: 'find.wholeTextbox', + value: 'whole', + }, + { + name: 'find.distinguishTextbox', + value: 'capslock', + }, + ], + }; + } + + private setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState) => { + let { content, buttons, match } = prevState; + + content.forEach((item) => { + item.label = locale.get(item.name); + }); + buttons.forEach((item) => { + item.label = locale.get(item.name); + }); + match.forEach((item) => { + item.label = locale.get(item.name); + }); + + return { + content, + buttons, + match, + }; + }); + } + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + // get search text + private changeSearchText = (e: Event) => { + const target = e.target as HTMLInputElement; + this._searchText = target.value; + }; + + // get match condition + private getMatch = (value: string[]) => { + this._matchList = value; + }; + + private findText = (isFull?: boolean) => { + // @ts-ignore + // TODO ... + // BUG + let textFinder = this._context.getPluginManager().getPluginByName('pluginFind')!.createTextFinder('A1:B10', this._searchText); + if (this._matchList.includes('capslock')) textFinder = textFinder.matchCase(true); + if (this._matchList.includes('whole')) textFinder = textFinder.matchEntireCell(true); + if (isFull) { + textFinder.findAll(); + } else { + textFinder.findNext(); + } + }; + + render(props: ContentProps, state: ContentState) { + const Button = this.Render.renderFunction('Button'); + const CheckboxGroup = this.Render.renderFunction('CheckboxGroup'); + const Input = this.Render.renderFunction('Input'); + const { keys } = props; + const { content, buttons, match } = state; + return ( +
        +
        +
        +
        + {content[0].label}: + this.changeSearchText(e)} /> +
        + {keys === 'replace' ? ( +
        + {content[1].label}: + +
        + ) : ( + <> + )} +
        +
        + +
        +
        +
        + {buttons.map((item, index) => { + if (keys === 'replace') { + return ( + + ); + } + return ( + + ); + })} +
        +
        + ); + } +} diff --git a/packages/sheets-plugin-find/src/UI/index.module.less b/packages/sheets-plugin-find/src/UI/index.module.less new file mode 100644 index 0000000000..55c9918598 --- /dev/null +++ b/packages/sheets-plugin-find/src/UI/index.module.less @@ -0,0 +1,83 @@ +@import '@univer/style-universheet/assets/css/skin/default.module.less'; + +.location-content { + .radio-wrap { + padding: 0.5rem 0; + + .radio-left { + display: block; + + .label { + height: auto; + } + + .circle { + width: 0.8em; + height: 0.8em; + } + + .active { + .fork { + width: 0.4em; + height: 0.4em; + } + } + + .checkbox-group { + padding: 0 1em; + + .checkbox-wrapper { + .check-box { + .checkbox-inner { + width: 12px; + height: 12px; + } + } + } + } + } + } +} + +.tab-box { + padding: 0.5em; + border: 1px solid @gray-5; + + .input-box { + display: flex; + justify-content: space-around; + margin-bottom: 1em; + + .text-box { + div { + &:first-child { + margin: 0.5rem 0; + } + + span { + margin-right: 0.4em; + } + + input { + width: auto; + line-height: normal; + } + } + } + + .checkbox-group { + flex-direction: column; + + label { + margin-bottom: 0.8em; + } + } + } + + .btn-box { + button { + margin-right: 0.5em; + padding: 0 0.5em; + } + } +} diff --git a/packages/sheets-plugin-find/src/UI/test.js b/packages/sheets-plugin-find/src/UI/test.js new file mode 100644 index 0000000000..4e49641e7c --- /dev/null +++ b/packages/sheets-plugin-find/src/UI/test.js @@ -0,0 +1,7053 @@ +window.sheetFormula = { + name: 'Formula', + color: '', + config: { + merge: { + '12_2': { + rs: 1, + cs: 6, + r: 12, + c: 2, + }, + '19_2': { + rs: 1, + cs: 6, + r: 19, + c: 2, + }, + '20_6': { + rs: 1, + cs: 5, + r: 20, + c: 6, + }, + '22_6': { + rs: 1, + cs: 2, + r: 22, + c: 6, + }, + '23_6': { + rs: 1, + cs: 2, + r: 23, + c: 6, + }, + '28_2': { + rs: 1, + cs: 6, + r: 28, + c: 2, + }, + '31_6': { + rs: 1, + cs: 3, + r: 31, + c: 6, + }, + '33_6': { + rs: 1, + cs: 3, + r: 33, + c: 6, + }, + '35_6': { + rs: 1, + cs: 3, + r: 35, + c: 6, + }, + '37_6': { + rs: 1, + cs: 3, + r: 37, + c: 6, + }, + '29_6': { + r: 29, + c: 6, + rs: 1, + cs: 3, + }, + }, + rowlen: {}, + columnlen: { + 0: 111, + 2: 105, + 3: 82, + 4: 71, + 5: 84, + 6: 123, + 7: 48, + 8: 192, + 9: 56, + 10: 56, + }, + }, + index: '1', + chart: [], + order: '1', + column: 18, + row: 45, + celldata: [ + { + r: 0, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 0, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 0, + v: { + v: 'Basic Function', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Basic Function', + bg: null, + bl: 1, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 2, + v: { + v: 'Name', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Name', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 3, + v: { + v: 'Age', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Age', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 6, + v: { + v: 'Indirect Function', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Indirect Function', + bg: null, + bl: 1, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 8, + v: { + v: 'J2', + ct: { + fa: 'General', + t: 'g', + }, + m: 'J2', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 9, + v: { + v: 1, + ct: { + fa: 'General', + t: 'n', + }, + m: '1', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 1, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 2, + v: { + v: 'Jack', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Jack', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 3, + v: { + v: 17, + ct: { + fa: 'General', + t: 'n', + }, + m: '17', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 8, + v: { + v: 'I', + ct: { + fa: 'General', + t: 'g', + }, + m: 'I', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 9, + v: { + v: 2, + ct: { + fa: 'General', + t: 'n', + }, + m: '2', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 2, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 2, + v: { + v: 'Lily', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Lily', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 3, + v: { + v: 23, + ct: { + fa: 'General', + t: 'n', + }, + m: '23', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 8, + v: { + v: 'J', + ct: { + fa: 'General', + t: 'g', + }, + m: 'J', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 9, + v: { + v: 3, + ct: { + fa: 'General', + t: 'n', + }, + m: '3', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 3, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 2, + v: { + v: 'Bob', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Bob', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 3, + v: { + v: 30, + ct: { + fa: 'General', + t: 'n', + }, + m: '30', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 4, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 2, + v: { + v: 'Mary', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Mary', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 3, + v: { + v: 25, + ct: { + fa: 'General', + t: 'n', + }, + m: '25', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 8, + v: { + v: '=INDIRECT("I2")', + ct: { + fa: 'General', + t: 'g', + }, + m: '=INDIRECT("I2")', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 5, + c: 9, + v: { + v: 'J2', + ct: { + fa: 'General', + t: 'g', + }, + m: 'J2', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=INDIRECT("I2")', + }, + }, + { + r: 5, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 2, + v: { + v: 'Average Age:', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Average Age:', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 3, + v: { + v: 23.75, + ct: { + fa: 'General', + t: 'n', + }, + m: '23.75', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=AVERAGE(D3:D6)', + }, + }, + { + r: 6, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 8, + v: { + v: '=INDIRECT(I2)', + ct: { + fa: 'General', + t: 'g', + }, + m: '=INDIRECT(I2)', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 6, + c: 9, + v: { + v: 1, + ct: { + fa: 'General', + t: 'n', + }, + m: '1', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=INDIRECT(I2)', + }, + }, + { + r: 6, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 2, + v: { + v: 'Max Age:', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Max Age:', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 3, + v: { + v: 30, + ct: { + fa: 'General', + t: 'n', + }, + m: '30', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=MAX(D3:D6)', + }, + }, + { + r: 7, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 8, + v: { + v: '=INDIRECT("I"&(1+2))', + ct: { + fa: 'General', + t: 'g', + }, + m: '=INDIRECT("I"&(1+2))', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 7, + c: 9, + v: { + v: 'I', + ct: { + fa: 'General', + t: 'g', + }, + m: 'I', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=INDIRECT("I"&(1+2))', + }, + }, + { + r: 7, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 2, + v: { + v: 'Min Age:', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Min Age:', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 3, + v: { + v: 17, + ct: { + fa: 'General', + t: 'n', + }, + m: '17', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=MIN(D3:D6)', + }, + }, + { + r: 8, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 8, + v: { + v: '=INDIRECT(I4&J3)', + ct: { + fa: 'General', + t: 'g', + }, + m: '=INDIRECT(I4&J3)', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 8, + c: 9, + v: { + v: 1, + ct: { + fa: 'General', + t: 'n', + }, + m: '1', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=INDIRECT(I4&J3)', + }, + }, + { + r: 8, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 8, + v: { + v: '=INDIRECT("Formula!"&I2)', + ct: { + fa: 'General', + t: 'g', + }, + m: '=INDIRECT("Formula!"&I2)', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 9, + c: 9, + v: { + ct: { + fa: 'General', + t: 'n', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + v: 1, + m: '1', + f: '=INDIRECT("Formula!"&I2)', + }, + }, + { + r: 9, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 8, + v: { + v: '=INDIRECT("Formula!I2")', + ct: { + fa: 'General', + t: 'g', + }, + m: '=INDIRECT("Formula!I2")', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 10, + c: 9, + v: { + ct: { + fa: 'General', + t: 'g', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + m: 'J2', + v: 'J2', + f: '=INDIRECT("Formula!I2")', + }, + }, + { + r: 10, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 11, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 12, + c: 0, + v: { + v: 'Array Formula', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Array Formula', + bg: null, + bl: 1, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 12, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 12, + c: 2, + v: { + v: 'Calculation', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Calculation', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 6, + r: 12, + c: 2, + }, + }, + }, + { + r: 12, + c: 3, + v: { + mc: { + r: 12, + c: 2, + }, + }, + }, + { + r: 12, + c: 4, + v: { + mc: { + r: 12, + c: 2, + }, + }, + }, + { + r: 12, + c: 5, + v: { + mc: { + r: 12, + c: 2, + }, + }, + }, + { + r: 12, + c: 6, + v: { + mc: { + r: 12, + c: 2, + }, + }, + }, + { + r: 12, + c: 7, + v: { + mc: { + r: 12, + c: 2, + }, + }, + }, + { + r: 12, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 12, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 12, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 3, + v: { + v: 'Match', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Match', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 4, + v: { + v: 'Physical', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Physical', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 5, + v: { + v: 'Chemistry', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Chemistry', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 6, + v: { + v: 'Alex', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Alex', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 7, + v: { + v: 'Sum', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Sum', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 13, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 2, + v: { + v: 'Alice', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Alice', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 3, + v: { + v: 97, + ct: { + fa: 'General', + t: 'n', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + m: '97', + }, + }, + { + r: 14, + c: 4, + v: { + v: 61, + ct: { + fa: 'General', + t: 'n', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + m: '61', + }, + }, + { + r: 14, + c: 5, + v: { + v: 53, + ct: { + fa: 'General', + t: 'n', + }, + m: '53', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 6, + v: { + v: 43, + ct: { + fa: 'General', + t: 'n', + }, + m: '43', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 7, + v: { + ct: { + fa: 'General', + t: 'n', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + v: 207, + m: '207', + f: '=SUBTOTAL(9,OFFSET($D$15,ROW($D$15:$D$18)-ROW($D$15),1,3))', + }, + }, + { + r: 14, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 14, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 2, + v: { + v: 'John', + ct: { + fa: 'General', + t: 'g', + }, + m: 'John', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 3, + v: { + v: 65, + ct: { + fa: 'General', + t: 'n', + }, + m: '65', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 4, + v: { + v: 76, + ct: { + fa: 'General', + t: 'n', + }, + m: '76', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 5, + v: { + v: 65, + ct: { + fa: 'General', + t: 'n', + }, + m: '65', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 6, + v: { + v: 55, + ct: { + fa: 'General', + t: 'n', + }, + m: '55', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 7, + v: { + ct: { + fa: 'General', + t: 'n', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + v: 182, + m: '182', + f: '=SUBTOTAL(9,OFFSET(E15,ROW(E15:E18)-ROW(E15),1,3))', + }, + }, + { + r: 15, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + ct: { + fa: 'General', + t: 'n', + }, + }, + }, + { + r: 15, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 15, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 2, + v: { + v: 'Bob', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Bob', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 3, + v: { + v: 55, + ct: { + fa: 'General', + t: 'n', + }, + m: '55', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 4, + v: { + v: 70, + ct: { + fa: 'General', + t: 'n', + }, + m: '70', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 5, + v: { + v: 64, + ct: { + fa: 'General', + t: 'n', + }, + m: '64', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 6, + v: { + v: 54, + ct: { + fa: 'General', + t: 'n', + }, + m: '54', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 7, + v: { + v: 152, + ct: { + fa: 'General', + t: 'n', + }, + m: '152', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=SUBTOTAL(9,OFFSET(F15,ROW(F15:F18)-ROW(F15),1,3))', + }, + }, + { + r: 16, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 16, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 2, + v: { + v: 'Jack', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Jack', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 3, + v: { + v: 89, + ct: { + fa: 'General', + t: 'n', + }, + m: '89', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 4, + v: { + v: 77, + ct: { + fa: 'General', + t: 'n', + }, + m: '77', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 5, + v: { + v: 73, + ct: { + fa: 'General', + t: 'n', + }, + m: '73', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 6, + v: { + v: 73, + ct: { + fa: 'General', + t: 'n', + }, + m: '73', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 7, + v: { + v: 541, + ct: { + fa: 'General', + t: 'n', + }, + m: '541', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=SUBTOTAL(9,OFFSET(G15,ROW(G15:G18)-ROW(G15),1,3))', + }, + }, + { + r: 17, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 17, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 18, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 19, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 19, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 19, + c: 2, + v: { + v: 'Search', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Search', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 6, + r: 19, + c: 2, + }, + }, + }, + { + r: 19, + c: 3, + v: { + mc: { + r: 19, + c: 2, + }, + }, + }, + { + r: 19, + c: 4, + v: { + mc: { + r: 19, + c: 2, + }, + }, + }, + { + r: 19, + c: 5, + v: { + mc: { + r: 19, + c: 2, + }, + }, + }, + { + r: 19, + c: 6, + v: { + mc: { + r: 19, + c: 2, + }, + }, + }, + { + r: 19, + c: 7, + v: { + mc: { + r: 19, + c: 2, + }, + }, + }, + { + r: 19, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 19, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 19, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 20, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 20, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 20, + c: 2, + v: { + v: 'apple', + ct: { + fa: 'General', + t: 'g', + }, + m: 'apple', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 20, + c: 3, + v: { + v: 'apple', + ct: { + fa: 'General', + t: 'g', + }, + m: 'apple', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 20, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 20, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 20, + c: 6, + v: { + ct: { + fa: 'General', + t: 'g', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 5, + r: 20, + c: 6, + }, + }, + }, + { + r: 20, + c: 7, + v: { + mc: { + r: 20, + c: 6, + }, + }, + }, + { + r: 20, + c: 8, + v: { + mc: { + r: 20, + c: 6, + }, + }, + }, + { + r: 20, + c: 9, + v: { + mc: { + r: 20, + c: 6, + }, + }, + }, + { + r: 20, + c: 10, + v: { + mc: { + r: 20, + c: 6, + }, + }, + }, + { + r: 21, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 2, + v: { + v: 'banana', + ct: { + fa: 'General', + t: 'g', + }, + m: 'banana', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 3, + v: { + v: 'pear', + ct: { + fa: 'General', + t: 'g', + }, + m: 'pear', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 21, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 22, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 22, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 22, + c: 2, + v: { + v: 'pear', + ct: { + fa: 'General', + t: 'g', + }, + m: 'pear', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 22, + c: 3, + v: { + v: 'potato', + ct: { + fa: 'General', + t: 'g', + }, + m: 'potato', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 22, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 22, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 22, + c: 6, + v: { + v: 'ArrayFormula Result:', + ct: { + fa: 'General', + t: 'g', + }, + m: 'ArrayFormula Result:', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 2, + r: 22, + c: 6, + }, + }, + }, + { + r: 22, + c: 7, + v: { + mc: { + r: 22, + c: 6, + }, + }, + }, + { + r: 22, + c: 8, + v: { + v: 'dumpling', + ct: { + fa: 'General', + t: 'g', + }, + m: 'dumpling', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=INDEX(D21:D25,MATCH("dumpling",D21:D25),1)', + }, + }, + { + r: 22, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + ct: { + fa: 'General', + t: 'b', + }, + }, + }, + { + r: 22, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + ct: { + fa: 'General', + t: 'n', + }, + }, + }, + { + r: 23, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 23, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 23, + c: 2, + v: { + v: 'tomato', + ct: { + fa: 'General', + t: 'g', + }, + m: 'tomato', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 23, + c: 3, + v: { + v: 'potato', + ct: { + fa: 'General', + t: 'g', + }, + m: 'potato', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 23, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 23, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 23, + c: 6, + v: { + v: 'NomalFormula Result:', + ct: { + fa: 'General', + t: 'g', + }, + m: 'NomalFormula Result:', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 2, + r: 23, + c: 6, + }, + }, + }, + { + r: 23, + c: 7, + v: { + mc: { + r: 23, + c: 6, + }, + }, + }, + { + r: 23, + c: 8, + v: { + ct: { + fa: 'General', + t: 'b', + }, + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + m: 'FALSE', + v: false, + f: '=ISNA(MATCH(D21:D25,C21:C27,0))', + }, + }, + { + r: 23, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + ct: { + fa: 'General', + t: 'b', + }, + }, + }, + { + r: 23, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 2, + v: { + v: 'potato', + ct: { + fa: 'General', + t: 'g', + }, + m: 'potato', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 3, + v: { + v: 'dumpling', + ct: { + fa: 'General', + t: 'g', + }, + m: 'dumpling', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + ct: { + fa: 'General', + t: 'e', + }, + }, + }, + { + r: 24, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 24, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 2, + v: { + v: 'cake', + ct: { + fa: 'General', + t: 'g', + }, + m: 'cake', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 25, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 2, + v: { + v: 'noodel', + ct: { + fa: 'General', + t: 'g', + }, + m: 'noodel', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 26, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 2, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 3, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 4, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 6, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 27, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 28, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 28, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 28, + c: 2, + v: { + v: 'Statistics', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Statistics', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 6, + r: 28, + c: 2, + }, + }, + }, + { + r: 28, + c: 3, + v: { + mc: { + r: 28, + c: 2, + }, + }, + }, + { + r: 28, + c: 4, + v: { + mc: { + r: 28, + c: 2, + }, + }, + }, + { + r: 28, + c: 5, + v: { + mc: { + r: 28, + c: 2, + }, + }, + }, + { + r: 28, + c: 6, + v: { + mc: { + r: 28, + c: 2, + }, + }, + }, + { + r: 28, + c: 7, + v: { + mc: { + r: 28, + c: 2, + }, + }, + }, + { + r: 28, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 28, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 28, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 2, + v: { + v: 'Product', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Product', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 3, + v: { + v: 'Salesman', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Salesman', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 4, + v: { + v: 'Units Sold', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Units Sold', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 6, + v: { + v: 'Summing Sales: Faxes Sold By Brown', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Summing Sales: Faxes Sold By Brown', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + r: 29, + c: 6, + rs: 1, + cs: 3, + }, + }, + }, + { + r: 29, + c: 7, + v: { + mc: { + r: 29, + c: 6, + }, + }, + }, + { + r: 29, + c: 8, + v: { + mc: { + r: 29, + c: 6, + }, + }, + }, + { + r: 29, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 29, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 2, + v: { + v: 'Fax', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Fax', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 3, + v: { + v: 'Brown', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Brown', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 4, + v: { + v: 1, + ct: { + fa: 'General', + t: 'n', + }, + m: '1', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 6, + v: { + v: 61, + ct: { + fa: 'General', + t: 'n', + }, + m: '61', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=SUM((C31:C39="Fax")*(D31:D39="Brown")*(E31:E39))', + }, + }, + { + r: 30, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 30, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 2, + v: { + v: 'Phone', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Phone', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 3, + v: { + v: 'Smith', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Smith', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 4, + v: { + v: 10, + ct: { + fa: 'General', + t: 'n', + }, + m: '10', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 6, + v: { + v: 'Logical AND (Faxes And Brown)', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Logical AND (Faxes And Brown)', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 3, + r: 31, + c: 6, + }, + }, + }, + { + r: 31, + c: 7, + v: { + mc: { + r: 31, + c: 6, + }, + }, + }, + { + r: 31, + c: 8, + v: { + mc: { + r: 31, + c: 6, + }, + }, + }, + { + r: 31, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 31, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 2, + v: { + v: 'Fax', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Fax', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 3, + v: { + v: 'Jones', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Jones', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 4, + v: { + v: 20, + ct: { + fa: 'General', + t: 'n', + }, + m: '20', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 6, + v: { + v: 2, + ct: { + fa: 'General', + t: 'n', + }, + m: '2', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=SUM((C31:C39="Fax")*(D31:D39="Brown"))', + }, + }, + { + r: 32, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 32, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 2, + v: { + v: 'Fax', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Fax', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 3, + v: { + v: 'Smith', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Smith', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 4, + v: { + v: 30, + ct: { + fa: 'General', + t: 'n', + }, + m: '30', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 6, + v: { + v: 'Logical OR (Faxes Or Jones)', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Logical OR (Faxes Or Jones)', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 3, + r: 33, + c: 6, + }, + }, + }, + { + r: 33, + c: 7, + v: { + mc: { + r: 33, + c: 6, + }, + }, + }, + { + r: 33, + c: 8, + v: { + mc: { + r: 33, + c: 6, + }, + }, + }, + { + r: 33, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 33, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 2, + v: { + v: 'Phone', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Phone', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 3, + v: { + v: 'Jones', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Jones', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 4, + v: { + v: 40, + ct: { + fa: 'General', + t: 'n', + }, + m: '40', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 6, + v: { + v: 1, + ct: { + fa: 'General', + t: 'n', + }, + m: '1', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=SUM(IF((C31:C39="Fax")+(D31:D39="Jones"),1,0))', + }, + }, + { + r: 34, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 34, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 2, + v: { + v: 'PC', + ct: { + fa: 'General', + t: 'g', + }, + m: 'PC', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 3, + v: { + v: 'Smith', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Smith', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 4, + v: { + v: 50, + ct: { + fa: 'General', + t: 'n', + }, + m: '50', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 6, + v: { + v: 'Logical XOR (Fax Or Jones but not both)', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Logical XOR (Fax Or Jones but not both)', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 3, + r: 35, + c: 6, + }, + }, + }, + { + r: 35, + c: 7, + v: { + mc: { + r: 35, + c: 6, + }, + }, + }, + { + r: 35, + c: 8, + v: { + mc: { + r: 35, + c: 6, + }, + }, + }, + { + r: 35, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 35, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 2, + v: { + v: 'Fax', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Fax', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 3, + v: { + v: 'Brown', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Brown', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 4, + v: { + v: 60, + ct: { + fa: 'General', + t: 'n', + }, + m: '60', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 6, + v: { + v: 1, + ct: { + fa: 'General', + t: 'n', + }, + m: '1', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=SUM(IF(MOD((C31:C39="Fax")+(D31:D39="Jones"),2),1,0))', + }, + }, + { + r: 36, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 36, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 2, + v: { + v: 'Phone', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Phone', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 3, + v: { + v: 'Davis', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Davis', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 4, + v: { + v: 70, + ct: { + fa: 'General', + t: 'n', + }, + m: '70', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 6, + v: { + v: 'Logical NAND (All Sales Except Fax And Jones)', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Logical NAND (All Sales Except Fax And Jones)', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + mc: { + rs: 1, + cs: 3, + r: 37, + c: 6, + }, + }, + }, + { + r: 37, + c: 7, + v: { + mc: { + r: 37, + c: 6, + }, + }, + }, + { + r: 37, + c: 8, + v: { + mc: { + r: 37, + c: 6, + }, + }, + }, + { + r: 37, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 37, + c: 10, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 0, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 1, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 2, + v: { + v: 'PC', + ct: { + fa: 'General', + t: 'g', + }, + m: 'PC', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 3, + v: { + v: 'Jones', + ct: { + fa: 'General', + t: 'g', + }, + m: 'Jones', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 4, + v: { + v: 80, + ct: { + fa: 'General', + t: 'n', + }, + m: '80', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 5, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 6, + v: { + v: 1, + ct: { + fa: 'General', + t: 'n', + }, + m: '1', + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + f: '=SUM(IF((C31:C39="Fax")+(D31:D39="Jones")<>2,1,0))', + }, + }, + { + r: 38, + c: 7, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 8, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 9, + v: { + bg: null, + bl: 0, + it: 0, + ff: 9, + fs: 10, + fc: 'rgb(0, 0, 0)', + ht: 1, + vt: 0, + }, + }, + { + r: 38, + c: 10, + v: {}, + }, + ], + calcChain: [ + { + r: 6, + c: 3, + index: 1, + // "func": [true, 23.75, "=AVERAGE(D3:D6)"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 7, + c: 3, + index: 1, + // "func": [true, 30, "=MAX(D3:D6)"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 8, + c: 3, + index: 1, + // "func": [true, 17, "=MIN(D3:D6)"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 5, + c: 9, + index: 1, + // "func": [true, "J2", "=INDIRECT(\"I2\")"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 6, + c: 9, + index: 1, + // "func": [true, 1, "=INDIRECT(I2)"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 7, + c: 9, + index: 1, + // "func": [true, "I", "=INDIRECT(\"I\"&(1+2))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 8, + c: 9, + index: 1, + // "func": [true, 1, "=INDIRECT(I4&J3)"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 16, + c: 7, + index: 1, + // "func": [true, 152, "=SUBTOTAL(9,OFFSET(F15,ROW(F15:F18)-ROW(F15),1,3))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 17, + c: 7, + index: 1, + // "func": [true, 541, "=SUBTOTAL(9,OFFSET(G15,ROW(G15:G18)-ROW(G15),1,3))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 22, + c: 8, + index: 1, + // "func": [true, "dumpling", "=INDEX(D21:D25,MATCH(\"dumpling\",D21:D25),1)"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 38, + c: 6, + index: 1, + // "func": [true, 1, "=SUM(IF((C31:C39=\"Fax\")+(D31:D39=\"Jones\")<>2,1,0))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 30, + c: 6, + index: 1, + // "func": [true, "#NAME?", "=SUM((C31:C39=\"Fax\")*(D31:D39=\"Brown\")*(E31:E39))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 32, + c: 6, + index: 1, + // "func": [true, "#NAME?", "=SUM((C31:C39=\"Fax\")*(D31:D39=\"Brown\"))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 34, + c: 6, + index: 1, + // "func": [true, 1, "=SUM(IF((C31:C39=\"Fax\")+(D31:D39=\"Jones\"),1,0))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 36, + c: 6, + index: 1, + // "func": [true, 1, "=SUM(IF(MOD((C31:C39=\"Fax\")+(D31:D39=\"Jones\"),2),1,0))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 9, + c: 9, + index: 1, + // "func": [true, 1, "=INDIRECT(\"Formula!\"&I2)"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 10, + c: 9, + index: 1, + // "func": [true, "J2", "=INDIRECT(\"Formula!I2\")"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 14, + c: 7, + index: 1, + // "func": [true, 207, "=SUBTOTAL(9,OFFSET($D$15,ROW($D$15:$D$18)-ROW($D$15),1,3))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 15, + c: 7, + index: 1, + // "func": [true, 182, "=SUBTOTAL(9,OFFSET(E15,ROW(E15:E18)-ROW(E15),1,3))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + { + r: 23, + c: 8, + index: 1, + // "func": [true, false, "=ISNA(MATCH(D21:D25,C21:C27,0))"], + color: 'w', + parent: null, + chidren: {}, + times: 0, + }, + ], + ch_width: 1723, + rh_height: 1010, + universheet_select_save: [ + { + left: 532, + width: 123, + top: 780, + height: 19, + left_move: 532, + width_move: 123, + top_move: 780, + height_move: 19, + row: [39, 39], + column: [6, 6], + row_focus: 39, + column_focus: 6, + }, + ], + universheet_selection_range: [], + scrollLeft: 0, + scrollTop: 0, + frozen: { type: 'row' }, +}; + +// export default sheetFormula diff --git a/packages/sheets-plugin-find/src/Util/util.ts b/packages/sheets-plugin-find/src/Util/util.ts new file mode 100644 index 0000000000..370ec111ff --- /dev/null +++ b/packages/sheets-plugin-find/src/Util/util.ts @@ -0,0 +1 @@ +export const getRegExpStr = (str: string) => str.replace('~*', '\\*').replace('~?', '\\?').replace('.', '\\.').replace('*', '.*').replace('?', '.'); diff --git a/packages/sheets-plugin-find/src/View/Event.ts b/packages/sheets-plugin-find/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-find/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-find/src/View/ObserverList.ts b/packages/sheets-plugin-find/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-find/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-find/src/View/Render.ts b/packages/sheets-plugin-find/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-find/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-find/src/View/index.ts b/packages/sheets-plugin-find/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-find/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-find/src/index.ts b/packages/sheets-plugin-find/src/index.ts new file mode 100644 index 0000000000..cff85b051b --- /dev/null +++ b/packages/sheets-plugin-find/src/index.ts @@ -0,0 +1,3 @@ +import { FindPlugin } from './FindPlugin'; + +export { FindPlugin }; diff --git a/packages/sheets-plugin-find/src/main.tsx b/packages/sheets-plugin-find/src/main.tsx new file mode 100644 index 0000000000..6a84e92d46 --- /dev/null +++ b/packages/sheets-plugin-find/src/main.tsx @@ -0,0 +1,6818 @@ +import { UniverSheet, defaultWorkbookData } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { FindPlugin } from './FindPlugin'; + +// const defaultWorkbookData = { +// Locale: 'zh', +// creator: '', +// name: '', +// skin: 'default', +// timeZone: '', +// createdTime: '', +// modifiedTime: '', +// appVersion: '', +// lastModifiedBy: '', +// sheets: [ +// { +// celldata: [ +// { +// r: 0, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 2, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 0, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 0, +// v: { +// v: 'Basic Function', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Basic Function', +// bg: null, +// bl: 1, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 2, +// v: { +// v: 'Name', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Name', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 3, +// v: { +// v: 'Age', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Age', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 6, +// v: { +// v: 'Indirect Function', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Indirect Function', +// bg: null, +// bl: 1, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 8, +// v: { +// v: 'J2', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'J2', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 9, +// v: { +// v: 1, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '1', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 1, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 2, +// v: { +// v: 'Jack', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Jack', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 3, +// v: { +// v: 17, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '17', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 8, +// v: { +// v: 'I', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'I', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 9, +// v: { +// v: 2, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '2', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 2, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 2, +// v: { +// v: 'Lily', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Lily', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 3, +// v: { +// v: 23, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '23', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 8, +// v: { +// v: 'J', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'J', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 9, +// v: { +// v: 3, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '3', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 3, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 2, +// v: { +// v: 'Bob', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Bob', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 3, +// v: { +// v: 30, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '30', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 4, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 2, +// v: { +// v: 'Mary', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Mary', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 3, +// v: { +// v: 25, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '25', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 8, +// v: { +// v: '=INDIRECT("I2")', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: '=INDIRECT("I2")', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 5, +// c: 9, +// v: { +// v: 'J2', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'J2', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=INDIRECT("I2")', +// }, +// }, +// { +// r: 5, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 2, +// v: { +// v: 'Average Age:', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Average Age:', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 3, +// v: { +// v: 23.75, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '23.75', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=AVERAGE(D3:D6)', +// }, +// }, +// { +// r: 6, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 8, +// v: { +// v: '=INDIRECT(I2)', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: '=INDIRECT(I2)', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 6, +// c: 9, +// v: { +// v: 1, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '1', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=INDIRECT(I2)', +// }, +// }, +// { +// r: 6, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 2, +// v: { +// v: 'Max Age:', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Max Age:', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 3, +// v: { +// v: 30, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '30', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=MAX(D3:D6)', +// }, +// }, +// { +// r: 7, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 8, +// v: { +// v: '=INDIRECT("I"&(1+2))', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: '=INDIRECT("I"&(1+2))', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 7, +// c: 9, +// v: { +// v: 'I', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'I', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=INDIRECT("I"&(1+2))', +// }, +// }, +// { +// r: 7, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 2, +// v: { +// v: 'Min Age:', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Min Age:', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 3, +// v: { +// v: 17, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '17', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=MIN(D3:D6)', +// }, +// }, +// { +// r: 8, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 8, +// v: { +// v: '=INDIRECT(I4&J3)', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: '=INDIRECT(I4&J3)', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 8, +// c: 9, +// v: { +// v: 1, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '1', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=INDIRECT(I4&J3)', +// }, +// }, +// { +// r: 8, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 2, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 8, +// v: { +// v: '=INDIRECT("Formula!"&I2)', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: '=INDIRECT("Formula!"&I2)', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 9, +// c: 9, +// v: { +// ct: { +// fa: 'General', +// t: 'n', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// v: 1, +// m: '1', +// f: '=INDIRECT("Formula!"&I2)', +// }, +// }, +// { +// r: 9, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 2, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 8, +// v: { +// v: '=INDIRECT("Formula!I2")', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: '=INDIRECT("Formula!I2")', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 10, +// c: 9, +// v: { +// ct: { +// fa: 'General', +// t: 'g', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// m: 'J2', +// v: 'J2', +// f: '=INDIRECT("Formula!I2")', +// }, +// }, +// { +// r: 10, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 2, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 11, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 12, +// c: 0, +// v: { +// v: 'Array Formula', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Array Formula', +// bg: null, +// bl: 1, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 12, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 12, +// c: 2, +// v: { +// v: 'Calculation', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Calculation', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 6, +// r: 12, +// c: 2, +// }, +// }, +// }, +// { +// r: 12, +// c: 3, +// v: { +// mc: { +// r: 12, +// c: 2, +// }, +// }, +// }, +// { +// r: 12, +// c: 4, +// v: { +// mc: { +// r: 12, +// c: 2, +// }, +// }, +// }, +// { +// r: 12, +// c: 5, +// v: { +// mc: { +// r: 12, +// c: 2, +// }, +// }, +// }, +// { +// r: 12, +// c: 6, +// v: { +// mc: { +// r: 12, +// c: 2, +// }, +// }, +// }, +// { +// r: 12, +// c: 7, +// v: { +// mc: { +// r: 12, +// c: 2, +// }, +// }, +// }, +// { +// r: 12, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 12, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 12, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 2, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 3, +// v: { +// v: 'Match', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Match', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 4, +// v: { +// v: 'Physical', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Physical', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 5, +// v: { +// v: 'Chemistry', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Chemistry', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 6, +// v: { +// v: 'Alex', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Alex', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 7, +// v: { +// v: 'Sum', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Sum', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 13, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 2, +// v: { +// v: 'Alice', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Alice', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 3, +// v: { +// v: 97, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// m: '97', +// }, +// }, +// { +// r: 14, +// c: 4, +// v: { +// v: 61, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// m: '61', +// }, +// }, +// { +// r: 14, +// c: 5, +// v: { +// v: 53, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '53', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 6, +// v: { +// v: 43, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '43', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 7, +// v: { +// ct: { +// fa: 'General', +// t: 'n', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// v: 207, +// m: '207', +// f: '=SUBTOTAL(9,OFFSET($D$15,ROW($D$15:$D$18)-ROW($D$15),1,3))', +// }, +// }, +// { +// r: 14, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 14, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 2, +// v: { +// v: 'John', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'John', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 3, +// v: { +// v: 65, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '65', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 4, +// v: { +// v: 76, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '76', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 5, +// v: { +// v: 65, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '65', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 6, +// v: { +// v: 55, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '55', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 7, +// v: { +// ct: { +// fa: 'General', +// t: 'n', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// v: 182, +// m: '182', +// f: '=SUBTOTAL(9,OFFSET(E15,ROW(E15:E18)-ROW(E15),1,3))', +// }, +// }, +// { +// r: 15, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// }, +// }, +// { +// r: 15, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 15, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 2, +// v: { +// v: 'Bob', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Bob', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 3, +// v: { +// v: 55, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '55', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 4, +// v: { +// v: 70, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '70', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 5, +// v: { +// v: 64, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '64', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 6, +// v: { +// v: 54, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '54', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 7, +// v: { +// v: 152, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '152', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=SUBTOTAL(9,OFFSET(F15,ROW(F15:F18)-ROW(F15),1,3))', +// }, +// }, +// { +// r: 16, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 16, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 2, +// v: { +// v: 'Jack', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Jack', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 3, +// v: { +// v: 89, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '89', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 4, +// v: { +// v: 77, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '77', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 5, +// v: { +// v: 73, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '73', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 6, +// v: { +// v: 73, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '73', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 7, +// v: { +// v: 541, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '541', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=SUBTOTAL(9,OFFSET(G15,ROW(G15:G18)-ROW(G15),1,3))', +// }, +// }, +// { +// r: 17, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 17, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 2, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 18, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 19, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 19, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 19, +// c: 2, +// v: { +// v: 'Search', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Search', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 6, +// r: 19, +// c: 2, +// }, +// }, +// }, +// { +// r: 19, +// c: 3, +// v: { +// mc: { +// r: 19, +// c: 2, +// }, +// }, +// }, +// { +// r: 19, +// c: 4, +// v: { +// mc: { +// r: 19, +// c: 2, +// }, +// }, +// }, +// { +// r: 19, +// c: 5, +// v: { +// mc: { +// r: 19, +// c: 2, +// }, +// }, +// }, +// { +// r: 19, +// c: 6, +// v: { +// mc: { +// r: 19, +// c: 2, +// }, +// }, +// }, +// { +// r: 19, +// c: 7, +// v: { +// mc: { +// r: 19, +// c: 2, +// }, +// }, +// }, +// { +// r: 19, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 19, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 19, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 20, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 20, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 20, +// c: 2, +// v: { +// v: 'apple', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'apple', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 20, +// c: 3, +// v: { +// v: 'apple', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'apple', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 20, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 20, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 20, +// c: 6, +// v: { +// ct: { +// fa: 'General', +// t: 'g', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 5, +// r: 20, +// c: 6, +// }, +// }, +// }, +// { +// r: 20, +// c: 7, +// v: { +// mc: { +// r: 20, +// c: 6, +// }, +// }, +// }, +// { +// r: 20, +// c: 8, +// v: { +// mc: { +// r: 20, +// c: 6, +// }, +// }, +// }, +// { +// r: 20, +// c: 9, +// v: { +// mc: { +// r: 20, +// c: 6, +// }, +// }, +// }, +// { +// r: 20, +// c: 10, +// v: { +// mc: { +// r: 20, +// c: 6, +// }, +// }, +// }, +// { +// r: 21, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 2, +// v: { +// v: 'banana', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'banana', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 3, +// v: { +// v: 'pear', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'pear', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 21, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 22, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 22, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 22, +// c: 2, +// v: { +// v: 'pear', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'pear', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 22, +// c: 3, +// v: { +// v: 'potato', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'potato', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 22, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 22, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 22, +// c: 6, +// v: { +// v: 'ArrayFormula Result:', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'ArrayFormula Result:', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 2, +// r: 22, +// c: 6, +// }, +// }, +// }, +// { +// r: 22, +// c: 7, +// v: { +// mc: { +// r: 22, +// c: 6, +// }, +// }, +// }, +// { +// r: 22, +// c: 8, +// v: { +// v: 'dumpling', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'dumpling', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=INDEX(D21:D25,MATCH("dumpling",D21:D25),1)', +// }, +// }, +// { +// r: 22, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// ct: { +// fa: 'General', +// t: 'b', +// }, +// }, +// }, +// { +// r: 22, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// }, +// }, +// { +// r: 23, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 23, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 23, +// c: 2, +// v: { +// v: 'tomato', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'tomato', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 23, +// c: 3, +// v: { +// v: 'potato', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'potato', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 23, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 23, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 23, +// c: 6, +// v: { +// v: 'NomalFormula Result:', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'NomalFormula Result:', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 2, +// r: 23, +// c: 6, +// }, +// }, +// }, +// { +// r: 23, +// c: 7, +// v: { +// mc: { +// r: 23, +// c: 6, +// }, +// }, +// }, +// { +// r: 23, +// c: 8, +// v: { +// ct: { +// fa: 'General', +// t: 'b', +// }, +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// m: 'FALSE', +// v: false, +// f: '=ISNA(MATCH(D21:D25,C21:C27,0))', +// }, +// }, +// { +// r: 23, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// ct: { +// fa: 'General', +// t: 'b', +// }, +// }, +// }, +// { +// r: 23, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 2, +// v: { +// v: 'potato', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'potato', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 3, +// v: { +// v: 'dumpling', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'dumpling', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// ct: { +// fa: 'General', +// t: 'e', +// }, +// }, +// }, +// { +// r: 24, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 24, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 2, +// v: { +// v: 'cake', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'cake', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 25, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 2, +// v: { +// v: 'noodel', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'noodel', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 26, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 2, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 3, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 4, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 6, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 27, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 28, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 28, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 28, +// c: 2, +// v: { +// v: 'Statistics', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Statistics', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 6, +// r: 28, +// c: 2, +// }, +// }, +// }, +// { +// r: 28, +// c: 3, +// v: { +// mc: { +// r: 28, +// c: 2, +// }, +// }, +// }, +// { +// r: 28, +// c: 4, +// v: { +// mc: { +// r: 28, +// c: 2, +// }, +// }, +// }, +// { +// r: 28, +// c: 5, +// v: { +// mc: { +// r: 28, +// c: 2, +// }, +// }, +// }, +// { +// r: 28, +// c: 6, +// v: { +// mc: { +// r: 28, +// c: 2, +// }, +// }, +// }, +// { +// r: 28, +// c: 7, +// v: { +// mc: { +// r: 28, +// c: 2, +// }, +// }, +// }, +// { +// r: 28, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 28, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 28, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 2, +// v: { +// v: 'Product', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Product', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 3, +// v: { +// v: 'Salesman', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Salesman', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 4, +// v: { +// v: 'Units Sold', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Units Sold', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 6, +// v: { +// v: 'Summing Sales: Faxes Sold By Brown', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Summing Sales: Faxes Sold By Brown', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// r: 29, +// c: 6, +// rs: 1, +// cs: 3, +// }, +// }, +// }, +// { +// r: 29, +// c: 7, +// v: { +// mc: { +// r: 29, +// c: 6, +// }, +// }, +// }, +// { +// r: 29, +// c: 8, +// v: { +// mc: { +// r: 29, +// c: 6, +// }, +// }, +// }, +// { +// r: 29, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 29, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 2, +// v: { +// v: 'Fax', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Fax', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 3, +// v: { +// v: 'Brown', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Brown', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 4, +// v: { +// v: 1, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '1', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 6, +// v: { +// v: 61, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '61', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=SUM((C31:C39="Fax")*(D31:D39="Brown")*(E31:E39))', +// }, +// }, +// { +// r: 30, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 30, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 2, +// v: { +// v: 'Phone', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Phone', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 3, +// v: { +// v: 'Smith', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Smith', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 4, +// v: { +// v: 10, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '10', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 6, +// v: { +// v: 'Logical AND (Faxes And Brown)', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Logical AND (Faxes And Brown)', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 3, +// r: 31, +// c: 6, +// }, +// }, +// }, +// { +// r: 31, +// c: 7, +// v: { +// mc: { +// r: 31, +// c: 6, +// }, +// }, +// }, +// { +// r: 31, +// c: 8, +// v: { +// mc: { +// r: 31, +// c: 6, +// }, +// }, +// }, +// { +// r: 31, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 31, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 2, +// v: { +// v: 'Fax', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Fax', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 3, +// v: { +// v: 'Jones', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Jones', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 4, +// v: { +// v: 20, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '20', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 6, +// v: { +// v: 2, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '2', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=SUM((C31:C39="Fax")*(D31:D39="Brown"))', +// }, +// }, +// { +// r: 32, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 32, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 2, +// v: { +// v: 'Fax', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Fax', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 3, +// v: { +// v: 'Smith', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Smith', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 4, +// v: { +// v: 30, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '30', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 6, +// v: { +// v: 'Logical OR (Faxes Or Jones)', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Logical OR (Faxes Or Jones)', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 3, +// r: 33, +// c: 6, +// }, +// }, +// }, +// { +// r: 33, +// c: 7, +// v: { +// mc: { +// r: 33, +// c: 6, +// }, +// }, +// }, +// { +// r: 33, +// c: 8, +// v: { +// mc: { +// r: 33, +// c: 6, +// }, +// }, +// }, +// { +// r: 33, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 33, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 2, +// v: { +// v: 'Phone', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Phone', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 3, +// v: { +// v: 'Jones', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Jones', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 4, +// v: { +// v: 40, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '40', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 6, +// v: { +// v: 1, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '1', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=SUM(IF((C31:C39="Fax")+(D31:D39="Jones"),1,0))', +// }, +// }, +// { +// r: 34, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 34, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 2, +// v: { +// v: 'PC', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'PC', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 3, +// v: { +// v: 'Smith', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Smith', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 4, +// v: { +// v: 50, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '50', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 6, +// v: { +// v: 'Logical XOR (Fax Or Jones but not both)', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Logical XOR (Fax Or Jones but not both)', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 3, +// r: 35, +// c: 6, +// }, +// }, +// }, +// { +// r: 35, +// c: 7, +// v: { +// mc: { +// r: 35, +// c: 6, +// }, +// }, +// }, +// { +// r: 35, +// c: 8, +// v: { +// mc: { +// r: 35, +// c: 6, +// }, +// }, +// }, +// { +// r: 35, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 35, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 2, +// v: { +// v: 'Fax', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Fax', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 3, +// v: { +// v: 'Brown', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Brown', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 4, +// v: { +// v: 60, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '60', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 6, +// v: { +// v: 1, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '1', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=SUM(IF(MOD((C31:C39="Fax")+(D31:D39="Jones"),2),1,0))', +// }, +// }, +// { +// r: 36, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 36, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 2, +// v: { +// v: 'Phone', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Phone', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 3, +// v: { +// v: 'Davis', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Davis', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 4, +// v: { +// v: 70, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '70', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 6, +// v: { +// v: 'Logical NAND (All Sales Except Fax And Jones)', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Logical NAND (All Sales Except Fax And Jones)', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// mc: { +// rs: 1, +// cs: 3, +// r: 37, +// c: 6, +// }, +// }, +// }, +// { +// r: 37, +// c: 7, +// v: { +// mc: { +// r: 37, +// c: 6, +// }, +// }, +// }, +// { +// r: 37, +// c: 8, +// v: { +// mc: { +// r: 37, +// c: 6, +// }, +// }, +// }, +// { +// r: 37, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 37, +// c: 10, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 0, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 1, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 2, +// v: { +// v: 'PC', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'PC', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 3, +// v: { +// v: 'Jones', +// ct: { +// fa: 'General', +// t: 'g', +// }, +// m: 'Jones', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 4, +// v: { +// v: 80, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '80', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 5, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 6, +// v: { +// v: 1, +// ct: { +// fa: 'General', +// t: 'n', +// }, +// m: '1', +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// f: '=SUM(IF((C31:C39="Fax")+(D31:D39="Jones")<>2,1,0))', +// }, +// }, +// { +// r: 38, +// c: 7, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 8, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 9, +// v: { +// bg: null, +// bl: 0, +// it: 0, +// ff: 9, +// fs: 10, +// fc: 'rgb(0, 0, 0)', +// ht: 1, +// vt: 0, +// }, +// }, +// { +// r: 38, +// c: 10, +// v: {}, +// }, +// ], +// }, +// ], +// }; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; +const uiDefaultConfigLeft = { + containerId: 'universheet-demo-left', + layout: 'auto', +}; +const uiDefaultConfigRight = { + containerId: 'universheet-demo-right', + layout: 'auto', +}; +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +univerSheetUp.installPlugin(new FindPlugin()); + +const univerSheetDown = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetDown.installPlugin(new StyleUniverSheet(uiDefaultConfigDown)); +univerSheetDown.installPlugin(new FindPlugin()); + +const univerSheetLeft = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetLeft.installPlugin(new StyleUniverSheet(uiDefaultConfigLeft)); +univerSheetLeft.installPlugin(new FindPlugin()); + +const univerSheetRight = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetRight.installPlugin(new StyleUniverSheet(uiDefaultConfigRight)); +univerSheetRight.installPlugin(new FindPlugin()); diff --git a/packages/sheets-plugin-find/src/preact.d.ts b/packages/sheets-plugin-find/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-find/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-find/src/test/TextFind.test.ts b/packages/sheets-plugin-find/src/test/TextFind.test.ts new file mode 100644 index 0000000000..19e3a73644 --- /dev/null +++ b/packages/sheets-plugin-find/src/test/TextFind.test.ts @@ -0,0 +1,227 @@ +/** + * @jest-environment jsdom + */ +import { + BooleanNumber, + CommandManager, + Context, + Environment, + HooksManager, + IOCAttribute, + IOCContainer, + IWorkbookConfig, + Locale, + ObserverManager, + PluginManager, + ServerHttp, + ServerSocket, + UndoManager, + WorkBook, + WorkSheet, +} from '@univer/core'; +import { TextFinder } from '../Domain'; + +export function IOCContainerStartUpReady(workbookConfig?: Partial): IOCContainer { + const configure = { + id: '', + extensions: [], + sheetOrder: [], + socketEnable: BooleanNumber.FALSE, + socketUrl: '', + name: '', + timeZone: '', + appVersion: '', + theme: '', + skin: '', + locale: '', + creator: '', + styles: {}, + sheets: [], + lastModifiedBy: '', + createdTime: '', + modifiedTime: '', + ...workbookConfig, + }; + const attribute = new IOCAttribute({ value: configure }); + const container = new IOCContainer(attribute); + container.addSingletonMapping('Environment', Environment); + container.addSingletonMapping('Server', ServerSocket); + container.addSingletonMapping('ServerSocket', ServerSocket); + container.addSingletonMapping('ServerHttp', ServerHttp); + container.addSingletonMapping('WorkBook', WorkBook); + container.addSingletonMapping('Locale', Locale); + container.addSingletonMapping('Context', Context); + container.addSingletonMapping('UndoManager', UndoManager); + container.addSingletonMapping('CommandManager', CommandManager); + container.addSingletonMapping('PluginManager', PluginManager); + container.addSingletonMapping('ObserverManager', ObserverManager); + container.addSingletonMapping('ObservableHooksManager', HooksManager); + container.addMapping('WorkSheet', WorkSheet); + return container; +} + +function demo() { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: 1, + v: 1, + m: '1', + }, + 1: { + s: 1, + v: 1, + m: '1', + }, + }, + 1: { + 0: { + s: 1, + v: 1, + m: 'AB', + f: '=sum', + }, + 1: { + s: 1, + v: 1, + m: 'ab', + }, + }, + 2: { + 0: { + s: 1, + v: 1, + m: 'test', + }, + 1: { + s: 1, + v: 1, + m: 'ab', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance('WorkSheet', context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + return { worksheet, workbook }; +} + +test('Test findAll', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', 'a'); + const a = find.findAll(); + expect(a).toEqual(null); + const find1 = new TextFinder(worksheet, 'text', '1'); + const b = find1.findAll(); + expect(b?.length).toEqual(2); +}); + +test('Test findNext', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', '1'); + const b = find.findNext(); + expect(b?.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 0, + endColumn: 0, + }); +}); + +test('Test findPrevious', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', '1'); + const b = find.findPrevious(); + expect(b?.getRangeData()).toEqual({ + startRow: 0, + endRow: 0, + startColumn: 1, + endColumn: 1, + }); +}); + +test('Test matchCase', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', 'ab'); + const b = find.matchCase(true).findNext(); + expect(b?.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 1, + endColumn: 1, + }); +}); + +test('Test matchEntireCell', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', 'a'); + const b = find.matchEntireCell(true).findNext(); + expect(b?.getRangeData()).toEqual(null); +}); + +test('Test matchFormulaText', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', 'sum'); + const b = find.matchFormulaText(true).findNext(); + expect(b?.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 0, + }); +}); + +test('Test useRegularExpression', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', '/test/'); + const b = find.useRegularExpression(true).findNext(); + expect(b?.getRangeData()).toEqual({ + startRow: 2, + endRow: 2, + startColumn: 0, + endColumn: 0, + }); +}); + +test('Test replaceWith', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', 'ab'); + const b = find.replaceWith('cc'); + expect(b).toEqual(1); +}); + +test('Test replaceWith', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', 'ab'); + const b = find.replaceAllWith('cc'); + expect(b).toEqual(2); +}); + +test('Test getCurrentMatch', () => { + let { worksheet, workbook } = demo(); + const find = new TextFinder(worksheet, 'text', 'ab'); + find.findNext(); + const b = find.getCurrentMatch(); + expect(b?.getRangeData()).toEqual({ + startRow: 1, + endRow: 1, + startColumn: 0, + endColumn: 0, + }); +}); diff --git a/packages/sheets-plugin-find/test/TextFind.test.ts b/packages/sheets-plugin-find/test/TextFind.test.ts new file mode 100644 index 0000000000..80a89907df --- /dev/null +++ b/packages/sheets-plugin-find/test/TextFind.test.ts @@ -0,0 +1,152 @@ +/** + * @jest-environment jsdom + */ +import { + BooleanNumber, + CommandManager, + Context, + Environment, + HooksManager, + IOCAttribute, + IOCContainer, + IWorkbookConfig, + Locale, + ObserverManager, + PluginManager, + ServerHttp, + ServerSocket, + UndoManager, + WorkBook, + WorkSheet, +} from '@univer/core'; +import { TextFinder } from '../src/Domain/TextFind'; + +export function IOCContainerStartUpReady(workbookConfig?: Partial): IOCContainer { + const configure = Object.assign( + { + id: '', + extensions: [], + sheetOrder: [], + socketEnable: BooleanNumber.FALSE, + socketUrl: '', + name: '', + timeZone: '', + appVersion: '', + theme: '', + skin: '', + locale: '', + creator: '', + styles: {}, + sheets: [], + lastModifiedBy: '', + createdTime: '', + modifiedTime: '', + }, + workbookConfig + ); + const attribute = new IOCAttribute({ value: configure }); + const container = new IOCContainer(attribute); + container.addSingletonMapping('Environment', Environment); + container.addSingletonMapping('Server', ServerSocket); + container.addSingletonMapping('ServerSocket', ServerSocket); + container.addSingletonMapping('ServerHttp', ServerHttp); + container.addSingletonMapping('WorkBook', WorkBook); + container.addSingletonMapping('Locale', Locale); + container.addSingletonMapping('Context', Context); + container.addSingletonMapping('UndoManager', UndoManager); + container.addSingletonMapping('CommandManager', CommandManager); + container.addSingletonMapping('PluginManager', PluginManager); + container.addSingletonMapping('ObserverManager', ObserverManager); + container.addSingletonMapping('ObservableHooksManager', HooksManager); + container.addMapping('WorkSheet', WorkSheet); + return container; +} + +function demo() { + const container = IOCContainerStartUpReady({ + styles: { + 1: { + fs: 12, + }, + }, + }); + const context = container.getSingleton('Context'); + const workbook = container.getSingleton('WorkBook'); + const commandManager = workbook.getCommandManager(); + + const configure = { + sheetId: 'sheet', + cellData: { + 0: { + 0: { + s: 1, + v: 1, + m: 'a', + }, + 1: { + s: 1, + v: 1, + m: 'ab', + }, + 2: { + s: 1, + v: 1, + m: 'Abc', + }, + }, + 1: { + 0: { + s: 1, + v: 1, + m: 'c', + }, + 1: { + s: 1, + v: 1, + m: 'cD', + }, + 2: { + s: 1, + v: 1, + m: 'cde', + }, + }, + }, + status: 1, + }; + const worksheet = container.getInstance('WorkSheet', context, configure); + workbook.insertSheet(worksheet); + worksheet.setCommandManager(commandManager); + + return { worksheet, workbook }; +} + +test('Test findAll', () => { + let { worksheet, workbook } = demo(); + const a = new TextFinder(worksheet, 'text', 'a'); + const range = a.findAll(); +}); + +test('Test findNext', () => { + let { worksheet, workbook } = demo(); + const a = new TextFinder(worksheet, 'text', 'a'); + const range = a.findNext(); +}); + +test('Test findPrevious', () => { + let { worksheet, workbook } = demo(); + const a = new TextFinder(worksheet, 'text', 'a'); + const range = a.findPrevious(); +}); + +test('Test matchCase', () => { + let { worksheet, workbook } = demo(); + const a = new TextFinder(worksheet, 'text', 'a'); + a.matchCase(true); +}); + +test('Test matchCase', () => { + let { worksheet, workbook } = demo(); + const a = new TextFinder(worksheet, 'text', 'a'); + a.matchEntireCell(true); +}); diff --git a/packages/sheets-plugin-find/tsconfig.json b/packages/sheets-plugin-find/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-find/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-find/tsconfig.ref.json b/packages/sheets-plugin-find/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-find/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-find/tsconfig.test.json b/packages/sheets-plugin-find/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-find/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-find/vite.config.ts b/packages/sheets-plugin-find/vite.config.ts new file mode 100644 index 0000000000..803d7fbc8e --- /dev/null +++ b/packages/sheets-plugin-find/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginFind', + formats: ['es', 'umd'], + fileName: 'univer-sheets-plugin-find', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-format/README-zh.md b/packages/sheets-plugin-format/README-zh.md new file mode 100644 index 0000000000..81fc2b8417 --- /dev/null +++ b/packages/sheets-plugin-format/README-zh.md @@ -0,0 +1,98 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet 官方 UI 组件库 + +## 特性 + +- 纯 tsx 组件,可方便集成到 vue/react +- 按需引入,不用担心影响打包体积 + +## 用法 + +### 安装 + +```bash +npm i @univer/style-universheet +``` + +### 使用 + +```js +impport baseUI from '@univer/style-universheet' +universheet.install(baseUI) +universheet.create({ + plugins:['style-universheet'] +}) +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 7 + +### 安装 + +``` +npm i +``` + +### 开发 + +``` +# 当前目录 ./packages/style-universheet/ +npm run dev + +# 或者项目根目录 ./ +npm run dev -w @univer/style-universheet +``` + +### 打包 + +``` +# 当前目录 ./packages/style-universheet/ +npm run build + +# 或者根目录 ./ +npm run build -w @univer/style-universheet +``` + +## 组件库文档 + +使用[Docusaurus 2](https://docusaurus.io/)构建 + +### 安装 + +```console +# 根目录 +npm i +``` + +### 开发 + +```console +# 当前目录 +npm run start + +# 或者根目录 +npm run start -w @univer/style-universheet +``` + +此命令启动本地开发服务器并打开浏览器窗口。 大多数更改都会实时刷新,而无需重新启动服务器。 + +### 打包 + +```console +# 当前目录 +npm run build:doc + +# 或者根目录 +npm run build:doc -w @univer/style-universheet +``` + +此命令将静态内容生成到 `build` 目录中,并且可以使用任何静态内容托管服务来提供。 diff --git a/packages/sheets-plugin-format/README.md b/packages/sheets-plugin-format/README.md new file mode 100644 index 0000000000..7c47b06298 --- /dev/null +++ b/packages/sheets-plugin-format/README.md @@ -0,0 +1,98 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet official UI component library + +## Features + +- Pure tsx components, which can be easily integrated into vue/react +- Import on demand, don’t worry about affecting the package size + +## Usage + +### Install + +```bash +npm i @univer/style-universheet +``` + +### Import + +```js +impport baseUI from'@univer/style-universheet' +universheet.install(baseUI) +universheet.create({ + plugins:['style-universheet'] +}) +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 7 + +### Installation + +``` +npm i +``` + +### Development + +``` +# Current directory ./packages/style-universheet/ +npm run dev + +# Or project root directory ./ +npm run dev -w @univer/style-universheet +``` + +### Package + +``` +# Current directory ./packages/style-universheet/ +npm run build + +# Or root directory ./ +npm run build -w @univer/style-universheet +``` + +## Doc Website + +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +### Installation + +```console +# root folder +npm i +``` + +### Development + +```console +# style-universheet folder +npm run start + +# or root folder +npm run start -w @univer/style-universheet +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. + +### Build + +```console +# style-universheet folder +npm run build:doc + +# or root folder +npm run build:doc -w @univer/style-universheet +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. diff --git a/packages/sheets-plugin-format/index.html b/packages/sheets-plugin-format/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-format/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-format/index.ts b/packages/sheets-plugin-format/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-format/jest.config.js b/packages/sheets-plugin-format/jest.config.js new file mode 100644 index 0000000000..9122fca92b --- /dev/null +++ b/packages/sheets-plugin-format/jest.config.js @@ -0,0 +1,17 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + testEnvironment: 'jsdom', + preset: 'ts-jest', + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, + moduleFileExtensions: ['js', 'ts', 'tsx'], + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', + }, +}; diff --git a/packages/sheets-plugin-format/package.json b/packages/sheets-plugin-format/package.json new file mode 100644 index 0000000000..1bf19ac689 --- /dev/null +++ b/packages/sheets-plugin-format/package.json @@ -0,0 +1,58 @@ +{ + "name": "@univer/sheets-plugin-format", + "version": "0.0.1", + "description": "UniverSheet normal plugin UI manager", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14", + "ssf": "0.11.2" + } +} diff --git a/packages/sheets-plugin-format/src/DateFormat.ts b/packages/sheets-plugin-format/src/DateFormat.ts new file mode 100644 index 0000000000..3a5d4b3dfe --- /dev/null +++ b/packages/sheets-plugin-format/src/DateFormat.ts @@ -0,0 +1,16 @@ +// import {Context} from "@univer/core"; + +// export function DateFormat(ctx: Context) { +// const workbook = ctx.getWorkBook(); +// workbook.getFormatManager().add('date1', (value) => { +// // TODO ... +// // 转换格式化后的数据 +// return ''; +// }) +// workbook.getFormatManager().add('date2', (value) => { +// return ''; +// }) +// workbook.getFormatManager().add('date2', (value) => { +// return ''; +// }) +// } diff --git a/packages/sheets-plugin-format/src/FormatPlugin.ts b/packages/sheets-plugin-format/src/FormatPlugin.ts new file mode 100644 index 0000000000..bfaccbc5cc --- /dev/null +++ b/packages/sheets-plugin-format/src/FormatPlugin.ts @@ -0,0 +1,10 @@ +import { Context, IOCContainer, Plugin } from '@univer/core'; +// import { DateFormat } from './DateFormat'; + +export class FormatPlugin extends Plugin { + onMapping(container: IOCContainer): void {} + + onMounted(ctx: Context): void { + // DateFormat(ctx); + } +} diff --git a/packages/sheets-plugin-format/tsconfig.json b/packages/sheets-plugin-format/tsconfig.json new file mode 100644 index 0000000000..975f6f388a --- /dev/null +++ b/packages/sheets-plugin-format/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-format/tsconfig.ref.json b/packages/sheets-plugin-format/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-format/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-format/tsconfig.test.json b/packages/sheets-plugin-format/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-format/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-format/vite.config.ts b/packages/sheets-plugin-format/vite.config.ts new file mode 100644 index 0000000000..eba9965fd4 --- /dev/null +++ b/packages/sheets-plugin-format/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import { name, version } from './package.json'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginImage', + formats: ['es', 'umd'], + fileName: 'univer-sheets-plugin-formula', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', + generateScopedName: 'univer-[local]', + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-formula/README-zh.md b/packages/sheets-plugin-formula/README-zh.md new file mode 100644 index 0000000000..251ab48050 --- /dev/null +++ b/packages/sheets-plugin-formula/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet Formula 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-formula +``` + +### 使用 + +```js +impport {Formula} from '@univer/sheets-plugin-formula' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Formula()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-formula/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-formula dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-formula/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-formula build +``` diff --git a/packages/sheets-plugin-formula/README.md b/packages/sheets-plugin-formula/README.md new file mode 100644 index 0000000000..1456b7ec79 --- /dev/null +++ b/packages/sheets-plugin-formula/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin Formula + +### Installation + +```bash +npm i @univer/sheets-plugin-formula +``` + +### Usage + +```js +impport {Formula} from '@univer/sheets-plugin-formula' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Formula()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-formula/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-formula dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-formula/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-formula build +``` diff --git a/packages/sheets-plugin-formula/index.html b/packages/sheets-plugin-formula/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-formula/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-formula/jest.config.js b/packages/sheets-plugin-formula/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-formula/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-formula/package.json b/packages/sheets-plugin-formula/package.json new file mode 100644 index 0000000000..6535a58576 --- /dev/null +++ b/packages/sheets-plugin-formula/package.json @@ -0,0 +1,60 @@ +{ + "name": "@univer/sheets-plugin-formula", + "version": "0.0.1", + "description": "UniverSheet normal plugin formula", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-formula-engine": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "css-vars-ponyfill": "^2.4.6", + "csstype": "^3.0.11", + "preact": "^10.5.14", + "react-colorful": "^5.3.0" + } +} diff --git a/packages/sheets-plugin-formula/src/Basic/Cache.ts b/packages/sheets-plugin-formula/src/Basic/Cache.ts new file mode 100644 index 0000000000..002f98640e --- /dev/null +++ b/packages/sheets-plugin-formula/src/Basic/Cache.ts @@ -0,0 +1 @@ +export class Cache {} diff --git a/packages/sheets-plugin-formula/src/Basic/IFormula.ts b/packages/sheets-plugin-formula/src/Basic/IFormula.ts new file mode 100644 index 0000000000..79c97b9929 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Basic/IFormula.ts @@ -0,0 +1,8 @@ +import { Context } from '@univer/core'; + +export type IConfig = { + context: Context; +}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-formula/src/Basic/PLUGIN_NAME.ts b/packages/sheets-plugin-formula/src/Basic/PLUGIN_NAME.ts new file mode 100644 index 0000000000..272dbd75b8 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Basic/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const FORMULA_PLUGIN_NAME = 'formula'; diff --git a/packages/sheets-plugin-formula/src/Basic/State.ts b/packages/sheets-plugin-formula/src/Basic/State.ts new file mode 100644 index 0000000000..1884c5fff6 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Basic/State.ts @@ -0,0 +1,32 @@ +export class State {} + +export const FORMULA_CHILDREN = [ + { + name: 'formula.sum', + icon: 'SUM', + }, + { + name: 'formula.average', + icon: 'AVERAGE', + }, + { + name: 'formula.count', + icon: 'COUNT', + }, + { + name: 'formula.max', + icon: 'MAX', + }, + { + name: 'formula.min', + icon: 'MIN', + }, + { + name: 'formula.if', + icon: 'IF', + }, + { + name: 'formula.more', + icon: '', + }, +]; diff --git a/packages/sheets-plugin-formula/src/Basic/index.ts b/packages/sheets-plugin-formula/src/Basic/index.ts new file mode 100644 index 0000000000..6c6e3b5d96 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Basic/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './State'; diff --git a/packages/sheets-plugin-formula/src/Const/functionlist.ts b/packages/sheets-plugin-formula/src/Const/functionlist.ts new file mode 100644 index 0000000000..15c2f8636f --- /dev/null +++ b/packages/sheets-plugin-formula/src/Const/functionlist.ts @@ -0,0 +1,41 @@ +interface P { + name: string; + detail: string; + example: string; + require: string; + repeat: string; + type: string; +} +interface FunctionList { + n: string; + t: number; + d: string; + a: string; + m?: number[]; + p: P[]; +} + +interface FunctionType { + [index: string]: string; +} + +const functionType: FunctionType = { + '0': '数学', + '1': '统计', + '2': '查找', + '3': 'UniverSheet内置', + '4': '数据挖掘', + '5': '数据源', + '6': '日期', + '7': '过滤器', + '8': '财务', + '9': '工程计算', + '10': '逻辑', + '11': '运算符', + '12': '文本', + '13': '转换工具', + '14': '数组', + '15': '其他', +}; +export { functionType }; +export type { FunctionList, FunctionType, P }; diff --git a/packages/sheets-plugin-formula/src/FormulaPlugin.tsx b/packages/sheets-plugin-formula/src/FormulaPlugin.tsx new file mode 100644 index 0000000000..e38b2b0448 --- /dev/null +++ b/packages/sheets-plugin-formula/src/FormulaPlugin.tsx @@ -0,0 +1,71 @@ +import { Context, IOCContainer, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { FormulaEnginePlugin } from '../../base-formula-engine'; +import { FormulaButton } from './UI/FormulaButton'; +import { zh, en } from './Locale'; + +import { IConfig } from './Basic/IFormula'; +import { FORMULA_PLUGIN_NAME } from './Basic/PLUGIN_NAME'; + +type IPluginConfig = {}; + +export class FormulaPlugin extends Plugin { + protected _config: IPluginConfig; + + private _formulaEngine: FormulaEnginePlugin; + + constructor(config?: IPluginConfig) { + super(FORMULA_PLUGIN_NAME); + this._config = config || {}; + } + + static create(config?: IPluginConfig) { + return new FormulaPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + + const context = this.getContext(); + let formulaEngine = context.getPluginManager().getPluginByName('pluginFormulaEngine'); + if (!formulaEngine) { + formulaEngine = new FormulaEnginePlugin(); + universheetInstance.installPlugin(formulaEngine); + } + this._formulaEngine = formulaEngine; + } + + initialize(): void { + const context = this.getContext(); + /** + * load more Locale object + */ + this.getContext().getLocale().load({ + en, + zh, + }); + + const config: IConfig = { context }; + + const item: IToolBarItemProps = { + locale: FORMULA_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.addButton(item); + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} + + getFormulaEngine() { + return this._formulaEngine; + } +} diff --git a/packages/sheets-plugin-formula/src/Locale/en.ts b/packages/sheets-plugin-formula/src/Locale/en.ts new file mode 100644 index 0000000000..6b3052e89d --- /dev/null +++ b/packages/sheets-plugin-formula/src/Locale/en.ts @@ -0,0 +1,10579 @@ +export default { + formula: { + formulaLabel: 'SUM', + formulaRightLabel: 'more formule', + ok: 'Ok', + cancel: 'Cancel', + clearValidation: 'Clear Validation', + formula: { + sum: 'sum', + average: 'average', + count: 'count', + max: 'max', + min: 'min', + if: 'If formula generator', + more: 'More functions...', + }, + functionlist: [ + { + n: 'SUMIF', + t: 0, + d: 'Returns a conditional sum across a range.', + a: 'A conditional sum across a range.', + m: [2, 3], + p: [ + { + name: 'range', + detail: 'The range which is tested against `criterion`.', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'criterion', + detail: 'The pattern or test to apply to `range`.', + example: '">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'sum_range', + detail: 'The range to be summed, if different from `range`.', + example: 'B1:B10', + require: 'o', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'TAN', + t: 0, + d: 'Returns the tangent of an angle provided in radians.', + a: 'Tangent of an angle provided in radians.', + m: [1, 1], + p: [ + { + name: 'angle', + detail: 'The angle to find the tangent of, in radians.', + example: '45*PI()/180', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TANH', + t: 0, + d: 'Returns the hyperbolic tangent of any real number.', + a: 'Hyperbolic tangent of any real number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'Any real value to calculate the hyperbolic tangent of.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CEILING', + t: 0, + d: 'Rounds a number up to the nearest integer multiple of specified significance `factor`.', + a: 'Rounds number up to nearest multiple of a factor.', + m: [2, 2], + p: [ + { + name: 'value', + detail: 'The value to round up to the nearest integer multiple of `factor`.', + example: '23.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'factor', + detail: 'The number to whose multiples `value` will be rounded.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ATAN', + t: 0, + d: 'Returns the inverse tangent of a value, in radians.', + a: 'Inverse tangent of a value, in radians.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the inverse tangent.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ASINH', + t: 0, + d: 'Returns the inverse hyperbolic sine of a number.', + a: 'Inverse hyperbolic sine of a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the inverse hyperbolic sine.', + example: '0.9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ABS', + t: 0, + d: 'Returns the absolute value of a number.', + a: 'Absolute value of a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The number of which to return the absolute value.', + example: '-2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ACOS', + t: 0, + d: 'Returns the inverse cosine of a value, in radians.', + a: 'Inverse cosine of a value, in radians.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the inverse cosine. Must be between `-1` and `1`, inclusive.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ACOSH', + t: 0, + d: 'Returns the inverse hyperbolic cosine of a number.', + a: 'Inverse hyperbolic cosine of a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the inverse hyperbolic cosine. Must be greater than or equal to `1`.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MULTINOMIAL', + t: 0, + d: "Returns the factorial of the sum of values divided by the product of the values' factorials.", + a: 'Multinomial distribution function.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'ATANH', + t: 0, + d: 'Returns the inverse hyperbolic tangent of a number.', + a: 'Inverse hyperbolic tangent of a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the inverse hyperbolic tangent. Must be between -1 and 1, exclusive.', + example: '0.9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ATAN2', + t: 0, + d: 'Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair (`x`,`y`), in radians.', + a: 'Arctangent of a value.', + m: [2, 2], + p: [ + { + name: 'x', + detail: 'The x coordinate of the endpoint of the line segment for which to calculate the angle from the x-axis.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'y', + detail: 'The y coordinate of the endpoint of the line segment for which to calculate the angle from the x-axis.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUNTBLANK', + t: 1, + d: 'Returns the number of empty values in a list of values and ranges.', + a: 'Number of empty values.', + m: [1, 1], + p: [ + { + name: 'value1', + detail: 'The first value or range in which to count the number of blanks.', + example: 'A2:C100', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'COSH', + t: 0, + d: 'Returns the hyperbolic cosine of any real number.', + a: 'Hyperbolic cosine of any real number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'Any real value to calculate the hyperbolic cosine of.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'INT', + t: 0, + d: 'Rounds a number down to the nearest integer that is less than or equal to it.', + a: 'Rounds number down to nearest integer.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to round down to the nearest integer.', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ISEVEN', + t: 0, + d: 'Checks whether the provided value is even.', + a: 'Whether the provided value is even.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as even.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ISODD', + t: 0, + d: 'Checks whether the provided value is odd.', + a: 'Whether the provided value is odd.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as odd.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LCM', + t: 0, + d: 'Returns the least Common multiple of one or more integers.', + a: 'Least Common multiple of one or more integers.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range whose factors to consider in a calculation to find the least Common multiple.', + example: 'A2:A5', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges whose factors to consider to find the least Common multiple.', + example: '3', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'LN', + t: 0, + d: "Returns the logarithm of a number, base e (Euler's number).", + a: "The logarithm of a number, base e (euler's number).", + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the logarithm, base e.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOG', + t: 0, + d: 'Returns the logarithm of a number with respect to a base.', + a: 'The logarithm of a number with respect to a base.', + m: [1, 2], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the logarithm.', + example: '128', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'base', + detail: 'The base to use for calculation of the logarithm.', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOG10', + t: 0, + d: 'Returns the logarithm of a number, base 10.', + a: 'The logarithm of a number, base 10.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the logarithm, base 10.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MOD', + t: 0, + d: 'Returns the result of the modulo operator, the remainder after a division operation.', + a: 'Modulo (remainder) operator.', + m: [2, 2], + p: [ + { + name: 'dividend', + detail: 'The number to be divided to find the remainder.', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'divisor', + detail: 'The number to divide by.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MROUND', + t: 0, + d: 'Rounds one number to the nearest integer multiple of another.', + a: 'Rounds a number to the nearest integer multiple.', + m: [2, 2], + p: [ + { + name: 'value', + detail: 'The number to round to the nearest integer multiple of another.', + example: '21', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'factor', + detail: 'The number to whose multiples `value` will be rounded.', + example: '14', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ODD', + t: 0, + d: 'Rounds a number up to the nearest odd integer.', + a: 'Rounds a number up to the nearest odd integer.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to round to the next greatest odd number.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMSQ', + t: 0, + d: 'Returns the sum of the squares of a series of numbers and/or cells.', + a: 'Sum of squares.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first number or range whose squares to add together.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional numbers or ranges whose squares to add to the square(s) of `value1`.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'COMBIN', + t: 0, + d: 'Returns the number of ways to choose some number of objects from a pool of a given size of objects.', + a: 'Number of combinations from a set of objects.', + m: [2, 2], + p: [ + { + name: 'n', + detail: 'The size of the pool of objects to choose from.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'k', + detail: 'The number of objects to choose.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUM', + t: 0, + d: 'Returns the sum of a series of numbers and/or cells.', + a: 'Sum of a series of numbers and/or cells.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first number or range to add together.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional numbers or ranges to add to `value1`.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'SUBTOTAL', + t: 0, + d: 'Returns a subtotal for a vertical range of cells using a specified aggregation function.', + a: 'Subtotal for a range using a specific function.', + m: [2, 256], + p: [ + { + name: 'function_code', + detail: 'The function to use in subtotal aggregation.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'range1', + detail: 'The first range over which to calculate a subtotal.', + example: 'A2:A5', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'range2', + detail: 'Additional ranges over which to calculate subtotals.', + example: 'B2:B8', + require: 'o', + repeat: 'y', + type: 'range', + }, + ], + }, + { + n: 'ASIN', + t: 0, + d: 'Returns the inverse sine of a value, in radians.', + a: 'Inverse sine of a value, in radians.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the inverse sine. Must be between `-1` and `1`, inclusive.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUNTIF', + t: 1, + d: 'Returns a conditional count across a range.', + a: 'A conditional count across a range.', + m: [2, 2], + p: [ + { + name: 'range', + detail: 'The range that is tested against `criterion`.', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'criterion', + detail: 'The pattern or test to apply to `range`.', + example: '">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RADIANS', + t: 0, + d: 'Converts an angle value in degrees to radians.', + a: 'Converts an angle value in degrees to radians.', + m: [1, 1], + p: [ + { + name: 'angle', + detail: 'The angle to convert from degrees to radians.', + example: '180', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RAND', + t: 0, + d: 'Returns a random number between 0 inclusive and 1 exclusive.', + a: 'A random number between 0 inclusive and 1 exclusive.', + m: [0, 0], + p: [], + }, + { + n: 'COUNTUNIQUE', + t: 0, + d: 'Counts the number of unique values in a list of specified values and ranges.', + a: 'Counts number of unique values in a range.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider for uniqueness.', + example: 'A1:C100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider for uniqueness.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DEGREES', + t: 0, + d: 'Converts an angle value in radians to degrees.', + a: 'Converts an angle value in radians to degrees.', + m: [1, 1], + p: [ + { + name: 'angle', + detail: 'The angle to convert from radians to degrees.', + example: 'PI()', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ERFC', + t: 9, + d: 'Returns the complementary Gauss error function of a value.', + a: 'Complementary gauss error function of a value.', + m: [1, 1], + p: [ + { + name: 'z', + detail: 'The number for which to calculate the complementary Gauss error function.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EVEN', + t: 0, + d: 'Rounds a number up to the nearest even integer.', + a: 'Rounds a number up to the nearest even integer.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to round to the next greatest even number.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EXP', + t: 0, + d: "Returns Euler's number, e (~2.718) raised to a power.", + a: "Euler's number, e (~2.718) raised to a power.", + m: [1, 1], + p: [ + { + name: 'exponent', + detail: 'The exponent to raise e to.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FACT', + t: 0, + d: 'Returns the factorial of a number.', + a: 'Factorial of a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The number or reference to a number whose factorial will be calculated and returned.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FACTDOUBLE', + t: 0, + d: 'Returns the "double factorial" of a number.', + a: '"double factorial" of a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The number or reference to a number whose double factorial will be calculated and returned.', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PI', + t: 0, + d: 'Returns the value of Pi to 14 decimal places.', + a: 'The number pi.', + m: [0, 0], + p: [], + }, + { + n: 'FLOOR', + t: 0, + d: 'Rounds a number down to the nearest integer multiple of specified significance `factor`.', + a: 'Rounds number down to nearest multiple of a factor.', + m: [2, 2], + p: [ + { + name: 'value', + detail: 'The value to round down to the nearest integer multiple of `factor`.', + example: '23.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'factor', + detail: 'The number to whose multiples `value` will be rounded.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GCD', + t: 0, + d: 'Returns the greatest Common divisor of one or more integers.', + a: 'Greatest Common divisor of one or more integers.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range whose factors to consider in a calculation to find the greatest Common divisor.', + example: 'A2:A5', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges whose factors to consider to find the greatest Common divisor.', + example: '96', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANDBETWEEN', + t: 0, + d: 'Returns a uniformly random integer between two values, inclusive.', + a: 'Random integer between two values, inclusive.', + m: [2, 2], + p: [ + { + name: 'low', + detail: 'The low end of the random range.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'high', + detail: 'The high end of the random range.', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ROUND', + t: 0, + d: 'Rounds a number to a certain number of decimal places according to standard rules.', + a: 'Rounds a number according to standard rules.', + m: [2, 2], + p: [ + { + name: 'value', + detail: 'The value to round to `places` number of places.', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'places', + detail: 'The number of decimal places to which to round.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ROUNDDOWN', + t: 0, + d: 'Rounds a number to a certain number of decimal places, always rounding down to the next valid increment.', + a: 'Rounds down a number.', + m: [2, 2], + p: [ + { + name: 'value', + detail: 'The value to round to `places` number of places, always rounding down.', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'places', + detail: 'The number of decimal places to which to round.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ROUNDUP', + t: 0, + d: 'Rounds a number to a certain number of decimal places, always rounding up to the next valid increment.', + a: 'Rounds up a number.', + m: [2, 2], + p: [ + { + name: 'value', + detail: 'The value to round to `places` number of places, always rounding up.', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'places', + detail: 'The number of decimal places to which to round.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SERIESSUM', + t: 0, + d: 'Given parameters `x`, `n`, `m`, and `a`, returns the power series sum a', + a: 'Sum of a power series.', + m: [4, 4], + p: [ + { + name: 'x', + detail: 'The input to the power series. Varies depending on the type of approximation, may be angle, exponent, or some other value.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'n', + detail: 'The initial power to which to raise `x` in the power series.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'm', + detail: 'The additive increment by which to increase `x`.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'a', + detail: 'The array or range containing the coefficients of the power series.', + example: '{FACT(0)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SIGN', + t: 0, + d: 'Given an input number, returns `-1` if it is negative, `1` if positive, and `0` if it is zero.', + a: 'Sign of a provided number (+/-/0).', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value whose sign will be evaluated.', + example: '-42', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SIN', + t: 0, + d: 'Returns the sine of an angle provided in radians.', + a: 'Sine of an angle provided in radians.', + m: [1, 1], + p: [ + { + name: 'angle', + detail: 'The angle to find the sine of, in radians.', + example: 'PI()', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SINH', + t: 0, + d: 'Returns the hyperbolic sine of any real number.', + a: 'Hyperbolic sine of any real number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'Any real value to calculate the hyperbolic sine of.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SQRT', + t: 0, + d: 'Returns the positive square root of a positive number.', + a: 'Positive square root of a positive number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The number for which to calculate the positive square root.', + example: '9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SQRTPI', + t: 0, + d: 'Returns the positive square root of the product of Pi and the given positive number.', + a: 'Square root of the product of pi and number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: "The number which will be multiplied by Pi and have the product's square root returned", + example: '9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GAMMALN', + t: 1, + d: "Returns the logarithm of a specified Gamma function, base e (Euler's number).", + a: 'Logarithm of gamma function.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The input to the Gamma function. The natural logarithm of Gamma(`value`) will be returned.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COS', + t: 0, + d: 'Returns the cosine of an angle provided in radians.', + a: 'Cosine of an angle provided in radians.', + m: [1, 1], + p: [ + { + name: 'angle', + detail: 'The angle to find the cosine of, in radians.', + example: 'PI()', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TRUNC', + t: 0, + d: 'Truncates a number to a certain number of significant digits by omitting less significant digits.', + a: 'Truncates a number.', + m: [1, 2], + p: [ + { + name: 'value', + detail: 'The value to be truncated.', + example: '3.141592654', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'places', + detail: 'The number of significant digits to the right of the decimal point to retain.', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'QUOTIENT', + t: 0, + d: 'Returns one number divided by another.', + a: 'One number divided by another.', + m: [2, 2], + p: [ + { + name: 'dividend', + detail: 'The number to be divided.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'divisor', + detail: 'The number to divide by.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'POWER', + t: 0, + d: 'Returns a number raised to a power.', + a: 'A number raised to a power.', + m: [2, 2], + p: [ + { + name: 'base', + detail: 'The number to raise to the `exponent` power.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'exponent', + detail: 'The exponent to raise `base` to.', + example: '0.5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMIFS', + t: 0, + d: 'Returns the sum of a range depending on multiple criteria.', + a: 'Sums a range depending on multiple criteria.', + m: [3, 257], + p: [ + { + name: 'sum_range', + detail: 'The range to sum.', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'criteria_range1', + detail: 'The range to check against criterion1.', + example: ' B1:B10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'criterion1', + detail: 'The pattern or test to apply to criteria_range1.', + example: ' ">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria_range2', + detail: 'Additional ranges to check.', + example: ' C1:C10', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'COUNTIFS', + t: 1, + d: 'Returns the count of a range depending on multiple criteria.', + a: 'Count values depending on multiple criteria.', + m: [2, 256], + p: [ + { + name: 'criteria_range1', + detail: 'The range to check against `criterion1`.', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'criterion1', + detail: 'The pattern or test to apply to `criteria_range1`.', + example: ' ">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria_range2', + detail: 'Additional ranges to check.', + example: ' B1:B10', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'PRODUCT', + t: 0, + d: 'Returns the result of multiplying a series of numbers together.', + a: 'Result of multiplying a series of numbers together.', + m: [1, 255], + p: [ + { + name: 'factor1', + detail: 'The first number or range to calculate for the product.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'factor2', + detail: 'More numbers or ranges to calculate for the product.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HARMEAN', + t: 1, + d: 'Calculates the harmonic mean of a dataset.', + a: 'The harmonic mean of a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the population.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the population.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HYPGEOMDIST', + t: 1, + d: 'Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws.', + a: 'Hypergeometric distribution probability.', + m: [5, 5], + p: [ + { + name: 'num_successes', + detail: 'The desired number of successes.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'num_draws', + detail: 'The number of permitted draws.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'successes_in_pop', + detail: 'The total number of successes in the population.', + example: '20', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pop_size', + detail: 'The total size of the population', + example: '40', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Determine the logical value of the function form. \n\nIf cumulative is TRUE(), HYPGEOM.DIST returns the cumulative distribution function;\n\nif FALSE(), it returns the probability density function.', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'INTERCEPT', + t: 1, + d: 'Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0).', + a: 'Y-intercept of line derived via linear regression.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'KURT', + t: 1, + d: 'Calculates the kurtosis of a dataset, which describes the shape, and in particular the "peakedness" of that dataset.', + a: 'Kurtosis of a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the dataset.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the dataset.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LARGE', + t: 1, + d: 'Returns the nth largest element from a data set, where n is user-defined.', + a: 'Nth largest element from a data set.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'Array or range containing the dataset to consider.', + example: 'A2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'n', + detail: 'The rank from largest to smallest of the element to return.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STDEVA', + t: 1, + d: 'Calculates the standard deviation based on a sample, setting text to the value `0`.', + a: 'Standard deviation of sample (text as 0).', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the sample.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the sample.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STDEVP', + t: 1, + d: 'Calculates the standard deviation based on an entire population.', + a: 'Standard deviation of an entire population.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the population.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the population.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GEOMEAN', + t: 1, + d: 'Calculates the geometric mean of a dataset.', + a: 'The geometric mean of a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the population.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the population.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANK_EQ', + t: 1, + d: 'Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned.', + a: 'Top rank of a specified value in a dataset.', + m: [2, 3], + p: [ + { + name: 'value', + detail: 'The value whose rank will be determined.', + example: 'A10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'is_ascending', + detail: 'Whether to consider the values in `data` in descending or ascending order. If omitted, the default is descending (FALSE).', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANK_AVG', + t: 1, + d: 'Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned.', + a: 'Average rank of a specified value in a dataset.', + m: [2, 3], + p: [ + { + name: 'value', + detail: 'The value whose rank will be determined.', + example: 'A10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'is_ascending', + detail: 'Whether to consider the values in `data` in descending or ascending order. If omitted, the default is descending (FALSE).', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'PERCENTRANK_EXC', + t: 1, + d: 'Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset.', + a: 'Percentage rank (percentile) from 0 to 1 exclusive.', + m: [2, 3], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value', + detail: 'The value whose percentage rank will be determined.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'significant_digits', + detail: 'The number of significant figures to use in the calculation. Default is 3.', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PERCENTRANK_INC', + t: 1, + d: 'Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset.', + a: 'Percentage rank (percentile) from 0 to 1 inclusive.', + m: [2, 3], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value', + detail: 'The value whose percentage rank will be determined.', + example: ' A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'significant_digits', + detail: 'The number of significant figures to use in the calculation. Default is 3.', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FORECAST', + t: 1, + d: 'Calculates the expected y-value for a specified x based on a linear regression of a dataset.', + a: 'Expected y-value based of linear regression.', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'The value on the x-axis to forecast.', + example: 'A1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FISHERINV', + t: 1, + d: 'Returns the inverse Fisher transformation of a specified value.', + a: 'Inverse fisher transformation of a specified value.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the inverse Fisher transformation.', + example: '0.962', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FISHER', + t: 1, + d: 'Returns the Fisher transformation of a specified value.', + a: 'Fisher transformation of a specified value.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value for which to calculate the Fisher transformation.', + example: '0.962', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MODE_SNGL', + t: 1, + d: 'Returns the most commonly occurring value in a dataset.', + a: 'Most commonly occurring value in a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating mode.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating mode.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'WEIBULL_DIST', + t: 1, + d: 'Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale.', + a: 'Weibull distribution function.', + m: [4, 4], + p: [ + { + name: 'x', + detail: 'The input to the Weibull distribution function.', + example: '2.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'shape', + detail: 'The shape parameter of the Weibull distribution function.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'scale', + detail: 'The scale parameter of the Weibull distribution function.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Whether to use the cumulative distribution function.', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'COUNT', + t: 1, + d: 'Returns the number of numeric values in a dataset.', + a: 'The number of numeric values in dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when counting.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when counting.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'COUNTA', + t: 1, + d: 'Returns the number of values in a dataset.', + a: 'The number of values in a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when counting.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when counting.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVEDEV', + t: 1, + d: "Calculates the average of the magnitudes of deviations of data from a dataset's mean.", + a: 'Average magnitude of deviations from mean.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the sample.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the sample.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'AVERAGE', + t: 1, + d: 'Returns the numerical average value in a dataset, ignoring text.', + a: 'Numerical average value in a dataset, ignoring text.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating the average value.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating the average value.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVERAGEA', + t: 1, + d: 'Returns the numerical average value in a dataset.', + a: 'Numerical average value in a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating the average value.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating the average value.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'BINOM_DIST', + t: 1, + d: 'Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws.', + a: 'Binomial distribution probability.', + m: [4, 4], + p: [ + { + name: 'num_successes', + detail: 'The number of successes for which to calculate the probability in `num_trials` trials.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'num_trials', + detail: 'The number of independent trials.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'prob_success', + detail: 'The probability of success in any given trial.', + example: '0.005', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Whether to use the binomial cumulative distribution.', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'BINOM_INV', + t: 1, + d: 'Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria.', + a: 'Inverse cumulative binomial distribution function.', + m: [3, 3], + p: [ + { + name: 'num_trials', + detail: 'The number of independent trials.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'prob_success', + detail: 'The probability of success in any given trial.', + example: '0.005', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'target_prob', + detail: 'The desired threshold probability.', + example: '0.8', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CONFIDENCE_NORM', + t: 1, + d: 'Calculates the width of half the confidence interval for a normal distribution.', + a: 'Confidence interval for a normal distribution.', + m: [3, 3], + p: [ + { + name: 'alpha', + detail: 'One minus the desired confidence level. E.g. `0.1` for `0.9`, or 90%, confidence.', + example: '0.05', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_deviation', + detail: 'The standard deviation of the population.', + example: '1.6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pop_size', + detail: 'The size of the population.', + example: '250', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CORREL', + t: 1, + d: 'Calculates r, the Pearson product-moment correlation coefficient of a dataset.', + a: 'Pearson Product-Moment Correlation Coefficient.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COVARIANCE_P', + t: 1, + d: 'Calculates the covariance of a dataset.', + a: 'The covariance of a dataset.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COVARIANCE_S', + t: 1, + d: 'Calculates the sample covariance of a dataset.', + a: 'The sample covariance of a dataset.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEVSQ', + t: 1, + d: 'Calculates the sum of squares of deviations based on a sample.', + a: 'The sum of squares of deviations based on a sample.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the sample.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the sample.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EXPON_DIST', + t: 1, + d: 'Returns the value of the exponential distribution function with a specified lambda at a specified value.', + a: 'Exponential distribution function.', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'The input to the exponential distribution function.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'lambda', + detail: 'The lambda to specify the exponential distribution function.', + example: '0.5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Whether to use the exponential cumulative distribution.', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVERAGEIF', + t: 1, + d: 'Returns the average of a range depending on criteria.', + a: 'Average of values depending on criteria.', + m: [2, 3], + p: [ + { + name: 'criteria_range', + detail: 'The range to check against `criterion`.', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criterion', + detail: 'The pattern or test to apply to `criteria_range`.', + example: '">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'average_range', + detail: 'The range to average. If not included, `criteria_range` is used for the average instead.', + example: 'B1:B10', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVERAGEIFS', + t: 1, + d: 'Returns the average of a range depending on multiple criteria.', + a: 'Average of values depending on multiple criteria.', + m: [2, 255], + p: [ + { + name: 'average_range', + detail: 'The range to average.', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria_range1', + detail: 'The range to check against `criterion1`.', + example: ' B1:B10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criterion1', + detail: 'The pattern or test to apply to `criteria_range1`.', + example: ' ">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria_range2', + detail: 'Additional ranges to check.', + example: ' C1:C10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'PERMUT', + t: 1, + d: 'Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order.', + a: 'Number of permutations from a number of objects.', + m: [2, 2], + p: [ + { + name: 'n', + detail: 'The size of the pool of objects to choose from.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'k', + detail: 'The number of objects to choose.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TRIMMEAN', + t: 1, + d: 'Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset.', + a: 'Mean of a dataset excluding high/low ends.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'Array or range containing the dataset to consider.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'exclude_proportion', + detail: 'The proportion of the dataset to exclude, from the extremities of the set.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PERCENTILE_EXC', + t: 1, + d: 'Returns the value at a given percentile of a dataset exclusive of 0 and 1.', + a: 'Value at a given percentile of a dataset exclusive of 0 and 1.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'percentile', + detail: "The percentile, exclusive of 0 and 1, whose value within 'data' will be calculated and returned.", + example: '0.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PERCENTILE_INC', + t: 1, + d: 'Returns the value at a given percentile of a dataset.', + a: 'Value at a given percentile of a dataset.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'percentile', + detail: 'The percentile whose value within `data` will be calculated and returned.`', + example: '0.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PEARSON', + t: 1, + d: 'Calculates r, the Pearson product-moment correlation coefficient of a dataset.', + a: 'Pearson Product-Moment Correlation Coefficient.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NORM_S_INV', + t: 1, + d: 'Returns the value of the inverse standard normal distribution function for a specified value.', + a: 'Inverse standard normal distribution function.', + m: [1, 1], + p: [ + { + name: 'x', + detail: 'The input to the inverse standard normal distribution function.', + example: '0.75', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NORM_S_DIST', + t: 1, + d: 'Returns the value of the standard normal cumulative distribution function for a specified value.', + a: 'Standard normal cumulative distribution function.', + m: [2, 2], + p: [ + { + name: 'x', + detail: 'The input to the standard normal cumulative distribution function.', + example: '2.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Determine the logical value of the function form. \n\nIf TRUE(), it returns the cumulative distribution function;\n\nIf it is FALSE(), it returns the probability density function.', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NORM_INV', + t: 1, + d: 'Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation.', + a: 'Inverse normal distribution function.', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'The input to the inverse normal distribution function.', + example: '0.75', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'The mean (mu) of the normal distribution function.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_deviation', + detail: 'The standard deviation (sigma) of the normal distribution function.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NORM_DIST', + t: 1, + d: 'Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation.', + a: 'Normal distribution function.', + m: [4, 4], + p: [ + { + name: 'x', + detail: 'The input to the normal distribution function.', + example: '2.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'The mean (mu) of the normal distribution function.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_deviation', + detail: 'The standard deviation (sigma) of the normal distribution function.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Whether to use the normal cumulative distribution function rather than the distribution function.', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NEGBINOM_DIST', + t: 1, + d: 'Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials.', + a: 'Negative binomial distribution probability.', + m: [4, 4], + p: [ + { + name: 'num_failures', + detail: 'The number of failures to model.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'num_successes', + detail: 'The number of successes to model.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'prob_success', + detail: 'The probability of success in any given trial.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Determine the logical value of the function form. \n\nIf TRUE(), it returns the cumulative distribution function;\n\nIf it is FALSE(), it returns the probability density function.', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'MINA', + t: 1, + d: 'Returns the minimum numeric value in a dataset.', + a: 'Minimum numeric value in a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating the minimum value.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating the minimum value.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MIN', + t: 1, + d: 'Returns the minimum value in a numeric dataset.', + a: 'Minimum value in a numeric dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating the minimum value.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating the minimum value.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MEDIAN', + t: 1, + d: 'Returns the median value in a numeric dataset.', + a: 'Median value in a numeric dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating the median value.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating the median value.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MAXA', + t: 1, + d: 'Returns the maximum numeric value in a dataset.', + a: 'Maximum numeric value in a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating the maximum value.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating the maximum value.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MAX', + t: 1, + d: 'Returns the maximum value in a numeric dataset.', + a: 'Maximum value in a numeric dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range to consider when calculating the maximum value.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to consider when calculating the maximum value.', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOGNORM_INV', + t: 1, + d: 'Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value.', + a: 'Inverse log-normal cumulative distribution function.', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'The input to the inverse log-normal cumulative distribution function.', + example: '0.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'The mean (mu) of the inverse log-normal cumulative distribution function.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_deviation', + detail: 'The standard deviation (sigma) of the inverse log-normal cumulative distribution function.', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOGNORM_DIST', + t: 1, + d: 'Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value.', + a: 'Log-normal cumulative distribution probability.', + m: [4, 4], + p: [ + { + name: 'x', + detail: 'The input to the log-normal cumulative distribution function.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'The mean (mu) of the log-normal cumulative distribution function.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_deviation', + detail: 'The standard deviation (sigma) of the log-normal cumulative distribution function.', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Determine the logical value of the function form. \n\nIf TRUE(), it returns the cumulative distribution function;\n\nIf it is FALSE(), it returns the probability density function.', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'Z_TEST', + t: 1, + d: 'Returns the one-tailed p-value of a Z-test with standard distribution.', + a: 'One-tailed p-value of a z-test.', + m: [2, 3], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'value', + detail: 'The test statistic to use in the Z-test.', + example: 'B2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_deviation', + detail: 'The standard deviation to assume for the Z-test. If this is not provided, the standard deviation of the data will be used.', + example: '3', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PROB', + t: 1, + d: 'Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits.', + a: 'Probability values lie in a range.', + m: [3, 4], + p: [ + { + name: 'data', + detail: 'Array or range containing the dataset to consider.', + example: 'A3:A6', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'probabilities', + detail: 'Array or range containing probabilities corresponding to `data`.', + example: '2', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'low_limit', + detail: 'The lower bound on the value range for which to calculate the probability.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'high_limit', + detail: 'The upper bound on the value range for which to calculate the probability.', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'QUARTILE_EXC', + t: 1, + d: 'Returns a value nearest to a specified quartile of a dataset exclusive of 0 and 4.', + a: 'Value nearest to a specific quartile of a dataset exclusive of 0 and 4.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'quartile_number', + detail: 'Which quartile to return.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'QUARTILE_INC', + t: 1, + d: 'Returns a value nearest to a specified quartile of a dataset.', + a: 'Value nearest to a specific quartile of a dataset.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'quartile_number', + detail: 'Which quartile value to return.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'POISSON_DIST', + t: 1, + d: 'Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean.', + a: 'Poisson distribution function.', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'The input to the Poisson distribution function.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'The mean (mu) of the Poisson distribution function.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Whether to use the Poisson cumulative distribution function rather than the distribution function.', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RSQ', + t: 1, + d: 'Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset.', + a: 'Square of the correlation coefficient.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_DIST', + t: 1, + d: "Calculates the left tail probability for a Student's t-distribution with a given input (x).", + a: "The left-tailed Student's t-distribution", + m: [3, 3], + p: [ + { + name: 'x', + detail: 'The input to the t-distribution function.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: 'The number of degrees of freedom.', + example: '30', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'If cumulative is TRUE, T.DIST returns the cumulative distribution function; if FALSE, it returns the probability density function.', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'T_DIST_2T', + t: 1, + d: "Calculates the probability for two tailed Student's t-distribution with a given input (x).", + a: "The two tailed Student's t-distribution", + m: [2, 2], + p: [ + { + name: 'x', + detail: 'The input to the t-distribution function.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: 'The number of degrees of freedom.', + example: '30', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_DIST_RT', + t: 1, + d: "Calculates the right tail probability for a Student's t-distribution with a given input (x).", + a: "The right-tailed Student's t-distribution", + m: [2, 2], + p: [ + { + name: 'x', + detail: 'The input to the t-distribution function.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: 'The number of degrees of freedom.', + example: '30', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_INV', + t: 1, + d: 'Calculates the negative inverse of the one-tailed TDIST function.', + a: 'T.INV', + m: [2, 2], + p: [ + { + name: 'probability', + detail: 'The probability associated with the two-tailed t-distribution.', + example: '0.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: 'The number of degrees of freedom.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_INV_2T', + t: 1, + d: 'Calculates the inverse of the two-tailed TDIST function.', + a: 'T.INV.2T', + m: [2, 2], + p: [ + { + name: 'probability', + detail: 'The probability associated with the two-tailed t-distribution.', + example: '0.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: 'The number of degrees of freedom.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_TEST', + t: 1, + d: "t-test. Returns the probability associated with Student's t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean.", + a: 'Returns the probability associated with t-test.', + m: [4, 4], + p: [ + { + name: 'range1', + detail: 'The first sample of data or group of cells to consider for the t-test.', + example: 'A1:A4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'range2', + detail: 'The second sample of data or group of cells to consider for the t-test.', + example: 'B1:B4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'tails', + detail: 'Specifies the number of distribution tails.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: 'Specifies the type of t-test.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'F_DIST', + t: 1, + d: "Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor's F distribution.", + a: 'F probability distribution (left-tailed).', + m: [4, 4], + p: [ + { + name: 'x', + detail: 'The input to the F probability distribution function. The value at which to evaluate the function.', + example: '15.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom1', + detail: 'The numerator of the number of degrees of freedom.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom2', + detail: 'The denominator of the number of degrees of freedom.', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'Logical value that determines the form of the function.', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'F_DIST_RT', + t: 1, + d: "Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor's F distribution.", + a: 'F probability distribution.', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'The input to the F probability distribution function. The value at which to evaluate the function.', + example: '15.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom1', + detail: 'The numerator of the number of degrees of freedom.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom2', + detail: 'The denominator of the number of degrees of freedom.', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VAR_P', + t: 1, + d: 'Calculates the variance based on an entire population.', + a: 'Variance of entire population.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the population.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the population.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VAR_S', + t: 1, + d: 'Calculates the variance based on a sample.', + a: 'Variance.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the sample.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the sample.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VARA', + t: 1, + d: 'Calculates the variance based on a sample, setting text to the value `0`.', + a: 'Variance of sample (text as 0).', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the sample.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the sample.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VARPA', + t: 1, + d: 'Calculates the variance based on an entire population, setting text to the value `0`.', + a: 'Variance of entire population (text as 0).', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the population.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the population.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STEYX', + t: 1, + d: 'Calculates the standard error of the predicted y-value for each x in the regression of a dataset.', + a: 'Standard error of predicted y-values in regression.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STANDARDIZE', + t: 1, + d: 'Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution.', + a: 'Normalized equivalent of a random variable.', + m: [3, 3], + p: [ + { + name: 'value', + detail: 'The value of the random variable to normalize.', + example: '96', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'The mean of the distribution.', + example: '80', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_deviation', + detail: 'The standard deviation of the distribution.', + example: '6.7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SMALL', + t: 1, + d: 'Returns the nth smallest element from a data set, where n is user-defined.', + a: 'Nth smallest element in a data set.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'The array or range containing the dataset to consider.', + example: 'A2:B100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'n', + detail: 'The rank from smallest to largest of the element to return.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SLOPE', + t: 1, + d: 'Calculates the slope of the line resulting from linear regression of a dataset.', + a: 'Slope of line from linear regression of data.', + m: [2, 2], + p: [ + { + name: 'data_y', + detail: 'The range representing the array or matrix of dependent data.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'data_x', + detail: 'The range representing the array or matrix of independent data.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SKEW', + t: 1, + d: 'Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean.', + a: 'Skewness of a dataset.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the dataset.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the dataset.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'SKEW_P', + t: 1, + d: 'Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. This assumes the dataset is for the population.', + a: "Skewness of a population's dataset.", + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first value or range of the dataset.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional values or ranges to include in the dataset.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'VLOOKUP', + t: 2, + d: 'Vertical lookup. Searches down the first column of a range for a key and returns the value of a specified cell in the row found.', + a: 'Vertical lookup.', + m: [3, 4], + p: [ + { + name: 'search_key', + detail: 'The value to search for. For example, `42`, `"Cats"`, or `I24`.', + example: '10003', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'range', + detail: 'The range to consider for the search. The first column in the range is searched for the key specified in `search_key`.', + example: 'A2:B26', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'index', + detail: 'The column index of the value to be returned, where the first column in `range` is numbered 1.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'is_sorted', + detail: 'Indicates whether the column to be searched (the first column of the specified range) is sorted, in which case the closest match for `search_key` will be returned.', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'HLOOKUP', + t: 2, + d: 'Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found.', + a: 'Horizontal lookup', + m: [3, 4], + p: [ + { + name: 'search_key', + detail: 'The value to search for. For example, `42`, `"Cats"`, or `I24`.', + example: '10003', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'range', + detail: 'The range to consider for the search. The first row in the range is searched for the key specified in `search_key`.', + example: 'A2:Z6', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'index', + detail: 'The row index of the value to be returned, where the first row in `range` is numbered 1.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'is_sorted', + detail: 'Indicates whether the row to be searched (the first row of the specified range) is sorted.', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LOOKUP', + t: 2, + d: 'Looks through a sorted row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column.', + a: 'Look up a value.', + m: [2, 3], + p: [ + { + name: 'search_key', + detail: 'The value to search for in the row or column. For example, `42`, `"Cats"`, or `I24`.', + example: '10003', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'search_range|search_result_array', + detail: 'One method of using this function is to provide a single sorted row or column `search_range` to look through for the `search_key` with a second argument `result_range`. The other way is to combine these two arguments into one `search_result_array` where the first row or column is searched and a value is returned from the last row or column in the array. If `search_key` is not found, a non-exact match may be returned.', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'result_range', + detail: 'The range from which to return a result. The value returned corresponds to the location where `search_key` is found in `search_range`. This range must be only a single row or column and should not be used if using the `search_result_array` method.', + example: 'B1:B100', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ADDRESS', + t: 2, + d: 'Returns a cell reference as a string.', + a: 'Cell reference as a string.', + m: [2, 5], + p: [ + { + name: 'row', + detail: 'The row number of the cell reference', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'column', + detail: 'The column number (not name) of the cell reference. `A` is column number `1`.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'absolute_relative_mode', + detail: 'An indicator of whether the reference is row/column absolute. `1` is row and column absolute (e.g. $A$1), `2` is row absolute and column relative (e.g. A$1), `3` is row relative and column absolute (e.g. $A1), and `4` is row and column relative (e.g. A1).', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'use_a1_notation', + detail: 'A boolean indicating whether to use `A1` style notation (TRUE) or `R1C1` style notation (FALSE).', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'sheet', + detail: 'Text indicating the name of the sheet into which the address points.', + example: '"Sheet2"', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'INDIRECT', + t: 2, + d: 'Returns a cell reference specified by a string.', + a: 'A cell reference specified by a string.', + m: [1, 2], + p: [ + { + name: 'cell_reference_as_string', + detail: 'A cell reference, written as a string with surrounding quotation marks.', + example: '"Sheet2!"&B10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'is_A1_notation', + detail: 'Indicates if the cell reference is in A1 notation (TRUE) or R1C1 notation (FALSE).', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ROW', + t: 2, + d: 'Returns the row number of a specified cell.', + a: 'Row number of a specified cell.', + m: [0, 1], + p: [ + { + name: 'cell_reference', + detail: 'The cell whose row number will be returned.', + example: 'A9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ROWS', + t: 2, + d: 'Returns the number of rows in a specified array or range.', + a: 'Number of rows in a specified array or range.', + m: [1, 1], + p: [ + { + name: 'range', + detail: 'The range whose row count will be returned.', + example: 'A9:A62', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COLUMN', + t: 2, + d: 'Returns the column number of a specified cell, with `A=1`.', + a: 'Column number of a specified cell.', + m: [0, 1], + p: [ + { + name: 'cell_reference', + detail: 'The cell whose column number will be returned. Column `A` corresponds to `1`.', + example: 'A9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'COLUMNS', + t: 2, + d: 'Returns the number of columns in a specified array or range.', + a: 'Number of columns in a specified array or range.', + m: [1, 1], + p: [ + { + name: 'range', + detail: 'The range whose column count will be returned.', + example: 'A9:W62', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'OFFSET', + t: 2, + d: 'Returns a range reference shifted a specified number of rows and columns from a starting cell reference.', + a: 'A range reference offset relative to a cell.', + m: [3, 5], + p: [ + { + name: 'cell_reference', + detail: 'The starting point from which to count the offset rows and columns.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'offset_rows', + detail: 'The number of rows to offset by.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'offset_columns', + detail: 'The number of columns to offset by.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'height', + detail: 'The height of the range to return starting at the offset target.', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'width', + detail: 'The width of the range to return starting at the offset target.', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MATCH', + t: 2, + d: 'Returns the relative position of an item in a range that matches a specified value.', + a: 'Position of item in range that matches value.', + m: [2, 3], + p: [ + { + name: 'search_key', + detail: 'The value to search for. For example, `42`, `"Cats"`, or `I24`.', + example: '"Sunday"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'range', + detail: 'The one-dimensional array to be searched.', + example: 'A2:A9', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'search_type', + detail: 'The search method. `1` (default) finds the largest value less than or equal to `search_key` when `range` is sorted in ascending order. `0` finds the exact value when `range` is unsorted. `-1` finds the smallest value greater than or equal to `search_key` when `range` is sorted in descending order.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'INDEX', + t: 2, + d: 'Returns the content of a cell, specified by row and column offset.', + a: 'Content of cell specified by row and column offset.', + m: [2, 3], + p: [ + { + name: 'reference', + detail: 'The array of cells to be offset into.', + example: 'A1:C20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'row', + detail: 'The number of offset rows.', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'column', + detail: 'The number of offset columns.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GETPIVOTDATA', + t: 2, + d: 'Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings.', + a: 'Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings.', + m: [2, 254], + p: [ + { + name: 'value_name', + detail: 'The name of the value in the pivot table for which you want to get data.', + example: '"SUM of number of units"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'any_pivot_table_cell', + detail: 'Any reference to a cell in the desired pivot table (top corner recommended).', + example: "'Pivot table'!A1", + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'original_column', + detail: 'The name of the column in the original data set (not the pivot table).', + example: '"division"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + { + name: 'pivot_item', + detail: 'The name of the row or column shown in the pivot table corresponding to *original_column* that you want to retrieve.', + example: '"east"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'CHOOSE', + t: 2, + d: 'Returns an element from a list of choices based on index.', + a: 'An element from a list of choices based on index.', + m: [2, 255], + p: [ + { + name: 'index', + detail: 'Which choice (of the up to 30 provided) to return.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'choice1', + detail: 'A potential value to return. Required. May be a reference to a cell or an individual value.', + example: '"A"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'choice2', + detail: 'Additional values among which to choose.', + example: '"B"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'HYPERLINK', + t: 2, + d: 'Creates a hyperlink inside a cell.', + a: 'Creates a hyperlink inside a cell.', + p: [ + { + name: 'url', + detail: 'The full URL of the link location enclosed in quotation marks, or a reference to a cell containing such a URL.', + example: '"http://www.universheet.com/"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'link_label', + detail: 'The text to display in the cell as the link, enclosed in quotation marks, or a reference to a cell containing such a label.', + example: '"universheet"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TIME', + t: 6, + d: 'Converts a provided hour, minute, and second into a time.', + a: 'Converts hour/minute/second into a time.', + m: [3, 3], + p: [ + { + name: 'hour', + detail: 'The hour component of the time.', + example: '11', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'minute', + detail: 'The minute component of the time.', + example: '40', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'second', + detail: 'The second component of the time.', + example: '59', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TIMEVALUE', + t: 6, + d: 'Returns the fraction of a 24-hour day the time represents.', + a: 'Converts a time string into its serial number representation.', + m: [1, 1], + p: [ + { + name: 'time_string', + detail: 'The string that holds the time representation.', + example: '"2:15 PM"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EOMONTH', + t: 6, + d: 'Returns a date on the last day of a month that falls a specified number of months before or after another date.', + a: 'Last day of a month before or after a date.', + m: [2, 2], + p: [ + { + name: 'start_date', + detail: 'The date from which to calculate the result.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'months', + detail: "The number of months before (negative) or after (positive) 'start_date' to consider.", + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EDATE', + t: 6, + d: 'Returns a date a specified number of months before or after another date.', + a: 'Date a number of months before/after another date.', + m: [2, 2], + p: [ + { + name: 'start_date', + detail: 'The date from which to calculate the result.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'months', + detail: "The number of months before (negative) or after (positive) 'start_date' to calculate.", + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SECOND', + t: 6, + d: 'Returns the second component of a specific time, in numeric format.', + a: 'Second component of a specific time.', + m: [1, 1], + p: [ + { + name: 'time', + detail: 'The time from which to calculate the second component', + example: 'TIME(11', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'MINUTE', + t: 6, + d: 'Returns the minute component of a specific time, in numeric format.', + a: 'Minute component of a specific time.', + m: [1, 1], + p: [ + { + name: 'time', + detail: 'The time from which to calculate the minute component.', + example: 'TIME(11', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'HOUR', + t: 6, + d: 'Returns the hour component of a specific time, in numeric format.', + a: 'Hour component of a specific time.', + m: [1, 1], + p: [ + { + name: 'time', + detail: 'The time from which to calculate the hour component.', + example: 'TIME(11', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NOW', + t: 6, + d: 'Returns the current date and time as a date value.', + a: 'Current date and time as a date value.', + m: [0, 0], + p: [], + }, + { + n: 'NETWORKDAYS', + t: 6, + d: 'Returns the number of net working days between two provided days.', + a: 'Net working days between two provided days.', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: 'The start date of the period from which to calculate the number of net working days.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: 'The end date of the period from which to calculate the number of net working days.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'holidays', + detail: 'A range or array constant containing the date serial numbers to consider holidays.', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NETWORKDAYS_INTL', + t: 6, + d: 'Returns the number of net working days between two provided days excluding specified weekend days and holidays.', + a: 'Net working days between two dates (specifying weekends).', + m: [2, 4], + p: [ + { + name: 'start_date', + detail: 'The start date of the period from which to calculate the number of net working days.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: 'The end date of the period from which to calculate the number of net working days.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'weekend', + detail: 'A number or string representing which days of the week are considered weekends.', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'holidays', + detail: 'A range or array constant containing the dates to consider as holidays.', + example: 'DATE(1969', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISOWEEKNUM', + t: 6, + d: 'Returns a number representing the ISO week of the year where the provided date falls.', + a: 'ISO week number of the year.', + m: [1, 1], + p: [ + { + name: 'date', + detail: 'The date for which to determine the ISO week number. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'WEEKNUM', + t: 6, + d: 'Returns a number representing the week of the year where the provided date falls.', + a: 'Week number of the year.', + m: [1, 2], + p: [ + { + name: 'date', + detail: 'The date for which to determine the week number. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'type', + detail: 'A number representing the day that a week starts on. Sunday = 1.', + example: '7', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'WEEKDAY', + t: 6, + d: 'Returns a number representing the day of the week of the date provided.', + a: 'Day of the week of the date provided (as number).', + m: [1, 2], + p: [ + { + name: 'date', + detail: 'The date for which to determine the day of the week. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'type', + detail: 'A number indicating which numbering system to use to represent weekdays. By default, counts starting with Sunday = 1.', + example: '7', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DAY', + t: 6, + d: 'Returns the day of the month that a specific date falls on, in numeric format.', + a: 'Day of the month that a specific date falls on.', + m: [1, 1], + p: [ + { + name: 'date', + detail: 'The date from which to extract the day.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DAYS', + t: 6, + d: 'Returns the number of days between two dates.', + a: 'Number of days between two dates.', + m: [2, 2], + p: [ + { + name: 'end_date', + detail: 'The end of the date range.', + example: '2011-3-15', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'start_date', + detail: 'The start of the date range.', + example: '2011-2-1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DAYS360', + t: 6, + d: 'Returns the difference between two days based on the 360 day year used in some financial interest calculations.', + a: 'Days between two dates on a 360-day year.', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: 'The start date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: 'The end date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'method', + detail: 'An indicator of what day count method to use.', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DATE', + t: 6, + d: 'Converts a provided year, month, and day into a date.', + a: 'Converts year/month/day into a date.', + m: [3, 3], + p: [ + { + name: 'year', + detail: 'The year component of the date.', + example: '1969', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'month', + detail: 'The month component of the date.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day', + detail: 'The day component of the date.', + example: '20', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATEVALUE', + t: 6, + d: 'Converts a provided date string in a known format to a date value.', + a: 'Converts a date string to a date value.', + m: [1, 1], + p: [ + { + name: 'date_string', + detail: 'The string representing the date.', + example: '"1969-7-20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DATEDIF', + t: 6, + d: 'Calculates the number of days, months, or years between two dates.', + a: 'Date Difference.', + m: [3, 3], + p: [ + { + name: 'start_date', + detail: 'The start date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: 'The end date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'unit', + detail: 'A string abbreviation for unit of time. For example, "M" for month. Accepted values are "Y","M","D","MD","YM","YD".', + example: '16)', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'WORKDAY', + t: 6, + d: 'Calculates the date after a number of working days from a specified start date.', + a: 'Number of working days from start date.', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: 'The date from which to begin counting.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'num_days', + detail: 'The number of working days to advance from `start_date`. If negative, counts backwards.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'holidays', + detail: 'A range or array constant containing the dates to consider holidays.', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'WORKDAY_INTL', + t: 6, + d: 'Calculates the date after a specified number of workdays excluding specified weekend days and holidays.', + a: 'Date after a number of workdays (specifying weekends).', + m: [2, 4], + p: [ + { + name: 'start_date', + detail: 'The date from which to begin counting.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'num_days', + detail: 'The number of working days to advance from `start_date`. If negative, counts backwards.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'weekend', + detail: 'A number or string representing which days of the week are considered weekends.', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'holidays', + detail: 'A range or array constant containing the dates to consider holidays.', + example: 'DATE(1969', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'YEAR', + t: 6, + d: 'Returns the year specified by a given date.', + a: 'Year specified by a given date.', + m: [1, 1], + p: [ + { + name: 'date', + detail: 'The date from which to extract the year.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'YEARFRAC', + t: 6, + d: 'Returns the number of years, including fractional years, between two dates using a specified day count convention.', + a: 'Exact number of years between two dates.', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: 'The start date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: 'The end date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TODAY', + t: 6, + d: 'Returns the current date as a date value.', + a: 'Current date as a date value.', + m: [0, 0], + p: [], + }, + { + n: 'MONTH', + t: 6, + d: 'Returns the month of the year a specific date falls in, in numeric format.', + a: 'Month of the year a specific date falls in.', + m: [1, 1], + p: [ + { + name: 'date', + detail: 'The date from which to extract the month.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EFFECT', + t: 8, + d: 'Calculates the annual effective interest rate given the nominal rate and number of compounding periods per year.', + a: 'Annual effective interest rate.', + m: [2, 2], + p: [ + { + name: 'nominal_rate', + detail: 'The nominal interest rate per year.', + example: '0.99', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'periods_per_year', + detail: 'The number of compounding periods per year.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DOLLAR', + t: 12, + d: 'Formats a number into the currency specific to your spreadsheet Locale.', + a: 'Formats a number as currency specific to your spreadsheet Locale.', + m: [1, 2], + p: [ + { + name: 'number', + detail: 'The value to be formatted.', + example: '1.2351', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_places', + detail: 'The number of decimal places to display.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DOLLARDE', + t: 8, + d: 'Converts a price quotation given as a decimal fraction into a decimal value.', + a: 'Converts a decimal fraction to decimal value.', + m: [2, 2], + p: [ + { + name: 'fractional_price', + detail: 'The price quotation given using fractional decimal conventions.', + example: '100.10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'unit', + detail: 'The units of the fraction, e.g. `8` for 1/8ths or `32` for 1/32nds.', + example: '32', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DOLLARFR', + t: 8, + d: 'Converts a price quotation given as a decimal value into a decimal fraction.', + a: 'Converts a decimal value to decimal fraction.', + m: [2, 2], + p: [ + { + name: 'decimal_price', + detail: 'The price quotation given as a decimal value.', + example: '100.125', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'unit', + detail: 'The units of the desired fraction, e.g. `8` for 1/8ths or `32` for 1/32nds.', + example: '32', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DB', + t: 8, + d: 'Calculates the depreciation of an asset for a specified period using the arithmetic declining balance method.', + a: 'Depreciation via declining balance method.', + m: [4, 5], + p: [ + { + name: 'cost', + detail: 'The initial cost of the asset.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: 'The value of the asset at the end of depreciation.', + example: '50', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: 'The number of periods over which the asset is depreciated.', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: 'The single period within `life` for which to calculate depreciation.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'month', + detail: 'The number of months in the first year of depreciation.', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DDB', + t: 8, + d: 'Calculates the depreciation of an asset for a specified period using the double-declining balance method.', + a: 'Depreciation via double-declining balance method.', + m: [4, 5], + p: [ + { + name: 'cost', + detail: 'The initial cost of the asset.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: 'The value of the asset at the end of depreciation.', + example: '50', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: 'The number of periods over which the asset is depreciated.', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: 'The single period within `life` for which to calculate depreciation.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'factor', + detail: 'The factor by which depreciation decreases.', + example: '2.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RATE', + t: 8, + d: 'Calculates the interest rate of an annuity investment based on constant-amount periodic payments and the assumption of a constant interest rate.', + a: 'Interest rate of an annuity investment.', + m: [3, 6], + p: [ + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'payment_per_period', + detail: 'The amount per period to be paid.', + example: '-100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: '400', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'future_value', + detail: 'The future value remaining after the final payment has been made.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'rate_guess', + detail: 'An estimate for what the interest rate will be.', + example: '0.1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CUMPRINC', + t: 8, + d: 'Calculates the cumulative principal paid over a range of payment periods for an investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Cumulative principal paid over a set of periods.', + m: [6, 6], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'first_period', + detail: 'The number of the payment period to begin the cumulative calculation.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'last_period', + detail: 'The number of the payment period to end the cumulative calculation.', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPNUM', + t: 8, + d: 'Calculates the number of coupons, or interest payments, between the settlement date and the maturity date of the investment.', + a: 'Number of coupons between settlement and maturity.', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: '02', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SYD', + t: 8, + d: 'Calculates the depreciation of an asset for a specified period using the sum of years digits method.', + a: 'Depreciation via sum of years digits method.', + m: [4, 4], + p: [ + { + name: 'cost', + detail: 'The initial cost of the asset.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: 'The value of the asset at the end of depreciation.', + example: '50', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: 'The number of periods over which the asset is depreciated.', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: 'The single period within `life` for which to calculate depreciation.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TBILLEQ', + t: 8, + d: 'Calculates the equivalent annualized rate of return of a US Treasury Bill based on discount rate.', + a: 'Equivalent rate of return for a Treasury bill.', + m: [3, 3], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'discount', + detail: 'The discount rate of the bill at time of purchase.', + example: '2)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TBILLYIELD', + t: 8, + d: 'Calculates the yield of a US Treasury Bill based on price.', + a: 'The yield of a us treasury bill based on price.', + m: [3, 3], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'price', + detail: 'The price at which the security is bought per 100 face value.', + example: '95', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TBILLPRICE', + t: 8, + d: 'Calculates the price of a US Treasury Bill based on discount rate.', + a: 'Price of US treasury bill.', + m: [3, 3], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'discount', + detail: 'The discount rate of the bill at time of purchase.', + example: '0.09', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PV', + t: 8, + d: 'Calculates the present value of an annuity investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Present value of an annuity investment.', + m: [3, 5], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'payment_amount', + detail: 'The amount per period to be paid.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'future_value', + detail: 'The future value remaining after the final payment has been made.', + example: 'D2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ACCRINT', + t: 8, + d: 'Calculates the accrued interest of a security that has periodic payments.', + a: 'Accrued interest of security with periodic payments.', + m: [6, 8], + p: [ + { + name: 'issue', + detail: 'The date the security was initially issued.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'first_payment', + detail: 'The first date interest will be paid.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: 'The annualized rate of interest.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: 'The redemption amount per 100 face value, or par.', + example: '10000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'calc_method', + detail: '[Optional-defaults to TRUE()] - A logical value that specifies the method used to calculate the total accrued interest when the settlement date is later than the first interest accrual date. \n\nIf the value is TRUE, the total accrued interest from the issue date to the settlement date is returned. \n\nIf the value is FALSE, return the accrued interest from the first interest accrual date to the settlement date.', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ACCRINTM', + t: 8, + d: 'Calculates the accrued interest of a security that pays interest at maturity.', + a: 'Accrued interest of security paying at maturity.', + m: [4, 5], + p: [ + { + name: 'issue', + detail: 'The date the security was initially issued.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity date of the security.', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: 'The annualized rate of interest.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: 'The redemption amount per 100 face value, or par.', + example: '1000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPDAYBS', + t: 8, + d: 'Calculates the number of days from the first coupon, or interest payment, until settlement.', + a: 'Number of days from first coupon to settlement.', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPDAYS', + t: 8, + d: 'Calculates the number of days in the coupon, or interest payment, period that contains the specified settlement date.', + a: 'Days in coupon period containing settlement date.', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPDAYSNC', + t: 8, + d: 'Calculates the number of days from the settlement date until the next coupon, or interest payment.', + a: 'Days from settlement until next coupon.', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPNCD', + t: 8, + d: 'Calculates next coupon, or interest payment, date after the settlement date.', + a: 'Next coupon date after the settlement date.', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '01)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: 'DATE(2019', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPPCD', + t: 8, + d: 'Calculates last coupon, or interest payment, date before the settlement date.', + a: 'Last coupon date before settlement date.', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '01)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: 'DATE(2019', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FV', + t: 8, + d: 'Calculates the future value of an annuity investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Future value of an annuity investment.', + m: [3, 5], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'payment_amount', + detail: 'The amount per period to be paid.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: '400', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FVSCHEDULE', + t: 8, + d: 'Calculates the future value of some principal based on a specified series of potentially varying interest rates.', + a: 'Future value of principal from series of rates.', + m: [2, 2], + p: [ + { + name: 'principal', + detail: 'The amount of initial capital or value to compound against.', + example: '10000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'rate_schedule', + detail: 'A series of interest rates to compound against the `principal`.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'YIELD', + t: 8, + d: 'Calculates the annual yield of a security paying periodic interest, such as a US Treasury Bond, based on price.', + a: 'Annual yield of a security paying periodic interest.', + m: [6, 7], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: 'The annualized rate of interest.', + example: '0.057', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'price', + detail: 'The price at which the security is bought per 100 face value.', + example: '95', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: 'The redemption amount per 100 face value, or par.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'YIELDDISC', + t: 8, + d: 'Calculates the annual yield of a discount (non-interest-bearing) security, based on price.', + a: 'Annual yield of a discount security.', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'price', + detail: 'The price at which the security is bought per 100 face value.', + example: '95', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: 'The redemption amount per 100 face value, or par.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NOMINAL', + t: 8, + d: 'Calculates the annual nominal interest rate given the effective rate and number of compounding periods per year.', + a: 'Annual nominal interest rate.', + m: [2, 2], + p: [ + { + name: 'effective_rate', + detail: 'The effective interest rate per year.', + example: '0.85', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'periods_per_year', + detail: 'The number of compounding periods per year.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'XIRR', + t: 8, + d: 'Calculates the internal rate of return of an investment based on a specified series of potentially irregularly spaced cash flows.', + a: 'Internal rate of return given non-periodic cashflows.', + m: [2, 3], + p: [ + { + name: 'cashflow_amounts', + detail: 'An array or range containing the income or payments associated with the investment.', + example: 'B2:B25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'cashflow_dates', + detail: 'An array or range with dates corresponding to the cash flows in `cashflow_amounts`.', + example: 'C2:C25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'rate_guess', + detail: 'An estimate for what the internal rate of return will be.', + example: '250', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MIRR', + t: 8, + d: 'Calculates the modified internal rate of return on an investment based on a series of periodic cash flows and the difference between the interest rate paid on financing versus the return received on reinvested income.', + a: 'Modified internal rate of return.', + m: [3, 3], + p: [ + { + name: 'cashflow_amounts', + detail: 'An array or range containing the income or payments associated with the investment.', + example: 'A2:A25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'financing_rate', + detail: 'The interest rate paid on funds invested.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'reinvestment_return_rate', + detail: 'The return (as a percentage) earned on reinvestment of income received from the investment.', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'IRR', + t: 8, + d: 'Calculates the internal rate of return on an investment based on a series of periodic cash flows.', + a: 'Internal rate of return given periodic cashflows.', + m: [1, 2], + p: [ + { + name: 'cashflow_amounts', + detail: 'An array or range containing the income or payments associated with the investment.', + example: 'A2:A25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'rate_guess', + detail: 'An estimate for what the internal rate of return will be.', + example: '200', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NPV', + t: 8, + d: 'Calculates the net present value of an investment based on a series of periodic cash flows and a discount rate.', + a: 'The net present value of an investment based on a series of periodic cash flows and a discount rate.', + m: [2, 255], + p: [ + { + name: 'discount', + detail: 'The discount rate of the investment over one period.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cashflow1', + detail: 'The first future cash flow.', + example: '200', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'cashflow2', + detail: 'Additional future cash flows.', + example: '250', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'XNPV', + t: 8, + d: 'Calculates the net present value of an investment based on a specified series of potentially irregularly spaced cash flows and a discount rate.', + a: 'Net present value given non-periodic cashflows.', + m: [3, 3], + p: [ + { + name: 'discount', + detail: 'The discount rate of the investment over one period.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cashflow_amounts', + detail: 'A range of cells containing the income or payments associated with the investment.', + example: 'B2:B25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'cashflow_dates', + detail: 'A range of cells with dates corresponding to the cash flows in `cashflow_amounts`.', + example: 'C2:C25', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'CUMIPMT', + t: 8, + d: 'Calculates the cumulative interest over a range of payment periods for an investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Cumulative interest paid over a set of periods.', + m: [6, 6], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'first_period', + detail: 'The number of the payment period to begin the cumulative calculation.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'last_period', + detail: 'The number of the payment period to end the cumulative calculation.', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PMT', + t: 8, + d: 'Calculates the periodic payment for an annuity investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Periodic payment for an annuity investment.', + m: [3, 5], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '0.08', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: ' 100000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'future_value', + detail: 'The future value remaining after the final payment has been made.', + example: 'D2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'IPMT', + t: 8, + d: 'Calculates the payment on interest for an investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Payment on interest for an investment.', + m: [4, 6], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: 'The amortization period, in terms of number of periods.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: '80000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'future_value', + detail: 'The future value remaining after the final payment has been made.', + example: 'E2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PPMT', + t: 8, + d: 'Calculates the payment on the principal of an investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Payment on the principal of an investment.', + m: [4, 6], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: 'The amortization period, in terms of number of periods.', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_periods', + detail: 'The number of payments to be made.', + example: '3*12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: '100000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'future_value', + detail: 'The future value remaining after the final payment has been made.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'INTRATE', + t: 8, + d: 'Calculates the effective interest rate generated when an investment is purchased at one price and sold at another with no interest or dividends generated by the investment itself.', + a: 'Calculates effective interest rate.', + m: [4, 5], + p: [ + { + name: 'buy_date', + detail: 'The date of purchase of the investment.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'sell_date', + detail: 'The date of sale of the investment.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'buy_price', + detail: 'The price at which the investment was purchased.', + example: '100000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'sell_price', + detail: 'The price at which the investment was sold.', + example: '101200', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PRICE', + t: 8, + d: 'Calculates the price of a security paying periodic interest, such as a US Treasury Bond, based on expected yield.', + a: 'Price of a security paying periodic interest.', + m: [6, 7], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: 'The annualized rate of interest.', + example: '0.057', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yield', + detail: 'The expected annual yield of the security.', + example: '0.065', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: 'The redemption amount per 100 face value, or par.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PRICEDISC', + t: 8, + d: 'Calculates the price of a discount (non-interest-bearing) security, based on expected yield.', + a: 'Price of a discount security.', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'discount', + detail: 'The discount rate of the security at time of purchase.', + example: '0.0525', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: 'The redemption amount per 100 face value, or par.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PRICEMAT', + t: 8, + d: 'Calculates the price of a security paying interest at maturity, based on expected yield.', + a: 'Price of security paying interest at maturity.', + m: [5, 6], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'issue', + detail: 'The date the security was initially issued.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: 'The annualized rate of interest.', + example: '0.061', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yield', + detail: 'The expected annual yield of the security.', + example: '0.061', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RECEIVED', + t: 8, + d: 'Calculates the amount received at maturity for an investment in fixed-income securities purchased on a given date.', + a: 'Amount received at maturity for a security.', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'investment', + detail: 'The amount invested (irrespective of face value of each security).', + example: '10000000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'discount', + detail: 'The discount rate of the security invested in.', + example: '0.0575', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '12', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DISC', + t: 8, + d: 'Calculates the discount rate of a security based on price.', + a: 'The discount rate of a security based on price.', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'price', + detail: 'The price at which the security is bought per 100 face value.', + example: '97.975', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: 'The redemption amount per 100 face value, or par.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '12', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NPER', + t: 8, + d: 'Calculates the number of payment periods for an investment based on constant-amount periodic payments and a constant interest rate.', + a: 'Number of payment periods for an investment.', + m: [3, 5], + p: [ + { + name: 'rate', + detail: 'The interest rate.', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'payment_amount', + detail: 'The amount of each payment made.', + example: '500', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'present_value', + detail: 'The current value of the annuity.', + example: '40000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'future_value', + detail: 'The future value remaining after the final payment has been made.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_or_beginning', + detail: 'Whether payments are due at the end (`0`) or beginning (`1`) of each period.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SLN', + t: 8, + d: 'Calculates the depreciation of an asset for one period using the straight-line method.', + a: 'Depreciation of asset using the straight-line method.', + m: [3, 3], + p: [ + { + name: 'cost', + detail: 'The initial cost of the asset.', + example: '300000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: 'The value of the asset at the end of depreciation.', + example: '75000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: 'The number of periods over which the asset is depreciated.', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DURATION', + t: 8, + d: 'Calculates the number of compounding periods required for an investment of a specified present value appreciating at a given rate to reach a target value.', + a: 'Number of periods for an investment to reach a value.', + m: [5, 6], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: 'The annualized rate of interest.', + example: '0.08', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yield', + detail: 'The expected annual yield of the security.', + example: '0.09', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MDURATION', + t: 8, + d: 'Calculates the modified Macaulay duration of a security paying periodic interest, such as a US Treasury Bond, based on expected yield.', + a: 'Modified Macaulay duration.', + m: [5, 6], + p: [ + { + name: 'settlement', + detail: 'The settlement date of the security, the date after issuance when the security is delivered to the buyer.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: 'The maturity or end date of the security, when it can be redeemed at face, or par value.', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: 'The annualized rate of interest.', + example: '0.08', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yield', + detail: 'The expected annual yield of the security.', + example: '0.09', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: 'The number of interest or coupon payments per year (1, 2, or 4).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day_count_convention', + detail: 'An indicator of what day count method to use.', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'BIN2DEC', + t: 9, + d: 'Converts a signed binary number to decimal format.', + a: 'Converts a signed binary number to decimal format.', + m: [1, 1], + p: [ + { + name: 'signed_binary_number', + detail: 'The signed 10-bit binary value to be converted to decimal, provided as a string.', + example: '101', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'BIN2HEX', + t: 9, + d: 'Converts a signed binary number to signed hexadecimal format.', + a: 'Converts a binary number to hexadecimal.', + m: [1, 2], + p: [ + { + name: 'signed_binary_number', + detail: 'The signed 10-bit binary value to be converted to signed hexademical, provided as a string.', + example: '101', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'BIN2OCT', + t: 9, + d: 'Converts a signed binary number to signed octal format.', + a: 'Converts a binary number to octal.', + m: [1, 2], + p: [ + { + name: 'signed_binary_number', + detail: 'The signed 10-bit binary value to be converted to signed octal, provided as a string.', + example: '101', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEC2BIN', + t: 9, + d: 'Converts a decimal number to signed binary format.', + a: 'Converts a decimal number to signed binary format.', + m: [1, 2], + p: [ + { + name: 'decimal_number', + detail: 'The decimal value to be converted to signed binary, provided as a string.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEC2HEX', + t: 9, + d: 'Converts a decimal number to signed hexadecimal format.', + a: 'Converts a decimal number to hexadecimal.', + m: [1, 2], + p: [ + { + name: 'decimal_number', + detail: 'The decimal value to be converted to signed hexadecimal, provided as a string.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEC2OCT', + t: 9, + d: 'Converts a decimal number to signed octal format.', + a: 'Converts a decimal number to signed octal format.', + m: [1, 2], + p: [ + { + name: 'decimal_number', + detail: 'The decimal value to be converted to signed octal, provided as a string.', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HEX2BIN', + t: 9, + d: 'Converts a signed hexadecimal number to signed binary format.', + a: 'Converts a hexadecimal number to binary.', + m: [1, 2], + p: [ + { + name: 'signed_hexadecimal_number', + detail: 'The signed 40-bit hexadecimal value to be converted to signed binary, provided as a string.', + example: '"f3"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HEX2DEC', + t: 9, + d: 'Converts a signed hexadecimal number to decimal format.', + a: 'Converts a hexadecimal number to decimal.', + m: [1, 1], + p: [ + { + name: 'signed_hexadecimal_number', + detail: 'The signed 40-bit hexadecimal value to be converted to decimal, provided as a string.', + example: '"f3"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'HEX2OCT', + t: 9, + d: 'Converts a signed hexadecimal number to signed octal format.', + a: 'Converts a hexadecimal number to octal.', + m: [1, 2], + p: [ + { + name: 'signed_hexadecimal_number', + detail: 'The signed 40-bit hexadecimal value to be converted to signed octal, provided as a string.', + example: '"f3"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'OCT2BIN', + t: 9, + d: 'Converts a signed octal number to signed binary format.', + a: 'Converts an octal number to binary.', + m: [1, 2], + p: [ + { + name: 'signed_octal_number', + detail: 'The signed 30-bit octal value to be converted to signed binary, provided as a string.', + example: '37', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'OCT2DEC', + t: 9, + d: 'Converts a signed octal number to decimal format.', + a: 'Converts a signed octal number to decimal format.', + m: [1, 1], + p: [ + { + name: 'signed_octal_number', + detail: 'The signed 30-bit octal value to be converted to decimal, provided as a string.', + example: '37', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'OCT2HEX', + t: 9, + d: 'Converts a signed octal number to signed hexadecimal format.', + a: 'Converts an octal number to hexadecimal.', + m: [1, 2], + p: [ + { + name: 'signed_octal_number', + detail: 'The signed 30-bit octal value to be converted to signed hexadecimal, provided as a string.', + example: '37', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'significant_digits', + detail: 'The number of significant digits to ensure in the result.', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COMPLEX', + t: 9, + d: 'Creates a complex number given real and imaginary coefficients.', + a: 'Creates a complex number.', + m: [2, 3], + p: [ + { + name: 'real_part', + detail: 'The real coefficient.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'imaginary_part', + detail: 'The imaginary coefficient.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'suffix', + detail: "The suffix for the imaginary coefficient, can only be 'i' or 'j'. If omitted, 'i' will be used.", + example: '"j"', + require: 'o', + repeat: 'n', + type: 'rangestring', + }, + ], + }, + { + n: 'IMREAL', + t: 9, + d: 'Returns the real coefficient of a complex number.', + a: 'The real coefficient of a complex number.', + m: [1, 1], + p: [ + { + name: 'complex_number', + detail: 'The complex number, in the a+bi or a+bj format.', + example: '"4+5i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMAGINARY', + t: 9, + d: 'Returns the imaginary coefficient of a complex number.', + a: 'The imaginary coefficient of a complex number.', + m: [1, 1], + p: [ + { + name: 'complex_number', + detail: 'The complex number, in the a+bi or a+bj format.', + example: '"4+5i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMCONJUGATE', + t: 9, + d: 'Returns the complex conjugate of a number.', + a: 'The complex conjugate of a number.', + m: [1, 1], + p: [ + { + name: 'number', + detail: 'The complex number to calculate the conjugate for.', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMABS', + t: 9, + d: 'Returns absolute value (or modulus) of a complex number.', + a: 'The absolute value of a complex number.', + m: [1, 1], + p: [ + { + name: 'number', + detail: 'The complex number to calculate the absolute value of.', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DELTA', + t: 9, + d: "Compare two numeric values, returning 1 if they're equal.", + a: 'Compare two numeric values.', + m: [1, 2], + p: [ + { + name: 'number1', + detail: 'The first number to compare.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number2', + detail: 'The second number to compare.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'IMSUM', + t: 9, + d: 'Returns the sum of a series of complex numbers.', + a: 'Sum of a series of complex numbers.', + m: [1, 255], + p: [ + { + name: 'value1', + detail: 'The first complex number or range to add together.', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'Additional complex numbers or ranges to add to `value1`.', + example: '"5-3i"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMSUB', + t: 9, + d: 'Returns the difference between two complex numbers.', + a: 'The difference between two complex numbers.', + m: [2, 2], + p: [ + { + name: 'first_number', + detail: 'The complex number to subtract second_number from.', + example: '"6+5i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'second_number', + detail: 'The complex number to subtract from first_number.', + example: '"2+3i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMPRODUCT', + t: 9, + d: 'Returns the result of multiplying a series of complex numbers together.', + a: 'Result of multiplying a series of complex numbers together.', + m: [1, 255], + p: [ + { + name: 'factor1', + detail: 'The first number or range to calculate for the product.', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'factor2', + detail: 'Additional complex numbers or ranges to calculate for the product.', + example: '"5-3i"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMDIV', + t: 9, + d: 'Returns one complex number divided by another.', + a: 'One complex number divided by another.', + m: [2, 2], + p: [ + { + name: 'dividend', + detail: 'The complex number to be divided.', + example: '"11+16i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'divisor', + detail: 'The complex number to divide by.', + example: '"3+2i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NOT', + t: 10, + d: 'Returns the opposite of a logical value - `NOT(TRUE)` returns `FALSE`; `NOT(FALSE)` returns `TRUE`.', + a: 'Returns opposite of provided logical value.', + m: [1, 1], + p: [ + { + name: 'logical_expression', + detail: 'An expression or reference to a cell holding an expression that represents some logical value.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TRUE', + t: 10, + d: 'Returns the logical value `TRUE`.', + a: 'Logical value `true`.', + m: [0, 0], + p: [], + }, + { + n: 'FALSE', + t: 10, + d: 'Returns the logical value `FALSE`.', + a: 'Logical value `false`.', + m: [0, 0], + p: [], + }, + { + n: 'AND', + t: 10, + d: 'Returns true if all of the provided arguments are logically true, and false if any of the provided arguments are logically false.', + a: 'Logical `and` operator.', + m: [1, 255], + p: [ + { + name: 'logical_expression1', + detail: 'An expression or reference to a cell containing an expression that represents some logical value, i.e. `TRUE` or `FALSE`, or an expression that can be coerced to a logical value.', + example: 'A2 = "foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'logical_expression2', + detail: 'More expressions that represent logical values.', + example: 'A3 = "bar"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'IFERROR', + t: 10, + d: 'Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent.', + a: 'Value if it is not an error, otherwise 2nd argument.', + m: [2, 2], + p: [ + { + name: 'value', + detail: 'The value to return if `value` itself is not an error.', + example: 'A1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_if_error', + detail: 'The value the function returns if `value` is an error.', + example: '"Error in cell A1"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IF', + t: 10, + d: 'Returns one value if a logical expression is `TRUE` and another if it is `FALSE`.', + a: 'Returns value depending on logical expression.', + m: [2, 3], + p: [ + { + name: 'logical_expression', + detail: 'An expression or reference to a cell containing an expression that represents some logical value, i.e. `TRUE` or `FALSE`.', + example: 'A2 = "foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_if_true', + detail: 'The value the function returns if `logical_expression` is `TRUE`.', + example: '"A2 is foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_if_false', + detail: 'The value the function returns if `logical_expression` is `FALSE`.', + example: '"A2 was false"', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'OR', + t: 10, + d: 'Returns true if any of the provided arguments are logically true, and false if all of the provided arguments are logically false.', + a: 'Logical `or` operator.', + m: [1, 255], + p: [ + { + name: 'logical_expression1', + detail: 'An expression or reference to a cell containing an expression that represents some logical value, i.e. `TRUE` or `FALSE`, or an expression that can be coerced to a logical value.', + example: 'A2 = "foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'logical_expression2', + detail: 'More expressions that evaluate to logical values.', + example: ' A3 = "bar"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NE', + t: 11, + d: 'Returns `TRUE` if two specified values are not equal and `FALSE` otherwise. Equivalent to the `!=` operator.', + a: 'Not equal.', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The first value.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'The value to test against `value1` for inequality.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EQ', + t: 11, + d: 'Returns `TRUE` if two specified values are equal and `FALSE` otherwise. Equivalent to the `==` operator.', + a: 'Equal.', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The first value.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'The value to test against `value1` for equality.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'GT', + t: 11, + d: 'Returns `TRUE` if the first argument is strictly greater than the second, and `FALSE` otherwise. Equivalent to the `>` operator.', + a: 'Strictly greater than.', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The value to test as being greater than `value2`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'The second value.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'GTE', + t: 11, + d: 'Returns `TRUE` if the first argument is greater than or equal to the second, and `FALSE` otherwise. Equivalent to the `>=` operator.', + a: 'Greater than or equal to.', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The value to test as being greater than or equal to `value2`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'The second value.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LT', + t: 11, + d: 'Returns `TRUE` if the first argument is strictly less than the second, and `FALSE` otherwise. Equivalent to the `<` operator.', + a: 'Less than.', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The value to test as being less than `value2`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'The second value.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LTE', + t: 11, + d: 'Returns `TRUE` if the first argument is less than or equal to the second, and `FALSE` otherwise. Equivalent to the `<=` operator.', + a: 'Less than or equal to.', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The value to test as being less than or equal to `value2`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'The second value.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ADD', + t: 11, + d: 'Returns the sum of two numbers. Equivalent to the `+` operator.', + a: 'Sum of two numbers', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The first addend.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'The second addend.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MINUS', + t: 11, + d: 'Returns the difference of two numbers. Equivalent to the `-` operator.', + a: 'Difference of two numbers', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The minuend, or number to be subtracted from.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: 'The subtrahend, or number to subtract from `value1`.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MULTIPLY', + t: 11, + d: 'Returns the product of two numbers. Equivalent to the `*` operator.', + a: 'Product of two numbers', + m: [2, 2], + p: [ + { + name: 'factor1', + detail: 'The first multiplicand.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'factor2', + detail: 'The second multiplicand.', + example: 'B2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DIVIDE', + t: 11, + d: 'Returns one number divided by another. Equivalent to the `/` operator.', + a: 'One number divided by another', + m: [2, 2], + p: [ + { + name: 'dividend', + detail: 'The number to be divided.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'divisor', + detail: 'The number to divide by.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CONCAT', + t: 11, + d: 'Returns the concatenation of two values. Equivalent to the `&` operator.', + a: 'Concatenation of two values', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'The value to which `value2` will be appended.', + example: '"de"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: 'The value to append to `value1`.', + example: '"mystify"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'UNARY_PERCENT', + t: 11, + d: 'Returns a value interpreted as a percentage; that is, `UNARY_PERCENT(100)` equals `1`.', + a: 'Value interpreted as a percentage.', + m: [1, 1], + p: [ + { + name: 'percentage', + detail: 'The value to interpret as a percentage.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CONCATENATE', + t: 12, + d: 'Appends strings to one another.', + a: 'Appends strings to one another.', + m: [1, 255], + p: [ + { + name: 'string1', + detail: 'The initial string.', + example: '"Super"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'string2', + detail: 'More strings to append in sequence.', + example: '"calla"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'CODE', + t: 12, + d: 'Returns the numeric Unicode map value of the first character in the string provided.', + a: 'Numeric unicode map value of character.', + m: [1, 1], + p: [ + { + name: 'string', + detail: "The string whose first character's Unicode map value will be returned.", + example: '"a"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'CHAR', + t: 12, + d: 'Convert a number into a character according to the current Unicode table.', + a: 'Gets character associated with number.', + m: [1, 1], + p: [ + { + name: 'table_number', + detail: 'The number of the character to look up from the current Unicode table in decimal format.', + example: '97', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ARABIC', + t: 12, + d: 'Computes the value of a Roman numeral.', + a: 'Computes the value of a roman numeral.', + m: [1, 1], + p: [ + { + name: 'roman_numeral', + detail: 'The Roman numeral to format, whose value must be between 1 and 3999, inclusive.', + example: '"XIV"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ROMAN', + t: 12, + d: 'Formats a number in Roman numerals.', + a: 'Formats a number in Roman numerals.', + m: [1, 1], + p: [ + { + name: 'number', + detail: 'The number to format, between 1 and 3999, inclusive.', + example: '499', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'REGEXEXTRACT', + t: 12, + d: 'Extracts matching substrings according to a regular expression.', + a: 'Extracts matching substrings with regular expression.', + m: [2, 2], + p: [ + { + name: 'text', + detail: 'The input text.', + example: '"Needle in a haystack"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'regular_expression', + detail: 'The first part of `text` that matches this expression will be returned.', + example: '".e{2}dle"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REGEXMATCH', + t: 12, + d: 'Whether a piece of text matches a regular expression.', + a: 'Whether a piece of text matches regular expression.', + m: [2, 2], + p: [ + { + name: 'text', + detail: 'The text to be tested against the regular expression.', + example: '"Spreadsheets"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'regular_expression', + detail: 'The regular expression to test the text against.', + example: '"S.r"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REGEXREPLACE', + t: 12, + d: 'Replaces part of a text string with a different text string using regular expressions.', + a: 'Replaces text with regular expressions.', + m: [3, 3], + p: [ + { + name: 'text', + detail: 'The text, a part of which will be replaced.', + example: '"Spreadsheets"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'regular_expression', + detail: 'The regular expression. All matching instances in `text` will be replaced.', + example: '"S.*d"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'replacement', + detail: 'The text which will be inserted into the original text.', + example: '"Bed"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'T', + t: 12, + d: 'Returns string arguments as text, or the empty string if the value is not text.', + a: 'String arguments as text.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The argument to be converted to text.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FIXED', + t: 12, + d: 'Formats a number with a fixed number of decimal places.', + a: 'Formats number with fixed number of decimal places.', + m: [1, 3], + p: [ + { + name: 'number', + detail: 'The number to format.', + example: '3.141592653', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_of_places', + detail: 'The number of decimal places to display in the result.', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'suppress_separator', + detail: 'Whether or not to suppress the thousands separator used in some locales (e.g. `1,000` becomes `1000`). Separators will be present if this value is 0 or omitted, and absent otherwise.', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FIND', + t: 12, + d: 'Returns the position at which a string is first found within text where the capitalization of letters matters. Returns `#VALUE!` if the string is not found.', + a: 'First position of string found in text, case-sensitive.', + m: [2, 3], + p: [ + { + name: 'search_for', + detail: 'The string to look for within `text_to_search`.', + example: '"n"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'text_to_search', + detail: 'The text to search for the first occurrence of `search_for`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'starting_at', + detail: 'The character within `text_to_search` at which to start the search.', + example: '14', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FINDB', + t: 12, + d: 'Returns the position at which a string is first found within text counting each double-character as 2.', + a: 'Position at which a string is first found within text (binary).', + m: [2, 3], + p: [ + { + name: 'search_for', + detail: 'The string to look for within `text_to_search`.', + example: '"new"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'text_to_search', + detail: 'The text to search for the first occurrence of `search_for`.', + example: '"new year"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'starting_at', + detail: 'The character within `text_to_search` at which to start the search.', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'JOIN', + t: 12, + d: 'Concatenates the elements of one or more one-dimensional arrays using a specified delimiter.', + a: 'Concatenates elements of arrays with delimiter.', + m: [2, 255], + p: [ + { + name: 'delimiter', + detail: 'The character or string to place between each concatenated value.', + example: '" and-a "', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_or_array1', + detail: 'The value or values to be appended using `delimiter`.', + example: '{1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_or_array2', + detail: 'More values to be appended using `delimiter`.', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'LEFT', + t: 12, + d: 'Returns a substring from the beginning of a specified string.', + a: 'Substring from beginning of specified string.', + m: [1, 2], + p: [ + { + name: 'string', + detail: 'The string from which the left portion will be returned.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'number_of_characters', + detail: 'The number of characters to return from the left side of `string`.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RIGHT', + t: 12, + d: 'Returns a substring from the end of a specified string.', + a: 'A substring from the end of a specified string.', + m: [1, 2], + p: [ + { + name: 'string', + detail: 'The string from which the right portion will be returned.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'number_of_characters', + detail: 'The number of characters to return from the right side of `string`.', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MID', + t: 12, + d: 'Returns a segment of a string.', + a: 'A segment of a string.', + m: [3, 3], + p: [ + { + name: 'string', + detail: 'The string to extract a segment from.', + example: '"get this"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'starting_at', + detail: 'The index from the left of `string` from which to begin extracting. The first character in `string` has the index 1.', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'extract_length', + detail: 'The length of the segment to extract.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LEN', + t: 12, + d: 'Returns the length of a string.', + a: 'Length of a string.', + m: [1, 1], + p: [ + { + name: 'text', + detail: 'The string whose length will be returned.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LENB', + t: 12, + d: 'Returns the length of a string in bytes.', + a: 'Length of a string in bytes.', + m: [1, 1], + p: [ + { + name: 'text', + detail: 'The string whose length will be returned.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LOWER', + t: 12, + d: 'Converts a specified string to lowercase.', + a: 'Converts a specified string to lowercase.', + m: [1, 1], + p: [ + { + name: 'text', + detail: 'The string to convert to lowercase.', + example: '"LOREM IPSUM"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'UPPER', + t: 12, + d: 'Converts a specified string to uppercase.', + a: 'Converts a specified string to uppercase.', + m: [1, 1], + p: [ + { + name: 'text', + detail: 'The string to convert to uppercase.', + example: '"lorem ipsum"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EXACT', + t: 12, + d: 'Tests whether two strings are identical.', + a: 'Tests whether two strings are identical.', + m: [2, 2], + p: [ + { + name: 'string1', + detail: 'The first string to compare', + example: 'A1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'string2', + detail: 'The second string to compare', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REPLACE', + t: 12, + d: 'Replaces part of a text string with a different text string.', + a: 'Replaces part of a text string with different text.', + m: [4, 4], + p: [ + { + name: 'text', + detail: 'The text, a part of which will be replaced.', + example: '"Spreadsheets"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'position', + detail: 'The position where the replacement will begin (starting from 1).', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'length', + detail: 'The number of characters in the text to be replaced.', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'new_text', + detail: 'The text which will be inserted into the original text.', + example: '"Bed"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REPT', + t: 12, + d: 'Returns specified text repeated a number of times.', + a: 'Specified text repeated a number of times.', + m: [2, 2], + p: [ + { + name: 'text_to_repeat', + detail: 'The character or string to repeat.', + example: '"ha"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'number_of_repetitions', + detail: 'The number of times `text_to_repeat` should appear in the value returned.', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SEARCH', + t: 12, + d: 'Returns the position at which a string is first found within text and ignores capitalization of letters. Returns `#VALUE!` if the string is not found.', + a: 'First position of string found in text, ignoring case.', + m: [2, 3], + p: [ + { + name: 'search_for', + detail: 'The string to look for within `text_to_search`.', + example: '"n"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'text_to_search', + detail: 'The text to search for the first occurrence of `search_for`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'starting_at', + detail: 'The character within `text_to_search` at which to start the search.', + example: '14', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUBSTITUTE', + t: 12, + d: 'Replaces existing text with new text in a string.', + a: 'Replaces existing text with new text in a string.', + m: [3, 4], + p: [ + { + name: 'text_to_search', + detail: 'The text within which to search and replace.', + example: '"search for it"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'search_for', + detail: 'The string to search for within `text_to_search`.', + example: '"search for"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'replace_with', + detail: 'The string that will replace `search_for`.', + example: '"Google"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'occurrence_number', + detail: 'The instance of `search_for` within `text_to_search` to replace with `replace_with`. By default, all occurrences of `search_for` are replaced; however, if `occurrence_number` is specified, only the indicated instance of `search_for` is replaced.', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CLEAN', + t: 12, + d: 'Returns the text with the non-printable ASCII characters removed.', + a: 'Removes non-printable characters from a piece of text.', + m: [1, 1], + p: [ + { + name: 'text', + detail: 'The text whose non-printable characters are to be removed.', + example: '"AF"&CHAR(31)', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TEXT', + t: 12, + d: 'Converts a number into text according to a specified format.', + a: 'Formats a number into text.', + m: [2, 2], + p: [ + { + name: 'number', + detail: 'The number, date, or time to format.', + example: '1.23', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'format', + detail: 'The pattern by which to format the number, enclosed in quotation marks.', + example: '"$0.00"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TRIM', + t: 12, + d: 'Removes leading, trailing, and repeated spaces in text.', + a: 'Removes space characters.', + m: [1, 1], + p: [ + { + name: 'text', + detail: 'The text or reference to a cell containing text to be trimmed.', + example: '" lorem ipsum"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'VALUE', + t: 12, + d: 'Converts a string in any of the date, time or number formats that Google Sheets understands into a number.', + a: 'Converts a date/time/number string into a number.', + m: [1, 1], + p: [ + { + name: 'text', + detail: 'The string containing the value to be converted.', + example: '"123"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'PROPER', + t: 12, + d: 'Capitalizes each word in a specified string.', + a: 'Capitalizes each word in a specified string.', + m: [1, 1], + p: [ + { + name: 'text_to_capitalize', + detail: 'The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase.', + example: '"united states"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'CONVERT', + t: 13, + d: 'Converts a numeric value to a different unit of measure.', + a: 'Unit conversion for numbers.', + m: [3, 3], + p: [ + { + name: 'value', + detail: 'The numeric value in `start_unit` to convert to `end_unit`.', + example: '5.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'start_unit', + detail: 'The starting unit, the unit currently assigned to `value`.', + example: '"g"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_unit', + detail: 'The unit of measure into which to convert the argument, `value`.', + example: '"kg"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'SUMX2MY2', + t: 14, + d: 'Calculates the sum of the differences of the squares of values in two arrays.', + a: 'Sum of the differences of squares.', + m: [2, 2], + p: [ + { + name: 'array_x', + detail: 'The array or range of values whose squares will be reduced by the squares of corresponding entries in `array_y` and added together.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array_y', + detail: 'The array or range of values whose squares will be subtracted from the squares of corresponding entries in `array_x` and added together.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMX2PY2', + t: 14, + d: 'Calculates the sum of the sums of the squares of values in two arrays.', + a: 'Sum of the sums of squares.', + m: [2, 2], + p: [ + { + name: 'array_x', + detail: 'The array or range of values whose squares will be added to the squares of corresponding entries in `array_y` and added together.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array_y', + detail: 'The array or range of values whose squares will be added to the squares of corresponding entries in `array_x` and added together.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMXMY2', + t: 14, + d: 'Calculates the sum of the squares of differences of values in two arrays.', + a: 'Sum of the squares of differences.', + m: [2, 2], + p: [ + { + name: 'array_x', + detail: 'The array or range of values that will be reduced by corresponding entries in `array_y`, squared, and added together.', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array_y', + detail: 'The array or range of values that will be subtracted from corresponding entries in `array_x`, the result squared, and all such results added together.', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TRANSPOSE', + t: 14, + d: 'Transposes the rows and columns of an array or range of cells.', + a: 'Transposes the rows and columns of an array.', + m: [1, 1], + p: [ + { + name: 'array_or_range', + detail: 'The array or range whose rows and columns will be swapped.', + example: '{1,2}', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'TREND', + t: 14, + d: 'Given partial data about a linear trend, fits an ideal linear trend using the least squares method and/or predicts further values.', + a: 'Fits points to linear trend derived via least-squares.', + m: [1, 4], + p: [ + { + name: 'known_data_y', + detail: 'The array or range containing dependent (y) values that are already known, used to curve fit an ideal linear trend.', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_data_x', + detail: 'The values of the independent variable(s) corresponding with `known_data_y`.', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'new_data_x', + detail: 'The data points to return the `y` values for on the ideal curve fit.', + example: 'A11:A13', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'b', + detail: 'Given a general linear form of `y = m*x+b` for a curve fit, calculates `b` if `TRUE` or forces `b` to be `0` and only calculates the `m` values if `FALSE`, i.e. forces the curve fit to pass through the origin.', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FREQUENCY', + t: 14, + d: 'Calculates the frequency distribution of a one-column array into specified classes.', + a: 'The frequency distribution of array.', + m: [2, 2], + p: [ + { + name: 'data', + detail: 'The array or range containing the values to be counted.', + example: 'A2:A40', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'classes', + detail: 'The array or range containing the set of classes.', + example: 'B2:B5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GROWTH', + t: 14, + d: 'Given partial data about an exponential growth trend, fits an ideal exponential growth trend and/or predicts further values.', + a: 'Fits points to exponential growth trend.', + m: [1, 4], + p: [ + { + name: 'known_data_y', + detail: 'The array or range containing dependent (y) values that are already known, used to curve fit an ideal exponential growth curve.', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_data_x', + detail: 'The values of the independent variable(s) corresponding with `known_data_y`.', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'new_data_x', + detail: 'The data points to return the `y` values for on the ideal curve fit.', + example: 'A11:A13', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'b', + detail: 'Given a general exponential form of `y = b*m^x` for a curve fit, calculates `b` if `TRUE` or forces `b` to be `1` and only calculates the `m` values if `FALSE`.', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LINEST', + t: 14, + d: 'Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method.', + a: 'Best-fit linear trend via least-squares.', + m: [1, 4], + p: [ + { + name: 'known_data_y', + detail: 'The array or range containing dependent (y) values that are already known, used to curve fit an ideal linear trend.', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_data_x', + detail: 'The values of the independent variable(s) corresponding with `known_data_y`.', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'calculate_b', + detail: 'Given a linear form of `y = m*x+b`, calculates the y-intercept (`b`) if `TRUE`. Otherwise, forces `b` to be `0` and only calculates the `m` values if `FALSE`, i.e. forces the curve fit to pass through the origin.', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'verbose', + detail: 'A flag specifying whether to return additional regression statistics or only the linear coefficients and the y-intercept (default).', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LOGEST', + t: 14, + d: 'Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve.', + a: 'Best-fit exponential growth curve.', + m: [1, 4], + p: [ + { + name: 'known_data_y', + detail: 'The array or range containing dependent (y) values that are already known, used to curve fit an ideal exponential growth curve.', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_data_x', + detail: 'The values of the independent variable(s) corresponding with `known_data_y`.', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'b', + detail: 'Given a general exponential form of `y = b*m^x` for a curve fit, calculates `b` if `TRUE` or forces `b` to be `1` and only calculates the `m` values if `FALSE`.', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'verbose', + detail: 'A flag specifying whether to return additional regression statistics or only the calculated coefficient and exponents.', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'MDETERM', + t: 14, + d: 'Returns the matrix determinant of a square matrix specified as an array or range.', + a: 'Matrix determinant of a square matrix.', + m: [1, 1], + p: [ + { + name: 'square_matrix', + detail: 'An array or range with an equal number of rows and columns representing a matrix whose determinant will be calculated.', + example: 'A1:D4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MINVERSE', + t: 14, + d: 'Returns the multiplicative inverse of a square matrix specified as an array or range.', + a: 'Multiplicative inverse of square matrix.', + m: [1, 1], + p: [ + { + name: 'square_matrix', + detail: 'An array or range with an equal number of rows and columns representing a matrix whose multiplicative inverse will be calculated.', + example: 'A1:D4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MMULT', + t: 14, + d: 'Calculates the matrix product of two matrices specified as arrays or ranges.', + a: 'The matrix product of two matrices.', + m: [2, 2], + p: [ + { + name: 'matrix1', + detail: 'The first matrix in the matrix multiplication operation, represented as an array or range.', + example: 'A1:B3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'matrix2', + detail: 'The second matrix in the matrix multiplication operation, represented as an array or range.', + example: 'C1:F2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMPRODUCT', + t: 14, + d: 'Calculates the sum of the products of corresponding entries in two equal-sized arrays or ranges.', + a: 'Sum of products of elements in two arrays.', + m: [1, 255], + p: [ + { + name: 'array1', + detail: 'The first array or range whose entries will be multiplied with corresponding entries in the second such array or range.', + example: 'A2:C5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array2', + detail: 'The second array or range whose entries will be multiplied with corresponding entries in the first such array or range.', + example: 'D2:F5', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ISFORMULA', + t: 15, + d: 'Checks whether a value is a formula.', + a: 'Whether a value is a formula.', + m: [1, 1], + p: [ + { + name: 'cell', + detail: 'The cell to be verified as containing a formula.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'CELL', + t: 15, + d: 'Returns the requested information about the specified cell.', + a: 'Gets information about a cell.', + m: [2, 2], + p: [ + { + name: 'info_type', + detail: 'The type of information requested (see article for available types)', + example: '"type"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'reference', + detail: 'The reference to the cell.', + example: 'C2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'NA', + t: 15, + d: 'Returns the "value not available" error, `#N/A`.', + a: 'The `#N/A` error.', + m: [0, 0], + p: [], + }, + { + n: 'ERROR_TYPE', + t: 15, + d: 'Returns a number corresponding to the error value in a different cell.', + a: 'Error value of cell (as number).', + m: [1, 1], + p: [ + { + name: 'reference', + detail: 'The cell to find the error number for although you can also provide the error value directly.', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISBLANK', + t: 15, + d: 'Checks whether the referenced cell is empty.', + a: 'Whether the referenced cell is empty.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'Reference to the cell that will be checked for emptiness.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'ISERR', + t: 15, + d: 'Checks whether a value is an error other than `#N/A`.', + a: 'Whether a value is an error other than `#n/a`.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as an error type other than `#N/A`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISERROR', + t: 15, + d: 'Checks whether a value is an error.', + a: 'Whether a value is an error.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as an error type.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISLOGICAL', + t: 15, + d: 'Checks whether a value is `TRUE` or `FALSE`.', + a: 'Whether a value is `true` or `false`.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as a logical `TRUE` or `FALSE`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISNA', + t: 15, + d: 'Checks whether a value is the error `#N/A`.', + a: 'Whether a value is the error `#n/a`.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be compared with the error value `#N/A`.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISNONTEXT', + t: 15, + d: 'Checks whether a value is non-textual.', + a: 'Whether a value is non-textual.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be checked.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISNUMBER', + t: 15, + d: 'Checks whether a value is a number.', + a: 'Whether a value is a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as a number.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISREF', + t: 15, + d: 'Checks whether a value is a valid cell reference.', + a: 'Whether a value is a valid cell reference.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as a cell reference.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'ISTEXT', + t: 15, + d: 'Checks whether a value is text.', + a: 'Whether a value is text.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value to be verified as text.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TYPE', + t: 15, + d: 'Returns a number associated with the type of data passed into the function.', + a: 'Get the type of a value.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The value whose type is to be determined.', + example: 'C4', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'N', + t: 15, + d: 'Returns the argument provided as a number. Text is converted to 0 and errors are returned as-is.', + a: 'Argument provided as a number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The argument to be converted to a number.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TO_DATE', + t: 16, + d: 'Converts a provided number to a date.', + a: 'Converts a provided number to a date.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The argument or reference to a cell to be converted to a date.', + example: '25405', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TO_PURE_NUMBER', + t: 16, + d: 'Converts a provided date/time, percentage, currency or other formatted numeric value to a pure number without formatting.', + a: 'Converts any numeric value to a pure number.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The argument or reference to a cell to be converted to a pure number.', + example: '50%', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TO_TEXT', + t: 16, + d: 'Converts a provided numeric value to a text value.', + a: 'Converts a provided numeric value to a text value.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The argument or reference to a cell to be converted to text.', + example: '24', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TO_DOLLARS', + t: 16, + d: 'Converts a provided number to a dollar value.', + a: 'Converts a provided number to a dollar value.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The argument or reference to a cell to be converted to a dollar value.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TO_PERCENT', + t: 16, + d: 'Converts a provided number to a percentage.', + a: 'Converts a provided number to a percentage.', + m: [1, 1], + p: [ + { + name: 'value', + detail: 'The argument or reference to a cell to be converted to a percentage.', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DGET', + t: 17, + d: 'Returns a single value from a database table-like array or range using a SQL-like query.', + a: 'Single value from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DMAX', + t: 17, + d: 'Returns the maximum value selected from a database table-like array or range using a SQL-like query.', + a: 'Maximum of values from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DMIN', + t: 17, + d: 'Returns the minimum value selected from a database table-like array or range using a SQL-like query.', + a: 'Minimum of values from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DAVERAGE', + t: 17, + d: 'Returns the average of a set of values selected from a database table-like array or range using a SQL-like query.', + a: 'Average of a set of values from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DCOUNT', + t: 17, + d: 'Counts numeric values selected from a database table-like array or range using a SQL-like query.', + a: 'Counts values from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DCOUNTA', + t: 17, + d: 'Counts values, including text, selected from a database table-like array or range using a SQL-like query.', + a: 'Counts values and text from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DPRODUCT', + t: 17, + d: 'Returns the product of values selected from a database table-like array or range using a SQL-like query.', + a: 'Product of values from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DSTDEV', + t: 17, + d: 'Returns the standard deviation of a population sample selected from a database table-like array or range using a SQL-like query.', + a: 'Standard deviation of population sample from table.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DSTDEVP', + t: 17, + d: 'Returns the standard deviation of an entire population selected from a database table-like array or range using a SQL-like query.', + a: 'Standard deviation of entire population from table.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DSUM', + t: 17, + d: 'Returns the sum of values selected from a database table-like array or range using a SQL-like query.', + a: 'Sum of values from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DVAR', + t: 17, + d: 'Returns the variance of a population sample selected from a database table-like array or range using a SQL-like query.', + a: 'Variance of population sample from table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DVARP', + t: 17, + d: 'Returns the variance of an entire population selected from a database table-like array or range using a SQL-like query.', + a: 'Variance of a population from a table-like range.', + m: [3, 3], + p: [ + { + name: 'database', + detail: "The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.", + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: 'Indicates which column in `database` contains the values to be extracted and operated on.', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: 'An array or range containing zero or more criteria to filter the `database` values by before operating.', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'AGE_BY_IDCARD', + t: '3', + d: 'Calculate the age based on the Chinese ID number. Support 15 or 18', + a: 'Get age based on ID number.', + m: [1, 2], + p: [ + { + name: 'ID number', + example: 'A1', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Deadline', + example: '"2017-10-01"', + detail: 'The deadline or range of age calculation. The default is the current day.', + require: 'o', + repeat: 'n', + type: 'rangedatetime', + }, + ], + }, + { + n: 'SEX_BY_IDCARD', + t: '3', + d: 'Calculate gender based on Chinese ID number. Support 15 or 18', + a: 'Get gender based on ID number.', + m: [1, 1], + p: [ + { + name: 'ID number', + example: '"31033519900101XXXX"', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'BIRTHDAY_BY_IDCARD', + t: '3', + d: 'Calculate the birthday based on the Chinese ID number. Support 15 or 18', + a: 'Get the birthday based on the ID number.', + m: [1, 2], + p: [ + { + name: 'ID number', + example: '"31033519900101XXXX"', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Birthday format', + example: '0', + detail: 'Date type, default:0:[1900/01/01], 1:[1900-01-01], 2:[1900年1月1日]', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'PROVINCE_BY_IDCARD', + t: '3', + d: 'Calculate the province of birthplace based on the Chinese ID number. Support 15 or 18', + a: 'Get the province of birthplace based on the ID number.', + m: [1, 1], + p: [ + { + name: 'ID number', + example: '"31033519900101XXXX"', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'CITY_BY_IDCARD', + t: '3', + d: 'Calculate the city of birthplace based on the Chinese ID number. Support 15 or 18', + a: 'Get the city of birthplace based on the ID number.', + m: [1, 1], + p: [ + { + name: 'ID number', + example: '"31033519900101XXXX"', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'STAR_BY_IDCARD', + t: '3', + d: 'Calculate the constellation based on the Chinese ID number. Support 15 or 18', + a: 'Get the constellation based on the ID number.', + m: [1, 1], + p: [ + { + name: 'ID number', + example: '"31033519900101XXXX"', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ANIMAL_BY_IDCARD', + t: '3', + d: 'Calculate the zodiac (rat, ox, tiger, rabbit...) based on the Chinese ID number. Support 15 or 18', + a: 'Get the zodiac according to the ID number.', + m: [1, 1], + p: [ + { + name: 'ID number', + example: '"31033519900101XXXX"', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISIDCARD', + t: '3', + d: 'Verify that the format of the ID card is correct. Support 15 or 18', + a: 'Verify the correctness of the ID card format.', + m: [1, 1], + p: [ + { + name: 'ID number', + example: '"31033519900101XXXX"', + detail: '15-digit or 18-digit ID number or range.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DM_TEXT_CUTWORD', + t: '4', + d: 'Text segmentation. Split a series of words into a series of individual words', + a: 'Chinese text segmentation.', + m: [1, 2], + p: [ + { + name: 'Text', + example: '"I came to Beijing Tsinghua University"', + detail: 'Any text that needs word segmentation.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Word segmentation mode', + example: '0', + detail: 'The default is 0[precision mode], 1[full mode], 2[search engine mode].', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DM_TEXT_TFIDF', + t: '4', + d: 'Use tf-idf algorithm for keyword extraction. Identify keywords from a series of text', + a: 'tf-idf keyword recognition.', + m: [1, 3], + p: [ + { + name: 'Text', + example: '"I came to Beijing Tsinghua University"', + detail: 'Any text that needs word segmentation.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Number of keywords', + example: '20', + detail: 'The number of keywords returned by the algorithm, the default is 20', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'Corpus', + example: '1', + detail: 'Select a corpus in a specific field, the default is 0[General], 1[Finance], 2[Medical]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DM_TEXT_TEXTRANK', + t: '4', + d: 'Use TextRank algorithm to extract keywords. Identify keywords from a series of text', + a: 'TextRank keyword recognition.', + m: [1, 3], + p: [ + { + name: 'Text', + example: '"I came to Beijing Tsinghua University"', + detail: 'Any text that needs word segmentation.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Number of keywords', + example: '20', + detail: 'The number of keywords returned by the algorithm, the default is 20', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'Corpus', + example: '1', + detail: 'Select a corpus in a specific field, the default is 0[General], 1[Finance], 2[Medical]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATA_CN_STOCK_CLOSE', + t: '5', + d: 'According to the stock code and date, return the corresponding stock closing price of A shares.', + a: 'Returns the closing price of stock.', + m: [1, 3], + p: [ + { + name: 'Stock code', + example: '"000001"', + detail: '6-digit stock code, required.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Date', + example: '2015-01-08', + detail: 'The trading day of the stock, the default is the latest trading day', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: 'Reversion and exclusion', + example: '0', + detail: 'Select the ex right restoration type of the stock, default to 0 [former reversion], 1 [original price], 2 [post reversion]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATA_CN_STOCK_OPEN', + t: '5', + d: 'According to the stock code and date, return the opening price of stock.', + a: 'Return the opening price of a shares.', + m: [1, 3], + p: [ + { + name: 'Stock code', + example: '"000001"', + detail: '6-digit stock code, required.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Date', + example: '2015-01-08', + detail: 'The trading day of the stock, the default is the latest trading day', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: 'Reversion and exclusion', + example: '0', + detail: 'Select the ex right restoration type of the stock, default to 0 [former reversion], 1 [original price], 2 [post reversion]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATA_CN_STOCK_MAX', + t: '5', + d: 'According to the stock code and date, return the highest price of stock.', + a: 'Return the highest price of stock.', + m: [1, 3], + p: [ + { + name: 'Stock code', + example: '"000001"', + detail: '6-digit stock code, required.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Date', + example: '2015-01-08', + detail: 'The trading day of the stock, the default is the latest trading day', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: 'Reversion and exclusion', + example: '0', + detail: 'Select the ex right restoration type of the stock, default to 0 [former reversion], 1 [original price], 2 [post reversion]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATA_CN_STOCK_MIN', + t: '5', + d: 'According to the stock code and date, return the lowest price of stock.', + a: 'Returns the lowest price of stock.', + m: [1, 3], + p: [ + { + name: 'Stock code', + example: '"000001"', + detail: '6-digit stock code, required.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Date', + example: '2015-01-08', + detail: 'The trading day of the stock, the default is the latest trading day', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: 'Reversion and exclusion', + example: '0', + detail: 'Select the ex right restoration type of the stock, default to 0 [former reversion], 1 [original price], 2 [post reversion]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATA_CN_STOCK_VOLUMN', + t: '5', + d: 'According to the stock code and date, return the corresponding stock trading volume of A shares.', + a: 'Returns the corresponding stock trading volume of A shares.', + m: [1, 3], + p: [ + { + name: 'Stock code', + example: '"000001"', + detail: '6-digit stock code, required.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Date', + example: '2015-01-08', + detail: 'The trading day of the stock, the default is the latest trading day', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: 'Reversion and exclusion', + example: '0', + detail: 'Select the ex right restoration type of the stock, default to 0 [former reversion], 1 [original price], 2 [post reversion]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATA_CN_STOCK_AMOUNT', + t: '5', + d: 'According to the stock code and date, return the corresponding stock turnover of A shares.', + a: 'Returns the corresponding stock turnover of A shares.', + m: [1, 3], + p: [ + { + name: 'Stock code', + example: '"000001"', + detail: '6-digit stock code, required.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Date', + example: '2015-01-08', + detail: 'The trading day of the stock, the default is the latest trading day', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: 'Reversion and exclusion', + example: '0', + detail: 'Select the ex right restoration type of the stock, default to 0 [former reversion], 1 [original price], 2 [post reversion]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ISDATE', + t: '6', + d: 'Returns whether a value is a date.', + a: 'Whether a value is a date.', + m: [1, 1], + p: [ + { + name: 'value', + example: '"1990-01-01"', + detail: 'The value to be verified as a date.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LINESPLINES', + t: '3', + d: 'Generate sparklines embedded in the cell to describe the continuous trend of data', + a: 'Generate sparklines line chart', + m: [1, 8], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Line color', + example: '#2ec7c9', + detail: 'The line color of the line graph can be range A1, color table index value or specific color value. Set it to 0 or false to not display it. It supports regx, rgb, rgba, etc. Default #2ec7c9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Line thickness', + example: '1', + detail: 'Line thickness of the line graph, the default is 1px', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Auxiliary line', + example: 'avg', + detail: 'A horizontal line, which can be min, max, avg, median, range or custom value, default 0 none', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Auxiliary line color', + example: '#000', + detail: 'Color setting of auxiliary line, same as line color configuration, default #000', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Maximum mark', + example: '#fc5c5c', + detail: 'Identifies the maximum value of the line graph, the same line color configuration, default 0 does not display', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Minimum mark', + example: '#fc5c5c', + detail: 'Identify the minimum value of the line graph, the same line color configuration, default 0 does not display', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Mark size', + example: '1.5', + detail: 'The maximum and minimum mark size settings, the default is 1.5', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'AREASPLINES', + t: '3', + d: 'Generate sparklines embedded in the cell area chart, generally used to describe the continuous cumulative value trend of the data', + a: 'Generate sparklines area chart', + m: [1, 5], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Line color', + example: '#2ec7c9', + detail: 'The line color of the line graph can be range A1, color table index value or specific color value. Set it to 0 or false to not display it. It supports regx, rgb, rgba, etc. Default #2ec7c9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Fill color', + example: '#CCF3F4', + detail: 'Form an area chart, the same line color configuration, default 0 does not display', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Line thickness', + example: '1', + detail: 'Line thickness of the line graph, the default is 1px', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Auxiliary line', + example: 'avg', + detail: 'A horizontal line, which can be min, max, avg, median, range or custom value, default 0 none', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Auxiliary line color', + example: '#000', + detail: 'Color setting of auxiliary line, same as line color configuration, default #000', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'COLUMNSPLINES', + t: '3', + d: 'Generate sparklines embedded in the vertical histogram of cells, generally used to describe the size of discrete data', + a: 'Generate sparklines vertical histogram', + m: [1, 6], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar interval', + example: '1', + detail: 'The distance between bars, the default is 1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar color', + example: '#fc5c5c', + detail: 'The line color of the line graph can be range A1, color table index value or specific color value. Set it to 0 or false to not display it. It supports regx, rgb, rgba, etc. Default #fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Negative bar color', + example: '#97b552', + detail: 'Negative bar color setting, representing the color of negative value, same as the bar color configuration, default #97b552', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Max', + example: '100', + detail: 'The maximum value of the bar chart, used to standardize the length of the bar chart, the default is to automatically calculate false, auto, null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Color palette', + example: '#97b552', + detail: 'The color palette can set the color of each bar individually, multiple settings can be set, and two formats are supported: 1 color such as #000, which means that the color of the first bar is black; 2 value range: color, such as -2:# 000 indicates that the bar with a value of -2 is black, 0:5:#000 indicates that the bar with a value of 0-5 is black, and the default is empty', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'STACKCOLUMNSPLINES', + t: '3', + d: 'Generate sparklines, a cumulative vertical histogram embedded in a cell, generally used to describe the numerical size of multiple dimensions of discrete data', + a: 'Generate sparklines cumulative vertical histogram', + m: [1, 5], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Stack by column', + example: '1', + detail: 'If you need to stack by row, set this item to false or 0, the default is 1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar interval', + example: '1', + detail: 'The distance between bars, the default is 1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Max', + example: '100', + detail: 'The maximum value of the cumulative bar, used to regulate the length of the bar, the default is to automatically calculate false, auto, null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Color palette', + example: '#97b552', + detail: 'The color palette can individually set the bar color of each dimension, which can be set to the range of A1:A10, etc. The default is #2ec7c9, #fc5c5c, #5ab1ef, #ffb980...', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'BARSPLINES', + t: '3', + d: 'Generate sparklines embedded in the cell, generally used to describe the size of discrete data', + a: 'Generate sparklines horizontal bar graph', + m: [1, 6], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar interval', + example: '1', + detail: 'The distance between bars, the default is 1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar color', + example: '#fc5c5c', + detail: 'The line color of the line graph can be range A1, color table index value or specific color value. Set it to 0 or false to not display it. It supports regx, rgb, rgba, etc. Default #fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Negative bar color', + example: '#97b552', + detail: 'Negative bar color setting, representing the color of negative value, same as the bar color configuration, default #97b552', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Max', + example: '100', + detail: 'The maximum value of the bar chart, used to standardize the length of the bar chart, the default is to automatically calculate false, auto, null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Color palette', + example: '#97b552', + detail: 'The color palette can set the color of each bar individually, multiple settings can be set, and two formats are supported: 1 color such as #000, which means that the color of the first bar is black; 2 value range: color, such as -2:# 000 indicates that the bar with a value of -2 is black, 0:5:#000 indicates that the bar with a value of 0-5 is black, and the default is empty', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'STACKBARSPLINES', + t: '3', + d: 'Generate sparklines, a cumulative horizontal bar graph embedded in a cell, which is generally used to describe the numerical size of multiple dimensions of discrete data', + a: 'Generate sparklines cumulative horizontal bar graph', + m: [1, 5], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Stack by column', + example: '1', + detail: 'If you need to stack by row, set this item to false or 0, the default is 1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar interval', + example: '1', + detail: 'The distance between bars, the default is 1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Max', + example: '100', + detail: 'The maximum value of the cumulative bar, used to regulate the length of the bar, the default is to automatically calculate false, auto, null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Color palette', + example: '#97b552', + detail: 'The color palette can individually set the bar color of each dimension, which can be set to the range of A1:A10, etc. The default is #2ec7c9, #fc5c5c, #5ab1ef, #ffb980...', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'DISCRETESPLINES', + t: '3', + d: 'Generate sparklines embedded in the cell, generally used to describe the trend of discrete data', + a: 'Generate sparklines discrete graph', + m: [1, 4], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Segmentation threshold', + example: '1', + detail: 'Discrete graph column color distinction, for example: if the value is 0, blue is greater than 0, red is less than 0, and the default is 0', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Above threshold color', + example: '#2ec7c9', + detail: 'The line color of the line graph can be range A1, color table index value or specific color value. Set it to 0 or false to not display it. It supports regx, rgb, rgba, etc. Default #2ec7c9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Below threshold color', + example: '#fc5c5c', + detail: 'The color setting of the bar below the threshold, the same as the color above the threshold, default #fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TRISTATESPLINES', + t: '3', + d: 'Generate sparklines, a three-state graph embedded in the cell, which is generally used to describe the trend of three situations, such as winning, losing, or drawing.', + a: 'Generate sparklines three-state graph', + m: [1, 6], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar interval', + example: '1', + detail: 'The distance between bars, the default is 1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Bar color', + example: '#fc5c5c', + detail: 'The line color of the line graph can be range A1, color table index value or specific color value. Set it to 0 or false to not display it. It supports regx, rgb, rgba, etc. Default #fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Negative bar color', + example: '#97b552', + detail: 'Negative bar color setting, representing the color of negative value, same as the bar color configuration, default #97b552', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Zero value bar color', + example: '#999', + detail: 'Zero value bar color setting, representing 0 value color, the same color configuration of the bar, default #999', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Color palette', + example: '#97b552', + detail: 'The color palette can set the color of each bar individually, multiple settings can be set, and two formats are supported: 1 color such as #000, which means that the color of the first bar is black; 2 value range: color, such as -2:# 000 indicates that the bar with a value of -2 is black, 0:5:#000 indicates that the bar with a value of 0-5 is black, and the default is empty', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'PIESPLINES', + t: '3', + d: 'Generate sparklines pie chart embedded in the cell, generally used to describe the proportion of data', + a: 'Generate sparklines pie chart', + m: [1, 5], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Rotation angle', + example: '0', + detail: 'The rotation angle of the pie chart, the default is 0', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'border', + example: '0', + detail: 'Pie chart border size, default is none 0', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Border color', + example: '#000', + detail: 'The border color of the pie chart, the default is #000', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Color palette', + example: '#97b552', + detail: 'The color of the slice can be set in the palette, which can be set to the range of A1:A10, etc. The default is #2ec7c9, #fc5c5c, #5ab1ef, #ffb980...', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'BOXSPLINES', + t: '3', + d: 'Generate sparklines embedded in the cell box plot, generally used to describe the statistical distribution of the data set', + a: 'Generate sparklines box plot', + m: [1, 4], + p: [ + { + name: 'Range', + example: 'A1:A20', + detail: 'Range,Values can be calculated effectively, such as A1:A20, {1,2,3,4,5}, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Outlier ratio', + example: '1.5', + detail: 'The threshold range of outliers, if it is 0 or false, it will not be displayed, the default is 1.5 times', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Target value', + example: '10', + detail: 'The target value setting on the box plot, the default is false and does not display', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Point size', + example: '1.5', + detail: 'The radius of the target point and outlier is set, the default is 1.5', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'BULLETSPLINES', + t: '3', + d: 'Generate sparklines embedded in the cell, generally used to describe the task achievement rate', + a: 'Generating sparklines bullets', + m: [2, 3], + p: [ + { + name: 'Target', + example: '10', + detail: 'The numerical value can be calculated effectively for the achieved target value, such as A1, 100, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'achieved', + example: '8', + detail: 'Only when the value is completed can the value be calculated effectively, such as A1, 100, etc.', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'Contrast', + example: '12', + detail: 'Comparative values, such as excess, minimum, and bottom line for awards, can be effectively calculated, such as A1, 100, etc. You can set up to 9 comparison values', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'COMPOSESPLINES', + t: '3', + d: 'Support multiple types of pictures in the same cell, each parameter represents a sparklines diagram', + a: 'Combine sparklines graphs into one cell', + m: [1, 1], + p: [ + { + name: 'config', + example: 'PIESPLINES(A1:A20)', + detail: 'Sparklines chart settings, such as A1:A20, a completed pie chart, line chart settings, etc.', + require: 'm', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'SORT', + t: '14', + d: 'Sorts the rows of a given array or range by the values in one or more columns.', + a: 'Sorts rows of range by specified column.', + m: [1, 4], + p: [ + { + name: 'range', + detail: 'The data to be sorted.', + example: 'A2:A17', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'sort_column', + detail: 'The index of the column in `range` or a range outside of `range` containing the values by which to sort.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'is_ascending', + detail: '`TRUE` or `FALSE` indicating whether to sort `sort_column` in ascending order. `FALSE` sorts in descending order.', + example: '-1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'sort_column2', + detail: 'Additional columns.', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FILTER', + t: '14', + d: 'Returns a filtered version of the source range, returning only rows or columns which meet the specified conditions.', + a: 'Filters a range based off provided conditions.', + m: [2, 3], + p: [ + { + name: 'range', + detail: 'The data to be filtered.', + example: 'A5:D20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'condition1', + detail: 'A column or row containing true or false values corresponding to the first column or row of `range`, or an array formula evaluating to true or false.', + example: '1', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'condition2', + detail: 'Additional rows or columns containing boolean values `TRUE` or `FALSE` indicating whether the corresponding row or column in `range` should pass through `FILTER`. Can also contain array formula expressions which evaluate to such rows or columns. All conditions must be of the same type (row or column). Mixing row conditions and column conditions is not permitted.', + example: '""', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'UNIQUE', + t: '14', + d: 'Returns unique rows in the provided source range, discarding duplicates. Rows are returned in the order in which they first appear in the source range.', + a: 'Unique rows in the provided source range.', + m: [1, 3], + p: [ + { + name: 'range', + detail: 'The data to filter by unique entries.', + example: 'A2:B26', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'by_col', + detail: '[Option] - Logical value, indicating how to compare; by row = FALSE() or omitted; by column = TRUE().', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'occurs_once', + detail: '[Option] - Logical value, only one occurrence in the unique value is returned = TRUE(); including all unique values = FALSE() or omitted.', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANDARRAY', + t: '14', + d: 'Returns a grid of random numbers between 0 inclusive and 1 exclusive. The grid size will match the provided rows and columns arguments. If neither rows nor columns are provided, then the grid will be size 1 x 1.', + a: 'Returns a grid of random numbers.', + m: [0, 2], + p: [ + { + name: 'rows', + detail: 'The number of rows to populate with a random number.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'columns', + detail: 'The number of columns to populate with a random number.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SEQUENCE', + t: '14', + d: 'Returns a grid of sequential numbers starting at a specified start value and increasing by a specified step size. By default, the sequence starts at and increases by 1.', + a: 'Returns a grid of sequential numbers.', + m: [1, 4], + p: [ + { + name: 'rows', + detail: "The number of rows in the function's resulting grid.", + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'columns', + detail: "The number of columns in the function's resulting grid. If omitted, the result grid will have 1 column.", + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'start', + detail: 'The number, at which to start the sequence. If omitted, the sequence will start at 1.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'step', + detail: 'The amount each value in the sequence will differ by. If omitted, each value will differ by 1.', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EVALUATE', + t: '3', + d: 'Evaluate a formula or expression expressed in words and return the result', + a: 'Evaluate according to literal formula or expression.', + m: [1, 1], + p: [ + { + name: 'expression', + example: '"A1+5*2^2"', + detail: 'Formula or expression', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REMOTE', + t: '5', + d: 'Calls a function on a remote server', + a: 'Calls a function on a remote back end server/API.', + m: [1, 1], + p: [ + { + name: 'remote_expression', + example: 'SUM(A1:A10000000)', + detail: 'Formula', + require: 'm', + repeat: 'n', + type: 'string', + }, + ], + }, + ], + functiontype: { + '0': 'Math', + '1': 'Statistical', + '2': 'Lookup', + '3': 'UniverSheet', + '4': 'Data Mining', + '5': 'Database', + '6': 'Date', + '7': 'Filter', + '8': 'Financial', + '9': 'Engineering', + '10': 'Logical', + '11': 'Operator', + '12': 'Text', + '13': 'Parser', + '14': 'Array', + '15': 'Other', + }, + formulaMore: { + valueTitle: 'Value', + tipSelectDataRange: 'Select data range', + tipDataRangeTile: 'Data range', + findFunctionTitle: 'Search function', + tipInputFunctionName: 'Function name or brief description of function', + whole: 'Whole', + + Math: 'Math', + Statistical: 'Statistical', + Lookup: 'Lookup', + universheet: 'UniverSheet', + dataMining: 'Data Mining', + Database: 'Database', + Date: 'Date', + Filter: 'Filter', + Financial: 'Financial', + Engineering: 'Engineering', + Logical: 'Logical', + Operator: 'Operator', + Text: 'Text', + Parser: 'Parser', + Array: 'Array', + other: 'Other', + + selectFunctionTitle: 'Select a function', + calculationResult: 'Result', + + tipSuccessText: 'Success', + tipParamErrorText: 'Parameter type error', + + helpClose: 'Close', + helpCollapse: 'Collapse', + helpExample: 'Example', + helpAbstract: 'Abstract', + + execfunctionError: 'Error in the formula', + execfunctionSelfError: 'The formula cannot refer to its own cell', + execfunctionSelfErrorResult: 'The formula cannot refer to its own cell, which will lead to inaccurate calculation results', + + allowRepeatText: 'Repeat', + allowOptionText: 'Option', + + selectCategory: 'Or select a category', + }, + ifFormula: { + tipNotBelongToIf: 'This cell function does not belong to the if formula!', + tipSelectCell: 'Please select the cell to insert the function', + + ifGenCompareValueTitle: 'Comparison value', + ifGenSelectCellTitle: 'Click to select cell', + ifGenRangeTitle: 'Range', + ifGenRangeTo: 'to', + ifGenRangeEvaluate: 'Range evaluate', + ifGenSelectRangeTitle: 'Click to select range', + ifGenCutWay: 'Partition way', + ifGenCutSame: 'Same Partition value', + ifGenCutNpiece: 'Partition by N', + ifGenCutCustom: 'Custom', + ifGenCutConfirm: 'Confirm', + + ifGenTipSelectCell: 'Select cells', + ifGenTipSelectCellPlace: 'Please select cells', + + ifGenTipSelectRange: 'Select range', + ifGenTipSelectRangePlace: 'Please select range', + + ifGenTipNotNullValue: 'The comparison value cannot be empty!', + ifGenTipLableTitile: 'Label', + ifGenTipRangeNotforNull: 'The range cannot be empty!', + ifGenTipCutValueNotforNull: 'The partition value cannot be empty!', + ifGenTipNotGenCondition: 'No conditions are available for generation!', + }, + }, +}; diff --git a/packages/sheets-plugin-formula/src/Locale/index.ts b/packages/sheets-plugin-formula/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-formula/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-formula/src/Locale/zh.ts b/packages/sheets-plugin-formula/src/Locale/zh.ts new file mode 100644 index 0000000000..42c158bb97 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Locale/zh.ts @@ -0,0 +1,10686 @@ +export default { + formula: { + formulaLabel: '求和', + formulaRightLabel: '更多公式', + ok: '确定', + cancel: '取消', + clearValidation: '清除验证', + formula: { + sum: '求和', + average: '平均值', + count: '计数', + max: '最大值', + min: '最小值', + if: 'if公式生成器', + more: '更多函数...', + }, + functionlist: [ + { + n: 'SUMIF', + t: 0, + d: '对范围中符合指定条件的值求和。', + a: '对范围中符合指定条件的值求和。', + m: [2, 3], + p: [ + { + name: '范围', + detail: '要根据条件进行检测的范围。', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: '条件', + detail: '要应用于范围的模式或测试条件。\n\n如果范围包含的是要检测的文本,则条件必须为字符串。条件可以包含通配符,包括用于匹配单个字符的?或用于匹配零个或连续多个字符的*。要匹配问号星号本身,请在该字符前面加上波浪号(~)前缀(即~?和~*)。字符串条件必须用引号括起来。函数会检查范围中的每个单元格与条件是否相等或匹配(如果使用了通配符)。\n\n如果范围包含的是要检测的数字,则条件可以是字符串也可以是数字。如果给定的条件是一个数字,则检查范围中的每个单元格是否等于条件。另外,条件也可能是包含数字的字符串(也将对其进行相等检测),或者带有以下前缀的数字:=(检查是否相等)、>(检查范围单元格的值是否大于条件值)或<(检查范围单元格的值是否小于条件值)', + example: '">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '求和范围', + detail: '要求和的范围(如果与范围不同)。', + example: 'B1:B10', + require: 'o', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'TAN', + t: 0, + d: '返回已知角度的正切值。', + a: '返回已知角度的正切值。', + m: [1, 1], + p: [ + { + name: '角度', + detail: '要求其正切值的角度,以弧度表示。', + example: '45*PI()/180', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TANH', + t: 0, + d: '返回给定实数的双曲正切值。', + a: '返回给定实数的双曲正切值。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其双曲正切值的实数。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CEILING', + t: 0, + d: '将数值向上取整为最接近的指定因数的倍数。', + a: '将数值向上取整为最接近的指定因数的倍数。', + m: [2, 2], + p: [ + { + name: '值', + detail: '要向上舍入的数值。', + example: '23.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '因数', + detail: '要将值舍入到此数的整数倍。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ATAN', + t: 0, + d: '返回数值的反正切值,以弧度表示。', + a: '返回数值的反正切值', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其反正切值的数值。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ASINH', + t: 0, + d: '返回数值的反双曲正弦值。', + a: '返回数值的反双曲正弦值。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其反双曲正弦值的数值。', + example: '0.9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ABS', + t: 0, + d: '返回数值的绝对值。', + a: '返回数值的绝对值。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要返回其绝对值的数。', + example: '-2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ACOS', + t: 0, + d: '返回数值的反余弦值,以弧度表示。', + a: '返回数值的反余弦值', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其反余弦值的数值。必须介于-1和1之间,包括两端值。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ACOSH', + t: 0, + d: '返回数值的反双曲余弦值。', + a: '返回数值的反双曲余弦值。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其反双曲余弦值的数值。必须大于等于1。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MULTINOMIAL', + t: 0, + d: '返回参数和的阶乘除以各参数阶乘的乘积后得到的值。', + a: '返回参数和的阶乘除以各参数阶乘的乘积后得到的值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '用于计算的第一项数值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2', + detail: '用于计算的其他数值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'ATANH', + t: 0, + d: '返回数值的反双曲正切值。', + a: '返回数值的反双曲正切值。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其反双曲正切值的数值。必须介于-1和1之间(不包括-1和1)。', + example: '0.9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ATAN2', + t: 0, + d: '以弧度为单位返回 x 轴与从原点 (0,0) 到指定坐标点 (`x`,`y`) 之间连线的夹角。', + a: '以弧度为单位返回 x 轴与从原点 (0,0) 到指定坐标点 (`x`,`y`) 之间连线的夹角。', + m: [2, 2], + p: [ + { + name: 'x', + detail: '要计算其与x轴夹角大小的线段的终点x坐标。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'y', + detail: '要计算其与x轴夹角大小的线段的终点y坐标。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUNTBLANK', + t: 1, + d: '返回给定范围内的空单元格数。', + a: '返回给定范围内的空单元格数。', + m: [1, 1], + p: [ + { + name: '范围', + detail: '要统计空白单元格数量的范围。', + example: 'A2:C100', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'COSH', + t: 0, + d: '返回给定实数的双曲余弦值。', + a: '返回给定实数的双曲余弦值。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其双曲余弦值的实数值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'INT', + t: 0, + d: '将数值向下取整为小于或等于该数的最接近的整数。', + a: '将数值向下取整为小于或等于该数的最接近的整数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要向下取整为最接近的整数的数值。', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ISEVEN', + t: 0, + d: '检查所提供的数值是否为偶数。', + a: '检查所提供的数值是否为偶数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要验证其是否为偶数的数值。\n\n如果值为偶数或指向包含偶数的单元格的引用,ISEVEN将返回TRUE,否则返回FALSE。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ISODD', + t: 0, + d: '检查所提供的数值是否为奇数。', + a: '检查所提供的数值是否为奇数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要验证其是否为奇数的数值。\n\n如果值为奇数或指向包含奇数的单元格,ISODD将返回TRUE,否则返回FALSE。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LCM', + t: 0, + d: '返回一个或多个整数的最小公倍数。', + a: '返回一个或多个整数的最小公倍数。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '要在求最小公倍数数的计算中检查其因数的第一项数值或范围。', + example: 'A2:A5', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2', + detail: '[可选] - 在求最小公倍数时要考虑其因数的其他数值或范围。', + example: '3', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'LN', + t: 0, + d: '返回数值以 e(欧拉数)为底的对数。', + a: '返回数值以 e(欧拉数)为底的对数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要以 e 为底数计算其对数的值。\n\n值必须为正数。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOG', + t: 0, + d: '根据指定底数返回数字的对数。', + a: '根据指定底数返回数字的对数。', + m: [1, 2], + p: [ + { + name: '值', + detail: '想要计算其对数的正实数。', + example: '128', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '底数', + detail: '[可选] - 对数的底数。', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOG10', + t: 0, + d: '返回数值以10为底的对数。', + a: '返回数值以10为底的对数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其以10为底的对数的数值。\n\n值必须为正值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MOD', + t: 0, + d: '返回两数相除的余数, 结果的符号与除数相同。', + a: '返回两数相除的余数。', + m: [2, 2], + p: [ + { + name: '被除数', + detail: '要将其相除以得到余数的数值。', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '除数', + detail: '用于除其他数的数值。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MROUND', + t: 0, + d: '将数值取整为另一整数最接近的整数倍。', + a: '将数值取整为另一整数最接近的整数倍。', + m: [2, 2], + p: [ + { + name: '值', + detail: '要取整为另一整数最接近的整数倍的数值。', + example: '21', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '因数', + detail: '值将取此因数的整数倍。', + example: '14', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ODD', + t: 0, + d: '将数值向上取整为最接近的奇整数。', + a: '将数值向上取整为最接近的奇整数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要向上取整的数值,取整值为大于此值的最接近的奇数。\n\n如果值为负数,则将其取整为绝对值大于该值的相邻负奇数。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMSQ', + t: 0, + d: '返回一组数值和/或单元格的平方总和。', + a: '返回一组数值和/或单元格的平方总和。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '要将其平方相加的第一个数值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 要将其平方与值1的平方相加的其他数值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'COMBIN', + t: 0, + d: '给定集合中的对象总数和要选择的对象数量,返回共有多少种不同选择方式。', + a: '给定集合中的对象总数和要选择的对象数量', + m: [2, 2], + p: [ + { + name: 'n', + detail: '要从中进行选择的对象集合的大小。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'k', + detail: '要选择的对象数量。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUM', + t: 0, + d: '返回一组数值和/或单元格的总和。', + a: '返回一组数值和/或单元格的总和。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '要相加的第一个数值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2', + detail: '[可选] - 要相加的其他数值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'SUBTOTAL', + t: 0, + d: '使用指定的汇总函数,返回一系列纵向单元格的分类汇总。', + a: '使用指定的汇总函数', + m: [2, 256], + p: [ + { + name: '函数代码', + detail: '用于计算分类汇总的函数。\n\n1代表AVERAGE\n\n2代表COUNT\n\n3代表COUNTA\n\n4代表MAX\n\n5代表MIN\n\n6代表PRODUCT\n\n7代表STDEV\n\n8代表STDEVP\n\n9代表SUM\n\n10代表VAR\n\n11代表VARP\n\n通过在这些2位代码前附加10(对于1位代码)或1(对于2位代码),可以将隐藏值忽略。例如,102代表忽略隐藏单元格的COUNT,而110则代表忽略隐藏值的VAR。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '范围1', + detail: '要计算分类汇总的第一个范围。', + example: 'A2:A5', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: '范围2', + detail: '[可选] - 要计算分类汇总的其他范围。', + example: 'B2:B8', + require: 'o', + repeat: 'y', + type: 'range', + }, + ], + }, + { + n: 'ASIN', + t: 0, + d: '返回数值的反正弦值,以弧度表示。', + a: '返回数值的反正弦值', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其反正弦值的数值。必须介于-1和1之间,包括两端值。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUNTIF', + t: 1, + d: '返回范围内满足某个条件的单元格的数量。', + a: '返回范围内满足某个条件的单元格的数量。', + m: [2, 2], + p: [ + { + name: '范围', + detail: '要根据条件进行检测的范围。', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: '条件', + detail: '要应用于范围的模式或测试条件。\n\n如果范围包含的是要检测的文本,则条件必须为字符串。条件可以包含通配符,包括用于匹配单个字符的?或用于匹配零个或连续多个字符的*。要匹配问号星号本身,请在该字符前面加上波浪号(~)前缀(即~?和~*)。字符串条件必须用引号括起来。函数会检查范围中的每个单元格与条件是否相等或匹配(如果使用了通配符)。\n\n如果范围包含的是要检测的数字,则条件可以是字符串也可以是数字。如果给定的条件是一个数字,则检查范围中的每个单元格是否等于条件。另外,条件也可能是包含数字的字符串(也将对其进行相等检测),或者带有以下前缀的数字:=、>、>=、<或<=,这些条件将分别用于检查范围中的单元格是否等于、大于、大于等于、小于、小于等于条件值。', + example: '">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RADIANS', + t: 0, + d: '将以度表示的角度值转换为弧度。', + a: '将以度表示的角度值转换为弧度。', + m: [1, 1], + p: [ + { + name: '角度', + detail: '要从度转换为弧度的角度。', + example: '180', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RAND', + t: 0, + d: '返回一个介于0和1之间(包括0但不包括1)的随机数。', + a: '返回一个介于0和1之间(包括0但不包括1)的随机数。', + m: [0, 0], + p: [], + }, + { + n: 'COUNTUNIQUE', + t: 0, + d: '计算一列指定值和范围中不重复数值的个数。', + a: '计算一列指定值和范围中不重复数值的个数。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '要检查其是否唯一的第一个值或范围。', + example: 'A1:C100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2', + detail: '[可选] - 要检查是否唯一的其他值或范围。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DEGREES', + t: 0, + d: '将以弧度表示的角度值转换为度。', + a: '将以弧度表示的角度值转换为度。', + m: [1, 1], + p: [ + { + name: '角度', + detail: '要从弧度转换为度的角度。', + example: 'PI()', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ERFC', + t: 9, + d: '返回数值的互补高斯误差函数。', + a: '返回数值的互补高斯误差函数。', + m: [1, 1], + p: [ + { + name: 'z', + detail: '要为其计算互补高斯误差函数的数值。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EVEN', + t: 0, + d: '将数值向上取整为最接近的偶整数。', + a: '将数值向上取整为最接近的偶整数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要向上取整的数值,取整值为大于此值的最接近的偶数。\n\n如果值为负数,则将其取整为绝对值大于该值的相邻负偶数。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EXP', + t: 0, + d: '返回欧拉数 e (~2.718) 的指定次幂。', + a: '返回欧拉数 e (~2.718) 的指定次幂。', + m: [1, 1], + p: [ + { + name: '指数', + detail: '指定e的自乘幂次值。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FACT', + t: 0, + d: '返回数值的阶乘。', + a: '返回数值的阶乘。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算并返回其阶乘的数字或对数字(所在单元格)的引用。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FACTDOUBLE', + t: 0, + d: '返回数值的“双阶乘”。', + a: '返回数值的“双阶乘”。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算并返回其双阶乘的数字或对数字(所在单元格)的引用。', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PI', + t: 0, + d: '返回带有14位小数的 PI 值。', + a: '返回带有14位小数的 PI 值。', + m: [0, 0], + p: [], + }, + { + n: 'FLOOR', + t: 0, + d: '将数值向下取整为指定因数的最接近的整数倍。', + a: '将数值向下取整为指定因数的最接近的整数倍。', + m: [2, 2], + p: [ + { + name: '值', + detail: '要向下舍入为因数的最接近整数倍的数值。', + example: '23.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '因数', + detail: '要将值舍入到此数的整数倍。\n\n因数不得为0。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GCD', + t: 0, + d: '返回一个或多个整数的最大公约数。', + a: '返回一个或多个整数的最大公约数。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '要在查找最大公约数的计算中检查其因数的第一项数值或范围。', + example: 'A2:A5', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2', + detail: '[可选] - 在求最大公约数时要考虑其因数的其他数值或范围。', + example: '96', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANDBETWEEN', + t: 0, + d: '返回介于两个整数之间(包括这两个整数)的随机数。', + a: '返回介于两个整数之间(包括这两个整数)的随机数。', + m: [2, 2], + p: [ + { + name: '下界', + detail: '随机值范围的下界。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '上界', + detail: '随机值范围的上界。', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ROUND', + t: 0, + d: '将数字四舍五入到指定的位数。', + a: '将数字四舍五入到指定的位数。', + m: [2, 2], + p: [ + { + name: '值', + detail: '要四舍五入的数字。', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '位数', + detail: '要进行四舍五入运算的位数。\n\n位数可以取负值,在这种情况下会将值的小数点左侧部分舍入到指定的位数。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ROUNDDOWN', + t: 0, + d: '朝着零的方向将数字进行向下舍入。', + a: '朝着零的方向将数字进行向下舍入。', + m: [2, 2], + p: [ + { + name: '值', + detail: '需要向下舍入的任意实数。', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '位数', + detail: '要通过舍入达到的小数位数。\n\n位数可以取负值,在这种情况下会将值的小数点左侧部分舍入到指定的位数。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ROUNDUP', + t: 0, + d: '朝着远离 0(零)的方向将数字进行向上舍入。', + a: '朝着远离 0(零)的方向将数字进行向上舍入。', + m: [2, 2], + p: [ + { + name: '值', + detail: '要将其舍入为位数位数字的值,始终向上舍入。', + example: '99.44', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '位数', + detail: '要通过舍入达到的小数位数。\n\n位数可以取负值,在这种情况下会将值的小数点左侧部分舍入到指定的位数。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SERIESSUM', + t: 0, + d: '给定参数 x、n、m 和 a,返回幂级数的和 a1xn + a2x(n+m) + ... + aix(n+(i-1)m),其中 i 为范围 a 中的项数。', + a: '给定参数 x、n、m 和 a', + m: [4, 4], + p: [ + { + name: 'x', + detail: '幂级数的输入值。随相应的近似类型而变,有可能为角度、指数或其他一些值。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'n', + detail: '在幂级数中x的初始自乘幂次。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'm', + detail: 'x的幂次中的附加增量。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'a', + detail: '包含幂级数系数的数组或范围。', + example: '{FACT(0)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SIGN', + t: 0, + d: '给定输入数值,如果为负返回-1;如果为正返回1;如果为零则返回0。', + a: '给定输入数值', + m: [1, 1], + p: [ + { + name: '值', + detail: '要返回其符号的数值。', + example: '-42', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SIN', + t: 0, + d: '给定角度(以弧度表示),返回其正弦值。', + a: '给定角度(以弧度表示)', + m: [1, 1], + p: [ + { + name: '角度', + detail: '要返回其正弦值的角度,以弧度表示。', + example: 'PI()', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SINH', + t: 0, + d: '返回给定实数的双曲正弦值。', + a: '返回给定实数的双曲正弦值。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其双曲正弦值的实数值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SQRT', + t: 0, + d: '返回一个正数的正平方根。', + a: '返回一个正数的正平方根。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要计算其正平方根的数值。\n\n值必须为正数;如果为负,SQRT 将返回 #NUM! 错误。', + example: '9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SQRTPI', + t: 0, + d: '返回 PI 与给定正数乘积的正平方根。', + a: '返回 PI 与给定正数乘积的正平方根。', + m: [1, 1], + p: [ + { + name: '值', + detail: '要将其与 PI 相乘并返回该乘积的平方根的数值\n\n值必须为正数;如果为负数,SQRTPI 将返回 #NUM! 错误。', + example: '9', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GAMMALN', + t: 1, + d: '返回指定伽玛函数的以 e(欧拉数)为底的对数。', + a: '返回指定伽玛函数的以 e(欧拉数)为底的对数。', + m: [1, 1], + p: [ + { + name: '值', + detail: '伽玛函数的输入值。返回的将是伽玛 (值) 的自然对数。\n\n值必须为正数。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COS', + t: 0, + d: '返回给定角度的余弦值(角度以弧度表示)。', + a: '返回给定角度的余弦值(角度以弧度表示)。', + m: [1, 1], + p: [ + { + name: '角度', + detail: '要取其余弦值的角度,以弧度表示。', + example: 'PI()', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TRUNC', + t: 0, + d: '截除指定有效位之外的部分,取数据的指定有效位。', + a: '截除指定有效位之外的部分', + m: [1, 2], + p: [ + { + name: '值', + detail: '要截取的数据。', + example: '3.141592654', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '位数', + detail: '[可选 - 默认值为0] - 小数点右侧要保留的有效位数。\n\n如果位数大于值中的有效位数,则将“值”原样返回。\n\n位数可以取负值,在这种情况下会将小数点左侧指定位数的值更改为零。小数点右侧的所有位数都会被舍弃。如果值的所有位都被更改为零,则TRUNC会返回0。', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'QUOTIENT', + t: 0, + d: '返回以一个数除以另一个数所得的结果,不包含余数。', + a: '返回以一个数除以另一个数所得的结果', + m: [2, 2], + p: [ + { + name: '被除数', + detail: '要被除的数值。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '除数', + detail: '用于除其他数的数值。\n\n除数不得为0。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'POWER', + t: 0, + d: '返回数值的指定次幂。', + a: '返回数值的指定次幂。', + m: [2, 2], + p: [ + { + name: '底数', + detail: '要计算其指数次幂的数值。\n\n如果底数为负,则指数必须为整数。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '指数', + detail: '指定底数的自乘幂次值。', + example: '0.5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMIFS', + t: 0, + d: '根据多项条件返回范围之和。', + a: '根据多项条件返回范围之和。', + m: [3, 257], + p: [ + { + name: '求和范围', + detail: '要对其求和的范围。', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: '条件范围1', + detail: '要在哪个范围内检查条件1。', + example: ' B1:B10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: '条件1', + detail: '要应用于条件范围1的模式或测试条件。', + example: ' ">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '条件范围2, 条件2...', + detail: '[ 可选 ] - 要检查的其他范围和条件。', + example: ' C1:C10', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'COUNTIFS', + t: 1, + d: '根据多项条件返回范围中的单元格数量。', + a: '根据多项条件返回范围中的单元格数量。', + m: [2, 256], + p: [ + { + name: '条件范围1', + detail: '要在哪个范围内检查条件1。', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: '条件1', + detail: '要应用于条件范围1的模式或测试条件。', + example: ' ">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '条件范围2, 条件2...', + detail: '[ 可选 ] - 要检查的其他范围和条件,可重复。', + example: ' B1:B10', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'PRODUCT', + t: 0, + d: '返回将一组数相乘所得的结果。', + a: '返回将一组数相乘所得的结果。', + m: [1, 255], + p: [ + { + name: '乘数1', + detail: '用于计算乘积的第一个数值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '乘数2 ... 乘数30', + detail: '[可选] - 要相乘的其他数值。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HARMEAN', + t: 1, + d: '计算数据集的调和平均值。', + a: '计算数据集的调和平均值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 数据集中包含的其他数值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HYPGEOMDIST', + t: 1, + d: '返回超几何分布。 如果已知样本量、总体成功次数和总体大小,则 HYPGEOM.DIST 返回样本取得已知成功次数的概率。', + a: '返回超几何分布。', + m: [5, 5], + p: [ + { + name: 'Sample_s', + detail: '样本中成功的次数。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'Number_sample', + detail: '样本量。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'Population_s', + detail: '总体中成功的次数。', + example: '20', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'Number_pop', + detail: '总体大小。', + example: '40', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '决定函数形式的逻辑值。\n\n如果 cumulative 为 TRUE(),则 HYPGEOM.DIST 返回累积分布函数;\n\n如果为 FALSE(),则返回概率密度函数。', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'INTERCEPT', + t: 1, + d: '计算数据集的线性回归方程直线与 Y 轴的相交点 (x=0) 的 y 值。', + a: '计算数据集的线性回归方程直线与 Y 轴的相交点 (x=0) 的 y 值。', + m: [2, 2], + p: [ + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'KURT', + t: 1, + d: '计算数据集的峭度,该指标指示数据集(分布)的形态,尤其是该形态的陡峭程度。', + a: '计算数据集的峭度', + m: [1, 255], + p: [ + { + name: '值1', + detail: '数据集中的第一个值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 数据集中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LARGE', + t: 1, + d: '返回数据集中第 n 个最大元素,n 由用户指定。', + a: '返回数据集中第 n 个最大元素', + m: [2, 2], + p: [ + { + name: '数据', + detail: '包含相关数据集的数组或范围。', + example: 'A2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'n', + detail: '要返回的元素的排行位置(从大到小顺序)。\n\n例如,将n设为4将使LARGE返回数据中排名第4的最大元素。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STDEVA', + t: 1, + d: '基于样本计算标准偏差,将文本取值为0。', + a: '基于样本计算标准偏差', + m: [1, 255], + p: [ + { + name: '值1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2,…', + detail: '[可选] - 样本中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STDEVP', + t: 1, + d: '基于样本总体计算标准偏差。', + a: '基于样本总体计算标准偏差。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '数据集中的第一个值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 数据集中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GEOMEAN', + t: 1, + d: '计算数据集的几何平均值。', + a: '计算数据集的几何平均值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 数据集中包含的其他数值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANK_EQ', + t: 1, + d: '返回指定值在数据集中的排名。如果相同的值在数据集中存在多项,则返回其中的最高排名。', + a: '返回指定值在数据集中的排名。如果相同的值在数据集中存在多项,则返回其中的最高排名。', + m: [2, 3], + p: [ + { + name: 'number', + detail: '要确定其排名的值。', + example: 'A10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'ref', + detail: '包含相关数据集的数组或范围。', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'order', + detail: '[可选 - 默认为按降序 (FALSE()) ] - 要按升序还是按降序考虑“data”中的值。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANK_AVG', + t: 1, + d: '返回指定值在数据集中的排名。如果相同的值在数据集中存在多项,则返回这些项排名的平均值。', + a: '返回指定值在数据集中的排名。如果相同的值在数据集中存在多项,则返回这些项排名的平均值。', + m: [2, 3], + p: [ + { + name: 'number', + detail: '要确定其排名的值。', + example: 'A10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'ref', + detail: '包含相关数据集的数组或范围。', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'order', + detail: '[可选 - 默认为按降序 (FALSE()) ] - 要按升序还是按降序考虑“data”中的值。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'PERCENTRANK_EXC', + t: 1, + d: '以百分数形式返回指定值在给定数据集中的百分比排名(介于0和1之间,不包括两端值)。', + a: '以百分数形式返回指定值在给定数据集中的百分比排名(介于0和1之间,不包括两端值)。', + m: [2, 3], + p: [ + { + name: 'data', + detail: '包含相关数据集的数组或范围。', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'x', + detail: '要确定其百分比排位的值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'significance', + detail: '[可选 - 默认值为 3] - 要在计算中使用的有效位数。', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PERCENTRANK_INC', + t: 1, + d: '以百分比形式返回指定值在给定数据集中的百分比排名(介于0和1之间,包括两端值)。', + a: '以百分比形式返回指定值在给定数据集中的百分比排名(介于0和1之间,包括两端值)。', + m: [2, 3], + p: [ + { + name: 'data', + detail: '包含相关数据集的数组或范围。', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'x', + detail: '要确定其百分比排位的值。', + example: ' A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'significance', + detail: '[可选 - 默认值为 3] - 要在计算中使用的有效位数。', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FORECAST', + t: 1, + d: '基于数据集的线性回归,计算指定 x 的预期 y 值。', + a: '基于数据集的线性回归', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'x轴上用于预测的值。', + example: 'A1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FISHERINV', + t: 1, + d: '返回指定数值的 Fisher 逆变换。', + a: '返回指定数值的 Fisher 逆变换。', + m: [1, 1], + p: [ + { + name: 'y', + detail: '要计算其Fisher逆变换的数值。', + example: '0.962', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FISHER', + t: 1, + d: '返回指定数值的 Fisher 变换。', + a: '返回指定数值的 Fisher 变换。', + m: [1, 1], + p: [ + { + name: 'x', + detail: '要计算其Fisher变换的数值。', + example: '0.962', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MODE_SNGL', + t: 1, + d: '返回数据集中出现次数最多的值。', + a: '返回数据集中出现次数最多的值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算模式时要检查的第一个值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 在计算模式时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'WEIBULL_DIST', + t: 1, + d: '给定形状和尺度,返回韦伯分布函数(或韦伯累积分布函数)的值。', + a: '给定形状和尺度', + m: [4, 4], + p: [ + { + name: 'x', + detail: 'WEIBULL 分布函数的输入值。', + example: '2.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'alpha', + detail: 'Weibull 分布函数的形状参数。\n\nalpha 值必须大于 0。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'beta', + detail: 'Weibull 分布函数的尺度参数。\n\nbeta 值必须大于 0。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: 'TRUE() 表示使用累积分布函数,FALSE() 则表示使用概率密度函数。', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'COUNT', + t: 1, + d: '返回数据集中数值的个数。', + a: '返回数据集中数值的个数。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计数时要检查的第一个值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 在计数时要检查的其他值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'COUNTA', + t: 1, + d: '返回数据集中值的数量。', + a: '返回数据集中值的数量。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计数时要检查的第一个值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 在计数时要检查的其他值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVEDEV', + t: 1, + d: '计算数据与数据集均值之间的偏差大小的平均值。', + a: '计算数据与数据集均值之间的偏差大小的平均值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 样本中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'AVERAGE', + t: 1, + d: '返回数据集的算术平均值,对文本忽略不计。', + a: '返回数据集的算术平均值', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算平均值时用到的第一个数值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 在计算平均值时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVERAGEA', + t: 1, + d: '返回数据集的算术平均值。', + a: '返回数据集的算术平均值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算平均值时用到的第一个数值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 在计算平均值时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'BINOM_DIST', + t: 1, + d: '返回一元二项式分布的概率。', + a: '返回一元二项式分布的概率。', + m: [4, 4], + p: [ + { + name: 'number_s', + detail: '试验的成功次数。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'trials', + detail: '独立检验的次数。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'probability_s', + detail: '任一给定检验的成功概率。', + example: '0.005', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '是否使用二项式累积分布。', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'BINOM_INV', + t: 1, + d: '计算累积二项式分布大于或等于指定条件的最小值。', + a: '计算累积二项式分布大于或等于指定条件的最小值。', + m: [3, 3], + p: [ + { + name: 'trials', + detail: '贝努利试验次数。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'probability_s', + detail: '任一次给定检验的成功概率。', + example: '0.005', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'alpha', + detail: '期望的临界概率。', + example: '0.8', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CONFIDENCE_NORM', + t: 1, + d: '计算正态分布的置信区间的一半宽度。', + a: '计算正态分布的置信区间的一半宽度。', + m: [3, 3], + p: [ + { + name: 'alpha', + detail: '用来计算置信水平的显著性水平。\n\n置信水平等于 100*(1 - alpha)%,亦即,如果 alpha 为 0.05,则置信水平为 95%。', + example: '0.05', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_dev', + detail: '数据区域的总体标准偏差。', + example: '1.6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'size', + detail: '样本总量的大小。', + example: '250', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CORREL', + t: 1, + d: '计算给定数据集的皮尔逊积矩相关系数 r。', + a: '计算给定数据集的皮尔逊积矩相关系数 r。', + m: [2, 2], + p: [ + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COVARIANCE_P', + t: 1, + d: '计算数据集的总体协方差。', + a: '计算数据集的总体协方差。', + m: [2, 2], + p: [ + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COVARIANCE_S', + t: 1, + d: '计算数据集的样本协方差。', + a: '计算数据集的样本协方差。', + m: [2, 2], + p: [ + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEVSQ', + t: 1, + d: '基于样本计算其偏差的平方和。', + a: '基于样本计算其偏差的平方和。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 样本中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EXPON_DIST', + t: 1, + d: '返回带有指定 Lambda 和指定值的指数分布函数的值。', + a: '返回带有指定 Lambda 和指定值的指数分布函数的值。', + m: [3, 3], + p: [ + { + name: 'x', + detail: '指数分布函数的输入值。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'lambda', + detail: '用于指定指数分布函数的 lambda 值。', + example: '0.5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '是否使用指数累积分布。', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVERAGEIF', + t: 1, + d: '根据条件返回范围的平均值。', + a: '根据条件返回范围的平均值。', + m: [2, 3], + p: [ + { + name: 'criteria_range', + detail: '要对其检查 criterion 的范围。', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criterion', + detail: '要应用于 criteria_range 的模式或测试条件。\n\n等于:"文本" 或 1 或 "=文本" 或 "=1"\n\n大于:">1"\n\n大于等于:">=1"\n\n小于:"<1"\n\n小于等于:"<=1"\n\n不等于:"<>1"或"<>文本"', + example: '">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'average_range', + detail: '[可选] - 要计算平均值的范围。如果未提供此参数,则改用 criteria_range 来计算平均值。', + example: 'B1:B10', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'AVERAGEIFS', + t: 1, + d: '根据多项条件返回范围的平均值。', + a: '根据多项条件返回范围的平均值。', + m: [2, 255], + p: [ + { + name: 'average_range', + detail: '要计算平均值的范围。', + example: 'A1:A10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria_range1', + detail: '要对其检查 criterion1 的范围。', + example: ' B1:B10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criterion1', + detail: '要应用于 criteria_range1 的模式或测试条件。', + example: ' ">20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria_range2, criterion2, ...', + detail: '[可选] - 要检查的其他范围和条件。', + example: ' C1:C10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'PERMUT', + t: 1, + d: '返回可从数字对象中选择的给定数目对象的排列数。', + a: '返回可从数字对象中选择的给定数目对象的排列数。', + m: [2, 2], + p: [ + { + name: 'number', + detail: '表示对象个数的整数。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_chosen', + detail: '表示每个排列中对象个数的整数。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TRIMMEAN', + t: 1, + d: '在排除数据集高低两端的部分数据之后计算所得的均值。', + a: '在排除数据集高低两端的部分数据之后计算所得的均值。', + m: [2, 2], + p: [ + { + name: '数据', + detail: '包含相关数据集的数组或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: '排除比例', + detail: '要从数据集的极值部分排除的数据占数据集的比例。\n\n排除比例必须大于等于0且小于1。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PERCENTILE_EXC', + t: 1, + d: '返回数组的 K 百分点值,K 介于 0 到 1 之间,不含 0 与 1。', + a: '返回数组的 K 百分点值,K 介于 0 到 1 之间,不含 0 与 1。', + m: [2, 2], + p: [ + { + name: 'array', + detail: '定义相对位置的数组或数据区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'k', + detail: '0 到 1 之间的百分点值,不包含 0 和 1。', + example: '0.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PERCENTILE_INC', + t: 1, + d: '返回数组的 K 百分点值,K 介于 0 到 1 之间,包含 0 与 1。', + a: '返回数组的 K 百分点值,K 介于 0 到 1 之间,包含 0 与 1。', + m: [2, 2], + p: [ + { + name: 'array', + detail: '定义相对位置的数组或数据区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'k', + detail: '0 到 1 之间的百分点值,包含 0 和 1。', + example: '0.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PEARSON', + t: 1, + d: '返回皮尔生(Pearson)乘积矩相关系数 r。', + a: '返回皮尔生(Pearson)乘积矩相关系数 r。', + m: [2, 2], + p: [ + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NORM_S_INV', + t: 1, + d: '返回标准正态累积分布函数的反函数值。 该分布的平均值为 0,标准偏差为 1。', + a: '返回标准正态累积分布函数的反函数值。 该分布的平均值为 0,标准偏差为 1。', + m: [1, 1], + p: [ + { + name: 'probability', + detail: '对应于正态分布的概率。', + example: '0.75', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NORM_S_DIST', + t: 1, + d: '返回标准正态分布函数(该分布的平均值为 0,标准偏差为 1)。', + a: '返回标准正态分布函数(该分布的平均值为 0,标准偏差为 1)。', + m: [2, 2], + p: [ + { + name: 'z', + detail: '需要计算其分布的数值。', + example: '2.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '决定函数形式的逻辑值。\n\n如果为 TRUE(),则返回累积分布函数;\n\n如果为 FALSE(),则返回概率密度函数。', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NORM_INV', + t: 1, + d: '返回指定平均值和标准偏差的正态累积分布函数的反函数值。', + a: '返回指定平均值和标准偏差的正态累积分布函数的反函数值。', + m: [3, 3], + p: [ + { + name: 'probability', + detail: '对应于正态分布的概率。', + example: '0.75', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: '分布的算术平均值。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_dev', + detail: '分布的标准偏差。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NORM_DIST', + t: 1, + d: '返回指定平均值和标准偏差的正态分布函数。', + a: '返回指定平均值和标准偏差的正态分布函数。', + m: [4, 4], + p: [ + { + name: 'x', + detail: '需要计算其分布的数值。', + example: '2.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: '分布的算术平均值。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_dev', + detail: '分布的标准偏差。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '决定函数形式的逻辑值。\n\n如果为 TRUE(),则返回累积分布函数;\n\n如果为 FALSE(),则返回概率密度函数。', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NEGBINOM_DIST', + t: 1, + d: '返回负二项式分布。', + a: '返回负二项式分布。', + m: [4, 4], + p: [ + { + name: 'number_f', + detail: '要模拟的失败次数。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number_s', + detail: '要模拟的成功次数。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'probability_s', + detail: '任一次给定检验的成功概率。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '决定函数形式的逻辑值。\n\n如果为 TRUE(),则返回累积分布函数;\n\n如果为 FALSE(),则返回概率密度函数。', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'MINA', + t: 1, + d: '返回数据集中的最小数值。', + a: '返回数据集中的最小数值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算最小值时所用的第一个值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2', + detail: '[可选] - 在计算最小值时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MIN', + t: 1, + d: '返回数值数据集中的最小值。', + a: '返回数值数据集中的最小值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算最小值时所用的第一个值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2', + detail: '[可选] - 在计算最小值时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MEDIAN', + t: 1, + d: '返回数值数据集中的中值。', + a: '返回数值数据集中的中值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算中值时所用的第一个数值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2', + detail: '[可选] - 在计算中值时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MAXA', + t: 1, + d: '返回数据集中的最大数值。', + a: '返回数据集中的最大数值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算最大值时所用的第一个值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 在计算最大值时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MAX', + t: 1, + d: '返回数值数据集中的最大值。', + a: '返回数值数据集中的最大值。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '计算最大值时所用的第一个值或范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2', + detail: '[可选] - 在计算最大值时要考虑的其他数值或范围。', + example: 'B2:B100', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOGNORM_INV', + t: 1, + d: '返回 x 的对数累积分布函数的反函数值。', + a: '返回 x 的对数累积分布函数的反函数值。', + m: [3, 3], + p: [ + { + name: 'probability', + detail: '与对数分布相关的概率,介于 0 与 1 之间(不含 0 与 1)。', + example: '0.4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'ln(x) 的平均值。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_dev', + detail: 'ln(x) 的标准偏差,正数。', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LOGNORM_DIST', + t: 1, + d: '返回 x 的对数分布函数。', + a: '返回 x 的对数分布函数。', + m: [4, 4], + p: [ + { + name: 'x', + detail: '用来计算函数的值。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: 'ln(x) 的平均值。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_dev', + detail: 'ln(x) 的标准偏差,正数。', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '决定函数形式的逻辑值。\n\n如果为 TRUE(),则返回累积分布函数;\n\n如果为 FALSE(),则返回概率密度函数。', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'Z_TEST', + t: 1, + d: '返回 z 检验的单尾 P 值。', + a: '返回 z 检验的单尾 P 值。', + m: [2, 3], + p: [ + { + name: 'array', + detail: '用来检验 x 的数组或数据区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'x', + detail: '要测试的值。', + example: 'B2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'sigma', + detail: '[可选] - 总体(已知)标准偏差。 如果省略,则使用样本标准偏差。', + example: '3', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PROB', + t: 1, + d: '返回区域中的数值落在指定区间内的概率。', + a: '返回区域中的数值落在指定区间内的概率。', + m: [3, 4], + p: [ + { + name: 'x_range', + detail: '具有各自相应概率值的 x 数值区域。', + example: 'A3:A6', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'prob_range', + detail: '与 x_range 中的值相关联的一组概率值。', + example: '2', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'lower_limit', + detail: '要计算其概率的数值下界。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'upper_limit', + detail: '[可选 - 默认值为下界] - 要计算其概率的可选数值上界。\n\n如果省略上界,PROB则计算随机选取相应值的次数恰好等于下界的概率。', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'QUARTILE_EXC', + t: 1, + d: '基于 0 到 1 之间(不包括 0 和 1)的百分点值返回数据集的四分位数。', + a: '基于 0 到 1 之间(不包括 0 和 1)的百分点值返回数据集的四分位数。', + m: [2, 2], + p: [ + { + name: 'array', + detail: '要求得四分位数值的数组或数字型单元格区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'quart', + detail: '要返回第几个四分位值。\n\n1返回数据中最靠近第一个四分位值的值(25%标记)。\n\n2返回数据中最接近中值的值(50%标记)。\n\n3返回数据中最接近第三个四分位值的值(75%标记)。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'QUARTILE_INC', + t: 1, + d: '根据 0 到 1 之间的百分点值(包含 0 和 1)返回数据集的四分位数。', + a: '根据 0 到 1 之间的百分点值(包含 0 和 1)返回数据集的四分位数。', + m: [2, 2], + p: [ + { + name: 'array', + detail: '要求得四分位数值的数组或数字型单元格区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'quart', + detail: '要返回第几个四分位值。\n\n0返回数据中的最小值(0%标记)。\n\n1返回数据中最靠近第一个四分位值的值(25%标记)。\n\n2返回数据中最接近中值的值(50%标记)。\n\n3返回数据中最接近第三个四分位值的值(75%标记)。\n\n4返回数据中的最大值(100%标记)。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'POISSON_DIST', + t: 1, + d: '返回泊松分布。', + a: '返回泊松分布。', + m: [3, 3], + p: [ + { + name: 'x', + detail: '事件数。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: '期望值。非负数', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '一逻辑值,确定所返回的概率分布的形式。\n\n如果为 TRUE(),则返回发生的随机事件数在零(含零)和 x(含 x)之间的累积泊松概率;\n\n如果为 FALSE(),则返回发生的事件数正好是 x 的泊松概率密度函数。', + example: 'FALSE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RSQ', + t: 1, + d: '返回皮尔生(Pearson)乘积矩相关系数 r 的平方。', + a: '返回皮尔生(Pearson)乘积矩相关系数 r 的平方。', + m: [2, 2], + p: [ + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_DIST', + t: 1, + d: '返回学生的左尾 t 分布。', + a: '返回学生的左尾 t 分布。', + m: [3, 3], + p: [ + { + name: 'x', + detail: 'T-分布函数的输入。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: '自由度数值。', + example: '30', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'tails', + detail: '决定函数形式的逻辑值。\n\n如果 cumulative 为 TRUE(),则 HYPGEOM.DIST 返回累积分布函数;\n\n如果为 FALSE(),则返回概率密度函数。', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'T_DIST_2T', + t: 1, + d: '返回学生的双尾 t 分布。', + a: '返回学生的双尾 t 分布。', + m: [2, 2], + p: [ + { + name: 'x', + detail: 'T-分布函数的输入。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: '自由度数值。', + example: '30', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_DIST_RT', + t: 1, + d: '返回学生的右尾 t 分布。', + a: '返回学生的右尾 t 分布。', + m: [2, 2], + p: [ + { + name: 'x', + detail: 'T-分布函数的输入。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom', + detail: '自由度数值。', + example: '30', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_INV', + t: 1, + d: '返回学生的 t 分布的左尾反函数。', + a: '返回学生的 t 分布的左尾反函数。', + m: [2, 2], + p: [ + { + name: 'probability', + detail: '与学生的 t 分布相关的概率。\n\n必须大于 0 且小于 1。', + example: '0.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'deg_freedom', + detail: '自由度数值。\n\n如果所提供的参数不是整数,将截取其整数部分。\n\n必须大于等于 1。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_INV_2T', + t: 1, + d: '返回学生 t 分布的双尾反函数。', + a: '返回学生 t 分布的双尾反函数。', + m: [2, 2], + p: [ + { + name: 'probability', + detail: '与学生的 t 分布相关的概率。\n\n必须大于 0 且小于1。', + example: '0.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'deg_freedom', + detail: '自由度数值。\n\n如果所提供的参数不是整数,将截取其整数部分。\n\n必须大于等于 1。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'T_TEST', + t: 1, + d: '返回与t-检验相关的概率。用于判断两个样本是否可能是出自平均值相同的两个样本总体。', + a: '返回与t-检验相关的概率。用于判断两个样本是否可能是出自平均值相同的两个样本总体。', + m: [4, 4], + p: [ + { + name: 'array1', + detail: '将用于 t 检验的第一个数据样本或第一组单元格。', + example: 'A1:A4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array2', + detail: '将用于 t 检验的第二个数据样本或第二组单元格。', + example: 'B1:B4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'tails', + detail: '指定分布的尾数。\n\n如果为 1:使用单尾分布。\n\n如果为 2:使用双尾分布。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '指定 t 检验的类型。\n\n如果为 1:执行配对检验。\n\n如果为 2:执行双样本等方差(同方差)检验。\n\n如果为3:执行双样本不等方差(异方差)检验。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'F_DIST', + t: 1, + d: '给定输入值 x,计算两个数据集的左尾 F 概率分布(差异程度)。此分布也称为 Fisher-Snedecor 分布或 Snedecor F 分布。', + a: '给定输入值 x', + m: [4, 4], + p: [ + { + name: 'x', + detail: '用来计算函数的值。', + example: '15.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom1', + detail: '分子自由度。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom2', + detail: '分母自由度。', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cumulative', + detail: '用于确定函数形式的逻辑值。默认值为 FALSE。\n\n如果为 TRUE():F.DIST 将返回累积分布函数值。\n\n如果为 FALSE():F.DIST 将返回概率密度函数值。', + example: 'TRUE()', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'F_DIST_RT', + t: 1, + d: '给定输入x,计算两个数据集的右尾F概率分布(差异程度)。 此分布也称为Fisher-Snedecor分布或Snedecor F分布。', + a: '给定输入x', + m: [3, 3], + p: [ + { + name: 'x', + detail: '用来计算函数的值。', + example: '15.35', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom1', + detail: '分子自由度。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'degrees_freedom2', + detail: '分母自由度。', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VAR_P', + t: 1, + d: '基于样本总体计算方差。', + a: '基于样本总体计算方差。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '数据集中的第一个值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, …', + detail: '[可选] - 数据集中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VAR_S', + t: 1, + d: '基于样本计算方差。', + a: '基于样本计算方差。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, …', + detail: '[可选] - 样本中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VARA', + t: 1, + d: '基于样本计算方差,将文本取值为0。', + a: '基于样本计算方差', + m: [1, 255], + p: [ + { + name: 'value1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2, ...', + detail: '[可选] - 样本中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'VARPA', + t: 1, + d: '基于样本总体计算方差,将文本取值为0。', + a: '基于样本总体计算方差', + m: [1, 255], + p: [ + { + name: '值1', + detail: '样本中的第一项值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '值2, ...', + detail: '[可选] - 数据集中包含的其他数值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STEYX', + t: 1, + d: '返回通过线性回归法预测每个 x 的 y 值时所产生的标准误差。', + a: '返回通过线性回归法预测每个 x 的 y 值时所产生的标准误差。', + m: [2, 2], + p: [ + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'STANDARDIZE', + t: 1, + d: '给定分布的均值和标准偏差,计算一个随机变量正态化的相应值。', + a: '给定分布的均值和标准偏差,计算一个随机变量正态化的相应值。', + m: [3, 3], + p: [ + { + name: 'x', + detail: '要正态化的随机变量值。', + example: '96', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'mean', + detail: '分布的均值。', + example: '80', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'standard_dev', + detail: '分布的标准偏差。', + example: '6.7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SMALL', + t: 1, + d: '返回数据集中的第 k 个最小值。', + a: '返回数据集中的第 k 个最小值。', + m: [2, 2], + p: [ + { + name: 'array', + detail: '需要找到第 k 个最小值的数组或数值数据区域。', + example: 'A2:B100', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'k', + detail: '要返回的数据在数组或数据区域里的位置(从小到大)。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SLOPE', + t: 1, + d: '计算通过数据集的线性回归得到的直线的斜率。', + a: '计算通过数据集的线性回归得到的直线的斜率。', + m: [2, 2], + p: [ + { + name: '数据_y', + detail: '代表因变量数据数组或矩阵的范围。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '数据_x', + detail: '代表自变量数据数组或矩阵的范围。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SKEW', + t: 1, + d: '返回分布的偏斜度。 偏斜度表明分布相对于平均值的不对称程度。 正偏斜度表明分布的不对称尾部趋向于更多正值。 负偏斜度表明分布的不对称尾部趋向于更多负值。', + a: '返回分布的偏斜度。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '数据集中的第一个值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 数据集中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'SKEW_P', + t: 1, + d: '返回基于样本总体的分布不对称度:表明分布相对于平均值的不对称程度。', + a: '返回基于样本总体的分布不对称度:表明分布相对于平均值的不对称程度。', + m: [1, 255], + p: [ + { + name: '值1', + detail: '数据集中的第一个值或范围。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '值2, ...', + detail: '[可选] - 数据集中包含的其他值或范围。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'VLOOKUP', + t: 2, + d: '纵向查找。在范围的第一列中自上而下搜索某个键值,并返回所找到的行中指定单元格的值。', + a: '纵向查找。在范围的第一列中自上而下搜索某个键值', + m: [3, 4], + p: [ + { + name: '搜索键值', + detail: '要搜索的值,如 42、"Cats" 或 I24。', + example: '10003', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '范围', + detail: '要进行搜索的范围。VLOOKUP 将在该范围的第一列中搜索搜索键值中指定的键值。', + example: 'A2:B26', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '索引', + detail: '要返回的值的列索引,范围中的第一列编号为 1。\n\n如果索引不是介于 1 和范围中的列数之间,将返回 #VALUE! 。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '已排序', + detail: '[默认值为 TRUE() ] - 指示要搜索的列(指定范围的第一列)是否已排序。大多数情况下,建议设为 FALSE()。\n\n建议将已排序设为 FALSE。如果设为 FALSE,将返回完全匹配项。如果存在多个匹配值,将返回找到的第一个值对应的单元格的内容,如果找不到匹配值,则返回 #N/A。\n\n如果将已排序设为 TRUE 或省略,将返回(小于或等于搜索键值的)最接近的匹配项。如果搜索的列中所有的值均大于搜索键值,则返回 #N/A。', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'HLOOKUP', + t: 2, + d: '横向查找。在范围的第一行中搜索某个键值,并返回所找到的列中指定单元格的值。', + a: '横向查找。在范围的第一行中搜索某个键值', + m: [3, 4], + p: [ + { + name: '搜索键值', + detail: '要搜索的值。例如,42、"Cats"或I24。', + example: '10003', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '范围', + detail: '要进行搜索的范围。将在该范围的第一行中搜索在搜索键值中指定的键值。', + example: 'A2:Z6', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '索引', + detail: '要返回的值的行索引,范围中的第一行编号为1。\n\n如果索引不是介于1和范围中的行数之间,将返回#VALUE!。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '已排序', + detail: '[可选 - 默认值为TRUE()] - 指示要搜索的行(指定范围的第一行)是否已排序。\n\n如果将已排序设为TRUE或省略,将返回最接近的匹配值(小于或等于搜索键值)。如果在搜索的行中所有的值均大于搜索键值,则返回#N/A。\n\n如果将已排序设为TRUE或将其省略,而范围的首行并非处于已排序状态,则返回值可能会是错误的。\n\n如果将已排序设为FALSE,则仅返回完全匹配。如果存在多个匹配值,将返回与找到的第一个值对应的单元格的内容,如果找不到匹配值则返回#N/A。', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LOOKUP', + t: 2, + d: '在行或列中查找相应键,并将相应单元格的值返回到与搜索行或列所在位置相同的结果范围中。', + a: '在行或列中查找相应键', + m: [2, 3], + p: [ + { + name: '搜索键值', + detail: '要在行或列中搜索的值。例如,42、"Cats" 或 I24。', + example: '10003', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '搜索范围 | 搜索结果数组', + detail: '使用 LOOKUP 的一种方法是给定单行或单列形式的搜索范围进行搜索查找,这种方式要用到另一个参数结果范围。另一种方式是将这两个参数合并为一个搜索结果数组,其中第一行或第一列用于搜索,并将返回值放在该数组的最后一行或最后一列中。', + example: 'A1:A100', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '结果范围', + detail: '[ 可选 ] - 用于存放返回结果的范围。返回值对应于在搜索范围中找到搜索键值的位置。此范围必须仅为单行或单列,而如果您使用的是搜索结果数组方式,则不应提供此参数。', + example: 'B1:B100', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ADDRESS', + t: 2, + d: '返回字符串形式的单元格引用。', + a: '返回字符串形式的单元格引用。', + m: [2, 5], + p: [ + { + name: 'row_num', + detail: '一个数值,指定要在单元格引用中使用的行号。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'column_num', + detail: '一个数值,指定要在单元格引用中使用的列号(而非名称)。A列的编号为1。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'abs_num', + detail: '[可选 - 默认值为1] - 一个数值,指定要返回的引用类型。\n\n1 表示行列均采用绝对值(例如$A$1);\n\n2 表示采用绝对行号,相对列标(例如A$1);\n\n3 表示采用相对行号,绝对列标(例如$A1);\n\n4 表示行列均采用相对值(例如A1)。', + example: '4', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'A1', + detail: '[可选 - 默认值为TRUE()] - 一个布尔值,指示采用A1标记形式(TRUE)还是R1C1标记形式(FALSE)。', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'sheet_text', + detail: '[可选 - 默认缺省] - 用于指定地址所指向的工作表名称。', + example: '"Sheet2"', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'INDIRECT', + t: 2, + d: '返回以字符串指定的单元格引用。', + a: '返回以字符串指定的单元格引用。', + m: [1, 2], + p: [ + { + name: 'ref_text', + detail: '以带引号的字符串形式提供的单元格引用。', + example: '"Sheet2!"&B10', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'A1', + detail: '[可选 - 默认值为TRUE()] - 一个布尔值,指示采用A1标记形式(TRUE)还是R1C1标记形式(FALSE)。', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ROW', + t: 2, + d: '返回指定单元格的行号', + a: '返回指定单元格的行号', + m: [0, 1], + p: [ + { + name: 'reference', + detail: '[可选 - 默认为此公式所在的单元格] - 要返回其行号的单元格。\n\n如果单元格引用指向的范围其宽度大于一个单元格,而此公式不是用作数组公式的,这时会仅返回单元格引用中首行的编号值。', + example: 'A9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ROWS', + t: 2, + d: '返回指定数组或范围中的行数。', + a: '返回指定数组或范围中的行数。', + m: [1, 1], + p: [ + { + name: 'array', + detail: '要返回其行数的范围。', + example: 'A9:A62', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COLUMN', + t: 2, + d: '按照 `A=1` 的规则返回指定单元格的列号。', + a: '按照 `A=1` 的规则返回指定单元格的列号。', + m: [0, 1], + p: [ + { + name: 'reference', + detail: '[可选 - 默认为包含此公式的单元格] - 要返回其列号的单元格。A列对应的编号为1。\n\n如果单元格引用是宽度超过一个单元格的范围,而此公式不是作为数组公式来使用的,因此将返回单元格引用中的第一列的位置。', + example: 'A9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'COLUMNS', + t: 2, + d: '返回指定数组或范围中的列数。', + a: '返回指定数组或范围中的列数。', + m: [1, 1], + p: [ + { + name: 'array', + detail: '要返回其列数的范围。', + example: 'A9:W62', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'OFFSET', + t: 2, + d: '给定某范围的起始单元格引用以及该范围涵盖的行列数量,返回该范围的引用。', + a: '给定某范围的起始单元格引用以及该范围涵盖的行列数量,返回该范围的引用。', + m: [3, 5], + p: [ + { + name: 'reference', + detail: '用于计算行列偏移量的起点。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'rows', + detail: '要偏移的行数。\n\n行偏移量必须是整数,但也可以是负数。如果提供的参数带有小数,小数部分将被截去。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cols', + detail: '要偏移的列数。\n\n列偏移量必须是整数,但也可以是负数。如果提供的参数带有小数,小数部分将被截去。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'height', + detail: '[可选] - 要从偏移目标开始返回的范围的高度。', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'width', + detail: '[可选] - 要从偏移目标开始返回的范围的宽度。', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MATCH', + t: 2, + d: '在单元格中搜索指定项,然后返回该项在单元格区域中的相对位置。', + a: '在单元格中搜索指定项,然后返回该项在单元格区域中的相对位置。', + m: [2, 3], + p: [ + { + name: 'lookup_value', + detail: '要在 lookup_array 中匹配的值。', + example: '"Sunday"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'lookup_array', + detail: '要搜索的单元格区域。\n\n如果所用的范围的高度和宽度均大于1,MATCH将返回#N/A!。', + example: 'A2:A9', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'match_type', + detail: '[可选 - 默认值为1] - 要采用的搜索方式。\n\n1为默认类型,此时MATCH会假设范围已按升序排序,并返回小于等于搜索键值的最大值。\n\n0表示完全匹配,在范围未排序的情况下需要使用此方式。\n\n-1让MATCH假设范围是按降序排序的,并返回大于等于搜索键值的最小值。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'INDEX', + t: 2, + d: '返回表格或中的元素值,此元素由行号和列号的索引值给定。', + a: '返回表格或中的元素值,此元素由行号和列号的索引值给定。', + m: [2, 3], + p: [ + { + name: 'array', + detail: '单元格区域或数组常量。', + example: 'A1:C20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'row_num', + detail: '选择数组中的某行,函数从该行返回数值。', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'column_num', + detail: '选择数组中的某列,函数从该列返回数值。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GETPIVOTDATA', + t: 2, + d: '从与指定行和列标题对应的数据透视表中提取汇总值。', + a: '从与指定行和列标题对应的数据透视表中提取汇总值。', + m: [2, 254], + p: [ + { + name: 'data_field', + detail: '您想从数据透视表中获取其数据的值名称。\n值名称必须括在引号中或是指向包含相关文本的任何单元格的引用。\n如果有多个值字段,则必须使用数据透视表中显示的确切名称(如“销售总额”)。', + example: '"SUM of number of units"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'pivot_table', + detail: '目标数据透视表中的任何单元格的引用(推荐位于顶角的单元格)。', + example: "'Pivot table'!A1", + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'field1', + detail: '[可选] - 源数据集(不是数据透视表)中列的名称。', + example: '"division"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + { + name: 'item1', + detail: '[可选] - 数据透视表中显示的与您要检索的字段名称 1 相对应的行或列的名称。', + example: '"east"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'CHOOSE', + t: 2, + d: '基于索引返回选项列表中的元素。', + a: '基于索引返回选项列表中的元素。', + m: [2, 255], + p: [ + { + name: 'index_num', + detail: '指定要返回哪一项。\n\n如果索引为零、负值或大于提供的选择数量,将返回#VALUE!错误。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value1', + detail: '一项可能的返回值。必须提供。可以是单元格引用或单独的值。', + example: '"A"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '[可选] - 其他可以选择的值。选择', + example: '"B"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'HYPERLINK', + t: 2, + d: '在单元格内创建一个超链接。', + a: '在单元格内创建一个超链接。', + p: [ + { + name: '网址', + detail: '以引号括住的链接位置的完整网址,或对包含这种网址的单元格的引用。\n\n仅允许某些链接类型。其中包括:http://、https://、mailto:、aim:、ftp://、gopher://、telnet://和news://,明确禁用使用其他协议。如果指定的是其他协议,将会在单元格中显示链接标签,但该标签不会以链接形式呈现。\n\n如果未指定协议,则假设使用http://,并将其作为网址的前缀。', + example: '"http://www.google.com/"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '链接标签', + detail: '[可选 - 默认为网址] - 要在单元格中作为链接显示的文本(用引号括起来的),或者指向包含这种标签的单元格的引用。\n\n如果链接标签是指向某个空单元格的引用,如果网址有效,就将其作为链接显示,否则作为纯文本显示。\n\n如果链接标签为空字符串常量(""),所在单元格显示的内容将为空白,但通过点击该单元格或转入该单元格仍然可以访问链接。', + example: '"Google"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TIME', + t: 6, + d: '将给定的小时、分钟和秒转换为时间。', + a: '将给定的小时、分钟和秒转换为时间。', + m: [3, 3], + p: [ + { + name: '小时', + detail: '0(零)到 32767 之间的数字,代表小时。\n\n任何大于 23 的值都会除以 24,余数将作为小时值。', + example: '11', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '分钟', + detail: '0(零)到 32767 之间的数字,代表分钟。\n\n任何大于 59 的值将转换为小时和分钟。', + example: '40', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '秒', + detail: '0(零)到 32767 之间的数字,代表秒。\n\n任何大于 59 的值将转换为小时、分钟和秒。', + example: '59', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TIMEVALUE', + t: 6, + d: '按一天24小时返回该时间的分数表示。', + a: '按一天24小时返回该时间的分数表示。', + m: [1, 1], + p: [ + { + name: 'time_text', + detail: '用于表示时间的字符串。', + example: '"2:15 PM"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EOMONTH', + t: 6, + d: '返回某个月份最后一天的序列号,该月份在另一个日期之前或之后的数个月(月数由参数指定)。', + a: '返回某个月份最后一天的序列号', + m: [2, 2], + p: [ + { + name: 'start_date', + detail: '用于计算结果的参照日期。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'months', + detail: '用于计算的起始日期之前(负)或之后(正)的月数。返回的是计算所得月份的最后那天。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EDATE', + t: 6, + d: '返回表示某个日期的序列号,该日期在另一个日期的数月之前/之后。', + a: '返回表示某个日期的序列号', + m: [2, 2], + p: [ + { + name: 'start_date', + detail: '用于计算结果的参照日期。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'months', + detail: '用于计算的起始日期之前(负)或之后(正)的月数。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SECOND', + t: 6, + d: '返回时间值的秒数。 秒数是 0(零)到 59 范围内的整数。', + a: '返回时间值的秒数。 秒数是 0(零)到 59 范围内的整数。', + m: [1, 1], + p: [ + { + name: '时间', + detail: '用于计算秒钟部分的时间。必须为以下值之一:指向包含日期/时间值的单元格的引用、返回日期/时间的函数或者数字。', + example: 'TIME(11', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'MINUTE', + t: 6, + d: '以数字格式返回特定时间的分钟部分。', + a: '以数字格式返回特定时间的分钟部分。', + m: [1, 1], + p: [ + { + name: '时间', + detail: '用于计算分钟部分的时间。必须为以下值之一:指向包含日期/时间值的单元格的引用、返回日期/时间的函数或者数字。', + example: 'TIME(11', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'HOUR', + t: 6, + d: '以数字格式返回特定时间的小时部分。', + a: '以数字格式返回特定时间的小时部分。', + m: [1, 1], + p: [ + { + name: '时间', + detail: '用于计算小时部分的时间。必须为以下值之一:指向包含日期/时间值的单元格的引用、返回日期/时间的函数或者数字。', + example: 'TIME(11', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NOW', + t: 6, + d: '以日期值格式返回当前日期和时间。', + a: '以日期值格式返回当前日期和时间。', + m: [0, 0], + p: [], + }, + { + n: 'NETWORKDAYS', + t: 6, + d: '返回所提供的两个日期之间的净工作日天数。', + a: '返回所提供的两个日期之间的净工作日天数。', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: '用于计算净工作日天数的时间段开始日期。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: '用于计算净工作日天数的时间段结束日期。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'holidays', + detail: '[可选] - 一个范围或数组常量,其中包含作为节假日的日期序号。\n\n在节假日数组中提供的值必须是日期序号值(例如由N所返回的值)或日期值(例如由DATE、DATEVALUE或TO_DATE返回的值)。由范围指定的值应该是标准的日期值或日期序数值。', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NETWORKDAYS_INTL', + t: 6, + d: '返回给定的两个日期之间的净工作日天数(排除指定的周末和节假日)。', + a: '返回给定的两个日期之间的净工作日天数(排除指定的周末和节假日)。', + m: [2, 4], + p: [ + { + name: 'start_date', + detail: '用于计算净工作日天数的时间段开始日期。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: '用于计算净工作日天数的时间段结束日期。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'weekend', + detail: '[可选 - 默认值为1] - 用于表示哪些天为周末的数字或字符串。\n字符串方式:可以使用由0和1组成的字符串来指定周末,串中的第一个数字字符代表周一,最后一个则代表周日。零表示这一天是工作日,1表示这一天为周末。例如,“0000011”表示将周六和周日作为周末。\n数字方式:这种方式不使用上述字符串形式,而是使用一个数字。1 =周六/周日为周末,2 =周日/周一为周末,依此类推则7 =周五/周六。11 =周日为唯一周末,12 =周一为唯一周末,依此类推则17 =周六为唯一周末。', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'holidays', + detail: '[ 可选 ] - 这是一个范围或数组常量,其中包含作为节假日的日期。\n在节假日数组内提供的值必须为日期序数值(例如N的返回值)或日期值(例如DATE、DATEVALUE或TO_DATE的返回值)。由范围指定的值应该是标准的日期值或日期序数值。', + example: 'DATE(1969', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISOWEEKNUM', + t: 6, + d: '返回给定日期在全年中的 ISO 周数。', + a: '返回给定日期在全年中的 ISO 周数。', + m: [1, 1], + p: [ + { + name: 'date', + detail: '用于日期和时间计算的日期-时间代码。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'WEEKNUM', + t: 6, + d: '返回特定日期的周数。', + a: '返回特定日期的周数。', + m: [1, 2], + p: [ + { + name: 'serial_number', + detail: '要确定其位于第几周的日期,必须是对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'return_type', + detail: '[可选 - 默认值为 1 ] - 代表一周起始日的数字,系统也使用该数字来确定一年的第一周(1=周日,2=周一)。', + example: '7', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'WEEKDAY', + t: 6, + d: '返回一个数字,对应于给定日期所在的星期几。', + a: '返回一个数字,对应于给定日期所在的星期几。', + m: [1, 2], + p: [ + { + name: 'serial_number', + detail: '要为其确定星期几的日期。必须是对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'return_type', + detail: '[可选 - 默认值为 1] - 以数字指示使用哪种编号顺序来表示星期几。默认情况下,按星期日 (= 1) 开始计算。\n\n如果类型为 1,则星期值将从星期日开始算起,并且星期日的值为 1,因此星期六的值就是 7。\n\n如果类型为 2,则星期值将从星期一开始算起,并且星期一的值为 1,因此星期日的值就是 7。\n\n如果类型为 3,则星期值将从星期一算起,并且星期一的值为 0,因此星期日的值就是 6。', + example: '7', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DAY', + t: 6, + d: '以数字格式返回特定日期所在的当月几号。', + a: '以数字格式返回特定日期所在的当月几号。', + m: [1, 1], + p: [ + { + name: 'serial_number', + detail: '要从中提取具体几号的日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DAYS', + t: 6, + d: '返回两个日期之间的天数。', + a: '返回两个日期之间的天数。', + m: [2, 2], + p: [ + { + name: 'end_date', + detail: '计算中要使用的结束日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: '2011-3-15', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'start_date', + detail: '计算中要使用的开始日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: '2011-2-1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DAYS360', + t: 6, + d: '按照每年360天,返回两个日期之间的差(用于计算利息)。', + a: '按照每年360天,返回两个日期之间的差(用于计算利息)。', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: '计算中要使用的开始日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: '计算中要使用的结束日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'method', + detail: '[可选 - 默认为FALSE()] - 指示要使用哪种天数计算方法。\n\nFALSE - 采用美国 (NASD) 方法时,如果起始日期为某月的最后一天,为便于计算,会将起始日期的当月几号更改为30。此外,如果结束日期是所在月份的最后一天,而且起始日期在其所在月的30号之前,则将结束日期更改为结束日期之后那个月的第一天,否则将结束日期更改为该月的30号。\n\nTRUE - 采用欧洲方法时,会将所有日期在31号的起始日期或结束日期更改为当月的30号。', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DATE', + t: 6, + d: '将提供的年、月、日转换为日期。', + a: '将提供的年、月、日转换为日期。', + m: [3, 3], + p: [ + { + name: 'year', + detail: '日期的年份部分,包含一到四位数字。\n\n介于 0(零)到 1899 之间,会将该值与 1900 相加来计算年份;\n\n介于 1900 到 9999 之间,将使用该数值作为年份;\n\n小于 0 或大于等于 10000,返回 错误值 #NUM!。', + example: '1969', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'month', + detail: '日期的月份部分,一个正整数或负整数。\n\n如果 month 大于 12,则 month 会将该月份数与指定年中的第一个月相加。\n\n如果 month 小于 1,month 则从指定年份的一月份开始递减该月份数,然后再加上 1 个月。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'day', + detail: '日期的日部分,一个正整数或负整数。\n\n如果 day 大于月中指定的天数,则 day 会将天数与该月中的第一天相加。\n\n如果 day 小于 1,则 day 从指定月份的第一天开始递减该天数,然后再加上 1 天。', + example: '20', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DATEVALUE', + t: 6, + d: '将提供的日期字符串转换为日期的序列号。', + a: '将提供的日期字符串转换为日期的序列号。', + m: [1, 1], + p: [ + { + name: 'date_text', + detail: '表示日期的字符串。', + example: '"1969-7-20"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DATEDIF', + t: 6, + d: '计算两个日期之间的天数、月数或年数。', + a: '计算两个日期之间的天数、月数或年数。', + m: [3, 3], + p: [ + { + name: '起始日期', + detail: '计算中要使用的开始日期。必须是对包含DATE值的单元格的引用、返回DATE类型的函数或数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '结束日期', + detail: '计算中要使用的结束日期。必须是对包含DATE值的单元格的引用、返回DATE类型的函数或数字。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '单位', + detail: '时间单位的缩写文字。例如 "M" 代表月。有效值包括:"Y"、"M"、"D"、"MD"、"YM" 和 "YD"。\n\n"Y":返回起始日期和结束日期之间的整年数。\n\n"M":返回起始日期和结束日期之间的整月数。\n\n"D":返回起始日期和结束日期之间的天数。\n\n"MD":返回起始日期和结束日期之间的天数(不计整月数)。\n\n"YM":返回起始日期和结束日期之间的整月数(不计整年数)。\n\n"YD":返回起始日期和结束日期之间的天数(假设起始日期和结束日期的间隔不超过一年)。', + example: '16)', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'WORKDAY', + t: 6, + d: '指定工作日天数,计算结束日期。', + a: '指定工作日天数,计算结束日期。', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: '计算的开始日期。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'days', + detail: 'start_date 之前或之后不含周末及节假日的天数。\n\n为正值将生成未来日期;\n\n为负值生成过去日期。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'holidays', + detail: '[可选] - 一个范围或数组常量,其中包含作为节假日的日期序号。\n\n在节假日数组中提供的值必须是日期序号值(例如由N所返回的值)或日期值(例如由DATE、DATEVALUE或TO_DATE返回的值)。由范围指定的值应该是标准的日期值或日期序数值。', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'WORKDAY_INTL', + t: 6, + d: '返回指定的若干个工作日之前或之后的日期的序列号(使用自定义周末参数)。 ', + a: '返回指定的若干个工作日之前或之后的日期的序列号(使用自定义周末参数)。 ', + m: [2, 4], + p: [ + { + name: 'start_date', + detail: '开始日期(将被截尾取整)。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'days', + detail: 'start_date 之前或之后的工作日的天数。\n\n正值表示未来日期;\n\n负值表示过去日期;\n\n零值表示开始日期。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'weekend', + detail: '[可选 - 默认值为1] - 用于表示哪些天为周末的数字或字符串。\n字符串方式:可以使用由0和1组成的字符串来指定周末,串中的第一个数字字符代表周一,最后一个则代表周日。零表示这一天是工作日,1表示这一天为周末。例如,“0000011”表示将周六和周日作为周末。\n数字方式:这种方式不使用上述字符串形式,而是使用一个数字。1 =周六/周日为周末,2 =周日/周一为周末,依此类推则7 =周五/周六。11 =周日为唯一周末,12 =周一为唯一周末,依此类推则17 =周六为唯一周末。', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'holidays', + detail: '[ 可选 ] - 这是一个范围或数组常量,其中包含作为节假日的日期。\n在节假日数组内提供的值必须为日期序数值(例如N的返回值)或日期值(例如DATE、DATEVALUE或TO_DATE的返回值)。由范围指定的值应该是标准的日期值或日期序数值。', + example: 'DATE(1969', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'YEAR', + t: 6, + d: '返回对应于某个日期的年份。 Year 作为 1900 - 9999 之间的整数返回。', + a: '返回对应于某个日期的年份。 Year 作为 1900 - 9999 之间的整数返回。', + m: [1, 1], + p: [ + { + name: 'serial_number', + detail: '用于计算年份的日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'YEARFRAC', + t: 6, + d: '返回 start_date 和 end_date 之间的天数占全年天数的百分比。', + a: '返回 start_date 和 end_date 之间的天数占全年天数的百分比。', + m: [2, 3], + p: [ + { + name: 'start_date', + detail: '计算中要使用的开始日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'end_date', + detail: '计算中要使用的结束日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: '7', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 要使用的日计数基准类型。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '16)', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TODAY', + t: 6, + d: '以日期值格式返回当前日期。', + a: '以日期值格式返回当前日期。', + m: [0, 0], + p: [], + }, + { + n: 'MONTH', + t: 6, + d: '返回日期(以序列数表示)中的月份。 月份是介于 1(一月)到 12(十二月)之间的整数。', + a: '返回日期(以序列数表示)中的月份。 月份是介于 1(一月)到 12(十二月)之间的整数。', + m: [1, 1], + p: [ + { + name: 'serial_number', + detail: '要从中提取月份的日期。必须是以下一种:对包含日期的单元格的引用、返回日期类型的函数或者数字。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EFFECT', + t: 8, + d: '根据名义利率及每年的复利计息期数来计算实际年利率。', + a: '根据名义利率及每年的复利计息期数来计算实际年利率。', + m: [2, 2], + p: [ + { + name: 'nominal_rate', + detail: '每年的名义利率。', + example: '0.99', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'npery', + detail: '每年的复利计算期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DOLLAR', + t: 12, + d: '将数字格式设置为与语言区域相对应的货币格式。', + a: '将数字格式设置为与语言区域相对应的货币格式。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要设置格式的值。', + example: '1.2351', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'decimals', + detail: '[可选 - 默认值为 2] - 要显示的小数位数。\n\n如果这是负数,则将数字四舍五入到小数点左侧。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DOLLARDE', + t: 8, + d: '将以整数部分和分数部分转换为以小数部分表示的金额数字', + a: '将以整数部分和分数部分转换为以小数部分表示的金额数字', + m: [2, 2], + p: [ + { + name: 'fractional_dollar', + detail: '以整数部份和分数部分表示的数字,用小数点隔开。', + example: '100.10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fraction', + detail: '用作分数中的分母的整数。', + example: '32', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DOLLARFR', + t: 8, + d: '将小数转换为分数表示的金额数字。', + a: '将小数转换为分数表示的金额数字。', + m: [2, 2], + p: [ + { + name: 'decimal_dollar', + detail: '小数。', + example: '100.125', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fraction', + detail: '用作分数中的分母的整数。', + example: '32', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DB', + t: 8, + d: '使用固定余额递减法,返回指定期间内某项固定资产的折旧值。', + a: '使用固定余额递减法,返回指定期间内某项固定资产的折旧值。', + m: [4, 5], + p: [ + { + name: 'cost', + detail: '资产原值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: '折旧末尾时的值(有时也称为资产残值)。', + example: '50', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: '资产的折旧期数(有时也称作资产的使用寿命)。', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: '在使用期限内要计算折旧的折旧期。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'month', + detail: '[可选 - 默认值为12] - 折旧第一年中的月数。', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DDB', + t: 8, + d: '用双倍余额递减法,返回指定期间内某项固定资产的折旧值。', + a: '用双倍余额递减法,返回指定期间内某项固定资产的折旧值。', + m: [4, 5], + p: [ + { + name: 'cost', + detail: '资产原值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: '折旧末尾时的值(有时也称为资产残值)。', + example: '50', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: '资产的折旧期数(有时也称作资产的使用寿命)。', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: '在使用期限内要计算折旧的折旧期。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'factor', + detail: '[可选 - 默认值为2] - 折旧的递减系数。', + example: '2.25', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RATE', + t: 8, + d: '返回年金每期的利率。', + a: '返回年金每期的利率。', + m: [3, 6], + p: [ + { + name: 'nper', + detail: '年金的付款总期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pmt', + detail: '每期的付款金额,在年金周期内不能更改。', + example: '-100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '现值即一系列未来付款当前值的总和。', + example: '400', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fv', + detail: '[可选 - 默认值为0] - 未来值,或在最后一次付款后希望得到的现金余额。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '[可选 - 默认值为0] - 指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'guess', + detail: '[可选 - 默认值为0.1] - 预期利率。', + example: '0.1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CUMPRINC', + t: 8, + d: '基于等额分期付款和固定利率,计算投资在多个付款期内的累计本金偿还额。', + a: '基于等额分期付款和固定利率,计算投资在多个付款期内的累计本金偿还额。', + m: [6, 6], + p: [ + { + name: 'rate', + detail: '利率。', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'nper', + detail: '总付款期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '年金的现值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'start_period', + detail: '开始累计计算的付款期序号。\n\n首期必须大于等于1。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_period', + detail: '结束累计计算的付款期序号。\n\n末期必须大于首期。', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPNUM', + t: 8, + d: '返回在结算日和到期日之间的付息次数,向上舍入到最近的整数。', + a: '返回在结算日和到期日之间的付息次数,向上舍入到最近的整数。', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。到期日是有价证券有效期截止时的日期。', + example: '02', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SYD', + t: 8, + d: '返回在指定期间内资产按年限总和折旧法计算的折旧。', + a: '返回在指定期间内资产按年限总和折旧法计算的折旧。', + m: [4, 4], + p: [ + { + name: 'cost', + detail: '资产原值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: '折旧末尾时的值(有时也称为资产残值)。', + example: '50', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: '资产的折旧期数(有时也称作资产的使用寿命)。', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'period', + detail: '在使用期限内要计算折旧的折旧期。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TBILLEQ', + t: 8, + d: '基于贴现率计算美国政府短期债券的等效年化收益率。', + a: '基于贴现率计算美国政府短期债券的等效年化收益率。', + m: [3, 3], + p: [ + { + name: 'settlement', + detail: '债券的结算日期,此日期为债券发行后交付给买家的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '债券的到期或结束日期,届时可将其以面值或票面价值赎回。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'discount', + detail: '债券购买时的贴现率。', + example: '2)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TBILLYIELD', + t: 8, + d: '基于价格计算美国政府短期债券的收益率。', + a: '基于价格计算美国政府短期债券的收益率。', + m: [3, 3], + p: [ + { + name: 'settlement', + detail: '债券的结算日期,此日期为债券发行后交付给买家的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '债券的到期或结束日期,届时可将其以面值或票面价值赎回。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'pr', + detail: '债券的购买价格。', + example: '95', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TBILLPRICE', + t: 8, + d: '基于贴现率计算美国政府短期债券的价格。', + a: '基于贴现率计算美国政府短期债券的价格。', + m: [3, 3], + p: [ + { + name: 'settlement', + detail: '债券的结算日期,此日期为债券发行后交付给买家的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '债券的到期或结束日期,届时可将其以面值或票面价值赎回。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'discount', + detail: '债券购买时的贴现率。', + example: '0.09', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PV', + t: 8, + d: '基于等额分期付款和固定利率,计算年金投资的现值。', + a: '基于等额分期付款和固定利率,计算年金投资的现值。', + m: [3, 5], + p: [ + { + name: 'rate', + detail: '各期利率。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'nper', + detail: '年金的付款总期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pmt', + detail: '每期的付款金额,在年金周期内不能更改。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fv', + detail: '[可选] - 未来值,或在最后一次付款后希望得到的现金余额。', + example: 'D2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '[可选 - 默认值为0] - 指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ACCRINT', + t: 8, + d: '返回定期付息证券的应计利息。', + a: '返回定期付息证券的应计利息。', + m: [6, 8], + p: [ + { + name: 'issue', + detail: '有价证券的发行日。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'first_interest', + detail: '有价证券的首次计息日。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: '有价证券的年息票利率。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'par', + detail: '证券的票面值。', + example: '10000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示““欧洲30/360”方法” - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'calc_method', + detail: '[可选 - 默认为TRUE()] - 一个逻辑值,指定当结算日期晚于首次计息日期时用于计算总应计利息的方法。\n\n如果值为 TRUE,则返回从发行日到结算日的总应计利息。\n\n如果值为 FALSE,则返回从首次计息日到结算日的应计利息。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ACCRINTM', + t: 8, + d: '返回在到期日支付利息的有价证券的应计利息。', + a: '返回在到期日支付利息的有价证券的应计利息。', + m: [4, 5], + p: [ + { + name: 'issue', + detail: '有价证券的发行日。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'settlement', + detail: '有价证券的到期日。', + example: 'DATE(1969', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: '有价证券的年息票利率。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'par', + detail: '证券的票面值。', + example: '1000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPDAYBS', + t: 8, + d: '返回从付息期开始到结算日的天数。', + a: '返回从付息期开始到结算日的天数。', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPDAYS', + t: 8, + d: '返回结算日所在的付息期的天数。', + a: '返回结算日所在的付息期的天数。', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPDAYSNC', + t: 8, + d: '返回从结算日到下一票息支付日之间的天数。', + a: '返回从结算日到下一票息支付日之间的天数。', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPNCD', + t: 8, + d: '计算结算日之后的下一票息或利息派发日期。', + a: '计算结算日之后的下一票息或利息派发日期。', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '01)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: 'DATE(2019', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COUPPCD', + t: 8, + d: '计算结算日之前的最后一个票息或利息支付日。', + a: '计算结算日之前的最后一个票息或利息支付日。', + m: [3, 4], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '01)', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: 'DATE(2019', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FV', + t: 8, + d: '基于等额分期付款和固定利率,计算年金投资的未来价值。', + a: '基于等额分期付款和固定利率,计算年金投资的未来价值。', + m: [3, 5], + p: [ + { + name: 'rate', + detail: '各期利率。', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'nper', + detail: '年金的付款总期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pmt', + detail: '各期所应支付的金额,在整个年金期间保持不变。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '[可选 - 默认值为 0 ] - 现值,或一系列未来付款的当前值的累积和。', + example: '400', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '[可选 - 默认值为 0 ] - 指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FVSCHEDULE', + t: 8, + d: '返回应用一系列复利率计算的初始本金的未来值。', + a: '返回应用一系列复利率计算的初始本金的未来值。', + m: [2, 2], + p: [ + { + name: 'principal', + detail: '现值。', + example: '10000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'schedule', + detail: '用于计算本金复利的一组利率。\n\n利率表必须是范围或数组,其中包含要用于计算复利的一组利率。这些利率值应该以十进制小数形式表示,或者使用UNARY_PERCENT以百分比形式表示,即表示为0.09或UNARY_PERCENT(9),而不要表示为9。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'YIELD', + t: 8, + d: '返回定期支付利息的债券的收益率。', + a: '返回定期支付利息的债券的收益率。', + m: [6, 7], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: '有价证券的年息票利率。', + example: '0.057', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pr', + detail: '有价证券的价格。', + example: '95', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: '有价证券的清偿价值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'YIELDDISC', + t: 8, + d: '基于价格计算折价发行的(不带息)债券的年收益率。', + a: '基于价格计算折价发行的(不带息)债券的年收益率。', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'pr', + detail: '有价证券的价格。', + example: '95', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: '有价证券的清偿价值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NOMINAL', + t: 8, + d: '基于给定的实际利率和年复利期数,返回名义年利率。', + a: '基于给定的实际利率和年复利期数,返回名义年利率。', + m: [2, 2], + p: [ + { + name: 'effect_rate', + detail: '每年的实际利率。', + example: '0.85', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'npery', + detail: '每年的复利期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'XIRR', + t: 8, + d: '返回一组不一定定期发生的现金流的内部收益率。', + a: '返回一组不一定定期发生的现金流的内部收益率。', + m: [2, 3], + p: [ + { + name: 'values', + detail: '其中含有投资相关收益或支出的数组或范围。\n\n现金流数额中必须至少包含一项负的和一项正的现金流金额才能计算回报率。', + example: 'B2:B25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'dates', + detail: '与现金流数额参数中的现金流对应的日期数组或范围。', + example: 'C2:C25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'guess', + detail: '[可选 - 默认值为0.1] - 对内部回报率的估算值。', + example: '250', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MIRR', + t: 8, + d: '返回一系列定期现金流的修改后内部收益率。', + a: '返回一系列定期现金流的修改后内部收益率。', + m: [3, 3], + p: [ + { + name: 'values', + detail: '其中含有投资相关收益或支出的数组或范围。\n\n现金流数额中必须至少包含一项负的和一项正的现金流金额才能计算回报率。', + example: 'A2:A25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'finance_rate', + detail: '现金流中使用的资金支付的利率。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'reinvest_rate', + detail: '将现金流再投资的收益率。', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'IRR', + t: 8, + d: '返回由值中的数字表示的一系列现金流的内部收益率。 ', + a: '返回由值中的数字表示的一系列现金流的内部收益率。 ', + m: [1, 2], + p: [ + { + name: 'values', + detail: '其中含有投资相关收益或支出的数组或范围。\n\n现金流数额中必须至少包含一项负的和一项正的现金流金额才能计算回报率。', + example: 'A2:A25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'guess', + detail: '[可选 - 默认为 0.1] - 内部收益率的估值。', + example: '200', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NPV', + t: 8, + d: '使用贴现率和一系列未来支出(负值)和收益(正值)来计算一项投资的净现值。', + a: '使用贴现率和一系列未来支出(负值)和收益(正值)来计算一项投资的净现值。', + m: [2, 255], + p: [ + { + name: 'rate', + detail: '某一期间的贴现率。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value1', + detail: '第一笔支出(负值)和收益(正值)。', + example: '200', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2, ...', + detail: '[可选] - 其他支出(负值)和收益(正值)。', + example: '250', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'XNPV', + t: 8, + d: '返回一组现金流的净现值,这些现金流不一定定期发生。', + a: '返回一组现金流的净现值,这些现金流不一定定期发生。', + m: [3, 3], + p: [ + { + name: 'rate', + detail: '应用于现金流的贴现率。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'values', + detail: '与 dates 中的支付时间相对应的一系列现金流。', + example: 'B2:B25', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'dates', + detail: '与现金流支付相对应的支付日期表。', + example: 'C2:C25', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'CUMIPMT', + t: 8, + d: '基于等额分期付款和固定利率,计算投资在一系列付款期内的累计利息。', + a: '基于等额分期付款和固定利率,计算投资在一系列付款期内的累计利息。', + m: [6, 6], + p: [ + { + name: 'rate', + detail: '利息率。', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'nper', + detail: '总付款期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '现值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'start_period', + detail: '开始累计计算的付款期序号。\n\n首期必须大于等于1。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'end_period', + detail: '结束累计计算的付款期序号。\n\n末期必须大于首期。', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '0', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PMT', + t: 8, + d: '用于根据固定付款额和固定利率计算贷款的付款额。', + a: '用于根据固定付款额和固定利率计算贷款的付款额。', + m: [3, 5], + p: [ + { + name: 'rate', + detail: '贷款利率。', + example: '0.08', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'nper', + detail: '该项贷款的付款总数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '现值,或一系列未来付款额现在所值的总额,也叫本金。', + example: ' 100000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fv', + detail: '[可选 - 默认值为 0] - 未来值,或在最后一次付款后希望得到的现金余额。', + example: 'D2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '[可选 - 默认值为 0] - 指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'IPMT', + t: 8, + d: '基于固定利率及等额分期付款方式,返回给定期数内对投资的利息偿还额。', + a: '基于固定利率及等额分期付款方式,返回给定期数内对投资的利息偿还额。', + m: [4, 6], + p: [ + { + name: 'rate', + detail: '各期利率。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'per', + detail: '用于计算其利息数额的期数,必须在 1 到 nper 之间。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'nper', + detail: '年金的付款总期数。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '现值,或一系列未来付款的当前值的累积和。', + example: '80000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fv', + detail: '[可选 - 默认值为 0] - 未来值,或在最后一次付款后希望得到的现金余额。', + example: 'E2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '[可选 - 默认值为 0] - 指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PPMT', + t: 8, + d: '返回根据定期固定付款和固定利率而定的投资在已知期间内的本金偿付额。', + a: '返回根据定期固定付款和固定利率而定的投资在已知期间内的本金偿付额。', + m: [4, 6], + p: [ + { + name: 'rate', + detail: '各期利率。', + example: '0.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'per', + detail: '指定期数,该值必须在 1 到 nper 范围内。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'nper', + detail: '年金的付款总期数。', + example: '3*12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '现值即一系列未来付款当前值的总和。', + example: '100000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fv', + detail: '[可选 - 默认值为 0] - 未来值,或在最后一次付款后希望得到的现金余额。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '[可选 - 默认值为 0] - 指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'INTRATE', + t: 8, + d: '返回完全投资型证券的利率。', + a: '返回完全投资型证券的利率。', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'investment', + detail: '有价证券的投资额。', + example: '100000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: '有价证券到期时的兑换值。', + example: '101200', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PRICE', + t: 8, + d: '返回定期付息的面值 ¥100 的有价证券的价格。', + a: '返回定期付息的面值 ¥100 的有价证券的价格。', + m: [6, 7], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: '有价证券的年息票利率。', + example: '0.057', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yld', + detail: '有价证券的年收益率。', + example: '0.065', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: '面值 ¥100 的有价证券的清偿价值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PRICEDISC', + t: 8, + d: '返回折价发行的面值 ¥100 的有价证券的价格。', + a: '返回折价发行的面值 ¥100 的有价证券的价格。', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'discount', + detail: '有价证券的贴现率。', + example: '0.0525', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: '面值 ¥100 的有价证券的清偿价值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'PRICEMAT', + t: 8, + d: '返回到期付息的面值 ¥100 的有价证券的价格。', + a: '返回到期付息的面值 ¥100 的有价证券的价格。', + m: [5, 6], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'issue', + detail: '有价证券的发行日。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'rate', + detail: '有价证券在发行日的利率。', + example: '0.061', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yld', + detail: '有价证券的年收益率。', + example: '0.061', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RECEIVED', + t: 8, + d: '返回一次性付息的有价证券到期收回的金额。', + a: '返回一次性付息的有价证券到期收回的金额。', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'investment', + detail: '有价证券的投资额。', + example: '10000000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'discount', + detail: '有价证券的贴现率。', + example: '0.0575', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '12', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DISC', + t: 8, + d: '返回有价证券的贴现率。', + a: '返回有价证券的贴现率。', + m: [4, 5], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'pr', + detail: '有价证券的价格(按面值为 ¥100 计算)。', + example: '97.975', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'redemption', + detail: '面值 ¥100 的有价证券的清偿价值。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '12', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'NPER', + t: 8, + d: '基于固定利率及等额分期付款方式,返回某项投资的总期数。', + a: '基于固定利率及等额分期付款方式,返回某项投资的总期数。', + m: [3, 5], + p: [ + { + name: 'rate', + detail: '各期利率。', + example: '0.12', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pmt', + detail: '各期所应支付的金额,在整个年金期间保持不变。', + example: '500', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'pv', + detail: '现值,或一系列未来付款的当前值的累积和。', + example: '40000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'fv', + detail: '[可选 - 默认值为0] - 未来值,或在最后一次付款后希望得到的现金余额。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'type', + detail: '[可选 - 默认值为0] - 指定各期的付款时间是在期初还是期末。\n\n0 表示期末;\n\n1 表示期初。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SLN', + t: 8, + d: '返回一个期间内的资产的直线折旧。', + a: '返回一个期间内的资产的直线折旧。', + m: [3, 3], + p: [ + { + name: 'cost', + detail: '资产原值。', + example: '300000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'salvage', + detail: '折旧末尾时的值(有时也称为资产残值)。', + example: '75000', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'life', + detail: '资产的折旧期数(有时也称作资产的使用寿命)。', + example: '10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DURATION', + t: 8, + d: '返回假设面值 ¥100 的定期付息有价证券的修正期限。', + a: '返回假设面值 ¥100 的定期付息有价证券的修正期限。', + m: [5, 6], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'coupon', + detail: '有价证券的年息票利率。', + example: '0.08', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yld', + detail: '有价证券的年收益率。', + example: '0.09', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MDURATION', + t: 8, + d: '返回假设面值 ¥100 的有价证券的 Macauley 修正期限。', + a: '返回假设面值 ¥100 的有价证券的 Macauley 修正期限。', + m: [5, 6], + p: [ + { + name: 'settlement', + detail: '有价证券的结算日。 有价证券结算日是在发行日之后,有价证券卖给购买者的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'maturity', + detail: '有价证券的到期日。 到期日是有价证券有效期截止时的日期。', + example: 'DATE(2010', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'coupon', + detail: '有价证券的年息票利率。', + example: '0.08', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'yld', + detail: '有价证券的年收益率。', + example: '0.09', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'frequency', + detail: '年付息次数。\n\n如果按年支付,frequency = 1;\n\n按半年期支付,frequency = 2;\n\n按季支付,frequency = 4。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'basis', + detail: '[可选 - 默认为0] - 指示要使用哪种天数计算方法。\n\n0表示“美国(NASD) 30/360”方法 - 此方法按照美国全国证券交易商协会标准,假设每月30天、每年360天,并对所输入的月末日期进行具体调整。\n\n1表示“实际/实际”方法 - 此方法计算基于指定日期之间的实际天数和所涉及的年份中的实际天数进行计算。此方法用于美国长期债券,也是在非财经用途方面使用最多的方法。\n\n2表示“实际/360”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为360天。\n\n3表示“实际/365”方法 - 此方法基于指定日期之间的实际天数进行计算,但假定每年为365天。\n\n4表示“欧洲30/360”方法 - 类似于0,此方法基于每月30天、每年360天进行计算,但按照欧洲金融惯例对月末日期进行调整。', + example: '0', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'BIN2DEC', + t: 9, + d: '将二进制数转换为十进制数。', + a: '将二进制数转换为十进制数。', + m: [1, 1], + p: [ + { + name: 'number', + detail: '要转换为十进制数的带符号的10位二进制数值(以字符串形式提供)。\n\n带符号的二进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n对于此函数,最大的正数输入值为0111111111,最小的负数输入值为1000000000。\n\n如果所提供的带符号的二进制数是有效的二进制数,会自动将其转换为相应的字符串输入。例如,BIN2DEC(100)和BIN2DEC("100")得出的结果相同,均为4。', + example: '101', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'BIN2HEX', + t: 9, + d: '将二进制数转换为十六进制数。', + a: '将二进制数转换为十六进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的十六进制数的带符号的10位二进制数值(以字符串形式提供)。\n\n带符号的二进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n对于此函数,最大的正数输入值为0111111111,最小的负数输入值为1000000000。\n\n如果所提供的带符号的二进制数是有效的二进制数,会自动将其转换为相应的字符串输入。例如,BIN2HEX(11111)和BIN2HEX("11111")得出 的结果相同,均为1F。', + example: '101', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。例如,BIN2HEX("11111",8)所得的结果值为0000001F。\n\n如果带符号的二进制数的最高位为1,则忽略此值;即当提供的带符号的二进制数大于等于1000000000时忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'BIN2OCT', + t: 9, + d: '将二进制数转换为八进制数。', + a: '将二进制数转换为八进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的八进制数的带符号的10位二进制数值(以字符串形式提供)。\n\n带符号的二进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n对于此函数,最大的正数输入值为0111111111,最小的负数输入值为1000000000。\n\n如果所提供的带符号的二进制数是有效的二进制数,会自动将其转换为相应的字符串输入。例如,BIN2OCT(11111)和BIN2OCT("11111")得出的结果相同,均为37。', + example: '101', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。例如,BIN2OCT("11111")得到的结果值为00000037。\n\n如果带符号的二进制数的最高位为1,则忽略此值;即当提供的带符号的二进制数大于等于1000000000时忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEC2BIN', + t: 9, + d: '将十进制数转换为二进制数。', + a: '将十进制数转换为二进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的二进制数的十进制数值(以字符串形式提供)。\n\n对于此函数,最大的正数输入值为511,最小的负数输入值为-512。\n\n如果所提供的十进制数是有效的十进制数,会自动将其转换为相应的字符串输入。例如,DEC2BIN(199)和DEC2BIN("199")得出的结果相同,均为11000111。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。\n\n如果十进制数为负数,则忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEC2HEX', + t: 9, + d: '将十进制数转换为十六进制数。', + a: '将十进制数转换为十六进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的十六进制数的十进制数值(以字符串形式提供)。\n\n此函数可接受的最大正数值为549755813887,最小负数值为-549755814888。\n\n如果所提供的十进制数是有效的十进制数,会自动将其转换为相应的字符串输入。例如,DEC2HEX(100)和DEC2HEX("100")得出的结果相同,均为64。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。\n\n如果十进制数为负数,则忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DEC2OCT', + t: 9, + d: '将十进制数转换为八进制数。', + a: '将十进制数转换为八进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的八进制数的十进制数值(以字符串形式提供)。\n\n此函数可接受的最大正数值为536870911,最小负数值为-53687092。\n\n如果所提供的十进制数是有效的十进制数,会自动将其转换为相应的字符串输入。例如,DEC2OCT(199)和DEC2OCT("199")得出的结果相同,均为307。', + example: '100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。\n\n如果十进制数为负数,则忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HEX2BIN', + t: 9, + d: '将十六进制数转换为二进制数。', + a: '将十六进制数转换为二进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的二进制数的带符号的40位十六进制数值(以字符串形式提供)。\n\n带符号的十六进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n此函数可接受的最大正数值为1FF,最小负数值为FFFFFFFE00。\n\n如果所提供的带符号的十六进制数是有效的十六进制数,函数会自动将其转换为相应的字符串输入。例如,HEX2BIN(199)和HEX2BIN("199")得出的结果相同,均为110011001。', + example: '"f3"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。\n\n如果带符号的十六进制数的最高位为1,则忽略此值;即当提供的带符号的十六进制数大于等于8000000000时忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'HEX2DEC', + t: 9, + d: '将十六进制数转换为十进制数。', + a: '将十六进制数转换为十进制数。', + m: [1, 1], + p: [ + { + name: 'number', + detail: '要转换为十进制数的带符号的40位十六进制数值(以字符串形式提供)。\n\n带符号的十六进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n此函数可接受的最大正数值为7fffffffff,最小负数值为8000000000。\n\n如果所提供的带符号的十六进制数是有效的十六进制数,函数会自动将其转换为相应的字符串输入。例如,HEX2DEC(199)和HEX2DEC("199")得出的结果相同,均为409。', + example: '"f3"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'HEX2OCT', + t: 9, + d: '将十六进制数转换为八进制数。', + a: '将十六进制数转换为八进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的八进制数的带符号的40位十六进制数值(以字符串形式提供)。\n\n带符号的十六进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n此函数可接受的最大正数值为1FFFFFFF,最小负数值为FFE0000000。\n\n如果所提供的带符号的十六进制数是有效的十六进制数,函数会自动将其转换为相应的字符串输入。例如,HEX2OCT(199)和HEX2OCT("199")得出的结果相同,均为631。', + example: '"f3"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。\n\n如果带符号的十六进制数的最高位为1,则忽略此值;即当给定的带符号的十六进制数大于等于8000000000时忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'OCT2BIN', + t: 9, + d: '将八进制数转换为二进制数。', + a: '将八进制数转换为二进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的二进制数的带符号的30位八进制数值(以字符串形式提供)。\n\n带符号的八进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n此函数可接受的最大正数值为777,最小负数值为7777777000。\n\n如果所提供的带符号的八进制数是有效的八进制数,函数会自动将其转换为相应的字符串输入。例如,OCT2BIN(177)和OCT2BIN("177")得出的结果相同,均为1111111。', + example: '37', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。\n\n如果带符号的八进制数的最高位为1,则忽略此值;即当给定的带符号的八进制数大于等于4000000000时忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'OCT2DEC', + t: 9, + d: '将八进制数转换为十进制数。', + a: '将八进制数转换为十进制数。', + m: [1, 1], + p: [ + { + name: 'number', + detail: '要转换为十进制数的带符号的30位八进制数值(以字符串形式提供)。\n\n带符号的ba进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n此函数可接受的最大正数值为3777777777,最小负数值为4000000000。\n\n如果所提供的带符号的八进制数是有效的八进制数,函数会自动将其转换为相应的字符串输入。例如,OCT2DEC(177)和OCT2DEC("177")得出的结果相同,均为127。', + example: '37', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'OCT2HEX', + t: 9, + d: '将八进制数转换为十六进制数。', + a: '将八进制数转换为十六进制数。', + m: [1, 2], + p: [ + { + name: 'number', + detail: '要转换为带符号的十六进制数的带符号的30位八进制数值(以字符串形式提供)。\n\n带符号的ba进制数的最高位是符号位;也就是说,负数是以二的补码形式表示的。\n\n此函数可接受的最大正数值为3777777777,最小负数值为4000000000。\n\n如果所提供的带符号的八进制数是有效的八进制数,函数会自动将其转换为相应的字符串输入。例如,OCT2HEX(177)和OCT2HEX("177")得出的结果相同,均为7F。', + example: '37', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'places', + detail: '[ 可选 ] - 结果中要确保的有效位数。\n\n如果设置的有效位数大于结果中的有效位数,则在结果的左侧填充0,使总有效位数达到有效位数。\n\n如果带符号的八进制数的最高位为1,则忽略此值;即当给定的带符号的八进制数大于等于4000000000时忽略此值。', + example: '8', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'COMPLEX', + t: 9, + d: '将实系数及虚系数转换为 x+yi 或 x+yj 形式的复数。', + a: '将实系数及虚系数转换为 x+yi 或 x+yj 形式的复数。', + m: [2, 3], + p: [ + { + name: 'real_num', + detail: '复数的实系数。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'i_num', + detail: '复数的虚系数。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'suffix', + detail: '[可选 - 默认为 "i"] - 复数中虚系数的后缀。', + example: '"j"', + require: 'o', + repeat: 'n', + type: 'rangestring', + }, + ], + }, + { + n: 'IMREAL', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的复数的实系数。', + a: '返回以 x+yi 或 x+yj 文本格式表示的复数的实系数。', + m: [1, 1], + p: [ + { + name: 'inumber', + detail: '需要计算其实系数的复数。', + example: '"4+5i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMAGINARY', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的复数的虚系数。', + a: '返回以 x+yi 或 x+yj 文本格式表示的复数的虚系数。', + m: [1, 1], + p: [ + { + name: 'inumber', + detail: '需要计算其虚系数的复数。', + example: '"4+5i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMCONJUGATE', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的复数的共轭复数。', + a: '返回以 x+yi 或 x+yj 文本格式表示的复数的共轭复数。', + m: [1, 1], + p: [ + { + name: 'inumber', + detail: '需要计算其共轭数的复数。', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMABS', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的复数的绝对值(模)。', + a: '返回以 x+yi 或 x+yj 文本格式表示的复数的绝对值(模)。', + m: [1, 1], + p: [ + { + name: 'inumber', + detail: '要计算其绝对值的复数。', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'DELTA', + t: 9, + d: '检验两个值是否相等。 如果 number1=number2,则返回 1;否则返回 0。', + a: '检验两个值是否相等。 如果 number1=number2,则返回 1;否则返回 0。', + m: [1, 2], + p: [ + { + name: 'number1', + detail: '第一个数字。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'number2', + detail: '[可选 - 默认为 0] - 第二个数字。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'IMSUM', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的 1 至 255 个复数的和。', + a: '返回以 x+yi 或 x+yj 文本格式表示的 1 至 255 个复数的和。', + m: [1, 255], + p: [ + { + name: 'inumber1', + detail: '要相加的第一个复数', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'inumber2, …', + detail: '[可选] - 要与值1 相加的其他复数', + example: '"5-3i"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMSUB', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的两个复数的差。', + a: '返回以 x+yi 或 x+yj 文本格式表示的两个复数的差。', + m: [2, 2], + p: [ + { + name: 'inumber1', + detail: '从(复)数中减去 inumber2。', + example: '"6+5i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'inumber2', + detail: '从 inumber1 中减(复)数。', + example: '"2+3i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMPRODUCT', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的 1 至 255 个复数的乘积。', + a: '返回以 x+yi 或 x+yj 文本格式表示的 1 至 255 个复数的乘积。', + m: [1, 255], + p: [ + { + name: 'inumber1', + detail: '用于计算乘积的第一个复数', + example: '"3+4i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'inumber2, …', + detail: '[可选] - 要相乘的其他复数。', + example: '"5-3i"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'IMDIV', + t: 9, + d: '返回以 x+yi 或 x+yj 文本格式表示的两个复数的商。', + a: '返回以 x+yi 或 x+yj 文本格式表示的两个复数的商。', + m: [2, 2], + p: [ + { + name: 'inumber1', + detail: '复数分子或被除数。', + example: '"11+16i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'inumber2', + detail: '复数分母或除数。', + example: '"3+2i"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NOT', + t: 10, + d: '返回某个逻辑值的相反值 - “NOT(TRUE())”将返回 FALSE;“NOT(FALSE())”将返回 TRUE。', + a: '返回某个逻辑值的相反值 - “NOT(TRUE())”将返回 FALSE;“NOT(FALSE())”将返回 TRUE。', + m: [1, 1], + p: [ + { + name: 'logical', + detail: '计算结果为 TRUE 或 FALSE 的任何值或表达式。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TRUE', + t: 10, + d: '返回逻辑值 TRUE。', + a: '返回逻辑值 TRUE。', + m: [0, 0], + p: [], + }, + { + n: 'FALSE', + t: 10, + d: '返回逻辑值 FALSE。', + a: '返回逻辑值 FALSE。', + m: [0, 0], + p: [], + }, + { + n: 'AND', + t: 10, + d: '所有参数的计算结果为 TRUE 时,返回 TRUE;只要有一个参数的计算结果为 FALSE,即返回 FALSE。', + a: '所有参数的计算结果为 TRUE 时,返回 TRUE;只要有一个参数的计算结果为 FALSE,即返回 FALSE。', + m: [1, 255], + p: [ + { + name: 'logical1', + detail: '要测试的第一个条件,其计算结果可以为 TRUE 或 FALSE。', + example: 'A2 = "foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'logical2,...', + detail: '[可选] - 要测试的其他条件,其计算结果可以为 TRUE 或 FALSE,最多可包含 255 个条件。', + example: 'A3 = "bar"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'IFERROR', + t: 10, + d: '如果第一个参数不是错误值,就返回第一个参数;否则,返回第二个参数。', + a: '如果第一个参数不是错误值', + m: [2, 2], + p: [ + { + name: 'value', + detail: '检查是否存在错误的参数。', + example: 'A1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_if_error', + detail: '公式的计算结果错误时返回的值。 计算以下错误类型:#N/A、#VALUE!、#REF!、#DIV/0!、#NUM!、#NAME? 或 #NULL!。', + example: '"Error in cell A1"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'IF', + t: 10, + d: '当逻辑表达式的值为 TRUE 时返回一个值,而当其为 FALSE 时返回另一个值。', + a: '当逻辑表达式的值为 TRUE 时返回一个值,而当其为 FALSE 时返回另一个值。', + m: [2, 3], + p: [ + { + name: 'logical_test', + detail: '一个表达式或对包含表达式的单元格的引用,该表达式代表某种逻辑值(即TRUE或FALSE)。', + example: 'A2 = "foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_if_true', + detail: '当逻辑表达式为TRUE时的返回值。', + example: '"A2 is foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value_if_false', + detail: '[可选 - 默认为空白] - 当逻辑表达式等于FALSE时的函数返回值。', + example: '"A2 was false"', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'OR', + t: 10, + d: '只要有一个参数的计算结果为 TRUE 时,返回 TRUE;所有参数的计算结果为 FALSE,即返回 FALSE。', + a: '只要有一个参数的计算结果为 TRUE 时,返回 TRUE;所有参数的计算结果为 FALSE,即返回 FALSE。', + m: [1, 255], + p: [ + { + name: 'logical1', + detail: '要测试的第一个条件,其计算结果可以为 TRUE 或 FALSE。', + example: 'A2 = "foo"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '逻辑表达式2', + detail: '[可选] - 其他表达式或对包含表达式的单元格的引用,这些表达式代表某种逻辑值(即TRUE或FALSE)或者可以强制转换为逻辑值。', + example: ' A3 = "bar"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'NE', + t: 11, + d: '如果指定的值不相等,则返回“TRUE”;否则返回“FALSE”。相当于“<>”运算符。', + a: '如果指定的值不相等,则返回“TRUE”;否则返回“FALSE”。相当于“<>”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '第一个值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '要检查是否与 value1 不相等的值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EQ', + t: 11, + d: '如果指定的值相等,则返回“TRUE”;否则返回“FALSE”。相当于“=”运算符。', + a: '如果指定的值相等,则返回“TRUE”;否则返回“FALSE”。相当于“=”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '第一个值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '要检查是否与 value1 相等的值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'GT', + t: 11, + d: '如果第一个参数严格大于第二个,则返回 TRUE;否则返回 FALSE。相当于“>”运算符。', + a: '如果第一个参数严格大于第二个,则返回 TRUE;否则返回 FALSE。相当于“>”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '要测试其是否大于 value2 的值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '第二个值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'GTE', + t: 11, + d: '如果第一个参数大于或等于第二个,则返回 TRUE;否则返回 FALSE。相当于“>=”运算符。', + a: '如果第一个参数大于或等于第二个,则返回 TRUE;否则返回 FALSE。相当于“>=”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '要测试其是否大于等于 value2 的值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '第二个值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LT', + t: 11, + d: '如果第一个参数严格小于第二个,则返回 TRUE;否则返回 FALSE。相当于“<”运算符。', + a: '如果第一个参数严格小于第二个,则返回 TRUE;否则返回 FALSE。相当于“<”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '要测试其是否小于 value2 的值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '第二个值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LTE', + t: 11, + d: '如果第一个参数小于或等于第二个,则返回 TRUE;否则返回 FALSE。相当于“<=”运算符。', + a: '如果第一个参数小于或等于第二个,则返回 TRUE;否则返回 FALSE。相当于“<=”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '要测试其是否小于等于 value2 的值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '第二个值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ADD', + t: 11, + d: '返回两个数值之和。相当于 `+` 运算符。', + a: '返回两个数值之和。相当于 `+` 运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '第一个加数。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: '第二个加数。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MINUS', + t: 11, + d: '返回两个数值之差。相当于“-”运算符。', + a: '返回两个数值之差。相当于“-”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '被减数,即要对其计减的数值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: '减数,即要从 value1 中减除的数值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MULTIPLY', + t: 11, + d: '返回两个数的乘积。相当于“*”运算符。', + a: '返回两个数的乘积。相当于“*”运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '第一个乘数。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: '第二个乘数。', + example: 'B2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DIVIDE', + t: 11, + d: '返回两个参数相除所得的结果。相当于 `/` 运算符。', + a: '返回两个参数相除所得的结果。相当于 `/` 运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: '要被除的数值。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'value2', + detail: '用于除其他数的数值。\n\n除数不得为0。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CONCAT', + t: 11, + d: '返回两个值的串联。相当于 `&` 运算符。', + a: '返回两个值的串联。相当于 `&` 运算符。', + m: [2, 2], + p: [ + { + name: 'value1', + detail: 'value2 将附于其后的值。', + example: '"de"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'value2', + detail: '要附于 value1 之后的值。', + example: '"mystify"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'UNARY_PERCENT', + t: 11, + d: '返回按百分比解释的数值。例如,“UNARY_PERCENT(100)”等于1。', + a: '返回按百分比解释的数值。例如,“UNARY_PERCENT(100)”等于1。', + m: [1, 1], + p: [ + { + name: 'number', + detail: '要作为百分比解释的数值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CONCATENATE', + t: 12, + d: '将两个或多个文本字符串联接为一个字符串。', + a: '将两个或多个文本字符串联接为一个字符串。', + m: [1, 255], + p: [ + { + name: 'text1', + detail: '初始字符串。', + example: '"Super"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'text2 ...', + detail: '[可选] - 要按顺序连接在一起的其他字符串。', + example: '"calla"', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'CODE', + t: 12, + d: '返回所提供的字符串中首字符的 Unicode 映射值。', + a: '返回所提供的字符串中首字符的 Unicode 映射值。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要返回其首字符的Unicode映射值的字符串。', + example: '"a"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'CHAR', + t: 12, + d: '按照当前 Unicode 编码表,将数字转换为对应的字符。', + a: '按照当前 Unicode 编码表,将数字转换为对应的字符。', + m: [1, 1], + p: [ + { + name: 'number', + detail: '介于 1 到 255 之间的数字。', + example: '97', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ARABIC', + t: 12, + d: '将罗马数字转换为阿拉伯数字。', + a: '将罗马数字转换为阿拉伯数字。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要转换格式的罗马数字', + example: '"XIV"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ROMAN', + t: 12, + d: '将数字格式设置为罗马数字形式。', + a: '将数字格式设置为罗马数字形式。', + m: [1, 1], + p: [ + { + name: 'number', + detail: '要设置格式的数字,介于1到3999之间(包括这两个数字)。', + example: '499', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'REGEXEXTRACT', + t: 12, + d: '按照正则表达式提取匹配的子串。', + a: '按照正则表达式提取匹配的子串。', + m: [2, 2], + p: [ + { + name: 'text', + detail: '输入文本。', + example: '"Needle in a haystack"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'regular_expression', + detail: '此函数将返回文本中符合此表达式的第一个子串。', + example: '".e{2}dle"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REGEXMATCH', + t: 12, + d: '判断一段文本是否与正则表达式相匹配。', + a: '判断一段文本是否与正则表达式相匹配。', + m: [2, 2], + p: [ + { + name: 'text', + detail: '要用正则表达式测试的文本。', + example: '"Spreadsheets"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'regular_expression', + detail: '用来测试文本的正则表达式。', + example: '"S.r"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REGEXREPLACE', + t: 12, + d: '使用正则表达式将文本字符串中的一部分替换为其他文本字符串。', + a: '使用正则表达式将文本字符串中的一部分替换为其他文本字符串。', + m: [3, 3], + p: [ + { + name: 'text', + detail: '要对其局部进行替换操作的文本。', + example: '"Spreadsheets"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'regular_expression', + detail: '正则表达式。 text 中所有匹配的实例都将被替换。', + example: '"S.*d"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'replacement', + detail: '要插入到原有文本中的文本。', + example: '"Bed"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'T', + t: 12, + d: '返回文本格式的字符串参数。', + a: '返回文本格式的字符串参数。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要转换为文本的参数。\n\n如果值为文本,T将返回值本身。\n\n如果值为指向包含文本的单元格的引用,T将返回值中的内容。\n\n如果值为错误值或包含错误值的单元格,T将返回该错误值。\n\n对于所有其他情况,T将返回空串。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FIXED', + t: 12, + d: '以固定的小数位数设置数字的格式。', + a: '以固定的小数位数设置数字的格式。', + m: [1, 3], + p: [ + { + name: 'number', + detail: '要进行舍入并转换为文本的数字。', + example: '3.141592653', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'decimals', + detail: '[可选 - 默认值为2] - 结果中要显示的小数位数。\n\n如果数值的有效位数小于小数位数,将以零填充。如果数值的有效位数大于小数位数,则将其舍入到所需的小数位数而不是将其截断。', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'no_commas', + detail: '[可选 - 默认值为FALSE()] - 一个逻辑值,如果为 TRUE(),则会禁止 FIXED 在返回的文本中包含逗号。', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FIND', + t: 12, + d: '返回字符串在文本中首次出现的位置(区分大小写)。', + a: '返回字符串在文本中首次出现的位置(区分大小写)。', + m: [2, 3], + p: [ + { + name: 'find_text', + detail: '要在要搜索的文本中查找的字符串。', + example: '"n"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'within_text', + detail: '要在其中搜索搜索字符串的首次出现位置的文本。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'start_num', + detail: '[可选 - 默认值为1] - 要在要搜索的文本中开始搜索的字符位置。', + example: '14', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'FINDB', + t: 12, + d: '返回某个字符串在文本中首次出现的位置(每个双字节字符占两个位置)。', + a: '返回某个字符串在文本中首次出现的位置(每个双字节字符占两个位置)。', + m: [2, 3], + p: [ + { + name: 'find_text', + detail: '要在要搜索的文本中查找的字符串。', + example: '"新"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'within_text', + detail: '要在其中搜索搜索字符串的首次出现位置的文本。', + example: '"农历新年"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'start_num', + detail: '[可选 - 默认值为 1] - 要在要搜索的文本中开始搜索的字符位置。', + example: '2', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'JOIN', + t: 12, + d: '将一个或多个使用指定定界符的一维数组的元素连接到一起。', + a: '将一个或多个使用指定定界符的一维数组的元素连接到一起。', + m: [2, 255], + p: [ + { + name: 'separator', + detail: '置于相互连接的值之间的字符或字符串。\n\n定界符可以为空,例如JOIN(,{1,2,3})。', + example: '" and-a "', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'array1', + detail: '要使用定界符连接的一个或多个值。', + example: '{1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'array2, ...', + detail: '[可选] - 要使用定界符连接的其他值或数组。', + example: '2', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + { + n: 'LEFT', + t: 12, + d: '从文本字符串的第一个字符开始返回指定个数的字符。', + a: '从文本字符串的第一个字符开始返回指定个数的字符。', + m: [1, 2], + p: [ + { + name: 'text', + detail: '包含要提取的字符的文本字符串。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'num_chars', + detail: '[可选 - 默认值为1] - 指定要由 LEFT 提取的字符的数量。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'RIGHT', + t: 12, + d: '根据所指定的字符数返回文本字符串中最后一个或多个字符。', + a: '根据所指定的字符数返回文本字符串中最后一个或多个字符。', + m: [1, 2], + p: [ + { + name: 'text', + detail: '包含要提取的字符的文本字符串。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'num_chars', + detail: '[可选 - 默认值为1] - 指定要由 RIGHT 提取的字符的数量。', + example: '2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MID', + t: 12, + d: '返回文本字符串中从指定位置开始的特定数目的字符。', + a: '返回文本字符串中从指定位置开始的特定数目的字符。', + m: [3, 3], + p: [ + { + name: 'text', + detail: '包含要提取的字符的文本字符串。', + example: '"get this"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'start_num', + detail: '要从字符串中开始提取的位置。字符串中第一个字符的索引为1。', + example: '5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'num_chars', + detail: '指定要由 MID 提取的字符的数量。\n\n如果提取的字符数尚不足提取长度个字符时就到达了字符串尾部,则MID返回从开始位置到字符串尾部的字符。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'LEN', + t: 12, + d: '返回给定字符串的长度。', + a: '返回给定字符串的长度。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要返回其长度的字符串。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LENB', + t: 12, + d: '返回文本中所包含的字符数。与双字节字符集(DBCS)一起使用。', + a: '返回文本中所包含的字符数。与双字节字符集(DBCS)一起使用。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要返回其字节数的字符串。(一个汉字为两个字节数)', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LOWER', + t: 12, + d: '将指定字符串中的字母转换为小写。', + a: '将指定字符串中的字母转换为小写。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要转换为小写的字符串。', + example: '"LOREM IPSUM"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'UPPER', + t: 12, + d: '将指定字符串中的字母转换为大写。', + a: '将指定字符串中的字母转换为大写。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要转换为大写的字符串。', + example: '"lorem ipsum"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'EXACT', + t: 12, + d: '比较两个字符串是否相同。', + a: '比较两个字符串是否相同。', + m: [2, 2], + p: [ + { + name: 'text1', + detail: '要比较的第一个字符串。', + example: 'A1', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'text2', + detail: '要比较的第二个字符串。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REPLACE', + t: 12, + d: '将文本字符串的一部分替换为其他文本字符串。', + a: '将文本字符串的一部分替换为其他文本字符串。', + m: [4, 4], + p: [ + { + name: 'old_text', + detail: '要对其局部进行替换操作的文本。', + example: '"Spreadsheets"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'start_num', + detail: '开始进行替换操作的位置(文本开头位置为 1)。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'num_chars', + detail: '要在文本中替换的字符个数。', + example: '6', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'new_text', + detail: '要插入到原有文本中的文本。', + example: '"Bed"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'REPT', + t: 12, + d: '返回指定文本的多次重复。', + a: '返回指定文本的多次重复。', + m: [2, 2], + p: [ + { + name: 'text', + detail: '要重复的字符或字符串。', + example: '"ha"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'number_times', + detail: '要重复的文本要在返回值中出现的次数。\n\n最大重复次数为100。即使重复次数大于100,REPT也仅将相应文本重复100次。', + example: '4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SEARCH', + t: 12, + d: '返回字符串在文本中首次出现的位置(不区分大小写)。', + a: '返回字符串在文本中首次出现的位置(不区分大小写)。', + m: [2, 3], + p: [ + { + name: 'find_text', + detail: '要在要搜索的文本中查找的字符串。', + example: '"n"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'within_text', + detail: '要在其中搜索搜索字符串的首次出现位置的文本。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'start_num', + detail: '[ 可选 - 默认值为1 ] - 要在要搜索的文本中开始搜索的字符位置。', + example: '14', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUBSTITUTE', + t: 12, + d: '在文本字符串中用 new_text 替换 old_text。', + a: '在文本字符串中用 new_text 替换 old_text。', + m: [3, 4], + p: [ + { + name: 'text', + detail: '需要替换其中字符的文本,或对含有文本(需要替换其中字符)的单元格的引用。', + example: '"search for it"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'old_text', + detail: '需要替换的文本。', + example: '"search for"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'new_text', + detail: '用于替换 old_text 的文本。', + example: '"Google"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'instance_num', + detail: '[ 可选 ] - 指定要用 new_text 替换 old_text 的事件。 如果指定了 instance_num,则只有满足要求的 old_text 被替换。 否则,文本中出现的所有 old_text 都会更改为 new_text。', + example: '3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'CLEAN', + t: 12, + d: '移除文本中的不可打印 ASCII 字符后将其返回。', + a: '移除文本中的不可打印 ASCII 字符后将其返回。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要移除其中不可打印字符的文本。', + example: '"AF"&CHAR(31)', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TEXT', + t: 12, + d: '按照指定格式将数字转换为文本。', + a: '按照指定格式将数字转换为文本。', + m: [2, 2], + p: [ + { + name: 'value', + detail: '要设置格式的数字、日期或时间。', + example: '1.23', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'format_text', + detail: '以括号括起来的模式串,将按该模式设置数字的格式。\n\n0表示在数值位数少于格式指定的位数时必定以零填充。例如,TEXT(12.3,"000.00")将返回012.30。当数值的小数位数超过模式指定的小数位数时,四舍五入为指定的小数位数。例如,TEXT(12.305,"00.00")将返回12.31。\n\n#类似于0,但并不是在小数点的两侧都以零填充。例如,TEXT(12.3,"###.##")将返回12.3。', + example: '"$0.00"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TRIM', + t: 12, + d: '删除指定字符串前后的空格。', + a: '删除指定字符串前后的空格。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要修剪的字符串或指向包含该字符串的单元格的引用。', + example: '" lorem ipsum"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'VALUE', + t: 12, + d: '将可识别的任何日期、时间或数字格式的字符串转换为数字。', + a: '将可识别的任何日期、时间或数字格式的字符串转换为数字。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '包含要转换的值的字符串。', + example: '"123"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'PROPER', + t: 12, + d: '将指定字符串中每个单词的首字母转为大写。', + a: '将指定字符串中每个单词的首字母转为大写。', + m: [1, 1], + p: [ + { + name: 'text', + detail: '要转换的文本,其中每个单词的首字母都将转为大写,所有其他字母则转为小写。', + example: '"united states"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'CONVERT', + t: 13, + d: '将数字从一种度量系统转换为另一种度量系统。', + a: '将数字从一种度量系统转换为另一种度量系统。', + m: [3, 3], + p: [ + { + name: 'number', + detail: '是以 from_unit 为单位的需要进行转换的数值。', + example: '5.1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'from_unit', + detail: '是数值的单位。', + example: '"g"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'to_unit', + detail: '是结果的单位。', + example: '"kg"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'SUMX2MY2', + t: 14, + d: '返回两数组中对应数值的平方差之和。', + a: '返回两数组中对应数值的平方差之和。', + m: [2, 2], + p: [ + { + name: 'array_x', + detail: '第一个数组或数值区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array_y', + detail: '第二个数组或数值区域。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMX2PY2', + t: 14, + d: '返回两数组中对应数值的平方和之和。', + a: '返回两数组中对应数值的平方和之和。', + m: [2, 2], + p: [ + { + name: 'array_x', + detail: '第一个数组或数值区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array_y', + detail: '第二个数组或数值区域。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMXMY2', + t: 14, + d: '返回两数组中对应数值之差的平方和。', + a: '返回两数组中对应数值之差的平方和。', + m: [2, 2], + p: [ + { + name: 'array_x', + detail: '第一个数组或数值区域。', + example: 'A2:A100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array_y', + detail: '第二个数组或数值区域。', + example: 'B2:B100', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TRANSPOSE', + t: 14, + d: '将数组或单元格范围的行列转置。', + a: '将数组或单元格范围的行列转置。', + m: [1, 1], + p: [ + { + name: 'array', + detail: '要将其行列互换的数组或范围。', + example: '{1,2}', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'TREND', + t: 14, + d: '返回线性趋势值。', + a: '返回线性趋势值。', + m: [1, 4], + p: [ + { + name: 'known_y', + detail: '关系表达式 y = mx + b 中已知的 y 值集合。\n\n如果 known_y 为二维数组或范围,则 known_x 的维数必须与之相同,或者省略此参数。\n\n如果 known_y 为一维数组或范围,known_x 则可代表二维数组或范围中的多个自变量。也就是说,如果 known_y 为单行,则将 known_x 中的每行解释为各自独立的值,类似情况也适用于 known_y 为单列的情况。', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_x', + detail: '[可选 - 默认该数组为{1,2,3,...},其大小与 known_y 相同] - 关系表达式 y = mx + b 中已知的可选 x 值集合。\n\n如果 known_y 为一维数组或范围,known_x 则可代表二维数组或范围中的多个自变量。也就是说,如果 known_y 为单行,则将 known_x 中的每行解释为各自独立的值,类似情况也适用于 known_y 为单列的情况。', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'new_x', + detail: '[可选 - 默认与 known_x 相同] - 需要函数 TREND 返回对应 y 值的新 x 值。', + example: 'A11:A13', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'const', + detail: '[可选 - 默认值为TRUE()] - 一个逻辑值,用于指定是否将常量 b 强制设为 0。\n\nTRUE() 表示 b 将按正常计算;\n\nFALSE() 表示 b 将被设为 0(零),m 将被调整以使 y = mx。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FREQUENCY', + t: 14, + d: '计算数值在某个区域内的出现频率,然后返回一个垂直数组。', + a: '计算数值在某个区域内的出现频率,然后返回一个垂直数组。', + m: [2, 2], + p: [ + { + name: 'data_array', + detail: '要对其频率进行计数的一组数值或对这组数值的引用。', + example: 'A2:A40', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'bins_array', + detail: '要将 data_array 中的值插入到的间隔数组或对间隔的引用。\n\n为清晰起见,应将类别排序,但如果未排序,FREQUENCY会在内部对这些指定的值进行排序并返回正确结果。', + example: 'B2:B5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'GROWTH', + t: 14, + d: '使用现有数据计算预测的指数等比。', + a: '使用现有数据计算预测的指数等比。', + m: [1, 4], + p: [ + { + name: 'known_y', + detail: '关系表达式 y = b*m^x 中已知的 y 值集合。\n\n如果已知数据_y为二维数组或范围,则已知数据_x的维数必须与之相同,或者省略此参数。\n\n如果已知数据_y为一维数组或范围,已知数据_x则可代表二维数组或范围中的多个自变量。也就是说,如果已知数据_y为单行,则将已知数据_x中的每行解释为各自独立的值,类似情况也适用于已知数据_y为单列的情况。', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_x', + detail: '[可选 - 默认该数组为{1,2,3,...},其大小与 known_y 相同] - 关系表达式 y = b*m^x 中已知的可选 x 值集合。\n\n如果已知数据_y为一维数组或范围,已知数据_x则可代表二维数组或范围中的多个自变量。也就是说,如果已知数据_y为单行,则将已知数据_x中的每行解释为各自独立的值,类似情况也适用于已知数据_y为单列的情况。', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'new_x', + detail: '[可选 - 默认与 known_x 相同] - 需要函数 GROWTH 返回对应 y 值的新 x 值。', + example: 'A11:A13', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'const', + detail: '[可选 - 默认值为TRUE()] -一个逻辑值,用于指定是否将常量 b 强制设为 1。\n\nTRUE() 表示 b 将按正常计算;\n\nFALSE() 表示 b 将被设为 1,m 将被调整以使 y = m^x。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LINEST', + t: 14, + d: '可通过使用最小二乘法计算与现有数据最佳拟合的直线,来计算某直线的统计值,然后返回描述此直线的数组。', + a: '可通过使用最小二乘法计算与现有数据最佳拟合的直线,来计算某直线的统计值,然后返回描述此直线的数组。', + m: [1, 4], + p: [ + { + name: 'known_y', + detail: '关系表达式 y = mx + b 中已知的 y 值集合。\n\n如果 known_y 为二维数组或范围,则 known_x 的维数必须与之相同,或者省略此参数。\n\n如果 known_y 为一维数组或范围,known_x 则可代表二维数组或范围中的多个自变量。也就是说,如果 known_y 为单行,则将 known_x 中的每行解释为各自独立的值,类似情况也适用于 known_y 为单列的情况。', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_x', + detail: '[可选 - 默认该数组为{1,2,3,...},其大小与 known_y 相同] - 关系表达式 y = mx + b 中已知的可选 x 值集合。\n\n如果 known_y 为一维数组或范围,known_x 则可代表二维数组或范围中的多个自变量。也就是说,如果 known_y 为单行,则将 known_x 中的每行解释为各自独立的值,类似情况也适用于 known_y 为单列的情况。', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'const', + detail: '[可选 - 默认值为TRUE()] - 一个逻辑值,用于指定是否将常量 b 强制设为 0。\n\nTRUE() 表示 b 将按正常计算;\n\nFALSE() 表示 b 将被设为 0(零),m 将被调整以使 y = mx。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'stats', + detail: '[可选 - 默认值为FALSE()] - 一个逻辑值,用于指定是否返回附加回归统计值。\n\n如果详细为TRUE,除了对应于每个自变量的一组线性系数和y截距之外,LINEST还返回以下信息:\n\n每项系数和截距的标准误差、\n\n限定系数(介于0和1之间,1表示完全相关)、\n\n因变量值的标准误差、\n\nF统计或F观测值,指示所观测到的因变量和自变量变量之间的关系是随机的还是线性的、\n\n自由度,用于在参照表中查找F统计值以估算可信度、\n\n回归平方和,以及\n\n残差平方和。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'LOGEST', + t: 14, + d: '在回归分析中,计算最符合数据的指数回归拟合曲线,并返回描述该曲线的数值数组。', + a: '在回归分析中,计算最符合数据的指数回归拟合曲线,并返回描述该曲线的数值数组。', + m: [1, 4], + p: [ + { + name: 'known_y', + detail: '关系表达式 y = mx + b 中已知的 y 值集合。\n\n如果 known_y 为二维数组或范围,则 known_x 的维数必须与之相同,或者省略此参数。\n\n如果 known_y 为一维数组或范围,known_x 则可代表二维数组或范围中的多个自变量。也就是说,如果 known_y 为单行,则将 known_x 中的每行解释为各自独立的值,类似情况也适用于 known_y 为单列的情况。', + example: 'B2:B10', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'known_x', + detail: '[可选 - 默认该数组为{1,2,3,...},其大小与 known_y 相同] - 关系表达式 y = mx + b 中已知的可选 x 值集合。\n\n如果 known_y 为一维数组或范围,known_x 则可代表二维数组或范围中的多个自变量。也就是说,如果 known_y 为单行,则将 known_x 中的每行解释为各自独立的值,类似情况也适用于 known_y 为单列的情况。', + example: 'A2:A10', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'const', + detail: '[可选 - 默认值为TRUE()] - 一个逻辑值,用于指定是否将常量 b 强制设为 0。\n\nTRUE() 表示 b 将按正常计算;\n\nFALSE() 表示 b 将被设为 0(零),m 将被调整以使 y = mx。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'stats', + detail: '[可选 - 默认值为FALSE()] - 一个逻辑值,用于指定是否返回附加回归统计值。\n\n如果详细为TRUE,则除了为每个自变量和系数 b 返回一组指数值之外,LOGEST 还将返回以下数据:\n\n每项指数和系数的标准误差、\n\n限定系数(介于 0 和 1 之间,1 表示完全相关)、\n\n因变量值的标准误差、\n\nF 统计或 F 观测值,指示所观测到的因变量和自变量之间的关系是随机的还是指数的、\n\n自由度 - 用于在参照表中查找 F 统计值以估算可信度、\n\n回归平方和,以及\n\n残差平方和。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'MDETERM', + t: 14, + d: '返回一个数组的矩阵行列式的值。', + a: '返回一个数组的矩阵行列式的值。', + m: [1, 1], + p: [ + { + name: 'array', + detail: '行数和列数相等的数值数组。', + example: 'A1:D4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MINVERSE', + t: 14, + d: '返回数组中存储的矩阵的逆矩阵。', + a: '返回数组中存储的矩阵的逆矩阵。', + m: [1, 1], + p: [ + { + name: 'array', + detail: '行数和列数相等的数值数组。', + example: 'A1:D4', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'MMULT', + t: 14, + d: '返回两个数组的矩阵乘积。结果矩阵的行数与 array1 的行数相同,矩阵的列数与 array2 的列数相同。', + a: '返回两个数组的矩阵乘积。结果矩阵的行数与 array1 的行数相同,矩阵的列数与 array2 的列数相同。', + m: [2, 2], + p: [ + { + name: 'array1', + detail: '要进行矩阵乘法运算的第一个矩阵数组。\n\narray1 列数必须与 array2 的行数相同', + example: 'A1:B3', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array2', + detail: '要进行矩阵乘法运算的第二个矩阵数组。\n\narray2 的行数必须与 array1 列数相同', + example: 'C1:F2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SUMPRODUCT', + t: 14, + d: '在给定的几组数组中,将数组间对应的元素相乘,并返回乘积之和。', + a: '在给定的几组数组中,将数组间对应的元素相乘,并返回乘积之和。', + m: [1, 255], + p: [ + { + name: 'array1', + detail: '其相应元素需要进行相乘并求和的第一个数组参数。', + example: 'A2:C5', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'array2', + detail: '[可选] - 其相应元素需要进行相乘并求和的其它数组参数。', + example: 'D2:F5', + require: 'o', + repeat: 'y', + type: 'rangeNumber', + }, + ], + }, + { + n: 'ISFORMULA', + t: 15, + d: '检查公式是否位于引用的单元格中。', + a: '检查公式是否位于引用的单元格中。', + m: [1, 1], + p: [ + { + name: 'cell', + detail: '要检查是否存在公式的单元格。\n\n如果 cell 为包含公式的单元格,则 ISFORMULA 将返回 TRUE。如果 cell 为相应单元格范围,则当该范围内的首个单元格包含公式时,系统会返回 TRUE。如果是任何其他值,系统都将返回 FALSE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'CELL', + t: 15, + d: '返回有关单元格的格式、位置或内容的信息。', + a: '返回有关单元格的格式、位置或内容的信息。', + m: [2, 2], + p: [ + { + name: 'info_type', + detail: '一个文本值,指定要返回的单元格信息的类型。', + example: '"type"', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'reference', + detail: '需要其相关信息的单元格。', + example: 'C2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'NA', + t: 15, + d: '返回错误值 #N/A。', + a: '返回错误值 #N/A。', + m: [0, 0], + p: [], + }, + { + n: 'ERROR_TYPE', + t: 15, + d: '返回与其他单元格中的错误值相对应的数字。', + a: '返回与其他单元格中的错误值相对应的数字。', + m: [1, 1], + p: [ + { + name: 'error_val', + detail: '用于查找错误号的单元格,虽然您也可以直接提供错误值。', + example: 'A3', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISBLANK', + t: 15, + d: '检查所引用的单元格是否为空。', + a: '检查所引用的单元格是否为空。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '对要检查其是否为空的单元格的引用。\n\n如果是空单元格,则 TRUE;否则返回 FALSE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'ISERR', + t: 15, + d: '检查某个值是否为 #N/A 以外的错误值。', + a: '检查某个值是否为 #N/A 以外的错误值。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要验证其是否为#N/A以外的错误类型的值。\n\n如果值是除#N/A之外的任何错误(包括#DIV/0!、#NAME?、#NULL!、#NUM!、#VALUE!和#REF!),ISERR将返回TRUE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISERROR', + t: 15, + d: '检查某个值是否为错误值。', + a: '检查某个值是否为错误值。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要验证其是否为错误类型的值。\n\n只要值是某种错误值(包括#DIV/0!、#N/A、#NAME?、#NULL!、#NUM!、#VALUE!和#REF!),ISERROR就会返回TRUE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISLOGICAL', + t: 15, + d: '检查某个值是 TRUE 还是 FALSE。', + a: '检查某个值是 TRUE 还是 FALSE。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要验证其为逻辑TRUE还是逻辑FALSE的值。\n\n*如果值为TRUE或FALSE,或为指向值为TRUE或FALSE的单元格的引用,ISLOGICAL将返回TRUE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISNA', + t: 15, + d: '检查某个值是否为错误值 #N/A。', + a: '检查某个值是否为错误值 #N/A。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要与错误值#N/A进行比较的值。\n\n*如果值为#N/A或指向包含#N/A的单元格的引用,则ISNA将返回TRUE,否则返回FALSE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISNONTEXT', + t: 15, + d: '检查某个值是否为非文本。', + a: '检查某个值是否为非文本。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要检查的文本。\n\n*如果参数为文本值或指向包含文本的单元格的引用,ISNONTEXT将返回FALSE,否则返回TRUE。\n\n当值为指向空单元格的引用时,ISNONTEXT会返回TRUE。\n\n当值为空字符串时,ISNONTEXT将返回FALSE,因为空串被视作文本。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISNUMBER', + t: 15, + d: '检查某个值是否为数字。', + a: '检查某个值是否为数字。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要验证其是否为数字的值。\n\n*如果参数为数字或指向内容为数字值的单元格的引用,ISNUMBER将返回TRUE,否则返回FALSE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'ISREF', + t: 15, + d: '检查某个值是否为有效的单元格引用。', + a: '检查某个值是否为有效的单元格引用。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要验证其是否为单元格引用的值。\n\n*如果参数是有效的单元格引用,ISREF将返回TRUE,否则返回FALSE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'ISTEXT', + t: 15, + d: '检查某个值是否为文本。', + a: '检查某个值是否为文本。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要验证其是否为文本的值。\n\n如果参数为文本值或指向包含文本值的单元格的引用,ISTEXT将返回TRUE,否则返回FALSE。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TYPE', + t: 15, + d: '返回数值的类型。', + a: '返回数值的类型。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要确定其类型的数据。\n\n数字 返回 1;\n\n文本 返回 2;\n\n逻辑值 返回 4;\n\n错误值 返回 16;\n\n数组 返回 64;', + example: 'C4', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'N', + t: 15, + d: '返回转化为数值后的值。', + a: '返回转化为数值后的值。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要转换为数字的参数。\n\n如果值为数字,则返回该数字。\n\n如果值为日期,则返回该日期的序列号。\n\n如果值为TRUE,则返回1。\n\n如果值为FALSE,则返回0。\n\n如果值为错误值,则返回错误值。\n\n如果值为其他值,则返回0。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TO_DATE', + t: 16, + d: '将指定的数字转换为日期。', + a: '将指定的数字转换为日期。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要转换为日期的参数或其单元格引用。\n\n如果值为数字或指向内容为数值的单元格的引用,TO_DATE会将值转换为相应的日期并返回,值代表从十二月30日到对应的日期之间的天数,\n\n负值表示对应的日期在十二月30日之前,而小数值则代表一天中从午夜算起的时间。\n如果值不是数字或指向内容为数值的单元格的引用,则TO_DATE将在不做任何修改的情况下返回值。', + example: '25405', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TO_PURE_NUMBER', + t: 16, + d: '将给定的日期/时间、百分比、货币金额或其他格式的数值转换为不带格式的纯数字。', + a: '将给定的日期/时间、百分比、货币金额或其他格式的数值转换为不带格式的纯数字。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要转换为纯数字的参数或其单元格引用。\n\n如果值为数字或指向包含数值的单元格的引用,TO_PURE_NUMBER将以不带任何格式与解释的形式返回值。\n\n如果值不是数字或指向内容为数值的单元格的引用,则TO_PERCENT将在不做任何修改的情况下返回值。', + example: '50%', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TO_TEXT', + t: 16, + d: '将给定的数字值转换为文本格式。', + a: '将给定的数字值转换为文本格式。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要转换为文本的参数或其单元格引用。\n\n如果值为数字或指向包含数值的单元格的引用,TO_TEXT将返回字符串形式的值,并保持现有格式。即原为货币的仍为货币,原为十进制数的仍为十进制数,原为百分比的仍为百分比,原为日期的仍为日期。\n\n如果值不是数字或指向内容为数值的单元格的引用,则TO_TEXT将在不做任何修改的情况下返回值。', + example: '24', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'TO_DOLLARS', + t: 16, + d: '将指定的数字转换为美元金额。', + a: '将指定的数字转换为美元金额。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要转换为美元金额的参数或其单元格引用。\n\n如果值不是数字或指向内容为数值的单元格的引用,则 TO_DOLLARS 将在不做任何修改的情况下返回值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'TO_PERCENT', + t: 16, + d: '将指定的数字转换为百分比。', + a: '将指定的数字转换为百分比。', + m: [1, 1], + p: [ + { + name: 'value', + detail: '要转换为百分比的参数或其单元格引用。\n\n如果值为数字或指向包含数值的单元格的引用,TO_PERCENT会以1 = 100%为标准,将值转换为百分比。\n\n如果值不是数字或指向内容为数值的单元格的引用,则TO_PERCENT将在不做任何修改的情况下返回值。', + example: 'A2', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'DGET', + t: 17, + d: '使用 SQL 式查询,从列表或数据库的列中提取符合指定条件的单个值。', + a: '使用 SQL 式查询,从列表或数据库的列中提取符合指定条件的单个值。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DMAX', + t: 17, + d: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的最大数字。', + a: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的最大数字。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DMIN', + t: 17, + d: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的最小数字。', + a: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的最小数字。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DAVERAGE', + t: 17, + d: '使用 SQL 式查询,对列表或数据库中满足指定条件的记录字段(列)中的数值求平均值。', + a: '使用 SQL 式查询,对列表或数据库中满足指定条件的记录字段(列)中的数值求平均值。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DCOUNT', + t: 17, + d: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中包含数字的单元格的个数。', + a: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中包含数字的单元格的个数。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DCOUNTA', + t: 17, + d: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的非空单元格的个数。', + a: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的非空单元格的个数。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DPRODUCT', + t: 17, + d: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的数值的乘积。', + a: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的数值的乘积。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DSTDEV', + t: 17, + d: '使用 SQL 式查询,返回利用列表或数据库中满足指定条件的记录字段(列)中的数字作为一个样本估算出的总体标准偏差。', + a: '使用 SQL 式查询,返回利用列表或数据库中满足指定条件的记录字段(列)中的数字作为一个样本估算出的总体标准偏差。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DSTDEVP', + t: 17, + d: '使用 SQL 式查询,返回利用列表或数据库中满足指定条件的记录字段(列)中的数字作为样本总体计算出的总体标准偏差。', + a: '使用 SQL 式查询,返回利用列表或数据库中满足指定条件的记录字段(列)中的数字作为样本总体计算出的总体标准偏差。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DSUM', + t: 17, + d: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的数字之和。', + a: '使用 SQL 式查询,返回列表或数据库中满足指定条件的记录字段(列)中的数字之和。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DVAR', + t: 17, + d: '使用 SQL 式查询,返回利用列表或数据库中满足指定条件的记录字段(列)中的数字作为一个样本估算出的总体方差。', + a: '使用 SQL 式查询,返回利用列表或数据库中满足指定条件的记录字段(列)中的数字作为一个样本估算出的总体方差。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'DVARP', + t: 17, + d: '使用 SQL 式查询,通过使用列表或数据库中满足指定条件的记录字段(列)中的数字计算样本总体的样本总体方差。', + a: '使用 SQL 式查询,通过使用列表或数据库中满足指定条件的记录字段(列)中的数字计算样本总体的样本总体方差。', + m: [3, 3], + p: [ + { + name: 'database', + detail: '构成列表或数据库的单元格区域,列表的第一行包含每一列的标签。', + example: 'A2:F20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'field', + detail: '指定 database 中的哪一列包含要提取和用于计算的值。\n\nfield 可以是与 database 第一行中某个列标题对应的文本标签,也可以是指定相关列的数字索引,第一列的索引值为 1。', + example: 'G2', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'criteria', + detail: '包含所指定条件的单元格区域。计算之前将使用这些条件来过滤 database 中的值。', + example: 'A22:D23', + require: 'm', + repeat: 'n', + type: 'range', + }, + ], + }, + { + n: 'AGE_BY_IDCARD', + t: '3', + d: '根据中国身份证号计算出年龄。支持15位或18位身份证', + a: '根据身份证号得到年龄。', + m: [1, 2], + p: [ + { + name: '身份证号', + example: 'A1', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '截止日期', + example: '"2017-10-01"', + detail: '年龄计算的截止日期或范围,默认为当日。', + require: 'o', + repeat: 'n', + type: 'rangedatetime', + }, + ], + }, + // SEX_BY_IDCARD + { + n: 'SEX_BY_IDCARD', + t: '3', + d: '根据中国身份证号计算出性别。支持15位或18位身份证', + a: '根据身份证号得到性别。', + m: [1, 1], + p: [ + { + name: '身份证号', + example: '"31033519900101XXXX"', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // BIRTHDAY_BY_IDCARD + { + n: 'BIRTHDAY_BY_IDCARD', + t: '3', + d: '根据中国身份证号计算出生日。支持15位或18位身份证', + a: '根据身份证号得到生日。', + m: [1, 2], + p: [ + { + name: '身份证号', + example: '"31033519900101XXXX"', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '生日格式', + example: '0', + detail: '日期类型,默认0:[1900/01/01], 1:[1900-01-01], 2:[1900年1月1日]', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // PROVINCE_BY_IDCARD + { + n: 'PROVINCE_BY_IDCARD', + t: '3', + d: '根据中国身份证号计算出籍贯的省份。支持15位或18位身份证', + a: '根据身份证号得到籍贯的省份。', + m: [1, 1], + p: [ + { + name: '身份证号', + example: '"31033519900101XXXX"', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // CITY_BY_IDCARD + { + n: 'CITY_BY_IDCARD', + t: '3', + d: '根据中国身份证号计算出籍贯的城市。支持15位或18位身份证', + a: '根据身份证号得到籍贯的城市。', + m: [1, 1], + p: [ + { + name: '身份证号', + example: '"31033519900101XXXX"', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // STAR_BY_IDCARD + { + n: 'STAR_BY_IDCARD', + t: '3', + d: '根据中国身份证号计算出星座。支持15位或18位身份证', + a: '根据身份证号得到星座。', + m: [1, 1], + p: [ + { + name: '身份证号', + example: '"31033519900101XXXX"', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // ANIMAL_BY_IDCARD + { + n: 'ANIMAL_BY_IDCARD', + t: '3', + d: '根据中国身份证号计算出生肖(鼠、牛、虎、兔...)。支持15位或18位身份证', + a: '根据身份证号得到生肖。', + m: [1, 1], + p: [ + { + name: '身份证号', + example: '"31033519900101XXXX"', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // ISIDCARD + { + n: 'ISIDCARD', + t: '3', + d: '验证身份证的格式是否正确。支持15位或18位身份证', + a: '验证身份证格式正确性。', + m: [1, 1], + p: [ + { + name: '身份证号', + example: '"31033519900101XXXX"', + detail: '15位或者18位的身份证号或范围。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // DM_TEXT_CUTWORD + { + n: 'DM_TEXT_CUTWORD', + t: '4', + d: '文本分词。把一连串文字拆分为一系列单独词语', + a: '中文文本分词。', + m: [1, 2], + p: [ + { + name: '文本', + example: '"我来到北京清华大学"', + detail: '任意需要分词的文本。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '分词模式', + example: '0', + detail: '默认为0[精确模式], 1[全模式], 2[搜索引擎模式]。', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DM_TEXT_TFIDF + { + n: 'DM_TEXT_TFIDF', + t: '4', + d: '采用tf-idf算法进行关键词提取。从一连串文字中识别关键词', + a: 'tf-idf关键词识别。', + m: [1, 3], + p: [ + { + name: '文本', + example: '"我来到北京清华大学"', + detail: '任意需要分词的文本。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '关键词个数', + example: '20', + detail: '算法返回的关键词个数,默认20', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '语料库', + example: '1', + detail: '选择特定领域的语料库,默认0[通用], 1[金融], 2[医疗]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DM_TEXT_TEXTRANK + { + n: 'DM_TEXT_TEXTRANK', + t: '4', + d: '采用TextRank算法进行关键词提取。从一连串文字中识别关键词', + a: 'TextRank关键词识别。', + m: [1, 3], + p: [ + { + name: '文本', + example: '"我来到北京清华大学"', + detail: '任意需要分词的文本。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '关键词个数', + example: '20', + detail: '算法返回的关键词个数,默认20', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: '语料库', + example: '1', + detail: '选择特定领域的语料库,默认0[通用], 1[金融], 2[医疗]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DATA_CN_STOCK_CLOSE + { + n: 'DATA_CN_STOCK_CLOSE', + t: '5', + d: '根据股票代码和日期,返回A股对应股票收盘价。', + a: '返回A股对应股票收盘价。', + m: [1, 3], + p: [ + { + name: '股票代码', + example: '"000001"', + detail: '6位股票代码,必填项。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '日期', + example: '2015-01-08', + detail: '股票的交易日,默认为最新交易日', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: '复权除权', + example: '0', + detail: '选择股票的除权复权类型,默认0[前复权], 1[原始价格], 2[后复权]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DATA_CN_STOCK_OPEN + { + n: 'DATA_CN_STOCK_OPEN', + t: '5', + d: '根据股票代码和日期,返回A股对应股票开盘价。', + a: '返回A股对应股票开盘价。', + m: [1, 3], + p: [ + { + name: '股票代码', + example: '"000001"', + detail: '6位股票代码,必填项。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '日期', + example: '2015-01-08', + detail: '股票的交易日,默认为最新交易日', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: '复权除权', + example: '0', + detail: '选择股票的除权复权类型,默认0[前复权], 1[原始价格], 2[后复权]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DATA_CN_STOCK_MAX + { + n: 'DATA_CN_STOCK_MAX', + t: '5', + d: '根据股票代码和日期,返回A股对应股票最高价。', + a: '返回A股对应股票最高价。', + m: [1, 3], + p: [ + { + name: '股票代码', + example: '"000001"', + detail: '6位股票代码,必填项。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '日期', + example: '2015-01-08', + detail: '股票的交易日,默认为最新交易日', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: '复权除权', + example: '0', + detail: '选择股票的除权复权类型,默认0[前复权], 1[原始价格], 2[后复权]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DATA_CN_STOCK_MIN + { + n: 'DATA_CN_STOCK_MIN', + t: '5', + d: '根据股票代码和日期,返回A股对应股票最低价。', + a: '返回A股对应股票最低价。', + m: [1, 3], + p: [ + { + name: '股票代码', + example: '"000001"', + detail: '6位股票代码,必填项。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '日期', + example: '2015-01-08', + detail: '股票的交易日,默认为最新交易日', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: '复权除权', + example: '0', + detail: '选择股票的除权复权类型,默认0[前复权], 1[原始价格], 2[后复权]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DATA_CN_STOCK_VOLUMN + { + n: 'DATA_CN_STOCK_VOLUMN', + t: '5', + d: '根据股票代码和日期,返回A股对应股票成交量。', + a: '返回A股对应股票成交量。', + m: [1, 3], + p: [ + { + name: '股票代码', + example: '"000001"', + detail: '6位股票代码,必填项。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '日期', + example: '2015-01-08', + detail: '股票的交易日,默认为最新交易日', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: '复权除权', + example: '0', + detail: '选择股票的除权复权类型,默认0[前复权], 1[原始价格], 2[后复权]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // DATA_CN_STOCK_AMOUNT + { + n: 'DATA_CN_STOCK_AMOUNT', + t: '5', + d: '根据股票代码和日期,返回A股对应股票成交额。', + a: '返回A股对应股票成交额。', + m: [1, 3], + p: [ + { + name: '股票代码', + example: '"000001"', + detail: '6位股票代码,必填项。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + { + name: '日期', + example: '2015-01-08', + detail: '股票的交易日,默认为最新交易日', + require: 'o', + repeat: 'n', + type: 'rangedate', + }, + { + name: '复权除权', + example: '0', + detail: '选择股票的除权复权类型,默认0[前复权], 1[原始价格], 2[后复权]', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + // ISDATE + { + n: 'ISDATE', + t: '6', + d: '验证日期的格式是否正确。支持多种日期格式', + a: '验证日期格式正确性。', + m: [1, 1], + p: [ + { + name: '日期', + example: '"1990-01-01"', + detail: '日期值,例如1990/01/01, 1990年1月1日等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // sparklines函数,线图 + { + n: 'LINESPLINES', + t: '3', + d: '生成嵌入在单元格内的折线图sparklines,用于描述数据的连续走势', + a: '生成单元格折线图', + m: [1, 8], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // lineColor + { + name: '线条颜色', + example: '#2ec7c9', + detail: '线图的线条颜色,可以是否个范围A1、色表索引数值或者具体颜色值,设置为0或false则不显示,支持regx、rgb、rgba等。默认#2ec7c9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // { + // "name": "填充颜色", + // "example": '#CCF3F4', + // "detail": "形成面积图,同线条颜色配置,默认0不显示", + // "require": "o", + // "repeat": "n", + // "type": "rangeAll" + // }, + // lineWidth + { + name: '线条粗细', + example: '1', + detail: '折线图线段粗细,默认为1px', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // normalRangeMin和normalRangeMax设为相同的值,min、max、avg、median需要计算 + { + name: '辅助线', + example: 'avg', + detail: '一条横线,可以是min、max、avg、median、范围或自定义数值,默认0无', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // normalRangeColor + { + name: '辅助线颜色', + example: '#000', + detail: '辅助线的颜色设置,同线条颜色配置,默认#000', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // maxSpotColor + { + name: '最大值标识', + example: '#fc5c5c', + detail: '标识线图最大值,同线条颜色配置,默认0不显示', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // minSpotColor + { + name: '最小值标识', + example: '#fc5c5c', + detail: '标识线图最小值,同线条颜色配置,默认0不显示', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // spotRadius + { + name: '标识大小', + example: '1.5', + detail: '最大值和最小值的标识大小设置,默认为1.5', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // sparklines面积图 + { + n: 'AREASPLINES', + t: '3', + d: '生成嵌入在单元格内的面积图sparklines,一般用于描述数据的连续累积值走势', + a: '生成单元格面积图', + m: [1, 5], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // lineColor + { + name: '线条颜色', + example: '#2ec7c9', + detail: '线图的线条颜色,可以是否个范围A1、色表索引数值或者具体颜色值,设置为0或false则不显示,支持regx、rgb、rgba等。默认#2ec7c9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // fillColor + { + name: '填充颜色', + example: '#CCF3F4', + detail: '形成面积图,同线条颜色配置,默认0不显示', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // lineWidth + { + name: '线条粗细', + example: '1', + detail: '折线图线段粗细,默认为1px', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // normalRangeMin和normalRangeMax设为相同的值,min、max、avg、median需要计算 + { + name: '辅助线', + example: 'avg', + detail: '一条横线,可以是min、max、avg、median、范围或自定义数值,默认0无', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // normalRangeColor + { + name: '辅助线颜色', + example: '#000', + detail: '辅助线的颜色设置,同线条颜色配置,默认#000', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // //maxSpotColor + // { + // "name": "最大值标识", + // "example": '#fc5c5c', + // "detail": "标识线图最大值,同线条颜色配置,默认0不显示", + // "require": "o", + // "repeat": "n", + // "type": "rangeAll" + // }, + // //minSpotColor + // { + // "name": "最小值标识", + // "example": '#fc5c5c', + // "detail": "标识线图最大值,同线条颜色配置,默认0不显示", + // "require": "o", + // "repeat": "n", + // "type": "rangeAll" + // }, + // //spotRadius + // { + // "name": "标识大小", + // "example": '1.5', + // "detail": "最大值和最小值的标识大小设置,默认为1.5", + // "require": "o", + // "repeat": "n", + // "type": "rangeAll" + // } + ], + }, + // sparklines柱状图 + { + n: 'COLUMNSPLINES', + t: '3', + d: '生成嵌入在单元格内的垂直柱状图sparklines,一般用于描述离散数据之间的大小情况', + a: '生成单元格垂直柱状图', + m: [1, 6], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // barSpacing + { + name: '柱条间隔', + example: '1', + detail: '柱条之间的间隔距离,默认为1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // barColor + { + name: '柱条颜色', + example: '#fc5c5c', + detail: '线图的线条颜色,可以是否个范围A1、色表索引数值或者具体颜色值,设置为0或false则不显示,支持regx、rgb、rgba等。默认#fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // negBarColor + { + name: '负向柱条颜色', + example: '#97b552', + detail: '负向柱条颜色设置,代表负值的颜色,同柱条颜色配置,默认#97b552', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // chartRangeMax + { + name: '最大值', + example: '100', + detail: '柱图最大值,用于规范柱图长度,默认为自动计算false、auto、null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // colorMap + { + name: '色板', + example: '#97b552', + detail: '调色板可以单独设置每个柱条的颜色,可设置多个,支持两种格式:1颜色例如#000,代表第一个柱的颜色是黑色;2数值范围:颜色,例如-2:#000表示数值为-2的柱为黑色,0:5:#000表示数值0-5的柱为黑色,默认为空', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // sparklines累积柱状图 + { + n: 'STACKCOLUMNSPLINES', + t: '3', + d: '生成嵌入在单元格内的累积垂直柱状图sparklines,一般用于描述离散数据多个维度的数值大小', + a: '生成单元格累积垂直柱状图', + m: [1, 5], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // stackconfig + { + name: '按列堆积', + example: '1', + detail: '如果需要按行堆积则本项设为false或0,默认为是1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // barSpacing + { + name: '柱条间隔', + example: '1', + detail: '柱条之间的间隔距离,默认为1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // chartRangeMax + { + name: '最大值', + example: '100', + detail: '累积柱图最大值,用于规范柱图长度,默认为自动计算false、auto、null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // stackedBarColor + { + name: '累积色板', + example: '#97b552', + detail: '调色板可以单独设置每个维度的柱条颜色,可设置为A1:A10等范围,默认为#2ec7c9, #fc5c5c, #5ab1ef, #ffb980...', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // sparklines条形图 + { + n: 'BARSPLINES', + t: '3', + d: '生成嵌入在单元格内的横向条形图sparklines,一般用于描述离散数据之间的大小情况', + a: '生成单元格横向条形图', + m: [1, 6], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // barSpacing + { + name: '柱条间隔', + example: '1', + detail: '柱条之间的间隔距离,默认为1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // barColor + { + name: '柱条颜色', + example: '#fc5c5c', + detail: '线图的线条颜色,可以是否个范围A1、色表索引数值或者具体颜色值,设置为0或false则不显示,支持regx、rgb、rgba等。默认#fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // negBarColor + { + name: '负向柱条颜色', + example: '#97b552', + detail: '负向柱条颜色设置,代表负值的颜色,同柱条颜色配置,默认#97b552', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // chartRangeMax + { + name: '最大值', + example: '100', + detail: '柱图最大值,用于规范柱图长度,默认为自动计算false、auto、null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // colorMap + { + name: '色板', + example: '#97b552', + detail: '调色板可以单独设置每个柱条的颜色,可设置多个,支持两种格式:1颜色例如#000,代表第一个柱的颜色是黑色;2数值范围:颜色,例如-2:#000表示数值为-2的柱为黑色,0:5:#000表示数值0-5的柱为黑色,默认为空', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // sparklines累积条形图 + { + n: 'STACKBARSPLINES', + t: '3', + d: '生成嵌入在单元格内的累积横向条形图sparklines,一般用于描述离散数据多个维度的数值大小', + a: '生成单元格累积横向条形图', + m: [1, 5], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // stackconfig + { + name: '按列堆积', + example: '1', + detail: '如果需要按行堆积则本项设为false或0,默认为是1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // barSpacing + { + name: '柱条间隔', + example: '1', + detail: '柱条之间的间隔距离,默认为1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // chartRangeMax + { + name: '最大值', + example: '100', + detail: '累积柱图最大值,用于规范柱图长度,默认为自动计算false、auto、null', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // stackedBarColor + { + name: '累积色板', + example: '#97b552', + detail: '调色板可以单独设置每个维度的柱条颜色,可设置为A1:A10等范围,默认为#2ec7c9, #fc5c5c, #5ab1ef, #ffb980...', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // sparklines离散图 + { + n: 'DISCRETESPLINES', + t: '3', + d: '生成嵌入在单元格内的离散图sparklines,一般用于描述离散数据走势', + a: '生成单元格离散图', + m: [1, 4], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // thresholdValue + { + name: '分割阈值', + example: '1', + detail: '离散图柱形颜色的区分,例如:该值为0,则大于0为蓝色,小于0为红色,默认为0', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // lineColor + { + name: '阈值以上颜色', + example: '#2ec7c9', + detail: '线图的线条颜色,可以是否个范围A1、色表索引数值或者具体颜色值,设置为0或false则不显示,支持regx、rgb、rgba等。默认#2ec7c9', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // thresholdColor + { + name: '阈值以下颜色', + example: '#fc5c5c', + detail: '阈值以下柱条颜色设置,同阈值以上颜色,默认#fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // sparklines三态图 + { + n: 'TRISTATESPLINES', + t: '3', + d: '生成嵌入在单元格内的三态图sparklines,一般用于描述三种态势的走势例如胜负平', + a: '生成单元格三态图', + m: [1, 6], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // barSpacing + { + name: '柱条间隔', + example: '1', + detail: '柱条之间的间隔距离,默认为1', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // posBarColor + { + name: '柱条颜色', + example: '#fc5c5c', + detail: '线图的线条颜色,可以是否个范围A1、色表索引数值或者具体颜色值,设置为0或false则不显示,支持regx、rgb、rgba等。默认#fc5c5c', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // negBarColor + { + name: '负向柱条颜色', + example: '#97b552', + detail: '负向柱条颜色设置,代表负值的颜色,同柱条颜色配置,默认#97b552', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // zeroBarColor + { + name: '零值柱条颜色', + example: '#999', + detail: '零值柱条颜色设置,代表0值颜色,同柱条颜色配置,默认#999', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // colorMap + { + name: '色板', + example: '#97b552', + detail: '调色板可以单独设置每个柱条的颜色,可设置多个,支持两种格式:1颜色例如#000,代表第一个柱的颜色是黑色;2数值范围:颜色,例如-2:#000表示数值为-2的柱为黑色,0-5:#000表示数值0-5的柱为黑色,默认为空', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // sparklines饼图 + { + n: 'PIESPLINES', + t: '3', + d: '生成嵌入在单元格内的饼图sparklines,一般用于描述数据占比', + a: '生成单元格饼图', + m: [1, 5], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // offset + { + name: '旋转角度', + example: '0', + detail: '饼图的旋转角度,默认为0', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // borderWidth + { + name: '饼图边框', + example: '0', + detail: '饼图边框大小,默认为无0', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // borderColor + { + name: '边框颜色', + example: '#000', + detail: '饼图边框颜色,默认为#000', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // sliceColors + { + name: '饼图色板', + example: '#97b552', + detail: '调色板可以设置切片的颜色,可设置为A1:A10等范围,默认为#2ec7c9, #fc5c5c, #5ab1ef, #ffb980...', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // sparklines箱线图 + { + n: 'BOXSPLINES', + t: '3', + d: '生成嵌入在单元格内的箱线图sparklines,一般用于描述数据集的统计分布', + a: '生成单元格箱线图', + m: [1, 4], + p: [ + // data + { + name: '数据范围', + example: 'A1:A20', + detail: '数据范围,数值才能被有效计算,例如A1:A20, {1,2,3,4,5}等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // outlierIQR,如果为0或false则showOutliers设为false,否则为true + { + name: '离群点比例', + example: '1.5', + detail: '离群点的阈值范围,如果为0或false则不显示,默认为1.5倍', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // target + { + name: '目标点值', + example: '10', + detail: '箱线图上的目标值设置,默认为false不显示', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + // spotRadius + { + name: '数据点大小', + example: '1.5', + detail: '目标点和离群点的半径大小设置,默认为1.5', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + // sparklines子弹图 + { + n: 'BULLETSPLINES', + t: '3', + d: '生成嵌入在单元格内的子弹图sparklines,一般用于描述任务达成率', + a: '生成单元格子弹图', + m: [2, 3], + p: [ + // 目标data1 + { + name: '目标', + example: '10', + detail: '达成的目标值,数值才能被有效计算,例如A1, 100等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // 目前达成data2 + { + name: '实际完成', + example: '8', + detail: '目前完成值,数值才能被有效计算,例如A1, 100等。', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + // 对比值datax + { + name: '对比值', + example: '12', + detail: '对比值,例如超额、最低、获奖底线等,数值才能被有效计算,例如A1, 100等。可以设置最多9个对比值', + require: 'o', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // sparklines组合图,支持多个类型的图画在同一个单元格 + { + n: 'COMPOSESPLINES', + t: '3', + d: '支持多个类型的图画在同一个单元格,每个参数代表一个sparklines图', + a: '组合sparklines图到一个单元格', + m: [1, 1], + p: [ + // data + { + name: '图设置', + example: 'PIESPLINES(A1:A20)', + detail: 'sparklines图设置,例如A1:A20, 一个完成的饼图、线图设置等。', + require: 'm', + repeat: 'y', + type: 'rangeAll', + }, + ], + }, + // 动态数组公式 + { + n: 'SORT', + t: '14', + d: '返回数组中元素的排序数组。返回的数组与提供的数组参数形状相同。', + a: '返回数组中元素的排序数组。返回的数组与提供的数组参数形状相同。', + m: [1, 4], + p: [ + { + name: 'array', + detail: '要排序的范围或数组。', + example: 'A2:A17', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'sort_index', + detail: '[可选] - 表示要排序的行或列的数字。(默认row1/col1)', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'sort_order', + detail: '[可选] - 表示所需排序顺序的数字;1表示升序(默认),-1表示降序。', + example: '-1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'by_col', + detail: '[可选] - 表示所需排序方向的逻辑值;按行排序为FALSE()(默认),按列排序为TRUE()。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'FILTER', + t: '14', + d: '基于一个布尔(真/假)数组过滤一个数组。', + a: '基于一个布尔(真/假)数组过滤一个数组。', + m: [2, 3], + p: [ + { + name: 'array', + detail: '要筛选的数组或范围。', + example: 'A5:D20', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'include', + detail: '布尔数组,其高度或宽度与数组相同', + example: '1', + require: 'm', + repeat: 'n', + type: 'range', + }, + { + name: 'if_empty', + detail: '[可选] - 如果包含数组中的所有值都为空(filter不返回任何值),则返回的值。', + example: '""', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'UNIQUE', + t: '14', + d: '返回列表或区域中的唯一值的列表。', + a: '返回列表或区域中的唯一值的列表。', + m: [1, 3], + p: [ + { + name: 'array', + detail: '从其返回唯一值的数组或区域。', + example: 'A2:B26', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'by_col', + detail: '[可选] - 逻辑值,指示如何比较;按行 = FALSE() 或省略;按列 = TRUE()。', + example: 'TRUE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + { + name: 'occurs_once', + detail: '[可选] - 逻辑值,仅返回唯一值中出现一次 = TRUE();包括所有唯一值 = FALSE() 或省略。', + example: 'FALSE()', + require: 'o', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + { + n: 'RANDARRAY', + t: '14', + d: '返回 0 到 1 之间的随机数字数组。', + a: '返回 0 到 1 之间的随机数字数组。', + m: [0, 2], + p: [ + { + name: 'rows', + detail: '[可选] - 要返回的行数。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cols', + detail: '[可选] - 要返回的列数。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'SEQUENCE', + t: '14', + d: '生成数字序列的列表。', + a: '生成数字序列的列表。', + m: [1, 4], + p: [ + { + name: 'rows', + detail: '要返回的行数。', + example: '1', + require: 'm', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'cols', + detail: '[可选] - 要返回的列数。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'start', + detail: '[可选] - 序列中的第一个数字。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + { + name: 'step', + detail: '[可选] - 序列中每个序列值的增量。', + example: '1', + require: 'o', + repeat: 'n', + type: 'rangeNumber', + }, + ], + }, + { + n: 'EVALUATE', + t: '3', + d: '对以文字表示的公式或者表达式求值,并返回结果。', + a: '根据文字公式或者表达式求值。', + m: [1, 1], + p: [ + { + name: '公式', + example: '"A1+5*2^2"', + detail: '公式或表达式', + require: 'm', + repeat: 'n', + type: 'rangeAll', + }, + ], + }, + ], + functiontype: { + '0': '数学', + '1': '统计', + '2': '查找', + '3': 'UniverSheet内置', + '4': '数据挖掘', + '5': '数据源', + '6': '日期', + '7': '过滤器', + '8': '财务', + '9': '工程计算', + '10': '逻辑', + '11': '运算符', + '12': '文本', + '13': '转换工具', + '14': '数组', + '15': '其他', + }, + formulaMore: { + valueTitle: '值', + tipSelectDataRange: '选取数据范围', + tipDataRangeTile: '数据范围', + findFunctionTitle: '查找函数', + tipInputFunctionName: '请输入您要查找的函数名称或函数功能的简要描述', + whole: '全部', + + Math: '数学', + Statistical: '统计', + Lookup: '查找', + universheet: 'UniverSheet内置', + dataMining: '数据挖掘', + Database: '数据源', + Date: '日期', + Filter: '过滤器', + Financial: '财务', + Engineering: '工程计算', + Logical: '逻辑', + Operator: '运算符', + Text: '文本', + Parser: '转换工具', + Array: '数组', + other: '其它', + + selectFunctionTitle: '选择函数', + calculationResult: '计算结果', + + tipSuccessText: '成功', + tipParamErrorText: '参数类型错误', + + helpClose: '关闭', + helpCollapse: '收起', + helpExample: '示例', + helpAbstract: '摘要', + + execfunctionError: '提示", "公式存在错误', + execfunctionSelfError: '公式不可引用其本身的单元格', + execfunctionSelfErrorResult: '公式不可引用其本身的单元格,会导致计算结果不准确', + + allowRepeatText: '可重复', + allowOptionText: '可选', + + selectCategory: '或选择类别', + }, + ifFormula: { + tipNotBelongToIf: '该单元格函数不属于if公式!', + tipSelectCell: '请选择单元格插入函数', + + ifGenCompareValueTitle: '比较值', + ifGenSelectCellTitle: '点击选择单元格', + ifGenRangeTitle: '范围', + ifGenRangeTo: '至', + ifGenRangeEvaluate: '范围评估', + ifGenSelectRangeTitle: '点击选择范围', + ifGenCutWay: '划分方式', + ifGenCutSame: '划分值相同', + ifGenCutNpiece: '划分为N份', + ifGenCutCustom: '自定义输入', + ifGenCutConfirm: '生成', + + ifGenTipSelectCell: '选择单元格', + ifGenTipSelectCellPlace: '请选择单元格', + + ifGenTipSelectRange: '选择单范围', + ifGenTipSelectRangePlace: '请选择范围', + + ifGenTipNotNullValue: '比较值不能为空!', + ifGenTipLableTitile: '标签', + ifGenTipRangeNotforNull: '范围不能为空!', + ifGenTipCutValueNotforNull: '划分值不能为空!', + ifGenTipNotGenCondition: '没有生成可用的条件!', + }, + }, +}; diff --git a/packages/sheets-plugin-formula/src/Service/FormulaCacheLoader.ts b/packages/sheets-plugin-formula/src/Service/FormulaCacheLoader.ts new file mode 100644 index 0000000000..32d301bc38 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Service/FormulaCacheLoader.ts @@ -0,0 +1,64 @@ +import { CellValueType, ICellData, IWorkbookConfig, ObjectMatrix } from '@univer/core'; + +type SheetDataType = { [sheetName: string]: ObjectMatrix }; + +type CellDataType = string | number | boolean; + +export class FormulaCacheLoader { + // Workbook data + private _sheetData: SheetDataType; + + // Active worksheet name + private _activeSheetName: string; + + // 18.5.1.2 table (Table) + private _tableData: { [tableName: string]: string }; + + // 18.2.6 definedNames (Defined Names) + private _definedNames: { [name: string]: string }; + + // formula runtime data + private _sheetDataCache: SheetDataType = {}; + + loadSheetData(workBookData: IWorkbookConfig) {} + + loadTableData(tableData: { [tableName: string]: string }) {} + + loadDefinedNames(definedNames: { [name: string]: string }) {} + + getSheetData(sheetName: string) { + return this._sheetData[sheetName]; + } + + getActiveSheetData() { + return this._sheetData[this._activeSheetName]; + } + + getTable(tableName: string) { + return this._tableData[tableName]; + } + + getDefinedName(name: string) { + return this._definedNames[name]; + } + + setSheetDataCache(sheetName: string, row: number, column: number, value: CellDataType, cellType: CellValueType) { + if (!this._sheetDataCache) { + this._sheetDataCache = {}; + } + + if (!this._sheetDataCache[sheetName]) { + this._sheetDataCache[sheetName] = new ObjectMatrix(); + } + + const sheetDataCacheActive = this._sheetDataCache[sheetName]; + sheetDataCacheActive.setValue(row, column, { + v: value, + t: cellType, + }); + } + + resetSheetDataCache() { + this._sheetDataCache = {}; + } +} diff --git a/packages/sheets-plugin-formula/src/Service/index.ts b/packages/sheets-plugin-formula/src/Service/index.ts new file mode 100644 index 0000000000..7f937b0e26 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Service/index.ts @@ -0,0 +1,7 @@ +/** + * + * en: Operate and register localData + * + * zh: 操作、注册localData + */ +export * from './FormulaCacheLoader'; diff --git a/packages/sheets-plugin-formula/src/Types/index.d.ts b/packages/sheets-plugin-formula/src/Types/index.d.ts new file mode 100644 index 0000000000..66dff79c03 --- /dev/null +++ b/packages/sheets-plugin-formula/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-formula' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-formula/src/UI/FormulaButton.tsx b/packages/sheets-plugin-formula/src/UI/FormulaButton.tsx new file mode 100644 index 0000000000..6edd5db3f1 --- /dev/null +++ b/packages/sheets-plugin-formula/src/UI/FormulaButton.tsx @@ -0,0 +1,176 @@ +import { BaseComponentSheet, BaseIconProps, BaseSelectProps, Component, FunctionComponent, ISelectButton, IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { FORMULA_CHILDREN } from '../Basic'; +import { IProps } from '../Basic/IFormula'; +import { IfGenerate } from './IfGenerate'; +import { SearchFormulaModal } from './SearchFormulaModal'; + +// Types for state +interface IState { + formula: IToolBarItemProps; + searchMoadal: boolean; + ifModal: boolean; + locale: string; + functionList: any; +} + +// const formula = { +// name: 'formula', +// type: ISlotElement.SELECT, +// label: , +// icon: , +// show: true, +// selectType: ISelectButton.DOUBLE, +// border: false, +// onClick: () => {}, +// children: FORMULA_CHILDREN, +// }; + +export class FormulaButton extends Component { + private _localeObserver: Nullable>; + + SumIcon: FunctionComponent; + + NextIcon: FunctionComponent; + + Select: FunctionComponent; + + initialize(props: IProps) { + // super(props); + const component = this.props.config.context.getPluginManager().getPluginByName('ComponentSheet')!; + const render = component.getComponentRender(); + this.SumIcon = render.renderFunction('SumIcon'); + this.NextIcon = render.renderFunction('NextIcon'); + this.Select = render.renderFunction('Select'); + const { SumIcon, NextIcon } = this; + this.state = { + functionList: null, + searchMoadal: false, + ifModal: false, + locale: '', + formula: { + locale: 'formula', + type: ISlotElement.SELECT, + label: , + icon: , + show: true, + selectType: ISelectButton.DOUBLE, + border: false, + onClick: () => {}, + children: FORMULA_CHILDREN, + }, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + + this.state.formula.children?.forEach((item) => { + if (item.locale === 'formula.if') { + item.onClick = this.modalShow.bind(this, 'ifModal'); + } else if (item.locale === 'formula.more') { + item.onClick = this.modalShow.bind(this, 'searchMoadal'); + } + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + + const currentLocale = locale.options.currentLocale; + const functionList = locale.options.locales[currentLocale].functionlist; + + this.setState((prevState: IState) => { + let item = prevState.formula; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}Label`); + item.tooltipRight = locale.get(`${item.locale}RightLabel`); + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + + return { + formula: item, + functionList, + }; + }); + } + + /** + * + * @param {string} type searchMoadal || ifModal + */ + modalShow(type: string) { + let state = { + [type]: true, + }; + this.setState((prevState) => state); + } + + modalHide(e: Event) { + const container = document.getElementsByClassName('univer-modal-wrapper')[0] || document; + const close = document.getElementsByClassName('univer-modal-close')[0] || document; + if ((this.state.searchMoadal || this.state.ifModal) && (container.contains(e.target as Element) || close.contains(e.target as Element))) { + this.setState({ + searchMoadal: false, + ifModal: false, + }); + } + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + // eslint-disable-next-line @typescript-eslint/no-shadow + const { formula } = state; + // Set Provider for entire Container + // console.log(this.state.functionList); + const { Select } = this; + return ( + <> + + + +
        +
        {locale.ifFormula.ifGenRangeTitle}
        +
        +
        + +
        +
         {locale.ifFormula.ifGenRangeTo} 
        +
        + +
        +
        +
        + +
        +
        +
        +
        {locale.ifFormula.ifGenCutWay}
        +
        + {/* +
        + +
        +
        +
        + {locale.ifFormula.ifGenCutSame} +
        +
        +
        +
        + +
        +
        + + + E6 + + +
        +
        +
        + + + +
        + + + ); + } +} + +export { IfGenerate }; diff --git a/packages/sheets-plugin-formula/src/UI/IfGenerate/index.module.less b/packages/sheets-plugin-formula/src/UI/IfGenerate/index.module.less new file mode 100644 index 0000000000..ca35e5bdb6 --- /dev/null +++ b/packages/sheets-plugin-formula/src/UI/IfGenerate/index.module.less @@ -0,0 +1,80 @@ +@import '@univer/style-universheet/assets/css/skin/default.module.less'; + +.if-generate { + > div { + margin: 10px 0; + + .label { + font-size: 14px; + font-weight: bold; + margin-bottom: 10px; + } + + .inputSection { + display: flex; + align-items: center; + margin-bottom: 10px; + + .self-input { + height: 28px; + } + } + } + + .if-generate-code { + display: block; + border: 1px solid @gray-5; + overflow: auto; + max-height: 110px; + padding: 5px; + + .input-Group { + display: flex; + margin-top: 10px; + justify-content: space-between; + + .input-Group-big { + width: 60px; + } + + .input-Group-small { + width: 30px; + } + + input { + font-size: 12px; + border: none; + background-color: @gray-2; + outline: 1px solid @gray-5; + padding: 0 5px; + } + } + } + + .input { + border: 1px solid @gray-5; + padding: 5px; + border-radius: 3px; + display: flex; + + input { + width: 100%; + border: none; + outline: none; + background-color: @gray-2; + } + } +} + +.if-generate .btnGroup { + margin: 0; + display: flex; + justify-content: space-between; + + .btn { + text-align: center; + padding: 0 5px; + border-radius: 3px; + font-size: 14px; + } +} diff --git a/packages/sheets-plugin-formula/src/UI/IfGenerate/index.ts b/packages/sheets-plugin-formula/src/UI/IfGenerate/index.ts new file mode 100644 index 0000000000..de0e8519bb --- /dev/null +++ b/packages/sheets-plugin-formula/src/UI/IfGenerate/index.ts @@ -0,0 +1 @@ +export * from './ifGenerate'; diff --git a/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/SearchFormulaModal.tsx b/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/SearchFormulaModal.tsx new file mode 100644 index 0000000000..4ce88ec4c8 --- /dev/null +++ b/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/SearchFormulaModal.tsx @@ -0,0 +1,277 @@ +import { BaseButtonProps, BaseComponentSheet, BaseModalProps, BaseSelectProps, Component, createRef, FunctionComponent } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import * as func from '../../Const/FunctionList'; // 全部公式数组 +import { IConfig } from '../../Basic/IFormula'; +import styles from './index.module.less'; + +interface IProps { + visible: boolean; + onOk: () => void; + onCancel: (e: Event) => void; + config: IConfig; +} +interface IState { + functionList: func.FunctionList[] | null; + functionType: func.FunctionType; + activeIndex: number; + inputIndex: number; + option: any; + selectType: string; + selectFun: func.FunctionList | null; + formulaMore: any; + locale: any; +} + +class SearchFormulaModal extends Component { + Button: FunctionComponent; + + Modal: FunctionComponent; + + Select: FunctionComponent; + + inputRef = createRef(); + + funRef = createRef(); + + private _localeObserver: Nullable>; + + initialize(props: IProps) { + // super(); + const component = this.props.config.context.getPluginManager().getPluginByName('ComponentSheet')!; + const render = component.getComponentRender(); + this.Button = render.renderFunction('Button'); + this.Modal = render.renderFunction('Modal'); + this.Select = render.renderFunction('Select'); + this.state = { + functionList: null, // 所有公式 + functionType: func.functionType, + activeIndex: 0, // 已选公式的下标 + inputIndex: 0, // 已选 input 的下标 + option: [], // 公式类型 + selectType: '', + selectFun: null, // 选中的公式 + formulaMore: {}, + locale: null, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + setLocale() { + const locale = this._context.getLocale(); + const functionList = locale.get('functionlist') as unknown as func.FunctionList[]; + const functionType = locale.get('functiontype') as any; + const formulaMore = locale.get('formulaMore'); + let option = []; + for (const key in functionType) { + let op = { + key, + value: functionType[key], + label: functionType[key], + }; + option.push(op); + } + + this.setState({ + option, + functionList, + formulaMore, + locale, + }); + } + + /** + * search function + */ + onInput() { + const searchTxt = this.inputRef.current.value.toUpperCase(); + let list = this._context.getLocale().get('functionlist') as unknown as func.FunctionList[]; + + // eslint-disable-next-line array-callback-return + list = list.filter((item: { n: string; a: string }) => { + if (item.n.includes(searchTxt) || item.a.includes(searchTxt)) { + return item; + } + }); + this.setState({ + functionList: list, + activeIndex: 0, + }); + } + + selectType(val: { key: number }) { + let list = this._context.getLocale().get('functionlist') as unknown as func.FunctionList[]; + // eslint-disable-next-line array-callback-return + list = list.filter((item: { t: number }) => { + if (item.t === val.key) { + return item; + } + }); + this.setState({ + functionList: list, + activeIndex: 0, + }); + } + + /** + * select function + * @param index + */ + functionClick(index: number) { + this.setState({ + activeIndex: index, + }); + } + + /** + * select function + * @param index + */ + setDescriptionDetail(index: number) { + this.setState({ + inputIndex: index, + }); + } + + /** + * 确定 + */ + okClick() { + if (this.state.selectFun) { + console.log(111); + } else { + this.setState((prevState) => { + const fun = (prevState.functionList as func.FunctionList[])[prevState.activeIndex]; + return { + selectFun: fun, + inputIndex: 0, + }; + }); + } + } + + /** + * 取消 + */ + cancelClick(e: Event) { + if (this.state.selectFun) { + this.setState({ + selectFun: null, + inputIndex: 0, + activeIndex: 0, + }); + } else { + this.props.onCancel(e); + this.setState({ + activeIndex: 0, // 已选公式的下标 + inputIndex: 0, // 已选 input 的下标 + selectFun: null, + }); + } + } + + onCancel(evt: Event) { + this.props.onCancel(evt); + this.setState({ + activeIndex: 0, // 已选公式的下标 + inputIndex: 0, // 已选 input 的下标 + selectFun: null, + }); + } + + handleClick(...args: any[]) { + // console.dir(args); + } + + render() { + const { Modal, Select, Button } = this; + const { functionList, activeIndex, inputIndex, option, selectFun, formulaMore } = this.state; + let funParams; + let funName; + if (selectFun) { + funName = selectFun.a as string; + funParams = selectFun.p as func.P[]; + } + return ( + + {selectFun ? null : ( +
        +
        +
        {formulaMore.findFunctionTitle}
        +
        + +
        +
        +
        +
        {formulaMore.selectCategory}
        +
        + {/* */} + +
        +
        +
        +
        {formulaMore.selectFunctionTitle}
        +
          + {(functionList as func.FunctionList[]).map((item, index) => ( +
        • +
          {item.n}
          +
          {item.d}
          +
        • + ))} +
        +
        +
        + )} + {selectFun ? ( +
        +
        + {(funParams as func.P[]).map((item: { name: string }, index: number) => ( +
        + {item.name}: + + ={'{}'} +
        + ))} +
        +
        +
        {funName}
        +
        +
        {(funParams as func.P[])[inputIndex].name}
        +
        {(funParams as func.P[])[inputIndex].detail}
        +
        +
        +
        + {formulaMore.calculationResult}={} +
        +
        + ) : null} +
        + + +
        +
        + ); + } +} + +export { SearchFormulaModal }; diff --git a/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/index.module.less b/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/index.module.less new file mode 100644 index 0000000000..0ec95bdb6d --- /dev/null +++ b/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/index.module.less @@ -0,0 +1,113 @@ +@import '@univer/style-universheet/assets/css/skin/base.module.less'; +.function-modal { + font-size: 14px; + + > div { + margin-bottom: 10px; + } + + .function-search { + input { + width: 100%; + height: 24px; + line-height: 24px; + border: 1px solid #d4d4d4; + padding: 0 10px; + box-sizing: border-box; + font-size: 12px; + } + } + + .function-select { + display: flex; + align-items: center; + + .function-label { + margin-bottom: 0; + } + .function-selector { + border: 1px solid @border-color-base; + border-radius: @border-radius-base; + .select-button { + margin: 0; + } + } + } + + .function-list { + .function-lists { + width: 100%; + height: 170px; + border: 1px solid #d4d4d4; + overflow-y: auto; + .function-lists-item { + padding: 5px; + border-bottom: 1px solid #d4d4d4; + .function-lists-item-name { + } + .function-lists-item-detail { + } + + &.active { + background-color: #0188fb; + color: #fff; + } + &:hover { + background-color: #5391ff; + color: #fff; + } + } + } + } + + .function-label { + margin-right: 10px; + margin-bottom: 10px; + } +} + +.function-param-modal { + .function-param-list { + border: 1px solid; + padding: 0.5 em; + + > div { + display: flex; + padding: 5px 0; + > span { + flex: 1; + &:first-child { + text-align: right; + } + } + input { + flex: 1; + } + } + } + + .function-param-desc { + margin: 1em 0; + + .function-param-desc-detail { + } + .function-param-desc-details { + padding: 10px; + height: 100px; + overflow: auto; + margin-top: 1em; + box-sizing: border-box; + font-size: 12px; + } + } + .function-param-result { + } +} + +.function-btn { + .btn { + padding: 5px; + line-height: 1; + font-size: 14px; + } +} diff --git a/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/index.ts b/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/index.ts new file mode 100644 index 0000000000..8327037e0f --- /dev/null +++ b/packages/sheets-plugin-formula/src/UI/SearchFormulaModal/index.ts @@ -0,0 +1 @@ +export * from './SearchFormulaModal'; diff --git a/packages/sheets-plugin-formula/src/View/Event.ts b/packages/sheets-plugin-formula/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-formula/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-formula/src/View/ObserverList.ts b/packages/sheets-plugin-formula/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-formula/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-formula/src/View/Render.ts b/packages/sheets-plugin-formula/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-formula/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-formula/src/View/index.ts b/packages/sheets-plugin-formula/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-formula/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-formula/src/index.ts b/packages/sheets-plugin-formula/src/index.ts new file mode 100644 index 0000000000..8d2a22050d --- /dev/null +++ b/packages/sheets-plugin-formula/src/index.ts @@ -0,0 +1 @@ +export * from './FormulaPlugin'; diff --git a/packages/sheets-plugin-formula/src/main.tsx b/packages/sheets-plugin-formula/src/main.tsx new file mode 100644 index 0000000000..75a7ae1bc7 --- /dev/null +++ b/packages/sheets-plugin-formula/src/main.tsx @@ -0,0 +1,65 @@ +import { UniverSheet } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { FormulaPlugin } from './FormulaPlugin'; + +const defaultWorkbookData = { + locale: 'zh', + creator: '', + name: '', + skin: 'default', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +univerSheetUp.installPlugin(new FormulaPlugin()); + +// const univerSheetDown = UniverSheet.newInstance({workBookData:defaultWorkbookDataDown}); + +// univerSheetDown.installPlugin(new StyleUniverSheet(uiDefaultConfigDown)); + +// univerSheetDown.installPlugin(new FilterPlugin()); +// univerSheetDown.installPlugin(new Formula()); diff --git a/packages/sheets-plugin-formula/src/preact.d.ts b/packages/sheets-plugin-formula/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-formula/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-formula/tsconfig.json b/packages/sheets-plugin-formula/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-formula/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-formula/tsconfig.ref.json b/packages/sheets-plugin-formula/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-formula/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-formula/tsconfig.test.json b/packages/sheets-plugin-formula/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-formula/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-formula/vite.config.ts b/packages/sheets-plugin-formula/vite.config.ts new file mode 100644 index 0000000000..3e9f3ae01f --- /dev/null +++ b/packages/sheets-plugin-formula/vite.config.ts @@ -0,0 +1,44 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import legacy from '@vitejs/plugin-legacy'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + // lib: { + // entry: resolve('src/index.ts'), + // name: 'BaseStyleUniverSheet', + // formats: ['es', 'umd'], + // fileName: 'index', + // }, + // outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + legacy({ + targets: ['ie >= 11'], + additionalLegacyPolyfills: ['regenerator-runtime/runtime'], + }), + ], +}); diff --git a/packages/sheets-plugin-frozen/README-zh.md b/packages/sheets-plugin-frozen/README-zh.md new file mode 100644 index 0000000000..d5f9c8b4c1 --- /dev/null +++ b/packages/sheets-plugin-frozen/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet Frozen 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-frozen +``` + +### 使用 + +```js +impport {Frozen} from '@univer/sheets-plugin-frozen' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Frozen()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-frozen/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-frozen dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-frozen/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-frozen build +``` diff --git a/packages/sheets-plugin-frozen/README.md b/packages/sheets-plugin-frozen/README.md new file mode 100644 index 0000000000..6aaf63d7de --- /dev/null +++ b/packages/sheets-plugin-frozen/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin Frozen + +### Installation + +```bash +npm i @univer/sheets-plugin-frozen +``` + +### Usage + +```js +impport {Frozen} from '@univer/sheets-plugin-frozen' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Frozen()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-frozen/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-frozen dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-frozen/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-frozen build +``` diff --git a/packages/sheets-plugin-frozen/index.html b/packages/sheets-plugin-frozen/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-frozen/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-frozen/jest.config.js b/packages/sheets-plugin-frozen/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-frozen/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-frozen/package.json b/packages/sheets-plugin-frozen/package.json new file mode 100644 index 0000000000..32d140eb30 --- /dev/null +++ b/packages/sheets-plugin-frozen/package.json @@ -0,0 +1,56 @@ +{ + "name": "@univer/sheets-plugin-frozen", + "version": "0.0.1", + "description": "UniverSheet normal plugin-frozen", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-frozen/src/Action/index.ts b/packages/sheets-plugin-frozen/src/Action/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-frozen/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..686db2564a --- /dev/null +++ b/packages/sheets-plugin-frozen/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const FROZEN_PLUGIN_NAME = 'frozen'; diff --git a/packages/sheets-plugin-frozen/src/Controller/index.ts b/packages/sheets-plugin-frozen/src/Controller/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/Domain/Frozen.ts b/packages/sheets-plugin-frozen/src/Domain/Frozen.ts new file mode 100644 index 0000000000..7434ec66d3 --- /dev/null +++ b/packages/sheets-plugin-frozen/src/Domain/Frozen.ts @@ -0,0 +1,7 @@ +export class Frozen { + // constructor() {} + + setFrozenColumns(columns: number) {} + + setFrozenRows(rows: number) {} +} diff --git a/packages/sheets-plugin-frozen/src/Domain/index.ts b/packages/sheets-plugin-frozen/src/Domain/index.ts new file mode 100644 index 0000000000..f5a21fe649 --- /dev/null +++ b/packages/sheets-plugin-frozen/src/Domain/index.ts @@ -0,0 +1 @@ +export * from './Frozen'; diff --git a/packages/sheets-plugin-frozen/src/Extension/Conflict/index.ts b/packages/sheets-plugin-frozen/src/Extension/Conflict/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/Extension/ProxyData/index.ts b/packages/sheets-plugin-frozen/src/Extension/ProxyData/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/FrozenPlugin.tsx b/packages/sheets-plugin-frozen/src/FrozenPlugin.tsx new file mode 100644 index 0000000000..398c990763 --- /dev/null +++ b/packages/sheets-plugin-frozen/src/FrozenPlugin.tsx @@ -0,0 +1,56 @@ +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { Context, IOCContainer, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { FROZEN_PLUGIN_NAME } from './Const/PLUGIN_NAME'; +import { IConfig, IPluginConfig } from './IData'; +import { en, zh } from './Model/Locale'; +import { FrozenButton } from './View/UI/FrozenButton'; + +export class FrozenPlugin extends Plugin { + spreadsheetPlugin: any; + + constructor(config?: IPluginConfig) { + super(FROZEN_PLUGIN_NAME); + } + + static create(config?: IPluginConfig) { + return new FrozenPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = {}; + + const item: IToolBarItemProps = { + locale: FROZEN_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + // get spreadsheet plugin + this.spreadsheetPlugin = context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + + // extend comment + this.spreadsheetPlugin?.addButton(item); + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} +} diff --git a/packages/sheets-plugin-frozen/src/IData.ts b/packages/sheets-plugin-frozen/src/IData.ts new file mode 100644 index 0000000000..e692cb14b0 --- /dev/null +++ b/packages/sheets-plugin-frozen/src/IData.ts @@ -0,0 +1,2 @@ +export type IConfig = {}; +export type IPluginConfig = {}; diff --git a/packages/sheets-plugin-frozen/src/Model/Const/index.ts b/packages/sheets-plugin-frozen/src/Model/Const/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/Model/Enum/index.ts b/packages/sheets-plugin-frozen/src/Model/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/Model/Locale/en.ts b/packages/sheets-plugin-frozen/src/Model/Locale/en.ts new file mode 100644 index 0000000000..c1d4a16375 --- /dev/null +++ b/packages/sheets-plugin-frozen/src/Model/Locale/en.ts @@ -0,0 +1,9 @@ +export default { + frozenLabel: 'Frozen', + frozenRightLabel: 'more Frozen', + frozen: { + one: 'one', + two: 'two', + three: 'three', + }, +}; diff --git a/packages/sheets-plugin-frozen/src/Model/Locale/index.ts b/packages/sheets-plugin-frozen/src/Model/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-frozen/src/Model/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-frozen/src/Model/Locale/zh.ts b/packages/sheets-plugin-frozen/src/Model/Locale/zh.ts new file mode 100644 index 0000000000..e864c45e1d --- /dev/null +++ b/packages/sheets-plugin-frozen/src/Model/Locale/zh.ts @@ -0,0 +1,15 @@ +export default { + frozen: { + frozenLabel: '冻结首行', + frozenRightLabel: '更多选项', + + default: '冻结首行', + frozenRow: '冻结首行', + frozenColumn: '冻结首列', + frozenRC: '冻结行列', + frozenRowRange: '冻结行到选区', + frozenColumnRange: '冻结列到选区', + frozenRCRange: '冻结行列到选区', + frozenCancel: '取消冻结', + }, +}; diff --git a/packages/sheets-plugin-frozen/src/Model/Shared/index.ts b/packages/sheets-plugin-frozen/src/Model/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/Service/index.ts b/packages/sheets-plugin-frozen/src/Service/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/Types/index.d.ts b/packages/sheets-plugin-frozen/src/Types/index.d.ts new file mode 100644 index 0000000000..b7137531b2 --- /dev/null +++ b/packages/sheets-plugin-frozen/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-frozen' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-frozen/src/View/Render/index.ts b/packages/sheets-plugin-frozen/src/View/Render/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-frozen/src/View/UI/FrozenButton.tsx b/packages/sheets-plugin-frozen/src/View/UI/FrozenButton.tsx new file mode 100644 index 0000000000..fc7cadcc1d --- /dev/null +++ b/packages/sheets-plugin-frozen/src/View/UI/FrozenButton.tsx @@ -0,0 +1,139 @@ +import { BaseComponentRender, BaseComponentSheet, BaseSelectProps, Component, ISelectButton, IToolBarItemProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { IConfig } from '../../IData'; + +interface IProps { + config: IConfig; +} + +// Types for state +interface IState { + frozen: IToolBarItemProps; +} + +export class FrozenButton extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize(props: IProps) { + // super(props); + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + const NextIcon = this.Render.renderFunction('NextIcon'); + const FreezeIcon = this.Render.renderFunction('FreezeIcon'); + + this.state = { + frozen: { + locale: 'frozen', + type: 'select', + label: , + icon: , + show: true, + border: false, + selectType: ISelectButton.DOUBLE, + needChange: false, + children: [ + { + locale: 'frozen.default', + }, + { + locale: 'frozen.frozenRow', + }, + { + locale: 'frozen.frozenColumn', + }, + { + locale: 'frozen.frozenRC', + border: true, + }, + { + locale: 'frozen.frozenRowRange', + }, + { + locale: 'frozen.frozenColumnRange', + }, + { + locale: 'frozen.frozenRCRange', + }, + { + locale: 'frozen.frozenCancel', + border: true, + }, + ], + }, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.frozen; + // set current Locale string for tooltip + item.tooltip = locale.get(`frozen.${item.locale}Label`); + item.tooltipRight = locale.get(`frozen.${item.locale}RightLabel`); + + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + + return { + frozen: item, + }; + }); + } + + /** + * Render the component's HTML + * + * @returns + */ + render(props: IProps, state: IState) { + const Select = this.Render.renderFunction('Select'); + const { frozen } = state; + // Set Provider for entire Container + return ( + + 移动图片并调整单元格大小 + +
        + + 移动图片并不调整单元格大小 +
        +
        + + 不移动图片并调整单元格大小 +
        + + + + + {/* 固定 */} +
        +
        +
        + + 固定位置 +
        +
        +
        + + {/* 边框 */} +
        +
        +
        +
        +
        +
        + + {/* 样式 */} +
        +
        +
        边框
        +
        +
        + 宽度: + +
        +
        + 半径: + +
        +
        + 样式: + +
        +
        + 颜色: + {}} + onColor={(color: string) => {}} + /> +
        +
        +
        +
        + +
        + ); + } + + closed() { + const plugin: SpreadsheetPlugin = this.getPluginByName(PLUGIN_NAMES.SPREADSHEET)!; + plugin.showSiderByName(OVER_GRID_IMAGE_PLUGIN_NAME, false); + } + + componentWillMount() { + console.log('componentWillMount'); + } + + componentWillUnmount() { + console.log('componentWillUnmount'); + } +} diff --git a/packages/sheets-plugin-image/src/UI/ImageUploadButton/ImageUploadButtonUI.module.less b/packages/sheets-plugin-image/src/UI/ImageUploadButton/ImageUploadButtonUI.module.less new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-image/src/UI/ImageUploadButton/ImageUploadButtonUI.tsx b/packages/sheets-plugin-image/src/UI/ImageUploadButton/ImageUploadButtonUI.tsx new file mode 100644 index 0000000000..384026c888 --- /dev/null +++ b/packages/sheets-plugin-image/src/UI/ImageUploadButton/ImageUploadButtonUI.tsx @@ -0,0 +1,29 @@ +import { Component, IToolBarItemProps, VNode } from '@univer/base-component'; +import { Context } from '@univer/core'; +import { FileSelected } from '../../Library/FileSelected'; + +export interface ImageUploadButtonUIProps { + chooseCallback: (url: string, file: File) => void; +} + +export class ImageUploadButtonUI extends Component { + initialize(props: ImageUploadButtonUIProps) { + this.state = {}; + } + + render(): VNode { + const SingleButton = this.getComponentRender().renderFunction('SingleButton'); + const OrderIcon = this.getComponentRender().renderFunction('OrderIcon'); + const Props: IToolBarItemProps = { + locale: 'ImageUploadButtonUI', + type: 'single', + label: , + show: true, + onClick: async (item: IToolBarItemProps, context: Context) => { + const file = await FileSelected.chooseImage(); + this.props.chooseCallback(URL.createObjectURL(file), file); + }, + }; + return ; + } +} diff --git a/packages/sheets-plugin-image/src/View/OverImageRender.ts b/packages/sheets-plugin-image/src/View/OverImageRender.ts new file mode 100644 index 0000000000..0ac1f00cd1 --- /dev/null +++ b/packages/sheets-plugin-image/src/View/OverImageRender.ts @@ -0,0 +1,48 @@ +import { RenderEngine, EVENT_TYPE, Scene } from '@univer/base-render'; +import { PLUGIN_NAMES } from '@univer/core'; +import { OverGridImagePlugin, OverGridImageProperty } from '../OverGridImagePlugin'; +import { OverImageShape } from './OverImageShape'; + +export class OverImageRender { + static LAYER_Z_INDEX = 1000; + + private _plugin: OverGridImagePlugin; + + private _mainScene: Scene; + + private _activeShape: OverImageShape; + + constructor(plugin: OverGridImagePlugin) { + const engine = plugin.getPluginByName(PLUGIN_NAMES.BASE_RENDER)?.getEngine()!; + this._plugin = plugin; + this._mainScene = engine.getScenes('mainScene')!; + plugin.getObserver('onAddImage')!.add((eventData: OverGridImageProperty) => { + eventData.autoWidth = true; + eventData.autoHeight = true; + this.addOverImage(eventData); + }); + } + + addOverImage(property: OverGridImageProperty): void { + const shape = new OverImageShape(property); + shape.on(EVENT_TYPE.PointerDown, () => { + this._activeShape = shape; + this._plugin.showOverImagePanel(); + this._plugin.getObserver('onActiveImage')!.notifyObservers(shape.getProperty()); + }); + this._mainScene.addObject(shape, OverImageRender.LAYER_Z_INDEX); + } + + removeOverImage(id: string): void { + const layer = this._mainScene.getLayer(OverImageRender.LAYER_Z_INDEX); + if (!layer) { + return; + } + for (let i = 0; i < layer.objects.length; i++) { + const shape = layer.objects[i] as OverImageShape; + if (shape.getProperty().id === id) { + layer.objects.splice(i, 1); + } + } + } +} diff --git a/packages/sheets-plugin-image/src/View/OverImageShape.ts b/packages/sheets-plugin-image/src/View/OverImageShape.ts new file mode 100644 index 0000000000..cda86d3f1d --- /dev/null +++ b/packages/sheets-plugin-image/src/View/OverImageShape.ts @@ -0,0 +1,98 @@ +import { Picture } from '@univer/base-render'; +import { OverGridImageBorderType, OverGridImageProperty } from '../OverGridImagePlugin'; + +export class OverImageShape extends Picture { + static drawRoundRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, tl: number, tr: number, bl: number, br: number) { + ctx.beginPath(); + ctx.moveTo(x + tl, y); + ctx.lineTo(x + width - tr, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + tr); + ctx.lineTo(x + width, y + height - br); + ctx.quadraticCurveTo(x + width, y + height, x + width - br, y + height); + ctx.lineTo(x + bl, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - bl); + ctx.lineTo(x, y + tl); + ctx.quadraticCurveTo(x, y, x + tl, y); + ctx.closePath(); + } + + static drawDashedBorder(ctx: CanvasRenderingContext2D, shape: OverImageShape) { + const { radius = 60, borderWidth = 10, borderColor = 'red' } = shape.getProperty(); + const x = -borderWidth / 2; + const y = -borderWidth / 2; + const w = shape.width + borderWidth; + const h = shape.height + borderWidth; + OverImageShape.drawRoundRect(ctx, x, y, w, h, radius, radius, radius, radius); + ctx.setLineDash([10, 10, 10]); + ctx.lineWidth = borderWidth; + ctx.lineCap = 'round'; + ctx.strokeStyle = borderColor; + ctx.stroke(); + } + + static drawSolidBorder(ctx: CanvasRenderingContext2D, shape: OverImageShape) { + const { radius = 60, borderWidth = 10, borderColor = 'red' } = shape.getProperty(); + const x = -borderWidth / 2; + const y = -borderWidth / 2; + const w = shape.width + borderWidth; + const h = shape.height + borderWidth; + OverImageShape.drawRoundRect(ctx, x, y, w, h, radius, radius, radius, radius); + ctx.lineWidth = borderWidth; + ctx.strokeStyle = borderColor; + ctx.stroke(); + } + + static drawDoubleBorder(ctx: CanvasRenderingContext2D, shape: OverImageShape) { + const { radius = 60, borderWidth = 10, borderColor = 'red' } = shape.getProperty(); + const x1 = -borderWidth / 2; + const y1 = -borderWidth / 2; + const w1 = shape.width + borderWidth; + const h1 = shape.height + borderWidth; + OverImageShape.drawRoundRect(ctx, x1, y1, w1, h1, radius, radius, radius, radius); + ctx.lineWidth = borderWidth; + ctx.strokeStyle = borderColor; + ctx.stroke(); + const x2 = x1 - 5; + const y2 = y1 - 5; + const w2 = w1 + 5; + const h2 = h1 + 5; + OverImageShape.drawRoundRect(ctx, x2, y2, w2, h2, radius, radius, radius, radius); + ctx.lineWidth = borderWidth; + ctx.strokeStyle = borderColor; + ctx.stroke(); + } + + static drawWith(ctx: CanvasRenderingContext2D, shape: OverImageShape): void { + const { borderType } = shape.getProperty(); + switch (borderType) { + case OverGridImageBorderType.SOLID: { + OverImageShape.drawSolidBorder(ctx, shape); + break; + } + case OverGridImageBorderType.DASHED: { + OverImageShape.drawDashedBorder(ctx, shape); + break; + } + case OverGridImageBorderType.DOUBLE: { + OverImageShape.drawDoubleBorder(ctx, shape); + break; + } + } + } + + protected _property: OverGridImageProperty; + + protected _draw(ctx: CanvasRenderingContext2D) { + OverImageShape.drawWith(ctx, this); + super._draw(ctx); + } + + constructor(property: OverGridImageProperty) { + super(property); + this._property = property; + } + + getProperty(): OverGridImageProperty { + return this._property; + } +} diff --git a/packages/sheets-plugin-image/src/index.d.ts b/packages/sheets-plugin-image/src/index.d.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-image/src/index.ts b/packages/sheets-plugin-image/src/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-image/src/main.tsx b/packages/sheets-plugin-image/src/main.tsx new file mode 100644 index 0000000000..1836d1bc26 --- /dev/null +++ b/packages/sheets-plugin-image/src/main.tsx @@ -0,0 +1,19 @@ +import { defaultWorkbookData, UniverSheet } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { UniverComponentSheet } from '@univer/style-universheet'; +import { RenderEngine } from '@univer/base-render'; +import { OverGridImagePlugin } from './OverGridImagePlugin'; + +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const univerSheetInstance = UniverSheet.newInstance(defaultWorkbookData); +univerSheetInstance.installPlugin(new UniverComponentSheet()); +univerSheetInstance.installPlugin(new RenderEngine()); +univerSheetInstance.installPlugin(new SpreadsheetPlugin(uiDefaultConfigUp)); +univerSheetInstance.installPlugin( + new OverGridImagePlugin({ + value: [], + }) +); diff --git a/packages/sheets-plugin-image/src/preact.d.ts b/packages/sheets-plugin-image/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-image/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-image/src/vite-env.d.ts b/packages/sheets-plugin-image/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages/sheets-plugin-image/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/sheets-plugin-image/tsconfig.json b/packages/sheets-plugin-image/tsconfig.json new file mode 100644 index 0000000000..975f6f388a --- /dev/null +++ b/packages/sheets-plugin-image/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-image/tsconfig.ref.json b/packages/sheets-plugin-image/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-image/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-image/tsconfig.test.json b/packages/sheets-plugin-image/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-image/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-image/vite.config.ts b/packages/sheets-plugin-image/vite.config.ts new file mode 100644 index 0000000000..530cae8ec2 --- /dev/null +++ b/packages/sheets-plugin-image/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import { name, version } from './package.json'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginImage', + formats: ['es', 'umd'], + fileName: 'univer-sheets-plugin-image', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', + generateScopedName: 'univer-[local]', + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-link/README-zh.md b/packages/sheets-plugin-link/README-zh.md new file mode 100644 index 0000000000..4e247e948c --- /dev/null +++ b/packages/sheets-plugin-link/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet InsertLink 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-insert-link +``` + +### 使用 + +```js +impport {InsertLink} from '@univer/sheets-plugin-insert-link' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new InsertLink()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/plugin-insert-link/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-insert-link dev +``` + +### 打包 + +``` +# 当前目录 ./packages/plugin-insert-link/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-insert-link build +``` diff --git a/packages/sheets-plugin-link/README.md b/packages/sheets-plugin-link/README.md new file mode 100644 index 0000000000..37daabd4c4 --- /dev/null +++ b/packages/sheets-plugin-link/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin InsertLink + +### Installation + +```bash +npm i @univer/sheets-plugin-insert-link +``` + +### Usage + +```js +impport {InsertLink} from '@univer/sheets-plugin-insert-link' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new InsertLink()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/plugin-insert-link/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-insert-link dev +``` + +### Package + +``` +# Current directory ./packages/plugin-insert-link/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-insert-link build +``` diff --git a/packages/sheets-plugin-link/index.html b/packages/sheets-plugin-link/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-link/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-link/jest.config.js b/packages/sheets-plugin-link/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-link/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-link/package.json b/packages/sheets-plugin-link/package.json new file mode 100644 index 0000000000..23cf85fc38 --- /dev/null +++ b/packages/sheets-plugin-link/package.json @@ -0,0 +1,57 @@ +{ + "name": "@univer/sheets-plugin-link", + "version": "0.0.1", + "description": "UniverSheet normal plugin insertLink", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-link/src/Action/ActionLink.ts b/packages/sheets-plugin-link/src/Action/ActionLink.ts new file mode 100644 index 0000000000..b165f0d99c --- /dev/null +++ b/packages/sheets-plugin-link/src/Action/ActionLink.ts @@ -0,0 +1 @@ +export class ActionInsertLink {} diff --git a/packages/sheets-plugin-link/src/Action/IAction.ts b/packages/sheets-plugin-link/src/Action/IAction.ts new file mode 100644 index 0000000000..9b031a3c3c --- /dev/null +++ b/packages/sheets-plugin-link/src/Action/IAction.ts @@ -0,0 +1 @@ +export class IAction {} diff --git a/packages/sheets-plugin-link/src/Action/IActionSet.ts b/packages/sheets-plugin-link/src/Action/IActionSet.ts new file mode 100644 index 0000000000..f8e2a8f7e6 --- /dev/null +++ b/packages/sheets-plugin-link/src/Action/IActionSet.ts @@ -0,0 +1 @@ +export class IActionSet {} diff --git a/packages/sheets-plugin-link/src/Action/index.ts b/packages/sheets-plugin-link/src/Action/index.ts new file mode 100644 index 0000000000..6aacb88e9d --- /dev/null +++ b/packages/sheets-plugin-link/src/Action/index.ts @@ -0,0 +1,8 @@ +/** + * en: Modify localData, conflict handling, generate data transfer protocol, submit for background execution + * + * zh: 修改localData、冲突处理、生成数据传输协议、提交后台执行 + */ +export * from './ActionLink'; +export * from './IAction'; +export * from './IActionSet'; diff --git a/packages/sheets-plugin-link/src/Base/Cache.ts b/packages/sheets-plugin-link/src/Base/Cache.ts new file mode 100644 index 0000000000..002f98640e --- /dev/null +++ b/packages/sheets-plugin-link/src/Base/Cache.ts @@ -0,0 +1 @@ +export class Cache {} diff --git a/packages/sheets-plugin-link/src/Base/State.ts b/packages/sheets-plugin-link/src/Base/State.ts new file mode 100644 index 0000000000..f277253fef --- /dev/null +++ b/packages/sheets-plugin-link/src/Base/State.ts @@ -0,0 +1 @@ +export class State {} diff --git a/packages/sheets-plugin-link/src/Base/index.ts b/packages/sheets-plugin-link/src/Base/index.ts new file mode 100644 index 0000000000..6c6e3b5d96 --- /dev/null +++ b/packages/sheets-plugin-link/src/Base/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './State'; diff --git a/packages/sheets-plugin-link/src/Conflict/ConflictLink.ts b/packages/sheets-plugin-link/src/Conflict/ConflictLink.ts new file mode 100644 index 0000000000..281baca7f3 --- /dev/null +++ b/packages/sheets-plugin-link/src/Conflict/ConflictLink.ts @@ -0,0 +1 @@ +export class ConflictInsertLink {} diff --git a/packages/sheets-plugin-link/src/Conflict/IConflict.ts b/packages/sheets-plugin-link/src/Conflict/IConflict.ts new file mode 100644 index 0000000000..02b0979966 --- /dev/null +++ b/packages/sheets-plugin-link/src/Conflict/IConflict.ts @@ -0,0 +1 @@ +export class IConflict {} diff --git a/packages/sheets-plugin-link/src/Conflict/index.ts b/packages/sheets-plugin-link/src/Conflict/index.ts new file mode 100644 index 0000000000..dd9dc24972 --- /dev/null +++ b/packages/sheets-plugin-link/src/Conflict/index.ts @@ -0,0 +1,7 @@ +/** + * en: Handling action conflicts + * + * zh: 处理action冲突 + */ +export * from './IConflict'; +export * from './ConflictLink'; diff --git a/packages/sheets-plugin-link/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-link/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..19df146bad --- /dev/null +++ b/packages/sheets-plugin-link/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const LINK_PLUGIN_NAME = 'link'; diff --git a/packages/sheets-plugin-link/src/Const/index.ts b/packages/sheets-plugin-link/src/Const/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-link/src/Domain/Link.ts b/packages/sheets-plugin-link/src/Domain/Link.ts new file mode 100644 index 0000000000..ac337e2abe --- /dev/null +++ b/packages/sheets-plugin-link/src/Domain/Link.ts @@ -0,0 +1 @@ +export class Link {} diff --git a/packages/sheets-plugin-link/src/Domain/index.ts b/packages/sheets-plugin-link/src/Domain/index.ts new file mode 100644 index 0000000000..3db78f51f0 --- /dev/null +++ b/packages/sheets-plugin-link/src/Domain/index.ts @@ -0,0 +1 @@ +export * from './Link'; diff --git a/packages/sheets-plugin-link/src/Enum/index.ts b/packages/sheets-plugin-link/src/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-link/src/IData/ILink.ts b/packages/sheets-plugin-link/src/IData/ILink.ts new file mode 100644 index 0000000000..79c97b9929 --- /dev/null +++ b/packages/sheets-plugin-link/src/IData/ILink.ts @@ -0,0 +1,8 @@ +import { Context } from '@univer/core'; + +export type IConfig = { + context: Context; +}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-link/src/IData/index.ts b/packages/sheets-plugin-link/src/IData/index.ts new file mode 100644 index 0000000000..ef6463f8a8 --- /dev/null +++ b/packages/sheets-plugin-link/src/IData/index.ts @@ -0,0 +1 @@ +export * from './ILink'; diff --git a/packages/sheets-plugin-link/src/LinkPlugin.tsx b/packages/sheets-plugin-link/src/LinkPlugin.tsx new file mode 100644 index 0000000000..e68be9aa98 --- /dev/null +++ b/packages/sheets-plugin-link/src/LinkPlugin.tsx @@ -0,0 +1,53 @@ +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { Context, IOCContainer, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { en, zh } from './Locale'; +import { LinkButton } from './UI/LinkButton'; + +import { IConfig } from './IData/ILink'; +import { LINK_PLUGIN_NAME } from './Const/PLUGIN_NAME'; + +type IPluginConfig = {}; + +export class LinkPlugin extends Plugin { + constructor(config?: IPluginConfig) { + super(LINK_PLUGIN_NAME); + } + + static create(config?: IPluginConfig) { + return new LinkPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = { context }; + + const item: IToolBarItemProps = { + locale: LINK_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.addButton(item); + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} +} diff --git a/packages/sheets-plugin-link/src/Locale/en.ts b/packages/sheets-plugin-link/src/Locale/en.ts new file mode 100644 index 0000000000..84e1c3b51a --- /dev/null +++ b/packages/sheets-plugin-link/src/Locale/en.ts @@ -0,0 +1,18 @@ +export default { + insertLink: { + insertLinkLabel: 'InsertLink', + linkText: 'Text', + linkType: 'Link type', + external: 'External link', + internal: 'Internal link', + linkAddress: 'Link address', + linkSheet: 'Worksheet', + linkCell: 'Cell reference', + linkTooltip: 'Tooltip', + placeholder1: 'Please enter the web link address', + placeholder2: 'Please enter the cell to be quoted, example A1', + placeholder3: 'Please enter the prompt content', + tooltipInfo1: 'Please enter a valid link', + tooltipInfo2: 'Please enter the correct cell reference', + }, +}; diff --git a/packages/sheets-plugin-link/src/Locale/index.ts b/packages/sheets-plugin-link/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-link/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-link/src/Locale/zh.ts b/packages/sheets-plugin-link/src/Locale/zh.ts new file mode 100644 index 0000000000..0dd4c3fd57 --- /dev/null +++ b/packages/sheets-plugin-link/src/Locale/zh.ts @@ -0,0 +1,18 @@ +export default { + insertLink: { + insertLinkLabel: '插入链接', + linkText: '文本', + linkType: '链接类型', + external: '外部链接', + internal: '内部链接', + linkAddress: '链接地址', + linkSheet: '工作表', + linkCell: '单元格引用', + linkTooltip: '提示', + placeholder1: '请输入网页链接地址', + placeholder2: '请输入要引用的单元格,例A1', + placeholder3: '请输入提示内容', + tooltipInfo1: '请输入有效的链接', + tooltipInfo2: '请输入正确的单元格引用', + }, +}; diff --git a/packages/sheets-plugin-link/src/ProxyData/IProxyData.ts b/packages/sheets-plugin-link/src/ProxyData/IProxyData.ts new file mode 100644 index 0000000000..0bd8a3cf07 --- /dev/null +++ b/packages/sheets-plugin-link/src/ProxyData/IProxyData.ts @@ -0,0 +1 @@ +export class IProxyData {} diff --git a/packages/sheets-plugin-link/src/ProxyData/IProxyDataSet.ts b/packages/sheets-plugin-link/src/ProxyData/IProxyDataSet.ts new file mode 100644 index 0000000000..9bf75cd768 --- /dev/null +++ b/packages/sheets-plugin-link/src/ProxyData/IProxyDataSet.ts @@ -0,0 +1 @@ +export class IProxyDataSet {} diff --git a/packages/sheets-plugin-link/src/ProxyData/ProxyDataLink.ts b/packages/sheets-plugin-link/src/ProxyData/ProxyDataLink.ts new file mode 100644 index 0000000000..ddf6e06c83 --- /dev/null +++ b/packages/sheets-plugin-link/src/ProxyData/ProxyDataLink.ts @@ -0,0 +1 @@ +export class IProxyDataLink {} diff --git a/packages/sheets-plugin-link/src/ProxyData/index.ts b/packages/sheets-plugin-link/src/ProxyData/index.ts new file mode 100644 index 0000000000..3f863433d7 --- /dev/null +++ b/packages/sheets-plugin-link/src/ProxyData/index.ts @@ -0,0 +1,8 @@ +/** + * en: Transfer to background data protocol + * + * zh: 传输到后台数据协议 + */ +export * from './IProxyData'; +export * from './IProxyDataSet'; +export * from './ProxyDataLink'; diff --git a/packages/sheets-plugin-link/src/Service/LinkService.ts b/packages/sheets-plugin-link/src/Service/LinkService.ts new file mode 100644 index 0000000000..e17bd6012a --- /dev/null +++ b/packages/sheets-plugin-link/src/Service/LinkService.ts @@ -0,0 +1 @@ +export class LinkService {} diff --git a/packages/sheets-plugin-link/src/Service/index.ts b/packages/sheets-plugin-link/src/Service/index.ts new file mode 100644 index 0000000000..9b718b6864 --- /dev/null +++ b/packages/sheets-plugin-link/src/Service/index.ts @@ -0,0 +1,7 @@ +/** + * + * en: Operate and register localData + * + * zh: 操作、注册localData + */ +export * from './LinkService'; diff --git a/packages/sheets-plugin-link/src/Shared/index.ts b/packages/sheets-plugin-link/src/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-link/src/Types/index.d.ts b/packages/sheets-plugin-link/src/Types/index.d.ts new file mode 100644 index 0000000000..e5511b2d41 --- /dev/null +++ b/packages/sheets-plugin-link/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-insert-link' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-link/src/UI/LinkButton.tsx b/packages/sheets-plugin-link/src/UI/LinkButton.tsx new file mode 100644 index 0000000000..fa77196153 --- /dev/null +++ b/packages/sheets-plugin-link/src/UI/LinkButton.tsx @@ -0,0 +1,135 @@ +import { BaseComponentRender, BaseComponentSheet, Component, IToolBarItemProps, ModalProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { Icon } from '@univer/style-universheet'; +import { IProps } from '../IData/ILink'; +import styles from './index.module.less'; +import { LinkContent } from './LinkContent'; + +// Types for state +interface IState { + insertLink: IToolBarItemProps; + modalData: ModalProps[]; +} + +const insertLink: IToolBarItemProps = { + locale: 'toolbar.insertLink', + type: 'single', + label: , + show: true, +}; + +export class LinkButton extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize(props: IProps) { + // super(props); + const component = this.props.config.context.getPluginManager().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + insertLink, + modalData: [ + { + locale: 'insertLink.insertLinkLabel', + group: [ + { + locale: 'button.confirm', + type: 'primary', + }, + { + locale: 'button.cancel', + }, + ], + show: false, + children: , + onCancel: () => this.showModal(0, false), + }, + ], + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.insertLink; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}`); + + let modalData = prevState.modalData; + modalData.forEach((ele) => { + ele.title = locale.get(ele.locale!); + if (ele.group && ele.group.length) { + ele.group.forEach((node) => { + node.label = locale.get(node.locale!); + }); + } + }); + + return { + insertLink: item, + modalData, + }; + }); + } + + showModal = (index: number, isShow: boolean) => { + let { modalData } = this.state; + modalData[index].show = isShow; + this.setState((prevState) => ({ + modalData, + })); + }; + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const Button = this.Render.renderFunction('Button'); + const Modal = this.Render.renderFunction('Modal'); + + // eslint-disable-next-line @typescript-eslint/no-shadow + const { insertLink, modalData } = state; + // Set Provider for entire Container + return ( +
        + + {modalData.map((item) => { + if (!item.show) return; + return ; + })} +
        + ); + } +} diff --git a/packages/sheets-plugin-link/src/UI/LinkContent.tsx b/packages/sheets-plugin-link/src/UI/LinkContent.tsx new file mode 100644 index 0000000000..8438f9b8b2 --- /dev/null +++ b/packages/sheets-plugin-link/src/UI/LinkContent.tsx @@ -0,0 +1,192 @@ +import { BaseComponentRender, BaseComponentSheet, Component } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { IProps } from '../IData'; +import styles from './index.module.less'; + +type LinkContentProps = { + config: IProps; +}; +type LinkContentBaseProps = { + name: string; + show: boolean; + label?: string; + type: string; + id: string; + placeholder?: { + name?: string; + label?: string; + }; + children?: Array>; + onChange?: (e: Event) => void; +}; +type LinkContentState = { + data: LinkContentBaseProps[]; + type: string; +}; +export class LinkContent extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize(props: LinkContentProps) { + // super(props); + const component = this.props.config.config.context.getPluginManager().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + this.state = { + data: [ + { + name: 'insertLink.linkText', + show: true, + type: 'single', + id: 'linkText', + }, + { + name: 'insertLink.linkType', + show: true, + type: 'select', + id: 'linkType', + onChange: (e: Event) => this.changeLinkType(e), + children: [ + { + name: 'insertLink.external', + value: 'external', + }, + { + name: 'insertLink.internal', + value: 'internal', + }, + ], + }, + { + name: 'insertLink.linkAddress', + show: true, + type: 'single', + id: 'linkAddress', + placeholder: { name: 'insertLink.placeholder1' }, + }, + { + name: 'insertLink.linkSheet', + id: 'linkSheet', + show: false, + type: 'select', + }, + { + name: 'insertLink.linkCell', + show: false, + id: 'linkCell', + type: 'single', + placeholder: { name: 'insertLink.placeholder2' }, + }, + { + name: 'insertLink.linkTooltip', + show: true, + id: 'linkTooltip', + type: 'single', + placeholder: { name: 'insertLink.placeholder3' }, + }, + ], + type: 'single', + }; + } + + setValue = (value: object, fn?: () => void) => { + this.setState( + (prevState) => ({ + ...value, + }), + fn + ); + }; + + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState) => { + let { data } = prevState; + data.forEach((item) => { + item.label = locale.get(item.name); + if (item.children && item.children.length) { + item.children.forEach((ele) => { + ele.label = locale.get(ele.name); + }); + } + if (item.placeholder) { + item.placeholder.label = locale.get(item.placeholder.name); + } + }); + + return { + data, + }; + }); + } + + changeLinkType = (e: Event) => { + const target = e.target as HTMLSelectElement; + let { data } = this.state; + const addressIndex = data.findIndex((item) => item.id === 'linkAddress'); + const sheetIndex = data.findIndex((item) => item.id === 'linkSheet'); + const cellIndex = data.findIndex((item) => item.id === 'linkCell'); + if (target.value === 'external') { + data[addressIndex].show = true; + data[sheetIndex].show = false; + data[cellIndex].show = false; + } else { + data[addressIndex].show = false; + data[sheetIndex].show = true; + data[cellIndex].show = true; + } + this.setValue({ data }); + }; + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + render(props: LinkContentProps, state: LinkContentState) { + const Input = this.Render.renderFunction('Input'); + const { data } = state; + return ( +
        + {data.map((item) => { + if (!item.show) return <>; + if (item.type === 'single') { + return ( +
        + + +
        + ); + } + return ( +
        + + +
        + ); + })} +
        + ); + } +} diff --git a/packages/sheets-plugin-link/src/UI/index.module.less b/packages/sheets-plugin-link/src/UI/index.module.less new file mode 100644 index 0000000000..495d7c7c27 --- /dev/null +++ b/packages/sheets-plugin-link/src/UI/index.module.less @@ -0,0 +1,30 @@ +.single-button { + .insert-content { + font-size: 12px; + + > div { + margin-bottom: 1.2em; + + &:last-of-type { + margin-bottom: 0; + } + } + + label { + width: 6em; + text-align: right; + display: inline-block; + margin-right: 1em; + } + + input { + width: 50%; + height: 2em; + } + + select { + height: 2em; + width: 50%; + } + } +} diff --git a/packages/sheets-plugin-link/src/View/Event.ts b/packages/sheets-plugin-link/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-link/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-link/src/View/ObserverList.ts b/packages/sheets-plugin-link/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-link/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-link/src/View/Render.ts b/packages/sheets-plugin-link/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-link/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-link/src/View/index.ts b/packages/sheets-plugin-link/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-link/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-link/src/index.ts b/packages/sheets-plugin-link/src/index.ts new file mode 100644 index 0000000000..d71bfd6bfe --- /dev/null +++ b/packages/sheets-plugin-link/src/index.ts @@ -0,0 +1,3 @@ +import { LinkPlugin } from './LinkPlugin'; + +export { LinkPlugin }; diff --git a/packages/sheets-plugin-link/src/main.tsx b/packages/sheets-plugin-link/src/main.tsx new file mode 100644 index 0000000000..c604712b3d --- /dev/null +++ b/packages/sheets-plugin-link/src/main.tsx @@ -0,0 +1,58 @@ +import { UniverSheet } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { LinkPlugin } from './LinkPlugin'; + +const defaultWorkbookData = { + locale: 'zh', + creator: '', + name: '', + skin: 'default', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +univerSheetUp.installPlugin(new LinkPlugin()); diff --git a/packages/sheets-plugin-link/src/preact.d.ts b/packages/sheets-plugin-link/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-link/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-link/tsconfig.json b/packages/sheets-plugin-link/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-link/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-link/tsconfig.ref.json b/packages/sheets-plugin-link/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-link/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-link/tsconfig.test.json b/packages/sheets-plugin-link/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-link/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-link/vite.config.ts b/packages/sheets-plugin-link/vite.config.ts new file mode 100644 index 0000000000..e9d9ffc61c --- /dev/null +++ b/packages/sheets-plugin-link/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginInsertLink', + formats: ['es', 'umd'], + fileName: 'universheet-plugin-insert-link', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-pivot-table/README-zh.md b/packages/sheets-plugin-pivot-table/README-zh.md new file mode 100644 index 0000000000..82e14b24ca --- /dev/null +++ b/packages/sheets-plugin-pivot-table/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet PivotTable 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-pivot-table +``` + +### 使用 + +```js +impport {PivotTable} from '@univer/sheets-plugin-pivot-table' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new PivotTable()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-pivot-table/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-pivot-table dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-pivot-table/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-pivot-table build +``` diff --git a/packages/sheets-plugin-pivot-table/README.md b/packages/sheets-plugin-pivot-table/README.md new file mode 100644 index 0000000000..b13fb540a0 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin PivotTable + +### Installation + +```bash +npm i @univer/sheets-plugin-pivot-table +``` + +### Usage + +```js +impport {PivotTable} from '@univer/sheets-plugin-pivot-table' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new PivotTable()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-pivot-table/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-pivot-table dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-pivot-table/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-pivot-table build +``` diff --git a/packages/sheets-plugin-pivot-table/index.html b/packages/sheets-plugin-pivot-table/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-pivot-table/jest.config.js b/packages/sheets-plugin-pivot-table/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-pivot-table/package.json b/packages/sheets-plugin-pivot-table/package.json new file mode 100644 index 0000000000..8995793157 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/package.json @@ -0,0 +1,56 @@ +{ + "name": "@univer/sheets-plugin-pivot-table", + "version": "0.0.1", + "description": "UniverSheet normal plugin-pivot-table", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-pivot-table/src/Action/ActionPivotTable.ts b/packages/sheets-plugin-pivot-table/src/Action/ActionPivotTable.ts new file mode 100644 index 0000000000..2ba5026cc2 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Action/ActionPivotTable.ts @@ -0,0 +1 @@ +export class ActionPivotTable {} diff --git a/packages/sheets-plugin-pivot-table/src/Action/IAction.ts b/packages/sheets-plugin-pivot-table/src/Action/IAction.ts new file mode 100644 index 0000000000..9b031a3c3c --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Action/IAction.ts @@ -0,0 +1 @@ +export class IAction {} diff --git a/packages/sheets-plugin-pivot-table/src/Action/IActionSet.ts b/packages/sheets-plugin-pivot-table/src/Action/IActionSet.ts new file mode 100644 index 0000000000..f8e2a8f7e6 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Action/IActionSet.ts @@ -0,0 +1 @@ +export class IActionSet {} diff --git a/packages/sheets-plugin-pivot-table/src/Action/index.ts b/packages/sheets-plugin-pivot-table/src/Action/index.ts new file mode 100644 index 0000000000..4a30c47ce6 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Action/index.ts @@ -0,0 +1,8 @@ +/** + * en: Modify localData, conflict handling, generate data transfer protocol, submit for background execution + * + * zh: 修改localData、冲突处理、生成数据传输协议、提交后台执行 + */ +export * from './ActionPivotTable'; +export * from './IAction'; +export * from './IActionSet'; diff --git a/packages/sheets-plugin-pivot-table/src/Base/Cache.ts b/packages/sheets-plugin-pivot-table/src/Base/Cache.ts new file mode 100644 index 0000000000..002f98640e --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Base/Cache.ts @@ -0,0 +1 @@ +export class Cache {} diff --git a/packages/sheets-plugin-pivot-table/src/Base/State.ts b/packages/sheets-plugin-pivot-table/src/Base/State.ts new file mode 100644 index 0000000000..f277253fef --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Base/State.ts @@ -0,0 +1 @@ +export class State {} diff --git a/packages/sheets-plugin-pivot-table/src/Base/index.ts b/packages/sheets-plugin-pivot-table/src/Base/index.ts new file mode 100644 index 0000000000..6c6e3b5d96 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Base/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './State'; diff --git a/packages/sheets-plugin-pivot-table/src/Conflict/ConflictPivotTable.ts b/packages/sheets-plugin-pivot-table/src/Conflict/ConflictPivotTable.ts new file mode 100644 index 0000000000..a6837cdd79 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Conflict/ConflictPivotTable.ts @@ -0,0 +1 @@ +export class ConflictPivotTable {} diff --git a/packages/sheets-plugin-pivot-table/src/Conflict/IConflict.ts b/packages/sheets-plugin-pivot-table/src/Conflict/IConflict.ts new file mode 100644 index 0000000000..02b0979966 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Conflict/IConflict.ts @@ -0,0 +1 @@ +export class IConflict {} diff --git a/packages/sheets-plugin-pivot-table/src/Conflict/index.ts b/packages/sheets-plugin-pivot-table/src/Conflict/index.ts new file mode 100644 index 0000000000..142cdf84da --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Conflict/index.ts @@ -0,0 +1,7 @@ +/** + * en: Handling action conflicts + * + * zh: 处理action冲突 + */ +export * from './IConflict'; +export * from './ConflictPivotTable'; diff --git a/packages/sheets-plugin-pivot-table/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-pivot-table/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..4fb0f1e323 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const PIVOT_TABLE_PLUGIN_NAME = 'pivotTable'; diff --git a/packages/sheets-plugin-pivot-table/src/Const/index.ts b/packages/sheets-plugin-pivot-table/src/Const/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-pivot-table/src/Domain/PivotTable.ts b/packages/sheets-plugin-pivot-table/src/Domain/PivotTable.ts new file mode 100644 index 0000000000..f521824b13 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Domain/PivotTable.ts @@ -0,0 +1 @@ +export class PivotTable {} diff --git a/packages/sheets-plugin-pivot-table/src/Domain/index.ts b/packages/sheets-plugin-pivot-table/src/Domain/index.ts new file mode 100644 index 0000000000..1746420653 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Domain/index.ts @@ -0,0 +1 @@ +export * from './PivotTable'; diff --git a/packages/sheets-plugin-pivot-table/src/Enum/index.ts b/packages/sheets-plugin-pivot-table/src/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-pivot-table/src/IData/IPivotTable.ts b/packages/sheets-plugin-pivot-table/src/IData/IPivotTable.ts new file mode 100644 index 0000000000..a809ef3796 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/IData/IPivotTable.ts @@ -0,0 +1,4 @@ +export type IConfig = {}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-pivot-table/src/IData/index.ts b/packages/sheets-plugin-pivot-table/src/IData/index.ts new file mode 100644 index 0000000000..e9ba6c934a --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/IData/index.ts @@ -0,0 +1 @@ +export * from './IPivotTable'; diff --git a/packages/sheets-plugin-pivot-table/src/Locale/en.ts b/packages/sheets-plugin-pivot-table/src/Locale/en.ts new file mode 100644 index 0000000000..bb0ddeca2e --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Locale/en.ts @@ -0,0 +1,59 @@ +export default { + pivotTableLabel: 'PivotTable', + pivotTable: { + title: 'Pivot Table', + closePannel: 'Close', + editRange: 'Range', + tipPivotFieldSelected: 'Select the fields', + tipClearSelectedField: 'Clear all fields', + btnClearSelectedField: 'Clear', + btnFilter: 'Filter', + titleRow: 'Row', + titleColumn: 'Column', + titleValue: 'Value', + tipShowColumn: 'Statistics fields are displayed as columns', + tipShowRow: 'Statistics fields are displayed as rows', + + titleSelectionDataRange: 'Select range', + titleDataRange: 'Data range', + + valueSum: 'SUM', + + valueStatisticsSUM: 'Sum', + valueStatisticsCOUNT: 'Count', + valueStatisticsCOUNTA: 'Count A', + valueStatisticsCOUNTUNIQUE: 'Count Unique', + valueStatisticsAVERAGE: 'Average', + valueStatisticsMAX: 'Max', + valueStatisticsMIN: 'Min', + valueStatisticsMEDIAN: 'Median', + valueStatisticsPRODUCT: 'Product', + valueStatisticsSTDEV: 'Stdev', + + valueStatisticsSTDEVP: 'Stdevp', + valueStatisticslet: 'Var', + valueStatisticsVARP: 'VarP', + + errorNotAllowEdit: 'This operation is prohibited in non-editing mode!', + errorNotAllowMulti: 'Cannot perform this operation on multiple selection areas, please select a single range and try again', + errorSelectRange: 'Please select the range of the new pivot table', + errorIsDamage: 'The source data of this pivot table is corrupted!', + errorNotAllowPivotData: 'Cannot select pivot table as source data!', + errorSelectionRange: 'Selection failed, wrong input range!', + errorIncreaseRange: 'Please expand the selected range!', + + titleAddColumn: 'Add column to pivot table', + titleMoveColumn: 'Move the column to the white box below', + titleClearColumnFilter: 'Clear the filter for this column', + titleFilterColumn: 'Filter', + + titleSort: 'Sort', + titleNoSort: 'No sort', + titleSortAsc: 'ASC', + titleSortDesc: 'DESC', + titleSortBy: 'Sort by', + titleShowSum: 'Show total', + titleStasticTrue: 'Yes', + titleStasticFalse: 'No', + }, +}; diff --git a/packages/sheets-plugin-pivot-table/src/Locale/index.ts b/packages/sheets-plugin-pivot-table/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-pivot-table/src/Locale/zh.ts b/packages/sheets-plugin-pivot-table/src/Locale/zh.ts new file mode 100644 index 0000000000..d132060011 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Locale/zh.ts @@ -0,0 +1,59 @@ +export default { + pivotTableLabel: '数据透视表', + pivotTable: { + title: '数据透视表', + closePannel: '关闭', + editRange: '编辑范围', + tipPivotFieldSelected: '选择需要添加到数据透视表的字段', + tipClearSelectedField: '清除所有已选字段', + btnClearSelectedField: '清除', + btnFilter: '筛选', + titleRow: '行', + titleColumn: '列', + titleValue: '数值', + tipShowColumn: '统计字段显示为列', + tipShowRow: '统计字段显示为行', + + titleSelectionDataRange: '选取数据范围', + titleDataRange: '数据范围', + + valueSum: '总计', + + valueStatisticsSUM: '求和', + valueStatisticsCOUNT: '数值计数', + valueStatisticsCOUNTA: '计数', + valueStatisticsCOUNTUNIQUE: '去重计数', + valueStatisticsAVERAGE: '平均值', + valueStatisticsMAX: '最大值', + valueStatisticsMIN: '最小值', + valueStatisticsMEDIAN: '中位数', + valueStatisticsPRODUCT: '乘积', + valueStatisticsSTDEV: '标准差', + + valueStatisticsSTDEVP: '整体标准差', + valueStatisticslet: '方差', + valueStatisticsVARP: '整体方差', + + errorNotAllowEdit: '非编辑模式下禁止该操作!', + errorNotAllowMulti: '不能对多重选择区域执行此操作,请选择单个区域,然后再试', + errorSelectRange: '请选择新建透视表的区域', + errorIsDamage: '此数据透视表的源数据已损坏!', + errorNotAllowPivotData: '不可选择数据透视表为源数据!', + errorSelectionRange: '选择失败, 输入范围错误!', + errorIncreaseRange: '请扩大选择的数据范围!', + + titleAddColumn: '添加列到数据透视表', + titleMoveColumn: '移动该列到下方白框', + titleClearColumnFilter: '清除该列的筛选条件', + titleFilterColumn: '筛选该列', + + titleSort: '排序', + titleNoSort: '无排序', + titleSortAsc: '升序', + titleSortDesc: '降序', + titleSortBy: '排序依据', + titleShowSum: '显示总计', + titleStasticTrue: '是', + titleStasticFalse: '否', + }, +}; diff --git a/packages/sheets-plugin-pivot-table/src/PivotTablePlugin.tsx b/packages/sheets-plugin-pivot-table/src/PivotTablePlugin.tsx new file mode 100644 index 0000000000..28a1bdc890 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/PivotTablePlugin.tsx @@ -0,0 +1,59 @@ +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { Context, IOCContainer, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { en, zh } from './Locale'; +import { PivotTableButton } from './UI/PivotTableButton'; + +import { IConfig } from './IData/IPivotTable'; +import { PIVOT_TABLE_PLUGIN_NAME } from './Const/PLUGIN_NAME'; + +type IPluginConfig = {}; + +export class PivotTablePlugin extends Plugin { + spreadsheetPlugin: any; + + constructor(config?: IPluginConfig) { + super(PIVOT_TABLE_PLUGIN_NAME); + } + + static create(config?: IPluginConfig) { + return new PivotTablePlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = {}; + + const item: IToolBarItemProps = { + locale: PIVOT_TABLE_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + // get spreadsheet plugin + this.spreadsheetPlugin = context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET); + + // extend comment + this.spreadsheetPlugin?.addButton(item); + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} +} diff --git a/packages/sheets-plugin-pivot-table/src/ProxyData/IProxyData.ts b/packages/sheets-plugin-pivot-table/src/ProxyData/IProxyData.ts new file mode 100644 index 0000000000..0bd8a3cf07 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/ProxyData/IProxyData.ts @@ -0,0 +1 @@ +export class IProxyData {} diff --git a/packages/sheets-plugin-pivot-table/src/ProxyData/IProxyDataSet.ts b/packages/sheets-plugin-pivot-table/src/ProxyData/IProxyDataSet.ts new file mode 100644 index 0000000000..9bf75cd768 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/ProxyData/IProxyDataSet.ts @@ -0,0 +1 @@ +export class IProxyDataSet {} diff --git a/packages/sheets-plugin-pivot-table/src/ProxyData/ProxyDataPivotTable.ts b/packages/sheets-plugin-pivot-table/src/ProxyData/ProxyDataPivotTable.ts new file mode 100644 index 0000000000..39999cf189 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/ProxyData/ProxyDataPivotTable.ts @@ -0,0 +1 @@ +export class IProxyDataPivotTable {} diff --git a/packages/sheets-plugin-pivot-table/src/ProxyData/index.ts b/packages/sheets-plugin-pivot-table/src/ProxyData/index.ts new file mode 100644 index 0000000000..da713b14f7 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/ProxyData/index.ts @@ -0,0 +1,8 @@ +/** + * en: Transfer to background data protocol + * + * zh: 传输到后台数据协议 + */ +export * from './IProxyData'; +export * from './IProxyDataSet'; +export * from './ProxyDataPivotTable'; diff --git a/packages/sheets-plugin-pivot-table/src/Service/PivotTableService.ts b/packages/sheets-plugin-pivot-table/src/Service/PivotTableService.ts new file mode 100644 index 0000000000..6439a94e3e --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Service/PivotTableService.ts @@ -0,0 +1 @@ +export class PivotTableService {} diff --git a/packages/sheets-plugin-pivot-table/src/Service/index.ts b/packages/sheets-plugin-pivot-table/src/Service/index.ts new file mode 100644 index 0000000000..257ab9e1fe --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Service/index.ts @@ -0,0 +1,7 @@ +/** + * + * en: Operate and register localData + * + * zh: 操作、注册localData + */ +export * from './PivotTableService'; diff --git a/packages/sheets-plugin-pivot-table/src/Shared/index.ts b/packages/sheets-plugin-pivot-table/src/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-pivot-table/src/Types/index.d.ts b/packages/sheets-plugin-pivot-table/src/Types/index.d.ts new file mode 100644 index 0000000000..e17e19d30e --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-pivot-table' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-pivot-table/src/UI/PivotTableButton.tsx b/packages/sheets-plugin-pivot-table/src/UI/PivotTableButton.tsx new file mode 100644 index 0000000000..636d5452b9 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/UI/PivotTableButton.tsx @@ -0,0 +1,106 @@ +import { BaseComponentRender, BaseComponentSheet, Component, IToolBarItemProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { PIVOT_TABLE_PLUGIN_NAME } from '../Const/PLUGIN_NAME'; +import { IProps } from '../IData/IPivotTable'; +import styles from './index.module.less'; + +// Types for state +interface IState { + pivotTable: IToolBarItemProps; + // modalData: Array; +} + +export class PivotTableButton extends Component { + private _localeObserver: Nullable>; + + Render: BaseComponentRender; + + initialize(props: IProps) { + // super(props); + const component = new SpreadsheetPlugin().getPluginByName('ComponentSheet')!; + this.Render = component.getComponentRender(); + + const PivotableIcon = this.Render.renderFunction('PivotableIcon'); + const pivotTableState: IToolBarItemProps = { + locale: PIVOT_TABLE_PLUGIN_NAME, + type: 'single', + label: , + show: true, + }; + this.state = { + pivotTable: pivotTableState, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.pivotTable; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}Label`); + item.tooltipRight = locale.get(`${item.locale}RightLabel`); + + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + + return { + pivotTable: item, + }; + }); + } + + showPivotTable = () => {}; + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const Button = this.Render.renderFunction('Button'); + const Tooltip = this.Render.renderFunction('Tooltip'); + const { pivotTable } = state; + // Set Provider for entire Container + return ( +
        + + + +
        + ); + } +} diff --git a/packages/sheets-plugin-pivot-table/src/UI/index.module.less b/packages/sheets-plugin-pivot-table/src/UI/index.module.less new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-pivot-table/src/View/Event.ts b/packages/sheets-plugin-pivot-table/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-pivot-table/src/View/ObserverList.ts b/packages/sheets-plugin-pivot-table/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-pivot-table/src/View/Render.ts b/packages/sheets-plugin-pivot-table/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-pivot-table/src/View/index.ts b/packages/sheets-plugin-pivot-table/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-pivot-table/src/index.ts b/packages/sheets-plugin-pivot-table/src/index.ts new file mode 100644 index 0000000000..bcc926eec3 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/index.ts @@ -0,0 +1,3 @@ +import { PivotTablePlugin } from './PivotTablePlugin'; + +export { PivotTablePlugin }; diff --git a/packages/sheets-plugin-pivot-table/src/main.tsx b/packages/sheets-plugin-pivot-table/src/main.tsx new file mode 100644 index 0000000000..ab977be493 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/main.tsx @@ -0,0 +1,67 @@ +import { UniverSheet } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { FilterPlugin } from '@univer/sheets-plugin-filter'; +import { PivotTablePlugin } from './PivotTablePlugin'; + +const defaultWorkbookData = { + locale: 'zh', + creator: '', + name: '', + skin: 'default', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +univerSheetUp.installPlugin(new FilterPlugin()); +univerSheetUp.installPlugin(new PivotTablePlugin()); + +const univerSheetDown = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetDown.installPlugin(new StyleUniverSheet(uiDefaultConfigDown)); + +univerSheetDown.installPlugin(new FilterPlugin()); +univerSheetDown.installPlugin(new PivotTablePlugin()); diff --git a/packages/sheets-plugin-pivot-table/src/preact.d.ts b/packages/sheets-plugin-pivot-table/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-pivot-table/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-pivot-table/tsconfig.json b/packages/sheets-plugin-pivot-table/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-pivot-table/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-pivot-table/tsconfig.ref.json b/packages/sheets-plugin-pivot-table/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-pivot-table/tsconfig.test.json b/packages/sheets-plugin-pivot-table/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-pivot-table/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-pivot-table/vite.config.ts b/packages/sheets-plugin-pivot-table/vite.config.ts new file mode 100644 index 0000000000..133930c3c2 --- /dev/null +++ b/packages/sheets-plugin-pivot-table/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginPivotTable', + formats: ['es', 'umd'], + fileName: 'univer-sheets-plugin-pivot-table', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-print/README-zh.md b/packages/sheets-plugin-print/README-zh.md new file mode 100644 index 0000000000..cad13e29c7 --- /dev/null +++ b/packages/sheets-plugin-print/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet Print 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-print +``` + +### 使用 + +```js +impport {Print} from '@univer/sheets-plugin-print' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Print()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-print/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-print dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-print/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-print build +``` diff --git a/packages/sheets-plugin-print/README.md b/packages/sheets-plugin-print/README.md new file mode 100644 index 0000000000..4cb4481c47 --- /dev/null +++ b/packages/sheets-plugin-print/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin Print + +### Installation + +```bash +npm i @univer/sheets-plugin-print +``` + +### Usage + +```js +impport {Print} from '@univer/sheets-plugin-print' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Print()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-print/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-print dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-print/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-print build +``` diff --git a/packages/sheets-plugin-print/index.html b/packages/sheets-plugin-print/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-print/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
        +
        +
        + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-print/jest.config.js b/packages/sheets-plugin-print/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-print/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-print/package.json b/packages/sheets-plugin-print/package.json new file mode 100644 index 0000000000..a8c6ef5414 --- /dev/null +++ b/packages/sheets-plugin-print/package.json @@ -0,0 +1,56 @@ +{ + "name": "@univer/sheets-plugin-print", + "version": "0.0.1", + "description": "UniverSheet normal plugin-print", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-print/src/Action/ActionPrint.ts b/packages/sheets-plugin-print/src/Action/ActionPrint.ts new file mode 100644 index 0000000000..87d736b9d3 --- /dev/null +++ b/packages/sheets-plugin-print/src/Action/ActionPrint.ts @@ -0,0 +1 @@ +export class ActionPrint {} diff --git a/packages/sheets-plugin-print/src/Action/IAction.ts b/packages/sheets-plugin-print/src/Action/IAction.ts new file mode 100644 index 0000000000..9b031a3c3c --- /dev/null +++ b/packages/sheets-plugin-print/src/Action/IAction.ts @@ -0,0 +1 @@ +export class IAction {} diff --git a/packages/sheets-plugin-print/src/Action/IActionSet.ts b/packages/sheets-plugin-print/src/Action/IActionSet.ts new file mode 100644 index 0000000000..f8e2a8f7e6 --- /dev/null +++ b/packages/sheets-plugin-print/src/Action/IActionSet.ts @@ -0,0 +1 @@ +export class IActionSet {} diff --git a/packages/sheets-plugin-print/src/Action/index.ts b/packages/sheets-plugin-print/src/Action/index.ts new file mode 100644 index 0000000000..61b1675205 --- /dev/null +++ b/packages/sheets-plugin-print/src/Action/index.ts @@ -0,0 +1,8 @@ +/** + * en: Modify localData, conflict handling, generate data transfer protocol, submit for background execution + * + * zh: 修改localData、冲突处理、生成数据传输协议、提交后台执行 + */ +export * from './ActionPrint'; +export * from './IAction'; +export * from './IActionSet'; diff --git a/packages/sheets-plugin-print/src/Base/Cache.ts b/packages/sheets-plugin-print/src/Base/Cache.ts new file mode 100644 index 0000000000..002f98640e --- /dev/null +++ b/packages/sheets-plugin-print/src/Base/Cache.ts @@ -0,0 +1 @@ +export class Cache {} diff --git a/packages/sheets-plugin-print/src/Base/State.ts b/packages/sheets-plugin-print/src/Base/State.ts new file mode 100644 index 0000000000..f277253fef --- /dev/null +++ b/packages/sheets-plugin-print/src/Base/State.ts @@ -0,0 +1 @@ +export class State {} diff --git a/packages/sheets-plugin-print/src/Base/index.ts b/packages/sheets-plugin-print/src/Base/index.ts new file mode 100644 index 0000000000..6c6e3b5d96 --- /dev/null +++ b/packages/sheets-plugin-print/src/Base/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './State'; diff --git a/packages/sheets-plugin-print/src/Conflict/ConflictPrint.ts b/packages/sheets-plugin-print/src/Conflict/ConflictPrint.ts new file mode 100644 index 0000000000..7f56846223 --- /dev/null +++ b/packages/sheets-plugin-print/src/Conflict/ConflictPrint.ts @@ -0,0 +1 @@ +export class ConflictPrint {} diff --git a/packages/sheets-plugin-print/src/Conflict/IConflict.ts b/packages/sheets-plugin-print/src/Conflict/IConflict.ts new file mode 100644 index 0000000000..02b0979966 --- /dev/null +++ b/packages/sheets-plugin-print/src/Conflict/IConflict.ts @@ -0,0 +1 @@ +export class IConflict {} diff --git a/packages/sheets-plugin-print/src/Conflict/index.ts b/packages/sheets-plugin-print/src/Conflict/index.ts new file mode 100644 index 0000000000..e1f6e1ffe0 --- /dev/null +++ b/packages/sheets-plugin-print/src/Conflict/index.ts @@ -0,0 +1,7 @@ +/** + * en: Handling action conflicts + * + * zh: 处理action冲突 + */ +export * from './IConflict'; +export * from './ConflictPrint'; diff --git a/packages/sheets-plugin-print/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-print/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..f8c650a9c7 --- /dev/null +++ b/packages/sheets-plugin-print/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const PRINT_PLUGIN_NAME = 'print'; diff --git a/packages/sheets-plugin-print/src/Const/index.ts b/packages/sheets-plugin-print/src/Const/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-print/src/Domain/Print.ts b/packages/sheets-plugin-print/src/Domain/Print.ts new file mode 100644 index 0000000000..aef2cc53f1 --- /dev/null +++ b/packages/sheets-plugin-print/src/Domain/Print.ts @@ -0,0 +1 @@ +export class Print {} diff --git a/packages/sheets-plugin-print/src/Domain/index.ts b/packages/sheets-plugin-print/src/Domain/index.ts new file mode 100644 index 0000000000..3a6ad4096e --- /dev/null +++ b/packages/sheets-plugin-print/src/Domain/index.ts @@ -0,0 +1 @@ +export * from './Print'; diff --git a/packages/sheets-plugin-print/src/Enum/index.ts b/packages/sheets-plugin-print/src/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-print/src/IData/IPrint.ts b/packages/sheets-plugin-print/src/IData/IPrint.ts new file mode 100644 index 0000000000..79c97b9929 --- /dev/null +++ b/packages/sheets-plugin-print/src/IData/IPrint.ts @@ -0,0 +1,8 @@ +import { Context } from '@univer/core'; + +export type IConfig = { + context: Context; +}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-print/src/IData/index.ts b/packages/sheets-plugin-print/src/IData/index.ts new file mode 100644 index 0000000000..eb85e5b5d4 --- /dev/null +++ b/packages/sheets-plugin-print/src/IData/index.ts @@ -0,0 +1 @@ +export * from './IPrint'; diff --git a/packages/sheets-plugin-print/src/Locale/en.ts b/packages/sheets-plugin-print/src/Locale/en.ts new file mode 100644 index 0000000000..2d1ef11a47 --- /dev/null +++ b/packages/sheets-plugin-print/src/Locale/en.ts @@ -0,0 +1,28 @@ +export default { + print: { + printLabel: '插件', + printRightLabel: '更多 Print', + printSettings: 'Print Settings', + cancel: 'cancel', + next: 'next', + preview: 'preview', + print: 'print', + paperSize: 'paper size', + width: 'width', + height: 'height', + pageOrientation: 'page orientation', + zoom: 'zoom', + margin: 'margin', + formatOptions: 'format options', + gridlines: 'gridlines', + remarks: 'remarks', + pageOrder: 'page order', + alignment: 'alignment', + level: 'level', + vertical: 'vertical', + headerAndFooter: 'header and footer', + headers: 'Row and column headers', + rows: 'Freeze rows repeatedly', + column: 'Freeze columns repeatedly', + }, +}; diff --git a/packages/sheets-plugin-print/src/Locale/index.ts b/packages/sheets-plugin-print/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-print/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-print/src/Locale/zh.ts b/packages/sheets-plugin-print/src/Locale/zh.ts new file mode 100644 index 0000000000..1c5b2509a8 --- /dev/null +++ b/packages/sheets-plugin-print/src/Locale/zh.ts @@ -0,0 +1,28 @@ +export default { + print: { + printLabel: '插件', + printRightLabel: '更多 Print', + printSettings: '打印设置', + cancel: '取消', + next: '下一步', + preview: '预览', + print: '打印', + paperSize: '纸张大小', + width: '宽度', + height: '高度', + pageOrientation: '网页方向', + zoom: '缩放', + margin: '边距', + formatOptions: '格式设置', + gridlines: '网格线', + remarks: '备注', + pageOrder: '页面顺序', + alignment: '对齐', + level: '水平', + vertical: '垂直', + headerAndFooter: '页眉和页脚', + headers: '行标题和列标题', + rows: '重复冻结行', + column: '重复冻结列', + }, +}; diff --git a/packages/sheets-plugin-print/src/PrintPlugin.tsx b/packages/sheets-plugin-print/src/PrintPlugin.tsx new file mode 100644 index 0000000000..986f5cc06d --- /dev/null +++ b/packages/sheets-plugin-print/src/PrintPlugin.tsx @@ -0,0 +1,55 @@ +import { IToolBarItemProps, ISlotElement } from '@univer/base-component'; +import { Context, IOCContainer, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { en, zh } from './Locale'; +import { PrintButton } from './UI/PrintButton'; + +import { IConfig } from './IData/IPrint'; +import { PRINT_PLUGIN_NAME } from './Const/PLUGIN_NAME'; + +type IPluginConfig = {}; + +export class PrintPlugin extends Plugin { + constructor(config?: IPluginConfig) { + super(PRINT_PLUGIN_NAME); + } + + static create(config?: IPluginConfig) { + return new PrintPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = { + context, + }; + + const item: IToolBarItemProps = { + locale: PRINT_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.addButton(item); + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} +} diff --git a/packages/sheets-plugin-print/src/ProxyData/IProxyData.ts b/packages/sheets-plugin-print/src/ProxyData/IProxyData.ts new file mode 100644 index 0000000000..0bd8a3cf07 --- /dev/null +++ b/packages/sheets-plugin-print/src/ProxyData/IProxyData.ts @@ -0,0 +1 @@ +export class IProxyData {} diff --git a/packages/sheets-plugin-print/src/ProxyData/IProxyDataSet.ts b/packages/sheets-plugin-print/src/ProxyData/IProxyDataSet.ts new file mode 100644 index 0000000000..9bf75cd768 --- /dev/null +++ b/packages/sheets-plugin-print/src/ProxyData/IProxyDataSet.ts @@ -0,0 +1 @@ +export class IProxyDataSet {} diff --git a/packages/sheets-plugin-print/src/ProxyData/ProxyDataPrint.ts b/packages/sheets-plugin-print/src/ProxyData/ProxyDataPrint.ts new file mode 100644 index 0000000000..d2e6cb82e7 --- /dev/null +++ b/packages/sheets-plugin-print/src/ProxyData/ProxyDataPrint.ts @@ -0,0 +1 @@ +export class IProxyDataPrint {} diff --git a/packages/sheets-plugin-print/src/ProxyData/index.ts b/packages/sheets-plugin-print/src/ProxyData/index.ts new file mode 100644 index 0000000000..0fd7cf4ea6 --- /dev/null +++ b/packages/sheets-plugin-print/src/ProxyData/index.ts @@ -0,0 +1,8 @@ +/** + * en: Transfer to background data protocol + * + * zh: 传输到后台数据协议 + */ +export * from './IProxyData'; +export * from './IProxyDataSet'; +export * from './ProxyDataPrint'; diff --git a/packages/sheets-plugin-print/src/Service/PrintService.ts b/packages/sheets-plugin-print/src/Service/PrintService.ts new file mode 100644 index 0000000000..804db0e57d --- /dev/null +++ b/packages/sheets-plugin-print/src/Service/PrintService.ts @@ -0,0 +1 @@ +export class PrintService {} diff --git a/packages/sheets-plugin-print/src/Service/index.ts b/packages/sheets-plugin-print/src/Service/index.ts new file mode 100644 index 0000000000..fc48b8a1cd --- /dev/null +++ b/packages/sheets-plugin-print/src/Service/index.ts @@ -0,0 +1,7 @@ +/** + * + * en: Operate and register localData + * + * zh: 操作、注册localData + */ +export * from './PrintService'; diff --git a/packages/sheets-plugin-print/src/Shared/index.ts b/packages/sheets-plugin-print/src/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-print/src/Types/index.d.ts b/packages/sheets-plugin-print/src/Types/index.d.ts new file mode 100644 index 0000000000..66b6e2fc36 --- /dev/null +++ b/packages/sheets-plugin-print/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-print' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-print/src/UI/PrintButton.tsx b/packages/sheets-plugin-print/src/UI/PrintButton.tsx new file mode 100644 index 0000000000..09435e0043 --- /dev/null +++ b/packages/sheets-plugin-print/src/UI/PrintButton.tsx @@ -0,0 +1,139 @@ +import { BaseComponentSheet, BaseIconProps, BaseSelectProps, Component, IToolBarItemProps } from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { FunctionComponent } from 'preact'; +import { IProps } from '../IData/IPrint'; +import { PrintMode } from './PrintMode'; +// Types for state +interface IState { + print: IToolBarItemProps; + isPrintShow: boolean; +} + +export class PrintButton extends Component { + Select: FunctionComponent; + + PrintIcon: FunctionComponent; + + NextIcon: FunctionComponent; + + PrintAreaIcon: FunctionComponent; + + PrintTitleIcon: FunctionComponent; + + private _localeObserver: Nullable>; + + initialize(props: IProps) { + // super(props); + const component = this.props.config.context.getPluginManager().getPluginByName('ComponentSheet')!; + const render = component.getComponentRender(); + this.Select = render.renderFunction('Select'); + this.PrintIcon = render.renderFunction('PrintIcon'); + this.NextIcon = render.renderFunction('NextIcon'); + this.PrintAreaIcon = render.renderFunction('PrintAreaIcon'); + this.PrintTitleIcon = render.renderFunction('PrintTitleIcon'); + + this.state = { + isPrintShow: false, + print: { + locale: 'print', + type: 'select', + label: , + show: true, + icon: , + children: [ + { + locale: 'print.menuItemPrint', + icon: , + onClick: this.setPrintShow.bind(this), + }, + { + locale: 'print.menuItemAreas', + icon: , + border: true, + }, + { + locale: 'print.menuItemRows', + icon: , + }, + { + locale: 'print.menuItemColumns', + icon: , + }, + ], + }, + }; + } + + setPrintShow() { + this.setState({ isPrintShow: true }); + } + + setPrintHide() { + this.setState({ isPrintShow: false }); + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + this.setState((prevState: IState) => { + let item = prevState.print; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}Label`); + item.tooltipRight = locale.get(`${item.locale}RightLabel`); + + // set current Locale string for select + item.children?.forEach((ele: IToolBarItemProps) => { + if (ele.locale) { + ele.label = locale.get(`${ele.locale}`); + } + }); + item.label = typeof item.label === 'object' ? item.label : item.children![0].label; + + return { + print: item, + }; + }); + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const { print, isPrintShow } = state; + const { Select } = this; + // Set Provider for entire Container + return ( + <> + + + ); + } +} + +export { PrintInput }; diff --git a/packages/sheets-plugin-print/src/UI/PrintMode/printMode.tsx b/packages/sheets-plugin-print/src/UI/PrintMode/printMode.tsx new file mode 100644 index 0000000000..01fda27d82 --- /dev/null +++ b/packages/sheets-plugin-print/src/UI/PrintMode/printMode.tsx @@ -0,0 +1,392 @@ +import { + BaseButtonProps, + BaseCheckboxGroupProps, + BaseCollapseProps, + BaseComponentSheet, + BasePanelProps, + BaseRadioGroupProps, + BaseRadioIProps, + Component, + FunctionComponent, +} from '@univer/base-component'; +import { Nullable, Observer, WorkBook } from '@univer/core'; +import { IConfig } from '../../IData'; +import styles from './index.module.less'; +import { PrintInput } from './printInput'; +import { PrintSelect } from './printSelsct'; + +interface Options { + key: number; + value: string; +} +interface IProps { + visible: boolean; + onCancel: () => void; + config: IConfig; +} +interface IState { + active: string | number; + options: Options; + printSheet: any; + printPaperSize: any; + printZoom: any; + printMargin: any; + printPageOrder: any; + printHorizontal: any; + printVertical: any; + paperSizeCustom: boolean; + locale: any; +} + +class PrintMode extends Component { + // printModeRef = createRef(); + + private _localeObserver: Nullable>; + + Button: FunctionComponent; + + CheckboxGroup: FunctionComponent; + + Collapse: FunctionComponent; + + Panel: FunctionComponent; + + Radio: FunctionComponent; + + RadioGroup: FunctionComponent; + + initialize(props: IProps) { + const component = this.props.config.context.getPluginManager().getPluginByName('ComponentSheet')!; + const render = component.getComponentRender(); + this.Button = render.renderFunction('Button'); + this.CheckboxGroup = render.renderFunction('CheckboxGroup'); + this.Collapse = render.renderFunction('Collapse'); + this.Panel = render.renderFunction('Panel'); + this.Radio = render.renderFunction('Radio'); + this.RadioGroup = render.renderFunction('RadioGroup'); + + this.state = { + active: '1', + options: { key: 1, value: '111' }, + paperSizeCustom: false, + printSheet: [ + { + name: 'print.menuItemPrint', + label: '当前工作表', + }, + ], + printPaperSize: [ + { + name: 'print.menuItemPrint', + label: '信纸(21.6厘米x27.9厘米)', + }, + { + name: 'print.menuItemPrint', + label: '小报(27.9厘米x43.2厘米)', + }, + { + name: 'print.menuItemPrint', + label: '法律专用纸(21.6厘米x35.6厘米)', + }, + { + name: 'print.menuItemPrint', + label: '报表(14.0厘米x21.6厘米)', + }, + { + name: 'print.menuItemPrint', + label: '行政公文纸(18.4厘米x26.7厘米)', + }, + { + name: 'print.menuItemPrint', + label: '对折纸(18.4厘米x26.7厘米)', + }, + { + name: 'print.menuItemPrint', + label: 'A3(29.7厘米x42.0厘米)', + }, + { + name: 'print.menuItemPrint', + label: 'A4(21.0厘米x29.7厘米)', + }, + { + name: 'print.menuItemPrint', + label: 'A5(14.8厘米x21.0厘米)', + }, + { + name: 'print.menuItemPrint', + label: 'B4(25.0厘米x35.3厘米)', + }, + { + name: 'print.menuItemPrint', + label: 'B5(17.6厘米x25.0厘米)', + }, + { + name: 'print.custom', + label: '自定义', + }, + ], + printZoom: [ + { + name: 'print.menuItemPrint', + label: '正常(100%)', + }, + { + name: 'print.menuItemPrint', + label: '适合宽度', + }, + { + name: 'print.menuItemPrint', + label: '适合高度', + }, + { + name: 'print.menuItemPrint', + label: '适合页面大小', + }, + ], + printMargin: [ + { + name: 'print.menuItemPrint', + label: '正常', + }, + { + name: 'print.menuItemPrint', + label: '窄', + }, + { + name: 'print.menuItemPrint', + label: '宽', + }, + ], + printPageOrder: [ + { + name: 'print.menuItemPrint', + label: '先往下,再翻页', + }, + { + name: 'print.menuItemPrint', + label: '先翻页,再往下', + }, + ], + printHorizontal: [ + { + name: 'print.menuItemPrint', + label: '水平居中', + }, + { + name: 'print.menuItemPrint', + label: '左对齐', + }, + { + name: 'print.menuItemPrint', + label: '右对齐', + }, + ], + printVertical: [ + { + name: 'print.menuItemPrint', + label: '顶部对齐', + }, + { + name: 'print.menuItemPrint', + label: '底部对齐', + }, + { + name: 'print.menuItemPrint', + label: '垂直居中', + }, + ], + locale: null, + }; + } + + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + setLocale() { + const locale = this._context.getLocale().get('print'); + this.setState({ locale }); + } + + onChange(val: string | number) { + this.setState({ + active: val, + }); + } + + onReturn() { + this.props.onCancel(); + } + + render() { + const { Button, CheckboxGroup, Collapse, Panel, Radio, RadioGroup } = this; + const { locale } = this.state.locale; + return ( + <> + {this.props.visible ? ( +
        +
        +
        {locale.printSettings}
        +
        + + +
        +
        +
        +
        {locale.preview}
        +
        + {}} children={this.state.printSheet} /> + { + console.log(value); + switch (value.name) { + case 'print.custom': + this.setState({ + paperSizeCustom: true, + }); + break; + + default: + this.setState({ + paperSizeCustom: false, + }); + break; + } + }} + children={this.state.printPaperSize} + /> + {this.state.paperSizeCustom ? ( +
        + { + console.log(value); + }} + > + { + console.log(value); + }} + > +
        + ) : null} +
        +
        {locale.pageOrientation}
        + + 11111 + 22222 + +
        + {}} children={this.state.printZoom} /> + {}} children={this.state.printMargin} /> + + + <> + {}} + > + {}} children={this.state.printPageOrder} /> +
        {locale.alignment}
        +
        + {}} children={this.state.printHorizontal} /> + {}} children={this.state.printVertical} /> +
        + +
        + + <> + {}} + > +
        {locale.headers}
        +
        转到“查看”>“冻结”可选择要在所有页面重复的行/列
        + {}} + > + +
        +
        +
        +
        +
        + ) : null} + + ); + } +} + +export { PrintMode }; diff --git a/packages/sheets-plugin-print/src/UI/PrintMode/printSelsct.tsx b/packages/sheets-plugin-print/src/UI/PrintMode/printSelsct.tsx new file mode 100644 index 0000000000..f0aec10cc1 --- /dev/null +++ b/packages/sheets-plugin-print/src/UI/PrintMode/printSelsct.tsx @@ -0,0 +1,113 @@ +import { BaseComponentSheet, BaseIconProps, BaseUlProps, Component, createRef, FunctionComponent } from '@univer/base-component'; +import { IConfig } from '../../IData'; +import styles from './index.module.less'; + +interface IProps { + title: string; + isInput?: boolean; + children: any; + onChange: (value: any) => void; + config: IConfig; +} +interface IState { + active: any; + inputShow: boolean; +} + +let time = 100; +let timeOut: ReturnType | null = null; +class PrintSelect extends Component { + NextIcon: FunctionComponent; + + Ul: FunctionComponent; + + initialize(props: IProps) { + // super(); + const component = this.props.config.context.getPluginManager().getPluginByName('ComponentSheet')!; + const render = component.getComponentRender(); + this.NextIcon = render.renderFunction('NextIcon'); + this.Ul = render.renderFunction('Ul'); + this.state = { + active: {}, + inputShow: false, + }; + } + + ref = createRef(); + + inputRef = createRef(); + + onDblClick() { + clearTimeout(timeOut!); + if (!this.props.isInput) return; + this.setState({ + inputShow: true, + }); + } + + /** + * Listen for click events + * @eventProperty + */ + handleClick(...args: any[]) { + this.hideSelect(args[0]); + const { item } = args[1]; + this.props.onChange(item); + this.setState({ + active: item, + }); + } + + // 显示子组件 + showSelect = (e: MouseEvent) => { + if (this.state.inputShow) return; + clearTimeout(timeOut!); + timeOut = setTimeout(() => { + e.stopImmediatePropagation(); + const current = this.ref.current; + if (current) current.showSelect(); + }, time); + + // 点击外部隐藏子组件 + document.addEventListener('click', this.hideSelect, true); + }; + + // 隐藏子组件 + hideSelect = (e: MouseEvent) => { + if (this.state.inputShow && this.inputRef.current.contains(e.target)) return; + const current = this.ref.current; + + if (current) { + current.hideSelect(e); + this.setState({ + inputShow: false, + }); + } + + document.removeEventListener('click', this.hideSelect, true); + }; + + componentWillMount() { + this.setState({ + active: this.props.children[0], + }); + } + + render() { + const { NextIcon, Ul } = this; + return ( +
        +
        {this.props.title}
        +
        + + {this.state.inputShow ? : {this.state.active.label}} + + +
          +
          +
          + ); + } +} + +export { PrintSelect }; diff --git a/packages/sheets-plugin-print/src/View/Event.ts b/packages/sheets-plugin-print/src/View/Event.ts new file mode 100644 index 0000000000..8c34a9ab4c --- /dev/null +++ b/packages/sheets-plugin-print/src/View/Event.ts @@ -0,0 +1 @@ +export class Event {} diff --git a/packages/sheets-plugin-print/src/View/ObserverList.ts b/packages/sheets-plugin-print/src/View/ObserverList.ts new file mode 100644 index 0000000000..a1b96aa311 --- /dev/null +++ b/packages/sheets-plugin-print/src/View/ObserverList.ts @@ -0,0 +1 @@ +export class ObserverList {} diff --git a/packages/sheets-plugin-print/src/View/Render.ts b/packages/sheets-plugin-print/src/View/Render.ts new file mode 100644 index 0000000000..faca95e82e --- /dev/null +++ b/packages/sheets-plugin-print/src/View/Render.ts @@ -0,0 +1 @@ +export class Render {} diff --git a/packages/sheets-plugin-print/src/View/index.ts b/packages/sheets-plugin-print/src/View/index.ts new file mode 100644 index 0000000000..732247fef5 --- /dev/null +++ b/packages/sheets-plugin-print/src/View/index.ts @@ -0,0 +1,3 @@ +export * from './Event'; +export * from './ObserverList'; +export * from './Render'; diff --git a/packages/sheets-plugin-print/src/index.ts b/packages/sheets-plugin-print/src/index.ts new file mode 100644 index 0000000000..60cfd78569 --- /dev/null +++ b/packages/sheets-plugin-print/src/index.ts @@ -0,0 +1,3 @@ +import { PrintPlugin } from './PrintPlugin'; + +export { PrintPlugin }; diff --git a/packages/sheets-plugin-print/src/main.tsx b/packages/sheets-plugin-print/src/main.tsx new file mode 100644 index 0000000000..2f618b4ac2 --- /dev/null +++ b/packages/sheets-plugin-print/src/main.tsx @@ -0,0 +1,67 @@ +import { UniverSheet } from '@univer/core'; +import { StyleUniverSheet } from '@univer/style-universheet'; +import { FilterPlugin } from '@univer/sheets-plugin-filter'; +import { PrintPlugin } from './PrintPlugin'; + +const defaultWorkbookData = { + locale: 'zh', + creator: '', + name: '', + skin: 'default', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; + +const defaultWorkbookDataDown = { + locale: 'en', + creator: '', + name: '', + skin: 'dark', + timeZone: '', + createdTime: '', + modifiedTime: '', + appVersion: '', + lastModifiedBy: '', + sheets: [], +}; +const uiDefaultConfigUp = { + containerId: 'universheet-demo-up', + layout: 'auto', +}; +const uiDefaultConfigDown = { + containerId: 'universheet-demo-down', + layout: { + outerLeft: false, + + outerRight: true, + + innerLeft: false, + + innerRight: false, + + toolBar: true, + + toolBarConfig: { + undoRedo: true, + font: true, + }, + contentSplit: true, + }, +}; + +const univerSheetUp = UniverSheet.newInstance(defaultWorkbookData); + +univerSheetUp.installPlugin(new StyleUniverSheet(uiDefaultConfigUp)); +univerSheetUp.installPlugin(new FilterPlugin()); +univerSheetUp.installPlugin(new PrintPlugin()); + +// const univerSheetDown = UniverSheet.newInstance(defaultWorkbookData); + +// univerSheetDown.installPlugin(new StyleUniverSheet(uiDefaultConfigDown)); + +// univerSheetDown.installPlugin(new FilterPlugin()); +// univerSheetDown.installPlugin(new PrintPlugin()); diff --git a/packages/sheets-plugin-print/src/preact.d.ts b/packages/sheets-plugin-print/src/preact.d.ts new file mode 100644 index 0000000000..0d9c32b84c --- /dev/null +++ b/packages/sheets-plugin-print/src/preact.d.ts @@ -0,0 +1 @@ +import JSX = preact.JSX; diff --git a/packages/sheets-plugin-print/tsconfig.json b/packages/sheets-plugin-print/tsconfig.json new file mode 100644 index 0000000000..fc81cbd47e --- /dev/null +++ b/packages/sheets-plugin-print/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "rootDir": "./", + "outDir": "lib", + "target": "ESNext", + + "lib": ["ESNext", "DOM", "WebWorker"], + "module": "ESNext", + "moduleResolution": "Node", //This value is needed for Node.js. + + "declaration": true, + "declarationDir": "./lib", + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "strict": true, + "allowJs": true, + + "baseUrl": "./", + "skipLibCheck": true, + "esModuleInterop": true, // auto allowSyntheticDefaultImports tru + "suppressImplicitAnyIndexErrors": true, + "experimentalDecorators": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "strictPropertyInitialization": false, + "types": ["jest", "node", "@types/jest"] + }, + "include": ["src/**/*", "test/**/*.test.(ts|tsx|js)"] +} diff --git a/packages/sheets-plugin-print/tsconfig.ref.json b/packages/sheets-plugin-print/tsconfig.ref.json new file mode 100644 index 0000000000..0c86b2c554 --- /dev/null +++ b/packages/sheets-plugin-print/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/sheets-plugin-print/tsconfig.test.json b/packages/sheets-plugin-print/tsconfig.test.json new file mode 100644 index 0000000000..a2ac02c4ae --- /dev/null +++ b/packages/sheets-plugin-print/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact" + } +} diff --git a/packages/sheets-plugin-print/vite.config.ts b/packages/sheets-plugin-print/vite.config.ts new file mode 100644 index 0000000000..f38491edbe --- /dev/null +++ b/packages/sheets-plugin-print/vite.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; +import preact from '@preact/preset-vite'; +import createExternal from 'vite-plugin-external'; +import { name, version } from './package.json'; + +const resolve = (url: string) => path.resolve(__dirname, url); + +export default defineConfig({ + build: { + lib: { + entry: resolve('src/index.ts'), + name: 'UniverSheetPluginPrint', + formats: ['es', 'umd'], + fileName: 'univer-sheets-plugin-print', + }, + outDir: './lib', + }, + define: { + pkgJson: { name, version }, + }, + css: { + modules: { + localsConvention: 'camelCaseOnly', // dash to camelCase conversion// .apply-color -> applyColor + generateScopedName: 'univer-[local]', // custom prefix class name + }, + preprocessorOptions: { + less: { + javascriptEnabled: true, + }, + }, + }, + server: { + port: 3103, + open: true, // Automatically open the app in the browser on server start. + }, + plugins: [ + preact(), + createExternal({ + externals: { + '@univer/core': '@univer/core', + '@univer/style-universheet': '@univer/style-universheet', + preact: 'preact', + }, + }), + ], +}); diff --git a/packages/sheets-plugin-protection/README-zh.md b/packages/sheets-plugin-protection/README-zh.md new file mode 100644 index 0000000000..b7fcbfe7fe --- /dev/null +++ b/packages/sheets-plugin-protection/README-zh.md @@ -0,0 +1,55 @@ +# style-universheet + +简体中文 | [English](./README.md) + +## 介绍 + +UniverSheet Protection 插件 + +### 安装 + +```bash +npm i @univer/sheets-plugin-protection +``` + +### 使用 + +```js +impport {Protection} from '@univer/sheets-plugin-protection' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Protection()); +``` + +## 本地开发 + +### 环境 + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### 安装 + +``` +pnpm install +``` + +### 开发 + +``` +# 当前目录 ./packages/sheets-plugin-protection/ +npm run dev + +# 或者项目根目录 ./ +pnpm run --filter @univer/sheets-plugin-protection dev +``` + +### 打包 + +``` +# 当前目录 ./packages/sheets-plugin-protection/ +npm run build + +# 或者根目录 ./ +pnpm run --filter @univer/sheets-plugin-protection build +``` diff --git a/packages/sheets-plugin-protection/README.md b/packages/sheets-plugin-protection/README.md new file mode 100644 index 0000000000..9599aefb0c --- /dev/null +++ b/packages/sheets-plugin-protection/README.md @@ -0,0 +1,55 @@ +# style-universheet + +English| [简体中文](./README-zh.md) + +## Introduction + +UniverSheet Plugin Protection + +### Installation + +```bash +npm i @univer/sheets-plugin-protection +``` + +### Usage + +```js +impport {Protection} from '@univer/sheets-plugin-protection' + +const univerSheet = new UniverSheet(); +univerSheet.installPlugin(new Protection()); +``` + +## Local development + +### Requirements + +- [Node.js](https://nodejs.org/en/) Version >= 10 +- [npm](https://www.npmjs.com/) Version >= 6 + +### Installation + +``` +pnpm install +``` + +### Development + +``` +# Current directory ./packages/sheets-plugin-protection/ +npm run dev + +# Or project root directory ./ +pnpm run --filter @univer/sheets-plugin-protection dev +``` + +### Package + +``` +# Current directory ./packages/sheets-plugin-protection/ +npm run build + +# Or root directory ./ +pnpm run --filter @univer/sheets-plugin-protection build +``` diff --git a/packages/sheets-plugin-protection/index.html b/packages/sheets-plugin-protection/index.html new file mode 100644 index 0000000000..dfd279c1b4 --- /dev/null +++ b/packages/sheets-plugin-protection/index.html @@ -0,0 +1,18 @@ + + + + + + + + UniverSheet + + + +
          +
          +
          + + + + \ No newline at end of file diff --git a/packages/sheets-plugin-protection/jest.config.js b/packages/sheets-plugin-protection/jest.config.js new file mode 100644 index 0000000000..73ef4bae42 --- /dev/null +++ b/packages/sheets-plugin-protection/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'], + + roots: ['/src'], + testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.tsx?$': 'babel-jest', // https://github.com/kulshekhar/ts-jest/issues/937#issuecomment-455431207 + }, + globals: { + 'ts-jest': { + tsConfig: 'tsconfig.test.json', + }, + }, +}; diff --git a/packages/sheets-plugin-protection/package.json b/packages/sheets-plugin-protection/package.json new file mode 100644 index 0000000000..6e376e7cf4 --- /dev/null +++ b/packages/sheets-plugin-protection/package.json @@ -0,0 +1,56 @@ +{ + "name": "@univer/sheets-plugin-protection", + "version": "0.0.1", + "description": "UniverSheet normal plugin-protection", + "keywords": [], + "author": "DreamNum ", + "license": "MIT", + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/Types/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "publishConfig": { + "main": "./lib/index.umd.js", + "module": "./lib/index.es.js", + "types": "./lib/index.d.ts" + }, + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "jest", + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "devDependencies": { + "@preact/preset-vite": "2.0.0", + "@testing-library/preact": "^2.0.1", + "@types/jest": "^27.5.2", + "@types/node": "^16.4.6", + "@vitejs/plugin-legacy": "^1.5.1", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.3.0", + "identity-obj-proxy": "^3.0.0", + "jest-dev-server": "^6.1.1", + "jest-environment-jsdom": "^29.0.3", + "less": "^4.1.1", + "ts-jest": "29.0.1", + "ts-node": "^10.2.1", + "typescript": "^4.4.1-rc", + "vite": "^2.4.4", + "vite-plugin-external": "1.2.3" + }, + "dependencies": { + "@univer/base-component": "workspace:^0.0.1", + "@univer/core": "workspace:^0.0.1", + "@univer/sheets-plugin-filter": "workspace:^0.0.1", + "@univer/base-sheets": "workspace:^0.0.1", + "@univer/style-universheet": "workspace:^0.0.1", + "csstype": "^3.0.11", + "preact": "^10.5.14" + } +} diff --git a/packages/sheets-plugin-protection/src/Action/AddAllowedAction.ts b/packages/sheets-plugin-protection/src/Action/AddAllowedAction.ts new file mode 100644 index 0000000000..dbf1536bd7 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Action/AddAllowedAction.ts @@ -0,0 +1,43 @@ +import { ActionBase, ActionObservers, ACTION_NAMES, CONVERTOR_OPERATION, IActionData, WorkBook, WorkSheetConvertor } from '@univer/core'; +import { Allowed } from '../Domain'; +import { AddAllowedService, RemoveAllowedService } from '../Service'; + +export interface IAddAllowedActionData extends IActionData { + allowed: Allowed; +} + +export class AddAllowedAction extends ActionBase { + constructor(actionData: IAddAllowedActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + allowed: actionData.allowed, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + AddAllowedService(worksheet, this._doActionData.allowed); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + RemoveAllowedService(worksheet, this._doActionData.allowed); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-protection/src/Action/AddUnlockAction.ts b/packages/sheets-plugin-protection/src/Action/AddUnlockAction.ts new file mode 100644 index 0000000000..9a0f7337aa --- /dev/null +++ b/packages/sheets-plugin-protection/src/Action/AddUnlockAction.ts @@ -0,0 +1,42 @@ +import { ActionBase, ActionObservers, ACTION_NAMES, CONVERTOR_OPERATION, IActionData, Range, WorkBook, WorkSheetConvertor } from '@univer/core'; +import { AddUnlockService, RemoveUnlockService } from '../Service'; + +export interface IAddUnlockActionData extends IActionData { + unlock: Range; +} + +export class AddUnlockAction extends ActionBase { + constructor(actionData: IAddUnlockActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + unlock: actionData.unlock, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + AddUnlockService(worksheet, this._doActionData.unlock); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + RemoveUnlockService(worksheet, this._doActionData.unlock); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-protection/src/Action/SetProtectionAction.ts b/packages/sheets-plugin-protection/src/Action/SetProtectionAction.ts new file mode 100644 index 0000000000..e6ab63fd41 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Action/SetProtectionAction.ts @@ -0,0 +1,46 @@ +import { ActionBase, ActionObservers, ACTION_NAMES, CONVERTOR_OPERATION, IActionData, WorkBook, WorkSheetConvertor } from '@univer/core'; +import { SetProtectionService } from '../Service'; + +export interface ISetProtectionActionData extends IActionData { + enable: boolean; + password: string; +} + +export class SetProtectionAction extends ActionBase { + protected _oldValue: [boolean, string]; + + constructor(actionData: ISetProtectionActionData, workbook: WorkBook, observers: ActionObservers) { + super(actionData, workbook, observers); + this._doActionData = { + ...actionData, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this._oldActionData = { + actionName: ACTION_NAMES.SET_FROZEN_COLUMNS_ACTION, + sheetId: actionData.sheetId, + enable: actionData.enable, + password: actionData.password, + convertor: [new WorkSheetConvertor(CONVERTOR_OPERATION.SET)], + }; + this.do(); + this.validate(); + } + + do(): void { + this.redo(); + } + + redo(): void { + const worksheet = this.getWorkSheet(); + this._oldValue = SetProtectionService(worksheet, this._doActionData.enable, this._doActionData.password); + } + + undo(): void { + const worksheet = this.getWorkSheet(); + SetProtectionService(worksheet, this._oldValue[0], this._oldValue[1]); + } + + validate(): boolean { + return false; + } +} diff --git a/packages/sheets-plugin-protection/src/Action/index.ts b/packages/sheets-plugin-protection/src/Action/index.ts new file mode 100644 index 0000000000..749907f23f --- /dev/null +++ b/packages/sheets-plugin-protection/src/Action/index.ts @@ -0,0 +1,8 @@ +/** + * en: Modify localData, conflict handling, generate data transfer protocol, submit for background execution + * + * zh: 修改localData、冲突处理、生成数据传输协议、提交后台执行 + */ +export * from './AddAllowedAction'; +export * from './AddUnlockAction'; +export * from './SetProtectionAction'; diff --git a/packages/sheets-plugin-protection/src/Base/Cache.ts b/packages/sheets-plugin-protection/src/Base/Cache.ts new file mode 100644 index 0000000000..002f98640e --- /dev/null +++ b/packages/sheets-plugin-protection/src/Base/Cache.ts @@ -0,0 +1 @@ +export class Cache {} diff --git a/packages/sheets-plugin-protection/src/Base/State.ts b/packages/sheets-plugin-protection/src/Base/State.ts new file mode 100644 index 0000000000..f277253fef --- /dev/null +++ b/packages/sheets-plugin-protection/src/Base/State.ts @@ -0,0 +1 @@ +export class State {} diff --git a/packages/sheets-plugin-protection/src/Base/index.ts b/packages/sheets-plugin-protection/src/Base/index.ts new file mode 100644 index 0000000000..6c6e3b5d96 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Base/index.ts @@ -0,0 +1,2 @@ +export * from './Cache'; +export * from './State'; diff --git a/packages/sheets-plugin-protection/src/Conflict/ConflictProtection.ts b/packages/sheets-plugin-protection/src/Conflict/ConflictProtection.ts new file mode 100644 index 0000000000..98e82f55eb --- /dev/null +++ b/packages/sheets-plugin-protection/src/Conflict/ConflictProtection.ts @@ -0,0 +1 @@ +export class ConflictProtection {} diff --git a/packages/sheets-plugin-protection/src/Conflict/IConflict.ts b/packages/sheets-plugin-protection/src/Conflict/IConflict.ts new file mode 100644 index 0000000000..02b0979966 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Conflict/IConflict.ts @@ -0,0 +1 @@ +export class IConflict {} diff --git a/packages/sheets-plugin-protection/src/Conflict/index.ts b/packages/sheets-plugin-protection/src/Conflict/index.ts new file mode 100644 index 0000000000..7c13ea5ebf --- /dev/null +++ b/packages/sheets-plugin-protection/src/Conflict/index.ts @@ -0,0 +1,7 @@ +/** + * en: Handling action conflicts + * + * zh: 处理action冲突 + */ +export * from './IConflict'; +export * from './ConflictProtection'; diff --git a/packages/sheets-plugin-protection/src/Const/PLUGIN_NAME.ts b/packages/sheets-plugin-protection/src/Const/PLUGIN_NAME.ts new file mode 100644 index 0000000000..eb632b9ba6 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Const/PLUGIN_NAME.ts @@ -0,0 +1 @@ +export const PTOTECTION_PLUGIN_NAME = 'protection'; diff --git a/packages/sheets-plugin-protection/src/Const/index.ts b/packages/sheets-plugin-protection/src/Const/index.ts new file mode 100644 index 0000000000..caeb42be51 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Const/index.ts @@ -0,0 +1 @@ +export * from './PLUGIN_NAME'; diff --git a/packages/sheets-plugin-protection/src/Domain/Allowed.ts b/packages/sheets-plugin-protection/src/Domain/Allowed.ts new file mode 100644 index 0000000000..ae6954c3dc --- /dev/null +++ b/packages/sheets-plugin-protection/src/Domain/Allowed.ts @@ -0,0 +1,20 @@ +import { Range } from '../../Domain'; + +export class Allowed { + protected _password: string; + + protected _range: Range; + + constructor(password: string, range: Range) { + this._password = password; + this._range = range; + } + + getRange(): Range { + return this._range; + } + + getPassword(): string { + return this._password; + } +} diff --git a/packages/sheets-plugin-protection/src/Domain/AllowedList.ts b/packages/sheets-plugin-protection/src/Domain/AllowedList.ts new file mode 100644 index 0000000000..ab58266d0f --- /dev/null +++ b/packages/sheets-plugin-protection/src/Domain/AllowedList.ts @@ -0,0 +1,36 @@ +import { Nullable, Rectangle } from '../../Shared'; +import { Allowed } from './Allowed'; + +export class AllowedList { + protected _allowList: Allowed[]; + + constructor() { + this._allowList = new Array(); + } + + removeAllow(allow: Allowed): void { + for (let i = 0; i < this._allowList.length; i++) { + if (this._allowList[i] === allow) { + this._allowList.splice(i, 1); + } + } + } + + addAllow(allow: Allowed): void { + this._allowList.push(allow); + } + + isLockCell(row: number, column: number): Nullable { + for (let i = 0; i < this._allowList.length; i++) { + const allowed = this._allowList[i]; + const range = allowed.getRange(); + const data = range.getRangeData(); + const rect = new Rectangle(data.startRow, data.startColumn, data.endRow, data.endColumn); + const item = new Rectangle(row, column, row, column); + if (rect.intersects(item)) { + return allowed; + } + } + return null; + } +} diff --git a/packages/sheets-plugin-protection/src/Domain/Protection.ts b/packages/sheets-plugin-protection/src/Domain/Protection.ts new file mode 100644 index 0000000000..07bb4690e2 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Domain/Protection.ts @@ -0,0 +1,337 @@ +import { Nullable, Rectangle } from '@univer/core'; +import { Allowed } from './Allowed'; +import { AllowedList } from './AllowedList'; +import { UnlockList } from './UnlockList'; + +export class Protection { + protected _unlockList: UnlockList; + + protected _allowedList: AllowedList; + + protected _useColumnFormat: boolean; + + protected _useRowFormat: boolean; + + protected _useInsertRow: boolean; + + protected _useInsertColumn: boolean; + + protected _useRemoveRow: boolean; + + protected _useRemoveColumn: boolean; + + protected _useCellFormat: boolean; + + protected _useSort: boolean; + + protected _useFilter: boolean; + + protected _useSelectLockCell: boolean; + + protected _useSelectUnlockCell: boolean; + + protected _enable: boolean; + + protected _password: string; + + constructor() { + this._unlockList = new UnlockList(); + this._allowedList = new AllowedList(); + } + + isEnable(): boolean { + return this._enable; + } + + isLockCell(row: number, column: number): Nullable { + return this._unlockList.isLockCell(row, column); + } + + isAllowCell(row: number, column: number): Nullable { + return this._allowedList.isLockCell(row, column); + } + + getUseCellFormat(): boolean { + return this._useCellFormat; + } + + getUseSort(): boolean { + return this._useSort; + } + + getUseFilter(): boolean { + return this._useRemoveColumn; + } + + getUseSelectLockCell(): boolean { + return this._useSelectLockCell; + } + + getUseSelectUnlockCell(): boolean { + return this._useSelectUnlockCell; + } + + getUseColumnFormat(): boolean { + return this._useCellFormat; + } + + getUseRowFormat(): boolean { + return this._useCellFormat; + } + + getUseInsertRow(): boolean { + return this._useCellFormat; + } + + getUseInsertColumn(): boolean { + return this._useInsertColumn; + } + + getEnable(): boolean { + return this._enable; + } + + getUseRemoveRow(): boolean { + return this._useRemoveRow; + } + + getPassword(): string { + return this._password; + } + + getAllowedList(): AllowedList { + return this._allowedList; + } + + getUnlockList(): UnlockList { + return this._unlockList; + } + + getUseRemoveColumn(): boolean { + return this._useRemoveColumn; + } + + setUseSort(): boolean { + return this._useSort; + } + + setUseFilter(): boolean { + return this._useRemoveColumn; + } + + setUseCellFormat(): boolean { + return this._useCellFormat; + } + + setEnable(enable: boolean): void { + this._enable = enable; + } + + setUseSelectLockCell(value: boolean): void { + this._useSelectLockCell = value; + } + + setUseSelectUnlockCell(value: boolean): void { + this._useSelectUnlockCell = value; + } + + setUseColumnFormat(value: boolean): void { + this._useCellFormat = value; + } + + setPassword(password: string): void { + this._password = password; + } + + setUseRowFormat(value: boolean): void { + this._useCellFormat = value; + } + + setUseInsertRow(value: boolean): void { + this._useCellFormat = value; + } + + setUseInsertColumn(value: boolean): void { + this._useInsertColumn = value; + } + + setUseRemoveRow(value: boolean): void { + this._useRemoveRow = value; + } + + setUseRemoveColumn(value: boolean): void { + this._useRemoveColumn = value; + } +} + +// import { Range } from '@univer/core'; + +// interface IConfig { +// canSelect: boolean; + +// canSetCell: boolean; +// canSetRow: boolean; +// canSetCol: boolean; + +// canInsertRow: boolean; +// canInsertcol: boolean; +// canInsertLink: boolean; + +// canDeleteRow: boolean; +// canDeleteCol: boolean; + +// canSort: boolean; +// canFilter: boolean; +// canReport: boolean; +// } + +// export class Protection { +// // 是否可编辑 +// private _canEdit: boolean; + +// private _password: string; + +// private _config: IConfig; + +// // 可编辑范围 +// private _editRange: Array; + +// // 禁止编辑范围 +// private _notEditRange: Array; + +// setConfig() {} + +// getConfig() {} + +// // return sheet or range +// getProtectionType() {} +// } + +// class Demo { +// /** +// * 将给定用户添加到受保护工作表或范围的编辑器列表中。 +// * @param { string | User | string[]} emailAddress +// */ +// addEditor(user: string | User | string[]) {} + +// /** +// * 将指定的目标受众添加为受保护范围的编辑者。 +// * @param { string } audienceId +// */ +// addTargetAudience(audienceId: string) {} + +// /** +// * 确定拥有电子表格的域中的所有用户是否都有权编辑受保护的范围或工作表。 +// */ +// canDomainEdit() {} + +// /** +// * 确定用户是否有权编辑受保护的范围或工作表。 +// */ +// canEdit() {} + +// /** +// * 获取受保护的区域或工作表的说明。 +// */ +// getDescription() {} + +// /** +// * 获取受保护区域或工作表的编辑器列表。 +// */ +// getEditors() {} + +// /** +// * 获取受保护区域的类型,范围或 SHEET。 +// */ +// getProtectionType() {} + +// /** +// * 获取受保护的范围。 +// */ +// getRange() {} + +// /** +// * 获取受保护区域的名称(如果它与命名区域相关联)。 +// */ +// getRangeName() {} + +// /** +// * 返回可以编辑受保护范围的目标访问群体的 ID。 +// */ +// getTargetAudiences() {} + +// /** +// * 获取受保护工作表中未受保护的范围的数组。 +// */ +// getUnprotectedRanges() {} + +// /** +// * 确定受保护区域是否正在使用"基于警告"的保护。 +// */ +// isWarningOnly() {} + +// /** +// * 取消保护范围或工作表。 +// */ +// remove() {} + +// /** +// * 从受保护的工作表或范围的编辑器列表中删除给定用户。 +// * @param { string | User } user +// */ +// removeEditor(user: string | User) {} + +// /** +// * 从受保护的工作表或范围的编辑器列表中删除给定的用户数组。 +// * @param { string[] } emailAddresses +// */ +// removeEditors(emailAddresses: string[]) {} + +// /** +// * 将指定的目标访问群体作为受保护范围的编辑者删除。 +// * @param { string } audienceId +// */ +// removeTargetAudience(audienceId: string) {} + +// /** +// * 设置受保护的区域或工作表的说明。 +// * @param { string } description +// */ +// setDescription(description: string) {} + +// /** +// * 设置拥有电子表格的域中的所有用户是否都有权编辑受保护的区域或工作表。 +// * @param { string } editable +// */ +// setDomainEdit(editable: string) {} + +// /** +// * 将受保护的范围与现有的命名区域相关联。 +// * @param { NamedRange } namedRange +// */ +// setNamedRange(namedRange: NamedRange) {} + +// /** +// * 调整受保护的范围。 +// * @param { Range } range +// */ +// setRange(range: Range) {} + +// /** +// * 将受保护的范围与现有的命名区域相关联。 +// * @param { string } rangeName +// */ +// setRangeName(rangeName: string) {} + +// /** +// * 取消保护受保护工作表中给定的范围数组。 +// * @param { Range[] } ranges +// */ +// setUnprotectedRanges(ranges: Range[]) {} + +// /** +// * 设置此受保护范围是否使用"基于警告"的保护。 +// * @param { Boolean } warningOnly +// */ +// setWarningOnly(warningOnly: boolean) {} +// } diff --git a/packages/sheets-plugin-protection/src/Domain/UnlockList.ts b/packages/sheets-plugin-protection/src/Domain/UnlockList.ts new file mode 100644 index 0000000000..dbfca06dea --- /dev/null +++ b/packages/sheets-plugin-protection/src/Domain/UnlockList.ts @@ -0,0 +1,34 @@ +import { Nullable, Range, Rectangle } from '@univer/core'; + +export class UnlockList { + protected _rangeList: Range[]; + + constructor() { + this._rangeList = new Array(); + } + + removeUnlock(range: Range): void { + for (let i = 0; i < this._rangeList.length; i++) { + if (this._rangeList[i] === range) { + this._rangeList.splice(i, 1); + } + } + } + + addUnlock(range: Range): void { + this._rangeList.push(range); + } + + isLockCell(row: number, column: number): Nullable { + for (let i = 0; i < this._rangeList.length; i++) { + const range = this._rangeList[i]; + const data = range.getRangeData(); + const rect = new Rectangle(data.startRow, data.startColumn, data.endRow, data.endColumn); + const item = new Rectangle(row, column, row, column); + if (rect.intersects(item)) { + return rect; + } + } + return null; + } +} diff --git a/packages/sheets-plugin-protection/src/Domain/index.ts b/packages/sheets-plugin-protection/src/Domain/index.ts new file mode 100644 index 0000000000..641c4197d9 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Domain/index.ts @@ -0,0 +1,4 @@ +export * from './Allowed'; +export * from './AllowedList'; +export * from './Protection'; +export * from './UnlockList'; diff --git a/packages/sheets-plugin-protection/src/Enum/index.ts b/packages/sheets-plugin-protection/src/Enum/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-protection/src/IData/IProtection.ts b/packages/sheets-plugin-protection/src/IData/IProtection.ts new file mode 100644 index 0000000000..79c97b9929 --- /dev/null +++ b/packages/sheets-plugin-protection/src/IData/IProtection.ts @@ -0,0 +1,8 @@ +import { Context } from '@univer/core'; + +export type IConfig = { + context: Context; +}; + +// Types for props +export type IProps = { config: IConfig }; diff --git a/packages/sheets-plugin-protection/src/IData/index.ts b/packages/sheets-plugin-protection/src/IData/index.ts new file mode 100644 index 0000000000..36d0c93d31 --- /dev/null +++ b/packages/sheets-plugin-protection/src/IData/index.ts @@ -0,0 +1 @@ +export * from './IProtection'; diff --git a/packages/sheets-plugin-protection/src/Locale/en.ts b/packages/sheets-plugin-protection/src/Locale/en.ts new file mode 100644 index 0000000000..f6a7fc9a87 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Locale/en.ts @@ -0,0 +1,10 @@ +export default { + protection: { + protectionLabel: 'Worksheet Protection', + Ok: 'OK', + Cancel: 'Cancel', + ProtectionWorksheet: 'Protection Worksheet', + AllowUsersToEdit: 'Allow users to edit', + Password: 'Please enter the password (can be left blank)', + }, +}; diff --git a/packages/sheets-plugin-protection/src/Locale/index.ts b/packages/sheets-plugin-protection/src/Locale/index.ts new file mode 100644 index 0000000000..3ac4bcd0fd --- /dev/null +++ b/packages/sheets-plugin-protection/src/Locale/index.ts @@ -0,0 +1,4 @@ +import zh from './zh'; +import en from './en'; + +export { zh, en }; diff --git a/packages/sheets-plugin-protection/src/Locale/zh.ts b/packages/sheets-plugin-protection/src/Locale/zh.ts new file mode 100644 index 0000000000..e4e693d75f --- /dev/null +++ b/packages/sheets-plugin-protection/src/Locale/zh.ts @@ -0,0 +1,10 @@ +export default { + protection: { + protectionLabel: '工作表保护', + Ok: '确定', + Cancel: '取消', + ProtectionWorksheet: '保护工作表', + AllowUsersToEdit: '允许用户编辑', + Password: '请输入密码(可留空)', + }, +}; diff --git a/packages/sheets-plugin-protection/src/ProtectionPlugin.tsx b/packages/sheets-plugin-protection/src/ProtectionPlugin.tsx new file mode 100644 index 0000000000..2585733a9f --- /dev/null +++ b/packages/sheets-plugin-protection/src/ProtectionPlugin.tsx @@ -0,0 +1,75 @@ +import { ISlotElement, ISlotProps, IToolBarItemProps } from '@univer/base-component'; +import { Context, IOCContainer, UniverSheet, Plugin, PLUGIN_NAMES } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; +import { PTOTECTION_PLUGIN_NAME } from './Const/PLUGIN_NAME'; +import { Protection } from './Domain/Protection'; +import { IConfig } from './IData/IProtection'; +import { en, zh } from './Locale'; +import { ProtectionButton } from './UI/ProtectionButton'; +import { ProtectionSide } from './UI/ProtectionSide'; + +type IPluginConfig = {}; + +export class ProtectionPlugin extends Plugin { + _protectionSilder: Protection; + + constructor(config?: IPluginConfig) { + super(PTOTECTION_PLUGIN_NAME); + } + + static create(config?: IPluginConfig) { + return new ProtectionPlugin(config); + } + + installTo(universheetInstance: UniverSheet) { + universheetInstance.installPlugin(this); + } + + initialize(): void { + const context = this.getContext(); + + /** + * load more Locale object + */ + context.getLocale().load({ + en, + zh, + }); + const config: IConfig = { + context, + }; + + const item: IToolBarItemProps = { + locale: PTOTECTION_PLUGIN_NAME, + type: ISlotElement.JSX, + show: true, + label: , + }; + context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.addButton(item); + + // TODO + this._protectionSilder = new Protection(); + + const rangeData = { + startRow: 0, + endRow: 10, + startColumn: 0, + endColumn: 10, + }; + + const siderItem: ISlotProps = { + name: 'protection', + type: ISlotElement.JSX, + label: , + }; + context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.addSider(siderItem); + } + + onMapping(IOC: IOCContainer): void {} + + onMounted(ctx: Context): void { + this.initialize(); + } + + onDestroy(): void {} +} diff --git a/packages/sheets-plugin-protection/src/ProxyData/IProxyData.ts b/packages/sheets-plugin-protection/src/ProxyData/IProxyData.ts new file mode 100644 index 0000000000..0bd8a3cf07 --- /dev/null +++ b/packages/sheets-plugin-protection/src/ProxyData/IProxyData.ts @@ -0,0 +1 @@ +export class IProxyData {} diff --git a/packages/sheets-plugin-protection/src/ProxyData/IProxyDataSet.ts b/packages/sheets-plugin-protection/src/ProxyData/IProxyDataSet.ts new file mode 100644 index 0000000000..9bf75cd768 --- /dev/null +++ b/packages/sheets-plugin-protection/src/ProxyData/IProxyDataSet.ts @@ -0,0 +1 @@ +export class IProxyDataSet {} diff --git a/packages/sheets-plugin-protection/src/ProxyData/ProxyDataProtection.ts b/packages/sheets-plugin-protection/src/ProxyData/ProxyDataProtection.ts new file mode 100644 index 0000000000..4d3d99358f --- /dev/null +++ b/packages/sheets-plugin-protection/src/ProxyData/ProxyDataProtection.ts @@ -0,0 +1 @@ +export class IProxyDataProtection {} diff --git a/packages/sheets-plugin-protection/src/ProxyData/index.ts b/packages/sheets-plugin-protection/src/ProxyData/index.ts new file mode 100644 index 0000000000..34a53ab3e8 --- /dev/null +++ b/packages/sheets-plugin-protection/src/ProxyData/index.ts @@ -0,0 +1,8 @@ +/** + * en: Transfer to background data protocol + * + * zh: 传输到后台数据协议 + */ +export * from './IProxyData'; +export * from './IProxyDataSet'; +export * from './ProxyDataProtection'; diff --git a/packages/sheets-plugin-protection/src/Service/AddAllowedService.ts b/packages/sheets-plugin-protection/src/Service/AddAllowedService.ts new file mode 100644 index 0000000000..3d3b6baa29 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Service/AddAllowedService.ts @@ -0,0 +1,7 @@ +import { WorkSheet } from '@univer/core'; +import { Allowed } from '@univer/core/src/Module/Protection'; + +export function AddAllowedService(workSheet: WorkSheet, allowed: Allowed) { + const protection = workSheet.getProtection(); + protection.getAllowedList().addAllow(allowed); +} diff --git a/packages/sheets-plugin-protection/src/Service/AddUnlockService.ts b/packages/sheets-plugin-protection/src/Service/AddUnlockService.ts new file mode 100644 index 0000000000..9a74f11d1f --- /dev/null +++ b/packages/sheets-plugin-protection/src/Service/AddUnlockService.ts @@ -0,0 +1,6 @@ +import { Range, WorkSheet } from '@univer/core'; + +export function AddUnlockService(workSheet: WorkSheet, unlock: Range) { + const protection = workSheet.getProtection(); + protection.getUnlockList().addUnlock(unlock); +} diff --git a/packages/sheets-plugin-protection/src/Service/RemoveAllowedService.ts b/packages/sheets-plugin-protection/src/Service/RemoveAllowedService.ts new file mode 100644 index 0000000000..548e8ee3f1 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Service/RemoveAllowedService.ts @@ -0,0 +1,7 @@ +import { WorkSheet } from '@univer/core'; +import { Allowed } from '@univer/core/src/Module/Protection'; + +export function RemoveAllowedService(workSheet: WorkSheet, allowed: Allowed) { + const protection = workSheet.getProtection(); + protection.getAllowedList().removeAllow(allowed); +} diff --git a/packages/sheets-plugin-protection/src/Service/RemoveUnlockService.ts b/packages/sheets-plugin-protection/src/Service/RemoveUnlockService.ts new file mode 100644 index 0000000000..eaae5e9a98 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Service/RemoveUnlockService.ts @@ -0,0 +1,6 @@ +import { Range, WorkSheet } from '@univer/core'; + +export function RemoveUnlockService(workSheet: WorkSheet, unlock: Range) { + const protection = workSheet.getProtection(); + protection.getUnlockList().removeUnlock(unlock); +} diff --git a/packages/sheets-plugin-protection/src/Service/SetProtectionService.ts b/packages/sheets-plugin-protection/src/Service/SetProtectionService.ts new file mode 100644 index 0000000000..e213d642e6 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Service/SetProtectionService.ts @@ -0,0 +1,10 @@ +import { WorkSheet } from '@univer/core'; + +export function SetProtectionService(workSheet: WorkSheet, enable: boolean, password: string): [boolean, string] { + const protection = workSheet.getProtection(); + const oldEnable = protection.getEnable(); + const oldPassword = protection.getPassword(); + protection.setPassword(password); + protection.setEnable(enable); + return [oldEnable, oldPassword]; +} diff --git a/packages/sheets-plugin-protection/src/Service/index.ts b/packages/sheets-plugin-protection/src/Service/index.ts new file mode 100644 index 0000000000..f77b53954d --- /dev/null +++ b/packages/sheets-plugin-protection/src/Service/index.ts @@ -0,0 +1,5 @@ +export * from './AddAllowedService'; +export * from './AddUnlockService'; +export * from './RemoveUnlockService'; +export * from './RemoveAllowedService'; +export * from './SetProtectionService'; diff --git a/packages/sheets-plugin-protection/src/Shared/index.ts b/packages/sheets-plugin-protection/src/Shared/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sheets-plugin-protection/src/Types/index.d.ts b/packages/sheets-plugin-protection/src/Types/index.d.ts new file mode 100644 index 0000000000..1a0684ed99 --- /dev/null +++ b/packages/sheets-plugin-protection/src/Types/index.d.ts @@ -0,0 +1,15 @@ +/// + +import JSX = preact.JSX; + +export * from '../index'; +declare module '@univer/sheets-plugin-protection' {} + +declare module 'es6-proxy-polyfill'; +// declare module '*.less'; + +// use css module +declare module '*.less' { + const resource: { [key: string]: string }; + export = resource; +} diff --git a/packages/sheets-plugin-protection/src/UI/ProtectionButton.tsx b/packages/sheets-plugin-protection/src/UI/ProtectionButton.tsx new file mode 100644 index 0000000000..e4f122b975 --- /dev/null +++ b/packages/sheets-plugin-protection/src/UI/ProtectionButton.tsx @@ -0,0 +1,96 @@ +import { BaseComponentSheet, BaseIconProps, BaseSingleButtonProps, Component, FunctionComponent, IToolBarItemProps } from '@univer/base-component'; +import { PLUGIN_NAMES, Nullable, Observer, WorkBook } from '@univer/core'; +import { SpreadsheetPlugin } from '@univer/base-sheets'; + +import { PTOTECTION_PLUGIN_NAME } from '../Const'; +import { IProps } from '../IData/IProtection'; + +// Types for state +interface IState { + protection: IToolBarItemProps; +} + +export class ProtectionButton extends Component { + private _localeObserver: Nullable>; + + SingleButton: FunctionComponent; + + LockIcon: FunctionComponent; + + initialize(props: IProps) { + // super(); + + const component = this._context.getPluginManager().getPluginByName('ComponentSheet')!; + const render = component.getComponentRender(); + this.SingleButton = render.renderFunction('SingleButton'); + this.LockIcon = render.renderFunction('LockIcon'); + + const protection: IToolBarItemProps = { + locale: 'protection', + type: 'single', + label: , + show: true, + onClick: () => { + this._context.getPluginManager().getPluginByName(PLUGIN_NAMES.SPREADSHEET)?.showSiderByName(PTOTECTION_PLUGIN_NAME, true); + }, + }; + this.state = { + protection, + }; + } + + /** + * init + */ + componentWillMount() { + this.setLocale(); + + // subscribe Locale change event + + this._localeObserver = this._context + .getObserverManager() + .getObserver('onAfterChangeUILocaleObservable', 'workbook') + ?.add(() => { + this.setLocale(); + }); + } + + /** + * destory + */ + componentWillUnmount() { + this._context.getObserverManager().getObserver('onAfterChangeUILocaleObservable', 'workbook')?.remove(this._localeObserver); + } + + /** + * set text by config setting and Locale message + */ + setLocale() { + const locale = this._context.getLocale(); + let item = this.state.protection; + // set current Locale string for tooltip + item.tooltip = locale.get(`${item.locale}.${item.locale}Label`); + this.setState({ protection: item }); + } + + /** + * Render the component's HTML + * + * @returns {void} + */ + render(props: IProps, state: IState) { + const { protection } = state; + // Set Provider for entire Container + return ( + + // + + +
          + +