/** * Copyright 2023-present DreamNum Co., Ltd. * * 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 { DocumentDataModel, IDisposable, IDocumentData, IExecutionOptions, ILanguagePack, IParagraphStyle, ITextDecoration, ITextStyle, LifecycleStages, LocaleType } from '@univerjs/core'; import type { Theme } from '@univerjs/themes'; import type { Subscription } from 'rxjs'; import type { ICommandEvent, IEventParamConfig } from './f-event'; import { CanceledError, Disposable, ICommandService, Inject, Injector, IUniverInstanceService, LifecycleService, LocaleService, ParagraphStyleBuilder, ParagraphStyleValue, RedoCommand, RegionService, RichTextBuilder, RichTextValue, TextDecorationBuilder, TextStyleBuilder, TextStyleValue, ThemeService, toDisposable, UndoCommand, Univer, UniverInstanceType, } from '@univerjs/core'; import { FBlob } from './f-blob'; import { FDoc } from './f-doc'; import { FEnum } from './f-enum'; import { FEventName } from './f-event'; import { FEventRegistry } from './f-event-registry'; import { FUserManager } from './f-usermanager'; import { FUtil } from './f-util'; /** * @ignore */ const InitializerSymbol = Symbol('initializers'); /** * @ignore */ type Initializers = Array<(injector: Injector) => void>; /** * The root Facade API object to interact with Univer. Please use `newAPI` static method * to create a new instance. * * @hideconstructor */ export class FUniver extends Disposable { /** * Creates a Facade API instance for an existing Univer instance or its injector. * @static * @param {Univer | Injector} wrapped - The Univer instance or injector instance. * @returns {FUniver} - The FUniver instance. * * @example * ```ts * const univerAPI = FUniver.newAPI(univer); * ``` */ static newAPI(wrapped: Univer | Injector): FUniver { const injector = wrapped instanceof Univer ? wrapped.__getInjector() : wrapped; return injector.createInstance(FUniver); } declare private [InitializerSymbol]: Initializers | undefined; /** * @ignore */ _initialize(_injector: Injector): void { } /** * @ignore */ static extend(source: { prototype: object }): void { Object.getOwnPropertyNames(source.prototype).forEach((name) => { if (name === '_initialize') { let initializers = this.prototype[InitializerSymbol]; if (!initializers) { initializers = []; this.prototype[InitializerSymbol] = initializers; } initializers.push(Reflect.get(source.prototype, name)); } else if (name !== 'constructor') { Reflect.set(this.prototype, name, Reflect.get(source.prototype, name)); } }); Object.getOwnPropertyNames(source).forEach((name) => { if (name !== 'prototype' && name !== 'name' && name !== 'length') { Reflect.set(this, name, Reflect.get(source, name)); } }); } protected _eventRegistry = new FEventRegistry(); /** * Registers a factory for the subscription that produces an event. * The factory starts when listeners exist and its subscription is disposed when the last listener is removed. * @param event The event name. * @param handler Creates the underlying event subscription. * @returns A disposable that unregisters the factory and disposes its active subscription. */ registerEventHandler = (event: string, handler: () => IDisposable | Subscription): IDisposable => { return this._eventRegistry.registerEventHandler(event, handler); }; constructor( @Inject(Injector) protected readonly _injector: Injector, @ICommandService protected readonly _commandService: ICommandService, @IUniverInstanceService protected readonly _univerInstanceService: IUniverInstanceService, @Inject(LifecycleService) protected readonly _lifecycleService: LifecycleService ) { super(); this.disposeWithMe( this.registerEventHandler( this.Event.LifeCycleChanged, () => toDisposable( this._lifecycleService.lifecycle$.subscribe((stage) => { this.fireEvent(this.Event.LifeCycleChanged, { stage }); }) ) ) ); this._initUnitEvent(this._injector); this._initBeforeCommandEvent(this._injector); this._initCommandEvent(this._injector); this._injector.onDispose(() => { this.dispose(); }); const initializers = Object.getPrototypeOf(this)[InitializerSymbol]; if (initializers) { initializers.forEach((fn: (injector: Injector) => void) => { fn.call(this, this._injector); }); } } private _initCommandEvent(injector: Injector): void { const commandService = injector.get(ICommandService); this.disposeWithMe( this.registerEventHandler( this.Event.Redo, () => commandService.onCommandExecuted((commandInfo) => { const { id, type: propType, params } = commandInfo; if (commandInfo.id === RedoCommand.id) { const type = propType!; const eventParams: ICommandEvent = { id, type, params }; this.fireEvent(this.Event.Redo, eventParams); } }) ) ); this.disposeWithMe( this.registerEventHandler( this.Event.Undo, () => commandService.onCommandExecuted((commandInfo) => { const { id, type: propType, params } = commandInfo; if (commandInfo.id === UndoCommand.id) { const type = propType!; const eventParams: ICommandEvent = { id, type, params }; this.fireEvent(this.Event.Undo, eventParams); } }) ) ); this.disposeWithMe( this.registerEventHandler( this.Event.CommandExecuted, () => commandService.onCommandExecuted((commandInfo, options) => { const { id, type: propType, params } = commandInfo; if (commandInfo.id !== RedoCommand.id && commandInfo.id !== UndoCommand.id) { const type = propType!; const eventParams: ICommandEvent = { id, type, params, options }; this.fireEvent(this.Event.CommandExecuted, eventParams); } }) ) ); } private _initBeforeCommandEvent(injector: Injector): void { const commandService = injector.get(ICommandService); this.disposeWithMe( this.registerEventHandler( this.Event.BeforeRedo, () => commandService.beforeCommandExecuted((commandInfo) => { const { id, type: propType, params } = commandInfo; if (commandInfo.id === RedoCommand.id) { const type = propType!; const eventParams: ICommandEvent = { id, type, params }; this.fireEvent(this.Event.BeforeRedo, eventParams); if (eventParams.cancel) { throw new CanceledError(); } } }) ) ); this.disposeWithMe( this.registerEventHandler( this.Event.BeforeUndo, () => commandService.beforeCommandExecuted((commandInfo) => { const { id, type: propType, params } = commandInfo; if (commandInfo.id === UndoCommand.id) { const type = propType!; const eventParams: ICommandEvent = { id, type, params }; this.fireEvent(this.Event.BeforeUndo, eventParams); if (eventParams.cancel) { throw new CanceledError(); } } }) ) ); this.disposeWithMe( this.registerEventHandler( this.Event.BeforeCommandExecute, () => commandService.beforeCommandExecuted((commandInfo, options) => { const { id, type: propType, params } = commandInfo; if (commandInfo.id !== RedoCommand.id && commandInfo.id !== UndoCommand.id) { const type = propType!; const eventParams: ICommandEvent = { id, type, params, options }; this.fireEvent(this.Event.BeforeCommandExecute, eventParams); if (eventParams.cancel) { throw new CanceledError(); } } }) ) ); } private _initUnitEvent(injector: Injector): void { const univerInstanceService = injector.get(IUniverInstanceService); this.disposeWithMe( this.registerEventHandler( this.Event.DocDisposed, () => univerInstanceService.unitDisposed$.subscribe((unit) => { if (unit.type === UniverInstanceType.UNIVER_DOC) { this.fireEvent(this.Event.DocDisposed, { unitId: unit.getUnitId(), unitType: unit.type, snapshot: unit.getSnapshot() as IDocumentData, }); } }) ) ); this.disposeWithMe( this.registerEventHandler( this.Event.DocCreated, () => univerInstanceService.unitAdded$.subscribe((event) => { const { unit } = event; if (unit.type === UniverInstanceType.UNIVER_DOC) { const doc = unit as DocumentDataModel; const docUnit = injector.createInstance(FDoc, doc); this.fireEvent(this.Event.DocCreated, { unitId: unit.getUnitId(), type: unit.type, doc: docUnit, unit: docUnit, }); } }) ) ); } /** * Disposes the document, workbook, or other Univer unit identified by `unitId`, unloading it from the application. * @param unitId The ID of the unit to dispose. * @returns Whether the Univer instance is disposed successfully. * * @example * ```ts * const fWorkbook = univerAPI.getActiveWorkbook(); * const unitId = fWorkbook?.getId(); * * if (unitId) { * univerAPI.disposeUnit(unitId); * } * ``` */ disposeUnit(unitId: string): boolean { return this._univerInstanceService.disposeUnit(unitId); } /** * Get the current lifecycle stage. * @returns {LifecycleStages} - The current lifecycle stage. * * @example * ```ts * const stage = univerAPI.getCurrentLifecycleStage(); * console.log(stage); * ``` */ getCurrentLifecycleStage(): LifecycleStages { const lifecycleService = this._injector.get(LifecycleService); return lifecycleService.stage; } /** * Undo an editing on the currently focused document. * @returns {Promise} undo result * * @example * ```ts * await univerAPI.undo(); * ``` */ undo(): Promise { return this._commandService.executeCommand(UndoCommand.id); } /** * Redo an editing on the currently focused document. * @returns {Promise} redo result * * @example * ```ts * await univerAPI.redo(); * ``` */ redo(): Promise { return this._commandService.executeCommand(RedoCommand.id); } /** * Set the theme used by Univer. * @param {Theme} theme - The complete theme to use. * @example * ```ts * import { defaultTheme } from '@univerjs/themes'; * * univerAPI.setTheme({ * ...defaultTheme, * primary: { * ...defaultTheme.primary, * 600: '#274fee', * }, * }); * ``` */ setTheme(theme: Theme): void { const themeService = this._injector.get(ThemeService); themeService.setTheme(theme); } /** * Get the theme currently used by Univer. * @returns {Theme} The current theme. * @example * ```ts * const theme = univerAPI.getCurrentTheme(); * console.log(theme.primary[600]); * ``` */ getCurrentTheme(): Theme { const themeService = this._injector.get(ThemeService); return themeService.getCurrentTheme(); } /** * Whether Univer is currently using dark mode. * @returns {boolean} Whether dark mode is enabled. * @example * ```ts * const darkMode = univerAPI.isDarkMode(); * ``` */ isDarkMode(): boolean { const themeService = this._injector.get(ThemeService); return themeService.darkMode; } /** * Toggle dark mode on or off. * @param {boolean} isDarkMode - Whether the dark mode is enabled. * @example * ```ts * univerAPI.toggleDarkMode(true); * ``` */ toggleDarkMode(isDarkMode: boolean): void { const themeService = this._injector.get(ThemeService); themeService.setDarkMode(isDarkMode); } /** * Load locales for the given locale. * @description This method is utilized to load locales, which can be either built-in or custom-defined. * @param {string} locale - A unique locale identifier. * @param {ILanguagePack} locales - The locales object containing the translations. * @example * ```ts * univerAPI.loadLocales('esES', { * 'Hello World': 'Hola Mundo', * }); * ``` */ loadLocales(locale: string, locales: ILanguagePack): void { const localeService = this._injector.get(LocaleService); localeService.load({ [locale]: locales }); } /** * Set the current locale. * @param {string} locale - A unique locale identifier. * @example * ```ts * univerAPI.setLocale('esES'); * ``` */ setLocale(locale: string): void { const localeService = this._injector.get(LocaleService); localeService.setLocale(locale as LocaleType); } /** * Set the region used for locale-sensitive formatting. * @param {string} region - A unique region identifier. * @example * ```ts * univerAPI.setRegion('enUS'); * ``` */ setRegion(region: string): void { const regionService = this._injector.get(RegionService); regionService.setRegion(region as LocaleType); } /** * Get the region currently used for locale-sensitive formatting. * @returns {string} The current region identifier. * @example * ```ts * const currentRegion = univerAPI.getCurrentRegion(); * console.log(currentRegion); * ``` */ getCurrentRegion(): string { const regionService = this._injector.get(RegionService); return regionService.getCurrentRegion(); } /** * Set the current layout direction. * @param {('ltr' | 'rtl')} direction - The layout direction. * @example * ```ts * univerAPI.setDirection('rtl'); * ``` */ setDirection(direction: 'ltr' | 'rtl'): void { const localeService = this._injector.get(LocaleService); localeService.setDirection(direction); } /** * Get the current locale. * @returns {string} The current locale identifier. * @example * ```ts * const currentLocale = univerAPI.getCurrentLocale(); * console.log(currentLocale); * ``` */ getCurrentLocale(): string { const localeService = this._injector.get(LocaleService); return localeService.getCurrentLocale(); } /** * Get the locales for the current locale. * @returns {ILanguagePack | undefined} The locales object for the current locale, it returns undefined if the locales is not loaded. * @example * ```ts * const locales = univerAPI.getLocales(); * console.log(locales); * ``` */ getLocales(): ILanguagePack | undefined { const localeService = this._injector.get(LocaleService); return localeService.getLocales(); } /** * Execute a command with the given id and parameters. * @param id Identifier of the command. * @param [params] Parameters of this execution. * @param [options] Options of this execution. * @returns {Promise} The result of the execution. It is a boolean value by default which indicates the command is executed. * * @example * ```ts * univerAPI.executeCommand('sheet.command.set-range-values', { * value: { v: "Hello, Univer!" }, * range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 } * }); * ``` */ executeCommand

( id: string, params?: P, options?: IExecutionOptions ): Promise { return this._commandService.executeCommand(id, params, options); } /** * Execute a command with the given id and parameters synchronously. * @param id Identifier of the command. * @param [params] Parameters of this execution. * @param [options] Options of this execution. * @returns The result of the execution. It is a boolean value by default which indicates the command is executed. * * @example * ```ts * univerAPI.syncExecuteCommand('sheet.command.set-range-values', { * value: { v: "Hello, Univer!" }, * range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 } * }); * ``` */ syncExecuteCommand

( id: string, params?: P, options?: IExecutionOptions ): R { return this._commandService.syncExecuteCommand(id, params, options); } /** * Enums exposed by the registered Facade extensions. */ get Enum(): FEnum { return FEnum.get(); } /** * Event names to use with `addEvent`. */ get Event(): FEventName { return FEventName.get(); } /** * Utility functions exposed by the registered Facade extensions. */ get Util(): FUtil { return FUtil.get(); } /** * Add an event listener * @param {T} event key of event * @param {(params: IEventParamConfig[T]) => void} callback callback when event triggered * @returns {IDisposable} A disposable that removes the event listener. * @example * ```ts * // Add life cycle changed event listener * const disposable = univerAPI.addEvent(univerAPI.Event.LifeCycleChanged, (params) => { * const { stage } = params; * console.log('life cycle changed', params); * }); * * // Remove the event listener, use `disposable.dispose()` * ``` */ addEvent(event: T, callback: (params: IEventParamConfig[T]) => void): IDisposable { if (!event || !callback) throw new Error('Cannot add empty event'); return this._eventRegistry.addEvent(event, callback); } /** * Fire an event, used in internal only. * @param {T} event key of event * @param {IEventParamConfig[T]} params params of event * @returns {boolean | undefined} The event's `cancel` value after listeners run; `undefined` if it was not set. * @example * ```ts * this.fireEvent(univerAPI.Event.LifeCycleChanged, params); * ``` */ fireEvent(event: T, params: IEventParamConfig[T]): boolean | undefined { return this._eventRegistry.fireEvent(event, params); } /** * Gets the facade for reading the current user. * @returns {FUserManager} The user manager facade. * @example * ```ts * const user = univerAPI.getUserManager().getCurrentUser(); * ``` */ getUserManager(): FUserManager { return this._injector.createInstance(FUserManager); } /** * Create a new blob. * @returns {FBlob} The new blob instance * @example * ```ts * const blob = univerAPI.newBlob(); * ``` */ newBlob(): FBlob { return this._injector.createInstance(FBlob, null); } /** * Create a new rich text. * @returns {RichTextBuilder} The new rich text instance * @example * ```ts * const richText = univerAPI.newRichText() * .text('Read ') * .link('Univer documentation', 'https://docs.univer.ai') * .text(' for details.'); * ``` */ newRichText(): RichTextBuilder { return RichTextBuilder.create(); } /** * Create a rich-text builder from raw Univer document data. * * This is an advanced integration escape hatch for importers and adapters. Application and agent code should use * the fluent builder returned by `newRichText()`. * * @param data Raw Univer document data. * @returns A rich-text builder initialized with the supplied document data. * @advanced */ newRichTextFromDocumentData(data: IDocumentData): RichTextBuilder { return RichTextBuilder.create(data); } /** * Create a new rich text value. * * This is an advanced integration escape hatch for callers that already own Univer document data. Application and * agent code should prefer the fluent value returned by `newRichText()`. * * @param {IDocumentData} data The raw Univer document data. * @returns {RichTextValue} The new rich text value instance * @advanced */ newRichTextValue(data: IDocumentData): RichTextValue { return RichTextValue.create(data); } /** * Create a new paragraph style. * * This is an advanced document-model API. Application and agent code should normally use * `newRichText().paragraph({ ... })`. * * @param {IParagraphStyle} [style] The paragraph style * @returns {ParagraphStyleBuilder} The new paragraph style instance * @advanced */ newParagraphStyle(style?: IParagraphStyle): ParagraphStyleBuilder { return ParagraphStyleBuilder.create(style); } /** * Create a new paragraph style value. * @param {IParagraphStyle} [style] - The paragraph style * @returns {ParagraphStyleValue} The new paragraph style value instance * @example * ```ts * const paragraphStyleValue = univerAPI.newParagraphStyleValue(); * ``` */ newParagraphStyleValue(style?: IParagraphStyle): ParagraphStyleValue { return ParagraphStyleValue.create(style); } /** * Create a new text style. * @param {ITextStyle} [style] - The text style * @returns {TextStyleBuilder} The new text style instance * @example * ```ts * const textStyle = univerAPI.newTextStyle(); * ``` */ newTextStyle(style?: ITextStyle): TextStyleBuilder { return TextStyleBuilder.create(style); } /** * Create a new text style value. * @param {ITextStyle} [style] - The text style * @returns {TextStyleValue} The new text style value instance * @example * ```ts * const textStyleValue = univerAPI.newTextStyleValue(); * ``` */ newTextStyleValue(style?: ITextStyle): TextStyleValue { return TextStyleValue.create(style); } /** * Create a new text decoration. * @param {ITextDecoration} [decoration] - The text decoration * @returns {TextDecorationBuilder} The new text decoration instance * @example * ```ts * const decoration = univerAPI.newTextDecoration(); * ``` */ newTextDecoration(decoration?: ITextDecoration): TextDecorationBuilder { return new TextDecorationBuilder(decoration); } }