mirror of
https://github.com/dream-num/univer.git
synced 2026-08-28 23:01:30 +08:00
fix(sheets): stabilize embedded clipboard and autofill (#7307)
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ICellData, Injector, IStyleData, Nullable, Univer, Workbook } from '@univerjs/core';
|
||||
import type { ICellData, Injector, IRange, IStyleData, Nullable, Workbook } from '@univerjs/core';
|
||||
import {
|
||||
CellValueType,
|
||||
ICommandService,
|
||||
@@ -45,8 +45,10 @@ import {
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import { IPlatformService, IShortcutService, PlatformService, ShortcutService } from '@univerjs/ui';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { AutoFillUIController } from '../../../controllers/auto-fill-ui.controller';
|
||||
import { Subject } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { AutoFillRenderController, AutoFillUIController, detectAutoFillRange } from '../../../controllers/auto-fill-ui.controller';
|
||||
import { createRenderTestBed } from '../../../controllers/render-controllers/__tests__/render-test-bed';
|
||||
import { EditorBridgeService, IEditorBridgeService } from '../../../services/editor-bridge.service';
|
||||
import { ISheetSelectionRenderService } from '../../../services/selection/base-selection-render.service';
|
||||
import { SheetSelectionRenderService } from '../../../services/selection/selection-render.service';
|
||||
@@ -55,10 +57,72 @@ import { SheetsRenderService } from '../../../services/sheets-render.service';
|
||||
import { createCommandTestBed } from './create-command-test-bed';
|
||||
|
||||
class mockSheetsRenderService {
|
||||
registerSkeletonChangingMutations(id: string) {
|
||||
registerSkeletonChangingMutations(_id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
describe('AutoFillRenderController', () => {
|
||||
it('routes fill events to the workbook owned by the render context', () => {
|
||||
const selectionFilled$ = new Subject<IRange | null>();
|
||||
const getSelectionControls = vi.fn(() => [{
|
||||
model: {
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
startRow: 2,
|
||||
endRow: 2,
|
||||
},
|
||||
selectionFilled$,
|
||||
fillControl: {
|
||||
onDblclick$: {
|
||||
subscribeEvent: vi.fn(() => ({ dispose: vi.fn() })),
|
||||
},
|
||||
onPointerDown$: {
|
||||
subscribeEvent: vi.fn(() => ({ dispose: vi.fn() })),
|
||||
},
|
||||
},
|
||||
}]);
|
||||
const testBed = createRenderTestBed({
|
||||
dependencies: [
|
||||
[ISheetSelectionRenderService, { useValue: { getSelectionControls } }],
|
||||
[IEditorBridgeService, {
|
||||
useValue: {
|
||||
isVisible: () => ({ visible: false }),
|
||||
},
|
||||
}],
|
||||
],
|
||||
});
|
||||
const executeCommand = vi.spyOn(testBed.commandService, 'executeCommand').mockResolvedValue(true);
|
||||
const controller = testBed.injector.createInstance(AutoFillRenderController, testBed.context);
|
||||
|
||||
selectionFilled$.next({
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
startRow: 2,
|
||||
endRow: 5,
|
||||
});
|
||||
|
||||
expect(executeCommand).toHaveBeenCalledWith(AutoFillCommand.id, {
|
||||
sourceRange: {
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
startRow: 2,
|
||||
endRow: 2,
|
||||
},
|
||||
targetRange: {
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
startRow: 2,
|
||||
endRow: 5,
|
||||
},
|
||||
unitId: testBed.context.unitId,
|
||||
subUnitId: testBed.sheet.getActiveSheet().getSheetId(),
|
||||
});
|
||||
|
||||
controller.dispose();
|
||||
testBed.univer.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
const TEST_WORKBOOK_DATA = {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
@@ -263,10 +327,8 @@ const TEST_WORKBOOK_DATA = {
|
||||
};
|
||||
|
||||
describe('Test auto fill rules in controller', () => {
|
||||
let univer: Univer;
|
||||
let get: Injector['get'];
|
||||
let commandService: ICommandService;
|
||||
let autoFillController: AutoFillUIController;
|
||||
let themeService: ThemeService;
|
||||
|
||||
let getValues: (
|
||||
@@ -282,8 +344,6 @@ describe('Test auto fill rules in controller', () => {
|
||||
endRow: number,
|
||||
endColumn: number
|
||||
) => Array<Array<Nullable<IStyleData>>> | undefined;
|
||||
let selectionManagerService: SheetsSelectionsService;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createCommandTestBed(TEST_WORKBOOK_DATA, [
|
||||
[DocSelectionManagerService],
|
||||
@@ -300,7 +360,6 @@ describe('Test auto fill rules in controller', () => {
|
||||
[AutoFillController],
|
||||
[AutoFillUIController],
|
||||
]);
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
|
||||
commandService = get(ICommandService);
|
||||
@@ -309,8 +368,7 @@ describe('Test auto fill rules in controller', () => {
|
||||
const newTheme = set(theme, 'black', '#35322b');
|
||||
themeService.setTheme(newTheme);
|
||||
|
||||
autoFillController = get(AutoFillUIController);
|
||||
selectionManagerService = get(SheetsSelectionsService);
|
||||
get(AutoFillUIController);
|
||||
commandService.registerCommand(SetRangeValuesMutation);
|
||||
commandService.registerCommand(SetSelectionsOperation);
|
||||
commandService.registerCommand(RemoveWorksheetMergeMutation);
|
||||
@@ -655,11 +713,19 @@ describe('Test auto fill rules in controller', () => {
|
||||
const workbook = get(IUniverInstanceService).getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
if (!workbook) throw new Error('This is an error');
|
||||
// test other string
|
||||
(autoFillController as any)._handleDbClickFill({
|
||||
const sourceRange: IRange = {
|
||||
startRow: 10,
|
||||
startColumn: 1,
|
||||
endRow: 10,
|
||||
endColumn: 1,
|
||||
};
|
||||
const worksheet = workbook.getSheetBySheetId('sheet1');
|
||||
if (!worksheet) throw new Error('Worksheet sheet1 does not exist');
|
||||
await commandService.executeCommand(AutoFillCommand.id, {
|
||||
sourceRange,
|
||||
targetRange: detectAutoFillRange(sourceRange, worksheet),
|
||||
unitId: workbook.getUnitId(),
|
||||
subUnitId: worksheet.getSheetId(),
|
||||
});
|
||||
expect(workbook.getSheetBySheetId('sheet1')?.getCell(11, 1)?.v).toBe(2);
|
||||
expect(workbook.getSheetBySheetId('sheet1')?.getCell(12, 1)?.v).toBe(2);
|
||||
|
||||
+57
@@ -18,6 +18,7 @@ import type { IRange } from '@univerjs/core';
|
||||
import {
|
||||
ICommandService,
|
||||
IConfirmService,
|
||||
IPermissionService,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
} from '@univerjs/core';
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
DeleteRangeMoveUpCommand,
|
||||
InsertRangeMoveDownCommand,
|
||||
InsertRangeMoveRightCommand,
|
||||
SheetPermissionCheckController,
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import * as sheets from '@univerjs/sheets';
|
||||
@@ -257,6 +259,13 @@ describe('clipboard command branches', () => {
|
||||
const accessor = createAccessor([
|
||||
[ISheetClipboardService, clipboardService],
|
||||
[IClipboardInterfaceService, { supportClipboard: true, read }],
|
||||
[IUniverInstanceService, { getCurrentUnitOfType: vi.fn(() => null) }],
|
||||
[IPermissionService, { getPermissionPoint: vi.fn(() => undefined) }],
|
||||
[LocaleService, { t: vi.fn((key: string) => key) }],
|
||||
[SheetPermissionCheckController, {
|
||||
permissionCheckWithRanges: vi.fn(() => true),
|
||||
blockExecuteWithoutPermission: vi.fn(),
|
||||
}],
|
||||
]);
|
||||
|
||||
expect(await SheetCopyCommand.handler(accessor)).toBe(true);
|
||||
@@ -267,6 +276,13 @@ describe('clipboard command branches', () => {
|
||||
const accessorWithoutClipboardAPI = createAccessor([
|
||||
[ISheetClipboardService, clipboardService],
|
||||
[IClipboardInterfaceService, { supportClipboard: false, read: vi.fn(async () => []) }],
|
||||
[IUniverInstanceService, { getCurrentUnitOfType: vi.fn(() => null) }],
|
||||
[IPermissionService, { getPermissionPoint: vi.fn(() => undefined) }],
|
||||
[LocaleService, { t: vi.fn((key: string) => key) }],
|
||||
[SheetPermissionCheckController, {
|
||||
permissionCheckWithRanges: vi.fn(() => true),
|
||||
blockExecuteWithoutPermission: vi.fn(),
|
||||
}],
|
||||
]);
|
||||
expect(await SheetPasteCommand.handler(accessorWithoutClipboardAPI, { value: 'value-only' })).toBe(true);
|
||||
expect(pasteByCopyId).toHaveBeenCalledWith('copy-1', 'value-only');
|
||||
@@ -277,6 +293,13 @@ describe('clipboard command branches', () => {
|
||||
copyContentCache: () => ({ getLastCopyId: () => '' }),
|
||||
}],
|
||||
[IClipboardInterfaceService, { supportClipboard: true, read: vi.fn(async () => []) }],
|
||||
[IUniverInstanceService, { getCurrentUnitOfType: vi.fn(() => null) }],
|
||||
[IPermissionService, { getPermissionPoint: vi.fn(() => undefined) }],
|
||||
[LocaleService, { t: vi.fn((key: string) => key) }],
|
||||
[SheetPermissionCheckController, {
|
||||
permissionCheckWithRanges: vi.fn(() => true),
|
||||
blockExecuteWithoutPermission: vi.fn(),
|
||||
}],
|
||||
]);
|
||||
expect(await SheetPasteCommand.handler(accessorNoData, { value: 'none' })).toBe(false);
|
||||
|
||||
@@ -292,6 +315,40 @@ describe('clipboard command branches', () => {
|
||||
expect(rePasteWithPasteType).toHaveBeenCalledWith(PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE);
|
||||
});
|
||||
|
||||
it('checks clipboard permissions only after the sheet implementation is selected', async () => {
|
||||
const copy = vi.fn(async () => true);
|
||||
const cut = vi.fn(async () => true);
|
||||
const legacyPaste = vi.fn();
|
||||
const permissionCheckWithRanges = vi.fn(() => false);
|
||||
const blockExecuteWithoutPermission = vi.fn((message: string) => {
|
||||
throw new Error(message);
|
||||
});
|
||||
const accessor = createAccessor([
|
||||
[ISheetClipboardService, {
|
||||
copy,
|
||||
cut,
|
||||
legacyPaste,
|
||||
}],
|
||||
[IUniverInstanceService, { getCurrentUnitOfType: vi.fn(() => null) }],
|
||||
[IPermissionService, { getPermissionPoint: vi.fn(() => undefined) }],
|
||||
[LocaleService, { t: vi.fn((key: string) => `translated:${key}`) }],
|
||||
[SheetPermissionCheckController, {
|
||||
permissionCheckWithRanges,
|
||||
blockExecuteWithoutPermission,
|
||||
}],
|
||||
]);
|
||||
|
||||
await expect(SheetCopyCommand.handler(accessor)).rejects.toThrow('translated:sheets-ui.permission.dialog.copyErr');
|
||||
await expect(SheetCutCommand.handler(accessor)).rejects.toThrow('translated:sheets-ui.permission.dialog.copyErr');
|
||||
await expect(SheetPasteCommand.handler(accessor, { value: PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE })).rejects.toThrow('translated:sheets-ui.permission.dialog.pasteErr');
|
||||
await expect(SheetPasteShortKeyCommand.handler(accessor, {})).rejects.toThrow('translated:sheets-ui.permission.dialog.pasteErr');
|
||||
|
||||
expect(permissionCheckWithRanges).toHaveBeenCalledTimes(4);
|
||||
expect(copy).not.toHaveBeenCalled();
|
||||
expect(cut).not.toHaveBeenCalled();
|
||||
expect(legacyPaste).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('routes special paste commands to SheetPasteCommand', async () => {
|
||||
const executeCommand = vi.fn(async () => true);
|
||||
const accessor = createAccessor([
|
||||
|
||||
@@ -15,8 +15,22 @@
|
||||
*/
|
||||
|
||||
import type { IAccessor, ICommand, IMultiCommand } from '@univerjs/core';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { IPasteHookKeyType } from '../../services/clipboard/type';
|
||||
import { CommandType, ICommandService } from '@univerjs/core';
|
||||
import { CommandType, ICommandService, IPermissionService, IUniverInstanceService, LocaleService } from '@univerjs/core';
|
||||
import {
|
||||
getSheetCommandTarget,
|
||||
RangeProtectionPermissionEditPoint,
|
||||
RangeProtectionPermissionViewPoint,
|
||||
SheetPermissionCheckController,
|
||||
WorkbookCopyPermission,
|
||||
WorkbookEditablePermission,
|
||||
WorksheetCopyPermission,
|
||||
WorksheetEditPermission,
|
||||
WorksheetSetCellStylePermission,
|
||||
WorksheetSetCellValuePermission,
|
||||
WorksheetSetColumnStylePermission,
|
||||
} from '@univerjs/sheets';
|
||||
import { CopyCommand, CutCommand, IClipboardInterfaceService, PasteCommand, SheetPasteShortKeyCommandName } from '@univerjs/ui';
|
||||
import { whenSheetFocused } from '../../controllers/shortcuts/utils';
|
||||
import { ISheetClipboardService, PREDEFINED_HOOK_NAME_PASTE } from '../../services/clipboard/clipboard.service';
|
||||
@@ -31,6 +45,7 @@ export const SheetCopyCommand: IMultiCommand = {
|
||||
priority: SHEET_CLIPBOARD_PRIORITY,
|
||||
preconditions: whenSheetFocused,
|
||||
handler: async (accessor) => {
|
||||
checkSheetClipboardPermission(accessor, CopyCommand.id);
|
||||
const sheetClipboardService = accessor.get(ISheetClipboardService);
|
||||
return sheetClipboardService.copy();
|
||||
},
|
||||
@@ -44,6 +59,7 @@ export const SheetCutCommand: IMultiCommand = {
|
||||
priority: SHEET_CLIPBOARD_PRIORITY,
|
||||
preconditions: whenSheetFocused,
|
||||
handler: async (accessor) => {
|
||||
checkSheetClipboardPermission(accessor, CutCommand.id);
|
||||
const sheetClipboardService = accessor.get(ISheetClipboardService);
|
||||
return sheetClipboardService.cut();
|
||||
},
|
||||
@@ -68,6 +84,7 @@ export const SheetPasteCommand: IMultiCommand = {
|
||||
priority: SHEET_CLIPBOARD_PRIORITY,
|
||||
preconditions: whenSheetFocused,
|
||||
handler: async (accessor: IAccessor, params: ISheetPasteParams) => {
|
||||
checkSheetClipboardPermission(accessor, PasteCommand.id, params);
|
||||
// const messageService = accessor.get(IMessageService);
|
||||
|
||||
// TODO: @yuhongz: check if there is excel content in the clipboard, if so
|
||||
@@ -97,6 +114,7 @@ export const SheetPasteShortKeyCommand: ICommand = {
|
||||
id: SheetPasteShortKeyCommandName,
|
||||
type: CommandType.COMMAND,
|
||||
handler: async (accessor: IAccessor, params: ISheetPasteByShortKeyParams) => {
|
||||
checkSheetClipboardPermission(accessor, PasteCommand.id);
|
||||
const clipboardService = accessor.get(ISheetClipboardService);
|
||||
const { htmlContent, textContent, files, formulaClipboardPayload } = params;
|
||||
clipboardService.legacyPaste(htmlContent, textContent, files, formulaClipboardPayload);
|
||||
@@ -105,6 +123,91 @@ export const SheetPasteShortKeyCommand: ICommand = {
|
||||
},
|
||||
};
|
||||
|
||||
function checkSheetClipboardPermission(accessor: IAccessor, commandId: string, params?: ISheetPasteParams): void {
|
||||
const permissionCheckController = accessor.get(SheetPermissionCheckController);
|
||||
let permission = true;
|
||||
let errorKey: LocaleKey = 'sheets-ui.permission.dialog.commonErr';
|
||||
|
||||
switch (commandId) {
|
||||
case CopyCommand.id:
|
||||
permission = permissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookCopyPermission],
|
||||
worksheetTypes: [WorksheetCopyPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
});
|
||||
errorKey = 'sheets-ui.permission.dialog.copyErr';
|
||||
break;
|
||||
case CutCommand.id:
|
||||
permission = permissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookCopyPermission, WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetCopyPermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint, RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
errorKey = 'sheets-ui.permission.dialog.copyErr';
|
||||
break;
|
||||
case PasteCommand.id:
|
||||
permission = checkSheetPastePermission(permissionCheckController, params);
|
||||
errorKey = 'sheets-ui.permission.dialog.pasteErr';
|
||||
break;
|
||||
}
|
||||
|
||||
if (permission) {
|
||||
return;
|
||||
}
|
||||
|
||||
const localeService = accessor.get(LocaleService);
|
||||
let errorMsg = localeService.t<LocaleKey>(errorKey);
|
||||
if (commandId === CopyCommand.id || commandId === CutCommand.id) {
|
||||
const instanceService = accessor.get(IUniverInstanceService);
|
||||
const target = getSheetCommandTarget(instanceService);
|
||||
const permissionService = accessor.get(IPermissionService);
|
||||
if (
|
||||
target &&
|
||||
!permissionService.getPermissionPoint(new WorkbookCopyPermission(target.unitId).id)?.value
|
||||
) {
|
||||
errorMsg = localeService.t<LocaleKey>('sheets-ui.permission.dialog.workbookCopyErr');
|
||||
}
|
||||
}
|
||||
|
||||
permissionCheckController.blockExecuteWithoutPermission(errorMsg);
|
||||
}
|
||||
|
||||
function checkSheetPastePermission(
|
||||
permissionCheckController: SheetPermissionCheckController,
|
||||
params?: ISheetPasteParams
|
||||
): boolean {
|
||||
if (
|
||||
params?.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE ||
|
||||
params?.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA ||
|
||||
params?.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT
|
||||
) {
|
||||
return permissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetSetCellStylePermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
}
|
||||
|
||||
if (params?.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_COL_WIDTH) {
|
||||
return permissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [
|
||||
WorksheetEditPermission,
|
||||
WorksheetSetCellValuePermission,
|
||||
WorksheetSetCellStylePermission,
|
||||
WorksheetSetColumnStylePermission,
|
||||
],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
}
|
||||
|
||||
return permissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetSetCellValuePermission, WorksheetSetCellStylePermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
}
|
||||
|
||||
export const SheetPasteValueCommand: ICommand = {
|
||||
id: 'sheet.command.paste-value',
|
||||
type: CommandType.COMMAND,
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ICommandInfo, IExecutionOptions, IMutationCommonParams, IRange, Nullable, UnitModel, Workbook } from '@univerjs/core';
|
||||
import type { ICommandInfo, IExecutionOptions, IMutationCommonParams, IRange, Nullable, UnitModel, Workbook, Worksheet } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderModule } from '@univerjs/engine-render';
|
||||
import type { IAutoFillLocation, IRemoveSheetMutationParams } from '@univerjs/sheets';
|
||||
import {
|
||||
Disposable,
|
||||
@@ -22,9 +23,8 @@ import {
|
||||
ICommandService,
|
||||
Inject,
|
||||
IUniverInstanceService,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { DeviceInputEventType, getCurrentTypeOfRenderer, IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { DeviceInputEventType } from '@univerjs/engine-render';
|
||||
import {
|
||||
AUTO_FILL_HOOK_TYPE,
|
||||
AutoClearContentCommand,
|
||||
@@ -42,10 +42,10 @@ import {
|
||||
RemoveSheetMutation,
|
||||
SetRangeValuesCommand,
|
||||
SetRangeValuesMutation,
|
||||
SetSelectionsOperation,
|
||||
SetWorksheetActiveOperation,
|
||||
SetWorksheetColWidthMutation,
|
||||
SetWorksheetRowHeightMutation,
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import { SetCellEditVisibleOperation } from '../commands/operations/cell-edit.operation';
|
||||
import { SetZoomRatioOperation } from '../commands/operations/set-zoom-ratio.operation';
|
||||
@@ -61,8 +61,6 @@ export class AutoFillUIController extends Disposable {
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IAutoFillService private readonly _autoFillService: IAutoFillService,
|
||||
@Inject(AutoFillController) private _autoFillController: AutoFillController,
|
||||
@IEditorBridgeService private readonly _editorBridgeService: IEditorBridgeService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@Inject(SheetsRenderService) private _sheetsRenderService: SheetsRenderService
|
||||
) {
|
||||
super();
|
||||
@@ -72,7 +70,6 @@ export class AutoFillUIController extends Disposable {
|
||||
|
||||
private _init() {
|
||||
this._initDefaultHook();
|
||||
this._initSelectionControlFillChanged();
|
||||
this._initQuitListener();
|
||||
this._initSkeletonChange();
|
||||
}
|
||||
@@ -144,182 +141,147 @@ export class AutoFillUIController extends Disposable {
|
||||
this._autoFillController.quit();
|
||||
this._autoFillService.setShowMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
private _initSelectionControlFillChanged() {
|
||||
const disposableCollection = new DisposableCollection();
|
||||
let pendingRetry = false;
|
||||
let retryCount = 0;
|
||||
export class AutoFillRenderController extends Disposable implements IRenderModule {
|
||||
private readonly _selectionControlDisposables = new DisposableCollection();
|
||||
|
||||
const scheduleUpdateListener = (listener: () => void) => {
|
||||
if (pendingRetry) {
|
||||
return;
|
||||
}
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<Workbook>,
|
||||
@Inject(ISheetSelectionRenderService) private readonly _selectionRenderService: ISheetSelectionRenderService,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IEditorBridgeService private readonly _editorBridgeService: IEditorBridgeService
|
||||
) {
|
||||
super();
|
||||
|
||||
pendingRetry = true;
|
||||
setTimeout(() => {
|
||||
pendingRetry = false;
|
||||
listener();
|
||||
}, 0);
|
||||
};
|
||||
this._initSelectionControlFillChanged();
|
||||
}
|
||||
|
||||
const updateListener = () => {
|
||||
// Each range change requires re-listening.
|
||||
disposableCollection.dispose();
|
||||
override dispose(): void {
|
||||
this._selectionControlDisposables.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
const currentRenderer = getCurrentTypeOfRenderer(UniverInstanceType.UNIVER_SHEET, this._univerInstanceService, this._renderManagerService);
|
||||
if (!currentRenderer) return;
|
||||
|
||||
const selectionRenderService = getResolvedSelectionRenderService(currentRenderer);
|
||||
if (!selectionRenderService) {
|
||||
retryCount += 1;
|
||||
if (retryCount <= 3) {
|
||||
scheduleUpdateListener(updateListener);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
retryCount = 0;
|
||||
|
||||
const selectionControls = selectionRenderService.getSelectionControls();
|
||||
selectionControls.forEach((controlSelection) => {
|
||||
disposableCollection.add(controlSelection.selectionFilled$.subscribe((filled) => {
|
||||
if (
|
||||
filled == null ||
|
||||
filled.startColumn === -1 ||
|
||||
filled.startRow === -1 ||
|
||||
filled.endColumn === -1 ||
|
||||
filled.endRow === -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const source: IRange = {
|
||||
startColumn: controlSelection.model.startColumn,
|
||||
endColumn: controlSelection.model.endColumn,
|
||||
startRow: controlSelection.model.startRow,
|
||||
endRow: controlSelection.model.endRow,
|
||||
};
|
||||
const selection: IRange = {
|
||||
startColumn: filled.startColumn,
|
||||
endColumn: filled.endColumn,
|
||||
startRow: filled.startRow,
|
||||
endRow: filled.endRow,
|
||||
};
|
||||
|
||||
this._commandService.executeCommand(AutoFillCommand.id, { sourceRange: source, targetRange: selection });
|
||||
}));
|
||||
|
||||
// double click to fill range, range length will align to left or right column.
|
||||
// fill results will be as same as drag operation
|
||||
disposableCollection.add(controlSelection.fillControl.onDblclick$.subscribeEvent(() => {
|
||||
const source = {
|
||||
startColumn: controlSelection.model.startColumn,
|
||||
endColumn: controlSelection.model.endColumn,
|
||||
startRow: controlSelection.model.startRow,
|
||||
endRow: controlSelection.model.endRow,
|
||||
};
|
||||
this._handleDbClickFill(source);
|
||||
}));
|
||||
|
||||
disposableCollection.add(controlSelection.fillControl.onPointerDown$.subscribeEvent(() => {
|
||||
const visibleState = this._editorBridgeService.isVisible();
|
||||
if (visibleState.visible) {
|
||||
this._commandService.syncExecuteCommand(
|
||||
SetCellEditVisibleOperation.id,
|
||||
{
|
||||
visible: false,
|
||||
eventType: DeviceInputEventType.PointerDown,
|
||||
unitId: currentRenderer.unitId,
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
scheduleUpdateListener(updateListener);
|
||||
|
||||
// Should subscribe current current renderer change as well.
|
||||
// TODO@yuhongz: this seems not ideal. This should be an `IRenderModule` for running with multiple renderers?
|
||||
this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => {
|
||||
if (command.id === SetSelectionsOperation.id) {
|
||||
scheduleUpdateListener(updateListener);
|
||||
}
|
||||
private _initSelectionControlFillChanged(): void {
|
||||
this._updateSelectionControlListeners();
|
||||
this.disposeWithMe(this._selectionManagerService.selectionChanged$.subscribe(() => {
|
||||
this._updateSelectionControlListeners();
|
||||
}));
|
||||
}
|
||||
|
||||
this.disposeWithMe(this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET)
|
||||
.subscribe(() => scheduleUpdateListener(updateListener)));
|
||||
private _updateSelectionControlListeners(): void {
|
||||
// Selection controls are recreated when the selected ranges change.
|
||||
this._selectionControlDisposables.dispose();
|
||||
|
||||
this._selectionRenderService.getSelectionControls().forEach((controlSelection) => {
|
||||
this._selectionControlDisposables.add(controlSelection.selectionFilled$.subscribe((filled) => {
|
||||
if (
|
||||
filled == null ||
|
||||
filled.startColumn === -1 ||
|
||||
filled.startRow === -1 ||
|
||||
filled.endColumn === -1 ||
|
||||
filled.endRow === -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceRange: IRange = {
|
||||
startColumn: controlSelection.model.startColumn,
|
||||
endColumn: controlSelection.model.endColumn,
|
||||
startRow: controlSelection.model.startRow,
|
||||
endRow: controlSelection.model.endRow,
|
||||
};
|
||||
const targetRange: IRange = {
|
||||
startColumn: filled.startColumn,
|
||||
endColumn: filled.endColumn,
|
||||
startRow: filled.startRow,
|
||||
endRow: filled.endRow,
|
||||
};
|
||||
|
||||
this._executeAutoFill(sourceRange, targetRange);
|
||||
}));
|
||||
|
||||
// Double click has the same effect as dragging the fill control, but its target range is detected automatically.
|
||||
this._selectionControlDisposables.add(controlSelection.fillControl.onDblclick$.subscribeEvent(() => {
|
||||
const sourceRange: IRange = {
|
||||
startColumn: controlSelection.model.startColumn,
|
||||
endColumn: controlSelection.model.endColumn,
|
||||
startRow: controlSelection.model.startRow,
|
||||
endRow: controlSelection.model.endRow,
|
||||
};
|
||||
this._handleDbClickFill(sourceRange);
|
||||
}));
|
||||
|
||||
this._selectionControlDisposables.add(controlSelection.fillControl.onPointerDown$.subscribeEvent(() => {
|
||||
const visibleState = this._editorBridgeService.isVisible();
|
||||
if (visibleState.visible) {
|
||||
this._commandService.syncExecuteCommand(SetCellEditVisibleOperation.id, {
|
||||
visible: false,
|
||||
eventType: DeviceInputEventType.PointerDown,
|
||||
unitId: this._context.unitId,
|
||||
});
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private _executeAutoFill(sourceRange: IRange, targetRange: IRange): void {
|
||||
const subUnitId = this._context.unit.getActiveSheet().getSheetId();
|
||||
this._commandService.executeCommand(AutoFillCommand.id, {
|
||||
sourceRange,
|
||||
targetRange,
|
||||
unitId: this._context.unitId,
|
||||
subUnitId,
|
||||
});
|
||||
}
|
||||
|
||||
private _handleDbClickFill(source: IRange) {
|
||||
const selection = this._detectFillRange(source);
|
||||
const worksheet = this._context.unit.getActiveSheet();
|
||||
const selection = detectAutoFillRange(source, worksheet);
|
||||
// double click only works when dest range is longer than source range
|
||||
if (selection.endRow <= source.endRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// double click effect is the same as drag effect, but the apply area is automatically calculated (by method '_detectFillRange')
|
||||
this._commandService.executeCommand(AutoFillCommand.id, { sourceRange: source, targetRange: selection });
|
||||
}
|
||||
|
||||
private _detectFillRange(source: IRange) {
|
||||
const { startRow, endRow, startColumn, endColumn } = source;
|
||||
const worksheet = this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)?.getActiveSheet();
|
||||
if (!worksheet) {
|
||||
return source;
|
||||
}
|
||||
const matrix = worksheet.getCellMatrix();
|
||||
const maxRow = worksheet.getMaxRows();
|
||||
const maxColumn = worksheet.getMaxColumns();
|
||||
let detectEndRow = endRow + 1;
|
||||
// left column first, or consider right column.
|
||||
if (startColumn > 0 && matrix.getValue(detectEndRow, startColumn - 1)?.v != null) {
|
||||
while (matrix.getValue(detectEndRow + 1, startColumn - 1)?.v != null && detectEndRow < maxRow) {
|
||||
detectEndRow += 1;
|
||||
}
|
||||
} else if (endColumn < maxColumn - 1 && matrix.getValue(detectEndRow, endColumn + 1)?.v != null) {
|
||||
while (matrix.getValue(detectEndRow + 1, endColumn + 1)?.v != null && detectEndRow < maxRow) {
|
||||
detectEndRow += 1;
|
||||
}
|
||||
} else {
|
||||
detectEndRow = endRow;
|
||||
}
|
||||
|
||||
// If the fill range contains data, stop filling at the first row of data.
|
||||
for (let i = endRow + 1; i <= detectEndRow; i++) {
|
||||
for (let j = startColumn; j <= endColumn; j++) {
|
||||
if (matrix.getValue(i, j)?.v != null) {
|
||||
detectEndRow = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
startColumn,
|
||||
endColumn,
|
||||
startRow,
|
||||
endRow: detectEndRow,
|
||||
};
|
||||
// Double click uses the same fill command as dragging, with a target range calculated from adjacent data.
|
||||
this._executeAutoFill(source, selection);
|
||||
}
|
||||
}
|
||||
|
||||
function getResolvedSelectionRenderService(renderer: unknown): ISheetSelectionRenderService | undefined {
|
||||
const injector = (renderer as { getInjector?: () => unknown }).getInjector?.();
|
||||
const resolvedDependencies = (injector as {
|
||||
resolvedDependencyCollection?: {
|
||||
resolvedDependencies?: Map<unknown, unknown[]>;
|
||||
};
|
||||
} | undefined)?.resolvedDependencyCollection?.resolvedDependencies;
|
||||
|
||||
if (!resolvedDependencies) {
|
||||
return undefined;
|
||||
export function detectAutoFillRange(source: IRange, worksheet: Worksheet): IRange {
|
||||
const { startRow, endRow, startColumn, endColumn } = source;
|
||||
const matrix = worksheet.getCellMatrix();
|
||||
const maxRow = worksheet.getMaxRows();
|
||||
const maxColumn = worksheet.getMaxColumns();
|
||||
let detectEndRow = endRow + 1;
|
||||
// left column first, or consider right column.
|
||||
if (startColumn > 0 && matrix.getValue(detectEndRow, startColumn - 1)?.v != null) {
|
||||
while (matrix.getValue(detectEndRow + 1, startColumn - 1)?.v != null && detectEndRow < maxRow) {
|
||||
detectEndRow += 1;
|
||||
}
|
||||
} else if (endColumn < maxColumn - 1 && matrix.getValue(detectEndRow, endColumn + 1)?.v != null) {
|
||||
while (matrix.getValue(detectEndRow + 1, endColumn + 1)?.v != null && detectEndRow < maxRow) {
|
||||
detectEndRow += 1;
|
||||
}
|
||||
} else {
|
||||
detectEndRow = endRow;
|
||||
}
|
||||
|
||||
for (const [identifier, values] of resolvedDependencies) {
|
||||
if ((identifier as { decoratorName?: unknown }).decoratorName === (ISheetSelectionRenderService as unknown as { decoratorName?: unknown }).decoratorName) {
|
||||
return values.length === 1 ? values[0] as ISheetSelectionRenderService : undefined;
|
||||
// If the fill range contains data, stop filling at the first row of data.
|
||||
for (let i = endRow + 1; i <= detectEndRow; i++) {
|
||||
for (let j = startColumn; j <= endColumn; j++) {
|
||||
if (matrix.getValue(i, j)?.v != null) {
|
||||
detectEndRow = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return {
|
||||
startColumn,
|
||||
endColumn,
|
||||
startRow,
|
||||
endRow: detectEndRow,
|
||||
};
|
||||
}
|
||||
|
||||
+24
-7
@@ -24,6 +24,7 @@ import { InsertTextCommand } from '@univerjs/docs';
|
||||
import { IMEInputCommand } from '@univerjs/docs-ui';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SheetCopyCommand, SheetCutCommand, SheetPasteCommand } from '../../../commands/commands/clipboard.command';
|
||||
import { SheetPermissionCheckUIController } from '../sheet-permission-check-ui.controller';
|
||||
|
||||
type ControllerConstructorArgs = ConstructorParameters<typeof SheetPermissionCheckUIController>;
|
||||
@@ -31,6 +32,7 @@ type ControllerConstructorArgs = ConstructorParameters<typeof SheetPermissionChe
|
||||
function createController() {
|
||||
let beforeCommandExecuted: ((commandInfo: ICommandInfo) => void) | undefined;
|
||||
const permissionCheckWithoutRange = vi.fn(() => false);
|
||||
const permissionCheckWithRanges = vi.fn(() => true);
|
||||
const blockExecuteWithoutPermission = vi.fn();
|
||||
const controller = new SheetPermissionCheckUIController(
|
||||
{
|
||||
@@ -39,23 +41,24 @@ function createController() {
|
||||
return { dispose: vi.fn() };
|
||||
}),
|
||||
} as unknown as ControllerConstructorArgs[0],
|
||||
{} as unknown as ControllerConstructorArgs[1],
|
||||
{ getShowComponents: vi.fn(() => true) } as unknown as ControllerConstructorArgs[2],
|
||||
{ open: vi.fn(), close: vi.fn() } as unknown as ControllerConstructorArgs[3],
|
||||
{} as unknown as ControllerConstructorArgs[4],
|
||||
{ t: vi.fn((key: string) => `translated:${key}`) } as unknown as ControllerConstructorArgs[5],
|
||||
{ getContextValue: vi.fn(() => false) } as unknown as ControllerConstructorArgs[6],
|
||||
{ getShowComponents: vi.fn(() => true) } as unknown as ControllerConstructorArgs[1],
|
||||
{ open: vi.fn(), close: vi.fn() } as unknown as ControllerConstructorArgs[2],
|
||||
{} as unknown as ControllerConstructorArgs[3],
|
||||
{ t: vi.fn((key: string) => `translated:${key}`) } as unknown as ControllerConstructorArgs[4],
|
||||
{ getContextValue: vi.fn(() => false) } as unknown as ControllerConstructorArgs[5],
|
||||
{
|
||||
triggerPermissionUIEvent$: EMPTY,
|
||||
permissionCheckWithoutRange,
|
||||
permissionCheckWithRanges,
|
||||
blockExecuteWithoutPermission,
|
||||
} as unknown as ControllerConstructorArgs[7]
|
||||
} as unknown as ControllerConstructorArgs[6]
|
||||
);
|
||||
|
||||
return {
|
||||
controller,
|
||||
executeBefore: (commandInfo: ICommandInfo) => beforeCommandExecuted?.(commandInfo),
|
||||
permissionCheckWithoutRange,
|
||||
permissionCheckWithRanges,
|
||||
blockExecuteWithoutPermission,
|
||||
};
|
||||
}
|
||||
@@ -96,4 +99,18 @@ describe('SheetPermissionCheckUIController', () => {
|
||||
expect(blockExecuteWithoutPermission).toHaveBeenCalledWith('translated:sheets-ui.permission.dialog.editErr');
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[SheetCopyCommand.id, 'copy'],
|
||||
[SheetCutCommand.id, 'cut'],
|
||||
[SheetPasteCommand.id, 'paste'],
|
||||
])('does not preflight the global %s command before its multi-command implementation is selected', (id) => {
|
||||
const { controller, executeBefore, permissionCheckWithRanges, blockExecuteWithoutPermission } = createController();
|
||||
|
||||
executeBefore({ id });
|
||||
|
||||
expect(permissionCheckWithRanges).not.toHaveBeenCalled();
|
||||
expect(blockExecuteWithoutPermission).not.toHaveBeenCalled();
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import type { ICommandInfo } from '@univerjs/core';
|
||||
import type { IInsertTextCommandParams } from '@univerjs/docs';
|
||||
import type { IIMEInputCommandParams } from '@univerjs/docs-ui';
|
||||
import type { ISheetPasteParams } from '../../commands/commands/clipboard.command';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { IEditorBridgeServiceVisibleParam } from '../../services/editor-bridge.service';
|
||||
import {
|
||||
@@ -29,37 +28,23 @@ import {
|
||||
IContextService,
|
||||
Inject,
|
||||
IPermissionService,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
SHEET_EDITOR_UNITS,
|
||||
} from '@univerjs/core';
|
||||
import { InsertTextCommand } from '@univerjs/docs';
|
||||
import { IMEInputCommand } from '@univerjs/docs-ui';
|
||||
import {
|
||||
getSheetCommandTarget,
|
||||
RangeProtectionPermissionEditPoint,
|
||||
RangeProtectionPermissionViewPoint,
|
||||
RangeProtectionRuleModel,
|
||||
SheetPermissionCheckController,
|
||||
WorkbookCopyPermission,
|
||||
WorkbookEditablePermission,
|
||||
WorksheetCopyPermission,
|
||||
WorksheetEditPermission,
|
||||
WorksheetSetCellStylePermission,
|
||||
WorksheetSetCellValuePermission,
|
||||
WorksheetSetColumnStylePermission,
|
||||
} from '@univerjs/sheets';
|
||||
import { IDialogService } from '@univerjs/ui';
|
||||
import {
|
||||
SheetCopyCommand,
|
||||
SheetCutCommand,
|
||||
SheetPasteColWidthCommand,
|
||||
SheetPasteCommand,
|
||||
SheetPasteShortKeyCommand,
|
||||
} from '../../commands/commands/clipboard.command';
|
||||
import { ApplyFormatPainterCommand } from '../../commands/commands/set-format-painter.command';
|
||||
import { SetCellEditVisibleOperation } from '../../commands/operations/cell-edit.operation';
|
||||
import { PREDEFINED_HOOK_NAME_PASTE } from '../../services/clipboard/clipboard.service';
|
||||
import {
|
||||
UNIVER_SHEET_PERMISSION_ALERT_DIALOG,
|
||||
UNIVER_SHEET_PERMISSION_ALERT_DIALOG_ID,
|
||||
@@ -70,7 +55,6 @@ export class SheetPermissionCheckUIController extends Disposable {
|
||||
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@IPermissionService private readonly _permissionService: IPermissionService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@Inject(RangeProtectionRuleModel) private _rangeProtectionRuleModel: RangeProtectionRuleModel,
|
||||
@@ -117,14 +101,12 @@ export class SheetPermissionCheckUIController extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function,complexity
|
||||
private _getPermissionCheck(commandInfo: ICommandInfo) {
|
||||
const { id } = commandInfo;
|
||||
|
||||
let permission = true;
|
||||
let errorMsg = '';
|
||||
let params;
|
||||
let target;
|
||||
|
||||
switch (id) {
|
||||
case InsertTextCommand.id:
|
||||
@@ -160,21 +142,6 @@ export class SheetPermissionCheckUIController extends Disposable {
|
||||
});
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.editErr');
|
||||
break;
|
||||
case SheetPasteColWidthCommand.id:
|
||||
permission = this._sheetPermissionCheckController.permissionCheckWithoutRange({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetEditPermission, WorksheetSetColumnStylePermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.pasteErr');
|
||||
break;
|
||||
case SheetPasteShortKeyCommand.id:
|
||||
case SheetPasteCommand.id:
|
||||
params = commandInfo.params as ISheetPasteParams;
|
||||
|
||||
permission = this._permissionCheckByPaste(params);
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.pasteErr');
|
||||
break;
|
||||
case ApplyFormatPainterCommand.id:
|
||||
permission = this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
@@ -183,43 +150,6 @@ export class SheetPermissionCheckUIController extends Disposable {
|
||||
});
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.commonErr');
|
||||
break;
|
||||
case SheetCopyCommand.id:
|
||||
permission = this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookCopyPermission],
|
||||
worksheetTypes: [WorksheetCopyPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint],
|
||||
});
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.copyErr');
|
||||
|
||||
target = getSheetCommandTarget(this._univerInstanceService);
|
||||
if (
|
||||
!permission &&
|
||||
target &&
|
||||
!this._permissionService.getPermissionPoint(new WorkbookCopyPermission(target.unitId).id)?.value
|
||||
) {
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.workbookCopyErr');
|
||||
}
|
||||
|
||||
break;
|
||||
case SheetCutCommand.id:
|
||||
permission = this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookCopyPermission, WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetCopyPermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionViewPoint, RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.copyErr');
|
||||
|
||||
target = getSheetCommandTarget(this._univerInstanceService);
|
||||
if (
|
||||
!permission &&
|
||||
target &&
|
||||
!this._permissionService.getPermissionPoint(new WorkbookCopyPermission(target.unitId).id)?.value
|
||||
) {
|
||||
errorMsg = this._localeService.t<LocaleKey>('sheets-ui.permission.dialog.workbookCopyErr');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -240,26 +170,4 @@ export class SheetPermissionCheckUIController extends Disposable {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _permissionCheckByPaste(params: ISheetPasteParams) {
|
||||
if (params.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE || params.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA) {
|
||||
return this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetSetCellStylePermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
} else if (params.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT) {
|
||||
return this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetSetCellStylePermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
} else {
|
||||
return this._sheetPermissionCheckController.permissionCheckWithRanges({
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetSetCellValuePermission, WorksheetSetCellStylePermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import { UI_PLUGIN_CONFIG_KEY, UniverMobileUIPlugin } from '@univerjs/ui';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { AutoFillUIController } from './controllers/auto-fill-ui.controller';
|
||||
import { AutoFillRenderController, AutoFillUIController } from './controllers/auto-fill-ui.controller';
|
||||
import { AutoHeightController } from './controllers/auto-height.controller';
|
||||
import { AutoWidthController } from './controllers/auto-width.controller';
|
||||
import { CellCustomRenderController } from './controllers/cell-custom-render.controller';
|
||||
@@ -286,6 +286,7 @@ export class UniverSheetsMobileUIPlugin extends Plugin {
|
||||
[SheetContextMenuMobileRenderController],
|
||||
[MobileHeaderResizeRenderController],
|
||||
[MoveRangeRenderController],
|
||||
[AutoFillRenderController],
|
||||
|
||||
// editor
|
||||
[EditorBridgeRenderController],
|
||||
|
||||
@@ -36,7 +36,7 @@ import { UI_PLUGIN_CONFIG_KEY } from '@univerjs/ui';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { AutoFillUIController } from './controllers/auto-fill-ui.controller';
|
||||
import { AutoFillRenderController, AutoFillUIController } from './controllers/auto-fill-ui.controller';
|
||||
import { AutoHeightController } from './controllers/auto-height.controller';
|
||||
import { AutoWidthController } from './controllers/auto-width.controller';
|
||||
import { CellCustomRenderController } from './controllers/cell-custom-render.controller';
|
||||
@@ -289,6 +289,7 @@ export class UniverSheetsUIPlugin extends Plugin {
|
||||
[CellCustomRenderController],
|
||||
[SheetContextMenuRenderController],
|
||||
[MoveRangeRenderController],
|
||||
[AutoFillRenderController],
|
||||
|
||||
// editor
|
||||
[EditorBridgeRenderController],
|
||||
|
||||
@@ -45,7 +45,14 @@ import {
|
||||
LexerTreeBuilder,
|
||||
} from '@univerjs/engine-formula';
|
||||
import { IRenderManagerService, RenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetInterceptorService, SheetSkeletonService, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import {
|
||||
RangeProtectionRuleModel,
|
||||
SheetInterceptorService,
|
||||
SheetPermissionCheckController,
|
||||
SheetSkeletonService,
|
||||
SheetsSelectionsService,
|
||||
WorksheetProtectionRuleModel,
|
||||
} from '@univerjs/sheets';
|
||||
import {
|
||||
BrowserClipboardService,
|
||||
DesktopMessageService,
|
||||
@@ -573,6 +580,9 @@ export function clipboardTestBed(workbookData?: IWorkbookData, dependencies?: De
|
||||
const injector = this._injector;
|
||||
injector.add([IUIPartsService, { useClass: UIPartsService }]);
|
||||
injector.add([SheetsSelectionsService]);
|
||||
injector.add([WorksheetProtectionRuleModel]);
|
||||
injector.add([RangeProtectionRuleModel]);
|
||||
injector.add([SheetPermissionCheckController]);
|
||||
injector.add([IClipboardInterfaceService, { useClass: BrowserClipboardService, lazy: true }]);
|
||||
injector.add([ISheetClipboardService, { useClass: SheetClipboardService }]);
|
||||
injector.add([IMessageService, { useClass: DesktopMessageService, lazy: true }]);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import type { IAccessor, ICommand, IRange } from '@univerjs/core';
|
||||
import type { ISetRangeValuesMutationParams } from '../mutations/set-range-values.mutation';
|
||||
import type { ISheetCommandSharedParams } from '../utils/interface';
|
||||
import { CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, sequenceExecute } from '@univerjs/core';
|
||||
import { generateNullCellValue } from '../../basics/utils';
|
||||
import { IAutoFillService } from '../../services/auto-fill/auto-fill.service';
|
||||
@@ -26,11 +27,9 @@ import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '../mu
|
||||
import { SetSelectionsOperation } from '../operations/selection.operation';
|
||||
import { getSheetCommandTarget } from './utils/target-util';
|
||||
|
||||
export interface IAutoFillCommandParams {
|
||||
export interface IAutoFillCommandParams extends Partial<ISheetCommandSharedParams> {
|
||||
sourceRange: IRange;
|
||||
targetRange: IRange;
|
||||
unitId?: string; // if not provided, use current unitId
|
||||
subUnitId?: string; // if not provided, use current subUnitId
|
||||
applyType?: AUTO_FILL_APPLY_TYPE; // manual apply type
|
||||
}
|
||||
|
||||
@@ -135,7 +134,7 @@ export const SheetCopyRightCommand: ICommand = {
|
||||
handler: async (accessor: IAccessor) => executeSheetCopyFill(accessor, 'right'),
|
||||
};
|
||||
|
||||
export interface IAutoClearContentCommand {
|
||||
export interface IAutoClearContentCommand extends ISheetCommandSharedParams {
|
||||
clearRange: IRange;
|
||||
selectionRange: IRange;
|
||||
}
|
||||
@@ -145,7 +144,7 @@ export const AutoClearContentCommand: ICommand = {
|
||||
type: CommandType.COMMAND,
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
handler: async (accessor: IAccessor, params: IAutoClearContentCommand) => {
|
||||
const target = getSheetCommandTarget(accessor.get(IUniverInstanceService));
|
||||
const target = getSheetCommandTarget(accessor.get(IUniverInstanceService), params);
|
||||
if (!target) return false;
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
@@ -176,6 +176,8 @@ export class AutoFillService extends Disposable implements IAutoFillService {
|
||||
// situation 1: drag to smaller range, horizontally.
|
||||
if (selection.endColumn < source.endColumn && selection.endColumn > source.startColumn) {
|
||||
return this._commandService.executeCommand(AutoClearContentCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
clearRange: {
|
||||
startRow: selection.startRow,
|
||||
endRow: selection.endRow,
|
||||
@@ -188,6 +190,8 @@ export class AutoFillService extends Disposable implements IAutoFillService {
|
||||
// situation 2: drag to smaller range, vertically.
|
||||
if (selection.endRow < source.endRow && selection.endRow > source.startRow) {
|
||||
return this._commandService.executeCommand(AutoClearContentCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
clearRange: {
|
||||
startRow: selection.endRow + 1,
|
||||
endRow: source.endRow,
|
||||
@@ -225,8 +229,8 @@ export class AutoFillService extends Disposable implements IAutoFillService {
|
||||
|
||||
this.direction = direction;
|
||||
|
||||
const autoFillSource = this._injector.invoke((accessor: IAccessor) => rangeToDiscreteRange(source, accessor));
|
||||
const autoFillTarget = this._injector.invoke((accessor: IAccessor) => rangeToDiscreteRange(target, accessor));
|
||||
const autoFillSource = this._injector.invoke((accessor: IAccessor) => rangeToDiscreteRange(source, accessor, unitId, subUnitId));
|
||||
const autoFillTarget = this._injector.invoke((accessor: IAccessor) => rangeToDiscreteRange(target, accessor, unitId, subUnitId));
|
||||
|
||||
if (!autoFillSource || !autoFillTarget) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user