改变video组件数据

改变video组件数据

<template>
  <div class="video-player">
    <video class="video-js vjs-custom-skin" :class="{ 'live': options.live }"></video>
  </div>
</template>

<script>
//必需先安装videojs，-- :  $ npm install --S video.js
var videojs = require('video.js')

var languages = require('./languages.js')

export default {
  name: 'video-player',
  data: function () {
    return {
      customEventName: 'player-state-changed'
    }
  },
  props: {
    options: {
      type: Object,
      required: true,
      youtube: false,
      vimeo: false,
      switcher: true,
      hls: true
    },
  },

  created: function () {
    // Vue2.0 $on监听父组件命令
    if (this.$parent) {
      var _this = this
      this.$parent.$on('playerAction', function (action, options){
        _this.doAction(action, options);
      })
    }
  },
  ready: function() {
    if (this.options) { this.initialize() }
  },
  mounted: function() {
    if (this.options) { this.initialize() }
  },
  beforeDestroy: function() {
    this.dispose()
  },
  methods: {

    // 构建播放器
    initialize: function() {

      // console.log('init build player')
      // init
      var options = this.options

      if (options.hls) require('videojs-contrib-hls/dist/videojs-contrib-hls.js')
      if (options.youtube) require('videojs-youtube')
      if (options.vimeo) require('videojs-vimeo')
      if (options.switcher) require('videojs-resolution-switcher')

      // start_time
      options.start = options.start || 0
      // is_live?
      options.live  = options.live || false
      // player_src
      options.source  = options.source || false
      // playbackRates [0.7, 1.0, 1.5, 2.0]
      options.playbackRates = options.playbackRates || false
      // player defaultSrcReId
      options.defaultSrcReId = options.defaultSrcReId || 1
      // default muted
      options.muted = options.muted || false
      // playsinline
      options.playsinline = options.playsinline !== undefined ? options.playsinline : true;

      // 自定义事件名称
      var customEventName = options.customEventName || this.customEventName

      if (typeof options.source !== 'object') {
        this.dispose()
        return console.error('video resource must be a object or array')
      } else {
        if (options.source instanceof Array) {
          for (var i = 0, length = options.source.length; i < length; i++) {
            var item = options.source[i];
            if (!item.src) {
              this.dispose()
              return console.warn('video resource is illegitimate')
            }
          }
        } else {
          if (!options.source.src) {
            this.dispose()
            return console.warn('video resource is illegitimate')
          }
        }
      }

      // controlBar config 控制条的dom结构控制
      var controlBar = {
        remainingTimeDisplay: false,
        playToggle: {},
        progressControl: {},
        fullscreenToggle: {},
        volumeMenuButton: {
          inline: false,
          vertical: true
        }
      }

      // 直播
      if (options.live) {
        controlBar.timeDivider = false
        controlBar.durationDisplay = false
        controlBar.currentTimeDisplay = false
      }

      // build player config
      var video_options = {
        'controls': options.controls !== undefined ? options.controls : true,
        'autoplay': options.autoplay !== undefined ? options.autoplay : true,
        'preload': options.preload || 'auto',
        'poster': options.poster ||  '',
        'width': options.width || '100%',
        'height': options.height || 360,
        'fluid': options.fluid || false,
        'controlBar': options.controlBar || controlBar,
        'language': options.language || 'en',
        'techOrder': options.techOrder || ['html5', 'flash'],
        'flash': { hls: { withCredentials: false }},
        'html5': { hls: { withCredentials: false }},
        'youtube': { "ytControls": options.ytControls ? Number(options.ytControls) : 0 }
      }

      // 添加指定语言
      var language = video_options.language
      videojs.addLanguage(language, languages[language])

      // 是否应用IOS下的禁止自动全屏
      var playsinline = options.playsinline
      playsinline && (this.$el.children[0].setAttribute('playsinline', playsinline),this.$el.children[0].setAttribute('webkit-playsinline', playsinline))

      // 是否适用youtube
      // if (video_options.techOrder.indexOf('youtube') > -1) require('videojs-youtube')

      // 非直播情况
      if (!options.live) {

        // 单独播放资源
        if (!!options.source.src) {
          video_options.sources = [options.source]

        // 多播放源切换
        } else {
          video_options.plugins = { videoJsResolutionSwitcher: { default: options.defaultSrcReId, dynamicLabel: true }}
        }

        // 是否使用播放速度控制
        var playbackRates = options.playbackRates
        if (!!playbackRates && !!playbackRates.length) video_options.playbackRates = playbackRates
      }

      // 实例化播放器
      var _this = this
      this.player = null
      this.player = videojs(this.$el.children[0], video_options, function() {

        // 是否应用多版本切换清晰度
        if (!options.live) {
          if (!!options.source.length) {
            this.updateSrc(options.source)
            this.on('resolutionchange', function(){
              _this.$emit && _this.$emit(customEventName, { resolutionchange: this.src() })
              _this.$dispatch && _this.$dispatch(customEventName, { resolutionchange: this.src() })
            })
          }
        }

        if (options.live) {

          // console.log('live video', this, options.source)
          this.src(options.source)

          // var hls = this.tech({ IWillNotUseThisInPlugins: true }).hls
          // 直播每次的切片请求
          /*
          this.tech_.hls.xhr.beforeRequest = function(options) {
            console.log(options)
            return options
          }
          */
        }

        // 监听播放
        this.on('play', function() {
          _this.$emit && _this.$emit(customEventName, { play: true })
          _this.$dispatch && _this.$dispatch(customEventName, { play: true })
        })

        // 监听暂停
        this.on('pause', function() {
          _this.$emit && _this.$emit(customEventName, { pause: true })
          _this.$dispatch && _this.$dispatch(customEventName, { pause: true })
        })

        // 监听结束
        this.on('ended', function() {
          _this.$emit && _this.$emit(customEventName, { ended: true })
          _this.$dispatch && _this.$dispatch(customEventName, { ended: true })
        })

        // 元文件信息
        this.on('loadeddata', function() {
          if (!options.live && !!options.start) this.currentTime(options.start)
          this.muted(_this.options.muted)
          _this.$emit && _this.$emit(customEventName, { loadeddata: true })
          _this.$dispatch && _this.$dispatch(customEventName, { loadeddata: true })
        })

        this.on('waiting', function() {
          _this.$emit && _this.$emit(customEventName, { waiting: true })
          _this.$dispatch && _this.$dispatch(customEventName, { waiting: true })
        })

        this.on('playing', function() {
          _this.$emit && _this.$emit(customEventName, { playing: true })
          _this.$dispatch && _this.$dispatch(customEventName, { playing: true })
        })

        // 监听时间
        this.on('timeupdate', function() {
          _this.$emit && _this.$emit(customEventName, { currentTime: this.currentTime() })
          _this.$dispatch && _this.$dispatch(customEventName, { currentTime: this.currentTime() })
        })
      })
    },
    // 释放播放器
    dispose: function() {
      if (!!this.player && !!videojs) {
        // this.player.dispose()
        this.player.pause && this.player.pause()
        videojs(this.$el).dispose()
        delete this.player
      }
    },
    // 操作播放器
    doAction: function(action, options) {
      // console.log(action)
      if (!this.player) return
      if (action == 'sliderDrag') this.player.currentTime(options.currentTime);
      if (action == 'play') this.player.play()
      if (action == 'pause') this.player.pause()
      if (action == 'refresh') {
        if (!this.options.live) {
          this.player.currentTime(0)
          this.player.play()
        }
      }
    }
  },
  // Vue1.X监听父组件传播下来的事件
  events: {
    'playerAction': function(action) {
      this.doAction(action)
    }
  },
  watch: {
    // 观察选项的动态变化，选项变化了就重新初始化播放器
    options: {
      handler: function (newVal, oldVal) {
        var options = newVal
        if (typeof options.source !== 'object') {
          this.dispose()
          return console.error('video resource must be a object or array')
        } else {
          if (options.source instanceof Array) {
            for (var i = 0, length = options.source.length; i < length; i++) {
              var item = options.source[i]
              if (!item.src) {
                this.dispose()
                return console.warn('video resource is illegitimate')
              }
            }
          } else {
            if (!options.source.src) {
              this.dispose()
              return console.warn('video resource is illegitimate')
            }
          }
        }
        if (this.player) this.player.src(this.options.source)
        if (!this.player) this.initialize()
      },
      deep: true
    }
  }
}
</script>

<style src="video.js/dist/video-js.css"></style>

<style lang="css">
.video-js.vjs-custom-skin {
  width: 100%;
  font-family: "PingFang SC","Helvetica Neue","Hiragino Sans GB","Segoe UI","Microsoft YaHei","微软雅黑",sans-serif;
}

.video-js.vjs-custom-skin .vjs-menu-button-inline.vjs-slider-active,.video-js.vjs-custom-skin .vjs-menu-button-inline:focus,.video-js.vjs-custom-skin .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline {
  width: 10em
}

.video-js.vjs-custom-skin .vjs-controls-disabled .vjs-big-play-button {
  display: none!important
}

.video-js.vjs-custom-skin .vjs-control {
  width: 3em
}

.video-js.vjs-custom-skin .vjs-control.vjs-live-control{
  width: auto;
  padding-left: .5em;
  letter-spacing: .1em;
}

.video-js.vjs-custom-skin .vjs-menu-button-inline:before {
  width: 1.5em
}

.vjs-menu-button-inline .vjs-menu {
  left: 3em
}

.vjs-paused.vjs-has-started.video-js.vjs-custom-skin .vjs-big-play-button,.video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button {
  display: block
}

.video-js.vjs-custom-skin .vjs-load-progress div,.vjs-seeking .vjs-big-play-button,.vjs-waiting .vjs-big-play-button {
  display: none!important
}

.video-js.vjs-custom-skin .vjs-mouse-display:after,.video-js.vjs-custom-skin .vjs-play-progress:after {
  padding: 0 .4em .3em
}

.video-js.vjs-ended .vjs-loading-spinner {
  display: none;
}

.video-js.vjs-ended .vjs-big-play-button {
  display: block !important;
}

.video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button,.vjs-paused.vjs-has-started.video-js.vjs-custom-skin .vjs-big-play-button {
  display: block
}

.video-js.vjs-custom-skin .vjs-big-play-button {
  top: 50%;
  left: 50%;
  margin-left: -1.5em;
  margin-top: -1em
}

.video-js.vjs-custom-skin .vjs-big-play-button {
  background-color: rgba(0,0,0,0.45);
  font-size: 3.5em;
   /*border-radius: 50%;*/
  height: 2em !important;
  line-height: 2em !important;
  margin-top: -1em !important
}

.video-js:hover .vjs-big-play-button,.video-js.vjs-custom-skin .vjs-big-play-button:focus,.video-js.vjs-custom-skin .vjs-big-play-button:active {
  background-color: rgba(36,131,213,0.9)
}

.video-js.vjs-custom-skin .vjs-loading-spinner {
  border-color: rgba(36,131,213,0.8)
}

.video-js.vjs-custom-skin .vjs-control-bar2 {
  background-color: #000000
}

.video-js.vjs-custom-skin .vjs-control-bar {
   /*background-color: rgba(0,0,0,0.3) !important;*/
  color: #ffffff;
  font-size: 14px
}

