diff --git a/.vscode/settings.json b/.vscode/settings.json index dfc3162f9f..85da93c723 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -101,6 +101,7 @@ "Ungroup", "unhine", "Uniscript", + "Uniui", "univer", "univerdoc", "univerjs", @@ -169,5 +170,6 @@ "toml", "less", "css" - ] + ], + "commentTranslate.multiLineMerge": true } diff --git a/e2e/disposing/disposing.spec.ts b/e2e/disposing/disposing.spec.ts index 9992982efe..8669ba0488 100644 --- a/e2e/disposing/disposing.spec.ts +++ b/e2e/disposing/disposing.spec.ts @@ -16,21 +16,6 @@ import { expect, test } from '@playwright/test'; -// The type definition is copied from: -// examples/src/plugins/debugger/controllers/e2e/e2e-memory.controller.ts -export interface IE2EControllerAPI { - loadAndRelease(id: number): Promise; - loadDefaultSheet(): Promise; - disposeUniver(): Promise; -} - -declare global { - // eslint-disable-next-line ts/naming-convention - interface Window { - E2EControllerAPI: IE2EControllerAPI; - } -} - test('no error on constructing and disposing', async ({ page }) => { let errored = false; diff --git a/examples/package.json b/examples/package.json index cda7faba2b..722975a232 100644 --- a/examples/package.json +++ b/examples/package.json @@ -56,7 +56,11 @@ "@univerjs/thread-comment": "workspace:*", "@univerjs/thread-comment-ui": "workspace:*", "@univerjs/ui": "workspace:*", + "@univerjs/uni-docs-ui": "workspace:*", "@univerjs/uni-formula": "workspace:*", + "@univerjs/uni-formula-ui": "workspace:*", + "@univerjs/uni-sheets-ui": "workspace:*", + "@univerjs/uni-slides-ui": "workspace:*", "@univerjs/uniscript": "workspace:*", "@univerjs/uniui": "workspace:*", "clsx": "^2.1.1", diff --git a/examples/src/data/docs/default-document-data-en.ts b/examples/src/data/docs/default-document-data-en.ts index c9d831057c..f4875aebde 100644 --- a/examples/src/data/docs/default-document-data-en.ts +++ b/examples/src/data/docs/default-document-data-en.ts @@ -30,7 +30,7 @@ import { import { ptToPixel } from '@univerjs/engine-render'; export const DEFAULT_DOCUMENT_DATA_EN: IDocumentData = { - id: 'd', + id: 'd-en', drawings: { shapeTest1: { unitId: 'd', diff --git a/examples/src/data/sheets/demo/default-workbook-data-demo.ts b/examples/src/data/sheets/demo/default-workbook-data-demo.ts index 270589a2e2..ffb203b329 100644 --- a/examples/src/data/sheets/demo/default-workbook-data-demo.ts +++ b/examples/src/data/sheets/demo/default-workbook-data-demo.ts @@ -23925,7 +23925,37 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { }, { name: 'SHEET_DRAWING_PLUGIN', - data: '{"sheet-0011":{"data":{"sF2ogx":{"unitId":"workbook-01","subUnitId":"sheet-0011","drawingId":"sF2ogx","drawingType":0,"imageSourceType":"BASE64","source":"data:image/jpeg;base64,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","sheetTransform":{"angle":45,"from":{"column":14,"columnOffset":49.7,"row":3,"rowOffset":9.4},"to":{"column":18,"columnOffset":49.6,"row":17,"rowOffset":15.5}},"transform":{"flipY":false,"flipX":false,"angle":45,"skewX":0,"skewY":0,"left":1117.7,"top":86.4,"width":291.9,"height":215.1}},"UPBpL-A3RvKoOqPdd8VIq":{"unitId":"workbook-01","subUnitId":"sheet-0011","drawingId":"UPBpL-A3RvKoOqPdd8VIq","drawingType":8,"componentKey":"ImageDemo","sheetTransform":{"from":{"column":2,"columnOffset":8,"row":9,"rowOffset":9},"to":{"column":4,"columnOffset":62,"row":23,"rowOffset":0}},"transform":{"flipY":false,"flipX":false,"angle":0,"skewX":0,"skewY":0,"left":200,"top":200,"width":200,"height":200},"data":{"aa":"128"}}},"order":["sF2ogx","UPBpL-A3RvKoOqPdd8VIq"]}}', + data: JSON.stringify({ + 'sheet-0011': { + data: { + sF2ogx: { + unitId: 'workbook-01', + subUnitId: 'sheet-0011', + drawingId: 'sF2ogx', + drawingType: 0, + imageSourceType: 'BASE64', + source: 'data:image/jpeg;base64,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', sheetTransform: { angle: 45, from: { column: 14, columnOffset: 49.7, row: 3, rowOffset: 9.4 }, to: { column: 18, columnOffset: 49.6, row: 17, rowOffset: 15.5 } }, transform: { flipY: false, flipX: false, angle: 45, skewX: 0, skewY: 0, left: 1117.7, top: 86.4, width: 291.9, height: 215.1 }, + }, + // 'UPBpL-A3RvKoOqPdd8VIq': { + // unitId: 'workbook-01', + // subUnitId: 'sheet-0011', + // drawingId: 'UPBpL-A3RvKoOqPdd8VIq', + // drawingType: 8, + // componentKey: 'ImageDemo', + // sheetTransform: { + // from: { column: 2, columnOffset: 8, row: 9, rowOffset: 9 }, + // to: { column: 4, columnOffset: 62, row: 23, rowOffset: 0 }, + // }, + // transform: { flipY: false, flipX: false, angle: 0, skewX: 0, skewY: 0, left: 200, top: 200, width: 200, height: 200 }, + // data: { aa: '128' }, + // }, + }, + order: [ + 'sF2ogx', + // 'UPBpL-A3RvKoOqPdd8VIq' + ], + }, + }), }, ], // namedRanges: [ diff --git a/examples/src/docs/main.ts b/examples/src/docs/main.ts index 6e777a0ee2..0f02189a76 100644 --- a/examples/src/docs/main.ts +++ b/examples/src/docs/main.ts @@ -30,7 +30,7 @@ import { UniverDocsMentionUIPlugin } from '@univerjs/docs-mention-ui'; import { DEFAULT_DOCUMENT_DATA_CN } from '../data'; import { enUS, ruRU, zhCN } from '../locales'; -// const IS_E2E: boolean = !!process.env.IS_E2E; +const IS_E2E: boolean = !!process.env.IS_E2E; // package info // eslint-disable-next-line no-console @@ -77,7 +77,9 @@ univer.registerPlugin(UniverDocsThreadCommentUIPlugin); univer.registerPlugin(UniverDocsHyperLinkUIPlugin); univer.registerPlugin(UniverDocsMentionUIPlugin); -univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_CN); +if (!IS_E2E) { + univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_CN); +} // use for console test declare global { diff --git a/examples/src/uni/lazy.ts b/examples/src/uni/lazy.ts index f609227f1b..f5129383d6 100644 --- a/examples/src/uni/lazy.ts +++ b/examples/src/uni/lazy.ts @@ -16,6 +16,7 @@ import type { Plugin, PluginCtor } from '@univerjs/core'; import { UniverSheetsFilterUIPlugin } from '@univerjs/sheets-filter-ui'; +import { UniverDocUniFormulaUIPlugin } from '@univerjs/uni-formula-ui'; // import { UniverUniscriptPlugin } from '@univerjs/uniscript'; export default function getLazyPlugins(): Array<[PluginCtor] | [PluginCtor, unknown]> { @@ -33,5 +34,6 @@ export default function getLazyPlugins(): Array<[PluginCtor] | [PluginCt // }, // ], [UniverSheetsFilterUIPlugin], + [UniverDocUniFormulaUIPlugin], ]; } diff --git a/examples/src/uni/main.ts b/examples/src/uni/main.ts index 0071709d2b..687e15e69f 100644 --- a/examples/src/uni/main.ts +++ b/examples/src/uni/main.ts @@ -15,20 +15,20 @@ */ import type { Nullable } from '@univerjs/core'; -import { LocaleType, LogLevel, Univer, UniverInstanceType } from '@univerjs/core'; +import { Injector, LocaleType, LogLevel, Univer, UniverInstanceType } from '@univerjs/core'; import { defaultTheme } from '@univerjs/design'; import { UniverDocsPlugin } from '@univerjs/docs'; -import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; +import { DocUIController, UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'; -import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { SheetUIController, UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; +import { UniSheetsUIController } from '@univerjs/uni-sheets-ui'; import { UniverUniUIPlugin } from '@univerjs/uniui'; import { UniverDebuggerPlugin } from '@univerjs/debugger'; import { FUniver } from '@univerjs/facade'; -import { UniverDrawingPlugin } from '@univerjs/drawing'; import { UniverSheetsConditionalFormattingUIPlugin } from '@univerjs/sheets-conditional-formatting-ui'; import { UniverSheetsSortUIPlugin } from '@univerjs/sheets-sort-ui'; import { UniverSheetsHyperLinkUIPlugin } from '@univerjs/sheets-hyper-link-ui'; @@ -40,15 +40,18 @@ import { UniverSheetsDrawingUIPlugin } from '@univerjs/sheets-drawing-ui'; import type { IUniverRPCMainThreadConfig } from '@univerjs/rpc'; import { UniverRPCMainThreadPlugin } from '@univerjs/rpc'; import { UniverDocUniFormulaPlugin } from '@univerjs/uni-formula'; - -import { DEFAULT_DOCUMENT_DATA_CN } from '../data'; +import { UniverDocsDrawingUIPlugin } from '@univerjs/docs-drawing-ui'; +import { UniSlidesUIController } from '@univerjs/uni-slides-ui'; +import { UniverSlidesPlugin } from '@univerjs/slides'; +import { SlidesUIController, UniverSlidesUIPlugin } from '@univerjs/slides-ui'; +import { UniDocsUIController } from '@univerjs/uni-docs-ui'; +import { DEFAULT_DOCUMENT_DATA_CN, DEFAULT_DOCUMENT_DATA_EN, DEFAULT_SLIDE_DATA, DEFAULT_WORKBOOK_DATA_DEMO, DEFAULT_WORKBOOK_DATA_DEMO1 } from '../data'; import { enUS } from '../locales'; -import { DEFAULT_WORKBOOK_DATA_DEMO } from '../data/sheets/demo/default-workbook-data-demo'; /* eslint-disable-next-line node/prefer-global/process */ const IS_E2E: boolean = !!process.env.IS_E2E; -const LOAD_LAZY_PLUGINS_TIMEOUT = 1_000; +const LOAD_LAZY_PLUGINS_TIMEOUT = 4_000; // univer const univer = new Univer({ @@ -60,36 +63,10 @@ const univer = new Univer({ logLevel: LogLevel.VERBOSE, }); -univer.registerPlugin(UniverDocsPlugin, { - hasScroll: false, -}); -univer.registerPlugin(UniverFormulaEnginePlugin, { - notExecuteFormula: true, -}); -univer.registerPlugin(UniverRenderEnginePlugin); -univer.registerPlugin(UniverUniUIPlugin, { - container: 'app', -}); - -univer.registerPlugin(UniverDrawingPlugin); - -univer.registerPlugin(UniverDocsUIPlugin); - -univer.registerPlugin(UniverSheetsPlugin); -univer.registerPlugin(UniverSheetsUIPlugin); - -univer.registerPlugin(UniverSheetsNumfmtPlugin); -univer.registerPlugin(UniverSheetsFormulaPlugin); -univer.registerPlugin(UniverRPCMainThreadPlugin, { - workerURL: './worker.js', -} as IUniverRPCMainThreadConfig); - -univer.registerPlugin(UniverSheetsFindReplacePlugin); -univer.registerPlugin(UniverSheetsHyperLinkUIPlugin); -// univer.registerPlugin(UniverSheetsDataValidationPlugin); -univer.registerPlugin(UniverSheetsSortUIPlugin); - -univer.registerPlugin(UniverDocUniFormulaPlugin); +registerBasicPlugins(univer); +registerSheetPlugins(univer); +registerSlidePlugins(univer); +registerUniPlugins(univer); const mockUser = { userID: 'Owner_qxVnhPbQ', @@ -121,19 +98,11 @@ class CustomMentionDataService implements IThreadCommentMentionDataService { } } -univer.registerPlugin(UniverThreadCommentUIPlugin, { - overrides: [[IThreadCommentMentionDataService, { useClass: CustomMentionDataService }]], -}); -univer.registerPlugin(UniverSheetsThreadCommentPlugin); -univer.registerPlugin(UniverSheetsConditionalFormattingUIPlugin); - -univer.registerPlugin(UniverSheetsDrawingUIPlugin); - -// create univer sheet instance +// create univer instances if (!IS_E2E) { univer.createUnit(UniverInstanceType.UNIVER_SHEET, DEFAULT_WORKBOOK_DATA_DEMO); - // univer.createUnit(UniverInstanceType.UNIVER_SHEET, DEFAULT_WORKBOOK_DATA_DEMO1); - univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_CN); + univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_EN); + univer.createUnit(UniverInstanceType.UNIVER_SHEET, DEFAULT_WORKBOOK_DATA_DEMO1); } // debugger plugin @@ -151,8 +120,79 @@ setTimeout(() => { import('./lazy').then((lazy) => { const plugins = lazy.default(); plugins.forEach((p) => univer.registerPlugin(p[0], p[1])); + + univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_CN); + univer.createUnit(UniverInstanceType.UNIVER_SLIDE, DEFAULT_SLIDE_DATA); }); }, LOAD_LAZY_PLUGINS_TIMEOUT); window.univer = univer; window.univerAPI = FUniver.newAPI(univer); + +function registerBasicPlugins(univer: Univer) { + univer.registerPlugin(UniverDocsPlugin, { + hasScroll: false, + }); + univer.registerPlugin(UniverFormulaEnginePlugin, { + notExecuteFormula: true, + }); + univer.registerPlugin(UniverRenderEnginePlugin); + univer.registerPlugin(UniverUniUIPlugin, { + container: 'app', + }); + + univer.registerPlugin(UniverDocsUIPlugin, { + override: [ + [DocUIController, { + useFactory: (injector) => { + injector.createInstance(UniDocsUIController, {}); + }, deps: [Injector], + }], + ], + }); + + univer.registerPlugin(UniverRPCMainThreadPlugin, { + workerURL: './worker.js', + } as IUniverRPCMainThreadConfig); + univer.registerPlugin(UniverThreadCommentUIPlugin, { + overrides: [[IThreadCommentMentionDataService, { useClass: CustomMentionDataService }]], + }); + + univer.registerPlugin(UniverDocsDrawingUIPlugin); +} + +function registerSheetPlugins(univer: Univer) { + univer.registerPlugin(UniverSheetsPlugin); + univer.registerPlugin(UniverSheetsUIPlugin, { + override: [ + [SheetUIController, { + useFactory: (injector) => { + injector.createInstance(UniSheetsUIController, {}); + }, deps: [Injector], + }], + ], + }); + univer.registerPlugin(UniverSheetsNumfmtPlugin); + univer.registerPlugin(UniverSheetsFormulaPlugin); + univer.registerPlugin(UniverSheetsFindReplacePlugin); + univer.registerPlugin(UniverSheetsHyperLinkUIPlugin); + univer.registerPlugin(UniverSheetsSortUIPlugin); + univer.registerPlugin(UniverSheetsThreadCommentPlugin); + univer.registerPlugin(UniverSheetsConditionalFormattingUIPlugin); + univer.registerPlugin(UniverSheetsDrawingUIPlugin); +} + +function registerSlidePlugins(univer: Univer) { + univer.registerPlugin(UniverSlidesPlugin); + univer.registerPlugin(UniverSlidesUIPlugin, { + override: [ + [SlidesUIController, { + useClass: UniSlidesUIController, + }], + ], + }); +} + +function registerUniPlugins(univer: Univer) { + univer.registerPlugin(UniverDocUniFormulaPlugin); +} diff --git a/examples/src/uni/worker.ts b/examples/src/uni/worker.ts index c2c0ccb551..6a5de81c28 100644 --- a/examples/src/uni/worker.ts +++ b/examples/src/uni/worker.ts @@ -14,14 +14,13 @@ * limitations under the License. */ -import { LocaleType, LogLevel, Univer } from '@univerjs/core'; +import { LocaleType, Univer } from '@univerjs/core'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRPCWorkerThreadPlugin } from '@univerjs/rpc'; import { UniverSheetsPlugin } from '@univerjs/sheets'; const univer = new Univer({ locale: LocaleType.ZH_CN, - logLevel: LogLevel.VERBOSE, }); univer.registerPlugin(UniverSheetsPlugin, { onlyRegisterFormulaRelatedMutations: true }); diff --git a/packages-experimental/uni-docs-ui/README.md b/packages-experimental/uni-docs-ui/README.md new file mode 100644 index 0000000000..57db079f8f --- /dev/null +++ b/packages-experimental/uni-docs-ui/README.md @@ -0,0 +1,16 @@ +# @univerjs/uni-docs-ui + +[![npm version](https://img.shields.io/npm/v/@univerjs/uni-sheets-ui)](https://npmjs.org/packages/@univerjs/uni-sheets-ui) +[![license](https://img.shields.io/npm/l/@univerjs/uni-sheets-ui)](https://img.shields.io/npm/l/@univerjs/uni-sheets-ui) + +## Introduction + +> TODO: Introduction + +## Usage + +### Installation + +```shell +npm i @univerjs/uni-docs-ui +``` diff --git a/packages-experimental/uni-docs-ui/package.json b/packages-experimental/uni-docs-ui/package.json new file mode 100644 index 0000000000..f68801dc5a --- /dev/null +++ b/packages-experimental/uni-docs-ui/package.json @@ -0,0 +1,98 @@ +{ + "name": "@univerjs/uni-docs-ui", + "version": "0.2.6", + "private": true, + "description": "", + "author": "DreamNum ", + "license": "Apache-2.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/univer" + }, + "homepage": "https://univer.ai", + "repository": { + "type": "git", + "url": "https://github.com/dream-num/univer" + }, + "bugs": { + "url": "https://github.com/dream-num/univer/issues" + }, + "keywords": [], + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "exports": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "lint:types": "tsc --noEmit", + "build": "tsc && vite build" + }, + "peerDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/docs": "workspace:*", + "@univerjs/docs-drawing-ui": "workspace:*", + "@univerjs/docs-ui": "workspace:*", + "@univerjs/ui": "workspace:*", + "@univerjs/uniui": "workspace:*", + "rxjs": ">=7.0.0" + }, + "dependencies": { + "@univerjs/icons": "^0.1.72" + }, + "devDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/docs": "workspace:*", + "@univerjs/docs-ui": "workspace:*", + "@univerjs/shared": "workspace:*", + "@univerjs/ui": "workspace:*", + "@univerjs/uniui": "workspace:*", + "clsx": "^2.1.1", + "rxjs": "^7.8.1", + "typescript": "^5.5.4", + "vite": "^5.3.5", + "vitest": "^2.0.5" + }, + "univerSpace": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } +} diff --git a/packages-experimental/uni-docs-ui/src/controllers/menu.ts b/packages-experimental/uni-docs-ui/src/controllers/menu.ts new file mode 100644 index 0000000000..64a7661268 --- /dev/null +++ b/packages-experimental/uni-docs-ui/src/controllers/menu.ts @@ -0,0 +1,188 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor } from '@univerjs/core'; +import { BooleanNumber, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, ICommandService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import { SetInlineFormatCommand, SetTextSelectionsOperation, TextSelectionManagerService } from '@univerjs/docs'; + +import { getMenuHiddenObservable, type IMenuButtonItem, MenuGroup, MenuItemType } from '@univerjs/ui'; +import { FONT_GROUP_MENU_ID } from '@univerjs/uniui'; +import { Observable } from 'rxjs'; + +export const DOC_ITALIC_MUTATION_ID = 'doc.command.uni-italic'; +export const DOC_BOLD_MUTATION_ID = 'doc.command.uni-bold'; +export const DOC_UNDERLINE_MUTATION_ID = 'doc.command.uni-underline'; +export const DOC_STRIKE_MUTATION_ID = 'doc.command.uni-strike'; + +export function DocBoldMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + + return { + id: DOC_BOLD_MUTATION_ID, + type: MenuItemType.BUTTON, + icon: 'BoldSingle', + title: 'Set bold', + tooltip: 'toolbar.bold', + positions: [FONT_GROUP_MENU_ID], + activated$: new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + + if (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) { + const textRun = getFontStyleAtCursor(accessor); + + if (textRun == null) { + return; + } + + const bl = textRun.ts?.bl; + + subscriber.next(bl === BooleanNumber.TRUE); + } + }); + + subscriber.next(false); + + return disposable.dispose; + }), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), + }; +} + +export function DocItalicMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + + return { + id: DOC_ITALIC_MUTATION_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON, + icon: 'ItalicSingle', + title: 'Set italic', + tooltip: 'toolbar.italic', + positions: [FONT_GROUP_MENU_ID], + activated$: new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + + if (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) { + const textRun = getFontStyleAtCursor(accessor); + + if (textRun == null) { + return; + } + + const it = textRun.ts?.it; + + subscriber.next(it === BooleanNumber.TRUE); + } + }); + + subscriber.next(false); + + return disposable.dispose; + }), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), + }; +} + +export function DocUnderlineMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + + return { + id: DOC_UNDERLINE_MUTATION_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON, + icon: 'UnderlineSingle', + title: 'Set underline', + tooltip: 'toolbar.underline', + positions: [FONT_GROUP_MENU_ID], + activated$: new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + + if (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) { + const textRun = getFontStyleAtCursor(accessor); + + if (textRun == null) { + return; + } + + const ul = textRun.ts?.ul; + + subscriber.next(ul?.s === BooleanNumber.TRUE); + } + }); + + subscriber.next(false); + + return disposable.dispose; + }), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), + }; +} + +export function DocStrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + + return { + id: DOC_STRIKE_MUTATION_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON, + icon: 'StrikethroughSingle', + title: 'Set strike through', + tooltip: 'toolbar.strikethrough', + positions: [FONT_GROUP_MENU_ID], + activated$: new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + + if (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) { + const textRun = getFontStyleAtCursor(accessor); + + if (textRun == null) { + return; + } + + const st = textRun.ts?.st; + + subscriber.next(st?.s === BooleanNumber.TRUE); + } + }); + + subscriber.next(false); + + return disposable.dispose; + }), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), + }; +} + +function getFontStyleAtCursor(accessor: IAccessor) { + const univerInstanceService = accessor.get(IUniverInstanceService); + const textSelectionService = accessor.get(TextSelectionManagerService); + + const editorDataModel = univerInstanceService.getUniverDocInstance(DOCS_NORMAL_EDITOR_UNIT_ID_KEY); + const activeTextRange = textSelectionService.getActiveTextRange(); + + if (editorDataModel == null || activeTextRange == null) return null; + + const textRuns = editorDataModel.getBody()?.textRuns; + if (textRuns == null) return; + + const { startOffset } = activeTextRange; + const textRun = textRuns.find(({ st, ed }) => startOffset! >= st && startOffset! <= ed); + return textRun; +} diff --git a/packages-experimental/uni-docs-ui/src/controllers/uni-docs-ui.controller.tsx b/packages-experimental/uni-docs-ui/src/controllers/uni-docs-ui.controller.tsx new file mode 100644 index 0000000000..a9ef66af9d --- /dev/null +++ b/packages-experimental/uni-docs-ui/src/controllers/uni-docs-ui.controller.tsx @@ -0,0 +1,91 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ICommandService, Inject, Injector, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import type { IMenuItemFactory } from '@univerjs/ui'; +import { ComponentManager, ILayoutService, IMenuService, IShortcutService, IUIPartsService } from '@univerjs/ui'; +import { BuiltinUniToolbarItemId, generateCloneMutation, UniToolbarService } from '@univerjs/uniui'; +import type { IUniverDocsUIConfig } from '@univerjs/docs-ui'; +import { DocUIController } from '@univerjs/docs-ui'; +import { SetInlineFormatBoldCommand, SetInlineFormatFontFamilyCommand, SetInlineFormatFontSizeCommand, SetInlineFormatItalicCommand, SetInlineFormatStrikethroughCommand, SetInlineFormatTextBackgroundColorCommand, SetInlineFormatTextColorCommand, SetInlineFormatUnderlineCommand } from '@univerjs/docs'; +import { IMAGE_MENU_ID as DocsImageMenuId } from '@univerjs/docs-drawing-ui'; +import { DOC_BOLD_MUTATION_ID, DOC_ITALIC_MUTATION_ID, DOC_STRIKE_MUTATION_ID, DOC_UNDERLINE_MUTATION_ID, DocBoldMenuItemFactory, DocItalicMenuItemFactory, DocStrikeThroughMenuItemFactory, DocUnderlineMenuItemFactory } from './menu'; + +@OnLifecycle(LifecycleStages.Ready, UniDocsUIController) +export class UniDocsUIController extends DocUIController { + constructor( + config: Partial, + @Inject(Injector) injector: Injector, + @Inject(ComponentManager) componentManager: ComponentManager, + @ICommandService commandService: ICommandService, + @ILayoutService layoutService: ILayoutService, + @IMenuService menuService: IMenuService, + @IUIPartsService uiPartsService: IUIPartsService, + @IUniverInstanceService univerInstanceService: IUniverInstanceService, + @IShortcutService shortcutService: IShortcutService, + @Inject(UniToolbarService) private readonly _toolbarService: UniToolbarService + + ) { + super( + config, + injector, + componentManager, + commandService, + layoutService, + menuService, + uiPartsService, + univerInstanceService, + shortcutService + ); + this._initUniMenus(); + this._initMutations(); + } + + private _initUniMenus(): void { + ([ + [BuiltinUniToolbarItemId.FONT_FAMILY, SetInlineFormatFontFamilyCommand.id], + [BuiltinUniToolbarItemId.FONT_SIZE, SetInlineFormatFontSizeCommand.id], + [BuiltinUniToolbarItemId.COLOR, SetInlineFormatTextColorCommand.id], + [BuiltinUniToolbarItemId.BACKGROUND, SetInlineFormatTextBackgroundColorCommand.id], + [BuiltinUniToolbarItemId.IMAGE, DocsImageMenuId], + ]).forEach(([id, menuId]) => { + this.disposeWithMe(this._toolbarService.implementItem(id, { id: menuId, type: UniverInstanceType.UNIVER_DOC })); + }); + + ( + [ + DocBoldMenuItemFactory, + DocItalicMenuItemFactory, + DocUnderlineMenuItemFactory, + DocStrikeThroughMenuItemFactory, + ] as IMenuItemFactory[] + ).forEach((factory) => { + this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(factory), {})); + }); + } + + private _initMutations() { + [ + generateCloneMutation(DOC_BOLD_MUTATION_ID, SetInlineFormatBoldCommand), + generateCloneMutation(DOC_ITALIC_MUTATION_ID, SetInlineFormatItalicCommand), + generateCloneMutation(DOC_UNDERLINE_MUTATION_ID, SetInlineFormatUnderlineCommand), + generateCloneMutation(DOC_STRIKE_MUTATION_ID, SetInlineFormatStrikethroughCommand), + ].forEach((mutation) => { + this.disposeWithMe(this._commandService.registerCommand(mutation)); + }); + } +} + diff --git a/packages-experimental/uni-docs-ui/src/index.ts b/packages-experimental/uni-docs-ui/src/index.ts new file mode 100644 index 0000000000..b82418afb0 --- /dev/null +++ b/packages-experimental/uni-docs-ui/src/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { UniDocsUIController } from './controllers/uni-docs-ui.controller'; diff --git a/packages-experimental/uni-docs-ui/src/vite-env.d.ts b/packages-experimental/uni-docs-ui/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages-experimental/uni-docs-ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages-experimental/uni-docs-ui/tsconfig.json b/packages-experimental/uni-docs-ui/tsconfig.json new file mode 100644 index 0000000000..e8df90d34b --- /dev/null +++ b/packages-experimental/uni-docs-ui/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@univerjs/shared/tsconfigs/base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "references": [ + { + "path": "./tsconfig.node.json" + } + ], + "include": [ + "src" + ] +} diff --git a/packages-experimental/uni-docs-ui/tsconfig.node.json b/packages-experimental/uni-docs-ui/tsconfig.node.json new file mode 100644 index 0000000000..e53dac8868 --- /dev/null +++ b/packages-experimental/uni-docs-ui/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": "@univerjs/shared/tsconfigs/node", + "include": ["vite.config.ts"] +} diff --git a/packages-experimental/uni-docs-ui/vite.config.ts b/packages-experimental/uni-docs-ui/vite.config.ts new file mode 100644 index 0000000000..925b530b4d --- /dev/null +++ b/packages-experimental/uni-docs-ui/vite.config.ts @@ -0,0 +1,12 @@ +import createViteConfig from '@univerjs/shared/vite'; +import pkg from './package.json'; + +export default ({ mode }) => createViteConfig({}, { + mode, + pkg, + features: { + react: false, + css: true, + dom: true, + }, +}); diff --git a/packages-experimental/uni-formula-ui/package.json b/packages-experimental/uni-formula-ui/package.json index 12ab8ec569..a17b2552f8 100644 --- a/packages-experimental/uni-formula-ui/package.json +++ b/packages-experimental/uni-formula-ui/package.json @@ -1,6 +1,6 @@ { "name": "@univerjs/uni-formula-ui", - "version": "0.0.1", + "version": "0.2.6", "private": true, "description": "", "author": "DreamNum ", @@ -20,7 +20,8 @@ "keywords": [], "exports": { ".": "./src/index.ts", - "./*": "./src/*" + "./*": "./src/*", + "./locale/*": "./src/locale/*.ts" }, "main": "./lib/cjs/index.js", "module": "./lib/es/index.js", @@ -40,7 +41,8 @@ "require": "./lib/cjs/*", "types": "./lib/types/index.d.ts" }, - "./lib/*": "./lib/*" + "./lib/*": "./lib/*", + "./locale/*": "./lib/locale/*.json" } }, "directories": { @@ -58,12 +60,27 @@ }, "peerDependencies": { "@univerjs/core": "workspace:*", - "@univerjs/uni-formula": "workspace:*" + "@univerjs/docs": "workspace:*", + "@univerjs/docs-ui": "workspace:*", + "@univerjs/icons": "^0.1.72", + "@univerjs/sheets-formula": "workspace:*", + "@univerjs/ui": "workspace:*", + "@univerjs/uni-formula": "workspace:*", + "clsx": ">=2.0.0", + "react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "rxjs": ">=7.0.0" }, "devDependencies": { "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/docs": "workspace:*", + "@univerjs/docs-ui": "workspace:*", + "@univerjs/icons": "^0.1.72", "@univerjs/shared": "workspace:*", + "@univerjs/sheets-formula": "workspace:*", + "@univerjs/ui": "workspace:*", "@univerjs/uni-formula": "workspace:*", + "clsx": "^2.1.1", "rxjs": "^7.8.1", "typescript": "^5.5.4", "vite": "^5.3.5", @@ -80,6 +97,7 @@ "require": "./lib/cjs/*", "types": "./lib/types/index.d.ts" }, - "./lib/*": "./lib/*" + "./lib/*": "./lib/*", + "./locale/*": "./lib/locale/*.json" } } diff --git a/packages-experimental/uni-formula/src/commands/command.ts b/packages-experimental/uni-formula-ui/src/commands/command.ts similarity index 66% rename from packages-experimental/uni-formula/src/commands/command.ts rename to packages-experimental/uni-formula-ui/src/commands/command.ts index 9b940a3979..f7fc0a908a 100644 --- a/packages-experimental/uni-formula/src/commands/command.ts +++ b/packages-experimental/uni-formula-ui/src/commands/command.ts @@ -14,27 +14,45 @@ * limitations under the License. */ -import { CommandType, CustomRangeType, generateRandomId, type ICommand, ICommandService, type IMutationInfo, sequenceExecute } from '@univerjs/core'; -import { addCustomRangeBySelectionFactory } from '@univerjs/docs'; - -import type { IAddDocUniFormulaMutationParams, IRemoveDocUniFormulaMutationParams, IUpdateDocUniFormulaMutationParams } from './mutation'; -import { AddDocUniFormulaMutation, RemoveDocUniFormulaMutation, UpdateDocUniFormulaMutation } from './mutation'; +import type { ICommand, IDocumentBody, IMutationInfo } from '@univerjs/core'; +import { CommandType, CustomRangeType, generateRandomId, ICommandService, LocaleService, makeCustomRangeStream, sequenceExecute } from '@univerjs/core'; +import { makeSelection, replaceSelectionFactory } from '@univerjs/docs'; +import type { IAddDocUniFormulaMutationParams, IRemoveDocUniFormulaMutationParams, IUpdateDocUniFormulaMutationParams } from '@univerjs/uni-formula'; +import { AddDocUniFormulaMutation, RemoveDocUniFormulaMutation, UpdateDocUniFormulaMutation } from '@univerjs/uni-formula'; export interface IAddDocUniFormulaCommandParams { unitId: string; f: string; + startIndex: number; } export const AddDocUniFormulaCommand: ICommand = { type: CommandType.COMMAND, id: 'doc.command.add-uni-formula', async handler(accessor, params: IAddDocUniFormulaCommandParams) { - const { f, unitId } = params; + const { f, unitId, startIndex } = params; + const commandService = accessor.get(ICommandService); + const localeService = accessor.get(LocaleService); + const rangeId = generateRandomId(); - const redoMutation = addCustomRangeBySelectionFactory(accessor, { - rangeId, - rangeType: CustomRangeType.CUSTOM, + const placeholder = localeService.t('uni-formula.command.stream-placeholder'); + const dataStream = makeCustomRangeStream(placeholder); + const body: IDocumentBody = { + dataStream, + customRanges: [{ + startIndex: 0, + endIndex: dataStream.length - 1, + rangeId, + rangeType: CustomRangeType.UNI_FORMULA, + wholeEntity: true, + }], + }; + + const redoMutation = replaceSelectionFactory(accessor, { + unitId, + body, + selection: makeSelection(startIndex, startIndex + 1), }); if (redoMutation) { @@ -60,7 +78,7 @@ export interface IUpdateDocUniFormulaCommandParams { export const UpdateDocUniFormulaCommand: ICommand = { type: CommandType.COMMAND, - id: 'doc.command.add-uni-formula', + id: 'doc.command.update-uni-formula', handler: (accessor, params: IUpdateDocUniFormulaCommandParams) => { const commandService = accessor.get(ICommandService); return commandService.syncExecuteCommand(UpdateDocUniFormulaMutation.id, params as IUpdateDocUniFormulaMutationParams); @@ -77,7 +95,7 @@ export const RemoveDocUniFormulaCommand: ICommand { const commandService = accessor.get(ICommandService); - // TODO: maybed remove formula under selection? + // TODO: maybe remove formula under selection? return commandService.syncExecuteCommand(RemoveDocUniFormulaMutation.id, params as IRemoveDocUniFormulaMutationParams); }, }; diff --git a/packages-experimental/uni-formula-ui/src/commands/operation.ts b/packages-experimental/uni-formula-ui/src/commands/operation.ts new file mode 100644 index 0000000000..aa91b6c4f7 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/commands/operation.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICommand, IOperation } from '@univerjs/core'; +import { CommandType } from '@univerjs/core'; +import { DocFormulaPopupService } from '../services/formula-popup.service'; + +export interface IShowFormulaPopupOperationParams { + unitId: string; + startIndex: number; + + type?: 'new' | 'existing'; + rangeId?: string; +} + +export const ShowFormulaPopupOperation: IOperation = { + id: 'doc.operation.show-formula-popup', + type: CommandType.OPERATION, + handler(accessor, params: IShowFormulaPopupOperationParams) { + const { type = 'new', startIndex, unitId, rangeId } = params; + const docFormulaPopupService = accessor.get(DocFormulaPopupService); + + if (type === 'existing' && !rangeId) { + return false; + } + + return docFormulaPopupService.showPopup(unitId, startIndex, type, rangeId); + }, +}; + +export const CloseFormulaPopupOperation: IOperation = { + id: 'doc.operation.close-formula-popup', + type: CommandType.OPERATION, + handler(accessor) { + const docFormulaPopupService = accessor.get(DocFormulaPopupService); + return docFormulaPopupService.closePopup(true); + }, +}; + +export const ConfirmFormulaPopupCommand: ICommand = { + id: 'doc.operation.confirm-formula-popup', + type: CommandType.COMMAND, + handler(accessor) { + const docFormulaPopupService = accessor.get(DocFormulaPopupService); + return docFormulaPopupService.confirmPopup(); + }, +}; diff --git a/packages-experimental/uni-formula-ui/src/controllers/formula-input.controller.ts b/packages-experimental/uni-formula-ui/src/controllers/formula-input.controller.ts new file mode 100644 index 0000000000..0244f5fb01 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/controllers/formula-input.controller.ts @@ -0,0 +1,165 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CustomRangeType, Disposable, ICommandService, ILogService, Inject, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import type { IInsertCommandParams } from '@univerjs/docs'; +import { DeleteLeftCommand, InsertCommand, MoveCursorOperation, TextSelectionManagerService } from '@univerjs/docs'; +import { ComponentManager, IEditorService } from '@univerjs/ui'; +import { DocHoverManagerService } from '@univerjs/docs-ui'; + +import { AddDocUniFormulaCommand, RemoveDocUniFormulaCommand, UpdateDocUniFormulaCommand } from '../commands/command'; +import type { IShowFormulaPopupOperationParams } from '../commands/operation'; +import { CloseFormulaPopupOperation, ConfirmFormulaPopupCommand, ShowFormulaPopupOperation } from '../commands/operation'; +import { DocFormulaPopup, DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY } from '../views/components/DocFormulaPopup'; +import { DocFormulaPopupService } from '../services/formula-popup.service'; + +const FORMULA_INPUT_TRIGGER_CHAR = '='; + +@OnLifecycle(LifecycleStages.Steady, DocUniFormulaController) +export class DocUniFormulaController extends Disposable { + constructor( + @ICommandService private readonly _commandService: ICommandService, + @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, + @IEditorService private readonly _editorService: IEditorService, + @ILogService private readonly _logService: ILogService, + @Inject(DocHoverManagerService) private readonly _docHoverManagerService: DocHoverManagerService, + @Inject(DocFormulaPopupService) private readonly _docFormulaPopupService: DocFormulaPopupService, + @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, + @Inject(ComponentManager) private readonly _componentManager: ComponentManager + ) { + super(); + + this._initKeyboardListeners(); + this._initComponents(); + this._initCommands(); + this._initHoverListener(); + } + + private _initCommands(): void { + [ + ShowFormulaPopupOperation, + CloseFormulaPopupOperation, + ConfirmFormulaPopupCommand, + AddDocUniFormulaCommand, + RemoveDocUniFormulaCommand, + UpdateDocUniFormulaCommand, + ].forEach((command) => this._commandService.registerCommand(command)); + } + + private _initComponents(): void { + this.disposeWithMe(this._componentManager.register(DocFormulaPopup.componentKey, DocFormulaPopup)); + } + + private _initKeyboardListeners(): void { + // The formula input trigger works not exactly the same as Mention. + this.disposeWithMe(this._commandService.onCommandExecuted((commandInfo) => { + const currentEditor = this._editorService.getFocusEditor(); + const focusedUnit = this._instanceSrv.getFocusedUnit(); + + const { id } = commandInfo; + + if ( + currentEditor?.editorUnitId === DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY || + focusedUnit?.type !== UniverInstanceType.UNIVER_DOC + ) { + return; + } + + if (id === InsertCommand.id) { + const params = commandInfo.params as IInsertCommandParams; + const activeRange = this._textSelectionManagerService.getActiveTextRange(); + if (params.body.dataStream === FORMULA_INPUT_TRIGGER_CHAR && activeRange) { + this._showPopup({ + startIndex: activeRange.startOffset! - 1, + unitId: focusedUnit.getUnitId(), + }); + } else if (this._docFormulaPopupService.popupInfo) { + this._closePopup(); + } + } + + if (id === MoveCursorOperation.id || id === DeleteLeftCommand.id) { + this._closePopup(); + } + })); + } + + private _initHoverListener(): void { + this.disposeWithMe(this._docHoverManagerService.activeCustomRanges$.subscribe((customRanges) => { + const focusedUnit = this._instanceSrv.getFocusedUnit(); + + if ( + !focusedUnit || + this._docFormulaPopupService.popupInfo?.type === 'new' || + this._docFormulaPopupService.popupLocked + ) { + return; + } + + const formulaCustomRange = customRanges.find((range) => range.rangeType === CustomRangeType.UNI_FORMULA); + if (formulaCustomRange) { + const { startIndex, rangeId } = formulaCustomRange; + this._logService.debug('[DocUniFormulaController]: activeCustomRanges', customRanges); + this._showPopup({ + startIndex, + unitId: focusedUnit.getUnitId(), + rangeId, + type: 'existing', + }); + } else { + if (!this._hovered) { + this._closePopup(500); + } + } + })); + + this.disposeWithMe(this._docFormulaPopupService.popupHovered$.subscribe((hovered) => { + if (hovered) { + this._removeTimer(); + } + + this._hovered = hovered; + })); + } + + private _hovered = false; + + private _removeTimer(): void { + if (this._closePopupTimer !== null) { + window.clearTimeout(this._closePopupTimer); + this._closePopupTimer = null; + } + } + + private _showPopup(params: IShowFormulaPopupOperationParams): void { + this._removeTimer(); + this._commandService.executeCommand(ShowFormulaPopupOperation.id, params); + } + + private _closePopupTimer: number | null = null; + private _closePopup(timeout: number = 0): void { + if (!this._docFormulaPopupService.popupInfo) { + return; + } + + if (timeout === 0) { + this._commandService.executeCommand(CloseFormulaPopupOperation.id); + } else { + this._closePopupTimer = window.setTimeout(() => this._closePopup(0), timeout); + } + } +} + diff --git a/packages-experimental/uni-formula-ui/src/locale/en-US.ts b/packages-experimental/uni-formula-ui/src/locale/en-US.ts new file mode 100644 index 0000000000..3f0e266a59 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/locale/en-US.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type zhCN from './zh-CN'; + +const locale: typeof zhCN = { + 'uni-formula': { + popup: { + title: { + new: 'Create new formula', + existing: 'Edit formula', + }, + placeholder: 'Click here to insert formula or continue typing to close...', + button: { + confirm: 'Confirm', + cancel: 'Cancel', + }, + }, + command: { + 'stream-placeholder': 'Doc Formula', + }, + }, +}; + +export default locale; diff --git a/packages-experimental/uni-formula-ui/src/locale/ru-RU.ts b/packages-experimental/uni-formula-ui/src/locale/ru-RU.ts new file mode 100644 index 0000000000..6856c0ffe1 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/locale/ru-RU.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type zhCN from './zh-CN'; + +const locale: typeof zhCN = { + 'uni-formula': { + popup: { + title: { + new: 'Create new formula', + existing: 'Edit formula', + }, + placeholder: 'Click here to insert formula or continue typing to close...', + button: { + confirm: 'Confirm', + cancel: 'Cancel', + }, + }, + command: { + 'stream-placeholder': 'Doc Formula', + }, + }, +}; + +export default locale; + diff --git a/packages-experimental/uni-formula-ui/src/locale/vi-VN.ts b/packages-experimental/uni-formula-ui/src/locale/vi-VN.ts new file mode 100644 index 0000000000..6856c0ffe1 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/locale/vi-VN.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type zhCN from './zh-CN'; + +const locale: typeof zhCN = { + 'uni-formula': { + popup: { + title: { + new: 'Create new formula', + existing: 'Edit formula', + }, + placeholder: 'Click here to insert formula or continue typing to close...', + button: { + confirm: 'Confirm', + cancel: 'Cancel', + }, + }, + command: { + 'stream-placeholder': 'Doc Formula', + }, + }, +}; + +export default locale; + diff --git a/packages-experimental/uni-formula-ui/src/locale/zh-CN.ts b/packages-experimental/uni-formula-ui/src/locale/zh-CN.ts new file mode 100644 index 0000000000..20e223fab7 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/locale/zh-CN.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const locale = { + 'uni-formula': { + popup: { + title: { + new: '添加公式', + existing: '编辑公式', + }, + placeholder: '点击此处添加公式,或继续输入以关闭', + button: { + confirm: '确认', + cancel: '取消', + }, + }, + command: { + 'stream-placeholder': '文档公式', + }, + }, +}; + +export default locale; diff --git a/packages-experimental/uni-formula-ui/src/locale/zh-TW.ts b/packages-experimental/uni-formula-ui/src/locale/zh-TW.ts new file mode 100644 index 0000000000..6856c0ffe1 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/locale/zh-TW.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type zhCN from './zh-CN'; + +const locale: typeof zhCN = { + 'uni-formula': { + popup: { + title: { + new: 'Create new formula', + existing: 'Edit formula', + }, + placeholder: 'Click here to insert formula or continue typing to close...', + button: { + confirm: 'Confirm', + cancel: 'Cancel', + }, + }, + command: { + 'stream-placeholder': 'Doc Formula', + }, + }, +}; + +export default locale; + diff --git a/packages-experimental/uni-formula-ui/src/services/formula-popup.service.ts b/packages-experimental/uni-formula-ui/src/services/formula-popup.service.ts new file mode 100644 index 0000000000..67ac92afb2 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/services/formula-popup.service.ts @@ -0,0 +1,165 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IDisposable, Nullable } from '@univerjs/core'; +import { Disposable, FORMULA_EDITOR_ACTIVATED, ICommandService, IContextService, ILogService, Inject } from '@univerjs/core'; +import { makeSelection } from '@univerjs/docs'; +import { DocCanvasPopManagerService } from '@univerjs/docs-ui'; +import { BehaviorSubject, Subject } from 'rxjs'; +import type { IShortcutItem } from '@univerjs/ui'; +import { IShortcutService, KeyCode } from '@univerjs/ui'; +import { FORMULA_PROMPT_ACTIVATED } from '@univerjs/sheets-formula'; +import { UniFormulaService } from '@univerjs/uni-formula'; +import type { IAddDocUniFormulaCommandParams } from '../commands/command'; +import { AddDocUniFormulaCommand } from '../commands/command'; +import { ConfirmFormulaPopupCommand } from '../commands/operation'; + +export const DOC_FORMULA_POPUP_KEY = 'DOC_FORMULA_POPUP' as const; + +export interface IDocFormulaPopupInfo { + unitId: string; + + /** If the popup is for inserting a formula or inspecting an existing formula. */ + type: 'new' | 'existing'; + + f: Nullable; + + disposable: IDisposable; + + startIndex: number; +} + +export class DocFormulaPopupService extends Disposable { + private readonly _popupInfo$ = new BehaviorSubject>(null); + readonly popupInfo$ = this._popupInfo$.asObservable(); + get popupInfo(): Nullable { return this._popupInfo$.getValue(); } + + private _popupLocked = false; + get popupLocked(): boolean { return this._popupLocked; } + + private readonly _popupHovered$ = new Subject(); + readonly popupHovered$ = this._popupHovered$.asObservable(); + + private _cachedFormulaString = ''; + + constructor( + @Inject(DocCanvasPopManagerService) private readonly _docCanvasPopupManagerService: DocCanvasPopManagerService, + @Inject(UniFormulaService) private readonly _uniFormulaService: UniFormulaService, + @IContextService private readonly _contextService: IContextService, + @ILogService private readonly _logService: ILogService, + @ICommandService private readonly _commandService: ICommandService, + @IShortcutService private readonly _shortcutService: IShortcutService + ) { + super(); + + const UniFormulaConfirmShortcut: IShortcutItem = { + id: ConfirmFormulaPopupCommand.id, + binding: KeyCode.ENTER, + description: 'shortcut.doc.confirm-formula-popup', + preconditions: (contextService) => + !contextService.getContextValue(FORMULA_PROMPT_ACTIVATED) && this.canConfirmPopup(), + priority: 10000, + }; + + this.disposeWithMe(this._shortcutService.registerShortcut(UniFormulaConfirmShortcut)); + } + + override dispose(): void { + super.dispose(); + + this._popupInfo$.next(null); + this._popupInfo$.complete(); + + this._popupHovered$.complete(); + } + + cacheFormulaString(f: string): void { + this._cachedFormulaString = f; + } + + hoverPopup(hovered: boolean): void { + this._popupHovered$.next(hovered); + } + + showPopup(unitId: string, startIndex: number, type: 'new'): boolean; + showPopup(unitId: string, startIndex: number, type: 'existing', rangeId: string): boolean; + showPopup(unitId: string, startIndex: number, type: 'new' | 'existing', rangeId?: string): boolean; + showPopup(unitId: string, startIndex: number, type: 'new' | 'existing', rangeId?: string): boolean { + this.closePopup(); + + const f = (rangeId && type === 'existing') + ? this._uniFormulaService.getFormulaWithRangeId(unitId, rangeId)?.f ?? '=' + : '='; + const disposable = this._docCanvasPopupManagerService.attachPopupToRange(makeSelection(startIndex), { + componentKey: DOC_FORMULA_POPUP_KEY, + onClickOutside: () => this.closePopup(), // user may update ref range selections + direction: 'top', + }); + + this._popupInfo$.next({ unitId, disposable, type, f, startIndex }); + return true; + } + + lockPopup(): void { + this._popupLocked = true; + this._contextService.setContextValue(FORMULA_EDITOR_ACTIVATED, true); + } + + canConfirmPopup(): boolean { + return this._cachedFormulaString !== ''; + } + + async confirmPopup(): Promise { + const info = this.popupInfo; + if (!info) return true; + + const f = this._cachedFormulaString; + if (!f) { + this._logService.warn('[FormulaPopupService]: cannot write empty formula into the field.'); + return false; + } + + this.unlockPopup(); + this.closePopup(); + + // write this formula string to doc + return this._commandService.executeCommand(AddDocUniFormulaCommand.id, { + unitId: info.unitId, + f, + startIndex: info.startIndex, + } as IAddDocUniFormulaCommandParams); + } + + unlockPopup(): void { + this._popupLocked = false; + } + + closePopup(force = false): boolean { + if (this._popupLocked && !force) return false; + + this._popupLocked = false; + this._cachedFormulaString = ''; + + this.popupInfo?.disposable.dispose(); + this._popupInfo$.next(null); + this._popupHovered$.next(false); + + this._contextService.setContextValue(FORMULA_EDITOR_ACTIVATED, false); + + return true; + } +} + diff --git a/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts b/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts index afbc25c69c..3f1cefccec 100644 --- a/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts +++ b/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts @@ -14,14 +14,17 @@ * limitations under the License. */ +import type { Dependency } from '@univerjs/core'; import { DependentOn, Inject, Injector, Plugin, UniverInstanceType } from '@univerjs/core'; import { UniverDocUniFormulaPlugin } from '@univerjs/uni-formula'; import { DOC_FORMULA_UI_PLUGIN_NAME } from './const'; +import { DocFormulaPopupService } from './services/formula-popup.service'; +import { DocUniFormulaController } from './controllers/formula-input.controller'; @DependentOn(UniverDocUniFormulaPlugin) export class UniverDocUniFormulaUIPlugin extends Plugin { static override pluginName: string = DOC_FORMULA_UI_PLUGIN_NAME; - static override type: UniverInstanceType = UniverInstanceType.UNIVER_DOC; + static override type: UniverInstanceType = UniverInstanceType.UNIVER_SHEET; constructor( _config: unknown, @@ -29,4 +32,11 @@ export class UniverDocUniFormulaUIPlugin extends Plugin { ) { super(); } + + override onStarting(injector: Injector): void { + ([ + [DocUniFormulaController], + [DocFormulaPopupService], + ] as Dependency[]).forEach((d) => injector.add(d)); + } } diff --git a/packages-experimental/uni-formula-ui/src/views/components/DocFormulaPopup.tsx b/packages-experimental/uni-formula-ui/src/views/components/DocFormulaPopup.tsx new file mode 100644 index 0000000000..89ade0d894 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/views/components/DocFormulaPopup.tsx @@ -0,0 +1,157 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import clsx from 'clsx'; +import { TextEditor, useObservable } from '@univerjs/ui'; +import type { IDocumentData, Nullable } from '@univerjs/core'; +import { BooleanNumber, createInternalEditorID, DEFAULT_EMPTY_DOCUMENT_VALUE, DocumentFlavor, HorizontalAlign, ICommandService, LocaleService, useDependency, VerticalAlign, WrapStrategy } from '@univerjs/core'; +import { CheckMarkSingle, CloseSingle } from '@univerjs/icons'; +import type { IDocFormulaPopupInfo } from '../../services/formula-popup.service'; +import { DOC_FORMULA_POPUP_KEY, DocFormulaPopupService } from '../../services/formula-popup.service'; + +import { CloseFormulaPopupOperation, ConfirmFormulaPopupCommand } from '../../commands/operation'; +import styles from './index.module.less'; + +export const DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY = createInternalEditorID('UNI_FORMULA'); +function makeSnapshot(f: string): IDocumentData { + return { + id: DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY, + body: { + dataStream: `${f}${DEFAULT_EMPTY_DOCUMENT_VALUE}`, + textRuns: [], + paragraphs: [ + { + startIndex: 0, + }, + ], + }, + documentStyle: { + documentFlavor: DocumentFlavor.MODERN, + marginTop: 5, + marginBottom: 5, + marginRight: 0, + marginLeft: 0, + paragraphLineGapDefault: 0, + pageSize: { + width: Number.POSITIVE_INFINITY, + height: Number.POSITIVE_INFINITY, + }, + renderConfig: { + horizontalAlign: HorizontalAlign.UNSPECIFIED, + verticalAlign: VerticalAlign.TOP, + centerAngle: 0, + vertexAngle: 0, + wrapStrategy: WrapStrategy.OVERFLOW, + isRenderStyle: BooleanNumber.FALSE, + }, + }, + }; +} + +export function DocFormulaPopup() { + const docFormulaPopupService = useDependency(DocFormulaPopupService); + const popupInfo = useObservable(docFormulaPopupService.popupInfo$); + + if (!popupInfo) { + return null; + } + + return ; +} + +DocFormulaPopup.componentKey = DOC_FORMULA_POPUP_KEY; + +function DocFormula(props: { popupInfo: IDocFormulaPopupInfo }) { + const { popupInfo } = props; + const { f } = popupInfo; + + const localeService = useDependency(LocaleService); + const formulaPopupService = useDependency(DocFormulaPopupService); + const commandService = useDependency(ICommandService); + + const [formulaString, setFormulaString] = useState>(f); + const snapshotRef = useRef(makeSnapshot(f ?? '')); + + const [focused, setFocused] = useState(false); + + const onFormulaStringChange = useCallback((formulaString: string) => { + setFormulaString(formulaString); + formulaPopupService.cacheFormulaString(formulaString); + }, [formulaPopupService]); + + const onConfirm = useCallback(() => { + // TODO: call operation instead + commandService.executeCommand(ConfirmFormulaPopupCommand.id); + }, [commandService]); + + const onHovered = useCallback((hovered: boolean) => { + formulaPopupService.hoverPopup(hovered); + }, [formulaPopupService]); + + const onCancel = useCallback(() => { + commandService.executeCommand(CloseFormulaPopupOperation.id); + }, [commandService]); + + const handleEscKey = useCallback((event: { key: string }) => { + if (event.key === 'Escape') { + onCancel(); + } + }, [onCancel]); + + useEffect(() => { + document.addEventListener('keydown', handleEscKey); + return () => { + document.removeEventListener('keydown', handleEscKey); + }; + }, [handleEscKey]); + + return ( +
onHovered(true)} onMouseLeave={() => onHovered(false)}> + onFormulaStringChange(str ?? '')} + onFocus={() => { + formulaPopupService.lockPopup(); + setFocused(true); + }} + onBlur={() => setFocused(false)} + /> +
+ + + + + + + +
+
+ ); +} diff --git a/packages-experimental/uni-formula-ui/src/views/components/index.module.less b/packages-experimental/uni-formula-ui/src/views/components/index.module.less new file mode 100644 index 0000000000..899d2526fd --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/views/components/index.module.less @@ -0,0 +1,67 @@ +.doc-ui-formula-popup { + overflow: hidden; + + width: 482px; + display: flex; + + border-radius: 10px; + border: 1px solid var(--, #ecedef); + background: #fff; + height: 48px; + padding: 8px; + align-items: center; + box-sizing: border-box; + gap: 8px; + box-shadow: 0px 2px 4px 0px rgba(30, 34, 43, 0.06); + + &-title { + font-size: var(--font-size-xs); + } + + &-editor { + position: relative; + width: 100%; + height: 100%; + color: var(---Hyacinth-Blue-500, #274fee); + font-size: 13px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 153.846% */ + + user-select: none; + transition: all 0.15s; + border-radius: var(--radius-m, 6px); + border: 1px solid var(---Hyacinth-Blue-500, #274fee); + background: #f0f3fe; + box-shadow: 0px 0px 4px 0px rgba(39, 79, 238, 0.4); + + // &-activated { + // border-color: rgb(var(--primary-color)); + // } + } + + &-button-grp { + margin-top: var(--margin-xs); + text-align: right; + text-wrap: nowrap; + + button:not(:last-of-type) { + margin-right: var(--margin-xxs); + } + } + + .formula-icon { + display: flex; + } + .icon-container { + display: flex; + padding: var(--radius-m, 6px); + align-items: center; + gap: 10px; + border-radius: 8px; + &:hover { + background: var(--hyacinth-blue-5007, rgba(39, 79, 238, 0.07)); + color: #274fee; + } + } +} diff --git a/packages-experimental/uni-formula/src/controllers/doc-formula-input.controller.ts b/packages-experimental/uni-formula-ui/src/views/shapes/doc-formula.shape.ts similarity index 93% rename from packages-experimental/uni-formula/src/controllers/doc-formula-input.controller.ts rename to packages-experimental/uni-formula-ui/src/views/shapes/doc-formula.shape.ts index a3d6e36270..cf732b1e13 100644 --- a/packages-experimental/uni-formula/src/controllers/doc-formula-input.controller.ts +++ b/packages-experimental/uni-formula-ui/src/views/shapes/doc-formula.shape.ts @@ -14,7 +14,3 @@ * limitations under the License. */ -export class DocFormulaInputController { - -} - diff --git a/packages-experimental/uni-formula-ui/vite.config.ts b/packages-experimental/uni-formula-ui/vite.config.ts index 67b2fff8f2..925b530b4d 100644 --- a/packages-experimental/uni-formula-ui/vite.config.ts +++ b/packages-experimental/uni-formula-ui/vite.config.ts @@ -4,4 +4,9 @@ import pkg from './package.json'; export default ({ mode }) => createViteConfig({}, { mode, pkg, + features: { + react: false, + css: true, + dom: true, + }, }); diff --git a/packages-experimental/uni-formula/package.json b/packages-experimental/uni-formula/package.json index 062e08ae45..6002d0812d 100644 --- a/packages-experimental/uni-formula/package.json +++ b/packages-experimental/uni-formula/package.json @@ -1,6 +1,6 @@ { "name": "@univerjs/uni-formula", - "version": "0.0.1", + "version": "0.2.6", "private": true, "description": "", "author": "DreamNum ", @@ -61,6 +61,7 @@ }, "peerDependencies": { "@univerjs/core": "workspace:*", + "@univerjs/docs": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/rpc": "workspace:*", "@univerjs/sheets-formula": "workspace:*" diff --git a/packages-experimental/uni-formula/src/commands/mutation.ts b/packages-experimental/uni-formula/src/commands/mutation.ts index 9c33882260..0034edfd06 100644 --- a/packages-experimental/uni-formula/src/commands/mutation.ts +++ b/packages-experimental/uni-formula/src/commands/mutation.ts @@ -30,9 +30,9 @@ export interface IAddDocUniFormulaMutationParams { export const AddDocUniFormulaMutation: IMutation = { type: CommandType.MUTATION, id: 'doc.mutation.add-doc-uni-formula', - handler(accesstor, params: IAddDocUniFormulaMutationParams) { + handler(accessor, params: IAddDocUniFormulaMutationParams) { const { unitId, f, rangeId: id } = params; - const uniFormulaService = accesstor.get(UniFormulaService); + const uniFormulaService = accessor.get(UniFormulaService); uniFormulaService.registerDocFormula(unitId, id, f); diff --git a/packages-experimental/uni-formula/src/index.ts b/packages-experimental/uni-formula/src/index.ts index b21bca0091..cefdb3cc95 100644 --- a/packages-experimental/uni-formula/src/index.ts +++ b/packages-experimental/uni-formula/src/index.ts @@ -15,4 +15,18 @@ */ export { UniverDocUniFormulaPlugin } from './uni-formula.plugin'; +export { UniFormulaService, type IUpdateDocUniFormulaCacheMutationParams } from './services/uni-formula.service'; export { DOC_FORMULA_PLUGIN_NAME } from './const'; + +// #region - all commands + +export { + type IAddDocUniFormulaMutationParams, + type IRemoveDocUniFormulaMutationParams, + type IUpdateDocUniFormulaMutationParams, + AddDocUniFormulaMutation, + RemoveDocUniFormulaMutation, + UpdateDocUniFormulaMutation, +} from './commands/mutation'; + +// #endregion diff --git a/packages-experimental/uni-formula/src/services/uni-formula.service.ts b/packages-experimental/uni-formula/src/services/uni-formula.service.ts index 01d899f13c..35c4a58318 100644 --- a/packages-experimental/uni-formula/src/services/uni-formula.service.ts +++ b/packages-experimental/uni-formula/src/services/uni-formula.service.ts @@ -14,13 +14,30 @@ * limitations under the License. */ -import type { ICellData, IDisposable, IMutation, Nullable } from '@univerjs/core'; -import { CommandType, Disposable, ICommandService, Inject, IUniverInstanceService, LifecycleService, LifecycleStages, OnLifecycle, Optional, RCDisposable, ResourceManagerService, toDisposable, UniverInstanceType } from '@univerjs/core'; import { DataSyncPrimaryController } from '@univerjs/rpc'; import { RegisterOtherFormulaService } from '@univerjs/sheets-formula'; +import type { DocumentDataModel, ICellData, IDisposable, IDocumentBody, IMutation, Nullable } from '@univerjs/core'; +import { + CommandType, + CustomRangeType, + Disposable, + ICommandService, + Inject, + IResourceManagerService, + IUniverInstanceService, + LifecycleStages, + makeCustomRangeStream, + OnLifecycle, + Optional, + RCDisposable, + toDisposable, + UniverInstanceType, +} from '@univerjs/core'; +import { makeSelection, replaceSelectionFactory } from '@univerjs/docs'; import { type IDocFormulaCache, type IDocFormulaData, type IDocFormulaReference, toJson } from '../models/doc-formula'; import { DOC_FORMULA_PLUGIN_NAME } from '../const'; +import { AddDocUniFormulaMutation, RemoveDocUniFormulaMutation, UpdateDocUniFormulaMutation } from '../commands/mutation'; const DOC_PSEUDO_SUBUNIT = 'DOC_PSEUDO_SUBUNIT'; @@ -28,18 +45,66 @@ const DOC_PSEUDO_SUBUNIT = 'DOC_PSEUDO_SUBUNIT'; * Update calculating result in a batch. */ export interface IUpdateDocUniFormulaCacheMutationParams { + /** The doc in which formula results changed. */ unitId: string; + /** Range ids. */ ids: string[]; + /** Calculation results. */ cache: IDocFormulaCache[]; } -export const UpdateDocUniFormulaCacheMutation: IMutation = { +/** This mutation is internal. It should not be exposed to third-party developers. */ +const UpdateDocUniFormulaCacheMutation: IMutation = { type: CommandType.MUTATION, id: 'doc.mutation.update-doc-uni-formula-cache', handler(accessor, params: IUpdateDocUniFormulaCacheMutationParams) { const { unitId, ids, cache } = params; + const uniFormulaService = accessor.get(UniFormulaService); - return uniFormulaService.updateFormulaResults(unitId, ids, cache); + const instanceService = accessor.get(IUniverInstanceService); + const commandService = accessor.get(ICommandService); + + const data = instanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC); + + /** The document may have not loaded on this client. We are safe to ignore cache updating. */ + if (!data) return true; + const body = data.getBody(); + + function getRange(rangeId: string) { + return body?.customRanges?.find((r) => r.rangeId === rangeId); + } + + const saveCacheResult = uniFormulaService.updateFormulaResults(unitId, ids, cache); + if (!saveCacheResult) return false; + + return ids.every((id, index) => { + const range = getRange(id); + if (!range) return true; // If we cannot find that rangeId, we are save to ignore cache. + + const dataStream = makeCustomRangeStream(`${cache[index].v ?? ''}`); + const body: IDocumentBody = { + dataStream, + customRanges: [{ + startIndex: 0, + endIndex: dataStream.length - 1, + rangeId: id, + rangeType: CustomRangeType.UNI_FORMULA, + wholeEntity: true, + }], + }; + + const redoMutation = replaceSelectionFactory(accessor, { + unitId, + body, + selection: makeSelection(range.startIndex, range.endIndex), + }); + + if (redoMutation) { + return commandService.syncExecuteCommand(redoMutation.id, redoMutation.params, { onlyLocal: true }); + } + + return false; + }); }, }; @@ -47,15 +112,14 @@ export const UpdateDocUniFormulaCacheMutation: IMutation(); private readonly _formulaIdToKey = new Map(); constructor( - @Inject(ResourceManagerService) resoureManagerService: ResourceManagerService, - @Inject(LifecycleService) private readonly _lifecycleService: LifecycleService, + @IResourceManagerService resourceManagerService: IResourceManagerService, // TODO: wzhudev We may need to register the doc formulas to formula service in later lifecycle stages. @ICommandService private readonly _commandSrv: ICommandService, @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, @@ -64,11 +128,9 @@ export class UniFormulaService extends Disposable { ) { super(); - this.disposeWithMe(this._commandSrv.registerCommand(UpdateDocUniFormulaCacheMutation)); - - this._initDocFormulaResources(resoureManagerService); + this._initCommands(); + this._initDocFormulaResources(resourceManagerService); this._initDocDisposingListener(); - this._debugInitFormula(); } override dispose(): void { @@ -77,16 +139,29 @@ export class UniFormulaService extends Disposable { // TODO: wzhudev: concrete disposing methods } + private _initCommands(): void { + [ + AddDocUniFormulaMutation, + UpdateDocUniFormulaMutation, + UpdateDocUniFormulaCacheMutation, + RemoveDocUniFormulaMutation, + ].forEach((command) => this._commandSrv.registerCommand(command)); + } + /** * Remove all formulas under a doc. */ private _unregisterDoc(unitId: string): void { - const existingsFormulas = Array.from(this._docFormulas.entries()); - existingsFormulas.forEach(([_, value]) => { + const existingFormulas = Array.from(this._docFormulas.entries()); + existingFormulas.forEach(([_, value]) => { if (value.unitId === unitId) this.unregisterDocFormula(unitId, value.rangeId); }); } + getFormulaWithRangeId(unitId: string, rangeId: string): Nullable { + return this._docFormulas.get(getDocFormulaKey(unitId, rangeId)) ?? null; + } + /** * Register a doc formula into the formula system. */ @@ -128,15 +203,16 @@ export class UniFormulaService extends Disposable { } updateFormulaResults(unitId: string, formulaIds: string[], v: IDocFormulaCache[]): boolean { - // TODO: @wzhudev: should trigger re-render - return formulaIds.every((id, index) => { + formulaIds.forEach((id, index) => { const formulaData = this._docFormulas.get(getDocFormulaKey(unitId, id)); - if (!formulaData) return false; + if (!formulaData) return true; formulaData.v = v[index].v; formulaData.t = v[index].t; return true; }); + + return true; } private _dataSyncDisposables = new Map(); @@ -200,11 +276,7 @@ export class UniFormulaService extends Disposable { } } - private _debugInitFormula(): void { - this.registerDocFormula('test_doc', 'test_formula_id', '=\'[workbook-01]工作表11\'!A13'); - } - - private _initDocFormulaResources(resourceManagerService: ResourceManagerService): void { + private _initDocFormulaResources(resourceManagerService: IResourceManagerService): void { resourceManagerService.registerPluginResource({ pluginName: DOC_FORMULA_PLUGIN_NAME, businesses: [UniverInstanceType.UNIVER_DOC], diff --git a/packages-experimental/uni-formula/src/uni-formula.plugin.ts b/packages-experimental/uni-formula/src/uni-formula.plugin.ts index 2ac347555f..221fa8661e 100644 --- a/packages-experimental/uni-formula/src/uni-formula.plugin.ts +++ b/packages-experimental/uni-formula/src/uni-formula.plugin.ts @@ -16,13 +16,16 @@ import { DependentOn, Inject, Injector, Plugin, UniverInstanceType } from '@univerjs/core'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; +import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; import { UniFormulaService } from './services/uni-formula.service'; import { DOC_FORMULA_PLUGIN_NAME } from './const'; -@DependentOn(UniverFormulaEnginePlugin) +@DependentOn(UniverFormulaEnginePlugin, UniverSheetsFormulaPlugin) export class UniverDocUniFormulaPlugin extends Plugin { static override pluginName: string = DOC_FORMULA_PLUGIN_NAME; - static override type: UniverInstanceType = UniverInstanceType.UNIVER_DOC; + + // This plugin should load only when sheet related modules are loaded. + static override type: UniverInstanceType = UniverInstanceType.UNIVER_SHEET; constructor( _config: unknown, diff --git a/packages-experimental/uni-sheets-ui/README.md b/packages-experimental/uni-sheets-ui/README.md new file mode 100644 index 0000000000..52972216df --- /dev/null +++ b/packages-experimental/uni-sheets-ui/README.md @@ -0,0 +1,16 @@ +# @univerjs/uni-sheets-ui + +[![npm version](https://img.shields.io/npm/v/@univerjs/uni-sheets-ui)](https://npmjs.org/packages/@univerjs/uni-sheets-ui) +[![license](https://img.shields.io/npm/l/@univerjs/uni-sheets-ui)](https://img.shields.io/npm/l/@univerjs/uni-sheets-ui) + +## Introduction + +> TODO: Introduction + +## Usage + +### Installation + +```shell +npm i @univerjs/uni-sheets-ui +``` diff --git a/packages-experimental/uni-sheets-ui/package.json b/packages-experimental/uni-sheets-ui/package.json new file mode 100644 index 0000000000..2b20b7fcb8 --- /dev/null +++ b/packages-experimental/uni-sheets-ui/package.json @@ -0,0 +1,103 @@ +{ + "name": "@univerjs/uni-sheets-ui", + "version": "0.2.6", + "private": true, + "description": "", + "author": "DreamNum ", + "license": "Apache-2.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/univer" + }, + "homepage": "https://univer.ai", + "repository": { + "type": "git", + "url": "https://github.com/dream-num/univer" + }, + "bugs": { + "url": "https://github.com/dream-num/univer/issues" + }, + "keywords": [], + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "exports": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "lint:types": "tsc --noEmit", + "build": "tsc && vite build" + }, + "peerDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/docs": "workspace:*", + "@univerjs/sheets": "workspace:*", + "@univerjs/sheets-drawing-ui": "workspace:*", + "@univerjs/sheets-ui": "workspace:*", + "@univerjs/ui": "workspace:*", + "@univerjs/uniui": "workspace:*", + "react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "rxjs": ">=7.0.0" + }, + "dependencies": { + "@univerjs/icons": "^0.1.72" + }, + "devDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/docs": "workspace:*", + "@univerjs/docs-ui": "workspace:*", + "@univerjs/shared": "workspace:*", + "@univerjs/sheets": "workspace:*", + "@univerjs/sheets-ui": "workspace:*", + "@univerjs/ui": "workspace:*", + "@univerjs/uniui": "workspace:*", + "clsx": "^2.1.1", + "rxjs": "^7.8.1", + "typescript": "^5.5.3", + "vite": "^5.3.4", + "vitest": "^2.0.3" + }, + "univerSpace": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } +} diff --git a/packages-experimental/uni-sheets-ui/src/controllers/menu.ts b/packages-experimental/uni-sheets-ui/src/controllers/menu.ts new file mode 100644 index 0000000000..6ef3b44ae4 --- /dev/null +++ b/packages-experimental/uni-sheets-ui/src/controllers/menu.ts @@ -0,0 +1,282 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor } from '@univerjs/core'; +import { BooleanNumber, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, EDITOR_ACTIVATED, FOCUSING_SHEET, FontItalic, FontWeight, ICommandService, IContextService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import { SetInlineFormatCommand, SetTextSelectionsOperation, TextSelectionManagerService } from '@univerjs/docs'; +import { RangeProtectionPermissionEditPoint, SetRangeValuesMutation, SetSelectionsOperation, SetWorksheetActiveOperation, SheetsSelectionsService, WorkbookEditablePermission, WorksheetEditPermission, WorksheetSetCellStylePermission } from '@univerjs/sheets'; +import { deriveStateFromActiveSheet$, getCurrentRangeDisable$ } from '@univerjs/sheets-ui'; +import { getMenuHiddenObservable, type IMenuButtonItem, MenuGroup, MenuItemType } from '@univerjs/ui'; +import { FONT_GROUP_MENU_ID } from '@univerjs/uniui'; +import { Observable } from 'rxjs'; + +export const SHEET_BOLD_MUTATION_ID = 'sheet.command.uni-bold'; +export const SHEET_ITALIC_MUTATION_ID = 'sheet.command.uni-italic'; +export const SHEET_UNDERLINE_MUTATION_ID = 'sheet.command.uni-underline'; +export const SHEET_STRIKE_MUTATION_ID = 'sheet.command.uni-strike'; + +export function SheetBoldMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + const univerInstanceService = accessor.get(IUniverInstanceService); + const contextService = accessor.get(IContextService); + const selectionManagerService = accessor.get(SheetsSelectionsService); + + return { + id: SHEET_BOLD_MUTATION_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON, + icon: 'BoldSingle', + title: 'Set bold', + tooltip: 'toolbar.bold', + positions: [FONT_GROUP_MENU_ID], + disabled$: getCurrentRangeDisable$(accessor, { workbookTypes: [WorkbookEditablePermission], worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission], rangeTypes: [RangeProtectionPermissionEditPoint] }), + activated$: deriveStateFromActiveSheet$(univerInstanceService, false, ({ worksheet }) => new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + if (id === SetRangeValuesMutation.id || id === SetSelectionsOperation.id || id === SetWorksheetActiveOperation.id) { + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + let isBold = FontWeight.NORMAL; + + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + isBold = range?.getFontWeight(); + } + + subscriber.next(isBold === FontWeight.BOLD); + } + + if ( + (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) && + contextService.getContextValue(EDITOR_ACTIVATED) && + contextService.getContextValue(FOCUSING_SHEET) + ) { + const textRun = getFontStyleAtCursor(accessor); + if (textRun == null) { + return; + } + + const bl = textRun.ts?.bl; + subscriber.next(bl === BooleanNumber.TRUE); + } + }); + + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + if (!worksheet) { + subscriber.next(false); + return; + } + + let isBold = FontWeight.NORMAL; + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + isBold = range?.getFontWeight(); + } + subscriber.next(isBold === FontWeight.BOLD); + + return disposable.dispose; + })), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + }; +} + +export function SheetItalicMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + const univerInstanceService = accessor.get(IUniverInstanceService); + const selectionManagerService = accessor.get(SheetsSelectionsService); + const contextService = accessor.get(IContextService); + + return { + id: SHEET_ITALIC_MUTATION_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON, + icon: 'ItalicSingle', + title: 'Set italic', + tooltip: 'toolbar.italic', + positions: [FONT_GROUP_MENU_ID], + disabled$: getCurrentRangeDisable$(accessor, { workbookTypes: [WorkbookEditablePermission], worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission], rangeTypes: [RangeProtectionPermissionEditPoint] }), + activated$: deriveStateFromActiveSheet$(univerInstanceService, false, ({ worksheet }) => new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + if (id === SetRangeValuesMutation.id || id === SetSelectionsOperation.id || id === SetWorksheetActiveOperation.id) { + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + let isItalic = FontItalic.NORMAL; + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + isItalic = range?.getFontStyle(); + } + + subscriber.next(isItalic === FontItalic.ITALIC); + } + + if ( + (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) && + contextService.getContextValue(EDITOR_ACTIVATED) && + contextService.getContextValue(FOCUSING_SHEET) + ) { + const textRun = getFontStyleAtCursor(accessor); + if (textRun == null) return; + + const it = textRun.ts?.it; + subscriber.next(it === BooleanNumber.TRUE); + } + }); + + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + let isItalic = FontItalic.NORMAL; + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + isItalic = range?.getFontStyle(); + } + + subscriber.next(isItalic === FontItalic.ITALIC); + return disposable.dispose; + })), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + }; +} + +export function SheetUnderlineMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + const univerInstanceService = accessor.get(IUniverInstanceService); + const selectionManagerService = accessor.get(SheetsSelectionsService); + const contextService = accessor.get(IContextService); + + return { + id: SHEET_UNDERLINE_MUTATION_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON, + icon: 'UnderlineSingle', + title: 'Set underline', + tooltip: 'toolbar.underline', + positions: [FONT_GROUP_MENU_ID], + activated$: deriveStateFromActiveSheet$(univerInstanceService, false, ({ worksheet }) => new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + if (id === SetRangeValuesMutation.id || id === SetSelectionsOperation.id || id === SetWorksheetActiveOperation.id) { + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + let isUnderline; + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + isUnderline = range?.getUnderline(); + } + + subscriber.next(!!(isUnderline && isUnderline.s)); + } + + if ( + (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) && + contextService.getContextValue(EDITOR_ACTIVATED) && + contextService.getContextValue(FOCUSING_SHEET) + ) { + const textRun = getFontStyleAtCursor(accessor); + if (textRun == null) return; + + const ul = textRun.ts?.ul; + subscriber.next(ul?.s === BooleanNumber.TRUE); + } + }); + + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + let isUnderline; + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + isUnderline = range?.getUnderline(); + } + + subscriber.next(!!(isUnderline && isUnderline.s)); + return disposable.dispose; + })), + disabled$: getCurrentRangeDisable$(accessor, { workbookTypes: [WorkbookEditablePermission], worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission], rangeTypes: [RangeProtectionPermissionEditPoint] }), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + }; +} + +export function SheetStrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const commandService = accessor.get(ICommandService); + const univerInstanceService = accessor.get(IUniverInstanceService); + const selectionManagerService = accessor.get(SheetsSelectionsService); + const contextService = accessor.get(IContextService); + + return { + id: SHEET_STRIKE_MUTATION_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON, + icon: 'StrikethroughSingle', + title: 'Set strike through', + tooltip: 'toolbar.strikethrough', + positions: [FONT_GROUP_MENU_ID], + disabled$: getCurrentRangeDisable$(accessor, { workbookTypes: [WorkbookEditablePermission], worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission], rangeTypes: [RangeProtectionPermissionEditPoint] }), + activated$: deriveStateFromActiveSheet$(univerInstanceService, false, ({ worksheet }) => new Observable((subscriber) => { + const disposable = commandService.onCommandExecuted((c) => { + const id = c.id; + if (id === SetRangeValuesMutation.id || id === SetSelectionsOperation.id || id === SetWorksheetActiveOperation.id) { + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + let st; + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + st = range?.getStrikeThrough(); + } + + subscriber.next(!!(st && st.s)); + } + + if ( + (id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) && + contextService.getContextValue(EDITOR_ACTIVATED) && + contextService.getContextValue(FOCUSING_SHEET) + ) { + const textRun = getFontStyleAtCursor(accessor); + + if (textRun == null) { + return; + } + + const st = textRun.ts?.st; + + subscriber.next(st?.s === BooleanNumber.TRUE); + } + }); + + const primary = selectionManagerService.getCurrentLastSelection()?.primary; + let st; + if (primary != null) { + const range = worksheet.getRange(primary.startRow, primary.startColumn); + st = range?.getStrikeThrough(); + } + + subscriber.next(!!(st && st.s)); + return disposable.dispose; + })), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + }; +} + +function getFontStyleAtCursor(accessor: IAccessor) { + const univerInstanceService = accessor.get(IUniverInstanceService); + const textSelectionService = accessor.get(TextSelectionManagerService); + + const editorDataModel = univerInstanceService.getUniverDocInstance(DOCS_NORMAL_EDITOR_UNIT_ID_KEY); + const activeTextRange = textSelectionService.getActiveRectRange(); + + if (editorDataModel == null || activeTextRange == null) return null; + + const textRuns = editorDataModel.getBody()?.textRuns; + if (textRuns == null) return; + + const { startOffset } = activeTextRange; + const textRun = textRuns.find(({ st, ed }) => startOffset! >= st && startOffset! <= ed); + return textRun; +} diff --git a/packages-experimental/uni-sheets-ui/src/controllers/uni-sheets-ui.controller.tsx b/packages-experimental/uni-sheets-ui/src/controllers/uni-sheets-ui.controller.tsx new file mode 100644 index 0000000000..f0ab6702c7 --- /dev/null +++ b/packages-experimental/uni-sheets-ui/src/controllers/uni-sheets-ui.controller.tsx @@ -0,0 +1,110 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Workbook } from '@univerjs/core'; +import { connectInjector, ICommandService, Inject, Injector, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType, useDependency } from '@univerjs/core'; +import { type IUniverSheetsUIConfig, RenderSheetContent, SetRangeBoldCommand, SetRangeFontFamilyCommand, SetRangeFontSizeCommand, SetRangeItalicCommand, SetRangeStrickThroughCommand, SetRangeTextColorCommand, SetRangeUnderlineCommand, SheetUIController } from '@univerjs/sheets-ui'; +import type { IMenuItemFactory } from '@univerjs/ui'; +import { BuiltInUIPart, ComponentManager, ILayoutService, IMenuService, IShortcutService, IUIPartsService, useObservable } from '@univerjs/ui'; +import { BuiltinUniToolbarItemId, generateCloneMutation, UniToolbarService, UniUIPart } from '@univerjs/uniui'; +import React from 'react'; +import { SetBackgroundColorCommand } from '@univerjs/sheets'; +import { IMAGE_MENU_ID as SheetsImageMenuId } from '@univerjs/sheets-drawing-ui'; +import { UniSheetBar } from '../views/uni-sheet-bar/UniSheetBar'; +import { SHEET_BOLD_MUTATION_ID, SHEET_ITALIC_MUTATION_ID, SHEET_STRIKE_MUTATION_ID, SHEET_UNDERLINE_MUTATION_ID, SheetBoldMenuItemFactory, SheetItalicMenuItemFactory, SheetStrikeThroughMenuItemFactory, SheetUnderlineMenuItemFactory } from './menu'; + +@OnLifecycle(LifecycleStages.Ready, SheetUIController) +export class UniSheetsUIController extends SheetUIController { + constructor( + config: Partial, + @Inject(Injector) injector: Injector, + @Inject(ComponentManager) componentManager: ComponentManager, + @ILayoutService layoutService: ILayoutService, + @ICommandService commandService: ICommandService, + @IShortcutService shortcutService: IShortcutService, + @IMenuService menuService: IMenuService, + @IUIPartsService uiPartsService: IUIPartsService, + @Inject(UniToolbarService) private readonly _toolbarService: UniToolbarService + ) { + super( + config, + injector, + componentManager, + layoutService, + commandService, + shortcutService, + menuService, + uiPartsService + ); + this._initUniMenus(); + this._initMutations(); + } + + protected override _initWorkbenchParts(): void { + const uiController = this._uiPartsService; + const injector = this._injector; + + // this.disposeWithMe(uiController.registerComponent(BuiltInUIPart.HEADER, () => connectInjector(RenderSheetHeader, injector))); + this.disposeWithMe(uiController.registerComponent(UniUIPart.OUTLINE, () => connectInjector(RenderOutline, injector))); + this.disposeWithMe(uiController.registerComponent(BuiltInUIPart.CONTENT, () => connectInjector(RenderSheetContent, injector))); + } + + private _initUniMenus(): void { + ([ + [BuiltinUniToolbarItemId.FONT_FAMILY, SetRangeFontFamilyCommand.id], + [BuiltinUniToolbarItemId.FONT_SIZE, SetRangeFontSizeCommand.id], + [BuiltinUniToolbarItemId.COLOR, SetRangeTextColorCommand.id], + [BuiltinUniToolbarItemId.BACKGROUND, SetBackgroundColorCommand.id], + [BuiltinUniToolbarItemId.IMAGE, SheetsImageMenuId], + ]).forEach(([id, menuId]) => { + this._toolbarService.implementItem(id, { id: menuId, type: UniverInstanceType.UNIVER_SHEET }); + }); + + ( + [ + SheetBoldMenuItemFactory, + SheetItalicMenuItemFactory, + SheetUnderlineMenuItemFactory, + SheetStrikeThroughMenuItemFactory, + ] as IMenuItemFactory[] + ).forEach((factory) => { + this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(factory), {})); + }); + } + + private _initMutations() { + [ + generateCloneMutation(SHEET_BOLD_MUTATION_ID, SetRangeBoldCommand), + generateCloneMutation(SHEET_ITALIC_MUTATION_ID, SetRangeItalicCommand), + generateCloneMutation(SHEET_UNDERLINE_MUTATION_ID, SetRangeUnderlineCommand), + generateCloneMutation(SHEET_STRIKE_MUTATION_ID, SetRangeStrickThroughCommand), + ].forEach((mutation) => { + this.disposeWithMe(this._commandService.registerCommand(mutation)); + }); + } +} + +function RenderOutline() { + const univerInstanceService = useDependency(IUniverInstanceService); + const focused = useObservable(univerInstanceService.focused$); + const workbook = useObservable(() => univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET), null, false, []); + if (!workbook || focused !== workbook?.getUnitId()) return null; + + return ( + + ); +} + diff --git a/packages-experimental/uni-sheets-ui/src/index.ts b/packages-experimental/uni-sheets-ui/src/index.ts new file mode 100644 index 0000000000..db4a083b41 --- /dev/null +++ b/packages-experimental/uni-sheets-ui/src/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { UniSheetsUIController } from './controllers/uni-sheets-ui.controller'; diff --git a/packages-experimental/uni-sheets-ui/src/views/uni-sheet-bar/UniSheetBar.tsx b/packages-experimental/uni-sheets-ui/src/views/uni-sheet-bar/UniSheetBar.tsx new file mode 100644 index 0000000000..5bd5a3230e --- /dev/null +++ b/packages-experimental/uni-sheets-ui/src/views/uni-sheet-bar/UniSheetBar.tsx @@ -0,0 +1,127 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IBaseSheetBarProps } from '@univerjs/sheets-ui'; +import { useActiveWorkbook } from '@univerjs/sheets-ui'; +import { IncreaseSingle } from '@univerjs/icons'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import type { ICommandInfo } from '@univerjs/core'; +import { ICommandService, useDependency } from '@univerjs/core'; +import { InsertSheetCommand, InsertSheetMutation, RemoveSheetMutation, SetTabColorMutation, SetWorksheetActiveOperation, SetWorksheetHideMutation, SetWorksheetNameMutation, SetWorksheetOrderMutation } from '@univerjs/sheets'; +import { Tooltip } from '@univerjs/design'; +import styles from './index.module.less'; + +export function UniSheetBar() { + const [sheetList, setSheetList] = useState([]); + const [activeKey, setActiveKey] = useState(''); + const [barHeight, setBarHeight] = useState(0); + const workbook = useActiveWorkbook()!; + const commandService = useDependency(ICommandService); + const sheetBarRef = useRef(null); + + useEffect(() => { + const sheetBar = sheetBarRef.current; + if (sheetBar) { + setBarHeight(sheetBar.clientHeight - 38); + } + }, []); + + const updateSheetItems = useCallback(() => { + const currentSubUnitId = workbook.getActiveSheet()?.getSheetId() || ''; + setActiveKey(currentSubUnitId); + + const sheets = workbook.getSheets(); + const activeSheet = workbook.getActiveSheet(); + const sheetListItems = sheets + .filter((sheet) => !sheet.isSheetHidden()) + .map((sheet, index) => { + return { + sheetId: sheet.getSheetId(), + label: sheet.getName(), + index, + selected: activeSheet === sheet, + color: sheet.getTabColor() ?? undefined, + }; + }); + + setSheetList(sheetListItems); + setActiveKey(currentSubUnitId); + }, [workbook]); + + useEffect(() => { + updateSheetItems(); + const disposable = setupStatusUpdate(); + + return () => { + disposable.dispose(); + }; + }, [workbook]); + + const handleSheetActiveChange = (subUnitId: string | undefined) => { + if (!subUnitId) return; + + commandService.executeCommand(SetWorksheetActiveOperation.id, { + subUnitId, + unitId: workbook.getUnitId(), + }); + }; + + const handleSheetAdd = () => { + commandService.executeCommand(InsertSheetCommand.id); + }; + + const setupStatusUpdate = () => + commandService.onCommandExecuted((commandInfo: ICommandInfo) => { + switch (commandInfo.id) { + case SetTabColorMutation.id: + case SetWorksheetHideMutation.id: + case RemoveSheetMutation.id: + case SetWorksheetNameMutation.id: + case InsertSheetMutation.id: + case SetWorksheetOrderMutation.id: + case SetWorksheetActiveOperation.id: + updateSheetItems(); + break; + default: + break; + } + }); + + return ( +
+
+
+ {sheetList.map((item, index) => ( +
handleSheetActiveChange(item.sheetId)}> + + + {' '} + {item.label} + {' '} + + +
+ ))} +
+
+ + +
+ ); +} diff --git a/packages-experimental/uni-sheets-ui/src/views/uni-sheet-bar/index.module.less b/packages-experimental/uni-sheets-ui/src/views/uni-sheet-bar/index.module.less new file mode 100644 index 0000000000..af461a7981 --- /dev/null +++ b/packages-experimental/uni-sheets-ui/src/views/uni-sheet-bar/index.module.less @@ -0,0 +1,80 @@ +.uni-sheet-bar { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + user-select: none; + + .sheet-tab { + overflow-y: auto; + scrollbar-width: thin; + + .sheet-items { + display: flex; + flex-direction: column; + gap: var(--margin-xxs); + font-size: var(--font-size-xs); + font-weight: 500; + + .sheet-item { + display: flex; + align-items: center; + height: 32px; + padding: 0 var(--margin-xs); + border-radius: var(--border-radius-lg); + + > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 142px; + } + + &:hover { + background-color: rgb(var(--grey-100)); + cursor: pointer; + } + + &.active { + color: rgba(var(--hyacinth-500)); + background-color: rgb(var(--hyacinth-500), 0.07); + } + } + } + } + + .new-sheet-button { + position: relative; + height: 32px; + margin-top: 16px; + padding: 0 var(--margin-xs); + border-radius: var(--border-radius-lg); + color: rgba(var(--hyacinth-500)); + background-color: transparent; + border: 0; + display: flex; + align-items: center; + justify-content: center; + + &-icon { + width: 16px; + height: 16px; + margin-right: 5px; + } + + &:hover { + background-color: rgb(var(--grey-100)); + cursor: pointer; + } + + &::before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: -10px; + height: 1px; + background-color: #ecedef; + } + } +} diff --git a/packages-experimental/uni-sheets-ui/src/vite-env.d.ts b/packages-experimental/uni-sheets-ui/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages-experimental/uni-sheets-ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages-experimental/uni-sheets-ui/tsconfig.json b/packages-experimental/uni-sheets-ui/tsconfig.json new file mode 100644 index 0000000000..e8df90d34b --- /dev/null +++ b/packages-experimental/uni-sheets-ui/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@univerjs/shared/tsconfigs/base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "references": [ + { + "path": "./tsconfig.node.json" + } + ], + "include": [ + "src" + ] +} diff --git a/packages-experimental/uni-sheets-ui/tsconfig.node.json b/packages-experimental/uni-sheets-ui/tsconfig.node.json new file mode 100644 index 0000000000..e53dac8868 --- /dev/null +++ b/packages-experimental/uni-sheets-ui/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": "@univerjs/shared/tsconfigs/node", + "include": ["vite.config.ts"] +} diff --git a/packages-experimental/uni-sheets-ui/vite.config.ts b/packages-experimental/uni-sheets-ui/vite.config.ts new file mode 100644 index 0000000000..925b530b4d --- /dev/null +++ b/packages-experimental/uni-sheets-ui/vite.config.ts @@ -0,0 +1,12 @@ +import createViteConfig from '@univerjs/shared/vite'; +import pkg from './package.json'; + +export default ({ mode }) => createViteConfig({}, { + mode, + pkg, + features: { + react: false, + css: true, + dom: true, + }, +}); diff --git a/packages-experimental/uni-slides-ui/README.md b/packages-experimental/uni-slides-ui/README.md new file mode 100644 index 0000000000..e4801469ab --- /dev/null +++ b/packages-experimental/uni-slides-ui/README.md @@ -0,0 +1,16 @@ +# @univerjs/uni-slides-ui + +[![npm version](https://img.shields.io/npm/v/@univerjs/uni-slides-ui)](https://npmjs.org/packages/@univerjs/uni-slides-ui) +[![license](https://img.shields.io/npm/l/@univerjs/uni-slides-ui)](https://img.shields.io/npm/l/@univerjs/uni-slides-ui) + +## Introduction + +> TODO: Introduction + +## Usage + +### Installation + +```shell +npm i @univerjs/uni-slides-ui +``` diff --git a/packages-experimental/uni-slides-ui/package.json b/packages-experimental/uni-slides-ui/package.json new file mode 100644 index 0000000000..68f6b9ed29 --- /dev/null +++ b/packages-experimental/uni-slides-ui/package.json @@ -0,0 +1,106 @@ +{ + "name": "@univerjs/uni-slides-ui", + "version": "0.2.6", + "private": true, + "description": "", + "author": "DreamNum ", + "license": "Apache-2.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/univer" + }, + "homepage": "https://univer.ai", + "repository": { + "type": "git", + "url": "https://github.com/dream-num/univer" + }, + "bugs": { + "url": "https://github.com/dream-num/univer/issues" + }, + "keywords": [], + "sideEffects": [ + "**/*.css" + ], + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "exports": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "lint:types": "tsc --noEmit", + "build": "tsc && vite build" + }, + "peerDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/engine-render": "workspace:*", + "@univerjs/slides-ui": "workspace:*", + "@univerjs/ui": "workspace:*", + "@univerjs/uniui": "workspace:*", + "clsx": ">=2.0.0", + "react": "^16.9.0 || ^17.0.0 || ^18.0.0" + }, + "dependencies": { + "@univerjs/icons": "^0.1.72" + }, + "devDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/engine-render": "workspace:*", + "@univerjs/shared": "workspace:*", + "@univerjs/slides": "workspace:*", + "@univerjs/slides-ui": "workspace:*", + "@univerjs/ui": "workspace:*", + "@univerjs/uniui": "workspace:*", + "clsx": "^2.1.1", + "less": "^4.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "rxjs": "^7.8.1", + "typescript": "^5.5.4", + "vite": "^5.3.5", + "vitest": "^2.0.5" + }, + "univerSpace": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } +} diff --git a/packages-experimental/uni-slides-ui/src/controllers/uni-slides-ui.controller.tsx b/packages-experimental/uni-slides-ui/src/controllers/uni-slides-ui.controller.tsx new file mode 100644 index 0000000000..852425ac2f --- /dev/null +++ b/packages-experimental/uni-slides-ui/src/controllers/uni-slides-ui.controller.tsx @@ -0,0 +1,54 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import type { SlideDataModel } from '@univerjs/core'; +import { connectInjector, ICommandService, Inject, Injector, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType, useDependency } from '@univerjs/core'; +import type { IUniverSlidesDrawingConfig } from '@univerjs/slides-ui'; +import { SlidesUIController } from '@univerjs/slides-ui'; +import { ComponentManager, IMenuService, IShortcutService, IUIPartsService, useObservable } from '@univerjs/ui'; +import { UniUIPart } from '@univerjs/uniui'; +import { UniSlideSideBar } from '../views/UniSlideSideBar'; + +@OnLifecycle(LifecycleStages.Ready, UniSlidesUIController) +export class UniSlidesUIController extends SlidesUIController { + constructor( + _config: Partial, + @Inject(Injector) _injector: Injector, + @IMenuService _menuService: IMenuService, + @Inject(ComponentManager) _componentManager: ComponentManager, + @IUIPartsService _uiPartsService: IUIPartsService, + @ICommandService _commandService: ICommandService, + @IShortcutService _shortcutService: IShortcutService + ) { + super(_config, _injector, _menuService, _componentManager, _uiPartsService, _commandService, _shortcutService); + } + + protected override _initUIComponents(): void { + this.disposeWithMe(this._uiPartsService.registerComponent(UniUIPart.OUTLINE, () => connectInjector(RenderOutline, this._injector))); + } +} + +function RenderOutline() { + const univerInstanceService = useDependency(IUniverInstanceService); + const focused = useObservable(univerInstanceService.focused$); + const slide = useObservable(() => univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SLIDE), null, false, []); + if (!slide || focused !== slide.getUnitId()) return null; + + return ( + + ); +} diff --git a/packages-experimental/uni-slides-ui/src/index.ts b/packages-experimental/uni-slides-ui/src/index.ts new file mode 100644 index 0000000000..54b66877c0 --- /dev/null +++ b/packages-experimental/uni-slides-ui/src/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { UniSlidesUIController } from './controllers/uni-slides-ui.controller'; diff --git a/packages-experimental/uni-slides-ui/src/views/UniSlideSideBar.tsx b/packages-experimental/uni-slides-ui/src/views/UniSlideSideBar.tsx new file mode 100644 index 0000000000..926bb6df0d --- /dev/null +++ b/packages-experimental/uni-slides-ui/src/views/UniSlideSideBar.tsx @@ -0,0 +1,111 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { SlideDataModel } from '@univerjs/core'; +import clsx from 'clsx'; +import { ICommandService, IUniverInstanceService, UniverInstanceType, useDependency } from '@univerjs/core'; +import { Scrollbar } from '@univerjs/design'; +import type { RefObject } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { useObservable } from '@univerjs/ui'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { ActivateSlidePageOperation, SetSlidePageThumbOperation } from '@univerjs/slides-ui'; + +import styles from './index.module.less'; + +/** + * This components works as the root component of the left Sidebar of Slide. + */ + +export function UniSlideSideBar() { + const univerInstanceService = useDependency(IUniverInstanceService); + const commandService = useDependency(ICommandService); + const renderManagerService = useDependency(IRenderManagerService); + + const slideBarRef = useRef(null); + const currentSlide = useObservable( + () => univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SLIDE), + undefined, + undefined, + [] + ); + const pages = currentSlide?.getPages(); + const pageOrder = currentSlide?.getPageOrder(); + + if (!pages || !pageOrder) { + return null; + } + + const slideList = pageOrder.map((id) => pages[id]); + + const [divRefs, setDivRefs] = useState[]>([]); + const [activatePageId, setActivatePageId] = useState(pageOrder[0]); + + useEffect(() => { + setDivRefs(slideList.map((_) => React.createRef())); + }, [slideList.length]); + + useEffect(() => { + const subscriber = currentSlide?.activePage$.subscribe((page) => { + const id = page?.id ?? null; + + id && setActivatePageId(id); + }); + + return () => { + subscriber?.unsubscribe(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + divRefs.forEach((ref, index) => { + if (ref.current) { + const slide = slideList[index]; + renderManagerService.getRenderById(slide.id)?.engine.setContainer(ref.current); + } + }); + + if (divRefs.length > 0) { + commandService.syncExecuteCommand(SetSlidePageThumbOperation.id); + } + }, [divRefs]); // 依赖于divRefs数组的变化 + + const activatePage = useCallback((page: string) => { + commandService.syncExecuteCommand(ActivateSlidePageOperation.id, { id: page }); + }, [commandService]); + + return ( +
+ +
+ {slideList.map((item, index) => ( +
activatePage(item.id)} + > + {index + 1} +
+
+ ))} +
+ +
+ ); +} diff --git a/packages-experimental/uni-slides-ui/src/views/index.module.less b/packages-experimental/uni-slides-ui/src/views/index.module.less new file mode 100644 index 0000000000..bd6d4fa497 --- /dev/null +++ b/packages-experimental/uni-slides-ui/src/views/index.module.less @@ -0,0 +1,65 @@ +.uni-slide-bar { + display: flex; + flex-direction: column; + border-right: 1px solid #eaecf0; + + &-content { + .uni-slide-bar-item { + box-sizing: border-box; + position: relative; + display: flex; + overflow: hidden; + margin: 15px 0; + width: 160px; + border-radius: 8px; + border: 1px solid #ecedef; + + span { + position: absolute; + width: 15px; + font-size: 12px; + left: 6px; + bottom: 6px; + width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + overflow: hidden; + background: rgba(20, 34, 43, 0.4); + z-index: 10; + color: white; + } + + .uni-slide-bar-box { + position: relative; + width: 160px; + height: 90px; + } + + &-active { + border-color: rgb(var(--blue-500)); + } + + &:hover { + border-color: rgb(var(--blue-500)); + } + } + } + + .uni-slide-add-button { + padding: 10px 0; + text-align: center; + + button { + width: 45px; + height: auto; + + font-size: 24px; + line-height: 1; + + border-radius: 10px; + } + } +} diff --git a/packages-experimental/uni-slides-ui/src/vite-env.d.ts b/packages-experimental/uni-slides-ui/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages-experimental/uni-slides-ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages-experimental/uni-slides-ui/tsconfig.json b/packages-experimental/uni-slides-ui/tsconfig.json new file mode 100644 index 0000000000..e8df90d34b --- /dev/null +++ b/packages-experimental/uni-slides-ui/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@univerjs/shared/tsconfigs/base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "references": [ + { + "path": "./tsconfig.node.json" + } + ], + "include": [ + "src" + ] +} diff --git a/packages-experimental/uni-slides-ui/tsconfig.node.json b/packages-experimental/uni-slides-ui/tsconfig.node.json new file mode 100644 index 0000000000..e53dac8868 --- /dev/null +++ b/packages-experimental/uni-slides-ui/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": "@univerjs/shared/tsconfigs/node", + "include": ["vite.config.ts"] +} diff --git a/packages-experimental/uni-slides-ui/vite.config.ts b/packages-experimental/uni-slides-ui/vite.config.ts new file mode 100644 index 0000000000..e38cd8530b --- /dev/null +++ b/packages-experimental/uni-slides-ui/vite.config.ts @@ -0,0 +1,12 @@ +import createViteConfig from '@univerjs/shared/vite'; +import pkg from './package.json'; + +export default ({ mode }) => createViteConfig({}, { + mode, + pkg, + features: { + react: true, + css: true, + dom: true, + }, +}); diff --git a/packages-experimental/uniui/package.json b/packages-experimental/uniui/package.json index eb9c3c475e..caacb70066 100644 --- a/packages-experimental/uniui/package.json +++ b/packages-experimental/uniui/package.json @@ -1,6 +1,6 @@ { "name": "@univerjs/uniui", - "version": "0.2.1", + "version": "0.2.6", "private": true, "description": "", "author": "DreamNum ", @@ -62,7 +62,10 @@ "peerDependencies": { "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", + "@univerjs/docs": "workspace:*", "@univerjs/engine-render": "workspace:*", + "@univerjs/sheets": "workspace:*", + "@univerjs/sheets-ui": "workspace:*", "@univerjs/ui": "workspace:*", "clsx": ">=2.0.0", "react": "^16.9.0 || ^17.0.0 || ^18.0.0", @@ -70,7 +73,9 @@ "rxjs": ">=7.0.0" }, "dependencies": { + "@floating-ui/react-dom": "^2.1.1", "@univerjs/icons": "^0.1.72", + "@xyflow/react": "^12.0.4", "rc-util": "^5.43.0" }, "devDependencies": { @@ -78,8 +83,10 @@ "@types/react-dom": "^18.3.0", "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", + "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-render": "workspace:*", "@univerjs/shared": "workspace:*", + "@univerjs/sheets-ui": "workspace:*", "@univerjs/ui": "workspace:*", "clsx": "^2.1.1", "less": "^4.2.0", diff --git a/packages-experimental/uniui/src/commands/operations/set-flow-viewport.operation.ts b/packages-experimental/uniui/src/commands/operations/set-flow-viewport.operation.ts new file mode 100644 index 0000000000..b70086f1b3 --- /dev/null +++ b/packages-experimental/uniui/src/commands/operations/set-flow-viewport.operation.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor, ICommand, IExecutionOptions } from '@univerjs/core'; +import { CommandType } from '@univerjs/core'; +import { FlowManagerService } from '../../services/flow/flow-manager.service'; + +export interface ISetFlowViewportParams { + viewport: { + zoom: number; + x: number; + y: number; + }; +} + +// This operation will not trigger viewport change locally but for live-share collaboration. + +export const SetFlowViewportOperation: ICommand = { + id: 'uniui.operation.set-flow-viewport', + type: CommandType.OPERATION, + handler: async (accessor: IAccessor, params: ISetFlowViewportParams, options?: IExecutionOptions) => { + const { fromCollab } = options || {}; + if (!fromCollab) { + return true; + } + const flowManagerService = accessor.get(FlowManagerService); + const oldViewport = flowManagerService.getViewport(); + const { viewport } = params; + const { zoom, x, y } = viewport; + if (oldViewport) { + if (zoom === oldViewport.zoom && x === oldViewport.x && y === oldViewport.y) { + return false; + } + } + flowManagerService.setViewport(viewport); + return true; + }, +}; diff --git a/packages-experimental/uniui/src/commands/operations/uni-focus-unit.operation.ts b/packages-experimental/uniui/src/commands/operations/uni-focus-unit.operation.ts new file mode 100644 index 0000000000..5992fe5800 --- /dev/null +++ b/packages-experimental/uniui/src/commands/operations/uni-focus-unit.operation.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor, ICommand } from '@univerjs/core'; +import { CommandType, IUniverInstanceService } from '@univerjs/core'; + +export interface IUniFocusUnitParams { + unitId: string | null; +} + +export const UniFocusUnitOperation: ICommand = { + id: 'uniui.operation.uni-focus-unit', + type: CommandType.OPERATION, + handler: async (accessor: IAccessor, params: IUniFocusUnitParams) => { + const { unitId } = params; + const instanceService = accessor.get(IUniverInstanceService); + instanceService.focusUnit(unitId); + return true; + }, +}; diff --git a/packages-experimental/uniui/src/commands/operations/uni.operation.ts b/packages-experimental/uniui/src/commands/operations/uni.operation.ts new file mode 100644 index 0000000000..3c038846d2 --- /dev/null +++ b/packages-experimental/uniui/src/commands/operations/uni.operation.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor, ICommand } from '@univerjs/core'; +import { CommandType, IUniverInstanceService } from '@univerjs/core'; + +export const DisposeUnitOperation: ICommand = { + id: 'uniui.operation.dispose-unit', + type: CommandType.OPERATION, + handler: async (accessor: IAccessor) => { + const univerInstanceService = accessor.get(IUniverInstanceService); + const unitId = univerInstanceService.getFocusedUnit()?.getUnitId(); + if (unitId) { + univerInstanceService.disposeUnit(unitId); + } + return true; + }, +}; diff --git a/packages-experimental/uniui/src/controllers/menu.ts b/packages-experimental/uniui/src/controllers/menu.ts new file mode 100644 index 0000000000..3e98daccfa --- /dev/null +++ b/packages-experimental/uniui/src/controllers/menu.ts @@ -0,0 +1,236 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { type IAccessor, IUniverInstanceService } from '@univerjs/core'; +import { type IMenuButtonItem, type IMenuItem, type IMenuSelectorItem, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { map, Observable } from 'rxjs'; +import { DisposeUnitOperation } from '../commands/operations/uni.operation'; + +export const UNIT_LINE_COLOR_MENU_ID = 'UNIT_LINE_COLOR_MENU_ID'; +export const DOWNLOAD_MENU_ID = 'DOWNLOAD_MENU_ID'; +export const SHARE_MENU_ID = 'SHARE_MENU_ID'; +export const LOCK_MENU_ID = 'LOCK_MENU_ID'; +export const PRINT_MENU_ID = 'PRINT_MENU_ID'; +export const ZEN_MENU_ID = 'ZEN_MENU_ID'; +export const DELETE_MENU_ID = DisposeUnitOperation.id; +export const FRAME_SIZE_MENU_ID = 'FRAME_SIZE_MENU_ID'; + +export const FONT_GROUP_MENU_ID = 'FONT_GROUP_MENU_ID'; + +export const FAKE_FONT_FAMILY_MENU_ID = 'FAKE_FONT_FAMILY_MENU_ID'; +export const FAKE_FONT_SIZE_MENU_ID = 'FAKE_FONT_SIZE_MENU_ID'; +export const FAKE_FONT_COLOR_MENU_ID = 'FAKE_FONT_COLOR_MENU_ID'; +export const FAKE_BG_COLOR_MENU_ID = 'FAKE_BG_COLOR_MENU_ID'; +export const FAKE_IMAGE_MENU_ID = 'FAKE_IMAGE_MENU_ID'; +export const FAKE_FONT_GROUP_MENU_ID = 'FAKE_FONT_GROUP_MENU_ID'; + +export enum UNI_MENU_POSITIONS { + TOOLBAR_MAIN = 'toolbar_main', + TOOLBAR_FLOAT = 'toolbar_float', +} + +export function FakeFontFamilySelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + return { + id: FAKE_FONT_FAMILY_MENU_ID, + tooltip: 'toolbar.font', + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.SELECTOR, + label: 'UI_PLUGIN_DOCS_FONT_FAMILY_COMPONENT', + positions: [MenuPosition.TOOLBAR_START], + selections: [], + disabled$: new Observable((subscriber) => { subscriber.next(true); }), + }; +} + +export function FakeFontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + return { + id: FAKE_FONT_SIZE_MENU_ID, + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.SELECTOR, + tooltip: 'toolbar.fontSize', + label: { + name: 'UI_PLUGIN_DOCS_FONT_SIZE_COMPONENT', + props: { + min: 1, + max: 400, + }, + }, + positions: [MenuPosition.TOOLBAR_START], + selections: [], + disabled$: new Observable((subscriber) => { subscriber.next(true); }), + }; +} + +export function FakeTextColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + return { + id: FAKE_FONT_COLOR_MENU_ID, + icon: 'FontColor', + tooltip: 'toolbar.textColor.main', + + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON_SELECTOR, + positions: [MenuPosition.TOOLBAR_START], + selections: [], + disabled$: new Observable((subscriber) => { subscriber.next(true); }), + }; +} + +export function FakeBackgroundColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + return { + id: FAKE_BG_COLOR_MENU_ID, + tooltip: 'toolbar.fillColor.main', + group: MenuGroup.TOOLBAR_FORMAT, + type: MenuItemType.BUTTON_SELECTOR, + positions: [MenuPosition.TOOLBAR_START], + icon: 'PaintBucket', + selections: [], + disabled$: new Observable((subscriber) => { subscriber.next(true); }), + }; +} + +export function FakeImageMenuFactory(accessor: IAccessor): IMenuItem { + return { + id: FAKE_IMAGE_MENU_ID, + type: MenuItemType.SUBITEMS, + positions: [MenuPosition.TOOLBAR_START], + group: MenuGroup.TOOLBAR_FORMULAS_INSERT, + icon: 'addition-and-subtraction-single', + tooltip: 'sheetImage.title', + disabled$: new Observable((subscriber) => { subscriber.next(true); }), + }; +} + +export function FontGroupMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const univerInstanceService = accessor.get(IUniverInstanceService); + return { + id: FONT_GROUP_MENU_ID, + type: MenuItemType.SUBITEMS, + tooltip: 'Font group', + icon: 'BoldSingle', + positions: [MenuPosition.TOOLBAR_START], + hidden$: univerInstanceService.focused$.pipe(map((focused) => !focused)), + }; +} + +export function FakeFontGroupMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + return { + id: FAKE_FONT_GROUP_MENU_ID, + type: MenuItemType.SUBITEMS, + tooltip: 'Font group', + icon: 'BoldSingle', + positions: [MenuPosition.TOOLBAR_START], + disabled$: new Observable((subscriber) => { subscriber.next(true); }), + }; +} + +export function UnitLineColorMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: UNIT_LINE_COLOR_MENU_ID, + type: MenuItemType.BUTTON, + title: '', + icon: '', + positions: [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, + ], + + }; +} + +export function DownloadMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: DOWNLOAD_MENU_ID, + type: MenuItemType.BUTTON, + group: MenuGroup.TOOLBAR_OTHERS, + title: '', + tooltip: 'Download', + icon: 'DownloadSingle', + positions: [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, + ], + }; +} + +export function ShareMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: SHARE_MENU_ID, + type: MenuItemType.BUTTON, + group: MenuGroup.TOOLBAR_OTHERS, + title: '', + tooltip: 'Share', + icon: 'ShareSingle', + positions: [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, + ], + + }; +} + +export function LockMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: LOCK_MENU_ID, + type: MenuItemType.BUTTON, + group: MenuGroup.TOOLBAR_OTHERS, + title: '', + tooltip: 'Lock', + icon: 'LockSingle', + positions: [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, + ], + }; +} + +export function PrintMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: PRINT_MENU_ID, + type: MenuItemType.BUTTON, + group: MenuGroup.TOOLBAR_OTHERS, + title: '', + tooltip: 'Print', + icon: 'PrintSingle', + positions: [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, + ], + + }; +} + +export function ZenMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: ZEN_MENU_ID, + type: MenuItemType.BUTTON, + title: '', + icon: 'ZenSingle', + tooltip: 'Full screen', + group: MenuGroup.TOOLBAR_OTHERS, + positions: [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, + ], + }; +} + +export function DeleteMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + return { + id: DELETE_MENU_ID, + type: MenuItemType.BUTTON, + title: 'Delete', + tooltip: 'Delete', + icon: 'DeleteSingle', + group: MenuGroup.TOOLBAR_OTHERS, + positions: [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, + ], + }; +} diff --git a/packages-experimental/uniui/src/controllers/uniui-desktop.controller.tsx b/packages-experimental/uniui/src/controllers/uniui-desktop.controller.tsx index 337aed4bb4..5fdce38164 100644 --- a/packages-experimental/uniui/src/controllers/uniui-desktop.controller.tsx +++ b/packages-experimental/uniui/src/controllers/uniui-desktop.controller.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import { connectInjector, Disposable, Inject, Injector, LifecycleService, LifecycleStages, toDisposable } from '@univerjs/core'; +import { connectInjector, Disposable, Inject, Injector, LifecycleService, LifecycleStages, Optional, toDisposable } from '@univerjs/core'; import type { IUniverUIConfig, IWorkbenchOptions } from '@univerjs/ui'; -import { BuiltInUIPart, CanvasPopup, FloatDom, IUIPartsService } from '@univerjs/ui'; import type { IDisposable } from '@univerjs/core'; +import { BuiltInUIPart, CanvasPopup, FloatDom, ILayoutService, IUIPartsService } from '@univerjs/ui'; import React from 'react'; import { delay, filter, take } from 'rxjs'; import { render as createRoot, unmount } from 'rc-util/lib/React/render'; @@ -31,7 +31,8 @@ export class UniverUniUIController extends Disposable { private readonly _config: IUniverUIConfig, @Inject(Injector) private readonly _injector: Injector, @Inject(LifecycleService) private readonly _lifecycleService: LifecycleService, - @IUIPartsService private readonly _uiPartsService: IUIPartsService + @IUIPartsService private readonly _uiPartsService: IUIPartsService, + @Optional(ILayoutService) private readonly _layoutService?: ILayoutService ) { super(); @@ -42,7 +43,12 @@ export class UniverUniUIController extends Disposable { private _bootstrapWorkbench(): void { this.disposeWithMe( - bootstrap(this._injector, this._config, () => { + bootstrap(this._injector, this._config, (contentEl, containerEl) => { + if (this._layoutService) { + this.disposeWithMe(this._layoutService.registerContentElement(contentEl)); + this.disposeWithMe(this._layoutService.registerContainerElement(containerEl)); + } + this._lifecycleService.lifecycle$.pipe( filter((lifecycle) => lifecycle === LifecycleStages.Ready), delay(300), @@ -64,7 +70,7 @@ export class UniverUniUIController extends Disposable { function bootstrap( injector: Injector, options: IWorkbenchOptions, - callback: () => void + callback: (contentEl: HTMLElement, containerElement: HTMLElement) => void ): IDisposable { let mountContainer: HTMLElement; @@ -88,7 +94,7 @@ function bootstrap( callback(contentEl, mountContainer)} />, mountContainer ); diff --git a/packages-experimental/uniui/src/controllers/uniui-flow.controller.tsx b/packages-experimental/uniui/src/controllers/uniui-flow.controller.tsx new file mode 100644 index 0000000000..6a42f9a69a --- /dev/null +++ b/packages-experimental/uniui/src/controllers/uniui-flow.controller.tsx @@ -0,0 +1,55 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Disposable, ICommandService, Inject, LifecycleStages, OnLifecycle } from '@univerjs/core'; +import { auditTime, throttleTime } from 'rxjs'; +import { SetFlowViewportOperation } from '../commands/operations/set-flow-viewport.operation'; +import type { IFlowViewport } from '../services/flow/flow-manager.service'; +import { FlowManagerService } from '../services/flow/flow-manager.service'; +import { UniFocusUnitOperation } from '../commands/operations/uni-focus-unit.operation'; + +@OnLifecycle(LifecycleStages.Ready, UniuiFlowController) +export class UniuiFlowController extends Disposable { + constructor( + @ICommandService protected readonly _commandService: ICommandService, + @Inject(FlowManagerService) protected readonly _flowManagerService: FlowManagerService + ) { + super(); + this._initCommands(); + this._triggerCommands(); + } + + private _initCommands(): void { + [ + SetFlowViewportOperation, + UniFocusUnitOperation, + ].forEach((c) => { + this.disposeWithMe(this._commandService.registerCommand(c)); + }); + } + + private _triggerCommands() { + this._flowManagerService.viewportChanged$.pipe( + throttleTime(100, undefined, { leading: true, trailing: true }), + auditTime(100) + ).subscribe((viewport: IFlowViewport | null) => { + if (viewport) { + this._commandService.executeCommand(SetFlowViewportOperation.id, { viewport }); + } + }); + } +} + diff --git a/packages-experimental/uniui/src/controllers/uniui-leftsidebar.controller.tsx b/packages-experimental/uniui/src/controllers/uniui-leftsidebar.controller.tsx new file mode 100644 index 0000000000..eaefb50b57 --- /dev/null +++ b/packages-experimental/uniui/src/controllers/uniui-leftsidebar.controller.tsx @@ -0,0 +1,65 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Disposable, Inject, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import type { ISidebarService } from '@univerjs/ui'; +import { ComponentManager, ILeftSidebarService } from '@univerjs/ui'; +import { distinctUntilChanged, map } from 'rxjs'; +import { Outline } from '../views/outline/Outline'; + +const OUTLINE_COMPONENT = 'OUTLINE_COMPONENT'; + +@OnLifecycle(LifecycleStages.Ready, UniuiLeftSidebarController) +export class UniuiLeftSidebarController extends Disposable { + constructor( + @Inject(ComponentManager) private readonly _componentManager: ComponentManager, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @ILeftSidebarService private readonly _leftSidebarService: ISidebarService + ) { + super(); + + this._initComponents(); + this._initShowOutlineListener(); + } + + private _initComponents(): void { + this.disposeWithMe(this._componentManager.register(OUTLINE_COMPONENT, Outline)); + } + + private _initShowOutlineListener(): void { + this.disposeWithMe(this._univerInstanceService.focused$.pipe( + map((unitId) => !unitId ? false : this._univerInstanceService.getUnit(unitId)?.type !== UniverInstanceType.UNIVER_DOC), + distinctUntilChanged() + ).subscribe((showOutline) => { + if (showOutline) { + this._leftSidebarService.open({ + children: { label: OUTLINE_COMPONENT }, + visible: true, + width: 176, + }); + } else { + this._leftSidebarService.close(); + } + })); + + this.disposeWithMe(this._univerInstanceService.unitDisposed$.pipe( + distinctUntilChanged() + ).subscribe(() => { + this._leftSidebarService.close(); + })); + } +} + diff --git a/packages-experimental/uniui/src/controllers/uniui-toolbar.controller.tsx b/packages-experimental/uniui/src/controllers/uniui-toolbar.controller.tsx new file mode 100644 index 0000000000..ba62aed378 --- /dev/null +++ b/packages-experimental/uniui/src/controllers/uniui-toolbar.controller.tsx @@ -0,0 +1,92 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Disposable, ICommandService, Inject, Injector, LifecycleStages, OnLifecycle } from '@univerjs/core'; +import { DeleteSingle, DownloadSingle, LockSingle, PrintSingle, ShareSingle, ZenSingle } from '@univerjs/icons'; +import type { IMenuItemFactory, MenuConfig } from '@univerjs/ui'; +import { ComponentManager, IMenuService } from '@univerjs/ui'; +import { DisposeUnitOperation } from '../commands/operations/uni.operation'; +import { UniToolbarService } from '../services/toolbar/uni-toolbar-service'; +import { DeleteMenuItemFactory, DownloadMenuItemFactory, FakeBackgroundColorSelectorMenuItemFactory, FakeFontFamilySelectorMenuItemFactory, FakeFontGroupMenuItemFactory, FakeFontSizeSelectorMenuItemFactory, FakeImageMenuFactory, FakeTextColorSelectorMenuItemFactory, FontGroupMenuItemFactory, LockMenuItemFactory, PrintMenuItemFactory, ShareMenuItemFactory, ZenMenuItemFactory } from './menu'; + +export interface IUniuiToolbarConfig { + menu: MenuConfig; +} + +export const DefaultUniuiToolbarConfig = {}; + +@OnLifecycle(LifecycleStages.Ready, UniuiToolbarController) +export class UniuiToolbarController extends Disposable { + constructor( + @IMenuService protected readonly _menuService: IMenuService, + @Inject(Injector) protected readonly _injector: Injector, + @Inject(ComponentManager) protected readonly _componentManager: ComponentManager, + @ICommandService protected readonly _commandService: ICommandService, + @Inject(UniToolbarService) protected readonly _toolbarService: UniToolbarService + ) { + super(); + this._initComponent(); + this._initMenus(); + this._initCommands(); + } + + private _initComponent(): void { + const componentManager = this._componentManager; + const iconList: Record> = { + DownloadSingle, + ShareSingle, + LockSingle, + PrintSingle, + ZenSingle, + DeleteSingle, + }; + for (const k in iconList) { + this.disposeWithMe(componentManager.register(k, iconList[k])); + } + } + + private _initMenus(): void { + // register menu factories + ( + [ + DownloadMenuItemFactory, + ShareMenuItemFactory, + LockMenuItemFactory, + PrintMenuItemFactory, + ZenMenuItemFactory, + DeleteMenuItemFactory, + FontGroupMenuItemFactory, + FakeFontFamilySelectorMenuItemFactory, + FakeTextColorSelectorMenuItemFactory, + FakeFontSizeSelectorMenuItemFactory, + FakeBackgroundColorSelectorMenuItemFactory, + FakeImageMenuFactory, + FakeFontGroupMenuItemFactory, + ] as IMenuItemFactory[] + ).forEach((factory) => { + this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(factory), {})); + }); + } + + private _initCommands(): void { + [ + DisposeUnitOperation, + ].forEach((c) => { + this.disposeWithMe(this._commandService.registerCommand(c)); + }); + } +} + diff --git a/packages-experimental/uniui/src/controllers/utils.ts b/packages-experimental/uniui/src/controllers/utils.ts new file mode 100644 index 0000000000..2c6ebd0899 --- /dev/null +++ b/packages-experimental/uniui/src/controllers/utils.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICommand } from '@univerjs/core'; +import { Tools } from '@univerjs/core'; + +export const generateCloneMutation = (cloneId: string, sourceMutation: ICommand): ICommand => { + return { + ...Tools.deepClone(sourceMutation), + id: cloneId, + }; +}; diff --git a/packages-experimental/uniui/src/index.ts b/packages-experimental/uniui/src/index.ts index 30056d30d9..b78888372c 100644 --- a/packages-experimental/uniui/src/index.ts +++ b/packages-experimental/uniui/src/index.ts @@ -15,3 +15,12 @@ */ export { UniverUniUIPlugin } from './plugin'; +export { UniUIPart } from './views/outline/Outline'; +export { getGridUnitLocalCacheKey, UnitGridService, IUnitGridService, type IProjectNode, type IUnitGrid } from './services/unit-grid/unit-grid.service'; +export { UniFloatToolbarUIPart } from './views/uni-toolbar/UniFloatToolbar'; +export { SetFlowViewportOperation } from './commands/operations/set-flow-viewport.operation'; +export { UniFocusUnitOperation } from './commands/operations/uni-focus-unit.operation'; +export { FONT_GROUP_MENU_ID, UNI_MENU_POSITIONS, ZEN_MENU_ID, LOCK_MENU_ID, PRINT_MENU_ID, SHARE_MENU_ID, DELETE_MENU_ID, DOWNLOAD_MENU_ID } from './controllers/menu'; +export { UniToolbarService } from './services/toolbar/uni-toolbar-service'; +export { BuiltinUniToolbarItemId } from './services/toolbar/uni-toolbar-service'; +export { generateCloneMutation } from './controllers/utils'; diff --git a/packages-experimental/uniui/src/plugin.ts b/packages-experimental/uniui/src/plugin.ts index 93aa9023c5..cbd915d4ce 100644 --- a/packages-experimental/uniui/src/plugin.ts +++ b/packages-experimental/uniui/src/plugin.ts @@ -46,6 +46,7 @@ import { IEditorService, IGlobalZoneService, ILayoutService, + ILeftSidebarService, IMenuService, IMessageService, INotificationService, @@ -69,8 +70,13 @@ import { UNIVER_UI_PLUGIN_NAME, ZIndexManager, } from '@univerjs/ui'; +import { FlowManagerService } from './services/flow/flow-manager.service'; import { UniverUniUIController } from './controllers/uniui-desktop.controller'; -import { UnitGridService } from './services/unit-grid/unit-grid.service'; +import { IUnitGridService, UnitGridService } from './services/unit-grid/unit-grid.service'; +import { UniuiLeftSidebarController } from './controllers/uniui-leftsidebar.controller'; +import { UniuiToolbarController } from './controllers/uniui-toolbar.controller'; +import { UniuiFlowController } from './controllers/uniui-flow.controller'; +import { UniToolbarService } from './services/toolbar/uni-toolbar-service'; const UI_BOOTSTRAP_DELAY = 16; @@ -100,7 +106,9 @@ export class UniverUniUIPlugin extends Plugin { [ComponentManager], [ZIndexManager], [ShortcutPanelService], - [UnitGridService], + [FlowManagerService], + [UniToolbarService], + [IUnitGridService, { useClass: UnitGridService }], [IUIPartsService, { useClass: UIPartsService }], [ILayoutService, { useClass: DesktopLayoutService }], [IShortcutService, { useClass: ShortcutService }], @@ -112,6 +120,7 @@ export class UniverUniUIPlugin extends Plugin { [IDialogService, { useClass: DesktopDialogService, lazy: true }], [IConfirmService, { useClass: DesktopConfirmService, lazy: true }], [ISidebarService, { useClass: DesktopSidebarService, lazy: true }], + [ILeftSidebarService, { useClass: DesktopSidebarService, lazy: true }], [IZenZoneService, { useClass: DesktopZenZoneService, lazy: true }], [IGlobalZoneService, { useClass: DesktopGlobalZoneService, lazy: true }], [IMessageService, { useClass: DesktopMessageService, lazy: true }], @@ -132,6 +141,9 @@ export class UniverUniUIPlugin extends Plugin { [ShortcutPanelController, { useFactory: () => this._injector.createInstance(ShortcutPanelController, this._config), }], + [UniuiLeftSidebarController], + [UniuiToolbarController], + [UniuiFlowController], ], this._config.override); dependencies.forEach((dependency) => this._injector.add(dependency)); } diff --git a/packages-experimental/uniui/src/services/flow/flow-manager.service.ts b/packages-experimental/uniui/src/services/flow/flow-manager.service.ts new file mode 100644 index 0000000000..23ccb00fe7 --- /dev/null +++ b/packages-experimental/uniui/src/services/flow/flow-manager.service.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Disposable, LifecycleStages, OnLifecycle } from '@univerjs/core'; +import type { + ReactFlowInstance, +} from '@xyflow/react'; + +import { BehaviorSubject } from 'rxjs'; + +export interface IFlowViewport { + zoom: number; + x: number; + y: number; +} + +@OnLifecycle(LifecycleStages.Ready, FlowManagerService) +export class FlowManagerService extends Disposable { + private readonly _viewportChanged$ = new BehaviorSubject(null); + readonly viewportChanged$ = this._viewportChanged$.asObservable(); + + private _flowInstance: ReactFlowInstance; + + constructor() { + super(); + } + + getViewport() { + return this._flowInstance?.getViewport(); + } + + setViewport(viewport: { zoom: number; x: number; y: number }) { + this._flowInstance?.setViewport(viewport, { + duration: 100, + }); + } + + setReactFlowInstance(instance: ReactFlowInstance) { + this._flowInstance = instance; + } + + fitView() { + if (this._flowInstance) { + this._flowInstance.fitView(); + } + } + + zoomIn() { + if (this._flowInstance) { + this._flowInstance.zoomIn(); + } + } + + zoomOut() { + if (this._flowInstance) { + this._flowInstance.zoomOut(); + } + } + + setViewportChanged(viewport: IFlowViewport) { + this._viewportChanged$.next(viewport); + } +} + diff --git a/packages-experimental/uniui/src/services/toolbar/uni-toolbar-service.ts b/packages-experimental/uniui/src/services/toolbar/uni-toolbar-service.ts new file mode 100644 index 0000000000..98b0718111 --- /dev/null +++ b/packages-experimental/uniui/src/services/toolbar/uni-toolbar-service.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Disposable, RedoCommand, toDisposable, UndoCommand, UniverInstanceType } from '@univerjs/core'; +import { FAKE_BG_COLOR_MENU_ID, FAKE_FONT_COLOR_MENU_ID, FAKE_FONT_FAMILY_MENU_ID, FAKE_FONT_GROUP_MENU_ID, FAKE_FONT_SIZE_MENU_ID, FAKE_IMAGE_MENU_ID, FONT_GROUP_MENU_ID } from '../../controllers/menu'; + +interface IItemImpl { + id: string; + type: UniverInstanceType; +} + +export interface IUniToolbarItem { + id: string; + impl: IItemImpl[]; +} + +export enum BuiltinUniToolbarItemId { + UNDO = 'undo', + REDO = 'redo', + FONT_FAMILY = 'font-famaily', + FONT_SIZE = 'font-size', + FONT_GROUP = 'font-group', + COLOR = 'color', + BACKGROUND = 'background', + IMAGE = 'image', + PIVOT_TABLE = 'pivot-table', +} + +export class UniToolbarService extends Disposable { + private _items: IUniToolbarItem[]; + constructor() { + super(); + this._init(); + } + + private _init() { + this._items = [ + { + id: BuiltinUniToolbarItemId.UNDO, + impl: [{ id: UndoCommand.id, type: UniverInstanceType.UNIVER_UNKNOWN }], + }, + { + id: BuiltinUniToolbarItemId.REDO, + impl: [{ id: RedoCommand.id, type: UniverInstanceType.UNIVER_UNKNOWN }], + }, + { + id: BuiltinUniToolbarItemId.FONT_FAMILY, + impl: [{ id: FAKE_FONT_FAMILY_MENU_ID, type: UniverInstanceType.UNIVER_UNKNOWN }], + }, + { + id: BuiltinUniToolbarItemId.FONT_SIZE, + impl: [{ id: FAKE_FONT_SIZE_MENU_ID, type: UniverInstanceType.UNIVER_UNKNOWN }], + }, + { + id: BuiltinUniToolbarItemId.FONT_GROUP, + impl: [ + { id: FAKE_FONT_GROUP_MENU_ID, type: UniverInstanceType.UNIVER_UNKNOWN }, + { id: FONT_GROUP_MENU_ID, type: UniverInstanceType.UNIVER_SHEET }, + { id: FONT_GROUP_MENU_ID, type: UniverInstanceType.UNIVER_DOC }, + { id: FONT_GROUP_MENU_ID, type: UniverInstanceType.UNIVER_SLIDE }, + ], + }, + { + id: BuiltinUniToolbarItemId.COLOR, + impl: [{ id: FAKE_FONT_COLOR_MENU_ID, type: UniverInstanceType.UNIVER_UNKNOWN }], + }, + { + id: BuiltinUniToolbarItemId.BACKGROUND, + impl: [{ id: FAKE_BG_COLOR_MENU_ID, type: UniverInstanceType.UNIVER_UNKNOWN }], + }, + { + id: BuiltinUniToolbarItemId.IMAGE, + impl: [{ id: FAKE_IMAGE_MENU_ID, type: UniverInstanceType.UNIVER_UNKNOWN }], + }, + ]; + } + + addItem(item: IUniToolbarItem) { + this._items.push(item); + return toDisposable(() => { + this._items = this._items.filter((i) => i.id !== item.id); + }); + } + + getItems() { + return this._items; + } + + implementItem(id: string, impl: IItemImpl) { + const item = this._items.find((item) => item.id === id); + if (item) { + item.impl.push(impl); + return toDisposable(() => { + item.impl = item.impl.filter((i) => i.id !== impl.id); + }); + } + return toDisposable(() => {}); + } +} diff --git a/packages-experimental/uniui/src/services/unit-grid/unit-grid.service.ts b/packages-experimental/uniui/src/services/unit-grid/unit-grid.service.ts index f234c12396..2c35f33f66 100644 --- a/packages-experimental/uniui/src/services/unit-grid/unit-grid.service.ts +++ b/packages-experimental/uniui/src/services/unit-grid/unit-grid.service.ts @@ -14,59 +14,177 @@ * limitations under the License. */ -import { Disposable, isInternalEditorID } from '@univerjs/core'; +import type { Nullable, UnitModel, UniverInstanceType } from '@univerjs/core'; +import { createIdentifier, Disposable, ILocalStorageService, isInternalEditorID, IUniverInstanceService } from '@univerjs/core'; import type { IRender } from '@univerjs/engine-render'; import { IRenderManagerService } from '@univerjs/engine-render'; +import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; -export type IUnitGrid = string[]; +export type IUnitGrid = IProjectNode[]; + +export interface IProjectNode { + id: string; + data: { + unitId: string; + type: UniverInstanceType; + }; + style: { + width: string; + height: string; + display: string; + borderRadius: string; + border: string; + backgroundColor: string; + }; + position: { + x: number; + y: number; + }; +} + +export interface IUnitGridService { + readonly unitGrid: IProjectNode[]; + readonly unitGrid$: Observable; + readonly newNode: IProjectNode | null; + readonly newNode$: Observable; + + setContainerForRender(unitId: string, element: HTMLElement): void; + + changeDimension(id: string, dimensions: { width: number; height: number }): void; + changePosition(id: string, position: { x: number; y: number }): void; + +} + +export const IUnitGridService = createIdentifier('uniui.grid.service'); + +// NOTE: please note that caching position and dimension of units is just for demo. +// The grid should be a unit itself. /** * This services decides which (now at maximum two) units are going to be rendered in the browser. */ -export class UnitGridService extends Disposable { +export class UnitGridService extends Disposable implements IUnitGridService { // TODO@wzhudev: currently we only support at maximum 2 units rendered side by side. // In the future we would introduce a grid system (very much like vscode's) to support more // units and more complex layout. - private _unitGrid: IUnitGrid = []; + protected _unitGrid: IUnitGrid = []; private readonly _unitGrid$ = new BehaviorSubject(this._unitGrid); readonly unitGrid$ = this._unitGrid$.asObservable(); + protected _newNode: IProjectNode | null = null; + private readonly _newNode$ = new BehaviorSubject(null); + readonly newNode$ = this._newNode$.asObservable(); + + get unitGrid() { return this._unitGrid; } + get cachedGrid() { return this._cachedGrid; } + get newNode() { return this._newNode; } + + private _nodeIndex = 0; + constructor( - @IRenderManagerService private readonly _renderSrv: IRenderManagerService + @IRenderManagerService protected readonly _renderSrv: IRenderManagerService, + @ILocalStorageService protected readonly _localStorageService: ILocalStorageService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService ) { super(); this._init(); } - setContainerForRender(unitId: string, element: HTMLElement) { - this._renderSrv.getRenderById(unitId)?.engine.setContainer(element); - } - override dispose(): void { super.dispose(); this._unitGrid$.complete(); + this._newNode$.complete(); } - private _init(): void { + setNewNodeObserver() { + this._newNode$.next(this._newNode); + } + + setContainerForRender(unitId: string, element: HTMLElement) { + this._renderSrv.getRenderById(unitId)?.engine.setContainer(element); + } + + changeDimension(id: string, dimension: { width: number; height: number }): void { + const node = this._unitGrid.find((item) => item.id === id); + if (node) { + node.style.width = `${dimension.width}px`; + node.style.height = `${dimension.height}px`; + this._cacheData(); + } + } + + changePosition(id: string, position: { x: number; y: number }): void { + const node = this._unitGrid.find((item) => item.id === id); + if (node) { + node.position.x = position.x; + node.position.y = position.y; + this._cacheData(); + } + } + + private async _init(): Promise { + await this._initData(); + this._renderSrv.getRenderAll().forEach((renderer) => this._onRendererCreated(renderer)); - this.disposeWithMe(this._renderSrv.created$.subscribe((renderer) => this._onRendererCreated(renderer))); - this.disposeWithMe(this._renderSrv.disposed$.subscribe((unitId) => this._onRenderedDisposed(unitId))); + this.disposeWithMe(this._renderSrv.created$.subscribe((render) => this._onRendererCreated(render))); + // When hot reload in development mode, render dispose will be triggered, causing data to be cleared. Replace it with unitDispose + this.disposeWithMe(this._univerInstanceService.unitDisposed$.subscribe((unitModel) => this._onUnitDisposed(unitModel))); } - private _onRendererCreated(renderer: IRender): void { - if (isInternalEditorID(renderer.unitId)) { + protected _cachedGrid: Nullable = null; + protected async _initData(): Promise { + const raw = await this._localStorageService.getItem(getGridUnitLocalCacheKey('static')); + if (raw) { + this._cachedGrid = raw; + } + } + + protected _cacheData(): void { + this._localStorageService.setItem(getGridUnitLocalCacheKey('static'), this.unitGrid); + } + + protected _onRendererCreated(renderer: IRender): void { + const { unitId, type, isThumbNail } = renderer; + if (isInternalEditorID(unitId) || isThumbNail) { return; } - this._unitGrid.push(renderer.unitId); + this._insertNewNode(unitId, type); + } + + private _insertNewNode(unitId: string, type: UniverInstanceType): void { + const index = this._nodeIndex; + this._nodeIndex += 1; + + const MAX_COUNT_IN_ROW = 3; + const newNode: IProjectNode = this._cachedGrid?.find((item) => item.id === unitId) ?? { + id: unitId, + data: { + unitId, + type, + }, + style: { + width: '660px', + height: '600px', + display: 'flex', + borderRadius: '8px', + border: '1px solid #ccc', + backgroundColor: '#fff', + }, + position: { x: (index % MAX_COUNT_IN_ROW) * 750, y: Math.floor(index / MAX_COUNT_IN_ROW) * 750 + 40 }, + }; + + this._unitGrid.push(newNode); + this._newNode = newNode; // set new node, for viewport to scroll to this._emitLayoutChange(); } - private _onRenderedDisposed(unitId: string): void { - const idx = this._unitGrid.indexOf(unitId); + protected _onUnitDisposed(unitModel: UnitModel): void { + const unitId = unitModel.getUnitId(); + const idx = this._unitGrid.findIndex((item) => item.id === unitId); if (idx !== -1) { this._unitGrid.splice(idx, 1); this._emitLayoutChange(); @@ -74,6 +192,11 @@ export class UnitGridService extends Disposable { } private _emitLayoutChange(): void { - this._unitGrid$.next(this._unitGrid); + this._unitGrid$.next(this._unitGrid.slice()); + this._cacheData(); } } + +export function getGridUnitLocalCacheKey(projectId: string): string { + return `project-cache-${projectId}`; +} diff --git a/packages-experimental/uniui/src/views/hooks/title.ts b/packages-experimental/uniui/src/views/hooks/title.ts new file mode 100644 index 0000000000..154bfe7063 --- /dev/null +++ b/packages-experimental/uniui/src/views/hooks/title.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Nullable } from '@univerjs/core'; +import { IUniverInstanceService, useDependency } from '@univerjs/core'; +import { useObservable } from '@univerjs/ui'; +import { useMemo } from 'react'; + +export function useUnitTitle(unitId: string): string | undefined { + const instanceService = useDependency(IUniverInstanceService); + const unit = useMemo(() => instanceService.getUnit(unitId), [unitId, instanceService]); + const title = useObservable(unit?.name$, '', false, [unit]); + return title; +} + +export function useUnitFocused(unitId: string): boolean { + const instanceService = useDependency(IUniverInstanceService); + const focusedUnitId = useObservable>(instanceService.focused$); + return unitId === focusedUnitId; +} diff --git a/packages-experimental/uni-formula/src/controllers/uni-formula.controller.ts b/packages-experimental/uniui/src/views/outline/Outline.tsx similarity index 58% rename from packages-experimental/uni-formula/src/controllers/uni-formula.controller.ts rename to packages-experimental/uniui/src/views/outline/Outline.tsx index 5a982a2baf..3e03cd3187 100644 --- a/packages-experimental/uni-formula/src/controllers/uni-formula.controller.ts +++ b/packages-experimental/uniui/src/views/outline/Outline.tsx @@ -14,18 +14,20 @@ * limitations under the License. */ -import { IUniverInstanceService } from '@univerjs/core'; +import { ComponentContainer, useComponentsOfPart } from '@univerjs/ui'; +import React from 'react'; -/** - * This controller is responsible for registering formulas into `UniFormulaService`, - * when a doc is loaded or a new formula is created. It is also responsible for un-registering - * formula at current time. - */ -export class UniFormulaController { - constructor( - @IUniverInstanceService private readonly _univerInstanceSrv: IUniverInstanceService - ) { - - } +export enum UniUIPart { + OUTLINE = 'OUTLINE', } +export function Outline() { + const outlineComponent = useComponentsOfPart(UniUIPart.OUTLINE); + return ( +
+ +
+ ); +} + +Outline.componentName = 'Outline'; diff --git a/packages-experimental/uniui/src/views/uni-controls/UniControls.tsx b/packages-experimental/uniui/src/views/uni-controls/UniControls.tsx new file mode 100644 index 0000000000..20e5d33db3 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-controls/UniControls.tsx @@ -0,0 +1,177 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + useReactFlow, +} from '@xyflow/react'; +import React, { useCallback, useEffect, useLayoutEffect } from 'react'; +import { CheckMarkSingle, FullscreenSingle, IncreaseSingle, ZoomReduceSingle } from '@univerjs/icons'; +import { Dropdown, Tooltip } from '@univerjs/design'; +import clsx from 'clsx'; +import { useDependency } from '@univerjs/core'; +import { UniDiv } from '../uni-toolbar/UniFloatToolbar'; +import type { IProjectNode } from '../../services/unit-grid/unit-grid.service'; +import { IUnitGridService } from '../../services/unit-grid/unit-grid.service'; +import styles from './index.module.less'; + +export const UniControlButton = (props: { tooltips: string; children?: React.ReactElement; onClick: () => void; style?: React.CSSProperties }) => { + const { children, onClick, style, tooltips } = props; + return ( + +
+ {children} +
+
+ + ); +}; + +export const MAX_ZOOM = 2; +export const MIN_ZOOM = 0.5; +export const DEFAULT_ZOOM = 1; + +// 0 means fit view +const shortcuts = [50, 75, 100, 125, 150, 175, 200, 0]; + +export enum UniControlItem { + AI = 'AI', +} + +export const UniControls = ({ zoom, onItemClick }: { zoom: number; onItemClick?: (key: UniControlItem) => void }) => { + const zoomPercent = Math.floor(zoom * 100); + const { zoomIn, zoomOut, fitView, setViewport, getNodes, setCenter, getZoom } = useReactFlow(); + const unitGridService = useDependency(IUnitGridService); + + const onZoomInHandler = () => { + zoomIn(); + }; + const onZoomOutHandler = () => { + zoomOut(); + }; + + const onFullscreenHandler = () => { + document.body.requestFullscreen(); + }; + + const setZoomAtCenter = useCallback((zoom: number) => { + const nodes = getNodes(); + const xLeftPositions = nodes.map((node) => node.position.x); + const xRightPositions = nodes.map((node) => node.position.x + (node.measured?.width ?? 0)); + const yTopPositions = nodes.map((node) => node.position.y); + const yDownPositions = nodes.map((node) => node.position.y + (node.measured?.height ?? 0)); + const centerX = (Math.min(...xLeftPositions) + Math.max(...xRightPositions)) / 2; + const centerY = (Math.min(...yTopPositions) + Math.max(...yDownPositions)) / 2; + setCenter(centerX, centerY, { zoom }); + }, [getNodes, setCenter]); + + const handleFitView = (node: IProjectNode | null) => { + if (node) { + const { x, y } = node.position; + const width = Number.parseInt(`${node.style?.width}`) || 0; + const height = Number.parseInt(`${node.style?.height}`) || 0; + + // Use the setCenter method to position the viewport to the center of the node. + setCenter(x + width / 2, y + height / 2, { + duration: 300, + zoom: getZoom(), + }); + } + }; + const onZoomMenuChange = useCallback((value: number) => { + if (value === 0) { + fitView(); + } else { + setZoomAtCenter(value / 100); + } + }, [setZoomAtCenter, fitView]); + + useLayoutEffect(() => { + setTimeout(() => { + setZoomAtCenter(1); + }, 1000); + }, [setZoomAtCenter]); + + useEffect(() => { + const newNodeSubscribe = unitGridService.newNode$.subscribe((node) => handleFitView(node)); + + const timer = setTimeout(() => { + fitView(); + }, 1000); + + return () => { + clearTimeout(timer); + newNodeSubscribe.unsubscribe(); + }; + }, [fitView]); + + return ( + + )} + > + + {zoomPercent} + % + + + + + + + onItemClick?.(UniControlItem.AI)} style={{ background: '#274FEE' }}> + + + + + + + + + + +
+ ); +}; + diff --git a/packages-experimental/uniui/src/views/uni-controls/index.module.less b/packages-experimental/uniui/src/views/uni-controls/index.module.less new file mode 100644 index 0000000000..e605dc0711 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-controls/index.module.less @@ -0,0 +1,234 @@ +.uni-controls { + display: flex; + padding: 8px; + align-items: center; + gap: 12px; + border-radius: 10px; + border: 1px solid var(--, #ecedef); + background: #fff; + + box-shadow: 0px 2px 4px 0px rgba(30, 34, 43, 0.06); + width: fit-content; + + position: fixed; + right: 12px; + bottom: 12px; +} + +.uni-control-button { + display: flex; + padding: var(--radius-m, 6px); + align-items: center; + justify-content: center; + border-radius: 8px; + box-sizing: border-box; + width: 32px; + height: 32px; + + &:hover { + background: rgba(30, 34, 43, 0.05); + } +} + +@slider-bg: rgb(var(--text-color-secondary)); +@slider-width: 116px; +@slider-height: 2px; +@slider-radius: 15px; + +@slider-handle-size: 12px; + +@slider-value-width: 55px; +@slider-value-height: 28px; + +@slider-parts-hover-color: rgb(var(--border-color)); + +:export { + --slider-width: unit(@slider-width); + --slider-handle-size: unit(@slider-handle-size); +} + +.slider { + user-select: none; + display: flex; + gap: var(--margin-xxs); + align-items: center; + + &-disabled { + cursor: not-allowed; + + .slider { + &-rail { + background-color: rgb(var(--grey-300)); + } + + &-handle { + background-color: rgb(var(--grey-300)); + box-shadow: none; + + &:hover { + cursor: not-allowed; + background-color: rgb(var(--grey-300)); + } + } + + &-reset-point { + cursor: not-allowed; + } + + &-value { + &:hover { + cursor: not-allowed; + background-color: rgb(var(--grey-300)); + } + } + + &-value { + pointer-events: none; + color: rgb(var(--grey-300)); + + &:hover { + background-color: transparent; + } + } + } + } + + &-rail { + @padding: (@slider-handle-size / 2); + + position: relative; + + width: @slider-width; + height: @slider-height; + padding: 0 @padding; + + background-color: @slider-bg; + border-radius: @slider-radius; + } + + &-inner-rail { + position: relative; + height: @slider-height; + } + + &-handle { + cursor: pointer; + + position: absolute; + top: calc(50% - @slider-handle-size / 2); + transform: translateX(-50%); + + width: @slider-handle-size; + height: @slider-handle-size; + + background-color: rgb(var(--color-white)); + border-radius: 50%; + box-shadow: 0 0 6px 0 rgb(0 0 0 / 10%); + + transition: background 0.2s; + + &:hover { + background-color: @slider-parts-hover-color; + box-shadow: none; + } + } + + &-reset-point { + cursor: pointer; + + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + box-sizing: border-box; + width: @slider-height * 2; + height: @slider-height; + + background-color: rgb(var(--color-white)); + border-radius: 50%; + } + + &-value { + cursor: pointer; + + width: @slider-value-width; + height: @slider-value-height; + + font-size: var(--font-size-xs); + line-height: 2; + color: rgb(var(--text-color-secondary-darker)); + text-align: center; + + border-radius: var(--border-radius-base); + + transition: all 0.2s; + + text-decoration: none; + + &:hover { + background-color: @slider-parts-hover-color; + text-decoration: inherit; + color: inherit; + } + + &.dropdown-open { + background-color: @slider-parts-hover-color; + } + } + + &-shortcuts { + transform: translateX(-(@slider-value-width / 2)); + + display: grid; + gap: var(--margin-xxs); + align-items: center; + + box-sizing: border-box; + width: 128px; + padding: var(--padding-sm); + + font-size: var(--font-size-xs); + + background-color: rgb(var(--color-white)); + border: 1px solid rgb(var(--border-color)); + border-radius: var(--border-radius-lg); + box-shadow: var(--box-shadow-lg); + } + + &-shortcut { + cursor: pointer; + + position: relative; + + box-sizing: border-box; + padding: var(--padding-xs) 0 var(--padding-xs) 36px; + + color: rgb(var(--text-color)); + + border-radius: var(--border-radius-base); + + transition: background 0.2s; + text-decoration: none; + + &:hover { + background-color: rgb(var(--bg-color-hover)); + color: inherit; + text-decoration: inherit; + } + } + + &-shortcut-icon { + position: absolute; + top: 0; + left: var(--padding-xs); + height: 100%; + color: rgb(var(--success-color)); + display: flex; + align-items: center; + } + + &-shortcut-active { + background-color: rgb(var(--bg-color-hover)); + } +} diff --git a/packages-experimental/uniui/src/views/uni-sidebar/UniSidebar.tsx b/packages-experimental/uniui/src/views/uni-sidebar/UniSidebar.tsx new file mode 100644 index 0000000000..c34b341576 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-sidebar/UniSidebar.tsx @@ -0,0 +1,130 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CloseSingle } from '@univerjs/icons'; +import { useDependency } from '@univerjs/core'; +import clsx from 'clsx'; +import React, { useEffect, useMemo, useRef } from 'react'; +import type { ISidebarMethodOptions } from '@univerjs/ui'; +import { CustomLabel, ILeftSidebarService, ISidebarService, useObservable } from '@univerjs/ui'; +import styles from './index.module.less'; + +export interface IUniSidebarProps { + position: 'left' | 'right'; + sidebarService: ISidebarService; + showClose?: boolean; +} + +export function LeftSidebar() { + const sidebarService = useDependency(ILeftSidebarService); + return ; +} + +export function RightSidebar() { + const sidebarService = useDependency(ISidebarService); + return ; +} + +export function UniSidebar(props: IUniSidebarProps) { + const { sidebarService, position, showClose = true } = props; + const sidebarOptions = useObservable(sidebarService.sidebarOptions$); + const scrollRef = useRef(null); + + const options = useMemo(() => { + if (!sidebarOptions) { + return null; + } + + const copy = { ...sidebarOptions } as Omit & { + children?: React.ReactNode; + header?: React.ReactNode; + footer?: React.ReactNode; + }; + + for (const key of ['children', 'header', 'footer']) { + const k = key as keyof ISidebarMethodOptions; + + if (sidebarOptions[k]) { + const props = sidebarOptions[k] as any; + + if (props) { + (copy as any)[k] = ; + } + } + } + + return copy; + }, [sidebarOptions]); + + useEffect(() => { + const handleScroll = (e: Event) => { + sidebarService.scrollEvent$.next(e); + }; + const scrollElement = scrollRef.current; + if (scrollElement) { + scrollElement.addEventListener('scroll', handleScroll); + } + + return () => { + scrollElement?.removeEventListener('scroll', handleScroll); + }; + }, [sidebarService]); + + const rootClassName = clsx(styles.uniSidebar, { + [styles.uniSidebarOpen]: options?.visible, + [styles.uniSidebarLeft]: position === 'left', + [styles.uniSidebarRight]: position === 'right', + }); + + const width = useMemo(() => { + if (!options?.visible) return 0; + + if (typeof options.width === 'number') { + return `${options.width}px`; + } + + return options.width; + }, [options]); + + function handleClose() { + const options = { + ...sidebarOptions, + visible: false, + }; + + sidebarService.sidebarOptions$.next(options); + options?.onClose?.(); + } + return ( + + ); +} diff --git a/packages-experimental/uniui/src/views/uni-sidebar/index.module.less b/packages-experimental/uniui/src/views/uni-sidebar/index.module.less new file mode 100644 index 0000000000..5abc52dce7 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-sidebar/index.module.less @@ -0,0 +1,94 @@ +.uni-sidebar { + position: fixed; + + border: 1px solid rgb(var(--border-color)); + border-radius: 10px; + overflow: hidden; + box-sizing: border-box; + box-shadow: var(--box-shadow-lg); + top: 72px; + bottom: 12px; + z-index: 20; + + background-color: rgb(var(--bg-color-secondary)); + color: rgb(var(--text-color)); + + transition: all 0.15s; + + &-left { + left: 12px; + transform: translateX(calc(-100% - 12px)); + width: 180px; + } + + &-right { + min-width: 280px; + max-width: 400px; + right: 12px; + transform: translateX(calc(100% + 12px)); + } + + &&-open { + transform: translateX(0); + } + + &-container { + overflow-y: auto; + display: grid; + grid-template-rows: auto; + // grid-template-rows: auto 1fr auto; + + box-sizing: border-box; + width: 100%; + height: 0; + min-height: 100%; + margin: auto; + + background-color: rgb(var(--bg-color-secondary)); + + scrollbar-color: rgba(var(--scrollbar-color), 0.7) transparent; + scrollbar-gutter: auto; + scrollbar-width: thin; + } + + &-header { + position: sticky; + top: 0; + + display: flex; + align-items: center; + justify-content: space-between; + + height: 44px; + padding: var(--padding-xl) var(--padding-xl) 0; + + font-size: var(--font-size-lg); + font-weight: 500; + + background: rgb(var(--bg-color-secondary)); + box-sizing: border-box; + z-index: 10; + + &-close { + cursor: pointer; + color: rgb(var(--text-color-secondary)); + } + } + + &-body { + padding: var(--padding-sm); + box-sizing: border-box; + + & > div { + height: 100%; + } + } + + &-footer { + position: sticky; + bottom: 0; + padding: var(--padding-xl); + background: rgb(var(--bg-color-secondary)); + box-sizing: border-box; + } +} diff --git a/packages-experimental/uniui/src/views/uni-toolbar/UniFloatToolbar.tsx b/packages-experimental/uniui/src/views/uni-toolbar/UniFloatToolbar.tsx new file mode 100644 index 0000000000..81aeadfcf2 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-toolbar/UniFloatToolbar.tsx @@ -0,0 +1,109 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { flip, offset, shift, useFloating } from '@floating-ui/react-dom'; +import React, { useEffect, useImperativeHandle } from 'react'; +import { IUniverInstanceService, type Nullable, useDependency, useObservable } from '@univerjs/core'; +import { ComponentContainer, ToolbarItem, useComponentsOfPart, useToolbarGroups } from '@univerjs/ui'; +import type { IUnitRendererProps } from '../workbench/UniWorkbench'; +import { DELETE_MENU_ID, DOWNLOAD_MENU_ID, LOCK_MENU_ID, PRINT_MENU_ID, SHARE_MENU_ID, UNI_MENU_POSITIONS, ZEN_MENU_ID } from '../../controllers/menu'; +import styles from './index.module.less'; + +export interface IFloatingToolbarRef { + update: () => void; +} + +export enum UniFloatToolbarUIPart { + NAME = 'name', +} + +const MENU_POSITIONS = [ + UNI_MENU_POSITIONS.TOOLBAR_FLOAT, +]; + +const UNI_FLOATING_TOOLBAR_SCHEMA: string[] = [ + DOWNLOAD_MENU_ID, + SHARE_MENU_ID, + LOCK_MENU_ID, + PRINT_MENU_ID, + ZEN_MENU_ID, + DELETE_MENU_ID, +]; + +export const UniFloatingToolbar = React.forwardRef }>(({ node }, ref) => { + const { x, y, refs, strategy, update } = useFloating({ + placement: 'top', + middleware: [offset(10), flip(), shift({ padding: 5 })], + }); + + const { visibleItems } = useToolbarGroups(MENU_POSITIONS, UNI_MENU_POSITIONS.TOOLBAR_FLOAT); + const uniVisibleItems = UNI_FLOATING_TOOLBAR_SCHEMA.map((id) => visibleItems.find((item) => item.id === id)).filter((item) => !!item); + + const { setReference, setFloating } = refs; + + const toolbarNameComponents = useComponentsOfPart(UniFloatToolbarUIPart.NAME); + + useImperativeHandle(ref, () => ({ + update: () => update(), + }), [update]); + + useEffect(() => { + if (node) { + const ref = document.querySelector(`[data-id="${node.unitId}"]`); + setReference(ref); + } + }, [node, setReference]); + + if (!node) { + return null; + } + + return ( +
+
+ } + /> + +
+ {uniVisibleItems.map((subItem) => )} +
+
+
+ ); +}); + +export function UniDiv() { + return
; +} + +export function UnitName({ unitId }: { unitId: string }) { + const instanceService = useDependency(IUniverInstanceService); + const unit = instanceService.getUnit(unitId); + const name = useObservable(unit?.name$); + return ( +
+ {name} +
+ ); +}; + diff --git a/packages-experimental/uniui/src/views/uni-toolbar/UniFormulaBar.tsx b/packages-experimental/uniui/src/views/uni-toolbar/UniFormulaBar.tsx new file mode 100644 index 0000000000..4453cb4044 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-toolbar/UniFormulaBar.tsx @@ -0,0 +1,235 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useCallback, useLayoutEffect, useState } from 'react'; +import { CheckMarkSingle, CloseSingle, FxSingle } from '@univerjs/icons'; +import type { Nullable, Workbook } from '@univerjs/core'; +import { BooleanNumber, DEFAULT_EMPTY_DOCUMENT_VALUE, DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, DocumentFlavor, HorizontalAlign, IPermissionService, IUniverInstanceService, Rectangle, ThemeService, UniverInstanceType, useDependency, useObservable, VerticalAlign, WrapStrategy } from '@univerjs/core'; +import { IEditorBridgeService, IFormulaEditorManagerService, useActiveWorkbook } from '@univerjs/sheets-ui'; +import { RangeProtectionPermissionEditPoint, RangeProtectionRuleModel, SheetsSelectionsService, WorkbookEditablePermission, WorksheetEditPermission, WorksheetProtectionRuleModel, WorksheetSetCellValuePermission } from '@univerjs/sheets'; +import { EMPTY, merge, switchMap } from 'rxjs'; +import { DeviceInputEventType } from '@univerjs/engine-render'; +import { KeyCode, ProgressBar, TextEditor } from '@univerjs/ui'; +import clsx from 'clsx'; +import styles from './index.module.less'; + +export const UniFormulaBar = () => { + const editorBridgeService = useDependency(IEditorBridgeService); + const univerInstanceService = useDependency(IUniverInstanceService); + + const visibleInfo = useObservable(editorBridgeService.visible$); + const focusedId = useObservable(univerInstanceService.focused$); + const visible = (visibleInfo?.visible && focusedId) || false; + + const handleOpenWrite = useCallback(() => { + if (focusedId) { + editorBridgeService.changeVisible({ + visible: true, + eventType: DeviceInputEventType.PointerDown, + unitId: focusedId, + }); + } + }, [editorBridgeService, focusedId]); + + return ( + <> +
handleOpenWrite()} + > + + + Write formula + +
+ {visible && ( +
+ +
+ )} + + ); +}; + +export function FormulaBar() { + const formulaEditorManagerService = useDependency(IFormulaEditorManagerService); + const editorBridgeService = useDependency(IEditorBridgeService); + const themeService = useDependency(ThemeService); + const progressBarColor = themeService.getCurrentTheme().primaryColor; + const [disable, setDisable] = useState(false); + const univerInstanceService = useDependency(IUniverInstanceService); + const selectionManager = useDependency(SheetsSelectionsService); + const worksheetProtectionRuleModel = useDependency(WorksheetProtectionRuleModel); + const rangeProtectionRuleModel = useDependency(RangeProtectionRuleModel); + const permissionService = useDependency(IPermissionService); + const currentWorkbook = useActiveWorkbook(); + + function getPermissionIds(unitId: string, subUnitId: string): string[] { + return [ + new WorkbookEditablePermission(unitId).id, + new WorksheetSetCellValuePermission(unitId, subUnitId).id, + new WorksheetEditPermission(unitId, subUnitId).id, + ]; + } + + useLayoutEffect(() => { + const workbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; + const subscription = merge( + worksheetProtectionRuleModel.ruleChange$, + rangeProtectionRuleModel.ruleChange$, + selectionManager.selectionMoveEnd$, + workbook.activeSheet$ + ).pipe( + switchMap(() => { + const unitId = workbook.getUnitId(); + const worksheet = workbook.getActiveSheet(); + if (!worksheet) return EMPTY; + + const subUnitId = worksheet.getSheetId(); + const range = selectionManager.getCurrentLastSelection()?.range; + if (!range) return EMPTY; + + const permissionIds = getPermissionIds(unitId, subUnitId); + + const selectionRanges = selectionManager.getCurrentSelections()?.map((selection) => selection.range); + const permissionList = rangeProtectionRuleModel.getSubunitRuleList(unitId, subUnitId).filter((rule) => { + return rule.ranges.some((r) => selectionRanges?.some((selectionRange) => Rectangle.intersects(r, selectionRange))); + }); + + permissionList.forEach((p) => { + permissionIds.push(new RangeProtectionPermissionEditPoint(unitId, subUnitId, p.permissionId).id); + }); + + return permissionService.composePermission$(permissionIds); + }) + ).subscribe((permissions) => { + if (permissions) { + setDisable(!permissions.every((p) => p.value)); + } + }); + + return () => { + subscription.unsubscribe(); + }; + }, []); + + const INITIAL_SNAPSHOT = { + id: DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, + body: { + dataStream: `${DEFAULT_EMPTY_DOCUMENT_VALUE}`, + textRuns: [], + paragraphs: [ + { + startIndex: 0, + }, + ], + }, + documentStyle: { + pageSize: { + width: Number.POSITIVE_INFINITY, + height: Number.POSITIVE_INFINITY, + }, + documentFlavor: DocumentFlavor.MODERN, + marginTop: 5, + marginBottom: 5, + marginRight: 0, + marginLeft: 0, + paragraphLineGapDefault: 0, + renderConfig: { + horizontalAlign: HorizontalAlign.UNSPECIFIED, + verticalAlign: VerticalAlign.TOP, + centerAngle: 0, + vertexAngle: 0, + wrapStrategy: WrapStrategy.WRAP, + isRenderStyle: BooleanNumber.FALSE, + }, + }, + }; + + function resizeCallBack(editor: Nullable) { + if (editor == null) { + return; + } + + const editorRect = editor.getBoundingClientRect(); + + formulaEditorManagerService.setPosition(editorRect); + } + + // Handle click the "×" button. + function handleCloseBtnClick() { + const visibleState = editorBridgeService.isVisible(); + if (visibleState.visible) { + editorBridgeService.changeVisible({ + visible: false, + eventType: DeviceInputEventType.Keyboard, + keycode: KeyCode.ESC, + unitId: currentWorkbook?.getUnitId() ?? '', + }); + } + } + + // Handle click the "√" button. + function handleConfirmBtnClick() { + const visibleState = editorBridgeService.isVisible(); + if (visibleState.visible) { + editorBridgeService.changeVisible({ + visible: false, + eventType: DeviceInputEventType.PointerDown, + unitId: currentWorkbook?.getUnitId() ?? '', + }); + } + } + + function handlerFxBtnClick() { + formulaEditorManagerService.handleFxBtnClick(true); + } + + return ( +
+ e.preventDefault()} + className={clsx(styles.uniFormulaInput, styles.formulaContent)} + snapshot={INITIAL_SNAPSHOT} + isSingle={true} + /> +
+ + + + + + + +
+ +
+ ); +} diff --git a/packages-experimental/uniui/src/views/uni-toolbar/UniToolbar.tsx b/packages-experimental/uniui/src/views/uni-toolbar/UniToolbar.tsx new file mode 100644 index 0000000000..0dc468a0e1 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-toolbar/UniToolbar.tsx @@ -0,0 +1,71 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IUniverInstanceService, UniverInstanceType, useDependency, useObservable } from '@univerjs/core'; +import { MenuPosition, ToolbarItem, useToolbarGroups } from '@univerjs/ui'; +import type { ComponentType } from 'react'; +import React from 'react'; +import { useWorkbooks } from '@univerjs/sheets-ui'; +import { UNI_MENU_POSITIONS } from '../../controllers/menu'; +import { UniToolbarService } from '../../services/toolbar/uni-toolbar-service'; +import styles from './index.module.less'; +import { UniFormulaBar } from './UniFormulaBar'; + +const MENU_POSITIONS = [ + MenuPosition.TOOLBAR_START, + MenuPosition.TOOLBAR_INSERT, + MenuPosition.TOOLBAR_FORMULAS, + MenuPosition.TOOLBAR_DATA, + MenuPosition.TOOLBAR_VIEW, + UNI_MENU_POSITIONS.TOOLBAR_MAIN, +]; + +export interface IToolbarProps { + headerMenuComponents?: Set; +} + +export function UniToolbar() { + const uniToolbarService = useDependency(UniToolbarService); + const instanceService = useDependency(IUniverInstanceService); + const focusedUnit = useObservable(instanceService.focused$); + + const type = focusedUnit ? (instanceService.getUnit(focusedUnit)?.type ?? UniverInstanceType.UNIVER_UNKNOWN) : UniverInstanceType.UNIVER_UNKNOWN; + + const { visibleItems } = useToolbarGroups(MENU_POSITIONS); + + const toolbarItems = uniToolbarService.getItems(); + + const uniVisibleItems = toolbarItems.map((item) => { + const { impl } = item; + const id = impl.find((item) => item.type === type) || impl.find((item) => item.type === UniverInstanceType.UNIVER_UNKNOWN); + if (id !== undefined) { + return visibleItems.find((item) => item.id === id.id); + } + return null; + }).filter((item) => !!item); + + const hasWorkbooks = useWorkbooks().length > 0; + + return ( +
+ { hasWorkbooks && } +
+ {uniVisibleItems.map((subItem) => )} +
+
+ + ); +} diff --git a/packages-experimental/uniui/src/views/uni-toolbar/index.module.less b/packages-experimental/uniui/src/views/uni-toolbar/index.module.less new file mode 100644 index 0000000000..f290d5b0a1 --- /dev/null +++ b/packages-experimental/uniui/src/views/uni-toolbar/index.module.less @@ -0,0 +1,476 @@ +@toolbar-btn-size: 24px; + +.headerbar { + user-select: none; + position: relative; + + > .header-menu { + position: absolute; + top: 0; + right: 0; + + display: flex; + + box-sizing: border-box; + height: 100%; + + > div { + user-select: none; + + display: inline-flex; + align-items: center; + + height: 28px; + margin-left: 4px; + padding: 2px 4px; + + text-align: center; + + border-radius: 4px; + + &:hover { + background-color: rgb(var(--grey-300)); + } + } + } +} + +.menubar { + display: flex; + gap: var(--margin-xs); + align-items: center; + justify-content: center; + + height: 32px; + + &-item { + cursor: pointer; + + display: block; + + padding: var(--padding-xs) var(--padding-sm); + + font-size: var(--font-size-xs); + font-weight: 400; + + border-radius: var(--border-radius-base); + + transition: all 0.1s ease-in-out; + + &:hover:not(&-active) { + background: rgb(var(--grey-300)); + } + + &-active { + color: rgb(var(--color-white)); + background: rgb(var(--primary-color)); + } + } +} + +.uni-toolbar { + display: flex; + height: 48px; + padding: 8px; + align-items: center; + gap: 8px; + + border-radius: 10px; + border: 1px solid var(--, #ecedef); + overflow: hidden; + + box-shadow: 0px 2px 4px 0px rgba(30, 34, 43, 0.06); + + user-select: none; + position: relative; + + box-sizing: border-box; + + font-size: var(--font-size-base); + color: rgb(var(--text-color)); + + background-color: rgb(var(--bg-color-secondary)); + + &-container { + padding: 0 var(--padding-lg); + display: flex; + gap: var(--margin-xs); + align-items: center; + justify-content: center; + + box-sizing: border-box; + height: 100%; + margin: 0 auto; + max-width: 100%; + overflow: hidden; + } + + &-more { + max-width: 80%; + + &-container { + display: grid; + + box-sizing: border-box; + padding: var(--padding-sm); + + background-color: rgb(var(--bg-color-secondary)); + border-radius: var(--border-radius-base); + box-shadow: var(--box-shadow-base); + + .toolbar-group { + flex-wrap: wrap; + + &:not(:last-child) { + &::after { + content: initial; + } + + > * { + margin-bottom: var(--margin-xs); + } + } + } + } + } + + &-group { + display: flex; + gap: var(--margin-xs); + align-items: center; + flex-shrink: 0; + + > * { + flex-shrink: 0; + } + + &:not(:last-child, :empty) { + &::after { + content: ''; + + display: block; + + width: 1px; + height: 20px; + + background-color: rgb(var(--border-color)); + flex-shrink: 0; + } + } + } + + &-item { + &-select-button { + cursor: pointer; + + position: relative; + + overflow: hidden; + display: flex; + gap: var(--margin-xs); + align-items: center; + + height: @toolbar-btn-size; + padding-right: 18px; + + font-size: var(--font-size-lg); + + border-radius: var(--border-radius-base); + + &-disabled { + cursor: not-allowed; + color: rgb(var(--grey-200)); + &.toolbar-item-select-button-activated { + color: rgba(var(--text-color), 0.25); + .toolbar-item-select-button { + &-label, + &-arrow { + &:hover { + background-color: rgb( + var(--grey-100) + ) !important; + } + } + } + } + } + + &-activated { + background-color: rgb(var(--grey-100)); + } + + &:hover { + background-color: rgb(var(--grey-100)); + + .toolbar { + &-item { + &-select-button { + &-label { + background-color: rgb(var(--grey-100)); + } + } + } + } + } + + &-label { + position: relative; + z-index: 1; + + display: flex; + align-items: center; + + height: 100%; + padding: 0 var(--padding-xs); + + &:hover { + background-color: rgb(var(--grey-300)) !important; + } + } + + &-arrow { + position: absolute; + top: 0; + left: 0; + + display: flex; + align-items: center; + justify-content: flex-end; + + box-sizing: border-box; + width: 100%; + height: 100%; + padding-right: var(--padding-xs); + + color: rgb(var(--grey-400)); + + &:hover { + background-color: rgb(var(--grey-300)); + } + } + } + + &-select { + cursor: pointer; + + position: relative; + + display: flex; + gap: var(--margin-xs); + align-items: center; + + height: @toolbar-btn-size; + padding: 0 var(--padding-xs); + + font-size: var(--font-size-lg); + + border-radius: var(--border-radius-base); + + &-disabled { + cursor: not-allowed; + color: rgb(var(--grey-200)); + &.toolbar-item-select-activated { + color: rgba(var(--text-color), 0.25); + &:hover { + background-color: rgb(var(--grey-100)); + } + } + } + + &-activated { + background-color: rgb(var(--grey-100)); + } + + &:hover { + background-color: rgb(var(--grey-100)); + border-radius: var(--border-radius-base); + } + + &-arrow { + display: flex; + align-items: center; + + height: 100%; + + color: rgb(var(--grey-400)); + + &-disabled { + cursor: not-allowed; + color: rgb(var(--grey-200)); + &.toolbar-item-select-arrow-activated { + color: rgba(var(--text-color), 0.25); + &:hover { + background-color: rgb(var(--grey-100)); + } + } + } + + &-activated { + background-color: rgb(var(--grey-100)); + } + } + } + } +} + +.uni-formula-bar { + display: flex; + width: 133px; + padding: var(--radius-m, 6px) 12px; + + align-items: center; + gap: 8px; + border-radius: var(--radius-m, 6px); + + background: #f0f3fe; + color: var(---Hyacinth-Blue-500, #274fee); + font-style: normal; + font-weight: 500; + line-height: 20px; /* 153.846% */ + + &-text { + white-space: nowrap; + font-size: 13px; + } + &-disable { + opacity: 0.3; + } +} + +.uni-floating-toolbar { + display: inline-flex; + height: 48px; + padding: 8px; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.unit-name { + display: flex; + height: 24px; + padding: 0px 4px; + align-items: center; + gap: 4px; + border-radius: var(--radius-m, 6px); + background: var(---White, #fff); + color: #1e222b; + font-size: 13px; + font-weight: 400; +} + +.uni-div { + width: 1px; + height: 24px; + background: #ecedef; +} + +.uni-formula-bar-full-input { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(---White, #fff); + z-index: 10; + + .uni-formula-box { + position: relative; + display: flex; + align-items: center; + gap: 8px; + height: 100%; + box-sizing: border-box; + padding: 8px; + } + + .uni-formula-input { + position: relative; + flex: 1; + background: #f0f3fe; + overflow: hidden; + border-radius: var(--radius-m, 6px); + border: 1px solid var(---Hyacinth-Blue-500, #274fee); + background: #f0f3fe; + box-shadow: 0px 0px 4px 0px rgba(39, 79, 238, 0.4); + height: 100%; + } + + .formula-icon { + padding: 6px 0; + + &-disable { + color: rgb(var(--grey-200)); + cursor: not-allowed; + } + + &-wrapper { + position: relative; + + display: flex; + align-items: center; + justify-content: center; + + box-sizing: border-box; + width: 80px; + height: 100%; + + font-size: var(--font-size-xs); + + border-right: 1px solid rgb(var(--border-color)); + + .icon-container { + display: flex; + align-items: center; + justify-content: center; + + padding: var(--padding-xs); + + font-size: var(--font-size-lg); + + border-radius: var(--border-radius-base); + + &-success { + &.formula-active { + cursor: pointer; + color: rgb(var(--success-color)); + &:hover { + background-color: rgb(var(--grey-50)); + } + } + } + + &-error { + &.formula-active { + cursor: pointer; + color: rgb(var(--error-color)); + &:hover { + background-color: rgb(var(--grey-50)); + } + } + } + + &-fx { + cursor: pointer; + &:hover { + background-color: rgb(var(--grey-50)); + } + } + } + } + } + .formula-icon { + display: flex; + } + .icon-container { + display: flex; + padding: var(--radius-m, 6px); + align-items: center; + gap: 10px; + border-radius: 8px; + &:hover { + background: var(--hyacinth-blue-5007, rgba(39, 79, 238, 0.07)); + color: #274fee; + } + } +} diff --git a/packages-experimental/uniui/src/views/workbench/UniWorkbench.tsx b/packages-experimental/uniui/src/views/workbench/UniWorkbench.tsx index 5f3fd598d3..f6d552a641 100644 --- a/packages-experimental/uniui/src/views/workbench/UniWorkbench.tsx +++ b/packages-experimental/uniui/src/views/workbench/UniWorkbench.tsx @@ -16,9 +16,9 @@ // Refer to packages/ui/src/views/App.tsx -import { IContextService, IUniverInstanceService, LocaleService, ThemeService, useDependency } from '@univerjs/core'; -import { ConfigContext, ConfigProvider, defaultTheme, themeInstance } from '@univerjs/design'; +import { debounce, ICommandService, IContextService, IUniverInstanceService, LocaleService, ThemeService, useDependency } from '@univerjs/core'; import type { ILocale } from '@univerjs/design'; +import { ConfigContext, ConfigProvider, defaultTheme, themeInstance } from '@univerjs/design'; import { builtInGlobalComponents, BuiltInUIPart, @@ -26,31 +26,49 @@ import { ContextMenu, IMessageService, type IWorkbenchOptions, - Sidebar, - Toolbar, UNI_DISABLE_CHANGING_FOCUS_KEY, useComponentsOfPart, useObservable, - ZenZone, } from '@univerjs/ui'; +import type { + NodeTypes, + ReactFlowInstance, + Viewport, +} from '@xyflow/react'; +import { + Background, + NodeResizer, + ReactFlow, + ReactFlowProvider, + useNodesState, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import clsx from 'clsx'; import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import clsx from 'clsx'; -import { UnitGridService } from '../../services/unit-grid/unit-grid.service'; -import styles from './workbench.module.less'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { MenuSingle } from '@univerjs/icons'; +import { UniFocusUnitOperation } from '../../commands/operations/uni-focus-unit.operation'; +import { FlowManagerService } from '../../services/flow/flow-manager.service'; +import { IUnitGridService } from '../../services/unit-grid/unit-grid.service'; +import { useUnitFocused, useUnitTitle } from '../hooks/title'; +import { DEFAULT_ZOOM, MAX_ZOOM, MIN_ZOOM, UniControlItem, UniControls } from '../uni-controls/UniControls'; +import { LeftSidebar, RightSidebar } from '../uni-sidebar/UniSidebar'; +import { type IFloatingToolbarRef, UniFloatingToolbar } from '../uni-toolbar/UniFloatToolbar'; +import { UniToolbar } from '../uni-toolbar/UniToolbar'; +import styles from './workbench.module.less'; // Refer to packages/ui/src/views/workbench/Workbench.tsx export interface IUniWorkbenchProps extends IWorkbenchOptions { mountContainer: HTMLElement; - onRendered: () => void; + onRendered: (contentEl: HTMLElement) => void; } export function UniWorkbench(props: IUniWorkbenchProps) { const { header = true, - footer = true, contextMenu = true, mountContainer, onRendered, @@ -59,36 +77,50 @@ export function UniWorkbench(props: IUniWorkbenchProps) { const localeService = useDependency(LocaleService); const themeService = useDependency(ThemeService); const messageService = useDependency(IMessageService); - const unitGridService = useDependency(UnitGridService); + const unitGridService = useDependency(IUnitGridService); const instanceService = useDependency(IUniverInstanceService); + const renderManagerService = useDependency(IRenderManagerService); + const flowManagerService = useDependency(FlowManagerService); + const commandService = useDependency(ICommandService); const contentRef = useRef(null); + const floatingToolbarRef = useRef(null); + const reactFlowInstance = useRef(null); - const footerComponents = useComponentsOfPart(BuiltInUIPart.FOOTER); const headerComponents = useComponentsOfPart(BuiltInUIPart.HEADER); - const headerMenuComponents = useComponentsOfPart(BuiltInUIPart.HEADER_MENU); const contentComponents = useComponentsOfPart(BuiltInUIPart.CONTENT); - const leftSidebarComponents = useComponentsOfPart(BuiltInUIPart.LEFT_SIDEBAR); const globalComponents = useComponentsOfPart(BuiltInUIPart.GLOBAL); - const unitGrid = useObservable(unitGridService.unitGrid$, undefined, true); - const focused = useObservable(instanceService.focused$); - - const focusUnit = useCallback((unitId: string) => { - instanceService.focusUnit(unitId); - instanceService.setCurrentUnitForType(unitId); - }, [instanceService]); + const focusedUnit = useObservable(instanceService.focused$); useEffect(() => { if (!themeService.getCurrentTheme()) { themeService.setTheme(defaultTheme); } - - onRendered(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + if (contentRef.current) { + onRendered?.(contentRef.current); + } + }, [onRendered]); + const [locale, setLocale] = useState(localeService.getLocales() as unknown as ILocale); + const [zoom, setZoom] = useState(DEFAULT_ZOOM); + + const onControlItemClick = useCallback((key: UniControlItem) => { + switch (key) { + case UniControlItem.AI: { + commandService.executeCommand('project.controls-pro-search.operation'); + break; + } + } + }, [commandService]); + // eslint-disable-next-line react-hooks/exhaustive-deps + const resizeUnits = useCallback(debounce(() => { + renderManagerService.getRenderAll().forEach(((renderer) => renderer.engine.resize())); + }, 400), [renderManagerService]); // TODO: this is not // Create a portal container for injecting global component themes. const portalContainer = useMemo(() => document.createElement('div'), []); @@ -116,6 +148,43 @@ export function UniWorkbench(props: IUniWorkbenchProps) { }; }, [localeService, messageService, mountContainer, portalContainer, themeService.currentTheme$]); + const nodeTypes: NodeTypes = { + customNode: UnitNode, + }; + + const unitGrid = useObservable(unitGridService.unitGrid$, undefined, true); + const gridNodes = useMemo(() => unitGrid.map((item) => ({ + id: item.id, + type: 'customNode', + dragHandle: '.univer-uni-node-drag-handle', + style: item.style, + focusable: true, + data: { + unitId: item.data.unitId, + gridService: unitGridService, + instanceService, + }, + position: item.position, + })), [unitGrid]); + + const [nodes, setNodes, onNodesChange] = useNodesState(gridNodes); + useEffect(() => { + setNodes(gridNodes); + }, [gridNodes]); + + const onMove = useCallback((event: MouseEvent | TouchEvent | null, viewport: Viewport) => { + floatingToolbarRef.current?.update(); + const { zoom } = viewport; + setZoom(zoom); + flowManagerService.setViewportChanged(viewport); + }, [floatingToolbarRef, setZoom, flowManagerService]); + + const onFlowInit = useCallback((instance: ReactFlowInstance) => { + flowManagerService.setReactFlowInstance(instance); + }, [flowManagerService]); + + const disableReactFlowBehavior = !!focusedUnit; + return ( {/** @@ -123,85 +192,102 @@ export function UniWorkbench(props: IUniWorkbenchProps) { * all focusin event merged from its descendants. The DesktopLayoutService would listen to focusin events * bubbled to this element and refocus the input element. */} -
e.stopPropagation()}> - {/* header */} - {header && ( -
- -
- )} + +
e.stopPropagation()}> - {/* content */} -
-
- +
+
e.preventDefault()} + > + { + nodes.forEach((node) => { + if (node.type === 'dimensions' && node.resizing) { + unitGridService.changeDimension(node.id, { width: node.dimensions!.width, height: node.dimensions!.height }); + } else if (node.type === 'position') { + unitGridService.changePosition(node.id, { x: node.position!.x, y: node.position!.y }); + } + }); -
-
- {header && } -
- -
e.preventDefault()} + onNodesChange(nodes); + }} + onResize={resizeUnits} + fitView + defaultViewport={{ zoom: MIN_ZOOM, x: 0, y: 0 }} + onPointerDown={(event) => { + if (event.target instanceof HTMLElement + && ( + event.target.classList.contains('univer-render-canvas') + || event.target.classList.contains('react-flow__resize-control')) + ) { + return; + } + commandService.executeCommand(UniFocusUnitOperation.id, { unitId: null }); + }} + onMove={onMove} + onInit={onFlowInit} > - {/* Render units. */} - {unitGrid?.map((unitId) => ( - - ))} - -
+ + + + {/* Sheet cell editors etc. Their size would not be affected the scale of ReactFlow. */} +
- -
+
+ {/* header */} + {header && ( +
+
+ + +
+
+ )} + n.id === focusedUnit)?.data} /> - {/* footer */} - {footer && ( -
- -
- )} + + - -
-
- - - {contextMenu && } - + {/* uni mode controller buttons */} + +
+ + + + {contextMenu && } + +
); } -interface IUnitRendererProps { - unitId: string; - focused: boolean; - - gridService: UnitGridService; - instanceService: IUniverInstanceService; - - onFocus: (unitId: string) => void; +interface IUnitNodeProps { + data: IUnitRendererProps; } -function UnitRenderer(props: IUnitRendererProps) { - const { unitId, gridService, focused, onFocus } = props; - const mountRef = useRef(null); +function UnitNode({ data }: IUnitNodeProps) { + const { unitId } = data; + const title = useUnitTitle(unitId); + const focused = useUnitFocused(unitId); + const instanceService = useDependency(IUniverInstanceService); const contextService = useDependency(IContextService); + const commandService = useDependency(ICommandService); + const disableChangingUnitFocusing = useObservable( () => contextService.subscribeContextValue$(UNI_DISABLE_CHANGING_FOCUS_KEY), false, @@ -211,9 +297,45 @@ function UnitRenderer(props: IUnitRendererProps) { const focus = useCallback(() => { if (!disableChangingUnitFocusing && !focused) { - onFocus(unitId); + commandService.executeCommand(UniFocusUnitOperation.id, { unitId }); } - }, [focused, onFocus, disableChangingUnitFocusing, unitId]); + }, [disableChangingUnitFocusing, focused, unitId, commandService]); + + return ( +
+ + + +
+ +
+ +
+ {title} +
+
+ + ); +} + +export interface IUnitRendererProps { + unitId: string; + + gridService: IUnitGridService; + instanceService: IUniverInstanceService; +} + +function UnitRenderer(props: IUnitRendererProps) { + const { unitId, gridService } = props; + const mountRef = useRef(null); + + const instanceService = useDependency(IUniverInstanceService); + + const focusedUnit = useObservable(instanceService.focused$); + const focused = focusedUnit === unitId; useEffect(() => { if (mountRef.current) { @@ -229,8 +351,7 @@ function UnitRenderer(props: IUnitRendererProps) { ref={mountRef} // We bind these focusing events on capture phrase so the // other event handlers would have correct currently focused unit. - onPointerUpCapture={focus} - onWheelCapture={focus} + onWheel={(event) => event.stopPropagation()} > ); diff --git a/packages-experimental/uniui/src/views/workbench/workbench.module.less b/packages-experimental/uniui/src/views/workbench/workbench.module.less index 033a761ab2..32b9908498 100644 --- a/packages-experimental/uniui/src/views/workbench/workbench.module.less +++ b/packages-experimental/uniui/src/views/workbench/workbench.module.less @@ -12,6 +12,11 @@ // height: 32px; } + &-toolbar-wrapper { + max-width: calc(100% - 700px); + pointer-events: auto; + } + &-container { position: relative; display: flex; @@ -23,6 +28,10 @@ position: relative; z-index: 10; width: 100%; + display: flex; + justify-content: center; + align-items: center; + padding-top: 12px; } &-wrapper { @@ -31,6 +40,7 @@ grid-template-rows: 100%; height: 100%; overflow: hidden; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); } &-content { @@ -48,12 +58,15 @@ &-canvas-container { position: relative; display: flex; + height: 100%; + width: 100%; } &-canvas { flex: 1; position: relative; overflow: hidden; + border-radius: 8px; border: 1px solid transparent; &-focused { @@ -62,15 +75,77 @@ } &-left-sidebar { - height: 100%; - } - - &-sidebar { - height: 100%; + width: 340px; } } &-footer { - // height: 36px; + position: absolute; + bottom: 20px; + left: 20px; + width: calc(100% - 700px); + background-color: rgb(var(--bg-color-secondary)); + border: 2px solid rgb(var(--border-color)); + border-radius: 8px; + pointer-events: auto; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + } +} + +:global { + .react-flow__attribution { + display: none !important; + } +} + +.float-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + + > * { + pointer-events: all; + } +} + +.flow-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.uni-node { + &-title { + position: absolute; + top: -24px; + left: 0; + font-size: 11px; + color: rgb(var(--grey-600)); + } + + &-container { + width: 100%; + height: 100%; + position: relative; + display: flex; + } + + &-drag-handle { + display: flex; + border-radius: 4px; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + background-color: rgb(var(--bg-color-secondary)); + position: absolute; + left: -32px; + padding: 4px; + height: 18px; + width: 18px; + justify-content: center; + align-items: center; } } diff --git a/packages/core/src/common/unit.ts b/packages/core/src/common/unit.ts index 35e8092f7f..c817d6baa9 100644 --- a/packages/core/src/common/unit.ts +++ b/packages/core/src/common/unit.ts @@ -15,6 +15,7 @@ */ import type { UniverType } from '@univerjs/protocol'; +import type { Observable } from 'rxjs'; import { Disposable } from '../shared'; export { UniverType as UniverInstanceType } from '@univerjs/protocol'; @@ -24,4 +25,7 @@ export type UnitType = UniverType | number; export abstract class UnitModel<_D = object, T extends UnitType = UnitType> extends Disposable { abstract readonly type: T; abstract getUnitId(): string; + + abstract name$: Observable; + abstract setName(name: string): void; } diff --git a/packages/core/src/docs/data-model/document-data-model.ts b/packages/core/src/docs/data-model/document-data-model.ts index 994fa73a6f..a82f1e642b 100644 --- a/packages/core/src/docs/data-model/document-data-model.ts +++ b/packages/core/src/docs/data-model/document-data-model.ts @@ -50,12 +50,21 @@ class DocumentDataModelSimple extends UnitModel(''); + override name$ = this._name$.asObservable(); + protected snapshot: IDocumentData; constructor(snapshot: Partial) { super(); this.snapshot = { ...DEFAULT_DOC, ...snapshot }; + this._name$.next(this.snapshot.title ?? 'No Title'); + } + + setName(name: string) { + this.snapshot.title = name; + this._name$.next(name); } get drawings() { @@ -206,9 +215,6 @@ export class DocumentDataModel extends DocumentDataModelSimple { footerModelMap: Map = new Map(); - private _name$ = new BehaviorSubject(''); - readonly name$ = this._name$.asObservable(); - constructor(snapshot: Partial) { super(Tools.isEmptyObject(snapshot) ? getEmptySnapshot() : snapshot); @@ -220,11 +226,6 @@ export class DocumentDataModel extends DocumentDataModelSimple { this._name$.next(this.snapshot.title ?? ''); } - setName(name: string) { - this.snapshot.title = name; - this._name$.next(name); - } - override dispose() { super.dispose(); this.headerModelMap.forEach((header) => { diff --git a/packages/core/src/docs/data-model/text-x/apply-utils/common.ts b/packages/core/src/docs/data-model/text-x/apply-utils/common.ts index 9410e7ee63..cb3d104611 100644 --- a/packages/core/src/docs/data-model/text-x/apply-utils/common.ts +++ b/packages/core/src/docs/data-model/text-x/apply-utils/common.ts @@ -409,8 +409,10 @@ export function insertCustomRanges( body.dataStream[oldCustomRange.endIndex] === DataStreamTreeTokenType.CUSTOM_RANGE_END; if (isClosed) { + insertCustomRanges.push(customRange); continue; } + // old is start if (body.dataStream[oldCustomRange.startIndex] === DataStreamTreeTokenType.CUSTOM_RANGE_START) { oldCustomRange.endIndex = customRange.endIndex; diff --git a/packages/core/src/docs/data-model/types.ts b/packages/core/src/docs/data-model/types.ts index c03da36684..6795e4056e 100644 --- a/packages/core/src/docs/data-model/types.ts +++ b/packages/core/src/docs/data-model/types.ts @@ -50,3 +50,8 @@ export enum DataStreamTreeTokenType { LETTER = '', SPACE = ' ', } + +/** Wrap your stream in a pair of custom range tokens. */ +export function makeCustomRangeStream(stream: string): string { + return `${DataStreamTreeTokenType.CUSTOM_RANGE_START}${stream}${DataStreamTreeTokenType.CUSTOM_RANGE_END}`; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a0a579b4a3..700b235eb9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -153,6 +153,9 @@ export { SlideDataModel } from './slides/slide-model'; export * from './types/const'; export * from './types/enum'; export * from './types/interfaces'; + +// #region deprecated - these modules shot not be here + export { ISnapshotServerService } from './services/snapshot/snapshot-server.service'; export { transformSnapshotToWorkbookData, @@ -160,6 +163,7 @@ export { transformDocumentDataToSnapshot, transformSnapshotToDocumentData, generateTemporarySnap, + generateTempDocumentSnapshot, } from './services/snapshot/snapshot-transform'; export { textEncoder, textDecoder } from './services/snapshot/snapshot-utils'; export { type ILogContext } from './services/log/context'; @@ -167,6 +171,8 @@ export { b64DecodeUnicode, b64EncodeUnicode } from './shared/coder'; export { ClientSnapshotServerService } from './services/snapshot/snapshot-server.service'; export { getSheetBlocksFromSnapshot } from './services/snapshot/snapshot-transform'; +// #endregion + export { isBlackColor, isWhiteColor } from './shared/color/color-kit'; export { cellToRange } from './shared/common'; diff --git a/packages/core/src/services/command/command.service.ts b/packages/core/src/services/command/command.service.ts index 4774ca57cb..72a741cdcf 100644 --- a/packages/core/src/services/command/command.service.ts +++ b/packages/core/src/services/command/command.service.ts @@ -207,7 +207,7 @@ export class CommandRegistry { } } -interface ICommandExecutionStackItem extends ICommandInfo {} +interface ICommandExecutionStackItem extends ICommandInfo { } export const NilCommand: ICommand = { id: 'nil', @@ -319,40 +319,45 @@ export class CommandService extends Disposable implements ICommandService { params?: P | undefined, options?: IExecutionOptions ): R { - const item = this._commandRegistry.getCommand(id); - if (item) { - const [command] = item; - const commandInfo: ICommandInfo = { - id: command.id, - type: command.type, - params, - }; + try { + const item = this._commandRegistry.getCommand(id); + if (item) { + const [command] = item; + const commandInfo: ICommandInfo = { + id: command.id, + type: command.type, + params, + }; - // If the executed command is of type `Mutation`, we should add a trigger params, - // whose value is the command's ID that triggers the mutation. - if (command.type === CommandType.MUTATION) { - const triggerCommand = findLast( - this._commandExecutionStack, - (item) => item.type === CommandType.COMMAND - ); - if (triggerCommand) { - commandInfo.params = commandInfo.params ?? {}; - (commandInfo.params as IMutationCommonParams).trigger = triggerCommand.id; + // If the executed command is of type `Mutation`, we should add a trigger params, + // whose value is the command's ID that triggers the mutation. + if (command.type === CommandType.MUTATION) { + const triggerCommand = findLast( + this._commandExecutionStack, + (item) => item.type === CommandType.COMMAND + ); + if (triggerCommand) { + commandInfo.params = commandInfo.params ?? {}; + (commandInfo.params as IMutationCommonParams).trigger = triggerCommand.id; + } } + + const stackItemDisposable = this._pushCommandExecutionStack(commandInfo); + + this._beforeCommandExecutionListeners.forEach((listener) => listener(commandInfo, options)); + const result = this._syncExecute(command as ICommand, params, options); + this._commandExecutedListeners.forEach((listener) => listener(commandInfo, options)); + + stackItemDisposable.dispose(); + + return result; } - const stackItemDisposable = this._pushCommandExecutionStack(commandInfo); - - this._beforeCommandExecutionListeners.forEach((listener) => listener(commandInfo, options)); - const result = this._syncExecute(command as ICommand, params, options); - this._commandExecutedListeners.forEach((listener) => listener(commandInfo, options)); - - stackItemDisposable.dispose(); - - return result; + throw new Error(`[CommandService]: command "${id}" is not registered.`); + } catch (error) { + this._logService.error(error); + throw error; } - - throw new Error(`[CommandService]: command "${id}" is not registered.`); } private _pushCommandExecutionStack(stackItem: ICommandExecutionStackItem): IDisposable { diff --git a/packages/core/src/services/context/context.ts b/packages/core/src/services/context/context.ts index 39b2cc41e2..a2e960edae 100644 --- a/packages/core/src/services/context/context.ts +++ b/packages/core/src/services/context/context.ts @@ -14,6 +14,16 @@ * limitations under the License. */ +// NOTE: editors has the following states: +// 1. Not focused. The `contenteditable` is not focused and the editor would not respond +// to keyboard events. +// 2. Focused (but not activated). The `contenteditable` is focused and editor +// would respond to keyboard events. This is the state when a user focus a spreadsheet +// without the editor being visible. +// 3. Activated. User can see the cursor blinking. + +export const FOCUSING_UNIT = 'FOCUSING_UNIT'; + export const FOCUSING_SHEET = 'FOCUSING_SHEET'; export const FOCUSING_DOC = 'FOCUSING_DOC'; export const FOCUSING_SLIDE = 'FOCUSING_SLIDE'; @@ -25,14 +35,19 @@ export const EDITOR_ACTIVATED = 'EDITOR_ACTIVATED'; export const FOCUSING_EDITOR_INPUT_FORMULA = 'FOCUSING_EDITOR_INPUT_FORMULA'; /** The focusing state of the formula editor (Fx bar). */ -export const FOCUSING_FORMULA_EDITOR = 'FOCUSING_FORMULA_EDITOR'; +export const FOCUSING_FX_BAR_EDITOR = 'FOCUSING_FX_BAR_EDITOR'; export const FOCUSING_UNIVER_EDITOR = 'FOCUSING_UNIVER_EDITOR'; export const FOCUSING_EDITOR_STANDALONE = 'FOCUSING_EDITOR_INPUT_FORMULA'; + +// WTF: what does this even means? export const FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE = 'FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE'; /** * The focusing state of the common drawings. */ export const FOCUSING_COMMON_DRAWINGS = 'FOCUSING_COMMON_DRAWINGS'; + +export const FORMULA_EDITOR_ACTIVATED = 'FORMULA_EDITOR_ACTIVATED'; + diff --git a/packages/core/src/services/instance/instance.service.ts b/packages/core/src/services/instance/instance.service.ts index ea33e920b5..2ecfb0f534 100644 --- a/packages/core/src/services/instance/instance.service.ts +++ b/packages/core/src/services/instance/instance.service.ts @@ -24,11 +24,22 @@ import type { Nullable } from '../../shared'; import { Disposable } from '../../shared/lifecycle'; import { Workbook } from '../../sheets/workbook'; import { SlideDataModel } from '../../slides/slide-model'; -import { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE } from '../context/context'; +import { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../context/context'; import { IContextService } from '../context/context.service'; import type { UnitModel, UnitType } from '../../common/unit'; import { UniverInstanceType } from '../../common/unit'; +export type UnitCtor = new (...args: any[]) => UnitModel; + +export interface ICreateUnitOptions { + /** + * If Univer should make the new unit as current of its type. + * + * @default true + */ + makeCurrent?: boolean; +} + /** * IUniverInstanceService holds all the current univer instances and provides a set of * methods to add and remove univer instances. @@ -49,9 +60,10 @@ export interface IUniverInstanceService { /** Subscribe to curtain type of units' disposing. */ getTypeOfUnitDisposed$(type: UnitType): Observable; + /** An observable value that emits the id of the focused unit. */ focused$: Observable>; + /** Focus a unit. */ focusUnit(unitId: string | null): void; - getFocusedUnit(): Nullable; getCurrentUnitForType(type: UnitType): Nullable; @@ -60,7 +72,7 @@ export interface IUniverInstanceService { getCurrentTypeOfUnit$(type: UnitType): Observable>; /** Create a unit with snapshot info. */ - createUnit(type: UnitType, data: Partial): U; + createUnit(type: UnitType, data: Partial, options?: ICreateUnitOptions): U; /** Dispose a unit */ disposeUnit(unitId: string): boolean; @@ -98,13 +110,19 @@ export class UniverInstanceService extends Disposable implements IUniverInstance this._focused$.complete(); } - private _createHandler!: (type: UnitType, data: unknown, ctor: new (...args: any[]) => UnitModel) => UnitModel; - __setCreateHandler(handler: (type: UnitType, data: unknown, ctor: new (...args: any[]) => UnitModel) => UnitModel): void { + private _createHandler!: ( + type: UnitType, + data: unknown, + ctor: UnitCtor, + options?: ICreateUnitOptions, + ) => UnitModel; + + __setCreateHandler(handler: (type: UnitType, data: unknown, ctor: UnitCtor, options?: ICreateUnitOptions) => UnitModel): void { this._createHandler = handler; } - createUnit(type: UnitType, data: T): U { - const model = this._createHandler(type, data, this._ctorByType.get(type)!); + createUnit(type: UnitType, data: T, options?: ICreateUnitOptions): U { + const model = this._createHandler(type, data, this._ctorByType.get(type)!, options); return model as U; } @@ -126,7 +144,7 @@ export class UniverInstanceService extends Disposable implements IUniverInstance return this.currentUnits$.pipe(map((units) => units.get(type) ?? null), distinctUntilChanged()) as Observable>; } - getCurrentUnitForType(type: UnitType): Nullable { + getCurrentUnitForType(type: UnitType): Nullable { return this._currentUnits.get(type) as Nullable; } @@ -138,11 +156,6 @@ export class UniverInstanceService extends Disposable implements IUniverInstance this._currentUnits$.next(this._currentUnits); } - private _removeCurrentUnitForType(type: UnitType): void { - this._currentUnits.set(type, null); - this._currentUnits$.next(this._currentUnits); - } - private readonly _unitAdded$ = new Subject(); readonly unitAdded$ = this._unitAdded$.asObservable(); getTypeOfUnitAdded$>(type: UnitType): Observable { @@ -156,17 +169,25 @@ export class UniverInstanceService extends Disposable implements IUniverInstance * * @param unit The unit to be added. */ - __addUnit(unit: UnitModel): void { + __addUnit(unit: UnitModel, options?: ICreateUnitOptions): void { const type = unit.type; if (!this._unitsByType.has(type)) { this._unitsByType.set(type, []); } - this._unitsByType.get(type)!.push(unit); + const units = this._unitsByType.get(type)!; + const newUnitId = unit.getUnitId(); + if (units.findIndex((u) => u.getUnitId() === newUnitId) !== -1) { + throw new Error(`[UniverInstanceService]: cannot create a unit with the same unit id: ${newUnitId}.`); + } + + units.push(unit); this._unitAdded$.next(unit); - this.setCurrentUnitForType(unit.getUnitId()); + if (options?.makeCurrent ?? true) { + this.setCurrentUnitForType(unit.getUnitId()); + } } private _unitDisposed$ = new Subject(); @@ -222,17 +243,28 @@ export class UniverInstanceService extends Disposable implements IUniverInstance this._focused$.next(id); if (this.focused instanceof Workbook) { + this._contextService.setContextValue(FOCUSING_UNIT, true); this._contextService.setContextValue(FOCUSING_DOC, false); this._contextService.setContextValue(FOCUSING_SHEET, true); this._contextService.setContextValue(FOCUSING_SLIDE, false); + this.setCurrentUnitForType(id!); } else if (this.focused instanceof DocumentDataModel) { + this._contextService.setContextValue(FOCUSING_UNIT, true); this._contextService.setContextValue(FOCUSING_DOC, true); this._contextService.setContextValue(FOCUSING_SHEET, false); this._contextService.setContextValue(FOCUSING_SLIDE, false); + this.setCurrentUnitForType(id!); } else if (this.focused instanceof SlideDataModel) { + this._contextService.setContextValue(FOCUSING_UNIT, true); this._contextService.setContextValue(FOCUSING_DOC, false); this._contextService.setContextValue(FOCUSING_SHEET, false); this._contextService.setContextValue(FOCUSING_SLIDE, true); + this.setCurrentUnitForType(id!); + } else { + this._contextService.setContextValue(FOCUSING_UNIT, false); + this._contextService.setContextValue(FOCUSING_DOC, false); + this._contextService.setContextValue(FOCUSING_SHEET, false); + this._contextService.setContextValue(FOCUSING_SLIDE, false); } } @@ -256,16 +288,28 @@ export class UniverInstanceService extends Disposable implements IUniverInstance const index = units.indexOf(unit); units.splice(index, 1); - // Firstly un-mark the unit as "current". - this._removeCurrentUnitForType(type); - this._focused$.next(null); + this._tryRemoveCurrentUnitForType(unitId, type); + this._tryBlurUnitOnRemoval(unitId); - // Then dispose the unit. this._unitDisposed$.next(unit); return true; } + private _tryRemoveCurrentUnitForType(unitId: string, type: UnitType): void { + const current = this.getCurrentUnitForType(type); + if (current?.getUnitId() === unitId) { + this._currentUnits.set(type, null); + this._currentUnits$.next(this._currentUnits); + } + } + + private _tryBlurUnitOnRemoval(unitId: string): void { + if (this.focused?.getUnitId() === unitId) { + this._focused$.next(null); + } + }; + private _getUnitById(unitId: string): Nullable<[UnitModel, UnitType]> { for (const [type, units] of this._unitsByType) { const unit = units.find((unit) => unit.getUnitId() === unitId); diff --git a/packages/core/src/services/resource-manager/type.ts b/packages/core/src/services/resource-manager/type.ts index 3f71085322..dbcf85ba43 100644 --- a/packages/core/src/services/resource-manager/type.ts +++ b/packages/core/src/services/resource-manager/type.ts @@ -42,4 +42,4 @@ export interface IResourceManagerService { unloadResources(unitId: string): void; } -export const IResourceManagerService = createIdentifier('resource-manager-service'); +export const IResourceManagerService = createIdentifier('core.resource-manager.service'); diff --git a/packages/core/src/services/snapshot/snapshot-transform.ts b/packages/core/src/services/snapshot/snapshot-transform.ts index 456b822138..3094d54352 100644 --- a/packages/core/src/services/snapshot/snapshot-transform.ts +++ b/packages/core/src/services/snapshot/snapshot-transform.ts @@ -35,6 +35,33 @@ import { } from './snapshot-utils'; import type { ISnapshotServerService } from './snapshot-server.service'; +export async function generateTempDocumentSnapshot( + _context: ILogContext, + document: IDocumentData, + unitID: string, + rev: number +) { + const documentMeta: IDocumentMeta = { + unitID: document.id, + rev, + creator: '', + name: document.title ?? '', + resources: document.resources || [], + originalMeta: encodeDocOriginalMeta(document), + }; + const snapshot: ISnapshot = { + unitID, + rev: documentMeta.rev, + type: UniverType.UNIVER_DOC, + workbook: undefined, + doc: documentMeta, + }; + + return { + snapshot, + }; +} + export async function generateTemporarySnap( context: ILogContext, workbook: IWorkbookData, @@ -375,7 +402,7 @@ export async function getSheetBlocksFromSnapshot(snapshot: ISnapshot, snapshotSe const blocks: ISheetBlock[] = []; const promises: Promise[] = []; - Object.entries(workbookMeta.blockMeta).forEach(([sheetID, blocksOfSheet]) => { + Object.entries(workbookMeta.blockMeta).forEach(([_, blocksOfSheet]) => { const blockPromises = blocksOfSheet.blocks.map(async (blockID) => { const params: IGetSheetBlockRequest = { unitID: workbookMeta.unitID, diff --git a/packages/core/src/services/undoredo/undoredo.service.ts b/packages/core/src/services/undoredo/undoredo.service.ts index 3c1fcc50b4..b10220d064 100644 --- a/packages/core/src/services/undoredo/undoredo.service.ts +++ b/packages/core/src/services/undoredo/undoredo.service.ts @@ -23,7 +23,7 @@ import { DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, DOCS_NORMAL_EDITOR_UNIT_ID_KEY } f import { Disposable, toDisposable } from '../../shared/lifecycle'; import type { ICommand, IMutationInfo } from '../command/command.service'; import { CommandType, ICommandService, sequenceExecute } from '../command/command.service'; -import { EDITOR_ACTIVATED, FOCUSING_FORMULA_EDITOR, FOCUSING_SHEET } from '../context/context'; +import { EDITOR_ACTIVATED, FOCUSING_FX_BAR_EDITOR, FOCUSING_SHEET } from '../context/context'; import { IContextService } from '../context/context.service'; import { IUniverInstanceService } from '../instance/instance.service'; import type { Nullable } from '../../common/type-util'; @@ -342,7 +342,7 @@ export class LocalUndoRedoService extends Disposable implements IUndoRedoService let unitID: string = ''; const isFocusSheet = this._contextService.getContextValue(FOCUSING_SHEET); - const isFocusFormulaEditor = this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR); + const isFocusFormulaEditor = this._contextService.getContextValue(FOCUSING_FX_BAR_EDITOR); const isFocusEditor = this._contextService.getContextValue(EDITOR_ACTIVATED); if (isFocusSheet) { diff --git a/packages/core/src/sheets/workbook.ts b/packages/core/src/sheets/workbook.ts index e85d4da7eb..06fd6e820f 100644 --- a/packages/core/src/sheets/workbook.ts +++ b/packages/core/src/sheets/workbook.ts @@ -131,6 +131,7 @@ export class Workbook extends UnitModel; + override name$: Observable; + private _snapshot: ISlideData; private _unitId: string; @@ -50,6 +53,15 @@ export class SlideDataModel extends UnitModel { + (type: UnitType, data, ctor, options) => { if (!this._startedTypes.has(type)) { this._pluginService.startPluginForType(type); this._startedTypes.add(type); const model = injector.createInstance(ctor, data); - univerInstanceService.__addUnit(model); + univerInstanceService.__addUnit(model, options); this._tryProgressToReady(); @@ -140,7 +140,7 @@ export class Univer { } const model = injector.createInstance(ctor, data); - univerInstanceService.__addUnit(model); + univerInstanceService.__addUnit(model, options); return model; } ); diff --git a/packages/design/src/components/dropdown/Dropdown.tsx b/packages/design/src/components/dropdown/Dropdown.tsx index 9e6db494f7..10d5209bce 100644 --- a/packages/design/src/components/dropdown/Dropdown.tsx +++ b/packages/design/src/components/dropdown/Dropdown.tsx @@ -91,7 +91,6 @@ export function Dropdown(props: IDropdownProps) { onVisibleChange, } = props; - // eslint-disable-next-line react/prefer-destructuring-assignment const trigger = disabled ? [] : (props.trigger || ['click']); const { mountContainer } = useContext(ConfigContext); diff --git a/packages/design/src/components/tooltip/Tooltip.tsx b/packages/design/src/components/tooltip/Tooltip.tsx index fe075bd3cd..10e63640a4 100644 --- a/packages/design/src/components/tooltip/Tooltip.tsx +++ b/packages/design/src/components/tooltip/Tooltip.tsx @@ -26,7 +26,7 @@ import { useIsEllipsis } from './hooks'; export interface ITooltipProps { visible?: boolean; - placement?: 'top' | 'bottom'; + placement?: 'top' | 'bottom' | 'left' | 'right'; title: (() => React.ReactNode) | React.ReactNode; @@ -55,6 +55,8 @@ export const Tooltip = forwardRef((props, ref const { mountContainer } = useContext(ConfigContext); const [tooltipEl, setTooltipEl] = useState(); const tooltipRef = useRef(null); + + // FIXME: cannot set value in a render method const refHandler = useCallback((ref: NullableTooltipRef) => { if (ref?.nativeElement) { setTooltipEl(ref.nativeElement); diff --git a/packages/design/src/themes/base.less b/packages/design/src/themes/base.less index 1cd85dbbf9..37c8d3589f 100644 --- a/packages/design/src/themes/base.less +++ b/packages/design/src/themes/base.less @@ -364,6 +364,7 @@ @bg-color: @grey-100; @bg-color-hover: @grey-300; @bg-color-secondary: @color-white; +@bg-color-overlay: @color-white; // Text Color @text-color: @color-black; @@ -418,6 +419,7 @@ --bg-color: @bg-color; --bg-color-hover: @bg-color-hover; --bg-color-secondary: @bg-color-secondary; + --bg-color-overlay: @bg-color-overlay; // Text Color --text-color: @text-color; diff --git a/packages/docs-drawing-ui/src/index.ts b/packages/docs-drawing-ui/src/index.ts index 54ce4bf1dc..6fb891026e 100644 --- a/packages/docs-drawing-ui/src/index.ts +++ b/packages/docs-drawing-ui/src/index.ts @@ -31,4 +31,6 @@ export { EditDocDrawingOperation } from './commands/operations/edit-doc-drawing. export { InsertDocImageOperation } from './commands/operations/insert-image.operation'; export { SidebarDocDrawingOperation } from './commands/operations/open-drawing-panel.operation'; +export { IMAGE_MENU_ID } from './views/menu/image.menu'; + // #endregion diff --git a/packages/docs-drawing-ui/src/views/menu/image.menu.ts b/packages/docs-drawing-ui/src/views/menu/image.menu.ts index 14ef448ce5..147f792330 100644 --- a/packages/docs-drawing-ui/src/views/menu/image.menu.ts +++ b/packages/docs-drawing-ui/src/views/menu/image.menu.ts @@ -23,7 +23,7 @@ import { combineLatest, Observable } from 'rxjs'; import { COMPONENT_DOC_UPLOAD_FILE_MENU } from '../upload-component/component-name'; export const ImageUploadIcon = 'addition-and-subtraction-single'; -const IMAGE_MENU_ID = 'doc.menu.image'; +export const IMAGE_MENU_ID = 'doc.menu.image'; const IMAGE_MENU_UPLOAD_FLOAT_ID = 'doc.menu.image.upload.float'; // TODO: @Jocs, remove this when cell support drawing. diff --git a/packages/docs-drawing/package.json b/packages/docs-drawing/package.json index 947a3dc758..6846663ca3 100644 --- a/packages/docs-drawing/package.json +++ b/packages/docs-drawing/package.json @@ -59,8 +59,7 @@ }, "peerDependencies": { "@univerjs/core": "workspace:*", - "@univerjs/drawing": "workspace:*", - "rxjs": ">=7.0.0" + "@univerjs/drawing": "workspace:*" }, "devDependencies": { "@univerjs/core": "workspace:*", diff --git a/packages/docs-mention-ui/src/commands/operations/mention-popup.operation.ts b/packages/docs-mention-ui/src/commands/operations/mention-popup.operation.ts index 5c6d618ba8..35c3c27337 100644 --- a/packages/docs-mention-ui/src/commands/operations/mention-popup.operation.ts +++ b/packages/docs-mention-ui/src/commands/operations/mention-popup.operation.ts @@ -22,9 +22,10 @@ export interface IShowMentionInfoPopupOperationParams { mentionId: string; } +// FIXME: anti-pattern: use command as an event export const ShowMentionInfoPopupOperation: ICommand = { type: CommandType.OPERATION, - id: 'docs.operation.show-mention-info-popup', + id: 'doc.operation.show-mention-info-popup', handler(accessor, params) { return false; }, @@ -32,7 +33,7 @@ export const ShowMentionInfoPopupOperation: ICommand = { type: CommandType.OPERATION, - id: 'docs.operation.show-mention-edit-popup', + id: 'doc.operation.show-mention-edit-popup', handler(accessor, params) { if (!params) { return false; @@ -57,7 +58,7 @@ export const ShowMentionEditPopupOperation: ICommand this.disposeWithMe(this._commandService.registerCommand(command))); - } - private _initCustomComponents(): void { const componentManager = this._componentManager; this.disposeWithMe(componentManager.register(COMPONENT_DOC_HEADER_FOOTER_PANEL, DocHeaderFooterPanel)); diff --git a/packages/docs-ui/src/controllers/doc-ui.controller.ts b/packages/docs-ui/src/controllers/doc-ui.controller.ts index 7c0e2868e8..586320a7f4 100644 --- a/packages/docs-ui/src/controllers/doc-ui.controller.ts +++ b/packages/docs-ui/src/controllers/doc-ui.controller.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { connectInjector, Disposable, Inject, Injector, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import { connectInjector, Disposable, ICommandService, Inject, Injector, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; import type { IMenuItemFactory } from '@univerjs/ui'; import { BuiltInUIPart, ComponentManager, ILayoutService, IMenuService, IShortcutService, IUIPartsService } from '@univerjs/ui'; @@ -29,6 +29,8 @@ import { } from '../components/font-family'; import { FONT_SIZE_COMPONENT, FontSize } from '../components/font-size'; import type { IUniverDocsUIConfig } from '../basics'; +import { CoreHeaderFooterCommand, OpenHeaderFooterPanelCommand } from '../commands/commands/doc-header-footer.command'; +import { SidebarDocHeaderFooterPanelOperation } from '../commands/operations/doc-header-footer-panel.operation'; import { DocFooter } from '../views/doc-footer'; import { AlignCenterShortCut, @@ -75,14 +77,15 @@ import { CopyMenuFactory, CutMenuFactory, DeleteColumnsMenuItemFactory, DeleteMe @OnLifecycle(LifecycleStages.Rendered, DocUIController) export class DocUIController extends Disposable { constructor( - private readonly _config: Partial, - @Inject(Injector) private readonly _injector: Injector, - @Inject(ComponentManager) private readonly _componentManager: ComponentManager, - @ILayoutService private readonly _layoutService: ILayoutService, - @IMenuService private readonly _menuService: IMenuService, - @IUIPartsService private readonly _uiPartsService: IUIPartsService, - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @IShortcutService private readonly _shortcutService: IShortcutService + protected readonly _config: Partial, + @Inject(Injector) protected readonly _injector: Injector, + @Inject(ComponentManager) protected readonly _componentManager: ComponentManager, + @ICommandService protected readonly _commandService: ICommandService, + @ILayoutService protected readonly _layoutService: ILayoutService, + @IMenuService protected readonly _menuService: IMenuService, + @IUIPartsService protected readonly _uiPartsService: IUIPartsService, + @IUniverInstanceService protected readonly _univerInstanceService: IUniverInstanceService, + @IShortcutService protected readonly _shortcutService: IShortcutService ) { super(); @@ -188,10 +191,19 @@ export class DocUIController extends Disposable { this._initCustomComponents(); this._initMenus(); this._initFocusHandler(); + this._initCommands(); this._initUiParts(); this._initShortCut(); } + private _initCommands(): void { + [ + CoreHeaderFooterCommand, + OpenHeaderFooterPanelCommand, + SidebarDocHeaderFooterPanelOperation, + ].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command))); + } + private _initFocusHandler(): void { this.disposeWithMe( this._layoutService.registerFocusHandler(UniverInstanceType.UNIVER_DOC, () => { diff --git a/packages/docs-ui/src/controllers/render-controllers/doc.render-controller.ts b/packages/docs-ui/src/controllers/render-controllers/doc.render-controller.ts index 521b06d306..d066c36040 100644 --- a/packages/docs-ui/src/controllers/render-controllers/doc.render-controller.ts +++ b/packages/docs-ui/src/controllers/render-controllers/doc.render-controller.ts @@ -15,7 +15,7 @@ */ import type { DocumentDataModel, EventState, ICommandInfo, Nullable } from '@univerjs/core'; -import { ICommandService, IConfigService, Inject, RxDisposable } from '@univerjs/core'; +import { FOCUSING_DOC, ICommandService, IConfigService, IContextService, Inject, RxDisposable } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import { DOCS_COMPONENT_BACKGROUND_LAYER_INDEX, DOCS_COMPONENT_DEFAULT_Z_INDEX, DOCS_COMPONENT_HEADER_LAYER_INDEX, DOCS_COMPONENT_MAIN_LAYER_INDEX, DOCS_VIEW_KEY, DocSkeletonManagerService, RichTextEditingMutation, VIEWPORT_KEY } from '@univerjs/docs'; import type { DocumentSkeleton, IRenderContext, IRenderModule, IWheelEvent } from '@univerjs/engine-render'; @@ -26,6 +26,7 @@ import { takeUntil } from 'rxjs'; export class DocRenderController extends RxDisposable implements IRenderModule { constructor( private readonly _context: IRenderContext, + @IContextService private readonly _contextService: IContextService, @ICommandService private readonly _commandService: ICommandService, @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @IConfigService private readonly _configService: IConfigService, @@ -79,8 +80,11 @@ export class DocRenderController extends RxDisposable implements IRenderModule { scene.attachControl(); scene.onMouseWheel$.subscribeEvent((evt: unknown, state: EventState) => { - const e = evt as IWheelEvent; + if (!this._contextService.getContextValue(FOCUSING_DOC)) { + return; + } + const e = evt as IWheelEvent; if (e.ctrlKey) { const deltaFactor = Math.abs(e.deltaX); let scrollNum = deltaFactor < 40 ? 0.2 : deltaFactor < 80 ? 0.4 : 0.2; diff --git a/packages/docs-ui/src/controllers/render-controllers/text-selection.render-controller.ts b/packages/docs-ui/src/controllers/render-controllers/text-selection.render-controller.ts index 2e96aeb7ea..fb2c3f42e6 100644 --- a/packages/docs-ui/src/controllers/render-controllers/text-selection.render-controller.ts +++ b/packages/docs-ui/src/controllers/render-controllers/text-selection.render-controller.ts @@ -213,33 +213,36 @@ export class DocTextSelectionRenderController extends Disposable implements IRen } private _skeletonListener() { + let init = false; + // Change text selection runtime(skeleton, scene) and update text selection manager current selection. this.disposeWithMe(this._docSkeletonManagerService.currentSkeleton$.subscribe((skeleton) => { - if (skeleton == null) { - return; - } + if (!skeleton) return; const { scene, mainComponent, unitId } = this._context; + const isInternalEditor = isInternalEditorID(unitId); - this._textSelectionRenderManager.changeRuntime(skeleton, scene, mainComponent as Documents); + if (init || !isInternalEditorID(this._context.unitId)) { + this._textSelectionRenderManager.changeRuntime(skeleton, scene, mainComponent as Documents); + this._textSelectionManagerService.setCurrentSelectionNotRefresh({ + unitId, + subUnitId: '', + }); - this._textSelectionManagerService.setCurrentSelectionNotRefresh({ - unitId, - subUnitId: '', - }); - - // The initial cursor is set at the beginning of the document, - // and can be set to the previous cursor position in the future. - // The skeleton of the editor has not been calculated at this moment, and it is determined whether it is an editor by its ID. - if (!isInternalEditorID(unitId)) { - this._textSelectionManagerService.replaceTextRanges([ - { - startOffset: 0, - endOffset: 0, - }, - ], false); + // The initial cursor is set at the beginning of the document, + // and can be set to the previous cursor position in the future. + // The skeleton of the editor has not been calculated at this moment, and it is determined whether it is an editor by its ID. + if (!isInternalEditor) { + this._textSelectionManagerService.replaceTextRanges([ + { + startOffset: 0, + endOffset: 0, + }, + ], false); + } } - }) - ); + + init = true; + })); } } diff --git a/packages/docs-ui/src/controllers/render-controllers/zoom.render-controller.ts b/packages/docs-ui/src/controllers/render-controllers/zoom.render-controller.ts index 28e48817c2..5197308f79 100644 --- a/packages/docs-ui/src/controllers/render-controllers/zoom.render-controller.ts +++ b/packages/docs-ui/src/controllers/render-controllers/zoom.render-controller.ts @@ -17,22 +17,22 @@ import type { DocumentDataModel, ICommandInfo } from '@univerjs/core'; import { Disposable, + FOCUSING_DOC, ICommandService, + IContextService, Inject, IUniverInstanceService, } from '@univerjs/core'; +import type { ISetDocZoomRatioOperationParams } from '@univerjs/docs'; import { DocSkeletonManagerService, neoGetDocObject, SetDocZoomRatioCommand, SetDocZoomRatioOperation, TextSelectionManagerService } from '@univerjs/docs'; import type { IRenderContext, IRenderModule, IWheelEvent } from '@univerjs/engine-render'; import { IEditorService } from '@univerjs/ui'; import { DocPageLayoutService } from '../../services/doc-page-layout.service'; -interface ISetDocMutationParams { - unitId: string; -} - export class DocZoomRenderController extends Disposable implements IRenderModule { constructor( private readonly _context: IRenderContext, + @IContextService private readonly _contextService: IContextService, @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @ICommandService private readonly _commandService: ICommandService, @@ -42,10 +42,6 @@ export class DocZoomRenderController extends Disposable implements IRenderModule ) { super(); - this._init(); - } - - private _init() { this._initSkeletonListener(); this._initCommandExecutedListener(); this._initRenderRefresher(); @@ -65,7 +61,7 @@ export class DocZoomRenderController extends Disposable implements IRenderModule } this.disposeWithMe(scene.onMouseWheel$.subscribeEvent((e: IWheelEvent) => { - if (!e.ctrlKey) { + if (!e.ctrlKey || !this._contextService.getContextValue(FOCUSING_DOC)) { return; } @@ -115,18 +111,9 @@ export class DocZoomRenderController extends Disposable implements IRenderModule const updateCommandList = [SetDocZoomRatioOperation.id]; this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => { - if (updateCommandList.includes(command.id)) { - const documentModel = this._univerInstanceService.getCurrentUniverDocInstance(); - if (!documentModel) return; - - const params = command.params; - const { unitId } = params as ISetDocMutationParams; - if (!(unitId === documentModel.getUnitId())) { - return; - } - + if (updateCommandList.includes(command.id) && (command.params as ISetDocZoomRatioOperationParams).unitId === this._context.unitId) { + const documentModel = this._context.unit; const zoomRatio = documentModel.zoomRatio || 1; - this.updateViewZoom(zoomRatio); } })); diff --git a/packages/docs-ui/src/docs-ui-plugin.ts b/packages/docs-ui/src/docs-ui-plugin.ts index ce0a271ce0..caa0329137 100644 --- a/packages/docs-ui/src/docs-ui-plugin.ts +++ b/packages/docs-ui/src/docs-ui-plugin.ts @@ -22,6 +22,7 @@ import { Inject, Injector, IUniverInstanceService, + mergeOverrideWithDependencies, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import { IEditorService, IShortcutService } from '@univerjs/ui'; @@ -150,7 +151,9 @@ export class UniverDocsUIPlugin extends Plugin { [DocParagraphSettingController], ]; - dependencies.forEach((d) => injector.add(d)); + const dependency = mergeOverrideWithDependencies(dependencies, this._config.override); + + dependency.forEach((d) => injector.add(d)); } private _markDocAsFocused() { diff --git a/packages/docs-ui/src/index.ts b/packages/docs-ui/src/index.ts index 564c26084c..d722b52c5f 100644 --- a/packages/docs-ui/src/index.ts +++ b/packages/docs-ui/src/index.ts @@ -25,7 +25,7 @@ export { DocsRenderService } from './services/docs-render.service'; export { DocCanvasPopManagerService } from './services/doc-popup-manager.service'; export { docDrawingPositionToTransform, transformToDocDrawingPosition } from './basics/transform-position'; export { DocHoverManagerService } from './services/doc-hover-manager.service'; - +export { DocUIController } from './controllers/doc-ui.controller'; // #region - all commands export { DocCopyCommand, DocCutCommand, DocPasteCommand } from './commands/commands/clipboard.command'; diff --git a/packages/docs/src/basics/selection.ts b/packages/docs/src/basics/selection.ts index 10a2ccd7ec..818f9bcd72 100644 --- a/packages/docs/src/basics/selection.ts +++ b/packages/docs/src/basics/selection.ts @@ -19,7 +19,19 @@ import { DataStreamTreeTokenType } from '@univerjs/core'; import { DeleteDirection } from '../types/enums/delete-direction'; import { isCustomRangeSplitSymbol } from './custom-range'; -export function normalizeSelection(selection: ITextRange) { +export function makeSelection(startOffset: number, endOffset?: number): ITextRange { + if (typeof endOffset === 'undefined') { + return { startOffset, endOffset: startOffset, collapsed: true }; + } + + if (endOffset < startOffset) { + throw new Error(`Cannot make a doc selection when endOffset ${endOffset} is less than startOffset ${startOffset}.`); + } + + return { startOffset, endOffset, collapsed: startOffset === endOffset }; +} + +export function normalizeSelection(selection: ITextRange): ITextRange { const { startOffset, endOffset, collapsed } = selection; const start = Math.min(startOffset, endOffset); const end = Math.max(startOffset, endOffset); diff --git a/packages/docs/src/index.ts b/packages/docs/src/index.ts index 1527fe912e..7bd3cbc96c 100644 --- a/packages/docs/src/index.ts +++ b/packages/docs/src/index.ts @@ -16,7 +16,7 @@ export { getParagraphsInRange, getParagraphsInRanges } from './commands/commands/list.command'; export { replaceSelectionFactory } from './basics/replace'; -export { getSelectionText, getDeleteSelection, getInsertSelection, isSegmentIntersects } from './basics/selection'; +export { makeSelection, getSelectionText, getDeleteSelection, getInsertSelection, isSegmentIntersects } from './basics/selection'; export type { IDocObjectParam } from './basics/component-tools'; export { getDocObject, neoGetDocObject, getDocObjectById } from './basics/component-tools'; export * from './basics/docs-view-key'; diff --git a/packages/engine-render/src/components/docs/layout/style/custom-range.ts b/packages/engine-render/src/components/docs/layout/style/custom-range.ts index 070534c480..4301c00b7a 100644 --- a/packages/engine-render/src/components/docs/layout/style/custom-range.ts +++ b/packages/engine-render/src/components/docs/layout/style/custom-range.ts @@ -18,7 +18,10 @@ import type { ICustomRangeForInterceptor, ITextStyle, Nullable } from '@univerjs import { BooleanNumber, CustomRangeType } from '@univerjs/core'; export function getCustomRangeStyle(customRange: ICustomRangeForInterceptor): Nullable { - if (customRange.rangeType === CustomRangeType.HYPERLINK || customRange.rangeType === CustomRangeType.MENTION) { + if ( + customRange.rangeType === CustomRangeType.HYPERLINK || + customRange.rangeType === CustomRangeType.MENTION + ) { return { ...customRange.active ? { ul: { s: BooleanNumber.TRUE } } : null, cl: { rgb: '#274fee' }, diff --git a/packages/engine-render/src/render-manager/render-manager.service.ts b/packages/engine-render/src/render-manager/render-manager.service.ts index 04566eedac..14a0b3908b 100644 --- a/packages/engine-render/src/render-manager/render-manager.service.ts +++ b/packages/engine-render/src/render-manager/render-manager.service.ts @@ -173,8 +173,8 @@ export class RenderManagerService extends Disposable implements IRenderManagerSe getAllRenderersOfType(type: UniverInstanceType): RenderUnit[] { const renderUnits: RenderUnit[] = []; - for (const [unitId, render] of this._renderMap) { - const renderType = this._univerInstanceService.getUnitType(unitId); + for (const [_, render] of this._renderMap) { + const renderType = render.type; if (renderType === type) { renderUnits.push(render as RenderUnit); } @@ -240,8 +240,9 @@ export class RenderManagerService extends Disposable implements IRenderManagerSe } else { // For slide pages renderUnit = { - unitId, + isThumbNail: true, type: UniverInstanceType.UNIVER_SLIDE, + unitId, engine, scene, mainComponent: null, diff --git a/packages/engine-render/src/render-manager/render-unit.ts b/packages/engine-render/src/render-manager/render-unit.ts index a95bc61a5c..6e56edd91a 100644 --- a/packages/engine-render/src/render-manager/render-unit.ts +++ b/packages/engine-render/src/render-manager/render-unit.ts @@ -28,6 +28,7 @@ export interface IRender { mainComponent: Nullable; components: Map; isMainScene: boolean; + isThumbNail?: boolean; with(dependency: DependencyIdentifier): T; } diff --git a/packages/engine-render/src/viewport.ts b/packages/engine-render/src/viewport.ts index 8fbb708ead..c6cafd59c4 100644 --- a/packages/engine-render/src/viewport.ts +++ b/packages/engine-render/src/viewport.ts @@ -1484,9 +1484,9 @@ export class Viewport { if (!preCacheVisibleBound) return 0b01; const viewBoundOutCacheArea = viewBound.right > preCacheVisibleBound.right || - viewBound.top < preCacheVisibleBound.top || - viewBound.left < preCacheVisibleBound.left || - viewBound.bottom > preCacheVisibleBound.bottom + viewBound.top < preCacheVisibleBound.top || + viewBound.left < preCacheVisibleBound.left || + viewBound.bottom > preCacheVisibleBound.bottom ? 0b01 : 0b00; diff --git a/packages/find-replace/src/controllers/find-replace.controller.ts b/packages/find-replace/src/controllers/find-replace.controller.ts index c5426ee1cb..e6c6ddedfb 100644 --- a/packages/find-replace/src/controllers/find-replace.controller.ts +++ b/packages/find-replace/src/controllers/find-replace.controller.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import type { + IDisposable, + Nullable } from '@univerjs/core'; import { ICommandService, Inject, @@ -23,6 +26,7 @@ import { LocaleService, OnLifecycle, RxDisposable, + toDisposable, } from '@univerjs/core'; import { SearchSingle16 } from '@univerjs/icons'; import type { MenuConfig } from '@univerjs/ui'; @@ -87,6 +91,13 @@ export class FindReplaceController extends RxDisposable { this._initShortcuts(); } + override dispose(): void { + super.dispose(); + + this._closingListenerDisposable?.dispose(); + this._closingListenerDisposable = null; + } + private _initCommands(): void { [ OpenFindDialogOperation, @@ -123,15 +134,6 @@ export class FindReplaceController extends RxDisposable { this._openPanel(); } }); - - // If focusing on a non-sheet Univer document, close the find replace dialog. - this.disposeWithMe( - this._univerInstanceService.focused$.pipe(takeUntil(this.dispose$)).subscribe((focused) => { - if (!focused || !this._univerInstanceService.getUniverSheetInstance(focused)) { - this.closePanel(); - } - }) - ); } private _openPanel(): void { @@ -146,9 +148,23 @@ export class FindReplaceController extends RxDisposable { preservePositionOnDestroy: true, onClose: () => this.closePanel(), }); + + this._closingListenerDisposable = toDisposable(this._univerInstanceService.focused$.pipe(takeUntil(this.dispose$)).subscribe((focused) => { + if (!focused || !this._univerInstanceService.getUniverSheetInstance(focused)) { + this.closePanel(); + } + })); } + private _closingListenerDisposable: Nullable; closePanel(): void { + if (!this._closingListenerDisposable) { + return; + } + + this._closingListenerDisposable.dispose(); + this._closingListenerDisposable = null; + this._dialogService.close(FIND_REPLACE_DIALOG_ID); this._findReplaceService.terminate(); diff --git a/packages/sheets-conditional-formatting-ui/package.json b/packages/sheets-conditional-formatting-ui/package.json index 565ce4b627..87d36ca0f6 100644 --- a/packages/sheets-conditional-formatting-ui/package.json +++ b/packages/sheets-conditional-formatting-ui/package.json @@ -94,7 +94,7 @@ "@univerjs/design": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons-svg": "^0.1.70", + "@univerjs/icons-svg": "^0.1.72", "@univerjs/shared": "workspace:*", "@univerjs/sheets": "workspace:*", "@univerjs/sheets-conditional-formatting": "workspace:*", diff --git a/packages/sheets-conditional-formatting/package.json b/packages/sheets-conditional-formatting/package.json index 929aa0c1ff..f5170b032f 100644 --- a/packages/sheets-conditional-formatting/package.json +++ b/packages/sheets-conditional-formatting/package.json @@ -75,7 +75,7 @@ "@univerjs/core": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", - "@univerjs/icons-svg": "^0.1.70", + "@univerjs/icons-svg": "^0.1.72", "@univerjs/shared": "workspace:*", "@univerjs/sheets": "workspace:*", "dayjs": "^1.11.12", diff --git a/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts b/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts index a47fee006a..8d06b13dba 100644 --- a/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts +++ b/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts @@ -48,9 +48,11 @@ export class SheetDataValidationManager extends DataValidationManager(unitId, UniverInstanceType.UNIVER_SHEET)!.getSheetBySheetId(subUnitId)!; const matrix = new ObjectMatrix(); + this._ruleMatrix = new RuleMatrix(matrix, worksheet); } diff --git a/packages/sheets-data-validation/src/services/dv-formula.service.ts b/packages/sheets-data-validation/src/services/dv-formula.service.ts index 191beeec69..b25ec8cb4c 100644 --- a/packages/sheets-data-validation/src/services/dv-formula.service.ts +++ b/packages/sheets-data-validation/src/services/dv-formula.service.ts @@ -15,7 +15,7 @@ */ import type { Nullable } from '@univerjs/core'; -import { Disposable, Inject, isFormulaString } from '@univerjs/core'; +import { Disposable, Inject, isFormulaString, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; import { DataValidationModel } from '@univerjs/data-validation'; import type { IFormulaInfo, IOtherFormulaResult } from '@univerjs/sheets-formula'; import { RegisterOtherFormulaService } from '@univerjs/sheets-formula'; @@ -29,6 +29,7 @@ export class DataValidationFormulaService extends Disposable { private _formulaRuleMap: Map>> = new Map(); constructor( + @IUniverInstanceService private readonly _instanceService: IUniverInstanceService, @Inject(RegisterOtherFormulaService) private _registerOtherFormulaService: RegisterOtherFormulaService, @Inject(DataValidationCacheService) private readonly _dataValidationCacheService: DataValidationCacheService, @Inject(DataValidationModel) private readonly _dataValidationModel: DataValidationModel @@ -41,6 +42,10 @@ export class DataValidationFormulaService extends Disposable { this.disposeWithMe(this._registerOtherFormulaService.formulaResult$.subscribe((resultMap) => { for (const unitId in resultMap) { const unitMap = resultMap[unitId]; + + const type = this._instanceService.getUnitType(unitId); + if (type !== UniverInstanceType.UNIVER_SHEET) continue; + for (const subUnitId in unitMap) { const results = unitMap[subUnitId]; const formulaMap = this._ensureRuleFormulaMap(unitId, subUnitId); diff --git a/packages/sheets-drawing-ui/src/controllers/shortcuts/drawing.shortcut.ts b/packages/sheets-drawing-ui/src/controllers/shortcuts/drawing.shortcut.ts index 870c19fcfd..2abac03652 100644 --- a/packages/sheets-drawing-ui/src/controllers/shortcuts/drawing.shortcut.ts +++ b/packages/sheets-drawing-ui/src/controllers/shortcuts/drawing.shortcut.ts @@ -15,7 +15,7 @@ */ import type { IContextService } from '@univerjs/core'; -import { Direction, EDITOR_ACTIVATED, FOCUSING_COMMON_DRAWINGS, FOCUSING_FORMULA_EDITOR } from '@univerjs/core'; +import { Direction, EDITOR_ACTIVATED, FOCUSING_COMMON_DRAWINGS, FOCUSING_FX_BAR_EDITOR } from '@univerjs/core'; import type { IShortcutItem } from '@univerjs/ui'; import { KeyCode } from '@univerjs/ui'; import { type IMoveDrawingsCommandParams, MoveDrawingsCommand } from '../../commands/commands/move-drawings.command'; @@ -23,7 +23,7 @@ import { DeleteDrawingsCommand } from '../../commands/commands/delete-drawings.c export function whenSheetDrawingFocused(contextService: IContextService): boolean { return ( - !contextService.getContextValue(FOCUSING_FORMULA_EDITOR) && + !contextService.getContextValue(FOCUSING_FX_BAR_EDITOR) && !contextService.getContextValue(EDITOR_ACTIVATED) && contextService.getContextValue(FOCUSING_COMMON_DRAWINGS) ); diff --git a/packages/sheets-drawing-ui/src/index.ts b/packages/sheets-drawing-ui/src/index.ts index 2beba29dc3..36a69f2c85 100644 --- a/packages/sheets-drawing-ui/src/index.ts +++ b/packages/sheets-drawing-ui/src/index.ts @@ -33,4 +33,5 @@ export { EditSheetDrawingOperation } from './commands/operations/edit-sheet-draw export { InsertFloatImageOperation, InsertCellImageOperation } from './commands/operations/insert-image.operation'; export { SidebarSheetDrawingOperation } from './commands/operations/open-drawing-panel.operation'; +export { IMAGE_MENU_ID } from './views/menu/image.menu'; // #endregion diff --git a/packages/sheets-drawing-ui/src/plugin.ts b/packages/sheets-drawing-ui/src/plugin.ts index 23b14fc263..e5f60cc9aa 100644 --- a/packages/sheets-drawing-ui/src/plugin.ts +++ b/packages/sheets-drawing-ui/src/plugin.ts @@ -67,6 +67,7 @@ export class UniverSheetsDrawingUIPlugin extends Plugin { [DrawingPopupMenuController], [SheetDrawingPrintingController], [SheetDrawingPermissionController], + [SheetsDrawingCopyPasteController], ]; dependencies.forEach((dependency) => this._injector.add(dependency)); } @@ -75,7 +76,6 @@ export class UniverSheetsDrawingUIPlugin extends Plugin { ([ [SheetDrawingUpdateController], [SheetDrawingTransformAffectedController], - [SheetsDrawingCopyPasteController], ] as Dependency[]).forEach((m) => { this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, m)); }); diff --git a/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts b/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts index 7c4898e81a..417bb41f73 100644 --- a/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts +++ b/packages/sheets-drawing-ui/src/services/canvas-float-dom-manager.service.ts @@ -19,7 +19,7 @@ import { Disposable, DisposableCollection, DrawingTypeEnum, ICommandService, Inj import type { IDrawingJsonUndo1 } from '@univerjs/drawing'; import { getDrawingShapeKeyByDrawingSearch, IDrawingManagerService } from '@univerjs/drawing'; import type { BaseObject, IBoundRectNoAngle, IRectProps, IRender, Scene, SpreadsheetSkeleton } from '@univerjs/engine-render'; -import { DRAWING_OBJECT_LAYER_INDEX, IRenderManagerService, Rect, SHEET_VIEWPORT_KEY } from '@univerjs/engine-render'; +import { DRAWING_OBJECT_LAYER_INDEX, IRenderManagerService, pxToNum, Rect, SHEET_VIEWPORT_KEY } from '@univerjs/engine-render'; import type { ISetFrozenMutationParams } from '@univerjs/sheets'; import { getSheetCommandTarget, SetFrozenMutation } from '@univerjs/sheets'; import type { IFloatDomData, ISheetDrawingPosition, ISheetFloatDom } from '@univerjs/sheets-drawing'; @@ -134,8 +134,13 @@ const calcPosition = ( skeleton: SpreadsheetSkeleton, worksheet: Worksheet ): IFloatDomLayout => { - const { scene } = currentRender; + const { scene, engine } = currentRender; const { left, top, width, height, angle } = targetObject; + const canvasElement = engine.getCanvasElement(); + const canvasClientRect = canvasElement.getBoundingClientRect(); + const widthOfCanvas = pxToNum(canvasElement.style.width); // declared width + const { top: topOffset, left: leftOffset, width: domWidth } = canvasClientRect; // real width affected by scale + const scaleAdjust = domWidth / widthOfCanvas; const bound: IBoundRectNoAngle = { left, @@ -146,13 +151,13 @@ const calcPosition = ( const offsetBound = transformBound2DOMBound(bound, scene, skeleton, worksheet); return { - startX: offsetBound.left, - endX: offsetBound.right, - startY: offsetBound.top, - endY: offsetBound.bottom, + startX: offsetBound.left * scaleAdjust + leftOffset, + endX: offsetBound.right * scaleAdjust + leftOffset, + startY: offsetBound.top * scaleAdjust + topOffset, + endY: offsetBound.bottom * scaleAdjust + topOffset, rotate: angle, - width, - height, + width: width * scaleAdjust, + height: height * scaleAdjust, absolute: offsetBound.absolute, }; }; diff --git a/packages/sheets-drawing-ui/src/views/menu/image.menu.ts b/packages/sheets-drawing-ui/src/views/menu/image.menu.ts index 74b3f535a8..f246e754fc 100644 --- a/packages/sheets-drawing-ui/src/views/menu/image.menu.ts +++ b/packages/sheets-drawing-ui/src/views/menu/image.menu.ts @@ -23,7 +23,7 @@ import { COMPONENT_UPLOAD_FILE_MENU, UploadFileType } from '../upload-component/ import { InsertCellImageOperation, InsertFloatImageOperation } from '../../commands/operations/insert-image.operation'; export const IMAGE_UPLOAD_ICON = 'addition-and-subtraction-single'; -const IMAGE_MENU_ID = 'sheet.menu.image'; +export const IMAGE_MENU_ID = 'sheet.menu.image'; export function ImageMenuFactory(accessor: IAccessor): IMenuItem { return { diff --git a/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts b/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts index b75bd79dda..ff6bf76da1 100644 --- a/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts +++ b/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts @@ -62,9 +62,9 @@ export class SheetsFindReplaceController extends Disposable implements IDisposab this.disposeWithMe(this._findReplaceService.registerFindReplaceProvider(provider)); // The find replace panel should be closed when sheet cell editor is activated, or the formula editor is focused. - this.disposeWithMe( - this._contextService.subscribeContextValue$(EDITOR_ACTIVATED).pipe(filter((v) => !!v)).subscribe(() => this._findReplaceController.closePanel()) - ); + this.disposeWithMe(this._contextService.subscribeContextValue$(EDITOR_ACTIVATED) + .pipe(filter((v) => !!v)) + .subscribe(() => this._findReplaceController.closePanel())); } private _initCommands(): void { @@ -214,10 +214,8 @@ export class SheetFindModel extends FindModel { })); this.disposeWithMe( - fromCallback(this._commandService.onCommandExecuted.bind(this._commandService.onCommandExecuted)) - .pipe( - filter(([command, options]) => command.id === SetWorksheetActiveOperation.id && !options?.fromFindReplace) - ) + fromCallback(this._commandService.onCommandExecuted.bind(this._commandService)) + .pipe(filter(([command, options]) => command.id === SetWorksheetActiveOperation.id && !options?.fromFindReplace)) .subscribe(() => { const activeSheet = this._workbook.getActiveSheet(); if (!activeSheet) { diff --git a/packages/sheets-formula/src/commands/operations/editor-formula.operation.ts b/packages/sheets-formula/src/commands/operations/editor-formula.operation.ts index 5775712fbc..ef693daf0b 100644 --- a/packages/sheets-formula/src/commands/operations/editor-formula.operation.ts +++ b/packages/sheets-formula/src/commands/operations/editor-formula.operation.ts @@ -28,38 +28,10 @@ export interface ISelectEditorFormulaOperationParam { isSingleEditor?: boolean; } +// FIXME: this is an anti-pattern, using operation as an event + export const SelectEditorFormulaOperation: IOperation = { id: 'formula-ui.operation.select-editor-formula', type: CommandType.OPERATION, - handler: (accessor, params) => - // const { eventType, keycode } = params; - // const formulaPromptService = accessor.get(IFormulaPromptService); - // const editorBridgeService = accessor.get(IEditorBridgeService); - - // switch (keycode) { - // case KeyCode.ARROW_DOWN: - // formulaPromptService.navigate({ direction: Direction.DOWN }); - // break; - // case KeyCode.ARROW_UP: - // formulaPromptService.navigate({ direction: Direction.UP }); - // break; - // // case KeyCode.ENTER: - // // case KeyCode.TAB: - // // if (formulaPromptService.isSearching()) { - // // formulaPromptService.accept(true); - // // } else { - // // const editorBridgeParameters = { - // // visible: false, - // // eventType, - // // keycode, - // // }; - - // // editorBridgeService.changeVisible(editorBridgeParameters); - // // } - // // break; - // default: - // break; - // } - - true, + handler: (accessor, params) => true, }; diff --git a/packages/sheets-formula/src/controllers/prompt.controller.ts b/packages/sheets-formula/src/controllers/prompt.controller.ts index 259b72c1ac..90fb3f2a25 100644 --- a/packages/sheets-formula/src/controllers/prompt.controller.ts +++ b/packages/sheets-formula/src/controllers/prompt.controller.ts @@ -34,6 +34,7 @@ import { DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, FOCUSING_EDITOR_INPUT_FORMULA, + FORMULA_EDITOR_ACTIVATED, getCellInfoInMergeData, ICommandService, IContextService, @@ -94,8 +95,10 @@ import { MoveSelectionCommand, SheetSkeletonManagerService, } from '@univerjs/sheets-ui'; +import type { Editor } from '@univerjs/ui'; import { IContextMenuService, IEditorService, KeyCode, MetaKeys, SetEditorResizeOperation, UNI_DISABLE_CHANGING_FOCUS_KEY } from '@univerjs/ui'; +import { distinctUntilChanged, distinctUntilKeyChanged } from 'rxjs'; import type { ISelectEditorFormulaOperationParam } from '../commands/operations/editor-formula.operation'; import { SelectEditorFormulaOperation } from '../commands/operations/editor-formula.operation'; import { HelpFunctionOperation } from '../commands/operations/help-function.operation'; @@ -223,7 +226,7 @@ export class PromptController extends Disposable { this._initSelectionsEndListener(); - this._initialExitEditor(); + this._closeRangePromptWhenEditorInvisible(); this._initialEditorInputChange(); @@ -233,8 +236,6 @@ export class PromptController extends Disposable { this._userMouseListener(); - this._inputFormulaListener(); - this._initialChangeEditor(); } @@ -271,7 +272,7 @@ export class PromptController extends Disposable { const editor = this._editorService.getEditor(params.unitId); if (!editor || editor.onlyInputContent() - || (editor.isSheetEditor() && this._editorBridgeService.isVisible().visible === false) + || (editor.isSheetEditor() && !this._isFormulaEditorActivated()) ) { return; } @@ -334,16 +335,22 @@ export class PromptController extends Disposable { ); } - private _initialExitEditor() { - this.disposeWithMe( - this._editorBridgeService.afterVisible$.subscribe((visibleParam) => { - if (visibleParam.visible === true) { - return; - } + private _closeRangePromptWhenEditorInvisible() { + // NOTE: to be refactored - this._closeRangePrompt(); + this.disposeWithMe(this._editorBridgeService.afterVisible$ + .pipe(distinctUntilKeyChanged('visible')) + .subscribe((visibleParam) => { + if (!visibleParam.visible) this._closeRangePrompt(); }) + ); + + this.disposeWithMe(this._contextService.subscribeContextValue$(FORMULA_EDITOR_ACTIVATED) + .pipe(distinctUntilChanged()) + .subscribe((activated) => { + if (!activated) this._closeRangePrompt(); + })); } private _initialChangeEditor() { @@ -1793,47 +1800,6 @@ export class PromptController extends Disposable { } } - private _inputFormulaListener() { - this.disposeWithMe( - this._editorService.inputFormula$.subscribe((param) => { - const { formulaString, editorUnitId } = param; - - if (formulaString.substring(0, 1) !== compareToken.EQUALS) { - return; - } - - const visibleState = this._editorBridgeService.isVisible(); - if (visibleState.visible === false) { - this._editorBridgeService.changeVisible({ - visible: true, - eventType: DeviceInputEventType.Dblclick, - unitId: '', - }); - } - - const lastSequenceNodes = this._lexerTreeBuilder.sequenceNodesBuilder(formulaString) || []; - - this._formulaPromptService.setSequenceNodes(lastSequenceNodes); - - this._syncToEditor(lastSequenceNodes, formulaString.length - 1, editorUnitId, true, false); - }) - ); - - this.disposeWithMe( - // TODO: unfinished - this._editorService.singleSelection$.subscribe((state) => { - if (state === true) { - // this._selectionRenderService.enableSingleSelection(); - } else { - // this._selectionRenderService.disableSingleSelection(); - } - }) - ); - } - - /** - * Absolute range, triggered by F4 - */ private _changeRefString() { const activeRange = this._textSelectionManagerService.getActiveTextRangeWithStyle(); @@ -1937,4 +1903,14 @@ export class PromptController extends Disposable { const editor = this._editorService.getEditor(editorUnitId); return editor?.render; } + + private _isFormulaEditorActivated(): boolean { + // TODO: Finally we will remove 'this._editorBridgeService.isVisible().visible === true' to + // just the the context value. + return this._editorBridgeService.isVisible().visible === true || this._contextService.getContextValue(FORMULA_EDITOR_ACTIVATED); + } + + private _isSheetOrFormulaEditor(editor: Editor): boolean { + return editor.isSheetEditor() || editor.isFormulaEditor(); + } } diff --git a/packages/sheets-formula/src/index.ts b/packages/sheets-formula/src/index.ts index 9619971187..9a54e6c7a6 100644 --- a/packages/sheets-formula/src/index.ts +++ b/packages/sheets-formula/src/index.ts @@ -22,6 +22,7 @@ export { RegisterFunctionService } from './services/register-function.service'; export { IRegisterFunctionService } from './services/register-function.service'; export { IRemoteRegisterFunctionService, RemoteRegisterFunctionService } from './services/remote/remote-register-function.service'; export { FormulaRefRangeService } from './services/formula-ref-range.service'; +export { FORMULA_PROMPT_ACTIVATED } from './services/prompt.service'; export { RegisterOtherFormulaService } from './services/register-other-formula.service'; export type { IFormulaInfo, IOtherFormulaResult } from './services/formula-common'; diff --git a/packages/sheets-formula/src/services/prompt.service.ts b/packages/sheets-formula/src/services/prompt.service.ts index 51f0069dbf..ec54da2d92 100644 --- a/packages/sheets-formula/src/services/prompt.service.ts +++ b/packages/sheets-formula/src/services/prompt.service.ts @@ -16,12 +16,15 @@ import type { Direction, IDisposable } from '@univerjs/core'; import { type IFunctionInfo, type ISequenceNode, sequenceNodeType } from '@univerjs/engine-formula'; -import { createIdentifier } from '@univerjs/core'; +import { createIdentifier, IContextService } from '@univerjs/core'; import type { Observable } from 'rxjs'; import { Subject } from 'rxjs'; import type { ISearchItem } from './description.service'; +/** If the formula prompt is visible. */ +export const FORMULA_PROMPT_ACTIVATED = 'FORMULA_PROMPT_ACTIVATED'; + export interface ISearchFunctionOperationParams { /** * show SearchFunction Component or not @@ -182,6 +185,11 @@ export class FormulaPromptService implements IFormulaPromptService, IDisposable private _isLockedOnSelectionInsertRefString: boolean = false; + constructor( + @IContextService private readonly _contextService: IContextService + ) { + } + dispose(): void { this._search$.complete(); this._help$.complete(); @@ -192,6 +200,7 @@ export class FormulaPromptService implements IFormulaPromptService, IDisposable } search(param: ISearchFunctionOperationParams) { + this._contextService.setContextValue(FORMULA_PROMPT_ACTIVATED, param.visible); this._searching = param.visible; this._search$.next(param); } diff --git a/packages/sheets-formula/src/services/render-services/ref-selections.render-service.ts b/packages/sheets-formula/src/services/render-services/ref-selections.render-service.ts index 10588fc544..4d88212cde 100644 --- a/packages/sheets-formula/src/services/render-services/ref-selections.render-service.ts +++ b/packages/sheets-formula/src/services/render-services/ref-selections.render-service.ts @@ -100,9 +100,7 @@ export class RefSelectionsRenderService extends BaseSelectionRenderService imple const { scene } = this._context; const listenerDisposables = new DisposableCollection(); - listenerDisposables.add(spreadsheet?.onPointerDown$.subscribeEvent((evt: IPointerEvent | IMouseEvent, state) => { - console.warn('debug start dragging', this._skeleton.worksheet?.getUnitId()); this._onPointerDown(evt, spreadsheet.zIndex + 1, RANGE_TYPE.NORMAL, this._getActiveViewport(evt)); if (evt.button !== 2) { state.stopPropagation(); diff --git a/packages/sheets-formula/src/views/prompt/search-function/SearchFunction.tsx b/packages/sheets-formula/src/views/prompt/search-function/SearchFunction.tsx index fdbad092f8..3792d1f39d 100644 --- a/packages/sheets-formula/src/views/prompt/search-function/SearchFunction.tsx +++ b/packages/sheets-formula/src/views/prompt/search-function/SearchFunction.tsx @@ -46,7 +46,7 @@ export function SearchFunction() { return; } - // const isFocusFormulaEditor = contextService.getContextValue(FOCUSING_FORMULA_EDITOR); + // const isFocusFormulaEditor = contextService.getContextValue(FOCUSING_FX_BAR_EDITOR); const position = getPosition(); diff --git a/packages/sheets-ui/src/components/hook.ts b/packages/sheets-ui/src/components/hook.ts index 20b8e88cb5..079795e1b8 100644 --- a/packages/sheets-ui/src/components/hook.ts +++ b/packages/sheets-ui/src/components/hook.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { IUniverInstanceService, UniverInstanceType, type Workbook } from '@univerjs/core'; +import type { Workbook } from '@univerjs/core'; +import { IUniverInstanceService, UniverInstanceType, useDependency } from '@univerjs/core'; import { useObservable } from '@univerjs/ui'; -import { useDependency } from '@univerjs/core'; -import { of } from 'rxjs'; +import { map, merge, of, startWith } from 'rxjs'; export function useActiveWorkbook(): Workbook | null { const univerInstanceService = useDependency(IUniverInstanceService); @@ -29,3 +29,16 @@ export function useActiveWorksheet(workbook?: Workbook | null) { const worksheet = useObservable(() => workbook?.activeSheet$ ?? of(null), undefined, undefined, [workbook]); return worksheet; } + +export function useWorkbooks(): Workbook[] { + const univerInstanceService = useDependency(IUniverInstanceService); + return useObservable(() => { + return merge([ + univerInstanceService.getTypeOfUnitAdded$(UniverInstanceType.UNIVER_SHEET), + univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_SHEET), + ]).pipe( + map(() => univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET)), + startWith(univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET)) + ); + }, [], undefined, [univerInstanceService]); +} diff --git a/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts b/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts index 58248917ef..9f452f5184 100644 --- a/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts +++ b/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts @@ -22,7 +22,7 @@ import { FOCUSING_EDITOR_BUT_HIDDEN, FOCUSING_EDITOR_INPUT_FORMULA, FOCUSING_EDITOR_STANDALONE, - FOCUSING_FORMULA_EDITOR, + FOCUSING_FX_BAR_EDITOR, FOCUSING_SHEET, FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE, HorizontalAlign, @@ -61,7 +61,7 @@ import { Rect, ScrollBar, } from '@univerjs/engine-render'; -import { IEditorService, KeyCode, SetEditorResizeOperation } from '@univerjs/ui'; +import { IEditorService, ILayoutService, KeyCode, SetEditorResizeOperation } from '@univerjs/ui'; import type { WorkbookSelections } from '@univerjs/sheets'; import { ClearSelectionFormatCommand, SetRangeValuesCommand, SetSelectionsOperation, SetWorksheetActivateCommand, SheetsSelectionsService } from '@univerjs/sheets'; import { distinctUntilChanged, filter } from 'rxjs'; @@ -110,6 +110,7 @@ export class EditingRenderController extends Disposable implements IRenderModule constructor( private readonly _context: IRenderContext, + @ILayoutService private readonly _layoutService: ILayoutService, @Inject(SheetsSelectionsService) selectionManagerService: SheetsSelectionsService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @IContextService private readonly _contextService: IContextService, @@ -295,7 +296,6 @@ export class EditingRenderController extends Disposable implements IRenderModule const { verticalAlign, paddingData, fill } = documentLayoutObject; let editorWidth = endX - startX; - let editorHeight = endY - startY; if (editorWidth < actualWidth) { @@ -397,7 +397,6 @@ export class EditingRenderController extends Disposable implements IRenderModule * determine whether a scrollbar appears, * and calculate the editor's boundaries relative to the browser. */ - private _editAreaProcessing( editorWidth: number, editorHeight: number, @@ -408,17 +407,27 @@ export class EditingRenderController extends Disposable implements IRenderModule scaleY: number = 1 ) { const editorObject = this._getEditorObject(); - if (editorObject == null) { return; } + function pxToNum(width: string): number { + return Number.parseInt(width.replace('px', '')); + } + + const engine = this._context.engine; + const canvasElement = engine.getCanvasElement(); + const canvasClientRect = canvasElement.getBoundingClientRect(); + + // We should take the scale into account when canvas is scaled by CSS. + const widthOfCanvas = pxToNum(canvasElement.style.width); // declared width + const { top, left, width } = canvasClientRect; // real width affected by scale + const scaleAdjust = width / widthOfCanvas; + let { startX, startY } = actualRangeWithCoord; - const { document: documentComponent, scene, engine: docEngine } = editorObject; - - const viewportMain = scene.getViewport(DOC_VIEWPORT_KEY.VIEW_MAIN); - + const { document: documentComponent, scene: editorScene, engine: docEngine } = editorObject; + const viewportMain = editorScene.getViewport(DOC_VIEWPORT_KEY.VIEW_MAIN); const clientHeight = document.body.clientHeight - startY - @@ -452,23 +461,20 @@ export class EditingRenderController extends Disposable implements IRenderModule } startX -= FIX_ONE_PIXEL_BLUR_OFFSET; - startY -= FIX_ONE_PIXEL_BLUR_OFFSET; - // physicHeight = editorHeight; + this._addBackground(editorScene, editorWidth / scaleX, editorHeight / scaleY, fill); - this._addBackground(scene, editorWidth / scaleX, editorHeight / scaleY, fill); + const { scaleX: precisionScaleX, scaleY: precisionScaleY } = editorScene.getPrecisionScale(); - const { scaleX: precisionScaleX, scaleY: precisionScaleY } = scene.getPrecisionScale(); - - scene.transformByState({ - width: editorWidth / scaleX, - height: editorHeight / scaleY, - scaleX, - scaleY, + editorScene.transformByState({ + width: editorWidth * scaleAdjust / scaleX, + height: editorHeight * scaleAdjust / scaleY, + scaleX: scaleX * scaleAdjust, + scaleY: scaleY * scaleAdjust, }); - documentComponent.resize(editorWidth / scaleX, editorHeight / scaleY); + documentComponent.resize(editorWidth * scaleAdjust / scaleX, editorHeight * scaleAdjust / scaleY); /** * sometimes requestIdleCallback is invalid, so use setTimeout to ensure the successful execution of the resizeBySize method. @@ -482,17 +488,17 @@ export class EditingRenderController extends Disposable implements IRenderModule ); }, 0); - // const canvasElement = this._context.engine.getCanvasElement(); - // const canvasBoundingRect = canvasElement.getBoundingClientRect(); - // startX += canvasBoundingRect.left; - // startY += canvasBoundingRect.top; + const contentBoundingRect = this._layoutService.getContentElement().getBoundingClientRect(); + const canvasBoundingRect = canvasElement.getBoundingClientRect(); + startX = startX * scaleAdjust + (canvasBoundingRect.left - contentBoundingRect.left); + startY = startY * scaleAdjust + (canvasBoundingRect.top - contentBoundingRect.top); // Update cell editor container position and size. this._cellEditorManagerService.setState({ startX, startY, - endX: editorWidth + startX, - endY: physicHeight + startY, + endX: editorWidth * scaleAdjust + startX, + endY: physicHeight * scaleAdjust + startY, show: true, }); } @@ -678,9 +684,12 @@ export class EditingRenderController extends Disposable implements IRenderModule */ private _initialKeyboardListener(d: DisposableCollection) { d.add(this._textSelectionRenderManager.onInputBefore$.subscribe((config) => { - const isFocusFormulaEditor = this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR); - const isFocusSheets = this._contextService.getContextValue(FOCUSING_SHEET); + if (!this._isCurrentSheetFocused()) { + return; + } + const isFocusFormulaEditor = this._contextService.getContextValue(FOCUSING_FX_BAR_EDITOR); + const isFocusSheets = this._contextService.getContextValue(FOCUSING_SHEET); // TODO@Jocs: should get editor instead of current doc const unitId = this._instanceSrv.getCurrentUniverDocInstance()?.getUnitId(); if (unitId && isFocusSheets && !isFocusFormulaEditor && this._editorService.isSheetEditor(unitId)) { @@ -715,7 +724,11 @@ export class EditingRenderController extends Disposable implements IRenderModule const params = command.params as IRichTextEditingMutationParams; const { unitId: commandUnitId } = params; - if (!this._editorService.isSheetEditor(commandUnitId)) { + // Only when the sheet it attached to is focused. Maybe we should change it to the render unit sys. + if ( + !this._isCurrentSheetFocused() || + !this._editorService.isSheetEditor(commandUnitId) + ) { return; } @@ -761,7 +774,7 @@ export class EditingRenderController extends Disposable implements IRenderModule * but move the cursor within the editor instead. */ if (keycode != null && - (this._cursorChange === CursorChange.CursorChange || this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR)) + (this._cursorChange === CursorChange.CursorChange || this._contextService.getContextValue(FOCUSING_FX_BAR_EDITOR)) ) { this._moveInEditor(keycode, isShift); return; @@ -903,7 +916,7 @@ export class EditingRenderController extends Disposable implements IRenderModule this._contextService.setContextValue(FOCUSING_EDITOR_INPUT_FORMULA, false); this._contextService.setContextValue(EDITOR_ACTIVATED, false); this._contextService.setContextValue(FOCUSING_EDITOR_BUT_HIDDEN, false); - this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, false); + this._contextService.setContextValue(FOCUSING_FX_BAR_EDITOR, false); this._cellEditorManagerService.setState({ show: param.visible, @@ -992,6 +1005,12 @@ export class EditingRenderController extends Disposable implements IRenderModule }); } } + + // WTF: this is should not exist at all. It is because all editor instances reuse the singleton + // "TextSelectionManagerService" and other modules. Which will be refactored soon in August, 2024. + private _isCurrentSheetFocused(): boolean { + return this._instanceSrv.getFocusedUnit()?.getUnitId() === this._context.unitId; + } } export function getCellDataByInput( diff --git a/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts b/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts index d2bd1552c3..bf87049423 100644 --- a/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts +++ b/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts @@ -21,7 +21,7 @@ import { DOCS_NORMAL_EDITOR_UNIT_ID_KEY, EDITOR_ACTIVATED, FOCUSING_EDITOR_BUT_HIDDEN, - FOCUSING_FORMULA_EDITOR, + FOCUSING_FX_BAR_EDITOR, HorizontalAlign, ICommandService, IContextService, @@ -93,7 +93,7 @@ export class FormulaEditorController extends RxDisposable { const { unitId } = param; // Mark formula editor as non-focused, when current selection is not in formula editor. if (unitId !== DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY) { - this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, false); + this._contextService.setContextValue(FOCUSING_FX_BAR_EDITOR, false); this._undoRedoService.clearUndoRedo(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); } }); @@ -129,7 +129,7 @@ export class FormulaEditorController extends RxDisposable { if (isFocusButHidden) { this._univerInstanceService.setCurrentUnitForType(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); - this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, true); + this._contextService.setContextValue(FOCUSING_FX_BAR_EDITOR, true); const currentSheet = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); const formulaEditorDataModel = this._univerInstanceService.getUniverDocInstance( @@ -208,7 +208,7 @@ export class FormulaEditorController extends RxDisposable { } // Open the normal editor first, and then we mark formula editor as activated. - this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, true); + this._contextService.setContextValue(FOCUSING_FX_BAR_EDITOR, true); this._undoRedoService.clearUndoRedo(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); }) ); diff --git a/packages/sheets-ui/src/controllers/permission/sheet-permission-interceptor-canvas-render.controller.ts b/packages/sheets-ui/src/controllers/permission/sheet-permission-interceptor-canvas-render.controller.ts index 255dea062f..29aa578d01 100644 --- a/packages/sheets-ui/src/controllers/permission/sheet-permission-interceptor-canvas-render.controller.ts +++ b/packages/sheets-ui/src/controllers/permission/sheet-permission-interceptor-canvas-render.controller.ts @@ -327,9 +327,9 @@ export class SheetPermissionInterceptorCanvasRenderController extends RxDisposab this.disposeWithMe( this._headerFreezeRenderController.interceptor.intercept(this._headerFreezeRenderController.interceptor.getInterceptPoints().FREEZE_PERMISSION_CHECK, { handler: (_: Nullable, __) => { - const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; - const worksheet = workbook.getActiveSheet(); - if (!worksheet) { + const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); + const worksheet = workbook?.getActiveSheet(); + if (!worksheet || !workbook) { return false; } const permission = this._permissionService.getPermissionPoint(new WorkbookEditablePermission(workbook.getUnitId()).id)?.value ?? false; diff --git a/packages/sheets-ui/src/controllers/render-controllers/mobile/mobile-contextmenu.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/mobile/mobile-contextmenu.render-controller.ts index 3c4633c811..14c2e8b518 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/mobile/mobile-contextmenu.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/mobile/mobile-contextmenu.render-controller.ts @@ -58,7 +58,7 @@ export class SheetContextMenuMobileRenderController extends Disposable implement if (!selectionRangeWithStyle.primary) return; - const canvasRect = this._layoutService.getCanvasElement().getBoundingClientRect(); + const canvasRect = this._layoutService.getContentElement().getBoundingClientRect(); const range = this._selectionRenderService.attachSelectionWithCoord(selectionRangeWithStyle as unknown as ISelectionWithStyle); const rangeType = selectionRangeWithStyle.range.rangeType; diff --git a/packages/sheets-ui/src/controllers/render-controllers/scroll.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/scroll.render-controller.ts index 18a2ebd82f..7cbdc36f6d 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/scroll.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/scroll.render-controller.ts @@ -19,6 +19,7 @@ import { Direction, Disposable, ICommandService, + IContextService, Inject, Injector, RANGE_TYPE, toDisposable } from '@univerjs/core'; @@ -46,13 +47,16 @@ export class SheetsScrollRenderController extends Disposable implements IRenderM private readonly _context: IRenderContext, @Inject(Injector) private readonly _injector: Injector, @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, + @IContextService private readonly _contextService: IContextService, @ICommandService private readonly _commandService: ICommandService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @Inject(SheetScrollManagerService) private readonly _scrollManagerService: SheetScrollManagerService ) { super(); - this._init(); + this._initCommandListener(); + this._initScrollEventListener(); + this._initSkeletonListener(); } scrollToRange(range: IRange): boolean { @@ -75,26 +79,18 @@ export class SheetsScrollRenderController extends Disposable implements IRenderM } } - private _init() { - this._initCommandListener(); - this._initScrollEventListener(); - this._initSkeletonListener(); - } - private _initCommandListener(): void { - this.disposeWithMe( - this._commandService.onCommandExecuted((command) => { - if (SHEET_NAVIGATION_COMMANDS.includes(command.id)) { - this._scrollToSelection(); - } else if (command.id === ScrollToCellOperation.id) { - const param = command.params as IRange; - this.scrollToRange(param); - } else if (command.id === ExpandSelectionCommand.id) { - const param = command.params as IExpandSelectionCommandParams; - this._scrollToSelectionForExpand(param); - } - }) - ); + this.disposeWithMe(this._commandService.onCommandExecuted((command) => { + if (SHEET_NAVIGATION_COMMANDS.includes(command.id)) { + this._scrollToSelection(); + } else if (command.id === ScrollToCellOperation.id) { + const param = command.params as IRange; + this.scrollToRange(param); + } else if (command.id === ExpandSelectionCommand.id) { + const param = command.params as IExpandSelectionCommandParams; + this._scrollToSelectionForExpand(param); + } + })); } private _scrollToSelectionForExpand(param: IExpandSelectionCommandParams) { diff --git a/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts index ffe54de85f..ddab5fb57a 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts @@ -15,7 +15,7 @@ */ import type { ICommandInfo, IRange, Workbook, Worksheet } from '@univerjs/core'; -import { CommandType, ICommandService, Inject, Rectangle, RxDisposable } from '@univerjs/core'; +import { CommandType, FOCUSING_SHEET, ICommandService, IContextService, Inject, Rectangle, RxDisposable } from '@univerjs/core'; import type { IRenderContext, IRenderModule, IViewportInfos, IWheelEvent, Scene } from '@univerjs/engine-render'; import { Layer, @@ -48,6 +48,7 @@ interface ISetWorksheetMutationParams { export class SheetRenderController extends RxDisposable implements IRenderModule { constructor( private readonly _context: IRenderContext, + @IContextService private readonly _contextService: IContextService, @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, @Inject(SheetsRenderService) private readonly _sheetRenderService: SheetsRenderService, @ICommandService private readonly _commandService: ICommandService @@ -412,7 +413,7 @@ export class SheetRenderController extends RxDisposable implements IRenderModule private _initMouseWheel(scene: Scene, viewMain: Viewport) { this.disposeWithMe( scene.onMouseWheel$.subscribeEvent((evt: IWheelEvent, state) => { - if (evt.ctrlKey) { + if (evt.ctrlKey || !this._contextService.getContextValue(FOCUSING_SHEET)) { return; } diff --git a/packages/sheets-ui/src/controllers/render-controllers/zoom.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/zoom.render-controller.ts index d05d2e2172..f2259202e6 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/zoom.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/zoom.render-controller.ts @@ -15,7 +15,7 @@ */ import type { Workbook } from '@univerjs/core'; -import { Disposable, ICommandService, Inject } from '@univerjs/core'; +import { Disposable, FOCUSING_SHEET, ICommandService, IContextService, Inject } from '@univerjs/core'; import type { IRenderContext, IRenderModule, IWheelEvent } from '@univerjs/engine-render'; import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service'; import { getSheetObject } from '../utils/component-tools'; @@ -25,12 +25,13 @@ export class SheetsZoomRenderController extends Disposable implements IRenderMod constructor( private readonly _context: IRenderContext, @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, - @ICommandService private readonly _commandService: ICommandService + @ICommandService private readonly _commandService: ICommandService, + @IContextService private readonly _contextService: IContextService ) { super(); - this._skeletonListener(); - this._zoomEventBinding(); + this._initSkeletonListener(); + this._initZoomEventListener(); } updateZoom(worksheetId: string, zoomRatio: number): boolean { @@ -45,15 +46,12 @@ export class SheetsZoomRenderController extends Disposable implements IRenderMod return true; } - private _zoomEventBinding() { - const scene = this._getSheetObject()?.scene; - if (scene == null) { - return; - } + private _initZoomEventListener() { + const scene = this._getSheetObject().scene; this.disposeWithMe( scene.onMouseWheel$.subscribeEvent((e: IWheelEvent) => { - if (!e.ctrlKey) { + if (!e.ctrlKey || !this._contextService.getContextValue(FOCUSING_SHEET)) { return; } @@ -83,7 +81,7 @@ export class SheetsZoomRenderController extends Disposable implements IRenderMod ); } - private _skeletonListener() { + private _initSkeletonListener() { this.disposeWithMe(this._sheetSkeletonManagerService.currentSkeletonBefore$.subscribe((param) => { if (param == null) { return; @@ -106,6 +104,6 @@ export class SheetsZoomRenderController extends Disposable implements IRenderMod } private _getSheetObject() { - return getSheetObject(this._context.unit, this._context); + return getSheetObject(this._context.unit, this._context)!; } } diff --git a/packages/sheets-ui/src/controllers/sheet-ui.controller.ts b/packages/sheets-ui/src/controllers/sheet-ui.controller.ts index 50ae502d2b..10cfa81ba1 100644 --- a/packages/sheets-ui/src/controllers/sheet-ui.controller.ts +++ b/packages/sheets-ui/src/controllers/sheet-ui.controller.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { DependencyOverride } from '@univerjs/core'; import { connectInjector, Disposable, ICommandService, Inject, Injector, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; import { SetBoldCommand, @@ -230,6 +231,8 @@ import { sheetPermissionAddProtectContextMenuFactory, sheetPermissionChangeSheet export interface IUniverSheetsUIConfig { menu: MenuConfig; + + override?: DependencyOverride; } export const DefaultSheetUiConfig = {}; @@ -237,14 +240,14 @@ export const DefaultSheetUiConfig = {}; @OnLifecycle(LifecycleStages.Ready, SheetUIController) export class SheetUIController extends Disposable { constructor( - private readonly _config: Partial, - @Inject(Injector) private readonly _injector: Injector, - @Inject(ComponentManager) private readonly _componentManager: ComponentManager, - @ILayoutService private readonly _layoutService: ILayoutService, - @ICommandService private readonly _commandService: ICommandService, - @IShortcutService private readonly _shortcutService: IShortcutService, - @IMenuService private readonly _menuService: IMenuService, - @IUIPartsService private readonly _uiPartsService: IUIPartsService + protected readonly _config: Partial, + @Inject(Injector) protected readonly _injector: Injector, + @Inject(ComponentManager) protected readonly _componentManager: ComponentManager, + @ILayoutService protected readonly _layoutService: ILayoutService, + @ICommandService protected readonly _commandService: ICommandService, + @IShortcutService protected readonly _shortcutService: IShortcutService, + @IMenuService protected readonly _menuService: IMenuService, + @IUIPartsService protected readonly _uiPartsService: IUIPartsService ) { super(); @@ -507,7 +510,7 @@ export class SheetUIController extends Disposable { }); } - private _initWorkbenchParts(): void { + protected _initWorkbenchParts(): void { const uiController = this._uiPartsService; const injector = this._injector; @@ -516,7 +519,7 @@ export class SheetUIController extends Disposable { this.disposeWithMe(uiController.registerComponent(BuiltInUIPart.CONTENT, () => connectInjector(RenderSheetContent, injector))); } - private _initFocusHandler(): void { + protected _initFocusHandler(): void { this.disposeWithMe( this._layoutService.registerFocusHandler(UniverInstanceType.UNIVER_SHEET, (_unitId: string) => { // DEBT: `_unitId` is not used hence we cannot support Univer mode now diff --git a/packages/sheets-ui/src/controllers/shortcuts/utils.ts b/packages/sheets-ui/src/controllers/shortcuts/utils.ts index d0a535644b..747ec9040a 100644 --- a/packages/sheets-ui/src/controllers/shortcuts/utils.ts +++ b/packages/sheets-ui/src/controllers/shortcuts/utils.ts @@ -20,7 +20,7 @@ import { FOCUSING_COMMON_DRAWINGS, FOCUSING_EDITOR_INPUT_FORMULA, FOCUSING_EDITOR_STANDALONE, - FOCUSING_FORMULA_EDITOR, + FOCUSING_FX_BAR_EDITOR, FOCUSING_SHEET, FOCUSING_UNIVER_EDITOR, } from '@univerjs/core'; @@ -48,7 +48,7 @@ export function whenSheetEditorFocusedAndFxNotFocused(contextService: IContextSe contextService.getContextValue(FOCUSING_SHEET) && contextService.getContextValue(FOCUSING_UNIVER_EDITOR) && !contextService.getContextValue(EDITOR_ACTIVATED) && - !contextService.getContextValue(FOCUSING_FORMULA_EDITOR) && + !contextService.getContextValue(FOCUSING_FX_BAR_EDITOR) && !contextService.getContextValue(FOCUSING_COMMON_DRAWINGS) ); } @@ -77,7 +77,7 @@ export function whenEditorActivated(contextService: IContextService) { */ export function whenFormulaEditorFocused(contextService: IContextService) { return ( - contextService.getContextValue(FOCUSING_FORMULA_EDITOR) && + contextService.getContextValue(FOCUSING_FX_BAR_EDITOR) && contextService.getContextValue(FOCUSING_UNIVER_EDITOR) ); } diff --git a/packages/sheets-ui/src/controllers/utils/selections-tools.ts b/packages/sheets-ui/src/controllers/utils/selections-tools.ts index 06033a81b9..ab9b0bc0a2 100644 --- a/packages/sheets-ui/src/controllers/utils/selections-tools.ts +++ b/packages/sheets-ui/src/controllers/utils/selections-tools.ts @@ -18,22 +18,21 @@ import type { IAccessor, Workbook } from '@univerjs/core'; import { IUniverInstanceService, RANGE_TYPE, Rectangle, UniverInstanceType } from '@univerjs/core'; import type { ISelectionWithStyle } from '@univerjs/sheets'; import { MERGE_CELL_INTERCEPTOR_CHECK, MergeCellController, RangeProtectionRuleModel, SheetsSelectionsService } from '@univerjs/sheets'; -import { combineLatest, map } from 'rxjs'; +import { combineLatest, map, of, switchMap } from 'rxjs'; export function getSheetSelectionsDisabled$(accessor: IAccessor) { const selectionManagerService = accessor.get(SheetsSelectionsService); const rangeProtectionRuleModel = accessor.get(RangeProtectionRuleModel); const univerInstanceService = accessor.get(IUniverInstanceService); - const workbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; - const unitId = workbook.getUnitId(); - const mergeCellController = accessor.get(MergeCellController); + const workbook$ = univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET); return combineLatest([ selectionManagerService.selectionMoveEnd$, - workbook.activeSheet$, + workbook$.pipe(map((workbook) => workbook?.getUnitId() ?? '')), + workbook$.pipe(switchMap((workbook) => workbook?.activeSheet$ ?? of(null))), ]).pipe( - map(([selection, sheet]) => { + map(([selection, unitId, sheet]) => { if (!sheet) return false; if (!selection || selection.length === 0) return false; const subUnitId = sheet.getSheetId(); diff --git a/packages/sheets-ui/src/index.ts b/packages/sheets-ui/src/index.ts index 2aec698828..d246a24269 100644 --- a/packages/sheets-ui/src/index.ts +++ b/packages/sheets-ui/src/index.ts @@ -23,6 +23,7 @@ export { SheetsRenderService } from './services/sheets-render.service'; export { SHEET_VIEWPORT_KEY as VIEWPORT_KEY } from './common/keys'; export { AutoFillController } from './controllers/auto-fill.controller'; export { CellCustomRenderController } from './controllers/cell-custom-render.controller'; +export { SheetUIController, type IUniverSheetsUIConfig } from './controllers/sheet-ui.controller'; export { PASTE_SPECIAL_MENU_ID } from './controllers/menu/menu'; export { whenFormulaEditorActivated } from './controllers/shortcuts/utils'; export { @@ -32,7 +33,7 @@ export { getTransformCoord, } from './controllers/utils/component-tools'; export { checkInHeaderRanges } from './controllers/utils/selections-tools'; -export { useActiveWorkbook, useActiveWorksheet } from './components/hook'; +export { useActiveWorkbook, useActiveWorksheet, useWorkbooks } from './components/hook'; export { whenSheetEditorFocused } from './controllers/shortcuts/utils'; export type { IEditorBridgeServiceParam } from './services/editor-bridge.service'; export { AutoFillService, IAutoFillService } from './services/auto-fill/auto-fill.service'; @@ -88,6 +89,8 @@ export { FormulaEditorController } from './controllers/editor/formula-editor.con export { StatusBarController } from './controllers/status-bar.controller'; export { SheetPermissionInterceptorBaseController } from './controllers/permission/sheet-permission-interceptor-base.controller'; export type { IRangeProtectionRenderCellData } from './views/permission/extensions/range-protection.render'; +export { RenderSheetFooter, RenderSheetHeader, RenderSheetContent } from './views/sheet-container/SheetContainer'; +export { SheetBar } from './views/sheet-bar/SheetBar'; export { SheetPrintInterceptorService } from './services/print-interceptor.service'; export { UniverSheetsMobileUIPlugin } from './mobile-sheets-ui-plugin'; export { MobileSheetBar } from './views/mobile/sheet-bar/MobileSheetBar'; @@ -166,6 +169,9 @@ export { SetZoomRatioOperation } from './commands/operations/set-zoom-ratio.oper export { SheetPermissionOpenDialogOperation } from './commands/operations/sheet-permission-open-dialog.operation'; export { SheetPermissionOpenPanelOperation } from './commands/operations/sheet-permission-open-panel.operation'; export { SidebarDefinedNameOperation } from './commands/operations/sidebar-defined-name.operation'; +export { type IBaseSheetBarProps } from './views/sheet-bar/sheet-bar-tabs/SheetBarItem'; +export { FONT_FAMILY_COMPONENT, FONT_FAMILY_ITEM_COMPONENT } from './components/font-family/interface'; +export { FONT_SIZE_COMPONENT } from './components/font-size/interface'; export { attachSelectionWithCoord, attachPrimaryWithCoord } from './services/selection/util'; // #endregion diff --git a/packages/sheets-ui/src/services/canvas-pop-manager.service.ts b/packages/sheets-ui/src/services/canvas-pop-manager.service.ts index f9aa6beb53..371d73b798 100644 --- a/packages/sheets-ui/src/services/canvas-pop-manager.service.ts +++ b/packages/sheets-ui/src/services/canvas-pop-manager.service.ts @@ -282,16 +282,25 @@ export class SheetCanvasPopManagerService extends Disposable { y: activeViewport.viewportScrollY, }; - const canvasClientRect = engine.getCanvasElement().getBoundingClientRect(); - const { top, left } = canvasClientRect; + const canvasElement = engine.getCanvasElement(); + const canvasClientRect = canvasElement.getBoundingClientRect(); + + // We should take the scale into account when canvas is scaled by CSS. + const widthOfCanvas = pxToNum(canvasElement.style.width); // declared width + const { top, left, width } = canvasClientRect; // real width affected by scale + const scaleAdjust = width / widthOfCanvas; return { - left: ((cellInfo.startX - scrollXY.x) * scaleX) + left, - right: (cellInfo.endX - scrollXY.x) * scaleX + left, - top: ((cellInfo.startY - scrollXY.y) * scaleY) + top, - bottom: ((cellInfo.endY - scrollXY.y) * scaleY) + top, + left: ((cellInfo.startX - scrollXY.x) * scaleAdjust * scaleX) + left, + right: (cellInfo.endX - scrollXY.x) * scaleAdjust * scaleX + left, + top: ((cellInfo.startY - scrollXY.y) * scaleAdjust * scaleY) + top, + bottom: ((cellInfo.endY - scrollXY.y) * scaleAdjust * scaleY) + top, }; } // #endregion } + +function pxToNum(width: string): number { + return Number.parseInt(width.replace('px', '')); +} diff --git a/packages/sheets-ui/src/services/editor-bridge.service.ts b/packages/sheets-ui/src/services/editor-bridge.service.ts index 81bc62103c..f8dd539077 100644 --- a/packages/sheets-ui/src/services/editor-bridge.service.ts +++ b/packages/sheets-ui/src/services/editor-bridge.service.ts @@ -162,6 +162,12 @@ export class EditorBridgeService extends Disposable implements IEditorBridgeServ priority: -1, handler: (_value) => _value, })); + + this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_SHEET).subscribe((unit) => { + if (unit.getUnitId() === this._currentEditCellState?.unitId) { + this._clearCurrentEditCellState(); + } + }); } refreshEditCellState() { @@ -189,11 +195,16 @@ export class EditorBridgeService extends Disposable implements IEditorBridgeServ } const editCellState = this.getLatestEditCellState(); - this._currentEditCellState = editCellState; + this._currentEditCellState = editCellState; this._currentEditCellState$.next(editCellState); } + private _clearCurrentEditCellState() { + this._currentEditCellState = null; + this._currentEditCellState$.next(null); + } + getEditCellState(): Readonly> { return this._currentEditCellState; } diff --git a/packages/sheets-ui/src/sheets-ui-plugin.ts b/packages/sheets-ui/src/sheets-ui-plugin.ts index 73bd1aa446..90eea09899 100644 --- a/packages/sheets-ui/src/sheets-ui-plugin.ts +++ b/packages/sheets-ui/src/sheets-ui-plugin.ts @@ -15,7 +15,7 @@ */ import type { Dependency, Workbook } from '@univerjs/core'; -import { DependentOn, Inject, Injector, IUniverInstanceService, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; +import { DependentOn, Inject, Injector, IUniverInstanceService, mergeOverrideWithDependencies, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import { filter } from 'rxjs/operators'; import { IRenderManagerService } from '@univerjs/engine-render'; @@ -99,8 +99,8 @@ export class UniverSheetsUIPlugin extends Plugin { this._config = Tools.deepMerge({}, DefaultSheetUiConfig, this._config); } - override onStarting(): void { - ([ + override onStarting(injector: Injector): void { + mergeOverrideWithDependencies([ // services [ShortcutExperienceService], [IEditorBridgeService, { useClass: EditorBridgeService }], @@ -146,7 +146,7 @@ export class UniverSheetsUIPlugin extends Plugin { [SheetPermissionInterceptorBaseController], [SheetPermissionInitController], // [MoveRangeController], - ] as Dependency[]).forEach((d) => this._injector.add(d)); + ] as Dependency[], this._config.override).forEach((d) => injector.add(d)); this._injector.add( [ diff --git a/packages/sheets-ui/src/views/editor-container/EditorContainer.tsx b/packages/sheets-ui/src/views/editor-container/EditorContainer.tsx index 271f9b6dee..265d983dd0 100644 --- a/packages/sheets-ui/src/views/editor-container/EditorContainer.tsx +++ b/packages/sheets-ui/src/views/editor-container/EditorContainer.tsx @@ -35,10 +35,9 @@ const EDITOR_DEFAULT_POSITION = { }; /** - * Floating editor's container. + * Cell editor container. * @returns */ - export const EditorContainer: React.FC = () => { const [state, setState] = useState({ ...EDITOR_DEFAULT_POSITION, diff --git a/packages/sheets-ui/src/views/sheet-container/SheetContainer.tsx b/packages/sheets-ui/src/views/sheet-container/SheetContainer.tsx index 387e6a98af..cd40b9e76a 100644 --- a/packages/sheets-ui/src/views/sheet-container/SheetContainer.tsx +++ b/packages/sheets-ui/src/views/sheet-container/SheetContainer.tsx @@ -16,7 +16,7 @@ import { IUniverInstanceService, UniverInstanceType, useDependency } from '@univerjs/core'; import type { Workbook } from '@univerjs/core'; -import React from 'react'; +import React, { useMemo } from 'react'; import { useObservable } from '@univerjs/ui'; import { CountBar } from '../count-bar/CountBar'; @@ -25,11 +25,11 @@ import { FormulaBar } from '../formula-bar/FormulaBar'; import { OperateContainer } from '../operate-container/OperateContainer'; import { SheetBar } from '../sheet-bar/SheetBar'; import { StatusBar } from '../status-bar/StatusBar'; +import { useActiveWorkbook } from '../../components/hook'; import styles from './index.module.less'; export function RenderSheetFooter() { - const univerInstanceService = useDependency(IUniverInstanceService); - const workbook = useObservable(() => univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET), null, false, []); + const workbook = useActiveWorkbook(); if (!workbook) return null; return ( @@ -42,9 +42,8 @@ export function RenderSheetFooter() { } export function RenderSheetHeader() { - const univerInstanceService = useDependency(IUniverInstanceService); - const workbook = useObservable(() => univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET), null, false, []); - if (!workbook) return null; + const hasWorkbook = useHasWorkbook(); + if (!hasWorkbook) return null; return ( @@ -55,9 +54,8 @@ export function RenderSheetHeader() { * @deprecated We should not write into this component anymore. */ export function RenderSheetContent() { - const univerInstanceService = useDependency(IUniverInstanceService); - const workbook = useObservable(() => univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET), null, false, []); - if (!workbook) return null; + const hasWorkbook = useHasWorkbook(); + if (!hasWorkbook) return null; return ( <> @@ -66,3 +64,9 @@ export function RenderSheetContent() { ); } + +function useHasWorkbook(): boolean { + const univerInstanceService = useDependency(IUniverInstanceService); + const workbook = useObservable(() => univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET), null, false, []); + return useMemo(() => univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET).length > 0, [univerInstanceService, workbook]); +} diff --git a/packages/sheets/package.json b/packages/sheets/package.json index 54d99053b4..d588699df8 100644 --- a/packages/sheets/package.json +++ b/packages/sheets/package.json @@ -66,6 +66,7 @@ "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-numfmt": "workspace:*", "@univerjs/rpc": "workspace:*", + "@univerjs/sheets": "workspace:*", "rxjs": ">=7.0.0" }, "dependencies": { diff --git a/packages/slides-ui/package.json b/packages/slides-ui/package.json index 07d05063f4..29c321397d 100644 --- a/packages/slides-ui/package.json +++ b/packages/slides-ui/package.json @@ -1,7 +1,7 @@ { "name": "@univerjs/slides-ui", "version": "0.2.6", - "private": true, + "private": false, "description": "Univer normal ui-plugin-slides", "author": "DreamNum ", "license": "Apache-2.0", @@ -68,7 +68,6 @@ "@univerjs/core": "workspace:*", "@univerjs/design": "workspace:*", "@univerjs/docs": "workspace:*", - "@univerjs/docs-ui": "workspace:*", "@univerjs/drawing": "workspace:*", "@univerjs/engine-render": "workspace:*", "@univerjs/slides": "workspace:*", diff --git a/packages/slides-ui/src/components/slide-bar/SlideBar.tsx b/packages/slides-ui/src/components/slide-bar/SlideBar.tsx index 0fe4cdb222..948dff93f8 100644 --- a/packages/slides-ui/src/components/slide-bar/SlideBar.tsx +++ b/packages/slides-ui/src/components/slide-bar/SlideBar.tsx @@ -116,9 +116,6 @@ export function SlideSideBar() { ))} - {/*
- -
*/} ); diff --git a/packages/slides-ui/src/controllers/shortcuts/utils.ts b/packages/slides-ui/src/controllers/shortcuts/utils.ts index e4e96c5139..80fee8fa19 100644 --- a/packages/slides-ui/src/controllers/shortcuts/utils.ts +++ b/packages/slides-ui/src/controllers/shortcuts/utils.ts @@ -20,15 +20,17 @@ import { FOCUSING_COMMON_DRAWINGS, FOCUSING_EDITOR_INPUT_FORMULA, FOCUSING_EDITOR_STANDALONE, - FOCUSING_FORMULA_EDITOR, FOCUSING_SLIDE, FOCUSING_UNIVER_EDITOR, + FORMULA_EDITOR_ACTIVATED, } from '@univerjs/core'; export function whenSheetFocused(contextService: IContextService) { return contextService.getContextValue(FOCUSING_SLIDE); } +// FIXME: why coupled with sheet editors? + /** * Requires the currently focused unit to be Workbook and the sheet editor is focused but not activated. * @param contextService @@ -48,7 +50,7 @@ export function whenSheetEditorFocusedAndFxNotFocused(contextService: IContextSe contextService.getContextValue(FOCUSING_SLIDE) && contextService.getContextValue(FOCUSING_UNIVER_EDITOR) && !contextService.getContextValue(EDITOR_ACTIVATED) && - !contextService.getContextValue(FOCUSING_FORMULA_EDITOR) && + !contextService.getContextValue(FORMULA_EDITOR_ACTIVATED) && !contextService.getContextValue(FOCUSING_COMMON_DRAWINGS) ); } @@ -77,7 +79,7 @@ export function whenEditorActivated(contextService: IContextService) { */ export function whenFormulaEditorFocused(contextService: IContextService) { return ( - contextService.getContextValue(FOCUSING_FORMULA_EDITOR) && + contextService.getContextValue(FORMULA_EDITOR_ACTIVATED) && contextService.getContextValue(FOCUSING_UNIVER_EDITOR) ); } diff --git a/packages/slides-ui/src/controllers/slide-editing.render-controller.ts b/packages/slides-ui/src/controllers/slide-editing.render-controller.ts index b4bd047c6b..70ca41d65e 100644 --- a/packages/slides-ui/src/controllers/slide-editing.render-controller.ts +++ b/packages/slides-ui/src/controllers/slide-editing.render-controller.ts @@ -32,9 +32,9 @@ import { FOCUSING_EDITOR_BUT_HIDDEN, FOCUSING_EDITOR_INPUT_FORMULA, FOCUSING_EDITOR_STANDALONE, - FOCUSING_FORMULA_EDITOR, FOCUSING_SHEET, FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE, + FORMULA_EDITOR_ACTIVATED, HorizontalAlign, ICommandService, IContextService, @@ -660,7 +660,7 @@ export class SlideEditingRenderController extends Disposable implements IRenderM */ private _initialKeyboardListener(d: DisposableCollection) { d.add(this._textSelectionRenderManager.onInputBefore$.subscribe((config) => { - const isFocusFormulaEditor = this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR); + const isFocusFormulaEditor = this._contextService.getContextValue(FORMULA_EDITOR_ACTIVATED); const isFocusSheets = this._contextService.getContextValue(FOCUSING_SHEET); // TODO@Jocs: should get editor instead of current doc @@ -703,7 +703,7 @@ export class SlideEditingRenderController extends Disposable implements IRenderM * but move the cursor within the editor instead. */ if (keycode != null && - (this._cursorChange === CursorChange.CursorChange || this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR)) + (this._cursorChange === CursorChange.CursorChange || this._contextService.getContextValue(FORMULA_EDITOR_ACTIVATED)) ) { this._moveInEditor(keycode, isShift); return; @@ -773,7 +773,7 @@ export class SlideEditingRenderController extends Disposable implements IRenderM this._contextService.setContextValue(FOCUSING_EDITOR_INPUT_FORMULA, false); this._contextService.setContextValue(EDITOR_ACTIVATED, false); this._contextService.setContextValue(FOCUSING_EDITOR_BUT_HIDDEN, false); - this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, false); + this._contextService.setContextValue(FORMULA_EDITOR_ACTIVATED, false); this._cellEditorManagerService.setState({ show: param.visible, diff --git a/packages/slides-ui/src/controllers/slide-ui.controller.ts b/packages/slides-ui/src/controllers/slide-ui.controller.ts index 7b5977c86c..0db0ced686 100644 --- a/packages/slides-ui/src/controllers/slide-ui.controller.ts +++ b/packages/slides-ui/src/controllers/slide-ui.controller.ts @@ -41,7 +41,7 @@ import { SlideAddTextMenuItemFactory, TEXT_ICON_ID } from './text.menu'; export interface IUniverSlidesDrawingConfig { menu?: MenuConfig; - overrides?: DependencyOverride; + override?: DependencyOverride; } export const DefaultSlidesDrawingConfig: IUniverSlidesDrawingConfig = { @@ -50,18 +50,16 @@ export const DefaultSlidesDrawingConfig: IUniverSlidesDrawingConfig = { /** * This controller registers UI parts of slide workbench to the base-ui workbench. */ -// @OnLifecycle(LifecycleStages.Ready, SlideUIController) -@OnLifecycle(LifecycleStages.Ready, SlideUIController) -export class SlideUIController extends Disposable { +@OnLifecycle(LifecycleStages.Ready, SlidesUIController) +export class SlidesUIController extends Disposable { constructor( private readonly _config: Partial, - @Inject(Injector) private readonly _injector: Injector, - @IMenuService private readonly _menuService: IMenuService, - @Inject(ComponentManager) private readonly _componentManager: ComponentManager, - @IUIPartsService private readonly _uiPartsService: IUIPartsService, - @ICommandService private readonly _commandService: ICommandService, - @IShortcutService private readonly _shortcutService: IShortcutService - + @Inject(Injector) protected readonly _injector: Injector, + @IMenuService protected readonly _menuService: IMenuService, + @Inject(ComponentManager) protected readonly _componentManager: ComponentManager, + @IUIPartsService protected readonly _uiPartsService: IUIPartsService, + @ICommandService protected readonly _commandService: ICommandService, + @IShortcutService protected readonly _shortcutService: IShortcutService ) { super(); @@ -107,13 +105,13 @@ export class SlideUIController extends Disposable { ToggleSlideEditSidebarOperation, DeleteSlideElementOperation, - //cmds for editor + // commands for editor SetTextEditArrowOperation, ].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command))); } - private _initUIComponents(): void { + protected _initUIComponents(): void { this.disposeWithMe( this._uiPartsService.registerComponent(BuiltInUIPart.LEFT_SIDEBAR, () => connectInjector(SlideSideBar, this._injector)) ); diff --git a/packages/slides-ui/src/index.ts b/packages/slides-ui/src/index.ts index 557c0d3119..1a17228d08 100644 --- a/packages/slides-ui/src/index.ts +++ b/packages/slides-ui/src/index.ts @@ -15,8 +15,11 @@ */ export { UniverSlidesUIPlugin } from './slides-ui-plugin'; +export { SlidesUIController } from './controllers/slide-ui.controller'; +export { SlideSideBar } from './components/slide-bar/SlideBar'; export { SlideCanvasPopMangerService } from './services/slide-popup-manager.service'; +export type { IUniverSlidesDrawingConfig } from './controllers/slide-ui.controller'; // #region - all commands diff --git a/packages/slides-ui/src/slides-ui-plugin.ts b/packages/slides-ui/src/slides-ui-plugin.ts index a4a9f33432..07011a4606 100644 --- a/packages/slides-ui/src/slides-ui-plugin.ts +++ b/packages/slides-ui/src/slides-ui-plugin.ts @@ -15,11 +15,11 @@ */ import type { Dependency, SlideDataModel } from '@univerjs/core'; -import { Inject, Injector, IUniverInstanceService, Plugin, UniverInstanceType } from '@univerjs/core'; +import { Inject, Injector, IUniverInstanceService, mergeOverrideWithDependencies, Plugin, UniverInstanceType } from '@univerjs/core'; import { IRenderManagerService } from '@univerjs/engine-render'; import type { IUniverSlidesDrawingConfig } from './controllers/slide-ui.controller'; -import { SlideUIController } from './controllers/slide-ui.controller'; +import { SlidesUIController } from './controllers/slide-ui.controller'; import { SlideRenderController } from './controllers/slide.render-controller'; import { SlidePopupMenuController } from './controllers/popup-menu.controller'; import { SlideCanvasPopMangerService } from './services/slide-popup-manager.service'; @@ -44,16 +44,16 @@ export class UniverSlidesUIPlugin extends Plugin { } override onStarting(): void { - ([ + mergeOverrideWithDependencies([ [ISlideEditorBridgeService, { useClass: SlideEditorBridgeService }], // used by SlideUIController --> EditorContainer [ISlideEditorManagerService, { useClass: SlideEditorManagerService }], [SlideCanvasPopMangerService], - ] as Dependency[]).forEach((d) => this._injector.add(d)); + ] as Dependency[], this._config.override).forEach((d) => this._injector.add(d)); } override onReady(): void { - ([ + mergeOverrideWithDependencies([ // cannot register in _renderManagerService now. // [ISlideEditorBridgeService, { useClass: SlideEditorBridgeService }], // // used by SlideUIController --> EditorContainer @@ -62,11 +62,12 @@ export class UniverSlidesUIPlugin extends Plugin { // This controller should be registered in Ready stage. // this controller would add a new RenderUnit (__INTERNAL_EDITOR__DOCS_NORMAL) // so this new RenderUnit does not have ISlideEditorBridgeService & ISlideEditorManagerService if - // editorservice were create in renderManagerService - [SlideUIController], + [SlidesUIController, { + useFactory: () => this._injector.createInstance(SlidesUIController, this._config), + }], // editor service were create in renderManagerService [SlideRenderController], [SlidePopupMenuController], - ] as Dependency[]).forEach((m) => { + ] as Dependency[], this._config.override).forEach((m) => { this._injector.add(m); }); } diff --git a/packages/slides/package.json b/packages/slides/package.json index 0073bd6f44..e072d04ab9 100644 --- a/packages/slides/package.json +++ b/packages/slides/package.json @@ -1,7 +1,7 @@ { "name": "@univerjs/slides", "version": "0.2.6", - "private": true, + "private": false, "description": "", "author": "DreamNum ", "license": "Apache-2.0", @@ -61,7 +61,6 @@ }, "peerDependencies": { "@univerjs/core": "workspace:*", - "@univerjs/docs": "workspace:*", "@univerjs/engine-render": "workspace:*", "rxjs": ">=7.0.0" }, diff --git a/packages/ui/src/components/editor/TextEditor.tsx b/packages/ui/src/components/editor/TextEditor.tsx index 99931cb372..7f33aecd27 100644 --- a/packages/ui/src/components/editor/TextEditor.tsx +++ b/packages/ui/src/components/editor/TextEditor.tsx @@ -30,6 +30,7 @@ const excludeProps = new Set([ 'cancelDefaultResizeListener', 'isSheetEditor', 'canvasStyle', + 'isFormulaEditor', 'isSingle', 'isReadonly', 'onlyInputFormula', @@ -51,16 +52,18 @@ export interface ITextEditorProps { snapshot?: IDocumentData; // The default initialization snapshot for the editor can be simply replaced with the value attribute, for cellEditor and formulaBar + value?: string; // default values. + + // WTF: snapshot and value both exists? And use have to set value and snapshot.textStream separately> + resizeCallBack?: (editor: Nullable) => void; // Container scale callback. cancelDefaultResizeListener?: boolean; // Disable the default container scaling listener, for cellEditor and formulaBar - isSheetEditor?: boolean; // Specify whether the editor is bound to a sheet. Currently, there are cellEditor and formulaBar. - canvasStyle?: IEditorCanvasStyle; // Setting the style of the editor is similar to setting the drawing style of a canvas, and therefore, it should be distinguished from the CSS 'style'. At present, it only supports the 'fontsize' attribute. - value?: string; // default values. - + isSheetEditor?: boolean; // Specify whether the editor is bound to a sheet. Currently, there are cellEditor and formulaBar. + isFormulaEditor?: boolean; isSingle?: boolean; // Set whether the editor allows multiline input, default is true, equivalent to input; false is equivalent to textarea. isReadonly?: boolean; // Set the editor to read-only state. @@ -74,13 +77,10 @@ export interface ITextEditorProps { openForSheetSubUnitId?: Nullable; // Configuring the default worksheet where the selector opens determines whether the ref includes a [unitId]sheet1 prefix. onChange?: (value: Nullable) => void; // Callback for changes in the selector value. - onActive?: (state: boolean) => void; // Callback for editor active. - onValid?: (state: boolean) => void; // Editor input value validation, currently effective only under onlyRange and onlyFormula conditions. placeholder?: string; // Placeholder text. - isValueValid?: boolean; // Whether the value is valid. } @@ -99,23 +99,17 @@ export function TextEditor(props: ITextEditorProps & Omit { const localeService = useDependency(LocaleService); const viewValue = useMemo(() => { - let fontFamily = localeService.t(`fontFamily.${(`${value}` ?? '').replace(/\s/g, '')}`); + if (value == null) return ''; + let fontFamily = localeService.t(`fontFamily.${(`${value ?? ''}`).replace(/\s/g, '')}`); // Handle font family from copy paste. if (fontFamily.startsWith('fontFamily.') && typeof value === 'string') { diff --git a/packages/ui/src/components/hooks/observable.ts b/packages/ui/src/components/hooks/observable.ts index 3ae2fdf36e..86b384ad6e 100644 --- a/packages/ui/src/components/hooks/observable.ts +++ b/packages/ui/src/components/hooks/observable.ts @@ -33,6 +33,7 @@ function unwrap(o: ObservableOrFn): Observable { export function useObservable(observable: ObservableOrFn, defaultValue: T | undefined, shouldHaveSyncValue?: true): T; export function useObservable(observable: Nullable>, defaultValue: T): T; export function useObservable(observable: Nullable>, defaultValue?: undefined): T | undefined; +export function useObservable(observable: Nullable>, defaultValue: T, shouldHaveSyncValue?: boolean, deps?: any[]): T; export function useObservable(observable: Nullable>, defaultValue: undefined, shouldHaveSyncValue: true, deps?: any[]): T; export function useObservable(observable: Nullable>, defaultValue?: T, shouldHaveSyncValue?: boolean, deps?: any[]): T | undefined; /** diff --git a/packages/ui/src/controllers/ui/ui-desktop.controller.tsx b/packages/ui/src/controllers/ui/ui-desktop.controller.tsx index ef8139366b..eaa0e5aab2 100644 --- a/packages/ui/src/controllers/ui/ui-desktop.controller.tsx +++ b/packages/ui/src/controllers/ui/ui-desktop.controller.tsx @@ -51,10 +51,10 @@ export class DesktopUIController extends Disposable { private _bootstrapWorkbench(): void { this.disposeWithMe( - bootstrap(this._injector, this._config, (canvasElement, containerElement) => { + bootstrap(this._injector, this._config, (contentElement, containerElement) => { if (this._layoutService) { this.disposeWithMe(this._layoutService.registerRootContainerElement(containerElement)); - this.disposeWithMe(this._layoutService.registerCanvasElement(canvasElement as HTMLCanvasElement)); + this.disposeWithMe(this._layoutService.registerContentElement(contentElement)); } // TODO: this is subject to change in the future for Uni-mode @@ -64,7 +64,7 @@ export class DesktopUIController extends Disposable { if (!render.unitId) return; if (isInternalEditorID(render.unitId)) return; - render.engine.setContainer(canvasElement); + render.engine.setContainer(contentElement); } }); @@ -74,7 +74,7 @@ export class DesktopUIController extends Disposable { for (const [key, render] of allRenders) { if (isInternalEditorID(key) || !((render) as RenderUnit).isRenderUnit) continue; - render.engine.setContainer(canvasElement); + render.engine.setContainer(contentElement); } this._lifecycleService.stage = LifecycleStages.Rendered; @@ -93,7 +93,7 @@ export class DesktopUIController extends Disposable { function bootstrap( injector: Injector, options: IWorkbenchOptions, - callback: (canvasEl: HTMLElement, containerElement: HTMLElement) => void + callback: (contentEl: HTMLElement, containerElement: HTMLElement) => void ): IDisposable { let mountContainer: HTMLElement; @@ -112,7 +112,7 @@ function bootstrap( } const ConnectedApp = connectInjector(DesktopWorkbench, injector); - const onRendered = (canvasElement: HTMLElement) => callback(canvasElement, mountContainer); + const onRendered = (contentElement: HTMLElement) => callback(contentElement, mountContainer); function render() { createRoot( diff --git a/packages/ui/src/controllers/ui/ui-mobile.controller.tsx b/packages/ui/src/controllers/ui/ui-mobile.controller.tsx index 9fcd0b5c42..748f50ac4e 100644 --- a/packages/ui/src/controllers/ui/ui-mobile.controller.tsx +++ b/packages/ui/src/controllers/ui/ui-mobile.controller.tsx @@ -51,7 +51,7 @@ export class MobileUIController extends Disposable implements IUIController { bootstrap(this._injector, this._config, (canvasElement, containerElement) => { if (this._layoutService) { this.disposeWithMe(this._layoutService.registerRootContainerElement(containerElement)); - this.disposeWithMe(this._layoutService.registerCanvasElement(canvasElement as HTMLCanvasElement)); + this.disposeWithMe(this._layoutService.registerContentElement(canvasElement as HTMLCanvasElement)); } this._renderManagerService.currentRender$.subscribe((renderId) => { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 9d799a53a8..fd3fdbb24b 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -75,7 +75,7 @@ export { IGlobalZoneService } from './services/global-zone/global-zone.service'; export { KeyCode, MetaKeys } from './services/shortcut/keycode'; export { ShortcutService, type IShortcutItem, IShortcutService } from './services/shortcut/shortcut.service'; export { DesktopSidebarService } from './services/sidebar/desktop-sidebar.service'; -export { ISidebarService } from './services/sidebar/sidebar.service'; +export { ISidebarService, ILeftSidebarService } from './services/sidebar/sidebar.service'; export { IZenZoneService } from './services/zen-zone/zen-zone.service'; export { UniverUIPlugin, DISABLE_AUTO_FOCUS_KEY } from './ui-plugin'; export { UniverMobileUIPlugin } from './mobile-ui-plugin'; @@ -83,7 +83,7 @@ export * from './utils'; export { Menu } from './components/menu/desktop/Menu'; export { type IConfirmPartMethodOptions } from './views/components/confirm-part/interface'; export { ComponentContainer, useComponentsOfPart, type IComponentContainerProps } from './views/components/ComponentContainer'; -export { IEditorService, EditorService } from './services/editor/editor.service'; +export { IEditorService, EditorService, Editor } from './services/editor/editor.service'; export { TextEditor } from './components/editor/TextEditor'; export { RangeSelector } from './components/range-selector/RangeSelector'; export { ProgressBar } from './components/progress-bar/ProgressBar'; @@ -106,13 +106,15 @@ export { GlobalZone } from './views/components/global-zone/GlobalZone'; export { builtInGlobalComponents } from './views/parts'; export { DesktopContextMenu as ContextMenu } from './views/components/context-menu/ContextMenu'; export { Sidebar } from './views/components/sidebar/Sidebar'; +export { type ISidebarMethodOptions } from './views/components/sidebar/interface'; export { ZenZone } from './views/components/zen-zone/ZenZone'; export { CanvasPopup } from './views/components/popup/CanvasPopup'; export { CanvasFloatDomService, type IFloatDomLayout } from './services/dom/canvas-dom-layer.service'; export { FloatDom } from './views/components/dom/FloatDom'; export { MobileContextMenu } from './views/components/context-menu/MobileContextMenu'; export { Toolbar } from './views/components/doc-bars/Toolbar'; - +export { ToolbarItem } from './views/components/doc-bars/ToolbarItem'; +export { ToolbarButton } from './views/components/doc-bars/Button/ToolbarButton'; // #endregion // #region - all commands diff --git a/packages/ui/src/services/editor/editor.service.ts b/packages/ui/src/services/editor/editor.service.ts index 03a1209e3a..af60f74ca9 100644 --- a/packages/ui/src/services/editor/editor.service.ts +++ b/packages/ui/src/services/editor/editor.service.ts @@ -36,10 +36,16 @@ export interface IEditorConfigParam { initialSnapshot?: IDocumentData; cancelDefaultResizeListener?: boolean; canvasStyle?: IEditorCanvasStyle; - isSheetEditor: boolean; + isSheetEditor: boolean; + /** + * If the editor is for formula editing. + * @deprecated this is a temp fix before refactoring editor. + */ + isFormulaEditor: boolean; isSingle: boolean; isReadonly: boolean; + onlyInputFormula: boolean; onlyInputRange: boolean; onlyInputContent: boolean; @@ -165,6 +171,11 @@ export class Editor { return this._param.isSheetEditor === true; } + /** @deprecated */ + isFormulaEditor() { + return this._param.isFormulaEditor === true; + } + getValue() { const value = this._param.documentDataModel.getBody()?.dataStream || ''; return value.replace(/\r\n/g, '').replace(/\n/g, '').replace(/\n/g, ''); @@ -422,7 +433,7 @@ export class EditorService extends Disposable implements IEditorService, IDispos editor.setFocus(true); - this._contextService.setContextValue(EDITOR_ACTIVATED, true); + // this._contextService.setContextValue(EDITOR_ACTIVATED, true); this._contextService.setContextValue(FOCUSING_EDITOR_STANDALONE, true); this._contextService.setContextValue(FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE, editor.isSingle()); @@ -447,7 +458,7 @@ export class EditorService extends Disposable implements IEditorService, IDispos } const editor = this.getEditor(editorUnitId); - if (!editor || editor.isSheetEditor()) { + if (!editor || editor.isSheetEditor() || editor.isFormulaEditor()) { return true; } @@ -597,9 +608,12 @@ export class EditorService extends Disposable implements IEditorService, IDispos register(config: IEditorConfigParam, container: HTMLDivElement): IDisposable { const { initialSnapshot, editorUnitId, canvasStyle = {} } = config; - const documentDataModel = this._univerInstanceService.createUnit( - UniverInstanceType.UNIVER_DOC, initialSnapshot || this._getBlank(editorUnitId) - ); + const documentDataModel = this._univerInstanceService.getUnit(editorUnitId, UniverInstanceType.UNIVER_DOC) + ?? this._univerInstanceService.createUnit( + UniverInstanceType.UNIVER_DOC, + initialSnapshot || this._getBlank(editorUnitId), + { makeCurrent: false } + ); let render = this._renderManagerService.getRenderById(editorUnitId); if (render == null) { @@ -630,18 +644,15 @@ export class EditorService extends Disposable implements IEditorService, IDispos private _unRegister(editorUnitId: string) { const editor = this._editors.get(editorUnitId); - if (editor == null) { return; } this._renderManagerService.removeRender(editorUnitId); - editor.documentDataModel.dispose(); - this._editors.delete(editorUnitId); - this._univerInstanceService.disposeUnit(editorUnitId); + this._contextService.setContextValue(FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE, false); /** * Compatible with the editor in the sheet scenario, @@ -711,10 +722,6 @@ export class EditorService extends Disposable implements IEditorService, IDispos return current.getUnitId(); } - private _refresh(param: IEditorStateParam): void { - this._state$.next(param); - } - private _getBlank(id: string) { return { id, diff --git a/packages/ui/src/services/layout/layout.service.ts b/packages/ui/src/services/layout/layout.service.ts index 167942fd7a..8f71a2113a 100644 --- a/packages/ui/src/services/layout/layout.service.ts +++ b/packages/ui/src/services/layout/layout.service.ts @@ -54,15 +54,15 @@ export interface ILayoutService { registerFocusHandler(type: UniverInstanceType, handler: FocusHandlerFn): IDisposable; /** Register the root container element. */ registerRootContainerElement(container: HTMLElement): IDisposable; - /** Register a canvas element. */ - registerCanvasElement(container: HTMLCanvasElement): IDisposable; + /** Register a content element. */ + registerContentElement(container: HTMLElement): IDisposable; /** Register an element as a container, especially floating components like Dialogs and Notifications. */ registerContainerElement(container: HTMLElement): IDisposable; - getCanvasElement(): HTMLCanvasElement; + getContentElement(): HTMLElement; checkElementInCurrentContainers(element: HTMLElement): boolean; - checkCanvasIsFocused(): boolean; + checkContentIsFocused(): boolean; } export const ILayoutService = createIdentifier('ui.layout-service'); @@ -81,7 +81,8 @@ export class DesktopLayoutService extends Disposable implements ILayoutService { private readonly _focusHandlers = new Map(); - private _canvasContainers: HTMLCanvasElement[] = []; + // FIXME: this don't need to be plural + private _contentElements: HTMLElement[] = []; private _allContainers: HTMLElement[] = []; constructor( @@ -128,17 +129,17 @@ export class DesktopLayoutService extends Disposable implements ILayoutService { return toDisposable(() => this._focusHandlers.delete(type)); } - registerCanvasElement(container: HTMLCanvasElement): IDisposable { - if (this._canvasContainers.indexOf(container) === -1) { - this._canvasContainers.push(container); - return toDisposable(() => remove(this._canvasContainers, container)); + registerContentElement(container: HTMLElement): IDisposable { + if (this._contentElements.indexOf(container) === -1) { + this._contentElements.push(container); + return toDisposable(() => remove(this._contentElements, container)); } - throw new Error('[DesktopLayoutService]: canvas container already registered!'); + throw new Error('[DesktopLayoutService]: content container already registered!'); } - getCanvasElement(): HTMLCanvasElement { - return this._canvasContainers[0]; + getContentElement(): HTMLElement { + return this._contentElements[0]; } registerRootContainerElement(container: HTMLElement): IDisposable { @@ -168,8 +169,8 @@ export class DesktopLayoutService extends Disposable implements ILayoutService { return this._allContainers.some((container) => container.contains(element)); } - checkCanvasIsFocused(): boolean { - return this._canvasContainers.some((canvas) => canvas === document.activeElement || canvas.contains(document.activeElement)); + checkContentIsFocused(): boolean { + return this._contentElements.some((contentEl) => contentEl === document.activeElement || contentEl.contains(document.activeElement)); } private _initUniverFocusListener(): void { diff --git a/packages/ui/src/services/sidebar/sidebar.service.ts b/packages/ui/src/services/sidebar/sidebar.service.ts index c28c6075e2..6126b8c105 100644 --- a/packages/ui/src/services/sidebar/sidebar.service.ts +++ b/packages/ui/src/services/sidebar/sidebar.service.ts @@ -20,8 +20,6 @@ import type { Subject } from 'rxjs'; import type { ISidebarMethodOptions } from '../../views/components/sidebar/interface'; -export const ISidebarService = createIdentifier('univer.sidebar-service'); - export interface ISidebarService { readonly sidebarOptions$: Subject; readonly scrollEvent$: Subject; @@ -31,3 +29,6 @@ export interface ISidebarService { get visible(): boolean; } + +export const ILeftSidebarService = createIdentifier('ui.left-sidebar.service'); +export const ISidebarService = createIdentifier('ui.sidebar.service'); diff --git a/packages/ui/src/views/components/doc-bars/hook.ts b/packages/ui/src/views/components/doc-bars/hook.ts index 9b03ddd87b..abffe0b846 100644 --- a/packages/ui/src/views/components/doc-bars/hook.ts +++ b/packages/ui/src/views/components/doc-bars/hook.ts @@ -48,9 +48,9 @@ export interface IToolbarRenderHookHandler { * you can use this hook to get the toolbar status. * @returns toolbar status */ -export function useToolbarGroups(categories: MenuPositionWithCustom[]): IToolbarRenderHookHandler { +export function useToolbarGroups(categories: MenuPositionWithCustom[], initCategory?: string): IToolbarRenderHookHandler { const menuService = useDependency(IMenuService); - const [category, setCategory] = useState(MenuPosition.TOOLBAR_START); + const [category, setCategory] = useState(initCategory ?? MenuPosition.TOOLBAR_START); const [groups, setGroups] = useState([]); const [visibleItems, setVisibleItems] = useState[]>([]); diff --git a/packages/ui/src/views/workbench/Workbench.tsx b/packages/ui/src/views/workbench/Workbench.tsx index 008cb32571..e404f09cb4 100644 --- a/packages/ui/src/views/workbench/Workbench.tsx +++ b/packages/ui/src/views/workbench/Workbench.tsx @@ -35,7 +35,7 @@ import styles from './workbench.module.less'; export interface IUniverWorkbenchProps extends IWorkbenchOptions { mountContainer: HTMLElement; - onRendered?: (container: HTMLElement) => void; + onRendered?: (containerElement: HTMLElement) => void; } export function DesktopWorkbench(props: IUniverWorkbenchProps) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eda2130046..211f28f3b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -327,9 +327,21 @@ importers: '@univerjs/ui': specifier: workspace:* version: link:../packages/ui + '@univerjs/uni-docs-ui': + specifier: workspace:* + version: link:../packages-experimental/uni-docs-ui '@univerjs/uni-formula': specifier: workspace:* version: link:../packages-experimental/uni-formula + '@univerjs/uni-formula-ui': + specifier: workspace:* + version: link:../packages-experimental/uni-formula-ui + '@univerjs/uni-sheets-ui': + specifier: workspace:* + version: link:../packages-experimental/uni-sheets-ui + '@univerjs/uni-slides-ui': + specifier: workspace:* + version: link:../packages-experimental/uni-slides-ui '@univerjs/uniscript': specifier: workspace:* version: link:../packages/uniscript @@ -389,6 +401,52 @@ importers: specifier: ^5.5.4 version: 5.5.4 + packages-experimental/uni-docs-ui: + dependencies: + '@univerjs/docs-drawing-ui': + specifier: workspace:* + version: link:../../packages/docs-drawing-ui + '@univerjs/icons': + specifier: ^0.1.72 + version: 0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + devDependencies: + '@univerjs/core': + specifier: workspace:* + version: link:../../packages/core + '@univerjs/design': + specifier: workspace:* + version: link:../../packages/design + '@univerjs/docs': + specifier: workspace:* + version: link:../../packages/docs + '@univerjs/docs-ui': + specifier: workspace:* + version: link:../../packages/docs-ui + '@univerjs/shared': + specifier: workspace:* + version: link:../../common/shared + '@univerjs/ui': + specifier: workspace:* + version: link:../../packages/ui + '@univerjs/uniui': + specifier: workspace:* + version: link:../uniui + clsx: + specifier: ^2.1.1 + version: 2.1.1 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typescript: + specifier: ^5.5.4 + version: 5.5.4 + vite: + specifier: ^5.3.5 + version: 5.3.5(@types/node@22.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@22.1.0)(happy-dom@14.12.3)(jsdom@24.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) + packages-experimental/uni-formula: dependencies: '@univerjs/icons': @@ -436,16 +494,151 @@ importers: version: 2.0.5(@types/node@22.1.0)(happy-dom@14.12.3)(jsdom@24.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) packages-experimental/uni-formula-ui: + dependencies: + react: + specifier: ^16.9.0 || ^17.0.0 || ^18.0.0 + version: 18.3.1 devDependencies: '@univerjs/core': specifier: workspace:* version: link:../../packages/core + '@univerjs/design': + specifier: workspace:* + version: link:../../packages/design + '@univerjs/docs': + specifier: workspace:* + version: link:../../packages/docs + '@univerjs/docs-ui': + specifier: workspace:* + version: link:../../packages/docs-ui + '@univerjs/icons': + specifier: ^0.1.72 + version: 0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@univerjs/shared': specifier: workspace:* version: link:../../common/shared + '@univerjs/sheets-formula': + specifier: workspace:* + version: link:../../packages/sheets-formula + '@univerjs/ui': + specifier: workspace:* + version: link:../../packages/ui '@univerjs/uni-formula': specifier: workspace:* version: link:../uni-formula + clsx: + specifier: ^2.1.1 + version: 2.1.1 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typescript: + specifier: ^5.5.4 + version: 5.5.4 + vite: + specifier: ^5.3.5 + version: 5.3.5(@types/node@22.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@22.1.0)(happy-dom@14.12.3)(jsdom@24.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) + + packages-experimental/uni-sheets-ui: + dependencies: + '@univerjs/icons': + specifier: ^0.1.72 + version: 0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@univerjs/sheets-drawing-ui': + specifier: workspace:* + version: link:../../packages/sheets-drawing-ui + react: + specifier: ^16.9.0 || ^17.0.0 || ^18.0.0 + version: 18.3.1 + devDependencies: + '@univerjs/core': + specifier: workspace:* + version: link:../../packages/core + '@univerjs/design': + specifier: workspace:* + version: link:../../packages/design + '@univerjs/docs': + specifier: workspace:* + version: link:../../packages/docs + '@univerjs/docs-ui': + specifier: workspace:* + version: link:../../packages/docs-ui + '@univerjs/shared': + specifier: workspace:* + version: link:../../common/shared + '@univerjs/sheets': + specifier: workspace:* + version: link:../../packages/sheets + '@univerjs/sheets-ui': + specifier: workspace:* + version: link:../../packages/sheets-ui + '@univerjs/ui': + specifier: workspace:* + version: link:../../packages/ui + '@univerjs/uniui': + specifier: workspace:* + version: link:../uniui + clsx: + specifier: ^2.1.1 + version: 2.1.1 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typescript: + specifier: ^5.5.3 + version: 5.5.4 + vite: + specifier: ^5.3.4 + version: 5.3.5(@types/node@22.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) + vitest: + specifier: ^2.0.3 + version: 2.0.5(@types/node@22.1.0)(happy-dom@14.12.3)(jsdom@24.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) + + packages-experimental/uni-slides-ui: + dependencies: + '@univerjs/icons': + specifier: ^0.1.72 + version: 0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + devDependencies: + '@univerjs/core': + specifier: workspace:* + version: link:../../packages/core + '@univerjs/design': + specifier: workspace:* + version: link:../../packages/design + '@univerjs/engine-render': + specifier: workspace:* + version: link:../../packages/engine-render + '@univerjs/shared': + specifier: workspace:* + version: link:../../common/shared + '@univerjs/slides': + specifier: workspace:* + version: link:../../packages/slides + '@univerjs/slides-ui': + specifier: workspace:* + version: link:../../packages/slides-ui + '@univerjs/ui': + specifier: workspace:* + version: link:../../packages/ui + '@univerjs/uniui': + specifier: workspace:* + version: link:../uniui + clsx: + specifier: ^2.1.1 + version: 2.1.1 + less: + specifier: ^4.2.0 + version: 4.2.0 + react: + specifier: 18.3.1 + version: 18.3.1 + react-dom: + specifier: 18.3.1 + version: 18.3.1(react@18.3.1) rxjs: specifier: ^7.8.1 version: 7.8.1 @@ -461,9 +654,21 @@ importers: packages-experimental/uniui: dependencies: + '@floating-ui/react-dom': + specifier: ^2.1.1 + version: 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@univerjs/docs': + specifier: workspace:* + version: link:../../packages/docs '@univerjs/icons': specifier: ^0.1.72 version: 0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@univerjs/sheets': + specifier: workspace:* + version: link:../../packages/sheets + '@xyflow/react': + specifier: ^12.0.4 + version: 12.0.4(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: specifier: ^5.43.0 version: 5.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -480,12 +685,18 @@ importers: '@univerjs/design': specifier: workspace:* version: link:../../packages/design + '@univerjs/docs-ui': + specifier: workspace:* + version: link:../../packages/docs-ui '@univerjs/engine-render': specifier: workspace:* version: link:../../packages/engine-render '@univerjs/shared': specifier: workspace:* version: link:../../common/shared + '@univerjs/sheets-ui': + specifier: workspace:* + version: link:../../packages/sheets-ui '@univerjs/ui': specifier: workspace:* version: link:../../packages/ui @@ -767,10 +978,6 @@ importers: version: 2.0.5(@types/node@22.1.0)(happy-dom@14.12.3)(jsdom@24.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) packages/docs-drawing: - dependencies: - rxjs: - specifier: '>=7.0.0' - version: 7.8.1 devDependencies: '@univerjs/core': specifier: workspace:* @@ -1442,6 +1649,9 @@ importers: '@univerjs/protocol': specifier: 0.1.38 version: 0.1.38(@grpc/grpc-js@1.10.9)(rxjs@7.8.1) + '@univerjs/sheets': + specifier: workspace:* + version: 'link:' devDependencies: '@univerjs/core': specifier: workspace:* @@ -1487,8 +1697,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons-svg': - specifier: ^0.1.70 - version: 0.1.70 + specifier: ^0.1.72 + version: 0.1.72 '@univerjs/shared': specifier: workspace:* version: link:../../common/shared @@ -1560,8 +1770,8 @@ importers: specifier: workspace:* version: link:../engine-render '@univerjs/icons-svg': - specifier: ^0.1.70 - version: 0.1.70 + specifier: ^0.1.72 + version: 0.1.72 '@univerjs/shared': specifier: workspace:* version: link:../../common/shared @@ -2388,10 +2598,6 @@ importers: version: 2.0.5(@types/node@22.1.0)(happy-dom@14.12.3)(jsdom@24.1.0)(less@4.2.0)(sass@1.77.5)(terser@5.31.1) packages/slides: - dependencies: - '@univerjs/docs': - specifier: workspace:* - version: link:../docs devDependencies: '@univerjs/core': specifier: workspace:* @@ -2417,9 +2623,6 @@ importers: packages/slides-ui: dependencies: - '@univerjs/docs-ui': - specifier: workspace:* - version: link:../docs-ui '@univerjs/icons': specifier: ^0.1.72 version: 0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4134,6 +4337,12 @@ packages: '@floating-ui/dom@1.6.7': resolution: {integrity: sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==} + '@floating-ui/react-dom@2.1.1': + resolution: {integrity: sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@floating-ui/utils@0.2.4': resolution: {integrity: sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==} @@ -5107,6 +5316,24 @@ packages: '@types/cross-spawn@6.0.6': resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-selection@3.0.10': + resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==} + + '@types/d3-transition@3.0.8': + resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + '@types/doctrine@0.0.3': resolution: {integrity: sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==} @@ -5393,8 +5620,8 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@univerjs/icons-svg@0.1.70': - resolution: {integrity: sha512-cy/Op8BQ8C4+K1kZdeHx+yfCAZ2qqYT6cMPy6Gosz1s6uBwDPsQa3HzZreOQh48Ew80I8XvrSMg61wL4K+6Ckg==} + '@univerjs/icons-svg@0.1.72': + resolution: {integrity: sha512-/gdfACMJo6mXfY72SiLqvA+oxl4eK2pIw2JU+yX6srK4zg7X8j2sxAvfzy+FNdmPIDqjhlk+lzB+xdWaRWz71w==} '@univerjs/icons@0.1.72': resolution: {integrity: sha512-LfBM6v7YJhwGgEZtG7m9589Y4k6zmkshdm+166LTRbBlMBZ6Jl7hcmm9rQwT3jOt97rDma5xmnFbs+83hCD8Yw==} @@ -5563,6 +5790,15 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@xyflow/react@12.0.4': + resolution: {integrity: sha512-eeQzw1gIbLKOB55rp2+20uB1PASDUf1q6zy2VsgugnuPEcL/olVMX3WT42XxyG8m3rcbUiHlq2NSmPTFWEjiUQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@xyflow/system@0.0.37': + resolution: {integrity: sha512-hSIhezhxgftPUpC+xiQVIorcRILZUOWlLjpYPTyGWRu8s4RJvM4GqvrsFmD5OnMKXLgpU7/PqqUibDVO67oWQQ==} + '@yarnpkg/fslib@2.10.3': resolution: {integrity: sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==} engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} @@ -6036,6 +6272,9 @@ packages: class-validator@0.14.1: resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -6373,6 +6612,44 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + dargs@8.1.0: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} @@ -10496,6 +10773,11 @@ packages: url@0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} + use-sync-external-store@1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -10838,6 +11120,21 @@ packages: engines: {node: '>=8.0.0'} hasBin: true + zustand@4.5.4: + resolution: {integrity: sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + snapshots: '@adobe/css-tools@4.4.0': {} @@ -12277,6 +12574,12 @@ snapshots: '@floating-ui/core': 1.6.4 '@floating-ui/utils': 0.2.4 + '@floating-ui/react-dom@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.7 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@floating-ui/utils@0.2.4': {} '@grpc/grpc-js@1.10.9': @@ -13483,6 +13786,27 @@ snapshots: dependencies: '@types/node': 22.1.0 + '@types/d3-color@3.1.3': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.10 + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-selection@3.0.10': {} + + '@types/d3-transition@3.0.8': + dependencies: + '@types/d3-selection': 3.0.10 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.10 + '@types/doctrine@0.0.3': {} '@types/doctrine@0.0.9': {} @@ -13823,7 +14147,7 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@univerjs/icons-svg@0.1.70': {} + '@univerjs/icons-svg@0.1.72': {} '@univerjs/icons@0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -14095,6 +14419,27 @@ snapshots: '@xtuc/long@4.2.2': {} + '@xyflow/react@12.0.4(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@xyflow/system': 0.0.37 + classcat: 5.0.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.4(@types/react@18.3.3)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.37': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-selection': 3.0.10 + '@types/d3-transition': 3.0.8 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + '@yarnpkg/fslib@2.10.3': dependencies: '@yarnpkg/libzip': 2.3.0 @@ -14604,6 +14949,8 @@ snapshots: libphonenumber-js: 1.11.3 validator: 13.12.0 + classcat@5.0.5: {} + classnames@2.5.1: {} clean-css@5.3.3: @@ -14943,6 +15290,42 @@ snapshots: csstype@3.1.3: {} + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + dargs@8.1.0: {} data-uri-to-buffer@4.0.1: {} @@ -19606,6 +19989,10 @@ snapshots: punycode: 1.4.1 qs: 6.12.1 + use-sync-external-store@1.2.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} util@0.12.5: @@ -19999,3 +20386,10 @@ snapshots: validator: 13.12.0 optionalDependencies: commander: 9.5.0 + + zustand@4.5.4(@types/react@18.3.3)(react@18.3.1): + dependencies: + use-sync-external-store: 1.2.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + react: 18.3.1