Skip to content

Commit

Permalink
Merge pull request #2670 from microsoft/u/juliaroldi/image-edit-opera…
Browse files Browse the repository at this point in the history
…tions

Port Image Edit Operations
  • Loading branch information
juliaroldi authored Jun 5, 2024
2 parents 68bfc33 + 9dd412a commit b4fc6d6
Show file tree
Hide file tree
Showing 75 changed files with 4,948 additions and 157 deletions.
81 changes: 81 additions & 0 deletions demo/scripts/controlsV2/demoButtons/createImageEditButtons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { ImageEditor } from 'roosterjs-content-model-types';
import type { RibbonButton } from '../roosterjsReact/ribbon';

/**
* @internal
* "Image Crop" button on the format ribbon
*/
function createImageCropButton(handler: ImageEditor): RibbonButton<'buttonNameCropImage'> {
return {
key: 'buttonNameCropImage',
unlocalizedText: 'Crop Image',
iconName: 'Crop',
isDisabled: formatState =>
!formatState.canAddImageAltText || !handler.isOperationAllowed('crop'),
onClick: () => {
handler.cropImage();
},
};
}

const directions: Record<string, string> = {
left: 'Left',
right: 'Right',
};

/**
* @internal
* "Image Rotate" button on the format ribbon
*/
function createImageRotateButton(handler: ImageEditor): RibbonButton<'buttonNameRotateImage'> {
return {
key: 'buttonNameRotateImage',
unlocalizedText: 'Rotate Image',
iconName: 'Rotate',
dropDownMenu: {
items: directions,
},
isDisabled: formatState => !formatState.canAddImageAltText,
onClick: (_editor, direction) => {
const rotateDirection = direction as 'left' | 'right';
const rad = degreeToRad(rotateDirection == 'left' ? 270 : 90);
handler.rotateImage(rad);
},
};
}

const flipDirections: Record<string, string> = {
horizontal: 'horizontal',
vertical: 'vertical',
};

/**
* @internal
* "Image Flip" button on the format ribbon
*/
function createImageFlipButton(handler: ImageEditor): RibbonButton<'buttonNameFlipImage'> {
return {
key: 'buttonNameFlipImage',
unlocalizedText: 'Flip Image',
iconName: 'ImagePixel',
dropDownMenu: {
items: flipDirections,
},
isDisabled: formatState => !formatState.canAddImageAltText,
onClick: (_editor, flipDirection) => {
handler.flipImage(flipDirection as 'horizontal' | 'vertical');
},
};
}

export const createImageEditButtons = (handler: ImageEditor) => {
return [
createImageCropButton(handler),
createImageRotateButton(handler),
createImageFlipButton(handler),
];
};

