snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 05:09:45 +05:00
commit bcf6ef63ad
7118 changed files with 1236854 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
/**
* 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 { IImageWatermarkConfig, ITextWatermarkConfig, IUserInfoWatermarkConfig } from '@univerjs/engine-render';
export const UNIVER_WATERMARK_MENU = 'UNIVER_WATERMARK_MENU';
export const WATERMARK_PANEL = 'WATERMARK_PANEL';
export const WATERMARK_PANEL_FOOTER = 'WATERMARK_PANEL_FOOTER';
export const WATERMARK_IMAGE_ALLOW_IMAGE_LIST = ['image/png', 'image/jpeg', 'image/jpg', 'image/bmp'];
export const WatermarkTextBaseConfig: ITextWatermarkConfig = {
content: '',
fontSize: 16,
color: 'rgb(0,0,0)',
bold: false,
italic: false,
direction: 'ltr',
x: 60,
y: 36,
repeat: true,
spacingX: 200,
spacingY: 100,
rotate: 0,
opacity: 0.15,
};
export const WatermarkImageBaseConfig: IImageWatermarkConfig = {
url: '',
width: 100,
height: 100,
maintainAspectRatio: true,
originRatio: 1,
x: 60,
y: 36,
repeat: true,
spacingX: 200,
spacingY: 100,
rotate: 0,
opacity: 0.15,
};
export const WatermarkUserInfoBaseConfig: IUserInfoWatermarkConfig = {
name: true,
email: false,
phone: false,
uid: false,
fontSize: 16,
color: 'rgb(0,0,0)',
bold: false,
italic: false,
direction: 'ltr',
x: 60,
y: 60,
repeat: true,
spacingX: 200,
spacingY: 100,
rotate: -30,
opacity: 0.15,
};

View File

@@ -0,0 +1,29 @@
/**
* 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 { IImageWatermarkConfig, ITextWatermarkConfig, IUserInfoWatermarkConfig } from '@univerjs/engine-render';
export const WATERMARK_PLUGIN_CONFIG_KEY = 'watermark.config';
export const configSymbol = Symbol(WATERMARK_PLUGIN_CONFIG_KEY);
export interface IUniverWatermarkConfig {
userWatermarkSettings?: Partial<IUserInfoWatermarkConfig>;
textWatermarkSettings?: Partial<ITextWatermarkConfig>;
imageWatermarkSettings?: Partial<IImageWatermarkConfig>;
}
export const defaultPluginConfig: IUniverWatermarkConfig = {};

View File

@@ -0,0 +1,143 @@
/**
* 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 { awaitTime } from '@univerjs/core';
import { IWatermarkTypeEnum, UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render';
import { Subject } from 'rxjs';
import { describe, expect, it, vi } from 'vitest';
import { WatermarkRenderController } from '../watermark.render.controller';
const layerInstances: Array<{ updateConfig: ReturnType<typeof vi.fn> }> = [];
vi.mock('@univerjs/engine-render', async () => {
const actual = await vi.importActual<typeof import('@univerjs/engine-render')>('@univerjs/engine-render');
class WatermarkLayer {
readonly updateConfig = vi.fn();
constructor() {
layerInstances.push(this);
}
}
return {
...actual,
IWatermarkTypeEnum: {
UserInfo: 'userInfo',
Text: 'text',
Image: 'image',
},
UNIVER_WATERMARK_LAYER_INDEX: 1000,
UNIVER_WATERMARK_STORAGE_KEY: 'UNIVER_WATERMARK_STORAGE_KEY',
WatermarkLayer,
};
});
describe('WatermarkRenderController', () => {
it('should init layer, load config and react to updates', async () => {
layerInstances.length = 0;
const updateConfig$ = new Subject<unknown>();
const updateWatermarkConfig = vi.fn();
const scene = {
addLayer: vi.fn(),
};
const makeDirty = vi.fn();
const localStorageService = {
getItem: vi.fn(async () => ({
type: IWatermarkTypeEnum.Text,
config: { text: { content: 'init' } },
})),
};
const userManagerService = {
getCurrentUser: vi.fn(() => ({ id: 'u1' })),
};
const controller = new WatermarkRenderController(
{
scene,
mainComponent: { makeDirty },
} as never,
{
updateConfig$: updateConfig$.asObservable(),
updateWatermarkConfig,
} as never,
localStorageService as never,
userManagerService as never
);
expect(scene.addLayer).toHaveBeenCalledTimes(1);
expect(localStorageService.getItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY);
await awaitTime(0);
expect(updateWatermarkConfig).toHaveBeenCalledWith({
type: IWatermarkTypeEnum.Text,
config: { text: { content: 'init' } },
});
expect(makeDirty).toHaveBeenCalled();
const layer = layerInstances[0];
expect(layer).toBeDefined();
updateConfig$.next(null);
expect(layer.updateConfig).toHaveBeenCalledWith();
updateConfig$.next({
type: IWatermarkTypeEnum.UserInfo,
config: { userInfo: { name: true } },
});
expect(layer.updateConfig).toHaveBeenCalledWith(
{ type: IWatermarkTypeEnum.UserInfo, config: { userInfo: { name: true } } },
{ id: 'u1' }
);
updateConfig$.next({
type: IWatermarkTypeEnum.Image,
config: { image: { url: 'img' } },
});
expect(layer.updateConfig).toHaveBeenCalledWith({
type: IWatermarkTypeEnum.Image,
config: { image: { url: 'img' } },
});
await controller.dispose();
});
it('should skip init config update when storage is empty', async () => {
layerInstances.length = 0;
const updateConfig$ = new Subject<unknown>();
const updateWatermarkConfig = vi.fn();
const makeDirty = vi.fn();
const _controller = new WatermarkRenderController(
{
scene: { addLayer: vi.fn() },
mainComponent: { makeDirty },
} as never,
{
updateConfig$: updateConfig$.asObservable(),
updateWatermarkConfig,
} as never,
{ getItem: vi.fn(async () => undefined) } as never,
{ getCurrentUser: vi.fn(() => ({ id: 'u1' })) } as never
);
expect(_controller).toBeDefined();
await awaitTime(0);
expect(updateWatermarkConfig).not.toHaveBeenCalled();
expect(makeDirty).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,74 @@
/**
* 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 { UnitModel } from '@univerjs/core';
import type { IRenderContext, IRenderModule, IWatermarkConfigWithType } from '@univerjs/engine-render';
import { ILocalStorageService, Inject, RxDisposable, UserManagerService } from '@univerjs/core';
import {
IWatermarkTypeEnum,
UNIVER_WATERMARK_LAYER_INDEX,
UNIVER_WATERMARK_STORAGE_KEY,
WatermarkLayer,
} from '@univerjs/engine-render';
import { WatermarkService } from '../services/watermark.service';
export class WatermarkRenderController extends RxDisposable implements IRenderModule {
private readonly _watermarkLayer: WatermarkLayer;
constructor(
private readonly _context: IRenderContext<UnitModel>,
@Inject(WatermarkService) private _watermarkService: WatermarkService,
@Inject(ILocalStorageService) private _localStorageService: ILocalStorageService,
@Inject(UserManagerService) private _userManagerService: UserManagerService
) {
super();
this._watermarkLayer = new WatermarkLayer(_context.scene, [], UNIVER_WATERMARK_LAYER_INDEX);
this._initAddRender();
this._initWatermarkUpdate();
this._initWatermarkConfig();
}
private _initAddRender() {
const { scene } = this._context;
scene.addLayer(this._watermarkLayer);
}
private async _initWatermarkConfig() {
const config = await this._localStorageService.getItem<IWatermarkConfigWithType>(UNIVER_WATERMARK_STORAGE_KEY);
if (config) {
this._watermarkService.updateWatermarkConfig(config);
this._context.mainComponent?.makeDirty();
}
}
private _initWatermarkUpdate() {
this.disposeWithMe(
this._watermarkService.updateConfig$.subscribe((_config) => {
if (!_config) {
this._watermarkLayer.updateConfig();
this._context.mainComponent?.makeDirty();
return;
}
if (_config.type === IWatermarkTypeEnum.UserInfo) {
this._watermarkLayer.updateConfig(_config, this._userManagerService.getCurrentUser());
} else {
this._watermarkLayer.updateConfig(_config);
}
this._context.mainComponent?.makeDirty();
})
);
}
}

View File

@@ -0,0 +1,104 @@
/**
* 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 { Injector } from '@univerjs/core';
import { FEnum, FUniver } from '@univerjs/core/facade';
import { IWatermarkTypeEnum } from '@univerjs/engine-render';
import { WatermarkService, WatermarkTextBaseConfig } from '@univerjs/watermark';
import { describe, expect, it } from 'vitest';
import { FUniverWatermarkMixin, FWatermarkEnumMixin } from '../f-univer';
class TestWatermarkService {
readonly updatedConfigs: unknown[] = [];
deleteCount = 0;
updateWatermarkConfig(config: unknown): void {
this.updatedConfigs.push(config);
}
deleteWatermarkConfig(): void {
this.deleteCount += 1;
}
}
describe('watermark facade', () => {
it('adds text and image watermarks through the injected watermark service', () => {
const injector = new Injector();
injector.add([WatermarkService, { useClass: TestWatermarkService as never }]);
const service = injector.get(WatermarkService) as unknown as TestWatermarkService;
const thisArg = {
_injector: injector,
};
const addWatermark = FUniverWatermarkMixin.prototype.addWatermark as (
this: typeof thisArg,
type: IWatermarkTypeEnum,
config: unknown
) => FUniver;
expect(addWatermark.call(
thisArg,
IWatermarkTypeEnum.Text,
{ content: 'Confidential' } as never
)).toBe(thisArg);
expect(service.updatedConfigs.at(-1)).toEqual({
type: IWatermarkTypeEnum.Text,
config: {
text: {
...WatermarkTextBaseConfig,
content: 'Confidential',
},
},
});
addWatermark.call(
thisArg,
IWatermarkTypeEnum.Image,
{ url: 'https://example.com/watermark.png', width: 320 } as never
);
expect(service.updatedConfigs.at(-1)).toMatchObject({
type: IWatermarkTypeEnum.Image,
config: {
image: {
url: 'https://example.com/watermark.png',
width: 320,
},
},
});
});
it('deletes watermark config, rejects unknown types and exposes watermark enum on real facade classes', () => {
const injector = new Injector();
injector.add([WatermarkService, { useClass: TestWatermarkService as never }]);
const service = injector.get(WatermarkService) as unknown as TestWatermarkService;
const thisArg = {
_injector: injector,
};
const addWatermark = FUniverWatermarkMixin.prototype.addWatermark as (
this: typeof thisArg,
type: IWatermarkTypeEnum,
config: unknown
) => FUniver;
expect(() =>
addWatermark.call(thisArg, 'unknown' as never, {} as never)
).toThrow('Unknown watermark type');
expect(FUniverWatermarkMixin.prototype.deleteWatermark.call(thisArg as never)).toBe(thisArg);
expect(service.deleteCount).toBe(1);
expect(FEnum.get().IWatermarkTypeEnum).toBe(IWatermarkTypeEnum);
expect(Object.create(FWatermarkEnumMixin.prototype).IWatermarkTypeEnum).toBe(IWatermarkTypeEnum);
expect(typeof FUniver.prototype.addWatermark).toBe('function');
});
});

View File

@@ -0,0 +1,137 @@
/**
* 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 { IImageWatermarkConfig, ITextWatermarkConfig } from '@univerjs/engine-render';
import { FEnum, FUniver } from '@univerjs/core/facade';
import { IWatermarkTypeEnum } from '@univerjs/engine-render';
import { WatermarkImageBaseConfig, WatermarkService, WatermarkTextBaseConfig } from '@univerjs/watermark';
interface IFWatermarkEnumMixin {
/** Text and image watermark types accepted by `addWatermark`. */
IWatermarkTypeEnum: typeof IWatermarkTypeEnum;
}
export class FWatermarkEnumMixin extends FEnum implements IFWatermarkEnumMixin {
override get IWatermarkTypeEnum(): typeof IWatermarkTypeEnum {
return IWatermarkTypeEnum;
};
}
/**
* @ignore
*/
export interface IFUniverWatermarkMixin {
/**
* Adds a watermark to the unit. Supports both text and image watermarks based on the specified type.
* @param {IWatermarkTypeEnum.Text} type - The type of watermark to add is Text.
* @param {ITextWatermarkConfig} config - The configuration object for the text type watermark.
* @returns {FUniver} The {@link FUniver} instance for chaining.
* @throws {Error} Throws an error if the watermark type is unknown.
* @example
* ```ts
* univerAPI.addWatermark('text', {
* content: 'Univer',
* fontSize: 20,
* repeat: true,
* });
* ```
*/
addWatermark(type: IWatermarkTypeEnum.Text, config: ITextWatermarkConfig): FUniver;
/**
* Adds a watermark to the unit. Supports both text and image watermarks based on the specified type.
* @param {IWatermarkTypeEnum.Image} type - The type of watermark to add is Image.
* @param {IImageWatermarkConfig} config - The configuration object for the image type watermark.
* @returns {FUniver} The {@link FUniver} instance for chaining.
* @throws {Error} Throws an error if the watermark type is unknown.
* @example
* ```ts
* univerAPI.addWatermark('image', {
* url: 'https://avatars.githubusercontent.com/u/61444807?s=48&v=4',
* width: 100,
* height: 100,
* });
* ```
*/
addWatermark(type: IWatermarkTypeEnum.Image, config: IImageWatermarkConfig): FUniver;
addWatermark(
type: IWatermarkTypeEnum.Text | IWatermarkTypeEnum.Image,
config: ITextWatermarkConfig | IImageWatermarkConfig
): FUniver;
/**
* Deletes the currently applied watermark from the unit.
* This function retrieves the watermark service and invokes the method to remove any existing watermark configuration.
* @returns {FUniver} The {@link FUniver} instance for chaining.
* @example
* ```ts
* univerAPI.deleteWatermark();
* ```
*/
deleteWatermark(): FUniver;
}
export class FUniverWatermarkMixin extends FUniver {
// #region watermark
override addWatermark(type: IWatermarkTypeEnum.Text, config: ITextWatermarkConfig): FUniver;
override addWatermark(type: IWatermarkTypeEnum.Image, config: IImageWatermarkConfig): FUniver;
override addWatermark(
type: IWatermarkTypeEnum.Text | IWatermarkTypeEnum.Image,
config: ITextWatermarkConfig | IImageWatermarkConfig
): FUniver {
const watermarkService = this._injector.get(WatermarkService);
if (type === IWatermarkTypeEnum.Text) {
watermarkService.updateWatermarkConfig({
type: IWatermarkTypeEnum.Text,
config: {
text: {
...WatermarkTextBaseConfig,
...config,
},
},
});
} else if (type === IWatermarkTypeEnum.Image) {
watermarkService.updateWatermarkConfig({
type: IWatermarkTypeEnum.Image,
config: {
image: {
...WatermarkImageBaseConfig,
...config,
},
},
});
} else {
throw new Error('Unknown watermark type');
}
return this;
}
override deleteWatermark(): FUniver {
const watermarkService = this._injector.get(WatermarkService);
watermarkService.deleteWatermarkConfig();
return this;
}
}
FUniver.extend(FUniverWatermarkMixin);
FEnum.extend(FWatermarkEnumMixin);
declare module '@univerjs/core/facade' {
interface FUniver extends IFUniverWatermarkMixin { }
export interface FEnum extends IFWatermarkEnumMixin {
}
}

