feat: amis支持进入\强调\退出动画配置 (#11091)

* feat: amis支持进入\强调\退出动画配置

* chore: 增加样式管理器

* bugfix

* 增加编辑器配置面板

* 动画增加播放功能

* 调整下文案

* bugfix

* bugfix

---------

Co-authored-by: qinhaoyan <30946345+qinhaoyan@users.noreply.github.com>
This commit is contained in:
qkiroc 2024-10-23 17:16:32 +08:00 committed by GitHub
parent 7767ab7df0
commit 434a328619
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 837 additions and 7 deletions

View File

@ -18,7 +18,13 @@ import {IScopedContext, ScopedContext} from './Scoped';
import {Schema, SchemaNode} from './types'; import {Schema, SchemaNode} from './types';
import {DebugWrapper} from './utils/debug'; import {DebugWrapper} from './utils/debug';
import getExprProperties from './utils/filter-schema'; import getExprProperties from './utils/filter-schema';
import {anyChanged, chainEvents, autobind, TestIdBuilder} from './utils/helper'; import {
anyChanged,
chainEvents,
autobind,
TestIdBuilder,
formateId
} from './utils/helper';
import {SimpleMap} from './utils/SimpleMap'; import {SimpleMap} from './utils/SimpleMap';
import {bindEvent, dispatchEvent, RendererEvent} from './utils/renderer-event'; import {bindEvent, dispatchEvent, RendererEvent} from './utils/renderer-event';
import {isAlive} from 'mobx-state-tree'; import {isAlive} from 'mobx-state-tree';
@ -28,6 +34,9 @@ import {buildStyle} from './utils/style';
import {isExpression} from './utils/formula'; import {isExpression} from './utils/formula';
import {StatusScopedProps} from './StatusScoped'; import {StatusScopedProps} from './StatusScoped';
import {evalExpression, filter} from './utils/tpl'; import {evalExpression, filter} from './utils/tpl';
import {CSSTransition} from 'react-transition-group';
import {createAnimationStyle} from './utils/animations';
import styleManager from './StyleManager';
interface SchemaRendererProps interface SchemaRendererProps
extends Partial<Omit<RendererProps, 'statusStore'>>, extends Partial<Omit<RendererProps, 'statusStore'>>,
@ -89,17 +98,44 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
schema: any; schema: any;
path: string; path: string;
animationTimeout: {
enter?: number;
exit?: number;
} = {};
animationClassNames: {
appear?: string;
enter?: string;
exit?: string;
} = {};
reaction: any; reaction: any;
unbindEvent: (() => void) | undefined = undefined; unbindEvent: (() => void) | undefined = undefined;
isStatic: any = undefined; isStatic: any = undefined;
constructor(props: SchemaRendererProps) { constructor(props: SchemaRendererProps) {
super(props); super(props);
const animations = props?.schema?.animations;
if (animations) {
let id = props?.schema.id;
id = formateId(id);
if (animations.enter) {
this.animationTimeout.enter = (animations.enter.duration || 1) * 1000;
this.animationClassNames.enter = `${animations.enter.type}-${id}-enter`;
this.animationClassNames.appear = this.animationClassNames.enter;
}
if (animations.exit) {
this.animationTimeout.exit = (animations.exit.duration || 1) * 1000;
this.animationClassNames.exit = `${animations.exit.type}-${id}-exit`;
}
}
this.refFn = this.refFn.bind(this); this.refFn = this.refFn.bind(this);
this.renderChild = this.renderChild.bind(this); this.renderChild = this.renderChild.bind(this);
this.reRender = this.reRender.bind(this); this.reRender = this.reRender.bind(this);
this.resolveRenderer(this.props); this.resolveRenderer(this.props);
this.dispatchEvent = this.dispatchEvent.bind(this); this.dispatchEvent = this.dispatchEvent.bind(this);
this.addAnimationAttention = this.addAnimationAttention.bind(this);
this.removeAnimationAttention = this.removeAnimationAttention.bind(this);
// 监听statusStore更新 // 监听statusStore更新
this.reaction = reaction( this.reaction = reaction(
@ -121,9 +157,18 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
); );
} }
componentDidMount(): void {
if (this.props.schema.animations) {
let {animations, id} = this.props.schema;
id = formateId(id);
createAnimationStyle(id, animations);
}
}
componentWillUnmount() { componentWillUnmount() {
this.reaction?.(); this.reaction?.();
this.unbindEvent?.(); this.unbindEvent?.();
this.removeAnimationStyle();
} }
// 限制:只有 schema 除外的 props 变化,或者 schema 里面的某个成员值发生变化才更新。 // 限制:只有 schema 除外的 props 变化,或者 schema 里面的某个成员值发生变化才更新。
@ -154,6 +199,14 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
return false; return false;
} }
removeAnimationStyle() {
if (this.props.schema.animations) {
let {id} = this.props.schema;
id = formateId(id);
styleManager.removeStyles(id);
}
}
resolveRenderer(props: SchemaRendererProps, force = false): any { resolveRenderer(props: SchemaRendererProps, force = false): any {
let schema = props.schema; let schema = props.schema;
let path = props.$path; let path = props.$path;
@ -297,6 +350,25 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
this.forceUpdate(); this.forceUpdate();
} }
addAnimationAttention(node: HTMLElement) {
const {schema} = this.props || {};
const {attention} = schema?.animations || {};
if (attention) {
let {id} = schema;
id = formateId(id);
node.classList.add(`${attention.type}-${id}-attention`);
}
}
removeAnimationAttention(node: HTMLElement) {
const {schema} = this.props || {};
const {attention} = schema?.animations || {};
if (attention) {
let {id} = schema;
id = formateId(id);
node.classList.remove(`${attention.type}-${id}-attention`);
}
}
render(): JSX.Element | null { render(): JSX.Element | null {
let { let {
$path: _, $path: _,
@ -447,6 +519,8 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
} = schema; } = schema;
const Component = renderer.component!; const Component = renderer.component!;
let animationIn = true;
// 原来表单项的 visible: false 和 hidden: true 表单项的值和验证是有效的 // 原来表单项的 visible: false 和 hidden: true 表单项的值和验证是有效的
// 而 visibleOn 和 hiddenOn 是无效的, // 而 visibleOn 和 hiddenOn 是无效的,
// 这个本来就是个bug但是已经被广泛使用了 // 这个本来就是个bug但是已经被广泛使用了
@ -458,8 +532,12 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
!renderer.isFormItem || !renderer.isFormItem ||
(schema.visible !== false && !schema.hidden)) (schema.visible !== false && !schema.hidden))
) { ) {
if (schema.animations) {
animationIn = false;
} else {
return null; return null;
} }
}
// withStore 里面会处理,而且会实时处理 // withStore 里面会处理,而且会实时处理
// 这里处理反而导致了问题 // 这里处理反而导致了问题
@ -526,12 +604,28 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
} }
} }
const component = supportRef ? ( let component = supportRef ? (
<Component {...props} ref={this.childRef} /> <Component {...props} ref={this.childRef} />
) : ( ) : (
<Component {...props} forwardedRef={this.childRef} /> <Component {...props} forwardedRef={this.childRef} />
); );
if (schema.animations) {
component = (
<CSSTransition
in={animationIn}
timeout={this.animationTimeout}
classNames={this.animationClassNames}
onEntered={this.addAnimationAttention}
onExit={this.removeAnimationAttention}
appear
unmountOnExit
>
{component}
</CSSTransition>
);
}
return this.props.env.enableAMISDebug ? ( return this.props.env.enableAMISDebug ? (
<DebugWrapper renderer={renderer}>{component}</DebugWrapper> <DebugWrapper renderer={renderer}>{component}</DebugWrapper>
) : ( ) : (

View File

@ -0,0 +1,63 @@
import kebabCase from 'lodash/kebabCase';
interface Style {
[id: string]: {
[className: string]: {
[propName: string]: string | number;
};
};
}
class StyleManager {
styles: Style;
styleDom: HTMLStyleElement;
styleText: string;
constructor() {
this.styles = {};
this.styleDom = document.createElement('style');
this.styleDom.id = 'amis-styles';
document.head.appendChild(this.styleDom);
}
updateStyle(style: Style) {
Object.keys(style).forEach(className => {
if (!this.styles[className]) {
this.styles[className] = style[className];
} else {
this.styles[className] = {
...this.styles[className],
...style[className]
};
}
});
this.updateStyleDom();
}
removeStyles(id: string) {
delete this.styles[id];
this.updateStyleDom();
}
updateStyleDom() {
const styleText = Object.keys(this.styles)
.map(id => {
const style = this.styles[id];
return Object.keys(style)
.map(className => {
return `${className} {${Object.keys(style[className])
.map(propName => {
return `${kebabCase(propName)}: ${style[className][propName]};`;
})
.join('')}}`;
})
.join('');
})
.join('');
this.styleDom.innerHTML = styleText;
this.styleText = styleText;
}
}
export default new StyleManager();

View File

@ -36,6 +36,7 @@ import './polyfills';
import './renderers/builtin'; import './renderers/builtin';
import './renderers/register'; import './renderers/register';
export * from './utils/index'; export * from './utils/index';
export * from './utils/animations';
export * from './types'; export * from './types';
export * from './store'; export * from './store';
import * as utils from './utils/helper'; import * as utils from './utils/helper';
@ -123,6 +124,8 @@ import type {IItem} from './store/list';
import CustomStyle from './components/CustomStyle'; import CustomStyle from './components/CustomStyle';
import {StatusScoped} from './StatusScoped'; import {StatusScoped} from './StatusScoped';
import styleManager from './StyleManager';
// @ts-ignore // @ts-ignore
export const version = '__buildVersion'; export const version = '__buildVersion';
(window as any).amisVersionInfo = { (window as any).amisVersionInfo = {
@ -131,6 +134,7 @@ export const version = '__buildVersion';
}; };
export { export {
styleManager,
clearStoresCache, clearStoresCache,
updateEnv, updateEnv,
Renderer, Renderer,

View File

@ -3,6 +3,7 @@ import type {JSONSchema7} from 'json-schema';
import {ListenerAction} from './actions/Action'; import {ListenerAction} from './actions/Action';
import {debounceConfig, trackConfig} from './utils/renderer-event'; import {debounceConfig, trackConfig} from './utils/renderer-event';
import type {TestIdBuilder} from './utils/helper'; import type {TestIdBuilder} from './utils/helper';
import {AnimationsProps} from './utils/animations';
export interface Option { export interface Option {
/** /**
@ -307,6 +308,7 @@ export interface Schema {
static?: boolean; static?: boolean;
children?: JSX.Element | ((props: any, schema?: any) => JSX.Element) | null; children?: JSX.Element | ((props: any, schema?: any) => JSX.Element) | null;
definitions?: Definitions; definitions?: Definitions;
animations?: AnimationsProps;
[propName: string]: any; [propName: string]: any;
} }

View File

@ -0,0 +1,86 @@
import styleManager from '../StyleManager';
export interface AnimationsProps {
enter?: {
type: string;
duration?: number;
};
attention?: {
type: string;
duration?: number;
repeat?: string;
delay?: number;
};
exit?: {
type: string;
duration?: number;
};
}
function generateStyleByAnimation(
className: string[],
animation: {
name: string;
duration?: number;
iterationCount?: string;
delay?: number;
fillMode?: string;
timingFunction?: string;
}
) {
return {
[className.join(',')]: {
animationName: animation.name,
animationDuration: `${animation.duration || 1}s`,
animationIterationCount: animation.iterationCount || 1,
animationDelay: `${animation.delay || 0}s`,
animationTimingFunction: animation.timingFunction || 'ease',
animationFillMode: animation.fillMode || 'none'
}
};
}
export function createAnimationStyle(
id: string,
animationsConfig: AnimationsProps
) {
const enterAnimationConfig = animationsConfig.enter;
let enterStyle = {};
if (enterAnimationConfig?.type) {
enterStyle = generateStyleByAnimation(
[`.${enterAnimationConfig.type}-${id}-enter`],
{name: enterAnimationConfig.type, duration: enterAnimationConfig.duration}
);
}
const attentionAnimationConfig = animationsConfig.attention;
let attentionStyle = {};
if (attentionAnimationConfig?.type) {
attentionStyle = generateStyleByAnimation(
[`.${attentionAnimationConfig.type}-${id}-attention`],
{
name: attentionAnimationConfig.type,
duration: attentionAnimationConfig.duration,
iterationCount: attentionAnimationConfig.repeat || 'infinite',
delay: attentionAnimationConfig.delay
}
);
}
const exitAnimationConfig = animationsConfig.exit;
let exitStyle = {};
if (exitAnimationConfig?.type) {
exitStyle = generateStyleByAnimation(
[`.${exitAnimationConfig.type}-${id}-exit`],
{
name: exitAnimationConfig.type,
duration: exitAnimationConfig.duration,
fillMode: 'forwards'
}
);
}
styleManager.updateStyle({
[id]: Object.assign({}, enterStyle, attentionStyle, exitStyle)
});
}

View File

@ -2376,3 +2376,17 @@ export function supportsMjs() {
return false; return false;
} }
} }
export function formateId(id: string) {
// 将className非法字符替换为短横线
id = id.replace(/[^a-zA-Z0-9-]/g, '-');
// 将连续的-替换为单个-
id = id.replace(/-{2,}/g, '-');
// 去掉首尾的-
id = id.replace(/^-|-$/g, '');
// 首字母不能为数字
if (/^\d/.test(id)) {
id = 'amis-' + id;
}
return id;
}

View File

@ -790,7 +790,14 @@ export function filterSchemaForEditor(schema: any): any {
Object.keys(schema).forEach(key => { Object.keys(schema).forEach(key => {
const value = schema[key]; const value = schema[key];
if ( if (
~['visible', 'visibleOn', 'hidden', 'hiddenOn', 'toggled'].indexOf(key) ~[
'visible',
'visibleOn',
'hidden',
'hiddenOn',
'toggled',
'animations' // 编辑态也不能有动画
].indexOf(key)
) { ) {
key = `$$${key}`; key = `$$${key}`;
modified = true; modified = true;

View File

@ -1,6 +1,448 @@
import {setSchemaTpl, getSchemaTpl, defaultValue} from 'amis-editor-core'; import {setSchemaTpl, getSchemaTpl, defaultValue} from 'amis-editor-core';
import type {SchemaCollection} from 'amis'; import {createAnimationStyle, formateId, type SchemaCollection} from 'amis';
import kebabCase from 'lodash/kebabCase'; import kebabCase from 'lodash/kebabCase';
import {styleManager} from 'amis-core';
const animationOptions = {
enter: [
{
label: '淡入',
children: [
{
label: '淡入',
value: 'fadeIn'
},
{
value: 'fadeInDown',
label: '从上淡入'
},
{
value: 'fadeInDownBig',
label: '从上淡入(加强效果)'
},
{
value: 'fadeInLeft',
label: '从左淡入'
},
{
value: 'fadeInLeftBig',
label: '从左淡入(加强效果)'
},
{
value: 'fadeInRight',
label: '从右淡入'
},
{
value: 'fadeInRightBig',
label: '从右淡入(加强效果)'
},
{
value: 'fadeInUp',
label: '从下淡入'
},
{
value: 'fadeInUpBig',
label: '从下淡入(加强效果)'
}
]
},
{
label: '回弹',
children: [
{
value: 'backInDown',
label: '从上回弹进入'
},
{
value: 'backInLeft',
label: '从左回弹进入'
},
{
value: 'backInRight',
label: '从右回弹进入'
},
{
value: 'backInUp',
label: '从下回弹进入'
}
]
},
{
label: '旋转',
children: [
{
value: 'rotateIn',
label: '旋转进入'
},
{
value: 'rotateInDownLeft',
label: '左上角旋转进入'
},
{
value: 'rotateInDownRight',
label: '右上角旋转进入'
},
{
value: 'rotateInUpLeft',
label: '左下角旋转进入'
},
{
value: 'rotateInUpRight',
label: '右下角旋转进入'
}
]
},
{
label: '滑动',
children: [
{
value: 'slideInUp',
label: '从下滑入'
},
{
value: 'slideInDown',
label: '从上滑入'
},
{
value: 'slideInLeft',
label: '从左滑入'
},
{
value: 'slideInRight',
label: '从右滑入'
}
]
},
{
label: '翻页',
children: [
{
value: 'flip',
label: '翻页'
},
{
value: 'flipInY',
label: '水平翻页'
},
{
value: 'flipInX',
label: '垂直翻页'
}
]
},
{
label: '弹跳',
children: [
{
value: 'bounceIn',
label: '弹跳进入'
},
{
value: 'bounceInDown',
label: '从上弹跳进入'
},
{
value: 'bounceInLeft',
label: '从左弹跳进入'
},
{
value: 'bounceInRight',
label: '从右弹跳进入'
},
{
value: 'bounceInUp',
label: '从下弹跳进入'
}
]
},
{
label: '缩放',
children: [
{
value: 'zoomIn',
label: '缩放进入'
},
{
value: 'zoomInDown',
label: '从上缩放进入'
},
{
value: 'zoomInLeft',
label: '从左缩放进入'
},
{
value: 'zoomInRight',
label: '从右缩放进入'
},
{
value: 'zoomInUp',
label: '从下缩放进入'
}
]
},
{
label: '其他',
children: [
{
value: 'lightSpeedInLeft',
label: '从左光速进入'
},
{
value: 'lightSpeedInRight',
label: '从右光速进入'
},
{
value: 'rollIn',
label: '滚动进入'
}
]
}
],
attention: [
{
label: '弹跳',
value: 'bounce'
},
{
label: '闪烁',
value: 'flash'
},
{
value: 'headShake',
label: '摇头'
},
{
value: 'heartBeat',
label: '心跳'
},
{
value: 'jello',
label: '果冻'
},
{
label: '跳动',
value: 'pulse'
},
{
label: '摇摆',
value: 'swing'
},
{
label: '震动',
value: 'tada'
},
{
label: '晃动',
value: 'wobble'
},
{
label: '抖动',
value: 'shake'
},
{
value: 'shakeX',
label: '水平抖动'
},
{
value: 'shakeY',
label: '垂直抖动'
},
{
value: 'rubberBand',
label: '橡皮筋'
}
],
exit: [
{
label: '淡出',
children: [
{
label: '淡出',
value: 'fadeOut'
},
{
value: 'fadeOutDown',
label: '向下淡出'
},
{
value: 'fadeOutDownBig',
label: '向下淡出(加强效果)'
},
{
value: 'fadeOutLeft',
label: '向左淡出'
},
{
value: 'fadeOutLeftBig',
label: '向左淡出(加强效果)'
},
{
value: 'fadeOutRight',
label: '向右淡出'
},
{
value: 'fadeOutRightBig',
label: '向右淡出(加强效果)'
},
{
value: 'fadeOutUp',
label: '向上淡出'
},
{
value: 'fadeOutUpBig',
label: '向上淡出(加强效果)'
}
]
},
{
label: '回弹',
children: [
{
value: 'backOutDown',
label: '向下回弹退出'
},
{
value: 'backOutLeft',
label: '向左回弹退出'
},
{
value: 'backOutRight',
label: '向右回弹退出'
},
{
value: 'backOutUp',
label: '向上回弹退出'
}
]
},
{
label: '旋转',
children: [
{
value: 'rotateOut',
label: '旋转退出'
},
{
value: 'rotateOutDownLeft',
label: '左上角旋转退出'
},
{
value: 'rotateOutDownRight',
label: '右上角旋转退出'
},
{
value: 'rotateOutUpLeft',
label: '左下角旋转退出'
},
{
value: 'rotateOutUpRight',
label: '右下角旋转退出'
}
]
},
{
label: '滑动',
children: [
{
value: 'slideOutUp',
label: '向上滑入'
},
{
value: 'slideOutDown',
label: '向下滑入'
},
{
value: 'slideOutLeft',
label: '向左滑入'
},
{
value: 'slideOutRight',
label: '向右滑入'
}
]
},
{
label: '翻页',
children: [
{
value: 'flipOutY',
label: '水平翻页'
},
{
value: 'flipOutX',
label: '垂直翻页'
}
]
},
{
label: '弹跳',
children: [
{
value: 'bounceOut',
label: '弹跳退出'
},
{
value: 'bounceOutDown',
label: '向下弹跳退出'
},
{
value: 'bounceOutLeft',
label: '向左弹跳退出'
},
{
value: 'bounceOutRight',
label: '向右弹跳退出'
},
{
value: 'bounceOutUp',
label: '向上弹跳退出'
}
]
},
{
label: '缩放',
children: [
{
value: 'zoomOut',
label: '缩放退出'
},
{
value: 'zoomOutDown',
label: '向上缩放退出'
},
{
value: 'zoomOutLeft',
label: '向左缩放退出'
},
{
value: 'zoomOutRight',
label: '向右缩放退出'
},
{
value: 'zoomOutUp',
label: '向下缩放退出'
}
]
},
{
label: '其他',
children: [
{
value: 'lightSpeedOutLeft',
label: '向左光速退出'
},
{
value: 'lightSpeedOutRight',
label: '向右光速退出'
},
{
value: 'rollOut',
label: '滚动退出'
}
]
}
]
};
setSchemaTpl('style:formItem', ({renderer, schema}: any) => { setSchemaTpl('style:formItem', ({renderer, schema}: any) => {
return { return {
@ -809,7 +1251,8 @@ setSchemaTpl(
label: false label: false
} }
] ]
} },
getSchemaTpl('animation')
].filter(item => !~exclude.indexOf(item.key || '')); ].filter(item => !~exclude.indexOf(item.key || ''));
} }
); );
@ -845,3 +1288,118 @@ setSchemaTpl(
}; };
} }
); );
setSchemaTpl('animation', () => {
const animation = (
type: 'enter' | 'attention' | 'exit',
label: string,
schema: any = []
) => [
{
type: 'switch',
name: `animations.${type}`,
pipeIn: (value: boolean) => !!value,
pipeOut: (value: boolean) => {
if (value) {
return {};
}
return undefined;
},
label
},
{
type: 'container',
className: 'm-b ae-ExtendMore',
visibleOn: `animations.${type}`,
body: [
{
type: 'select',
name: `animations.${type}.type`,
selectMode: 'group',
options: animationOptions[type],
label: '类型',
selectFirst: true
},
{
type: 'input-number',
name: `animations.${type}.duration`,
label: '持续',
value: 1,
suffix: '秒',
min: 0,
precision: 3
},
...schema
]
},
{
type: 'button',
visibleOn: `animations.${type}`,
className: 'm-b',
block: true,
level: 'enhance',
size: 'sm',
label: '播放',
onClick: (e: any, {data}: any) => {
let doc = document;
const isMobile = (window as any).editorStore.isMobile;
if (isMobile) {
doc = (document.getElementsByClassName('ae-PreviewIFrame')[0] as any)
.contentDocument;
}
let {id, animations} = data;
const el = doc.querySelector(`[name="${id}"]`);
id = formateId(id);
const className = `${animations[type].type}-${id}-${type}`;
el?.classList.add(className);
createAnimationStyle(id, animations);
if (isMobile) {
let style = doc.getElementById('amis-styles');
if (!style) {
style = doc.createElement('style');
style.id = 'amis-styles';
doc.head.appendChild(style);
}
style.innerHTML = styleManager.styleText;
}
setTimeout(() => {
el?.classList.remove(className);
}, ((animations[type].duration || 1) + (animations[type].delay || 0)) * 1000);
}
}
];
return {
title: '动画',
body: [
...animation('enter', '进入动画'),
...animation('attention', '强调动画', [
{
label: '重复',
type: 'select',
name: 'animations.attention.repeat',
value: 'infinite',
options: [
...[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => ({
label: i,
value: i
})),
{label: '无限', value: 'infinite'}
]
},
{
label: '延迟',
type: 'input-number',
name: 'animations.attention.delay',
value: 0,
suffix: '秒',
precision: 3
}
]),
...animation('exit', '退出动画')
]
};
});

View File

@ -38,6 +38,7 @@
"@rc-component/mini-decimal": "^1.0.1", "@rc-component/mini-decimal": "^1.0.1",
"amis-core": "^6.8.0", "amis-core": "^6.8.0",
"amis-formula": "^6.8.0", "amis-formula": "^6.8.0",
"animate.css": "4.1.1",
"classnames": "2.3.2", "classnames": "2.3.2",
"codemirror": "^5.63.0", "codemirror": "^5.63.0",
"downshift": "6.1.12", "downshift": "6.1.12",

View File

@ -7,5 +7,6 @@
@import '../../../node_modules/video-react/dist/video-react'; @import '../../../node_modules/video-react/dist/video-react';
@import '../../../node_modules/cropperjs/dist/cropper'; @import '../../../node_modules/cropperjs/dist/cropper';
@import '../../../node_modules/office-viewer/dist/office'; @import '../../../node_modules/office-viewer/dist/office';
@import '../../../node_modules/animate.css/animate.min';
@import './components/react-datetime'; @import './components/react-datetime';