const degreeToRad = (degree: number) => {
return degree * (Math.PI / 180);
};
25 changes: 13 additions & 12 deletions demo/scripts/controlsV2/mainPane/MainPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import { ApiPlaygroundPlugin } from '../sidePane/apiPlayground/ApiPlaygroundPlug
import { ContentModelPanePlugin } from '../sidePane/contentModel/ContentModelPanePlugin';
import { createEmojiPlugin } from '../roosterjsReact/emoji';
import { createImageEditMenuProvider } from '../roosterjsReact/contextMenu/menus/createImageEditMenuProvider';
import { createLegacyPlugins } from '../plugins/createLegacyPlugins';
import { createListEditMenuProvider } from '../roosterjsReact/contextMenu/menus/createListEditMenuProvider';
import { createPasteOptionPlugin } from '../roosterjsReact/pasteOptions';
import { createRibbonPlugin, Ribbon, RibbonButton, RibbonPlugin } from '../roosterjsReact/ribbon';
import { darkModeButton } from '../demoButtons/darkModeButton';
import { Editor } from 'roosterjs-content-model-core';
import { EditorAdapter } from 'roosterjs-editor-adapter';
import { EditorOptionsPlugin } from '../sidePane/editorOptions/EditorOptionsPlugin';
import { EventViewPlugin } from '../sidePane/eventViewer/EventViewPlugin';
import { exportContentButton } from '../demoButtons/exportContentButton';
Expand Down Expand Up @@ -54,6 +52,7 @@ import {
CustomReplacePlugin,
EditPlugin,
HyperlinkPlugin,
ImageEditPlugin,
MarkdownPlugin,
PastePlugin,
ShortcutPlugin,
Expand Down Expand Up @@ -100,6 +99,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
private formatPainterPlugin: FormatPainterPlugin;
private samplePickerPlugin: SamplePickerPlugin;
private snapshots: Snapshots;
private imageEditPlugin: ImageEditPlugin;

protected sidePane = React.createRef<SidePane>();
protected updateContentPlugin: UpdateContentPlugin;
Expand Down Expand Up @@ -137,6 +137,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
this.ribbonPlugin = createRibbonPlugin();
this.formatPainterPlugin = new FormatPainterPlugin();
this.samplePickerPlugin = new SamplePickerPlugin();
this.imageEditPlugin = new ImageEditPlugin();

this.state = {
showSidePane: window.location.hash != '',
Expand Down Expand Up @@ -288,7 +289,11 @@ export class MainPane extends React.Component<{}, MainPaneState> {
private renderRibbon() {
return (
<Ribbon
buttons={getButtons(this.state.activeTab, this.formatPainterPlugin)}
buttons={getButtons(
this.state.activeTab,
this.formatPainterPlugin,
this.imageEditPlugin
)}
plugin={this.ribbonPlugin}
dir={this.state.isRtl ? 'rtl' : 'ltr'}
/>
Expand All @@ -310,14 +315,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
private resetEditor() {
this.setState({
editorCreator: (div: HTMLDivElement, options: EditorOptions) => {
const legacyPluginList = createLegacyPlugins(this.state.initState);

return legacyPluginList.length > 0
? new EditorAdapter(div, {
...options,
legacyPlugins: legacyPluginList,
})
: new Editor(div, options);
return new Editor(div, options);
},
});
}
Expand Down Expand Up @@ -505,13 +503,16 @@ export class MainPane extends React.Component<{}, MainPaneState> {
pluginList.tableEdit && new TableEditPlugin(),
pluginList.watermark && new WatermarkPlugin(watermarkText),
pluginList.markdown && new MarkdownPlugin(markdownOptions),
pluginList.imageEditPlugin && this.imageEditPlugin,
pluginList.emoji && createEmojiPlugin(),
pluginList.pasteOption && createPasteOptionPlugin(),
pluginList.sampleEntity && new SampleEntityPlugin(),
pluginList.contextMenu && createContextMenuPlugin(),
pluginList.contextMenu && listMenu && createListEditMenuProvider(),
pluginList.contextMenu && tableMenu && createTableEditMenuProvider(),
pluginList.contextMenu && imageMenu && createImageEditMenuProvider(),
pluginList.contextMenu &&
imageMenu &&
createImageEditMenuProvider(this.imageEditPlugin),
pluginList.hyperlink &&
new HyperlinkPlugin(
linkTitle?.indexOf(UrlPlaceholder) >= 0
Expand Down
18 changes: 0 additions & 18 deletions demo/scripts/controlsV2/plugins/createLegacyPlugins.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const ImageRotateMenuItem: ContextMenuItem<ImageEditMenuItemStringKey, ImageEdit
imageEditor.canRegenerateImage(node as HTMLImageElement)
);
},
onClick: (key, editor, node, strings, uiUtilities, imageEdit) => {
onClick: (key, _editor, _node, _strings, _uiUtilities, imageEdit) => {
switch (key) {
case 'menuNameImageRotateLeft':
imageEdit?.rotateImage(-Math.PI / 2);
Expand All @@ -116,7 +116,7 @@ const ImageFlipMenuItem: ContextMenuItem<ImageEditMenuItemStringKey, ImageEditor
imageEditor.canRegenerateImage(node as HTMLImageElement)
);
},
onClick: (key, editor, node, strings, uiUtilities, imageEdit) => {
onClick: (key, _editor, _node, _strings, _uiUtilities, imageEdit) => {
switch (key) {
case 'menuNameImageRotateFlipHorizontally':
imageEdit?.flipImage('horizontal');
Expand All @@ -137,7 +137,7 @@ const ImageCropMenuItem: ContextMenuItem<ImageEditMenuItemStringKey, ImageEditor
imageEditor.canRegenerateImage(node as HTMLImageElement)
);
},
onClick: (_, editor, node, strings, uiUtilities, imageEdit) => {
onClick: (_, _editor, _node, _strings, _uiUtilities, imageEdit) => {
imageEdit?.cropImage();
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ const initialState: OptionState = {
pasteOption: true,
sampleEntity: true,
markdown: true,
imageEditPlugin: true,
hyperlink: true,
customReplace: true,

// Legacy plugins
imageEdit: false,
},
defaultFormat: {
fontFamily: 'Calibri',
Expand All @@ -32,7 +30,6 @@ const initialState: OptionState = {
linkTitle: 'Ctrl+Click to follow the link:' + UrlPlaceholder,
watermarkText: 'Type content here ...',
forcePreserveRatio: false,
applyChangesOnMouseUp: false,
isRtl: false,
disableCache: false,
tableFeaturesContainerSelector: '#' + 'EditorContainer',
Expand Down
8 changes: 2 additions & 6 deletions demo/scripts/controlsV2/sidePane/editorOptions/OptionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { AutoFormatOptions, CustomReplace, MarkdownOptions } from 'roosterjs-con
import type { SidePaneElementProps } from '../SidePaneElement';
import type { ContentModelSegmentFormat, ExperimentalFeature } from 'roosterjs-content-model-types';

export interface LegacyPluginList {
imageEdit: boolean;
}

export interface NewPluginList {
autoFormat: boolean;
edit: boolean;
Expand All @@ -19,10 +15,11 @@ export interface NewPluginList {
sampleEntity: boolean;
markdown: boolean;
hyperlink: boolean;
imageEditPlugin: boolean;
customReplace: boolean;
}

export interface BuildInPluginList extends LegacyPluginList, NewPluginList {}
export interface BuildInPluginList extends NewPluginList {}

export interface OptionState {
pluginList: BuildInPluginList;
Expand All @@ -46,7 +43,6 @@ export interface OptionState {
// Editor options
isRtl: boolean;
disableCache: boolean;
applyChangesOnMouseUp: boolean;
experimentalFeatures: Set<ExperimentalFeature>;
}

Expand Down
22 changes: 6 additions & 16 deletions demo/scripts/controlsV2/sidePane/editorOptions/OptionsPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { Code } from './Code';
import { DefaultFormatPane } from './DefaultFormatPane';
import { EditorCode } from './codes/EditorCode';
import { ExperimentalFeatures } from './ExperimentalFeatures';
import { LegacyPlugins, Plugins } from './Plugins';
import { MainPane } from '../../mainPane/MainPane';
import { OptionPaneProps, OptionState } from './OptionState';
import { Plugins } from './Plugins';

const htmlStart =
'<html>\n' +
Expand All @@ -23,8 +23,6 @@ const htmlButtons =
'<button id=buttonDark>Dark mode</button>\n';
'<button id=buttonDark>Dark Mode</button>\n';
const jsCode = '<script src="https://microsoft.github.io/roosterjs/rooster-min.js"></script>\n';
const legacyJsCode =
'<script src="https://microsoft.github.io/roosterjs/rooster-legacy-min.js"></script>\n<script src="https://microsoft.github.io/roosterjs/rooster-adapter-min.js"></script>\n';
const htmlEnd = '</body>\n' + '</html>';

export class OptionsPane extends React.Component<OptionPaneProps, OptionState> {
Expand All @@ -39,7 +37,7 @@ export class OptionsPane extends React.Component<OptionPaneProps, OptionState> {
}
render() {
const editorCode = new EditorCode(this.state);
const html = this.getHtml(editorCode.requireLegacyCode());
const html = this.getHtml();

return (
<div>
Expand All @@ -58,12 +56,6 @@ export class OptionsPane extends React.Component<OptionPaneProps, OptionState> {
</summary>
<Plugins state={this.state} resetState={this.resetState} />
</details>
<details>
<summary>
<b>Legacy Plugins</b>
</summary>
<LegacyPlugins state={this.state} resetState={this.resetState} />
</details>
<details>
<summary>
<b>Experimental features</b>
Expand Down Expand Up @@ -136,7 +128,7 @@ export class OptionsPane extends React.Component<OptionPaneProps, OptionState> {
pluginList: { ...this.state.pluginList },
defaultFormat: { ...this.state.defaultFormat },
forcePreserveRatio: this.state.forcePreserveRatio,
applyChangesOnMouseUp: this.state.applyChangesOnMouseUp,

isRtl: this.state.isRtl,
disableCache: this.state.disableCache,
tableFeaturesContainerSelector: this.state.tableFeaturesContainerSelector,
Expand Down Expand Up @@ -165,7 +157,7 @@ export class OptionsPane extends React.Component<OptionPaneProps, OptionState> {
let code = editor.getCode();
let json = {
title: 'RoosterJs',
html: this.getHtml(editor.requireLegacyCode()),
html: this.getHtml(),
head: '',
js: code,
js_pre_processor: 'typescript',
Expand All @@ -188,9 +180,7 @@ export class OptionsPane extends React.Component<OptionPaneProps, OptionState> {
}, true);
};

private getHtml(requireLegacyCode: boolean) {
return `${htmlStart}${htmlButtons}${jsCode}${
requireLegacyCode ? legacyJsCode : ''
}${htmlEnd}`;
private getHtml() {
return `${htmlStart}${htmlButtons}${jsCode}${htmlEnd}`;
}
}
31 changes: 2 additions & 29 deletions demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import * as React from 'react';
import { UrlPlaceholder } from './OptionState';
import type {
BuildInPluginList,
LegacyPluginList,
NewPluginList,
OptionState,
} from './OptionState';
import type { BuildInPluginList, NewPluginList, OptionState } from './OptionState';

const styles = require('./OptionsPane.scss');

Expand Down Expand Up @@ -101,29 +96,6 @@ abstract class PluginsBase<PluginKey extends keyof BuildInPluginList> extends Re
};
}

export class LegacyPlugins extends PluginsBase<keyof LegacyPluginList> {
private forcePreserveRatio = React.createRef<HTMLInputElement>();

render() {
return (
<table>
<tbody>
{this.renderPluginItem(
'imageEdit',
'Image Edit Plugin',
this.renderCheckBox(
'Force preserve ratio',
this.forcePreserveRatio,
this.props.state.forcePreserveRatio,
(state, value) => (state.forcePreserveRatio = value)
)
)}
</tbody>
</table>
);
}
}

export class Plugins extends PluginsBase<keyof NewPluginList> {
private allowExcelNoBorderTable = React.createRef<HTMLInputElement>();
private listMenu = React.createRef<HTMLInputElement>();
Expand Down Expand Up @@ -292,6 +264,7 @@ export class Plugins extends PluginsBase<keyof NewPluginList> {
)
)}
{this.renderPluginItem('customReplace', 'Custom Replace')}
{this.renderPluginItem('imageEditPlugin', 'ImageEditPlugin')}
</tbody>
</table>
);
Expand Down
Loading

0 comments on commit b4fc6d6

Please sign in to comment.