.video-js.vjs-custom-skin .vjs-play-progress,.video-js.vjs-custom-skin  .vjs-volume-level {
  background-color: #2483d5
}

.video-js.vjs-custom-skin .vjs-play-progress:before {
  top: -0.3em;
}

.video-js.vjs-custom-skin .vjs-progress-control:hover .vjs-progress-holder {
  font-size: 1.3em;
}

.vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu {
  left: 0em;
}

.video-js.vjs-custom-skin .vjs-menu li {
  padding: 0;
  line-height: 2em;
  font-size: 1.1em;
  font-family: "PingFang SC","Helvetica Neue","Hiragino Sans GB","Segoe UI","Microsoft YaHei","微软雅黑",sans-serif;
}

.video-js.vjs-custom-skin .vjs-time-tooltip,
.video-js.vjs-custom-skin .vjs-mouse-display:after,
.video-js.vjs-custom-skin .vjs-play-progress:after {
  border-radius: 0;
  font-size: 1em;
  padding: 0;
  width: 3em;
  height: 1.5em;
  line-height: 1.5em;
  top: -3em;
}

.video-js.vjs-custom-skin .vjs-menu-button-popup .vjs-menu {
  width: 5em;
  left: -1em;
}

.video-js.vjs-custom-skin .vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu {
  left: 0;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-resolution-button .vjs-menu {
   /*order: 4;*/
}

 /*排序顺序*/
.video-js.vjs-custom-skin .vjs-control-bar .vjs-play-control {
  order: 0;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-time-control {
  min-width: 1em;
  padding: 0;
  margin: 0 .1em;
  text-align: center;
  display: block;
  order: 1;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-playback-rate .vjs-playback-rate-value{
  font-size: 1.2em;
  line-height: 2.4;
}

.video-js.vjs-custom-skin .vjs-progress-control.vjs-control {
  order: 2;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-volume-menu-button {
  order: 3;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-resolution-button {
  order: 4;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-resolution-button .vjs-resolution-button-label {
  display: block;
  line-height: 3em;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-playback-rate {
  order: 5;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-fullscreen-control {
  order: 6;
}

video::-webkit-media-controls {
  display:none !important;
}
video::-webkit-media-controls-enclosure {
  display: none !important;
}
</style>

<template>
  <div class="video-player">
    <video class="video-js vjs-custom-skin" :class="{ 'live': options.live }"></video>
  </div>
</template>

<script>
//必需先安装videojs，-- :  $ npm install --S video.js
var videojs = require('video.js')

var languages = require('./languages.js')

export default {
  name: 'video-player',
  data: function () {
    return {
      customEventName: 'player-state-changed'
    }
  },
  props: {
    configs: {
      type: Object,
      default: function () {
        return {
          youtube: false,
          vimeo: false,
          switcher: true,
          hls: true
        }
      }
    },
    options: {
      type: Object,
      required: true
    },
  },

  created: function () {
    // Vue2.0 $on监听父组件命令
    if (this.$parent) {
      var _this = this
      this.$parent.$on('playerAction', function (action, options){
        _this.doAction(action, options);
      })
    }
  },
  ready: function() {
    if (this.options) { this.initialize() }
  },
  mounted: function() {
    if (this.options) { this.initialize() }
  },
  beforeDestroy: function() {
    this.dispose()
  },
  methods: {

    // 构建播放器
    initialize: function() {

      // console.log('init build player')
      // init
      var options = this.options

      if (options.hls) require('videojs-contrib-hls/dist/videojs-contrib-hls.js')
      if (options.youtube) require('videojs-youtube')
      if (options.vimeo) require('videojs-vimeo')
      if (options.switcher) require('videojs-resolution-switcher')

      // start_time
      options.start = options.start || 0
      // is_live?
      options.live  = options.live || false
      // player_src
      options.source  = options.source || false
      // playbackRates [0.7, 1.0, 1.5, 2.0]
      options.playbackRates = options.playbackRates || false
      // player defaultSrcReId
      options.defaultSrcReId = options.defaultSrcReId || 1
      // default muted
      options.muted = options.muted || false
      // playsinline
      options.playsinline = options.playsinline !== undefined ? options.playsinline : true;

      // 自定义事件名称
      var customEventName = options.customEventName || this.customEventName

      if (typeof options.source !== 'object') {
        this.dispose()
        return console.error('video resource must be a object or array')
      } else {
        if (options.source instanceof Array) {
          for (var i = 0, length = options.source.length; i < length; i++) {
            var item = options.source[i];
            if (!item.src) {
              this.dispose()
              return console.warn('video resource is illegitimate')
            }
          }
        } else {
          if (!options.source.src) {
            this.dispose()
            return console.warn('video resource is illegitimate')
          }
        }
      }

      // controlBar config 控制条的dom结构控制
      var controlBar = {
        remainingTimeDisplay: false,
        playToggle: {},
        progressControl: {},
        fullscreenToggle: {},
        volumeMenuButton: {
          inline: false,
          vertical: true
        }
      }

      // 直播
      if (options.live) {
        controlBar.timeDivider = false
        controlBar.durationDisplay = false
        controlBar.currentTimeDisplay = false
      }

      // build player config
      var video_options = {
        'controls': options.controls !== undefined ? options.controls : true,
        'autoplay': options.autoplay !== undefined ? options.autoplay : true,
        'preload': options.preload || 'auto',
        'poster': options.poster ||  '',
        'width': options.width || '100%',
        'height': options.height || 360,
        'fluid': options.fluid || false,
        'controlBar': options.controlBar || controlBar,
        'language': options.language || 'en',
        'techOrder': options.techOrder || ['html5', 'flash'],
        'flash': { hls: { withCredentials: false }},
        'html5': { hls: { withCredentials: false }},
        'youtube': { "ytControls": options.ytControls ? Number(options.ytControls) : 0 }
      }

      // 添加指定语言
      var language = video_options.language
      videojs.addLanguage(language, languages[language])

      // 是否应用IOS下的禁止自动全屏
      var playsinline = options.playsinline
      playsinline && (this.$el.children[0].setAttribute('playsinline', playsinline),this.$el.children[0].setAttribute('webkit-playsinline', playsinline))

      // 是否适用youtube
      // if (video_options.techOrder.indexOf('youtube') > -1) require('videojs-youtube')

      // 非直播情况
      if (!options.live) {

        // 单独播放资源
        if (!!options.source.src) {
          video_options.sources = [options.source]

        // 多播放源切换
        } else {
          video_options.plugins = { videoJsResolutionSwitcher: { default: options.defaultSrcReId, dynamicLabel: true }}
        }

        // 是否使用播放速度控制
        var playbackRates = options.playbackRates
        if (!!playbackRates && !!playbackRates.length) video_options.playbackRates = playbackRates
      }

      // 实例化播放器
      var _this = this
      this.player = null
      this.player = videojs(this.$el.children[0], video_options, function() {

        // 是否应用多版本切换清晰度
        if (!options.live) {
          if (!!options.source.length) {
            this.updateSrc(options.source)
            this.on('resolutionchange', function(){
              _this.$emit && _this.$emit(customEventName, { resolutionchange: this.src() })
              _this.$dispatch && _this.$dispatch(customEventName, { resolutionchange: this.src() })
            })
          }
        }

        if (options.live) {

          // console.log('live video', this, options.source)
          this.src(options.source)

          // var hls = this.tech({ IWillNotUseThisInPlugins: true }).hls
          // 直播每次的切片请求
          /*
          this.tech_.hls.xhr.beforeRequest = function(options) {
            console.log(options)
            return options
          }
          */
        }

        // 监听播放
        this.on('play', function() {
          _this.$emit && _this.$emit(customEventName, { play: true })
          _this.$dispatch && _this.$dispatch(customEventName, { play: true })
        })

        // 监听暂停
        this.on('pause', function() {
          _this.$emit && _this.$emit(customEventName, { pause: true })
          _this.$dispatch && _this.$dispatch(customEventName, { pause: true })
        })

        // 监听结束
        this.on('ended', function() {
          _this.$emit && _this.$emit(customEventName, { ended: true })
          _this.$dispatch && _this.$dispatch(customEventName, { ended: true })
        })

        // 元文件信息
        this.on('loadeddata', function() {
          if (!options.live && !!options.start) this.currentTime(options.start)
          this.muted(_this.options.muted)
          _this.$emit && _this.$emit(customEventName, { loadeddata: true })
          _this.$dispatch && _this.$dispatch(customEventName, { loadeddata: true })
        })

        this.on('waiting', function() {
          _this.$emit && _this.$emit(customEventName, { waiting: true })
          _this.$dispatch && _this.$dispatch(customEventName, { waiting: true })
        })

        this.on('playing', function() {
          _this.$emit && _this.$emit(customEventName, { playing: true })
          _this.$dispatch && _this.$dispatch(customEventName, { playing: true })
        })

        // 监听时间
        this.on('timeupdate', function() {
          _this.$emit && _this.$emit(customEventName, { currentTime: this.currentTime() })
          _this.$dispatch && _this.$dispatch(customEventName, { currentTime: this.currentTime() })
        })
      })
    },
    // 释放播放器
    dispose: function() {
      if (!!this.player && !!videojs) {
        // this.player.dispose()
        this.player.pause && this.player.pause()
        videojs(this.$el).dispose()
        delete this.player
      }
    },
    // 操作播放器
    doAction: function(action, options) {
      // console.log(action)
      if (!this.player) return
      if (action == 'sliderDrag') this.player.currentTime(options.currentTime);
      if (action == 'play') this.player.play()
      if (action == 'pause') this.player.pause()
      if (action == 'refresh') {
        if (!this.options.live) {
          this.player.currentTime(0)
          this.player.play()
        }
      }
    }
  },
  // Vue1.X监听父组件传播下来的事件
  events: {
    'playerAction': function(action) {
      this.doAction(action)
    }
  },
  watch: {
    // 观察选项的动态变化，选项变化了就重新初始化播放器
    options: {
      handler: function (newVal, oldVal) {
        var options = newVal
        if (typeof options.source !== 'object') {
          this.dispose()
          return console.error('video resource must be a object or array')
        } else {
          if (options.source instanceof Array) {
            for (var i = 0, length = options.source.length; i < length; i++) {
              var item = options.source[i]
              if (!item.src) {
                this.dispose()
                return console.warn('video resource is illegitimate')
              }
            }
          } else {
            if (!options.source.src) {
              this.dispose()
              return console.warn('video resource is illegitimate')
            }
          }
        }
        if (this.player) this.player.src(this.options.source)
        if (!this.player) this.initialize()
      },
      deep: true
    }
  }
}
</script>

<style src="video.js/dist/video-js.css"></style>

<style lang="css">
.video-js.vjs-custom-skin {
  width: 100%;
  font-family: "PingFang SC","Helvetica Neue","Hiragino Sans GB","Segoe UI","Microsoft YaHei","微软雅黑",sans-serif;
}

.video-js.vjs-custom-skin .vjs-menu-button-inline.vjs-slider-active,.video-js.vjs-custom-skin .vjs-menu-button-inline:focus,.video-js.vjs-custom-skin .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline {
  width: 10em
}

.video-js.vjs-custom-skin .vjs-controls-disabled .vjs-big-play-button {
  display: none!important
}

.video-js.vjs-custom-skin .vjs-control {
  width: 3em
}

.video-js.vjs-custom-skin .vjs-control.vjs-live-control{
  width: auto;
  padding-left: .5em;
  letter-spacing: .1em;
}

.video-js.vjs-custom-skin .vjs-menu-button-inline:before {
  width: 1.5em
}

.vjs-menu-button-inline .vjs-menu {
  left: 3em
}

.vjs-paused.vjs-has-started.video-js.vjs-custom-skin .vjs-big-play-button,.video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button {
  display: block
}

.video-js.vjs-custom-skin .vjs-load-progress div,.vjs-seeking .vjs-big-play-button,.vjs-waiting .vjs-big-play-button {
  display: none!important
}

.video-js.vjs-custom-skin .vjs-mouse-display:after,.video-js.vjs-custom-skin .vjs-play-progress:after {
  padding: 0 .4em .3em
}

.video-js.vjs-ended .vjs-loading-spinner {
  display: none;
}

.video-js.vjs-ended .vjs-big-play-button {
  display: block !important;
}

.video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button,.vjs-paused.vjs-has-started.video-js.vjs-custom-skin .vjs-big-play-button {
  display: block
}

.video-js.vjs-custom-skin .vjs-big-play-button {
  top: 50%;
  left: 50%;
  margin-left: -1.5em;
  margin-top: -1em
}

.video-js.vjs-custom-skin .vjs-big-play-button {
  background-color: rgba(0,0,0,0.45);
  font-size: 3.5em;
   /*border-radius: 50%;*/
  height: 2em !important;
  line-height: 2em !important;
  margin-top: -1em !important
}

.video-js:hover .vjs-big-play-button,.video-js.vjs-custom-skin .vjs-big-play-button:focus,.video-js.vjs-custom-skin .vjs-big-play-button:active {
  background-color: rgba(36,131,213,0.9)
}

.video-js.vjs-custom-skin .vjs-loading-spinner {
  border-color: rgba(36,131,213,0.8)
}

.video-js.vjs-custom-skin .vjs-control-bar2 {
  background-color: #000000
}

.video-js.vjs-custom-skin .vjs-control-bar {
   /*background-color: rgba(0,0,0,0.3) !important;*/
  color: #ffffff;
  font-size: 14px
}

.video-js.vjs-custom-skin .vjs-play-progress,.video-js.vjs-custom-skin  .vjs-volume-level {
  background-color: #2483d5
}

.video-js.vjs-custom-skin .vjs-play-progress:before {
  top: -0.3em;
}

.video-js.vjs-custom-skin .vjs-progress-control:hover .vjs-progress-holder {
  font-size: 1.3em;
}

.vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu {
  left: 0em;
}

.video-js.vjs-custom-skin .vjs-menu li {
  padding: 0;
  line-height: 2em;
  font-size: 1.1em;
  font-family: "PingFang SC","Helvetica Neue","Hiragino Sans GB","Segoe UI","Microsoft YaHei","微软雅黑",sans-serif;
}

.video-js.vjs-custom-skin .vjs-time-tooltip,
.video-js.vjs-custom-skin .vjs-mouse-display:after,
.video-js.vjs-custom-skin .vjs-play-progress:after {
  border-radius: 0;
  font-size: 1em;
  padding: 0;
  width: 3em;
  height: 1.5em;
  line-height: 1.5em;
  top: -3em;
}

.video-js.vjs-custom-skin .vjs-menu-button-popup .vjs-menu {
  width: 5em;
  left: -1em;
}

.video-js.vjs-custom-skin .vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu {
  left: 0;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-resolution-button .vjs-menu {
   /*order: 4;*/
}

 /*排序顺序*/
.video-js.vjs-custom-skin .vjs-control-bar .vjs-play-control {
  order: 0;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-time-control {
  min-width: 1em;
  padding: 0;
  margin: 0 .1em;
  text-align: center;
  display: block;
  order: 1;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-playback-rate .vjs-playback-rate-value{
  font-size: 1.2em;
  line-height: 2.4;
}

.video-js.vjs-custom-skin .vjs-progress-control.vjs-control {
  order: 2;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-volume-menu-button {
  order: 3;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-resolution-button {
  order: 4;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-resolution-button .vjs-resolution-button-label {
  display: block;
  line-height: 3em;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-playback-rate {
  order: 5;
}

.video-js.vjs-custom-skin .vjs-control-bar .vjs-fullscreen-control {
  order: 6;
}

video::-webkit-media-controls {
  display:none !important;
}
video::-webkit-media-controls-enclosure {
  display: none !important;
}
</style>


Removing white border around plugin body

<template>

  <core-base
    :topLevelPageName="topLevelPageName"
    :appBarTitle="$tr('coachTitle')"
    :navBarNeeded="!isLoading && !currentPageIsImmersive"
  >

    <template v-if="userCanAccessPage">
      <template v-if="!isLoading && showTopNav">
        <class-selector :classes="classList" :currentClassId="classId" @changeClass="changeClass" />
        <top-nav />
      </template>

      <component
        class="content"
        :is="currentPage"
      />

    </template>

    <auth-message v-else authorizedRole="adminOrCoach" />

  </core-base>

</template>

<script>

  import { currentPageIsImmersive } from '../state/getters/main';
  import { PageNames } from '../constants';
  import { isAdmin, isCoach, isSuperuser } from 'kolibri.coreVue.vuex.getters';
  import { TopLevelPageNames } from 'kolibri.coreVue.vuex.constants';
  import authMessage from 'kolibri.coreVue.components.authMessage';
  import topNav from './top-nav';
  import classListPage from './class-list-page';
  import examsPage from './exams-page';
  import createExamPage from './create-exam-page';
  import examReportPage from './exam-report-page';
  import examReportDetailPage from './exam-report-detail-page';
  import groupsPage from './groups-page';
  import coreBase from 'kolibri.coreVue.components.coreBase';
  import learnerExerciseDetailPage from './reports/learner-exercise-detail-page';
  import recentItemsPage from './reports/recent-items-page';
  import channelListPage from './reports/channel-list-page';
  import itemListPage from './reports/item-list-page';
  import learnerListPage from './reports/learner-list-page';
  import classSelector from './class-selector';

  // lessons
  import { LessonsPageNames } from '../lessonsConstants';
  import LessonsRootPage from './lessons/LessonsRootPage';
  import LessonSummaryPage from './lessons/LessonSummaryPage';
  import LessonResourceSelectionPage from './lessons/LessonResourceSelectionPage';

  export default {
    name: 'coachRoot',
    $trs: {
      coachTitle: 'Coach',
    },
    components: {
      authMessage,
      topNav,
      classListPage,
      examsPage,
      createExamPage,
      // reports
      examReportPage,
      examReportDetailPage,
      groupsPage,
      coreBase,
      learnerExerciseDetailPage,
      recentItemsPage,
      channelListPage,
      itemListPage,
      learnerListPage,
      classSelector,
      // lessons
      LessonsRootPage,
      LessonSummaryPage,
      LessonResourceSelectionPage,
    },
    computed: {
      topLevelPageName: () => TopLevelPageNames.COACH,
      currentPage() {
        const pageNameToComponentMap = {
          [PageNames.CLASS_LIST]: 'class-list-page',
          [PageNames.EXAMS]: 'exams-page',
          [PageNames.GROUPS]: 'groups-page',
          [PageNames.CREATE_EXAM]: 'create-exam-page',
          // reports
          [PageNames.RECENT_CHANNELS]: 'channel-list-page',
          [PageNames.RECENT_ITEMS_FOR_CHANNEL]: 'recent-items-page',
          [PageNames.RECENT_LEARNERS_FOR_ITEM]: 'learner-list-page',
          [PageNames.RECENT_LEARNER_ITEM_DETAILS]: 'learner-exercise-detail-page',
          [PageNames.TOPIC_CHANNELS]: 'channel-list-page',
          [PageNames.TOPIC_CHANNEL_ROOT]: 'item-list-page',
          [PageNames.TOPIC_ITEM_LIST]: 'item-list-page',
          [PageNames.TOPIC_LEARNERS_FOR_ITEM]: 'learner-list-page',
          [PageNames.TOPIC_LEARNER_ITEM_DETAILS]: 'learner-exercise-detail-page',
          [PageNames.LEARNER_LIST]: 'learner-list-page',
          [PageNames.LEARNER_CHANNELS]: 'channel-list-page',
          [PageNames.LEARNER_CHANNEL_ROOT]: 'item-list-page',
          [PageNames.LEARNER_ITEM_LIST]: 'item-list-page',
          [PageNames.LEARNER_ITEM_DETAILS]: 'learner-exercise-detail-page',
          [PageNames.EXAM_REPORT]: 'exam-report-page',
          [PageNames.EXAM_REPORT_DETAIL]: 'exam-report-detail-page',

          // lessons
          [LessonsPageNames.ROOT]: 'LessonsRootPage',
          [LessonsPageNames.SUMMARY]: 'LessonSummaryPage',
          [LessonsPageNames.SELECTION_ROOT]: 'LessonResourceSelectionPage',
          [LessonsPageNames.SELECTION]: 'LessonResourceSelectionPage',
        };
        return pageNameToComponentMap[this.pageName];
      },
      showTopNav() {
        return (
          this.pageName !== PageNames.CLASS_LIST &&
          this.userCanAccessPage &&
          !this.currentPageIsImmersive
        );
      },
      userCanAccessPage() {
        return this.isCoach || this.isAdmin || this.isSuperuser;
      },
    },
    methods: {
      changeClass(classSelectedId) {
        if (this.pageName === PageNames.EXAM_REPORT) {
          this.$router.push({
            name: PageNames.EXAMS,
            params: { classId: classSelectedId },
          });
        } else {
          this.$router.push({ params: { classId: classSelectedId } });
        }
      },
    },
    vuex: {
      getters: {
        currentPageIsImmersive,
        pageName: state => state.pageName,
        isAdmin,
        isCoach,
        isSuperuser,
        classList: state => state.classList,
        classId: state => state.classId,
        isLoading: state => state.core.loading,
      },
    },
  };

</script>

<style lang="stylus" scoped>

  @require '~kolibri.styles.definitions'

  .content
    margin-top: 32px

</style>

<template>

  <core-base
    :topLevelPageName="topLevelPageName"
    :appBarTitle="$tr('coachTitle')"
    :navBarNeeded="!isLoading && !currentPageIsImmersive"
  >

    <div v-if="userCanAccessPage">
      <template v-if="!isLoading && showTopNav">
        <class-selector :classes="classList" :currentClassId="classId" @changeClass="changeClass" />
        <top-nav />
      </template>

      <component
        :class="{ content: !currentPageIsImmersive }"
        :is="currentPage"
      />

    </div>

    <auth-message v-else authorizedRole="adminOrCoach" />

  </core-base>

</template>

<script>

  import { currentPageIsImmersive } from '../state/getters/main';
  import { PageNames } from '../constants';
  import { isAdmin, isCoach, isSuperuser } from 'kolibri.coreVue.vuex.getters';
  import { TopLevelPageNames } from 'kolibri.coreVue.vuex.constants';
  import authMessage from 'kolibri.coreVue.components.authMessage';
  import topNav from './top-nav';
  import classListPage from './class-list-page';
  import examsPage from './exams-page';
  import createExamPage from './create-exam-page';
  import examReportPage from './exam-report-page';
  import examReportDetailPage from './exam-report-detail-page';
  import groupsPage from './groups-page';
  import coreBase from 'kolibri.coreVue.components.coreBase';
  import learnerExerciseDetailPage from './reports/learner-exercise-detail-page';
  import recentItemsPage from './reports/recent-items-page';
  import channelListPage from './reports/channel-list-page';
  import itemListPage from './reports/item-list-page';
  import learnerListPage from './reports/learner-list-page';
  import classSelector from './class-selector';

  // lessons
  import { LessonsPageNames } from '../lessonsConstants';
  import LessonsRootPage from './lessons/LessonsRootPage';
  import LessonSummaryPage from './lessons/LessonSummaryPage';
  import LessonResourceSelectionPage from './lessons/LessonResourceSelectionPage';

  export default {
    name: 'coachRoot',
    $trs: {
      coachTitle: 'Coach',
    },
    components: {
      authMessage,
      topNav,
      classListPage,
      examsPage,
      createExamPage,
      // reports
      examReportPage,
      examReportDetailPage,
      groupsPage,
      coreBase,
      learnerExerciseDetailPage,
      recentItemsPage,
      channelListPage,
      itemListPage,
      learnerListPage,
      classSelector,
      // lessons
      LessonsRootPage,
      LessonSummaryPage,
      LessonResourceSelectionPage,
    },
    computed: {
      topLevelPageName: () => TopLevelPageNames.COACH,
      currentPage() {
        const pageNameToComponentMap = {
          [PageNames.CLASS_LIST]: 'class-list-page',
          [PageNames.EXAMS]: 'exams-page',
          [PageNames.GROUPS]: 'groups-page',
          [PageNames.CREATE_EXAM]: 'create-exam-page',
          // reports
          [PageNames.RECENT_CHANNELS]: 'channel-list-page',
          [PageNames.RECENT_ITEMS_FOR_CHANNEL]: 'recent-items-page',
          [PageNames.RECENT_LEARNERS_FOR_ITEM]: 'learner-list-page',
          [PageNames.RECENT_LEARNER_ITEM_DETAILS]: 'learner-exercise-detail-page',
          [PageNames.TOPIC_CHANNELS]: 'channel-list-page',
          [PageNames.TOPIC_CHANNEL_ROOT]: 'item-list-page',
          [PageNames.TOPIC_ITEM_LIST]: 'item-list-page',
          [PageNames.TOPIC_LEARNERS_FOR_ITEM]: 'learner-list-page',
          [PageNames.TOPIC_LEARNER_ITEM_DETAILS]: 'learner-exercise-detail-page',
          [PageNames.LEARNER_LIST]: 'learner-list-page',
          [PageNames.LEARNER_CHANNELS]: 'channel-list-page',
          [PageNames.LEARNER_CHANNEL_ROOT]: 'item-list-page',
          [PageNames.LEARNER_ITEM_LIST]: 'item-list-page',
          [PageNames.LEARNER_ITEM_DETAILS]: 'learner-exercise-detail-page',
          [PageNames.EXAM_REPORT]: 'exam-report-page',
          [PageNames.EXAM_REPORT_DETAIL]: 'exam-report-detail-page',

          // lessons
          [LessonsPageNames.ROOT]: 'LessonsRootPage',
          [LessonsPageNames.SUMMARY]: 'LessonSummaryPage',
          [LessonsPageNames.SELECTION_ROOT]: 'LessonResourceSelectionPage',
          [LessonsPageNames.SELECTION]: 'LessonResourceSelectionPage',
        };
        return pageNameToComponentMap[this.pageName];
      },
      showTopNav() {
        return (
          this.pageName !== PageNames.CLASS_LIST &&
          this.userCanAccessPage &&
          !this.currentPageIsImmersive
        );
      },
      userCanAccessPage() {
        return this.isCoach || this.isAdmin || this.isSuperuser;
      },
    },
    methods: {
      changeClass(classSelectedId) {
        if (this.pageName === PageNames.EXAM_REPORT) {
          this.$router.push({
            name: PageNames.EXAMS,
            params: { classId: classSelectedId },
          });
        } else {
          this.$router.push({ params: { classId: classSelectedId } });
        }
      },
    },
    vuex: {
      getters: {
        currentPageIsImmersive,
        pageName: state => state.pageName,
        isAdmin,
        isCoach,
        isSuperuser,
        classList: state => state.classList,
        classId: state => state.classId,
        isLoading: state => state.core.loading,
      },
    },
  };

</script>

<style lang="stylus" scoped>

  @require '~kolibri.styles.definitions'

  .content
    background-color: $core-bg-light
    padding: 1em
    margin-top: 1em

</style>


Removes unused getter

Removes unused getter

<template>

  <core-base :topLevelPageName="topLevelPageName" :appBarTitle="$tr('coachTitle')">

      <template v-if="showTopNav">
        <class-selector :classes="classList" :currentClassId="classId"/>
        <top-nav/>
      </template>

        <component :is="currentPage"/>

{{ $tr('superUserPrompt') }}

{{ $tr('superUserCommand') }}

{{ $tr('logInPrompt') }}

{{ $tr('logInCommand') }}

  </core-base>

</template>


Tweaked welcome page text

Tweaked welcome page text

<template>

Sketchpad

Introduction

Sketchpad is an innovative live-coding, development and admin environment for your existing Laravel site.

It's a place to write, test, experiment and execute code, or just a place to group useful tools and functions you want easy access to.

Overview

Sketchpad provides an alternative structure of controllers, views, assets and routing that lives alongside your main Laravel installation.

You can navigate methods, interactively modify parameters, and even live-reload the code you're working on, all from a friendly UI.

Additional tooling and functionality is designed to make it quick and easy to develop or debug new tools, features and code.

Next steps…

Navigate the menu on the left, or view the help for more info.

</template>

<template>

Sketchpad

Introduction

Sketchpad is an innovative, bolt-on development environment for your existing Laravel site.

It's a place to write, test, experiment and execute code, or just a place to group useful tools and functions you want easy access to.

Overview

Sketchpad provides an alternative structure of controllers, views, assets and routing that lives alongside your main Laravel installation.

You can navigate methods, interactively modify parameters, and even live-reload the code you're working on, all from a friendly UI.

Additional tooling and functionality is designed to make it quick and easy to develop or debug new tools, features and code.

Next steps…

Navigate the menu on the left, or view the help for more info.

</template>


formatowanie daty

formatowanie daty

<template>

        {{ pollSync.title }}

         1" class="max-items">(* możesz oddać maksymalnie {{ poll.max_items }} {{ poll.max_items|declination(['głos', 'głosy', 'głosów']) }})

 1, 'custom-radio': pollSync.max_items === 1}" class="custom-control">

          <input
            v-if="pollSync.max_items > 1"
            :id="`item-${item.id}`"
            v-model="checkedOptions"
            :value="item.id"
            type="checkbox"
            class="custom-control-input"
          >

          <input
            v-else
            :id="`item-${item.id}`"
            v-model="checkedOptions"
            :value="item.id"
            type="radio"
            class="custom-control-input"
          >

          <label :for="`item-${item.id}`" class="custom-control-label">
            {{ item.text }}

        <template v-else>{{ item.text }}</template>

          <div class="progress-bar" role="progressbar" :style="`width: ${percentage(item)}%`">
            {{ percentage(item) }}%

        {{ percentage(item) }}%

        [{{ item.total }}]

        <vue-button :disabled="isProcessing" @click.native="vote" class="btn btn-sm btn-primary">Głosuj</vue-button>

Ankieta wygasła <vue-timeago :datetime="pollSync.expired_at"></vue-timeago>

</template>

<template>

        {{ pollSync.title }}

         1" class="max-items">(* możesz oddać maksymalnie {{ poll.max_items }} {{ poll.max_items|declination(['głos', 'głosy', 'głosów']) }})

 1, 'custom-radio': pollSync.max_items === 1}" class="custom-control">

          <input
            v-if="pollSync.max_items > 1"
            :id="`item-${item.id}`"
            v-model="checkedOptions"
            :value="item.id"
            type="checkbox"
            class="custom-control-input"
          >

          <input
            v-else
            :id="`item-${item.id}`"
            v-model="checkedOptions"
            :value="item.id"
            type="radio"
            class="custom-control-input"
          >

          <label :for="`item-${item.id}`" class="custom-control-label">
            {{ item.text }}

        <template v-else>{{ item.text }}</template>

          <div class="progress-bar" role="progressbar" :style="`width: ${percentage(item)}%`">
            {{ percentage(item) }}%

        {{ percentage(item) }}%

        [{{ item.total }}]

        <vue-button :disabled="isProcessing" @click.native="vote" class="btn btn-sm btn-primary">Głosuj</vue-button>

Ankieta wygasła {{ pollSync.expired_at }}

</template>


Fix bookmark functionality.

Fix bookmark functionality.

<template>
  <panel title="Song Metadata">
    <v-layout>
      <v-flex xs6>

          {{ song.title }}

          {{ song.artist }}

          {{ song.genre }}

        <v-btn dark class="cyan" :to="{name:'song-edit', params () {return { songId: song.id}}}">
          Edit
        </v-btn>

        <v-btn v-if="isUserLoggedIn && !bookmark" dark class="cyan" @click="setAsBookmark">
          Set As Bookmark
        </v-btn>
        <v-btn v-if="isUserLoggedIn && bookmark" dark class="cyan" @click="unsetAsBookmark">
          Unset As Bookmark
        </v-btn>

      </v-flex>

      <v-flex xs6>

        {{ song.album }}
      </v-flex>
    </v-layout>
  </panel>
</template>

<template>
  <panel title="Song Metadata">
    <v-layout>
      <v-flex xs6>

          {{ song.title }}

          {{ song.artist }}

          {{ song.genre }}

        <v-btn dark class="cyan" :to="{name:'song-edit', params () {return { songId: song.id}}}">
          Edit
        </v-btn>

        <v-btn v-if="isUserLoggedIn && !isBookmarked" dark class="cyan" @click="bookmark">
          Bookmark
        </v-btn>
        <v-btn v-if="isUserLoggedIn && isBookmarked" dark class="cyan" @click="unbookmark">
          Unbookmark
        </v-btn>

      </v-flex>

      <v-flex xs6>

        {{ song.album }}
      </v-flex>
    </v-layout>
  </panel>
</template>


LiveMpegTsVideo で動画時間を返さないように修正

LiveMpegTsVideo で動画時間を返さないように修正

<template>
    <video ref="video" autoplay playsinline></video>
</template>

<script lang="ts">
import BaseVideo from '@/components/video/BaseVideo';
import container from '@/model/ModelContainer';
import ISnackbarState from '@/model/state/snackbar/ISnackbarState';
import * as aribb24js from 'aribb24.js';
import { Component, Prop } from 'vue-property-decorator';
import Mpegts from 'mpegts.js';
import SubtitleUtil from '@/util/SubtitleUtil';

@Component({})
export default class LiveMpegTsVideo extends BaseVideo {
    @Prop({ required: true })
    public videoSrc!: string;

    private snackbarState: ISnackbarState = container.get<ISnackbarState>('ISnackbarState');
    private mepgtsPlayer: Mpegts.Player | null = null;
    private captionRenderer: aribb24js.CanvasB24Renderer | null = null;
    private superimposeRenderer: aribb24js.CanvasB24Renderer | null = null;

    public mounted(): void {
        super.mounted();
    }

    public async beforeDestroy(): Promise<void> {
        if (this.mepgtsPlayer !== null) {
            this.mepgtsPlayer.pause();
            this.mepgtsPlayer.unload();
            this.mepgtsPlayer.destroy();
            this.mepgtsPlayer = null;
        }

        if (this.captionRenderer !== null) {
            this.captionRenderer.detachMedia();
            this.captionRenderer.dispose();
            this.captionRenderer = null;
        }

        if (this.superimposeRenderer !== null) {
            this.superimposeRenderer.detachMedia();
            this.superimposeRenderer.dispose();
            this.superimposeRenderer = null;
        }

        super.beforeDestroy();
    }

    /**
     * video 再生初期設定
     */
    protected initVideoSetting(): void {
        // 対応しているか確認
        if (Mpegts.isSupported() === false || Mpegts.getFeatureList().mseLivePlayback === false) {
            this.snackbarState.open({
                color: 'error',
                text: '非対応ブラウザーです。',
            });

            throw new Error('UnsupportedBrowser');
        }

        if (this.video === null) {
            this.snackbarState.open({
                color: 'error',
                text: 'video 要素がありません。',
            });
            throw new Error('VideoIsNull');
        }

        // mpegts.js の設定
        Mpegts.LoggingControl.enableVerbose = false;
        const mpegtsConfig: Mpegts.Config = {
            enableWorker: true,
            liveBufferLatencyChasing: true,
            liveBufferLatencyMinRemain: 1.0,
            liveBufferLatencyMaxLatency: 2.0,
        };
        this.mepgtsPlayer = Mpegts.createPlayer(
            {
                type: 'mse',
                isLive: true,
                url: this.videoSrc,
            },
            mpegtsConfig,
        );

        this.mepgtsPlayer.attachMediaElement(this.video);
        this.mepgtsPlayer.load();
        this.mepgtsPlayer.play();

        // 字幕対応
        const captionOption = SubtitleUtil.getAribb24BaseOption();
        captionOption.data_identifer = 0x80;
        this.captionRenderer = new aribb24js.CanvasB24Renderer(captionOption);

        const superimposeOption = SubtitleUtil.getAribb24BaseOption();
        superimposeOption.data_identifer = 0x81;
        this.superimposeRenderer = new aribb24js.CanvasB24Renderer(superimposeOption);

        this.captionRenderer.attachMedia(this.video);
        this.superimposeRenderer.attachMedia(this.video);

        /**
         * 字幕スーパー用の処理
         * 元のソースは下記参照
         * https://twitter.com/magicxqq/status/1381813912539066373
         * https://github.com/l3tnun/EPGStation/commit/352bf9a69fdd0848295afb91859e1a402b623212#commitcomment-50407815
         */
        this.mepgtsPlayer.on(Mpegts.Events.PES_PRIVATE_DATA_ARRIVED, data => {
            if (data.stream_id === 0xbd && data.data[0] === 0x80 && this.captionRenderer !== null) {
                // private_stream_1, caption
                this.captionRenderer.pushData(data.pid, data.data, data.pts / 1000);
            } else if (data.stream_id === 0xbf && this.superimposeRenderer !== null) {
                // private_stream_2, superimpose
                let payload = data.data;
                if (payload[0] !== 0x81) {
                    payload = this.parseMalformedPES(data.data);
                }
                if (payload[0] !== 0x81) {
                    return;
                }
                this.superimposeRenderer.pushData(data.pid, payload, data.nearest_pts / 1000);
            }
        });
    }

    /**
     * 字幕スーパー用の処理
     * 元のソースは下記参照
     * https://twitter.com/magicxqq/status/1381813912539066373
     * https://github.com/l3tnun/EPGStation/commit/352bf9a69fdd0848295afb91859e1a402b623212#commitcomment-50407815
     */
    private parseMalformedPES(data: any): any {
        let pes_scrambling_control = (data[0] & 0x30) >>> 4;
        let pts_dts_flags = (data[1] & 0xc0) >>> 6;
        let pes_header_data_length = data[2];

        let payload_start_index = 3 + pes_header_data_length;
        let payload_length = data.byteLength - payload_start_index;

        let payload = data.subarray(payload_start_index, payload_start_index + payload_length);

        return payload;
    }

    /**
     * 動画の長さを返す (秒)
     * @return number
     */
    public getDuration(): number {
        return 0;
    }

    /**
     * 動画の現在再生位置を返す (秒)
     * @return number
     */
    public getCurrentTime(): number {
        return 0;
    }
}
</script>

<template>
    <video ref="video" autoplay playsinline></video>
</template>

<script lang="ts">
import BaseVideo from '@/components/video/BaseVideo';
import container from '@/model/ModelContainer';
import ISnackbarState from '@/model/state/snackbar/ISnackbarState';
import * as aribb24js from 'aribb24.js';
import { Component, Prop } from 'vue-property-decorator';
import Mpegts from 'mpegts.js';
import SubtitleUtil from '@/util/SubtitleUtil';

@Component({})
export default class LiveMpegTsVideo extends BaseVideo {
    @Prop({ required: true })
    public videoSrc!: string;

    private snackbarState: ISnackbarState = container.get<ISnackbarState>('ISnackbarState');
    private mepgtsPlayer: Mpegts.Player | null = null;
    private captionRenderer: aribb24js.CanvasB24Renderer | null = null;
    private superimposeRenderer: aribb24js.CanvasB24Renderer | null = null;

    public mounted(): void {
        super.mounted();
    }

    public async beforeDestroy(): Promise<void> {
        if (this.mepgtsPlayer !== null) {
            this.mepgtsPlayer.pause();
            this.mepgtsPlayer.unload();
            this.mepgtsPlayer.destroy();
            this.mepgtsPlayer = null;
        }

        if (this.captionRenderer !== null) {
            this.captionRenderer.detachMedia();
            this.captionRenderer.dispose();
            this.captionRenderer = null;
        }

        if (this.superimposeRenderer !== null) {
            this.superimposeRenderer.detachMedia();
            this.superimposeRenderer.dispose();
            this.superimposeRenderer = null;
        }

        super.beforeDestroy();
    }

    /**
     * video 再生初期設定
     */
    protected initVideoSetting(): void {
        // 対応しているか確認
        if (Mpegts.isSupported() === false || Mpegts.getFeatureList().mseLivePlayback === false) {
            this.snackbarState.open({
                color: 'error',
                text: '非対応ブラウザーです。',
            });

            throw new Error('UnsupportedBrowser');
        }

        if (this.video === null) {
            this.snackbarState.open({
                color: 'error',
                text: 'video 要素がありません。',
            });
            throw new Error('VideoIsNull');
        }

        // mpegts.js の設定
        Mpegts.LoggingControl.enableVerbose = false;
        const mpegtsConfig: Mpegts.Config = {
            enableWorker: true,
            liveBufferLatencyChasing: true,
            liveBufferLatencyMinRemain: 1.0,
            liveBufferLatencyMaxLatency: 2.0,
        };
        this.mepgtsPlayer = Mpegts.createPlayer(
            {
                type: 'mse',
                isLive: true,
                url: this.videoSrc,
            },
            mpegtsConfig,
        );

        this.mepgtsPlayer.attachMediaElement(this.video);
        this.mepgtsPlayer.load();
        this.mepgtsPlayer.play();

        // 字幕対応
        const captionOption = SubtitleUtil.getAribb24BaseOption();
        captionOption.data_identifer = 0x80;
        this.captionRenderer = new aribb24js.CanvasB24Renderer(captionOption);

        const superimposeOption = SubtitleUtil.getAribb24BaseOption();
        superimposeOption.data_identifer = 0x81;
        this.superimposeRenderer = new aribb24js.CanvasB24Renderer(superimposeOption);

        this.captionRenderer.attachMedia(this.video);
        this.superimposeRenderer.attachMedia(this.video);

        /**
         * 字幕スーパー用の処理
         * 元のソースは下記参照
         * https://twitter.com/magicxqq/status/1381813912539066373
         * https://github.com/l3tnun/EPGStation/commit/352bf9a69fdd0848295afb91859e1a402b623212#commitcomment-50407815
         */
        this.mepgtsPlayer.on(Mpegts.Events.PES_PRIVATE_DATA_ARRIVED, data => {
            if (data.stream_id === 0xbd && data.data[0] === 0x80 && this.captionRenderer !== null) {
                // private_stream_1, caption
                this.captionRenderer.pushData(data.pid, data.data, data.pts / 1000);
            } else if (data.stream_id === 0xbf && this.superimposeRenderer !== null) {
                // private_stream_2, superimpose
                let payload = data.data;
                if (payload[0] !== 0x81) {
                    payload = this.parseMalformedPES(data.data);
                }
                if (payload[0] !== 0x81) {
                    return;
                }
                this.superimposeRenderer.pushData(data.pid, payload, data.nearest_pts / 1000);
            }
        });
    }

    /**
     * 字幕スーパー用の処理
     * 元のソースは下記参照
     * https://twitter.com/magicxqq/status/1381813912539066373
     * https://github.com/l3tnun/EPGStation/commit/352bf9a69fdd0848295afb91859e1a402b623212#commitcomment-50407815
     */
    private parseMalformedPES(data: any): any {
        let pes_scrambling_control = (data[0] & 0x30) >>> 4;
        let pts_dts_flags = (data[1] & 0xc0) >>> 6;
        let pes_header_data_length = data[2];

        let payload_start_index = 3 + pes_header_data_length;
        let payload_length = data.byteLength - payload_start_index;

        let payload = data.subarray(payload_start_index, payload_start_index + payload_length);

        return payload;
    }
}
</script>


用户注册设置添加第三方

用户注册设置添加第三方

用户注册设置添加第三方

<template>

				用户注册设置

			  <loading :loadding="loadding"></loading>

					注册方式

							    	<input type="radio" name="type" checked="checked" value="all" v-model="type" /> 开放注册

							    	<input type="radio" name="type" disabled="disabled" value="invited" v-model="type" /> 仅邀请注册

							    	<input type="radio" name="type" disabled="disabled" value="thirdPart" v-model="type" /> 仅第三方绑定

		     			注册方式

						注册类型

								    	<input type="radio" name="method" disabled="disabled" value="mobile-only" v-model="method" /> 仅手机

								    	<input type="radio" name="method" disabled="disabled" value="mail-only" v-model="method" /> 仅邮箱

								    	<input type="radio" name="method" checked="checked" value="all" v-model="method" /> 手机或邮箱

								    	<input type="radio" name="method" disabled="disabled" value="third" v-model="method" /> 第三方

			     			账号注册时使用的类型

						完善资料

								    	<input type="radio" name="fixed" v-model="fixed" value="need" /> 注册时强制完善

								    	<input type="radio" name="fixed" checked="checked" value="no-need" v-model="fixed"/> 不需要强制完善

			     			完善资料，注册时强制完善，不需要强制完善

						服务条款和隐私政策

								    	<input type="radio" name="showTerms" value="open" v-model="showTerms" /> 开启

								    	<input type="radio" name="showTerms" checked="checked" value="close" v-model="showTerms" /> 关闭

			     			服务条款和隐私政策

			     		条款内容

				          <textarea
				          class="form-control question-editor"
				          rows="12"
				          placeholder="输入内容..."
				          v-model="content"
				          autofocus
				          required>
				          </textarea>

			     			支持 Markdown 格式

			          <button v-if="loading" type="button" class="btn btn-primary" disabled="disabled">

			          </button>
			          <button v-else type="button" class="btn btn-primary" @click="saveConfig">保存设置</button>

{{ message }}

</template>

<template>

				用户注册设置

			  <loading :loadding="loadding"></loading>

					注册方式

							    	<input type="radio" name="type" checked="checked" value="all" v-model="type" /> 开放注册

							    	<input type="radio" name="type" disabled="disabled" value="invited" v-model="type" /> 仅邀请注册

							    	<input type="radio" name="type" disabled="disabled" value="thirdPart" v-model="type" /> 仅第三方绑定

		     			注册方式

						注册类型

								    	<input type="radio" name="method" disabled="disabled" value="mobile-only" v-model="method" /> 仅手机

								    	<input type="radio" name="method" disabled="disabled" value="mail-only" v-model="method" /> 仅邮箱

								    	<input type="radio" name="method" checked="checked" value="all" v-model="method" /> 手机或邮箱

			     			账号注册时使用的类型

						完善资料

								    	<input type="radio" name="fixed" v-model="fixed" value="need" /> 注册时强制完善

								    	<input type="radio" name="fixed" checked="checked" value="no-need" v-model="fixed"/> 不需要强制完善

			     			完善资料，注册时强制完善，不需要强制完善

						服务条款和隐私政策

								    	<input type="radio" name="showTerms" value="open" v-model="showTerms" /> 开启

								    	<input type="radio" name="showTerms" checked="checked" value="close" v-model="showTerms" /> 关闭

			     			服务条款和隐私政策

			     		条款内容

				          <textarea
				          class="form-control question-editor"
				          rows="12"
				          placeholder="输入内容..."
				          v-model="content"
				          autofocus
				          required>
				          </textarea>

			     			支持 Markdown 格式

			          <button v-if="loading" type="button" class="btn btn-primary" disabled="disabled">

			          </button>
			          <button v-else type="button" class="btn btn-primary" @click="saveConfig">保存设置</button>

{{ message }}

</template>


add edit mode to xml file component

add edit mode to xml file component

<template>

            <button class="btn btn-outline-secondary" @click="toggleEditMode()">
                 Toggle Edit mode
            </button>
            <button class="btn btn-outline-secondary" v-show="!renderHtml" @click="toggleCodeHighlighting()">
                 Toggle Highlighting
            </button>
            <button class="btn btn-outline-secondary" v-if="isHtml" @click="toggleHtmlRendering()">
                 Toggle HTML-Rendering
            </button>

                <textarea class="w-100 h-100 p-2" v-model="content"></textarea>

</template>

<template>

        <button class="btn btn-outline-secondary" @click="toggleCodeHighlighting()">
             Toggle Highlighting
        </button>
        <button class="btn btn-outline-secondary" v-if="isHtml" @click="toggleHtmlRendering()">
             Toggle HTML-Rendering
        </button>

</template>


style(SPA): 移除无用方法

style(SPA): 移除无用方法

<template>

      <template v-if="timeLine">

{{ $t('date.today') }}

          {{ time.getDate() }}
          {{ $t(`date.months[${time.getMonth()}]`) }}

      </template>
      <Avatar v-else :user="user" />

{{ user.name }}

            {{ $t('pinned') }}
            {{ time | time2tips }}

{{ title }}

 0" class="m-card-con">

          <FeedImage
            v-if="images.length > 0"
            :id="feedId"
            :pics="images"
          />
          <FeedVideo
            v-if="video"
            :id="feedId"
            :video="video"
          />

          <svg class="m-style-svg m-svg-def">
            <use :xlink:href="liked ? '#icon-like' :'#icon-unlike'" />
          </svg>
          {{ likeCount | formatNum }}

          <svg class="m-style-svg m-svg-def">
            <use xlink:href="#icon-comment" />
          </svg>
          {{ commentCount | formatNum }}

          <svg class="m-style-svg m-svg-def">
            <use xlink:href="#icon-eye" />
          </svg>
          {{ viewCount | formatNum }}

            <svg class="m-style-svg m-svg-def">
              <use xlink:href="#icon-more" />
            </svg>

 0" class="m-card-comments">

          <CommentItem :comment="com" @click="commentAction" />

 5"
        class="m-router-link"
        @click="handleView('comment_list')"
      >
        {{ $t('article.view_all_comments') }}

</template>


Add transitions and tipp component

Add transitions and tipp component

<template>

    <transition
      name="slide-in-top"
    >
      <map-search
        v-if="!selectedTipp"
        ref="mapSearch"
        :map="map"
        :test="'test'"
      ></map-search>
    </transition>

    <transition
      name="slide-in"
    >
      <map-tipp-detail
        v-if="selectedTipp"
        :tipp="selectedTipp"
        @close="closeTippDetail"
      />
    </transition>

</template>

<template>

    <map-search
      ref="mapSearch"
      :map="map"
      :test="'test'"
    ></map-search>

</template>


Adding the toolbar option to the fab

Adding the toolbar option to the fab

<template>
    <div class="fixed-action-btn"
         :class="classes"
    >
        <m-btn-link large
                  floating
        >
            <m-icon>{{ icon }}</m-icon>
        </m-btn-link>
        <slot name="items"></slot>
    </div>
</template>

<script type="text/babel">
    export default {
        props: {
            icon: {
                type: String,
                required: true
            },

            horizontal: {
                type: Boolean,
                default: false
            },

            toggle: {
                type: Boolean,
                default: false
                        },
            toolbar: {
                type: Boolean,
                defaut: false
            }
        },

        computed: {
            classes () {
                return {
                    'click-to-toggle': this.toggle,
                    'horizontal': this.horizontal,
                    'toolbar': this.toolbar
                }
            }
        },

        methods: {
            open () {
                $(this.$el).openFAB()
                this.$emit('open')
            },

            close () {
                $(this.$el).closeFAB()
                this.$emit('close')
            }
        }
    }
</script>

<template>
    <div class="fixed-action-btn"
         :class="classes"
    >
        <m-btn-link large
                  floating
        >
            <m-icon>{{ icon }}</m-icon>
        </m-btn-link>
        <slot name="items"></slot>
    </div>
</template>

<script type="text/babel">
    export default {
        props: {
            icon: {
                type: String,
                required: true
            },

            horizontal: {
                type: Boolean,
                default: false
            },

            toggle: {
                type: Boolean,
                default: false
            }
        },

        computed: {
            classes () {
                return {
                    'click-to-toggle': this.toggle,
                    'horizontal': this.horizontal
                }
            }
        },

        methods: {
            open () {
                $(this.$el).openFAB()
                this.$emit('open')
            },

            close () {
                $(this.$el).closeFAB()
                this.$emit('close')
            }
        }
    }
</script>


Scroller: Remove pulldown examples

Scroller: Remove pulldown examples

<template>

    <divider>A Horizontal Scroller without Scrollbar</divider>
    <scroller lock-y :scrollbar-x=false>

{{' ' + i + ' '}}

    </scroller>

    <divider>A Horizontal Scroller with Scrollbar</divider>
    <scroller lock-y scrollbar-x>

{{' ' + i + ' '}}

    </scroller>

    <divider>A Horizontal Scroller without bounce effect</divider>
    <scroller lock-y scrollbar-x :bounce=false>

{{' ' + i + ' '}}

    </scroller>

    <divider>A Vertical Scroller</divider>
    <scroller lock-x style="height:200px;">

placeholder {{i}}

    </scroller>

    <divider>A Vertical Scroller with scrollbar y</divider>
    <scroller lock-x scrollbar-y style="height:200px;">

placeholder {{i}}

    </scroller>

</template>

<template>

    <divider>A Horizontal Scroller without Scrollbar</divider>
    <scroller lock-y :scrollbar-x=false>

{{' ' + i + ' '}}

    </scroller>

    <divider>A Horizontal Scroller with Scrollbar</divider>
    <scroller lock-y scrollbar-x>

{{' ' + i + ' '}}

    </scroller>

    <divider>A Horizontal Scroller without bounce effect</divider>
    <scroller lock-y scrollbar-x :bounce=false>

{{' ' + i + ' '}}

    </scroller>

    <divider>A Vertical Scroller</divider>
    <scroller lock-x style="height:200px;">

placeholder {{i}}

    </scroller>

    <divider>A Vertical Scroller with scrollbar y</divider>
    <scroller lock-x scrollbar-y style="height:200px;">

placeholder {{i}}

    </scroller>

    <divider>Pull Down to Refresh</divider>
    <scroller lock-x scrollbar-y use-pulldown style="height:200px;" @pulldown:loading="load">

placeholder {{i}}

    </scroller>

    <divider>Chinese: 下拉刷新</divider>
    <scroller lock-x scrollbar-y use-pulldown :pulldown-config={content:'下拉刷新',downContent:'下拉刷新',upContent:'释放刷新',loadingContent:'加载中'} style="height:200px;" @pulldown:loading="load">

占位 {{i}}

    </scroller>

    <divider>custom pulldown html template</divider>
    <scroller lock-x scrollbar-y use-pulldown style="height:200px;" :pulldown-status.sync="pulldownStatus" @pulldown:loading="load">

placeholder {{i}}

          ↓
          <spinner type="ios-small"></spinner>

    </scroller>

</template>


Fix annoying warning about missing key for v-for.

<template>
<el-dialog
	:title="`${modalType}${extraTitleText} Site Profile`"
	:visible.sync="visible"
	:modal-append-to-body="false"
	:before-close="promptToSave"
>
	<el-form :model="formData">
		<el-form-item v-for="(value, key, index) in schema"
			:label="schema[key].label"
			:key="key"
			:class="{ 'is-required': schema[key].validation && schema[key].validation.rules[key][0].required === true }">
			<el-input
				v-if="schema[key].type === 'textarea'"
				v-model="formData[key]"
				type="textarea"
				:rows="5"
				:name="key"
				@blur="validate(index, key)"
				@change="validate(index, key)"
			>
				{{ schema[key].label }}
			</el-input>

				<rich-text v-model="formData[key]" />

				<el-checkbox-group v-model="formData[key]">

							<el-checkbox
								:label="category.id"
								:key="category.id"
								@change="(value) => resetChildCategories(value, category.id)"
							>
								{{ category.name }}
								<el-button
									v-if="!show[category.slug] && category.children && category.children.length"
									size="mini"
									@click="showSubcategories(category.slug)"
								>

									Show subcategories
								</el-button>
							</el-checkbox>

									<el-checkbox
										:label="subCategory.id"
										:key="subCategory.id"
										@change="(value) => toggleParent(value, category.id)"
									>
										{{ subCategory.name }}
									</el-checkbox>

				</el-checkbox-group>

				<el-row :gutter="4" v-for="(data, index) in formData[key]" :key="index">
					<el-col :span="6">
						<el-select v-model="data.id" placeholder="Social media platform">
							<el-option
								v-for="platform in availableSocialMedia"
								:label="platform.name"
								:value="platform.id"
								:key="platform.slug"
							>
							</el-option>
						</el-select>
					</el-col>
					<el-col :span="14">
						<el-input v-model="data.link" placeholder="URL">{{ schema[key].label }}</el-input>
					</el-col>
					<el-col :span="4">
						<el-button @click="removeSocialMediaPlatform(index)">Delete</el-button>
					</el-col>
				</el-row>

					<el-form-item>
						<el-button @click="addSocialMediaPlatform">Add social media</el-button>
					</el-form-item>

			<el-input v-else :name="key"
				v-model="formData[key]"
				@blur="validate(index, key)"
				@change="validate(index, key)"
			>
			{{ schema[key].label }}</el-input>

				{{ errors[index][key].map((error)=> error.message).join(' ') }}

		</el-form-item>
	</el-form>

		<el-button @click="cancel">Cancel</el-button>
		<el-button type="primary" @click="createProfile">Save profile</el-button>

</el-dialog>
</template>

<template>
<el-dialog
	:title="`${modalType}${extraTitleText} Site Profile`"
	:visible.sync="visible"
	:modal-append-to-body="false"
	:before-close="promptToSave"
>
	<el-form :model="formData">
		<el-form-item v-for="(value, key, index) in schema"
			:label="schema[key].label"
			:key="key"
			:class="{ 'is-required': schema[key].validation && schema[key].validation.rules[key][0].required === true }">
			<el-input
				v-if="schema[key].type === 'textarea'"
				v-model="formData[key]"
				type="textarea"
				:rows="5"
				:name="key"
				@blur="validate(index, key)"
				@change="validate(index, key)"
			>
				{{ schema[key].label }}
			</el-input>

				<rich-text v-model="formData[key]" />

				<el-checkbox-group v-model="formData[key]">

							<el-checkbox
								:label="category.id"
								:key="category.id"
								@change="(value) => resetChildCategories(value, category.id)"
							>
								{{ category.name }}
								<el-button
									v-if="!show[category.slug] && category.children && category.children.length"
									size="mini"
									@click="showSubcategories(category.slug)"
								>

									Show subcategories
								</el-button>
							</el-checkbox>

									<el-checkbox
										:label="subCategory.id"
										:key="subCategory.id"
										@change="(value) => toggleParent(value, category.id)"
									>
										{{ subCategory.name }}
									</el-checkbox>

				</el-checkbox-group>

				<el-row :gutter="4" v-for="(data, index) in formData[key]">
					<el-col :span="6">
						<el-select v-model="data.id" placeholder="Social media platform">
							<el-option
								v-for="platform in availableSocialMedia"
								:label="platform.name"
								:value="platform.id"
								:key="platform.slug"
							>
							</el-option>
						</el-select>
					</el-col>
					<el-col :span="14">
						<el-input v-model="data.link" placeholder="URL">{{ schema[key].label }}</el-input>
					</el-col>
					<el-col :span="4">
						<el-button @click="removeSocialMediaPlatform(index)">Delete</el-button>
					</el-col>
				</el-row>

					<el-form-item>
						<el-button @click="addSocialMediaPlatform">Add social media</el-button>
					</el-form-item>

			<el-input v-else :name="key"
				v-model="formData[key]"
				@blur="validate(index, key)"
				@change="validate(index, key)"
			>
			{{ schema[key].label }}</el-input>

				{{ errors[index][key].map((error)=> error.message).join(' ') }}

		</el-form-item>
	</el-form>

		<el-button @click="cancel">Cancel</el-button>
		<el-button type="primary" @click="createProfile">Save profile</el-button>

</el-dialog>
</template>


Bugfix: null and undefined values fail

Bugfix: null and undefined values fail

<template>

    {{keyString}}
    <input v-if="modifiable" class="tree-view-item-value" :class="getValueType(data)" v-model="valueString" @keyup.enter="onUpdateData" @blur="onUpdateData">
    {{ valueFormed }}
    {{ error }}

</template>


task_5 - fixing errors: change in degree of colors in the background (making background resizable in different screen sizes)

<template>

        <navbar></navbar>

          <sidebar></sidebar>

                  Welcome to Tamiat CMS

                  GitHub

              <router-link to="/">

                  <object type="image/svg+xml" data="static/img/tamiat-logo-icon-color.svg" style="pointer-events: none; height: 7rem"></object>

              </router-link>

Version v0.3.0

        <router-view :key="$route.path" />

</template>


group: Fix demo

group: Fix demo

<template>

    <group label-width="4em" label-margin-right="2em" label-align="right">
      <x-input title="上报人" v-model="value1"></x-input>
      <x-input placeholder="I'm placeholder">

      </x-input>
      <x-input title="上报人" v-model="value1"></x-input>
      <x-number title="Quantity" align="left" v-model="numberValue" button-style="round" :min="0" :max="5"></x-number>
      <datetime title="时   间" v-model="time1" value-text-align="left"></datetime>
      <selector title="隐患类别" :options="['工艺技术', '其他']" v-model="value2"></selector>
      <selector title="隐患类别" placeholder="Placeholder" :options="['工艺技术', '其他']" v-model="value7"></selector>
      <selector title="隐患类别" :options="['工艺技术', '其他']" v-model="value8"></selector>
      <x-input title="隐患部位" placeholder="必填" v-model="value3"></x-input>
      <x-input title="密码" type="password" placeholder="必填" v-model="value4"></x-input>
      <popup-picker title="请选择" :data="list" v-model="value5" value-text-align="left"></popup-picker>
      <popup-picker title="请选择" placeholder="Required" :data="list" v-model="value6" value-text-align="left"></popup-picker>
       <x-address title="地址选择" v-model="addressValue" raw-value :list="addressData" value-text-align="left"></x-address>
    </group>

</template>

<template>

    <group label-width="4em" label-margin-right="2em" label-align="right">
      <x-input title="上报人" v-model="value1"></x-input>
      <x-input title="图标" placeholder="I'm placeholder">

      </x-input>
    <group label-width="4em" label-margin-right="2em" label-align="left">
      <x-input title="上报人" v-model="value1"></x-input>
      <x-number title="Quantity" align="left" v-model="numberValue" button-style="round" :min="0" :max="5"></x-number>
      <datetime title="时   间" v-model="time1" value-text-align="left"></datetime>
      <selector title="隐患类别" :options="['工艺技术', '其他']" v-model="value2"></selector>
      <selector title="隐患类别" placeholder="Placeholder" :options="['工艺技术', '其他']" v-model="value7"></selector>
      <selector title="隐患类别" :options="['工艺技术', '其他']" v-model="value8"></selector>
      <x-input title="隐患部位" placeholder="必填" v-model="value3"></x-input>
      <x-input title="密码" type="password" placeholder="必填" v-model="value4"></x-input>
      <popup-picker title="请选择" :data="list" v-model="value5" value-text-align="left"></popup-picker>
      <popup-picker title="请选择" placeholder="Required" :data="list" v-model="value6" value-text-align="left"></popup-picker>
       <x-address title="地址选择" v-model="addressValue" raw-value :list="addressData" value-text-align="left"></x-address>
    </group>

</template>


avatar module

avatar module

<template>

Gavin Bates

PHPConf Host

I'm a cool paragraph that lives inside of an even cooler modal. Wins! I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!

    <button class="close-button" data-close aria-label="Close modal" type="button">
      ×
    </button>

Caption

</template>

<template>

Gavin Bates

PHPConf Host

I'm a cool paragraph that lives inside of an even cooler modal. Wins! I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!I'm a cool paragraph that lives inside of an even cooler modal. Wins!

    <button class="close-button" data-close aria-label="Close modal" type="button">
      ×
    </button>

</template>


canvas 方法测试

canvas 方法测试

<template>

Canvas

6、Clip

            <canvas id="canvas-clip" width="400" height="250">您的浏览器不支持 HTML5 canvas 标签。</canvas>

5、贝塞尔曲线

            <canvas id="canvas-bezier" width="400" height="250">您的浏览器不支持 HTML5 canvas 标签。</canvas>

4、渐变

            <canvas id="canvas-gran" width="400" height="250">您的浏览器不支持 HTML5 canvas 标签。</canvas>

3、文字

            <canvas id="canvas-text" width="400" height="250">您的浏览器不支持 HTML5 canvas 标签。</canvas>

2、圆形

            <canvas id="canvas-circle" width="400" height="250">您的浏览器不支持 HTML5 canvas 标签。</canvas>

1、直线

            <canvas id="canvas-line" width="400" height="250">您的浏览器不支持 HTML5 canvas 标签。</canvas>

</template>


改一下获取滚动量的方式 (#50)

改一下获取滚动量的方式 (#50)

<template>
  <veui-overlay class="veui-dialog"
    :open="localOpen"
    mode="NORMAL"
    :overlay-class="mergedOverlayClass"
    :ui="ui"
    ref="overlay">

        <slot name="title">弹窗标题</slot>
        <icon name="close"></icon>

<slot></slot>

<slot name="foot"><veui-button ui="primary" @click="$emit('ok')">确定</veui-button><veui-button @click="$emit('cancel')">取消</veui-button></slot>

  </veui-overlay>
</template>


fixed forgotten classname

fixed forgotten classname

<template>

    Folders

    No folders

0">
      <md-button v-for="folder in folders" :key="folder.id" class="folder md-raised" @click.native="$emit('openfolder', folder)">
        <md-icon>folder</md-icon>
        {{folder.shortname}}
      </md-button>

    Files

    No files

    <md-table class="filetable" v-if="files && files.length>0">
      <md-table-header>
        <md-table-row>
          <md-table-head style="max-width: 48px"></md-table-head>
          <md-table-head @click.native="setSort('name')" :class="{sorted: sortBy == 'name'}">
            File
            <md-icon v-if="sortBy != 'name'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'name' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'name' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head @click.native="setSort('type')" class="hide-on-small" :class="{sorted: sortBy == 'type'}">
            Type
            <md-icon v-if="sortBy != 'type'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'type' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'type' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head @click.native="setSort('size')" :class="{sorted: sortBy == 'size'}">
            Size
            <md-icon v-if="sortBy != 'size'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'size' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'size' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head @click.native="setSort('date')" class="hide-on-small" :class="{sorted: sortBy == 'date'}">
            Date
            <md-icon v-if="sortBy != 'date'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'date' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'date' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head v-if="admin" style="max-width: 48px"></md-table-head>
        </md-table-row>
      </md-table-header>
      <md-table-body>
        <md-table-row v-for="file in filesFormatted" :key="file.id">
          <md-table-cell @click.native="$emit('downloadfile', file)" class="filenamecell">
            <md-button class="md-icon-button">
                <md-icon>file_download</md-icon>
              </md-button>
          </md-table-cell>
          <md-table-cell @click.native="$emit('openfile', file)" class="filenamecell">

<md-icon>{{file.icon}}</md-icon> {{file.name}}

          </md-table-cell>
          <md-table-cell class="hide-on-small">{{file.type}}</md-table-cell>
          <md-table-cell>{{file.size | fileSize}}</md-table-cell>
          <md-table-cell class="hide-on-small">{{file.date}}</md-table-cell>
          <md-table-cell v-if="admin" class="cell-file-menu">
            <md-menu md-direction="top left" md-size="3">
              <md-button class="md-icon-button" md-menu-trigger>
                <md-icon>more_horiz</md-icon>
              </md-button>
              <md-menu-content>
                <md-menu-item @selected="$emit('renamefile', file)">
                  <md-icon>mode_edit</md-icon>
                  rename
                </md-menu-item>
                <md-menu-item @selected="$emit('movefile', file)">
                  <md-icon>folder_open</md-icon>
                  move
                </md-menu-item>
                <md-menu-item @selected="$emit('deletefile', file)">
                  <md-icon>delete</md-icon>
                  delete
                </md-menu-item>
              </md-menu-content>
            </md-menu>
          </md-table-cell>
        </md-table-row>
      </md-table-body>
    </md-table>

</template>

<template>

    Folders

    No folders

0">
      <md-button v-for="folder in folders" :key="folder.id" class="folder md-raised" @click.native="$emit('openfolder', folder)">
        <md-icon>folder</md-icon>
        {{folder.shortname}}
      </md-button>

    Files

    No files

    <md-table class="filetable" v-if="files && files.length>0">
      <md-table-header>
        <md-table-row>
          <md-table-head style="max-width: 48px"></md-table-head>
          <md-table-head @click.native="setSort('name')" :class="{sorted: sortBy == 'name'}">
            File
            <md-icon v-if="sortBy != 'name'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'name' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'name' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head @click.native="setSort('type')" class="hide-on-small" :class="{sorted: sortBy == 'type'}">
            Type
            <md-icon v-if="sortBy != 'type'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'type' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'type' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head @click.native="setSort('size')" :class="{sorted: sortBy == 'size'}">
            Size
            <md-icon v-if="sortBy != 'size'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'size' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'size' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head @click.native="setSort('date')" class="hide-on-small" :class="{sorted: sortBy == 'date'}">
            Date
            <md-icon v-if="sortBy != 'date'" style="opacity: 0">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'date' && !sortReverse">arrow_downward</md-icon>
            <md-icon v-if="sortBy == 'date' && sortReverse">arrow_upward</md-icon>
            </md-table-head>
          <md-table-head v-if="admin" style="max-width: 48px"></md-table-head>
        </md-table-row>
      </md-table-header>
      <md-table-body>
        <md-table-row v-for="file in filesFormatted" :key="file.id">
          <md-table-cell @click.native="$emit('downloadfile', file)" class="filenamecell">
            <md-button class="md-icon-button">
                <md-icon>file_download</md-icon>
              </md-button>
          </md-table-cell>
          <md-table-cell @click.native="$emit('openfile', file)" class="filenamecell">

<md-icon>{{file.icon}}</md-icon> {{file.name}}

          </md-table-cell>
          <md-table-cell>{{file.type}}</md-table-cell>
          <md-table-cell>{{file.size | fileSize}}</md-table-cell>
          <md-table-cell>{{file.date}}</md-table-cell>
          <md-table-cell v-if="admin" class="cell-file-menu">
            <md-menu md-direction="top left" md-size="3">
              <md-button class="md-icon-button" md-menu-trigger>
                <md-icon>more_horiz</md-icon>
              </md-button>
              <md-menu-content>
                <md-menu-item @selected="$emit('renamefile', file)">
                  <md-icon>mode_edit</md-icon>
                  rename
                </md-menu-item>
                <md-menu-item @selected="$emit('movefile', file)">
                  <md-icon>folder_open</md-icon>
                  move
                </md-menu-item>
                <md-menu-item @selected="$emit('deletefile', file)">
                  <md-icon>delete</md-icon>
                  delete
                </md-menu-item>
              </md-menu-content>
            </md-menu>
          </md-table-cell>
        </md-table-row>
      </md-table-body>
    </md-table>

</template>


Bugfix(Header): No redirect after pressing the logo on the page

<template>

                    <button id="toggle-sidebar-btn" type="button" class="btn btn-default navbar-btn"
                            aria-expanded="false">
                        Toggle sidepanel
                    </button>

                    <button id="toggle-nav-btn" type="button" class="btn btn-default navbar-btn" data-toggle="collapse"
                            data-target="#main-nav" aria-expanded="false">
                        Toggle navigation
                    </button>

{{ appName }}

                  <component v-bind:is="view.component" v-for="view in currentView" :key="view.name" :dataOptions="view.options">
                  </component>

</template>


Add dark theme to splash page (oops, forgot this one)

<template>

 {{ $store.state.title }}

</template>


refactor(z-spot): improve template format to respect 2 spaces indentation

<template>

    <z-knob v-if="knobEnabled" :qty="computedQty" :unit="unit" :min="min" :max="max"/>
    <z-slider v-if="sliderEnabled" :progress='progress'/>

        {{ label }}  {{ progressLabel }}

      <slot v-if="!imagePath" name="image"></slot>

        {{ progressLabel }}
        <slot></slot>

    <slot name="extension"></slot>

</template>

<template>

      <z-knob v-if="knobEnabled" :qty="computedQty" :unit="unit" :min="min" :max="max" />
      <z-slider v-if="sliderEnabled" :progress='progress' />

        {{label}}  {{progressLabel}}

        <slot v-if="!imagePath" name="image"></slot>

          {{progressLabel}}
          <slot></slot>

      <slot name="extension"></slot>

</template>


Fix copy&paste error on Calendar doc (#8)

<template>

Calendar

    <vk-calendar v-ref:demo
      :year.sync="props.year.demo.value | number"
      :month.sync="props.month.demo.value | number"
      :disabled-dates="props.disabledDates.demo.value"
      :selected-dates="props.selectedDates.demo.value"
      :min="props.min.demo.value"
      :max="props.max.demo.value">
    </vk-calendar>

      The vk-calendar component renders a table based calendar.

    <vk-tabs>
      <vk-tab label="Props">
        <vk-docs-props :props="props"></vk-docs-props>
      </vk-tab>
      <vk-tab label="Events">
        <vk-docs-events
          :events="events"
          :connect="$refs.demo">
        </vk-docs-events>
      </vk-tab>
      <vk-tab label="Example">
        <vk-docs-code :code="code"></vk-docs-code>
      </vk-tab>
    </vk-tabs>

</template>


Fix layouts of repo lists

Fix layouts of repo lists

<template>

Hiroto-K.github.io

This is a web pages of hiroto-k.

Apps

            {{ app.name }}

Links

            {{ link.name }}

Public Repositories

Error

Sorry, An error has occurred.

Public Repositories {{ reposLength }}

              {{ repo.full_name }}

GitHub Pages {{ gitHubPagesLength }}

              {{ gh_page.full_name }}

      <button @click="reloadRepos()" type="button" class="btn btn-primary btn-sm float-right mb-5">Reload repos</button>

    <page-footer></page-footer>

</template>


Add Amazon link for Splatoon 2

Add Amazon link for Splatoon 2

<template>

Splatoon 2

Map Schedules

                            <Dropdown :options="regions" v-model="selectedRegionKey" style="margin: 0 -12px"></Dropdown>

                                    <SplatfestBox :festival="festival" :now="now"></SplatfestBox>

                            <ScheduleBox class="main-schedule-box splatfest-schedule-box tilt-right" cssClass="regular" :schedules="regular" :festival="festival" :now="now"></ScheduleBox>

                            <ScheduleBox class="main-schedule-box tilt-left" cssClass="regular" :schedules="regular" :now="now"></ScheduleBox>

                            <ScheduleBox class="main-schedule-box tilt-right" cssClass="ranked" :schedules="ranked" :now="now"></ScheduleBox>

                            <ScheduleBox class="main-schedule-box tilt-left" cssClass="league" :schedules="league" :now="now"></ScheduleBox>

 now">

                                    <SplatfestBox :festival="festival" :now="now"></SplatfestBox>

                                    <SalmonRunBox :coop="coop" :coopCalendar="coopCalendar" :now="now"></SalmonRunBox>

                This website is not affiliated with Nintendo.
                All product names, logos, and brands are property of their respective owners.
                About

        <Modal v-if="aboutOpen" @close="aboutOpen = false">

                                Hello!

                                Splatoon2.ink shows the current and upcoming map schedules for
                                Splatoon 2.

                                This site was built with Vue.js
                                and Bulma.
                                All data comes from the SplatNet 2 API.

                                Follow me on Twitter
                                or email me with any questions!

Notes & Issues

                                Salmon Run:
                                The SplatNet API doesn't provide a way to see maps, available weapons, or upcoming Salmon Run times.
                                Upcoming times are provided by
                                thejellydude.

                                Splatfests:
                                Only North American Splatfests are displayed since the API seems to be restricted to the region you purchased the game from.
                                I may manually enter times for Splatfests in other regions in the future.

        </Modal>

</template>


mock编辑切换状态修复

mock编辑切换状态修复

<template>

            <el-dialog title="添加项目" v-model="addProjectStatus">
                <el-input type="input" v-model="newProjectName"></el-input>

                    <el-button @click="addProjectStatus = false">取 消</el-button>
                    <el-button type="primary" @click="addProject">确 定</el-button>

            </el-dialog>
            <el-button type="primary" :plain="true" icon="plus" @click="addProjectStatus = true">添加项目</el-button>

                    {{item.name}}

            <el-dialog size="large" title="路径配置" v-model="addPathStatus">

Request Url

                        <el-input type="input" v-model="pathRequest.url"></el-input>

Request Method

                        <el-input type="input" v-model="pathRequest.method"></el-input>

Status Code

                        <el-input type="input" v-model="pathResponse.status"></el-input>

Response Headers

                        <el-input type="input" v-model="pathResponse.headers"></el-input>

Response Body

                        <codemirror v-model="pathResponse.body" :options="editorOption"></codemirror>

                    <el-button @click="cancelPath">取 消</el-button>
                    <el-button type="primary"  @click="savePath">保 存</el-button>

            </el-dialog>

当前项目：{{currentProject.name}}

            <el-button type="primary" :plain="true" icon="plus" @click="toAddPath">添加接口</el-button>
            <el-table
                class="mock-path__list"
                :data="projectPaths"
                border
                @selection-change="handlePathChange">
                <el-table-column
                type="selection"
                width="55">
                </el-table-column>
                <el-table-column
                prop="request.url"
                label="路径"
                width="400">
                </el-table-column>
                <el-table-column
                prop="request.method"
                label="Method"
                width="100">
                </el-table-column>
                <el-table-column label="操作">
                <template scope="scope">
                    <el-button
                    size="small"
                    @click="handleEdit(scope.$index, scope.row)">
                    编辑</el-button>
                    <el-button
                    size="small"
                    type="danger"
                    @click="handleDelete(scope.$index, scope.row)">
                    删除</el-button>
                </template>
                </el-table-column>
            </el-table>

</template>


update

update

<template lang="html">

0">{{totalCount}}

             1231四渡赤水

￥{{totalPrice}}

另需配送费￥{{deliveryPrice}}元

					￥{{minPrice}}元起送

</template>

<template lang="html">

{{totalCount}}

             1231四渡赤水

￥{{totalPrice}}

另需配送费￥{{deliveryPrice}}元

					￥{{minPrice}}元起送

</template>