View File

@@ -0,0 +1,19 @@
/**
* 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 './f-univer';
export type * from './f-univer';

View File

@@ -0,0 +1,25 @@
/**
* 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.
*/
export {
WATERMARK_IMAGE_ALLOW_IMAGE_LIST,
WatermarkImageBaseConfig,
WatermarkTextBaseConfig,
WatermarkUserInfoBaseConfig,
} from './common/const';
export type { IUniverWatermarkConfig } from './config/config';
export { UniverWatermarkPlugin } from './plugin';
export { WatermarkService } from './services/watermark.service';

View File

@@ -0,0 +1,126 @@
/**
* 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 { Dependency } from '@univerjs/core';
import type { IWatermarkConfigWithType } from '@univerjs/engine-render';
import type { IUniverWatermarkConfig } from './config/config';
import {
DependentOn,
IConfigService,
ILocalStorageService,
Inject,
Injector,
merge,
Plugin,
UniverInstanceType,
} from '@univerjs/core';
import {
IRenderManagerService,
IWatermarkTypeEnum,
UNIVER_WATERMARK_STORAGE_KEY,
UniverRenderEnginePlugin,
} from '@univerjs/engine-render';
import pkg from '../package.json';
import { WatermarkImageBaseConfig, WatermarkTextBaseConfig, WatermarkUserInfoBaseConfig } from './common/const';
import { defaultPluginConfig, WATERMARK_PLUGIN_CONFIG_KEY } from './config/config';
import { WatermarkRenderController } from './controllers/watermark.render.controller';
import { WatermarkService } from './services/watermark.service';
@DependentOn(UniverRenderEnginePlugin)
export class UniverWatermarkPlugin extends Plugin {
static override pluginName = 'UNIVER_WATERMARK_PLUGIN';
static override packageName = pkg.name;
static override version = pkg.version;
constructor(
private readonly _config: Partial<IUniverWatermarkConfig> = defaultPluginConfig,
@Inject(Injector) protected override _injector: Injector,
@IConfigService private readonly _configService: IConfigService,
@IRenderManagerService private readonly _renderManagerSrv: IRenderManagerService,
@Inject(ILocalStorageService) private readonly _localStorageService: ILocalStorageService
) {
super();
// Manage the plugin configuration.
const { ...rest } = merge(
{},
defaultPluginConfig,
this._config
);
this._configService.setConfig(WATERMARK_PLUGIN_CONFIG_KEY, rest);
this._initWatermarkStorage();
this._initDependencies();
}
private async _initWatermarkStorage() {
const config = this._configService.getConfig<IUniverWatermarkConfig>(WATERMARK_PLUGIN_CONFIG_KEY);
if (!config) {
return;
}
const { userWatermarkSettings, textWatermarkSettings, imageWatermarkSettings } = config;
if (userWatermarkSettings) {
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, {
type: IWatermarkTypeEnum.UserInfo,
config: {
userInfo: merge({}, WatermarkUserInfoBaseConfig, userWatermarkSettings),
},
});
} else if (textWatermarkSettings) {
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, {
type: IWatermarkTypeEnum.Text,
config: {
text: merge({}, WatermarkTextBaseConfig, textWatermarkSettings),
},
});
} else if (imageWatermarkSettings) {
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, {
type: IWatermarkTypeEnum.Image,
config: {
image: merge({}, WatermarkImageBaseConfig, imageWatermarkSettings),
},
});
} else {
const config = await this._localStorageService.getItem<IWatermarkConfigWithType>(UNIVER_WATERMARK_STORAGE_KEY);
if (config?.type === IWatermarkTypeEnum.UserInfo) {
this._localStorageService.removeItem(UNIVER_WATERMARK_STORAGE_KEY);
}
}
}
private _initDependencies(): void {
([[WatermarkService]] as Dependency[]).forEach((d) => {
this._injector.add(d);
});
}
override onRendered(): void {
const injector = this._injector;
injector.get(WatermarkService);
this._initRenderDependencies();
}
private _initRenderDependencies(): void {
([
[WatermarkRenderController],
] as Dependency[]).forEach((d) => {
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_SHEET, d);
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_DOC, d);
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_SLIDE, d);
this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_BASE, d);
});
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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 { ILocalStorageService, Injector } from '@univerjs/core';
import { UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render';
import { describe, expect, it, vi } from 'vitest';
import { WatermarkService } from '../watermark.service';
const getItem = vi.fn(async () => ({ type: 'text' }));
const setItem = vi.fn();
const removeItem = vi.fn();
class TestLocalStorage {
getItem = getItem;
setItem = setItem;
removeItem = removeItem;
}
describe('WatermarkService', () => {
it('should get/update/delete/refresh and dispose', async () => {
getItem.mockClear();
setItem.mockClear();
removeItem.mockClear();
const injector = new Injector();
injector.add([ILocalStorageService, { useClass: TestLocalStorage as never }]);
injector.add([WatermarkService]);
const service = injector.get(WatermarkService);
await expect(service.getWatermarkConfig()).resolves.toEqual({ type: 'text' });
expect(getItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY);
const updateValues: unknown[] = [];
const refreshValues: number[] = [];
service.updateConfig$.subscribe((value) => updateValues.push(value));
service.refresh$.subscribe((value) => refreshValues.push(value));
service.updateWatermarkConfig({ type: 'image', config: { image: { url: 'x' } } } as never);
expect(setItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY, {
type: 'image',
config: { image: { url: 'x' } },
});
service.deleteWatermarkConfig();
expect(removeItem).toHaveBeenCalledWith(UNIVER_WATERMARK_STORAGE_KEY);
expect(updateValues).toEqual([
{ type: 'image', config: { image: { url: 'x' } } },
null,
]);
service.refresh();
expect(refreshValues.length).toBe(1);
expect(refreshValues[0]).toBeTypeOf('number');
service.dispose();
});
});

View File

@@ -0,0 +1,58 @@
/**
* 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 { Nullable } from '@univerjs/core';
import type { IWatermarkConfigWithType } from '@univerjs/engine-render';
import { Disposable, ILocalStorageService, Inject } from '@univerjs/core';
import { UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render';
import { Subject } from 'rxjs';
export class WatermarkService extends Disposable {
private readonly _updateConfig$ = new Subject<Nullable<IWatermarkConfigWithType>>();
public readonly updateConfig$ = this._updateConfig$.asObservable();
private readonly _refresh$ = new Subject<number>();
public readonly refresh$ = this._refresh$.asObservable();
constructor(
@Inject(ILocalStorageService) private _localStorageService: ILocalStorageService
) {
super();
}
public async getWatermarkConfig() {
const res = await this._localStorageService.getItem<IWatermarkConfigWithType>(UNIVER_WATERMARK_STORAGE_KEY);
return res;
}
public updateWatermarkConfig(config: IWatermarkConfigWithType) {
this._localStorageService.setItem(UNIVER_WATERMARK_STORAGE_KEY, config);
this._updateConfig$.next(config);
}
public deleteWatermarkConfig() {
this._localStorageService.removeItem(UNIVER_WATERMARK_STORAGE_KEY);
this._updateConfig$.next(null);
}
public refresh() {
this._refresh$.next(Math.random());
}
override dispose(): void {
this._refresh$.complete();
this._updateConfig$.complete();
}
}