瀏覽代碼

移动端 事件/Scope

luoyifan 3 年之前
父節點
當前提交
9b8ac705ec
共有 9 個文件被更改,包括 1938 次插入21 次删除
  1. 123 6
      src/Scope.ts
  2. 1 0
      src/index.ts
  3. 64 14
      src/init.ts
  4. 39 0
      src/lib/fix.js
  5. 89 1
      src/lib/lib.ts
  6. 1196 0
      src/lib/systemLib.ts
  7. 21 0
      src/lib/systemLibEval.js
  8. 114 0
      src/message.ts
  9. 291 0
      src/wotu-ui.css

+ 123 - 6
src/Scope.ts

@@ -7,7 +7,9 @@ export class Scope {
      */
      */
     id = _.uniqueId('scope_')
     id = _.uniqueId('scope_')
 
 
-
+    /**
+     * 构造函数传入的最初始的 vjson
+     */
     originalVjson
     originalVjson
 
 
     /**
     /**
@@ -30,6 +32,17 @@ export class Scope {
      */
      */
     _handle
     _handle
 
 
+    /**
+     * 页面显示的时候带的参数 在设计刷新的时候使用
+     */
+    _vjsonOption
+    _dataOption
+
+    /**
+     * 最顶部的 scope 对象
+     */
+    topScope
+
     constructor({model, vjson}) {
     constructor({model, vjson}) {
         const that = this
         const that = this
         this.model = model
         this.model = model
@@ -64,6 +77,89 @@ export class Scope {
     }
     }
 
 
     /**
     /**
+     * 渲染模块到全屏
+     * @param vjsonOption 界面覆盖选项(可以为空)
+     * @param dataOption 数据覆盖选项(可以为空)
+     */
+    render(vjsonOption = {}, dataOption = {}) {
+        const that = this
+        this._vjsonOption = vjsonOption;
+        this._dataOption = dataOption;
+        const vmodel = _.defaultsDeep({
+            data: {}
+        }, that.model, dataOption)
+
+        this.viewModel = new Ext.app.ViewModel(vmodel);
+        this.viewModel.yvanScope = this
+
+        const config = _.defaultsDeep({
+                xtype: 'panel',
+                viewModel: this.viewModel,
+                yvanScope: this,
+                referenceHolder: true,
+
+                listeners: {
+                    renderedchange(sender, item, rendered, opts) {
+                        // 记录句柄
+                        if (rendered) {
+                            that._handle = this
+
+                            // 调用onLoad回调
+                            try {
+                                that.onLoad()
+                            } catch (e) {
+                                console.error('errorAt onLoad', e)
+                            }
+                        }
+
+                        // 如果vjson中配置了 afterrender ,需要恢复状态
+                        if (typeof that.vjson.listeners?.renderedchange === 'function') {
+                            that.vjson.listeners.renderedchange.apply(this, arguments)
+                        }
+
+                        this.fireEvent('afterrender', sender)
+                    },
+                },
+
+            },
+            vjsonOption,
+            that.vjson,
+            {
+                showAnimation: 'slide',
+                hideAnimation: 'slideOut',
+            }
+        )
+
+        that._handle = Ext.Viewport.add(config)
+        return that._handle
+    }
+
+    /**
+     * 对话框"保存"成功.
+     * 关闭对话框,并响应 success 方法
+     * @param data 要傳回的數據(可以為空)
+     */
+    dialogSuccess(data?) {
+        this._handle.fireEvent('success', this, data)
+        const sender = this._handle.config.animateTarget
+        const scope = lookupScope(sender)
+        if (typeof this['success'] === 'function') {
+            this['success'].call(scope, sender, data)
+        }
+        this.close()
+    }
+
+    /**
+     * 设置等待状态
+     * @param value
+     * @param msg
+     */
+    setLoading(value: boolean) {
+        const scope = this
+        scope._handle?.setLoading(value)
+    }
+
+    /**
      * 关闭对话框(或标签页)
      * 关闭对话框(或标签页)
      */
      */
     close() {
     close() {
@@ -71,12 +167,33 @@ export class Scope {
     }
     }
 
 
     /**
     /**
-     * 直接渲染到元素
-     * @param element 渲染目标
-     * @param vjsonOption 界面覆盖选项(可以为空)
-     * @param dataOption 数据覆盖选项(可以为空)
+     * 获取 viewModel 里包含的数据(只读)
+     */
+    get data() {
+        return this.viewModel.getData()
+    }
+
+    /**
+     * 设置 viewModel 中的数据
+     * 可以是 key, value 模式
+     * 也可以是 {key:value} 模式
      */
      */
-    renderTo(element, vjsonOption, dataOption) {
+    set(path, value) {
+        return this.viewModel.set(path, value)
+    }
 
 
+    /**
+     * 寻找模块内所有的 xtype 对应的对象
+     * @param xtypeKey
+     */
+    down(xtypeKey) {
+        return this._handle.down(xtypeKey)
+    }
+
+    /**
+     * 获取所有设置过 Reference 名称的组件
+     */
+    get refs() {
+        return this._handle.getReferences()
     }
     }
 }
 }

+ 1 - 0
src/index.ts

@@ -20,3 +20,4 @@ export * from './init'
 export * from './lib/ajax'
 export * from './lib/ajax'
 export * from './lib/lib'
 export * from './lib/lib'
 export * from './lib/config'
 export * from './lib/config'
+export * from './message'

+ 64 - 14
src/init.ts

@@ -1,16 +1,24 @@
 import _ from 'lodash'
 import _ from 'lodash'
 import {lookupFn, lookupScope} from "./lib/lib"
 import {lookupFn, lookupScope} from "./lib/lib"
 import initCols from './controls/cols'
 import initCols from './controls/cols'
+import * as SystemLib from './lib/systemLib'
+import './lib/fix'
 
 
 export function init() {
 export function init() {
+
     /**
     /**
-     * 改变事件的获取方式.
-     * 具体见: ext-all-debug.js:23216 addListener
-     * https://docs.sencha.com/extjs/6.6.0/classic/Ext.util.Observable.html#method-addListener
-     * https://docs.sencha.com/extjs/6.6.0/classic/src/Observable.js.html#Ext.mixin.Observable-method-addListener
+     * 改变事件的触发方式
      */
      */
-    const _doAddListener = Ext.mixin.Observable.prototype.doAddListener
-    Ext.mixin.Observable.prototype.doAddListener = function (ename, fn, scope, options, order, caller, manager) {
+    const _getFireInfo = Ext.util.Event.prototype.getFireInfo
+    Ext.util.Event.prototype.getFireInfo = function (listener, fromWrapped) {
+        var observable = this.observable,
+            fireFn = listener.fireFn,
+            scope = listener.scope,
+            namedScope = listener.namedScope,
+            fn, origin;
+
+        fn = fromWrapped ? listener.fn : fireFn;
+
         if (typeof fn === 'string' &&
         if (typeof fn === 'string' &&
             (_.startsWith(fn, 'scope.') ||
             (_.startsWith(fn, 'scope.') ||
                 _.startsWith(fn, 'system.') ||
                 _.startsWith(fn, 'system.') ||
@@ -20,19 +28,61 @@ export function init() {
             if (window["IS_DESIGN_MODE"]) {
             if (window["IS_DESIGN_MODE"]) {
                 fn = Ext.emptyFn
                 fn = Ext.emptyFn
             } else {
             } else {
-                // console.log('doAddListener', ename, fn, scope, options, order, caller, manager)
-                // const vm = this.lookupViewModel()
-                // if (vm && vm.yvanScope) {
-                //     scope = vm.yvanScope
-                //     fn = scope[fn.substr('scope.'.length)]
-                // }
-                scope = lookupScope(this)
+                scope = lookupScope(observable)
                 fn = lookupFn(scope, fn)
                 fn = lookupFn(scope, fn)
             }
             }
+
+            return {scope, fn}
         }
         }
 
 
-        _doAddListener.call(this, ename, fn, scope, options, order, caller, manager)
+        return _getFireInfo.call(this, listener, fromWrapped)
+    }
+    // const _addListener = Ext.mixin.Observable.prototype.addListener
+    // Ext.mixin.Observable.prototype.addListener = function (eventName, fn, scope, options, order, caller) {
+    //     console.log('addListener', eventName, fn, scope, options, order, caller)
+    //     // const scope = lookupScope(this)
+    //     // const fn = lookupFn(scope, fn)
+    //
+    //     return _addListener.call(this, eventName, fn, scope, options, order, caller)
+    // }
+
+    /**
+     * 改变事件的获取方式.
+     * 具体见: ext-all-debug.js:23216 addListener
+     * https://docs.sencha.com/extjs/6.6.0/classic/Ext.util.Observable.html#method-addListener
+     * https://docs.sencha.com/extjs/6.6.0/classic/src/Observable.js.html#Ext.mixin.Observable-method-addListener
+     */
+    // const _doAddListener = Ext.mixin.Observable.prototype.doAddListener
+    // Ext.mixin.Observable.prototype.doAddListener = function (ename, fn, scope, options, order, caller, manager) {
+    //     console.log('doAddListener', ename, fn, scope)
+    //     if (typeof fn === 'string' &&
+    //         (_.startsWith(fn, 'scope.') ||
+    //             _.startsWith(fn, 'system.') ||
+    //             _.startsWith(fn, 'format.')
+    //         )) {
+    //
+    //         if (window["IS_DESIGN_MODE"]) {
+    //             fn = Ext.emptyFn
+    //         } else {
+    //             // console.log('doAddListener', ename, fn, scope, options, order, caller, manager)
+    //             // const vm = this.lookupViewModel()
+    //             // if (vm && vm.yvanScope) {
+    //             //     scope = vm.yvanScope
+    //             //     fn = scope[fn.substr('scope.'.length)]
+    //             // }
+    //             scope = lookupScope(this)
+    //             fn = lookupFn(scope, fn)
+    //         }
+    //     }
+    //
+    //     _doAddListener.call(this, ename, fn, scope, options, order, caller, manager)
+    // }
+
+    // 将 SystemLib 扩展到 window.system 下
+    if (!window['system']) {
+        window['system'] = {}
     }
     }
+    _.extend(window['system'], SystemLib)
 
 
     initCols()
     initCols()
 }
 }

+ 39 - 0
src/lib/fix.js

@@ -0,0 +1,39 @@
+import _ from 'lodash'
+import {baseConfigProcess} from "./config";
+import {lookupFn} from "./lib";
+
+class FixClass {
+
+    @baseConfigProcess()
+    fix(me, config) {
+        const {fix} = config
+        if (!fix) {
+            return
+        }
+
+        delete config.fix
+
+        // 这里有个 bug
+        // 但凡是调用了 this.lookupViewModel() 就会出现绑定不了的情况
+        // const scope = config.$initParent.lookupReferenceHolder().yvanScope //lookupScope(me)
+        let scope
+        if (config.$initParent) {
+            // 在面板上的组件
+            scope = config.$initParent.lookupReferenceHolder().yvanScope //lookupScope(me)
+        } else if (config.column) {
+            // 在列上的组件
+            scope = config.column.lookupReferenceHolder().yvanScope //lookupScope(me)
+        }
+
+        if (_.isArray(fix)) {
+            _.each(fix, (f) => {
+                const fn = lookupFn(scope, f)
+                fn.call(this, me, config)
+            })
+
+        } else if (_.isString(fix)) {
+            const fn = lookupFn(scope, fix)
+            fn.call(this, me, config)
+        }
+    }
+}

+ 89 - 1
src/lib/lib.ts

@@ -1,5 +1,93 @@
+import _ from 'lodash'
+import {FunctionRegiste, LibParamType} from "../types"
 import {Scope} from "../Scope"
 import {Scope} from "../Scope"
 
 
+export function getRegList(): FunctionRegiste[] {
+    let regList = _.get(window, 'yvanLib.regList')
+    if (!regList) {
+        regList = []
+        _.set(window, 'yvanLib.regList', regList)
+    }
+    return regList
+}
+
+export function getRegParamList(methodName: string) {
+    let regParamList = _.get(window, 'yvanLib.regParamList')
+    if (!regParamList) {
+        regParamList = {}
+        _.set(window, 'yvanLib.regParamList', regParamList)
+    }
+
+    if (!_.has(regParamList, methodName)) {
+        regParamList[methodName] = []
+    }
+
+    return regParamList[methodName]
+}
+
+/**
+ * 模拟点击按钮
+ */
+export function raiseClick(buttonHandle) {
+    if (!buttonHandle) {
+        return false
+    }
+    if (buttonHandle.disabled) {
+        // 按钮是禁止状态
+        return false
+    }
+    if (buttonHandle.hidden) {
+        // 按钮是隐藏状态
+        return false
+    }
+
+    // 按钮点击
+    buttonHandle.click()
+    return true
+}
+
+/**
+ * 标注系统全局函数的参数
+ * @param title 函数名称
+ * @param type 函数类型
+ * @param allowEmpty 可否为空
+ */
+export function LibParam(title: string, type: LibParamType, allowEmpty = false) {
+    return function (target: any, methodName: any, paramsIndex: any) {
+        const systemFnArgs = getRegParamList(methodName)
+        systemFnArgs[paramsIndex] = {
+            type,
+            title,
+            name: methodName,
+            allowEmpty,
+        }
+    }
+}
+
+/**
+ * 标注函数变成"系统全局函数"
+ */
+export function Lib(registe: FunctionRegiste) {
+    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
+        const libList = getRegList()
+        libList.push({
+            ...registe,
+            name: propertyKey,
+            target: target[propertyKey]
+        })
+
+        if (registe.type === 'system') {
+            _.set(window, 'yvanLib.system.' + propertyKey, target[propertyKey])
+
+        } else if (registe.type === 'format') {
+            _.set(window, 'yvanLib.format.' + propertyKey, target[propertyKey])
+
+        }
+
+        return target
+    }
+}
+
 /**
 /**
  * 解析事件
  * 解析事件
  *   'scope.私有方法名'
  *   'scope.私有方法名'
@@ -64,4 +152,4 @@ export function lookupScope(extHandle: any): Scope {
     }
     }
     const vm = extHandle.lookupViewModel()
     const vm = extHandle.lookupViewModel()
     return vm.yvanScope
     return vm.yvanScope
-}
+}

文件差異過大導致無法顯示
+ 1196 - 0
src/lib/systemLib.ts


+ 21 - 0
src/lib/systemLibEval.js

@@ -0,0 +1,21 @@
+import _ from "lodash";
+
+/**
+ * 用于计算 express 表达式
+ */
+export function evalFunction(data, express) {
+    const keys = []
+    const values = []
+    _.forOwn(data, (value, key) => {
+        keys.push(key)
+        values.push(value)
+    })
+
+    const func = Function(...keys, 'return ' + express)
+
+    try {
+        return func(...values)
+    } catch (e) {
+        return
+    }
+}

+ 114 - 0
src/message.ts

@@ -0,0 +1,114 @@
+import $ from 'jquery'
+import _ from 'lodash'
+
+export function prompt(message, multiLine = false, value = '') {
+    return new Promise((resolve, reject) => {
+        Ext.Msg.show({
+            title: message,
+            message: null,
+            buttons: Ext.MessageBox.OKCANCEL,
+            prompt: true,
+            defaultFocus: 'textfield',
+            multiLine: multiLine,
+            showAnimation: false,
+            hideAnimation: false,
+            value: value,
+            fn(okcancel, value) {
+                if (okcancel === 'ok') {
+                    resolve(value)
+                }
+            }
+        });
+    })
+}
+
+export function confirm(message) {
+    return new Promise((resolve, reject) => {
+        return Ext.Msg.show({
+            title: '',
+            message: message || null,
+            buttons: Ext.MessageBox.YESNO,
+            defaultFocus: '#yes',
+            prompt: false,
+            showAnimation: false,
+            hideAnimation: false,
+            fn(yesno) {
+                if (yesno === 'yes') {
+                    resolve()
+                }
+            }
+        });
+    })
+}
+
+export function showInfoDialog(msg, fn?) {
+    console.error(msg)
+    Ext.Msg.show({
+        title: '提示',
+        message: msg,
+        showAnimation: false,
+        hideAnimation: false,
+        defaultFocus: '#ok',
+        prompt: false,
+        fn,
+    });
+}
+
+/**
+ * 显示错误异常信息
+ * @param msg 错误内容
+ * @param fn 确定之后的回调
+ */
+export function showErrorDialog(msg, fn?) {
+    console.error(msg)
+    Ext.Msg.show({
+        title: '错误',
+        message: msg,
+        showAnimation: false,
+        hideAnimation: false,
+        defaultFocus: '#ok',
+        prompt: false,
+        fn,
+    });
+}
+
+export function msgError(msg, fn?) {
+    showErrorDialog(msg, fn)
+}
+
+/**
+ * 中间灰底白字提示
+ */
+export function msg(message: string): void {
+    console.log(message)
+
+    const $body = $('body')
+
+    $body.find('[xtype=tooltip]').remove()
+    const $w = $(
+        '<div xtype="tooltip" class="yvan-msg yvan-anim yvan-anim-00">' +
+        '  <div class="yvan-msg-content">' +
+        _.escape(message) +
+        '</div></div>'
+    )
+    $body.append($w)
+
+    const iframeWidth = $w.parent().width() as number
+    const iframeHeight = $w.parent().height() as number
+
+    const windowWidth = $w.width() as number
+    const windowHeight = $w.height() as number
+
+    let setWidth = (iframeWidth - windowWidth) / 2
+    let setHeight = (iframeHeight - windowHeight) / 2
+    if (iframeHeight < windowHeight || setHeight < 0) {
+        setHeight = 0
+    }
+    if (iframeWidth < windowWidth || setWidth < 0) {
+        setWidth = 0
+    }
+    $w.css({left: setWidth, top: setHeight})
+    setTimeout(() => {
+        $w.remove()
+    }, 3000)
+}

+ 291 - 0
src/wotu-ui.css

@@ -0,0 +1,291 @@
+/*== layer提示框==*/
+.yvan-msg {
+    min-width: 100px;
+    background-color: rgba(0, 0, 0, .6);
+    color: #fff;
+    border: none;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+    margin: 0;
+    padding: 0;
+    border-radius: 2px;
+    position: fixed;
+    pointer-events: auto;
+    z-index: 99999999999;
+}
+
+.yvan-msg-content {
+    pointer-events: auto;
+    color: #fff;
+    padding: 12px 25px;
+    text-align: center;
+    position: relative;
+    line-height: 24px;
+    word-break: break-all;
+    overflow: hidden;
+    font-size: 14px;
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+/*== 动画 ==*/
+.yvan-anim {
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+    -webkit-animation-duration: 0.3s;
+    animation-duration: 0.3s;
+}
+
+@-webkit-keyframes yvan-bounceIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(0.5);
+        transform: scale(0.5);
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: scale(1);
+        transform: scale(1);
+    }
+}
+
+@keyframes yvan-bounceIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(0.5);
+        transform: scale(0.5);
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: scale(1);
+        transform: scale(1);
+    }
+}
+
+.yvan-anim-00 {
+    -webkit-animation-name: yvan-bounceIn;
+    animation-name: yvan-bounceIn;
+}
+
+@-webkit-keyframes yvan-zoomInDown {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(0.1) translatey(-2000px);
+        transform: scale(0.1) translatey(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out;
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(0.475) translatey(60px);
+        transform: scale(0.475) translatey(60px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out;
+    }
+}
+
+@keyframes yvan-zoomInDown {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(0.1) translatey(-2000px);
+        transform: scale(0.1) translatey(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out;
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(0.475) translatey(60px);
+        transform: scale(0.475) translatey(60px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out;
+    }
+}
+
+.yvan-anim-01 {
+    -webkit-animation-name: yvan-zoomInDown;
+    animation-name: yvan-zoomInDown;
+}
+
+@-webkit-keyframes yvan-fadeInUpBig {
+    0% {
+        opacity: 0;
+        -webkit-transform: translatey(2000px);
+        transform: translatey(2000px);
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translatey(0);
+        transform: translatey(0);
+    }
+}
+
+@keyframes yvan-fadeInUpBig {
+    0% {
+        opacity: 0;
+        -webkit-transform: translatey(2000px);
+        transform: translatey(2000px);
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translatey(0);
+        transform: translatey(0);
+    }
+}
+
+.yvan-anim-02 {
+    -webkit-animation-name: yvan-fadeInUpBig;
+    animation-name: yvan-fadeInUpBig;
+}
+
+@-webkit-keyframes yvan-zoomInLeft {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(0.1) translatex(-2000px);
+        transform: scale(0.1) translatex(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out;
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(0.475) translatex(48px);
+        transform: scale(0.475) translatex(48px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out;
+    }
+}
+
+@keyframes yvan-zoomInLeft {
+    0% {
+        opacity: 0;
+        -webkit-transform: scale(0.1) translatex(-2000px);
+        transform: scale(0.1) translatex(-2000px);
+        -webkit-animation-timing-function: ease-in-out;
+        animation-timing-function: ease-in-out;
+    }
+    60% {
+        opacity: 1;
+        -webkit-transform: scale(0.475) translatex(48px);
+        transform: scale(0.475) translatex(48px);
+        -webkit-animation-timing-function: ease-out;
+        animation-timing-function: ease-out;
+    }
+}
+
+.yvan-anim-03 {
+    -webkit-animation-name: yvan-zoomInLeft;
+    animation-name: yvan-zoomInLeft;
+}
+
+@-webkit-keyframes yvan-rollIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: translatex(-100%) rotate(-120deg);
+        transform: translatex(-100%) rotate(-120deg);
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translatex(0) rotate(0);
+        transform: translatex(0) rotate(0);
+    }
+}
+
+@keyframes yvan-rollIn {
+    0% {
+        opacity: 0;
+        -webkit-transform: translatex(-100%) rotate(-120deg);
+        transform: translatex(-100%) rotate(-120deg);
+    }
+    100% {
+        opacity: 1;
+        -webkit-transform: translatex(0) rotate(0);
+        transform: translatex(0) rotate(0);
+    }
+}
+
+.yvan-anim-04 {
+    -webkit-animation-name: yvan-rollIn;
+    animation-name: yvan-rollIn;
+}
+
+@-webkit-keyframes yvan-fadeIn {
+    0% {
+        opacity: 0;
+    }
+    100% {
+        opacity: 1;
+    }
+}
+
+@keyframes yvan-fadeIn {
+    0% {
+        opacity: 0;
+    }
+    100% {
+        opacity: 1;
+    }
+}
+
+.yvan-anim-05 {
+    -webkit-animation-name: yvan-fadeIn;
+    animation-name: yvan-fadeIn;
+}
+
+@-webkit-keyframes yvan-shake {
+    0%,
+    100% {
+        -webkit-transform: translatex(0);
+        transform: translatex(0);
+    }
+    10%,
+    30%,
+    50%,
+    70%,
+    90% {
+        -webkit-transform: translatex(-10px);
+        transform: translatex(-10px);
+    }
+    20%,
+    40%,
+    60%,
+    80% {
+        -webkit-transform: translatex(10px);
+        transform: translatex(10px);
+    }
+}
+
+@keyframes yvan-shake {
+    0%,
+    100% {
+        -webkit-transform: translatex(0);
+        transform: translatex(0);
+    }
+    10%,
+    30%,
+    50%,
+    70%,
+    90% {
+        -webkit-transform: translatex(-10px);
+        transform: translatex(-10px);
+    }
+    20%,
+    40%,
+    60%,
+    80% {
+        -webkit-transform: translatex(10px);
+        transform: translatex(10px);
+    }
+}
+
+.yvan-anim-06 {
+    -webkit-animation-name: yvan-shake;
+    animation-name: yvan-shake;
+}
+
+@-webkit-keyframes fadeIn {
+    0% {
+        opacity: 0;
+    }
+    100% {
+        opacity: 1;
+    }
+}