remove console.log

remove console.log

<template>

      <headline>
        {{collection}} - Watch
        <collection-dropdown
          class="icon-medium icon-black"
          :index="index"
          :collection="collection">
        </collection-dropdown>
      </headline>

      <filters
        @filters-basic-search="basicSearch"
        @filters-raw-search="rawSearch"
        @filters-refresh-search="refreshSearch"
        label-search-button="Apply filters"
        label-complex-query="Quick search disabled, click to open filter builder"
        :available-filters="availableFilters"
        :quick-filter-enabled="false"
        :sorting-enabled="false"
        :raw-filter="rawFilter"
        :basic-filter="basicFilter"
        :format-from-basic-search="formatFromBasicSearch"
        :format-sort="formatSort"
        :set-basic-filter="setBasicFilter"
        :basic-filter-form="basicFilterForm">
      </filters>

          <subscription-controls
            @realtime-toggle-subscription="toggleSubscription"
            @realtime-scroll-glue="setScrollGlue"
            @realtime-clear-messages="clear"
            :subscribed="subscribed"
            :warning="warning">
          </subscription-controls>

        <subscription-controls
          @realtime-toggle-subscription="toggleSubscription"
          @realtime-scroll-glue="setScrollGlue"
          @realtime-clear-messages="clear"
          :subscribed="subscribed"
          :warning="warning">
        </subscription-controls>

            You have not received any notification yet

          <notification
            :notification="notification">
          </notification>

</template>


Add source

Add source

<template lang="pug">
.menu__window
  .menu__window-buttons
    .menu__window-button.menu__window-button-download(@click="getZipClick")
      cloud-download.menu__window-icon
      .menu__window-button-text Download ZIP

    .menu__window-shadow

    .menu__window-button(@click="randomBGClick")
      .menu__window-button-text Random BG

    .menu__window-button(@click="getCurrentBGClick")
      .menu__window-button-text Get this BG

    input#urltextbox.menu__window-button.textbox.noclick(
      placeholder="Paste a background URL here",
      v-model="bgURL"
    )

    //- .menu__window-section-container
    //-   .menu__window-button.noclick.small
    //-     .menu__window-button-text Image Height

    //-   input.menu__window-button.length__slider(type="range")
    //-   input.menu__window-button.textbox.px(placeholder="px")

    .menu__window-section-container
      .menu__window-button.noclick.small
        .menu__window-button-text Zoom Scale

      .menu__window-scale__container
        input#scale50.menu__window-scale__input(
          name="scale",
          type="radio",
          value="50",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale50")
          .menu__window-scale__button 50%

        input#scale75.menu__window-scale__input(
          name="scale",
          type="radio",
          value="75",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale75")
          .menu__window-scale__button 75%

        input#scale100.menu__window-scale__input(
          name="scale",
          type="radio",
          value="100",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale100")
          .menu__window-scale__button 100%

        input#scale125.menu__window-scale__input(
          name="scale",
          type="radio",
          value="125",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale125")
          .menu__window-scale__button 125%

      a.menu__window-button(:href="loginUrl", v-if="!$store.state.user.id")
        .menu__window-button-text Login with Steam
      .menu__window-button(@click="logout", v-else)
        .menu__window-button-text Logout

  .menu__window-credits
    .menu__window-button.noclick.small
      .menu__window-button-text Our:
    .menu__window-multi-container
      a.menu__window-multi-label(
        href="https://github.com/sapic/sapic",
        target="_blank"
      )
        .menu__window-multi-button GitHub
      a.menu__window-multi-label(
        href="https://steamcommunity.com/groups/sapic",
        target="_blank"
      )
        .menu__window-multi-button Group
      a.menu__window-multi-label(
        href="https://old.steam.design/?utm_source=steam.design",
        target="_blank",
        rel="noopener noreferrer"
      )
        .menu__window-multi-button Old Site

      a.menu__window-multi-label(
        :href="extensionLink",
        target="_blank",
        v-if="extensionLink"
      )
        .menu__window-multi-button Extension

    img.menu__window-discord-image(
      src="https://discordapp.com/api/guilds/304986224467378177/widget.png?style=banner2",
      @click="$store.dispatch('openDiscord')"
    )
    .menu__window-credits-text Made with ❤ by TrueCarry and The Oddball
</template>

<script>
import CloudDownload from './CloudDownload'

export default {
  components: {
    CloudDownload,
  },

  computed: {
    previewScale: {
      get () {
        return this.$store.state.previewScale
      },
      set (value) {
        this.$store.commit('setPreviewScale', value)
        this.$store.dispatch('trackClick', ['setPreviewScale', value])
      },
    },
    /* eslint-disable */
    bgURL: {
      set (value) {
        if (value.match(/\.(?:jpeg|jpg|png|webm)$/i)) {
          this.$store.commit('setBackgroundURL', value)
        }
      },
      get () {
      }
    },
    /* eslint-enable */

    loginUrl () {
      const returnUrl = encodeURIComponent(
        process.env.NODE_ENV === 'production'
          ? 'https://steam.design'
          : 'http://localhost:3000',
      )
      const realm = encodeURIComponent(
        process.env.NODE_ENV === 'production'
          ? 'https://steam.design'
          : 'http://localhost:3000',
      )
      return `https://steamcommunity.com/openid/login?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.mode=checkid_setup&openid.return_to=${returnUrl}&openid.realm=${realm}&openid.ns.sreg=http%3A%2F%2Fopenid.net%2Fextensions%2Fsreg%2F1.1&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select`
    },

    extensionLink () {
      if (navigator.userAgent.indexOf('Chrome') !== -1) {
        return 'https://chrome.google.com/webstore/detail/steam-profile-assistant/mjmabgdoainclinjecbkdancpamdiaih'
      } else if (navigator.userAgent.indexOf('Firefox') !== -1) {
        return 'https://addons.mozilla.org/en-US/firefox/addon/steam-design-buttons/'
      }

      return null
    },
  },

  methods: {
    logout () {
      this.$store.commit('logout')
    },

    randomBGClick () {
      this.$store.dispatch('randomBackground')
      this.$store.dispatch('trackClick', ['randomBGButton'])
    },

    getZipClick () {
      this.$store.dispatch('downloadZip')
      this.$store.dispatch('trackClick', ['getZIPButton'])
    },

    getCurrentBGClick () {
      this.$store.dispatch('getCurrentBg')
      this.$store.dispatch('trackClick', ['getBGButton'])
    },
  },
}
</script>

<style scoped lang="stylus">
@import '../../../assets/css/color'

.menu__window
  height 100%
  width 250px
  background $color-main
  flex-shrink 0
  user-select none
  display flex
  flex-direction column
  align-items center

.menu__window-section-container
  margin-top 10px

.menu__window-discord-image
  margin 10px 10px 0 10px
  transition filter 0.25s ease
  text-align center
  display flex
  cursor pointer
  height 55px
  border-radius 3px

  &:hover
    filter drop-shadow(0px 3px 10px $color-black)
    transition filter 0.25s ease

.menu__window-button
  background-color $color-button
  transition background-color 0.25s ease
  border-radius 3px
  cursor pointer
  margin 10px 10px 0 10px
  text-align center
  display flex
  align-items center
  justify-content center
  text-decoration none

  &:hover
    transition background-color 0.25s ease
    background-color $color-button-hover

  &.noclick
    cursor default
    filter initial
    background-color $color-button

  &.textbox
    cursor text
    width 232px
    height 30px
    border none
    outline none
    transition box-shadow 0.25s ease-in-out

    &:focus
      box-shadow 0 0 40px rgba(0, 0, 0, 0.4)

    &.px
      width 55px

  &.length__slider
    margin 22px 10px 10px 0
    -webkit-appearance none
    width 164px
    height 6px
    background $color-button-hover
    outline none
    margin-left 5%
    float left

    &::-webkit-slider-thumb
      -webkit-appearance none
      appearance none
      width 15px
      height 15px
      background linear-gradient(45deg, $color-button-gradient-1 0%, $color-button-gradient-2 100%)
      box-shadow 0 0 20px rgba(78, 115, 238, 0.5)
      cursor pointer
      border-radius 50%

  &.small
    .menu__window-button-text
      margin 4px
      font-size 14px

.menu__window-button-text
  bottom 11px
  font-size 17px
  font-weight 300
  color $color-white
  margin 15px 0
  text-decoration none

.menu__window-button-download
  background linear-gradient(45deg, $color-button-gradient-1 0%, $color-button-gradient-2 100%)
  position relative
  z-index 2
  opacity 0.8
  transition opacity 0.25s ease

  &:hover
    opacity 1
    transition opacity 0.25s ease

.menu__window-shadow
  background linear-gradient(45deg, $color-button-gradient-1 0%, $color-button-gradient-2 100%)
  filter blur(12px)
  position relative
  height 53px
  top -63px
  margin 10px 10px -63px 10px
  width 232px
  opacity 0.5

.menu__window-item
  transition background-color 0.25s ease
  cursor pointer
  margin 10px 10px 0 10px
  text-align center
  transition filter 0.25s ease
  display flex
  align-items center
  justify-content center

  &:hover
    transition filter 0.25s ease
    filter drop-shadow(0px 3px 10px $color-black)
    transition background-color 0.25s ease
    background-color $color-button-hover

.menu__window-item-text
  bottom 11px
  color $color-white
  margin 0

#urltextbox
  color $color-white

.menu__window-scale__container
  display flex
  flex-direction row
  flex-wrap wrap
  justify-content space-between
  padding 0 10px

.menu__window-scale__input
  display none

  &:checked
    +label
      background #272d38
      cursor default

.menu__window-scale__label
  transition all 0.25s ease
  background-color $color-button
  border-radius 3px
  cursor pointer
  margin-top 5px
  text-align center
  width 49%
  flex 0 0 auto
  padding 12px

  &:hover
    background $color-button-hover

.menu__window-scale__button
  bottom 11px
  text-shadow 0px 4px 10px $color-black
  color $color-white

.menu__window-credits
  position absolute
  bottom 0

.menu__window-credits-text
  color $color-white
  font-size 11px
  margin-bottom 5px
  margin-top 5px
  text-align center

.menu__window-icon
  color $color-white
  margin-right 6px
  width 20px
  height 20px
  color white
  fill currentColor

.menu__window-multi-container
  display flex
  flex-direction row
  flex-wrap wrap
  justify-content space-between
  padding 0 10px
  max-width 250px

.menu__window-multi-label
  transition all 0.25s ease
  background-color $color-button
  border-radius 3px
  cursor pointer
  margin-top 5px
  text-align center
  width 49%
  flex 0 0 auto
  padding 12px
  text-decoration none

  &:hover
    background $color-button-hover

.menu__window-multi-button
  bottom 11px
  text-shadow 0px 4px 10px $color-black
  color $color-white

*
  box-sizing border-box
</style>

<template lang="pug">
.menu__window
  .menu__window-buttons
    .menu__window-button.menu__window-button-download(@click="getZipClick")
      cloud-download.menu__window-icon
      .menu__window-button-text Download ZIP

    .menu__window-shadow

    .menu__window-button(@click="randomBGClick")
      .menu__window-button-text Random BG

    .menu__window-button(@click="getCurrentBGClick")
      .menu__window-button-text Get this BG

    input#urltextbox.menu__window-button.textbox.noclick(
      placeholder="Paste a background URL here",
      v-model="bgURL"
    )

    //- .menu__window-section-container
    //-   .menu__window-button.noclick.small
    //-     .menu__window-button-text Image Height

    //-   input.menu__window-button.length__slider(type="range")
    //-   input.menu__window-button.textbox.px(placeholder="px")

    .menu__window-section-container
      .menu__window-button.noclick.small
        .menu__window-button-text Zoom Scale

      .menu__window-scale__container
        input#scale50.menu__window-scale__input(
          name="scale",
          type="radio",
          value="50",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale50")
          .menu__window-scale__button 50%

        input#scale75.menu__window-scale__input(
          name="scale",
          type="radio",
          value="75",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale75")
          .menu__window-scale__button 75%

        input#scale100.menu__window-scale__input(
          name="scale",
          type="radio",
          value="100",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale100")
          .menu__window-scale__button 100%

        input#scale125.menu__window-scale__input(
          name="scale",
          type="radio",
          value="125",
          v-model="previewScale"
        )
        label.menu__window-scale__label(for="scale125")
          .menu__window-scale__button 125%

      a.menu__window-button(:href="loginUrl", v-if="!$store.state.user.id")
        .menu__window-button-text Login with Steam
      .menu__window-button(@click="logout", v-else)
        .menu__window-button-text Logout

  .menu__window-credits
    .menu__window-button.noclick.small
      .menu__window-button-text Our:
    .menu__window-multi-container
      a.menu__window-multi-label(
        href="https://github.com/sapic/sapic",
        target="_blank"
      )
        .menu__window-multi-button GitHub
      a.menu__window-multi-label(
        href="https://steamcommunity.com/groups/sapic",
        target="_blank"
      )
        .menu__window-multi-button Group
      a.menu__window-multi-label(
        href="https://old.steam.design",
        target="_blank",
        rel="noopener noreferrer"
      )
        .menu__window-multi-button Old Site

      a.menu__window-multi-label(
        :href="extensionLink",
        target="_blank",
        v-if="extensionLink"
      )
        .menu__window-multi-button Extension

    img.menu__window-discord-image(
      src="https://discordapp.com/api/guilds/304986224467378177/widget.png?style=banner2",
      @click="$store.dispatch('openDiscord')"
    )
    .menu__window-credits-text Made with ❤ by TrueCarry and The Oddball
</template>

<script>
import CloudDownload from './CloudDownload'

export default {
  components: {
    CloudDownload,
  },

  computed: {
    previewScale: {
      get () {
        return this.$store.state.previewScale
      },
      set (value) {
        this.$store.commit('setPreviewScale', value)
        this.$store.dispatch('trackClick', ['setPreviewScale', value])
      },
    },
    /* eslint-disable */
    bgURL: {
      set (value) {
        if (value.match(/\.(?:jpeg|jpg|png|webm)$/i)) {
          this.$store.commit('setBackgroundURL', value)
        }
      },
      get () {
      }
    },
    /* eslint-enable */

    loginUrl () {
      const returnUrl = encodeURIComponent(
        process.env.NODE_ENV === 'production'
          ? 'https://steam.design'
          : 'http://localhost:3000',
      )
      const realm = encodeURIComponent(
        process.env.NODE_ENV === 'production'
          ? 'https://steam.design'
          : 'http://localhost:3000',
      )
      return `https://steamcommunity.com/openid/login?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.mode=checkid_setup&openid.return_to=${returnUrl}&openid.realm=${realm}&openid.ns.sreg=http%3A%2F%2Fopenid.net%2Fextensions%2Fsreg%2F1.1&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select`
    },

    extensionLink () {
      if (navigator.userAgent.indexOf('Chrome') !== -1) {
        return 'https://chrome.google.com/webstore/detail/steam-profile-assistant/mjmabgdoainclinjecbkdancpamdiaih'
      } else if (navigator.userAgent.indexOf('Firefox') !== -1) {
        return 'https://addons.mozilla.org/en-US/firefox/addon/steam-design-buttons/'
      }

      return null
    },
  },

  methods: {
    logout () {
      this.$store.commit('logout')
    },

    randomBGClick () {
      this.$store.dispatch('randomBackground')
      this.$store.dispatch('trackClick', ['randomBGButton'])
    },

    getZipClick () {
      this.$store.dispatch('downloadZip')
      this.$store.dispatch('trackClick', ['getZIPButton'])
    },

    getCurrentBGClick () {
      this.$store.dispatch('getCurrentBg')
      this.$store.dispatch('trackClick', ['getBGButton'])
    },
  },
}
</script>

<style scoped lang="stylus">
@import '../../../assets/css/color'

.menu__window
  height 100%
  width 250px
  background $color-main
  flex-shrink 0
  user-select none
  display flex
  flex-direction column
  align-items center

.menu__window-section-container
  margin-top 10px

.menu__window-discord-image
  margin 10px 10px 0 10px
  transition filter 0.25s ease
  text-align center
  display flex
  cursor pointer
  height 55px
  border-radius 3px

  &:hover
    filter drop-shadow(0px 3px 10px $color-black)
    transition filter 0.25s ease

.menu__window-button
  background-color $color-button
  transition background-color 0.25s ease
  border-radius 3px
  cursor pointer
  margin 10px 10px 0 10px
  text-align center
  display flex
  align-items center
  justify-content center
  text-decoration none

  &:hover
    transition background-color 0.25s ease
    background-color $color-button-hover

  &.noclick
    cursor default
    filter initial
    background-color $color-button

  &.textbox
    cursor text
    width 232px
    height 30px
    border none
    outline none
    transition box-shadow 0.25s ease-in-out

    &:focus
      box-shadow 0 0 40px rgba(0, 0, 0, 0.4)

    &.px
      width 55px

  &.length__slider
    margin 22px 10px 10px 0
    -webkit-appearance none
    width 164px
    height 6px
    background $color-button-hover
    outline none
    margin-left 5%
    float left

    &::-webkit-slider-thumb
      -webkit-appearance none
      appearance none
      width 15px
      height 15px
      background linear-gradient(45deg, $color-button-gradient-1 0%, $color-button-gradient-2 100%)
      box-shadow 0 0 20px rgba(78, 115, 238, 0.5)
      cursor pointer
      border-radius 50%

  &.small
    .menu__window-button-text
      margin 4px
      font-size 14px

.menu__window-button-text
  bottom 11px
  font-size 17px
  font-weight 300
  color $color-white
  margin 15px 0
  text-decoration none

.menu__window-button-download
  background linear-gradient(45deg, $color-button-gradient-1 0%, $color-button-gradient-2 100%)
  position relative
  z-index 2
  opacity 0.8
  transition opacity 0.25s ease

  &:hover
    opacity 1
    transition opacity 0.25s ease

.menu__window-shadow
  background linear-gradient(45deg, $color-button-gradient-1 0%, $color-button-gradient-2 100%)
  filter blur(12px)
  position relative
  height 53px
  top -63px
  margin 10px 10px -63px 10px
  width 232px
  opacity 0.5

.menu__window-item
  transition background-color 0.25s ease
  cursor pointer
  margin 10px 10px 0 10px
  text-align center
  transition filter 0.25s ease
  display flex
  align-items center
  justify-content center

  &:hover
    transition filter 0.25s ease
    filter drop-shadow(0px 3px 10px $color-black)
    transition background-color 0.25s ease
    background-color $color-button-hover

.menu__window-item-text
  bottom 11px
  color $color-white
  margin 0

#urltextbox
  color $color-white

.menu__window-scale__container
  display flex
  flex-direction row
  flex-wrap wrap
  justify-content space-between
  padding 0 10px

.menu__window-scale__input
  display none

  &:checked
    +label
      background #272d38
      cursor default

.menu__window-scale__label
  transition all 0.25s ease
  background-color $color-button
  border-radius 3px
  cursor pointer
  margin-top 5px
  text-align center
  width 49%
  flex 0 0 auto
  padding 12px

  &:hover
    background $color-button-hover

.menu__window-scale__button
  bottom 11px
  text-shadow 0px 4px 10px $color-black
  color $color-white

.menu__window-credits
  position absolute
  bottom 0

.menu__window-credits-text
  color $color-white
  font-size 11px
  margin-bottom 5px
  margin-top 5px
  text-align center

.menu__window-icon
  color $color-white
  margin-right 6px
  width 20px
  height 20px
  color white
  fill currentColor

.menu__window-multi-container
  display flex
  flex-direction row
  flex-wrap wrap
  justify-content space-between
  padding 0 10px
  max-width 250px

.menu__window-multi-label
  transition all 0.25s ease
  background-color $color-button
  border-radius 3px
  cursor pointer
  margin-top 5px
  text-align center
  width 49%
  flex 0 0 auto
  padding 12px
  text-decoration none

  &:hover
    background $color-button-hover

.menu__window-multi-button
  bottom 11px
  text-shadow 0px 4px 10px $color-black
  color $color-white

*
  box-sizing border-box
</style>


fix(App): putImageData

fix(App): putImageData

<template>
  <uni-canvas
    :canvas-id="canvasId"
    :disable-scroll="disableScroll"
    v-on="_listeners"
  >
    <canvas
      ref="canvas"
      width="300"
      height="150"
    />
    <div
      style="
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        overflow: hidden;
      "
    >
      <slot />
    </div>
    <v-uni-resize-sensor
      ref="sensor"
      @resize="_resize"
    />
  </uni-canvas>
</template>
<script>
import {
  subscriber
} from 'uni-mixins'

import {
  pixelRatio,
  wrapper
} from 'uni-helpers/hidpi'

import saveImage from 'uni-platform/helpers/save-image'
import { getSameOriginUrl } from 'uni-platform/helpers/file'

function resolveColor (color) {
  color = color.slice(0)
  color[3] = color[3] / 255
  return 'rgba(' + color.join(',') + ')'
}

function processTouches (target, touches) {
  return ([]).map.call(touches, (touch) => {
    var boundingClientRect = target.getBoundingClientRect()
    return {
      identifier: touch.identifier,
      x: touch.clientX - boundingClientRect.left,
      y: touch.clientY - boundingClientRect.top
    }
  })
}

var tempCanvas
function getTempCanvas (width = 0, height = 0) {
  if (!tempCanvas) {
    tempCanvas = document.createElement('canvas')
  }
  tempCanvas.width = width
  tempCanvas.height = height
  return tempCanvas
}

export default {
  name: 'Canvas',
  mixins: [subscriber],
  props: {
    canvasId: {
      type: String,
      default: ''
    },
    disableScroll: {
      type: [Boolean, String],
      default: false
    }
  },
  data () {
    return {
      actionsWaiting: false
    }
  },
  computed: {
    id () {
      return this.canvasId
    },
    _listeners () {
      var $listeners = Object.assign({}, this.$listeners)
      var events = ['touchstart', 'touchmove', 'touchend']
      events.forEach(event => {
        var existing = $listeners[event]
        var eventHandler = []
        if (existing) {
          eventHandler.push(($event) => {
            this.$trigger(event, Object.assign({}, $event, {
              touches: processTouches($event.currentTarget, $event.touches),
              changedTouches: processTouches($event.currentTarget, $event
                .changedTouches)
            }))
          })
        }
        if (this.disableScroll && event === 'touchmove') {
          eventHandler.push(this._touchmove)
        }
        $listeners[event] = eventHandler
      })
      return $listeners
    }
  },
  created () {
    this._actionsDefer = []
    this._images = {}
  },
  mounted () {
    this._resize()
  },
  beforeDestroy () {
    const canvas = this.$refs.canvas
    canvas.height = canvas.width = 0
  },
  methods: {
    _handleSubscribe ({
      type,
      data = {}
    }) {
      var method = this[type]
      if (type.indexOf('_') !== 0 && typeof method === 'function') {
        method(data)
      }
    },
    _resize (size) {
      var canvas = this.$refs.canvas
      var hasChanged = !size || (canvas.width !== Math.floor(size.width * pixelRatio) || canvas.height !== Math.floor(size.height * pixelRatio))
      if (!hasChanged) return
      if (canvas.width > 0 && canvas.height > 0) {
        var context = canvas.getContext('2d')
        var imageData = context.getImageData(0, 0, canvas.width, canvas.height)
        wrapper(canvas)
        context.putImageData(imageData, 0, 0)
      } else {
        wrapper(canvas)
      }
    },
    _touchmove (event) {
      event.preventDefault()
    },
    actionsChanged ({
      actions,
      reserve,
      callbackId
    }) {
      var self = this
      if (!actions) {
        return
      }
      if (this.actionsWaiting) {
        this._actionsDefer.push([actions, reserve, callbackId])
        return
      }
      var canvas = this.$refs.canvas
      var c2d = canvas.getContext('2d')
      if (!reserve) {
        c2d.fillStyle = '#000000'
        c2d.strokeStyle = '#000000'
        c2d.shadowColor = '#000000'
        c2d.shadowBlur = 0
        c2d.shadowOffsetX = 0
        c2d.shadowOffsetY = 0
        c2d.setTransform(1, 0, 0, 1, 0, 0)
        c2d.clearRect(0, 0, canvas.width, canvas.height)
      }
      this.preloadImage(actions)
      for (let index = 0; index < actions.length; index++) {
        const action = actions[index]
        let method = action.method
        const data = action.data
        if (/^set/.test(method) && method !== 'setTransform') {
          const method1 = method[3].toLowerCase() + method.slice(4)
          let color
          if (method1 === 'fillStyle' || method1 === 'strokeStyle') {
            if (data[0] === 'normal') {
              color = resolveColor(data[1])
            } else if (data[0] === 'linear') {
              const LinearGradient = c2d.createLinearGradient(...data[1])
              data[2].forEach(function (data2) {
                const offset = data2[0]
                const color = resolveColor(data2[1])
                LinearGradient.addColorStop(offset, color)
              })
              color = LinearGradient
            } else if (data[0] === 'radial') {
              const x = data[1][0]
              const y = data[1][1]
              const r = data[1][2]
              const LinearGradient = c2d.createRadialGradient(x, y, 0, x, y, r)
              data[2].forEach(function (data2) {
                const offset = data2[0]
                const color = resolveColor(data2[1])
                LinearGradient.addColorStop(offset, color)
              })
              color = LinearGradient
            } else if (data[0] === 'pattern') {
              const loaded = this.checkImageLoaded(data[1], actions.slice(index + 1), callbackId,
                function (image) {
                  if (image) {
                    c2d[method1] = c2d.createPattern(image, data[2])
                  }
                })
              if (!loaded) {
                break
              }
              continue
            }
            c2d[method1] = color
          } else if (method1 === 'globalAlpha') {
            c2d[method1] = data[0] / 255
          } else if (method1 === 'shadow') {
            var _ = ['shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowColor']
            data.forEach(function (color_, method_) {
              c2d[_[method_]] = _[method_] === 'shadowColor' ? resolveColor(color_) : color_
            })
          } else if (method1 === 'fontSize') {
            const font = c2d.__font__ || c2d.font
            c2d.__font__ = c2d.font = font.replace(/\d+\.?\d*px/, data[0] + 'px')
          } else if (method1 === 'lineDash') {
            c2d.setLineDash(data[0])
            c2d.lineDashOffset = data[1] || 0
          } else if (method1 === 'textBaseline') {
            if (data[0] === 'normal') {
              data[0] = 'alphabetic'
            }
            c2d[method1] = data[0]
          } else if (method1 === 'font') {
            c2d.__font__ = c2d.font = data[0]
          } else {
            c2d[method1] = data[0]
          }
        } else if (method === 'fillPath' || method === 'strokePath') {
          method = method.replace(/Path/, '')
          c2d.beginPath()
          data.forEach(function (data_) {
            c2d[data_.method].apply(c2d, data_.data)
          })
          c2d[method]()
        } else if (method === 'fillText') {
          c2d.fillText.apply(c2d, data)
        } else if (method === 'drawImage') {
          var A = (function () {
            var dataArray = [...data]
            var url = dataArray[0]
            var otherData = dataArray.slice(1)
            self._images = self._images || {}
            if (!self.checkImageLoaded(url, actions.slice(index + 1), callbackId, function (
              image) {
              if (image) {
                c2d.drawImage.apply(c2d, [image].concat([...otherData.slice(4, 8)],
                  [...otherData.slice(0, 4)]))
              }
            })) return 'break'
          }())
          if (A === 'break') {
            break
          }
        } else {
          if (method === 'clip') {
            data.forEach(function (data_) {
              c2d[data_.method].apply(c2d, data_.data)
            })
            c2d.clip()
          } else {
            c2d[method].apply(c2d, data)
          }
        }
      }
      if (!this.actionsWaiting && callbackId) {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg: 'drawCanvas:ok'
          }
        }, this.$page.id)
      }
    },
    preloadImage: function (actions) {
      var self = this
      actions.forEach(function (action) {
        var method = action.method
        var data = action.data
        var src = ''
        if (method === 'drawImage') {
          src = data[0]
          src = self.$getRealPath(src)
          data[0] = src
        } else if (method === 'setFillStyle' && data[0] === 'pattern') {
          src = data[1]
          src = self.$getRealPath(src)
          data[1] = src
        }
        if (src && !self._images[src]) {
          loadImage()
        }
        /**
         * 加载图像
         */
        function loadImage () {
          const image = self._images[src] = new Image()
          image.onload = function () {
            image.ready = true
          }

          // 安卓 WebView 除本地路径无跨域问题
          if (__PLATFORM__ === 'app-plus' && navigator.vendor === 'Google Inc.') {
            if (src.indexOf('file://') === 0) {
              image.crossOrigin = 'anonymous'
            }
            image.src = src
            return
          }
          getSameOriginUrl(src).then(src => {
            image.src = src
          }).catch(() => {
            image.src = src
          })
        }
      })
    },
    checkImageLoaded: function (src, actions, callbackId, fn) {
      var self = this
      var image = this._images[src]
      if (image.ready) {
        fn(image)
        return true
      } else {
        this._actionsDefer.unshift([actions, true])
        this.actionsWaiting = true
        image.onload = function () {
          image.ready = true
          fn(image)
          self.actionsWaiting = false
          var actions = self._actionsDefer.slice(0)
          self._actionsDefer = []
          for (var action = actions.shift(); action;) {
            self.actionsChanged({
              actions: action[0],
              reserve: action[1],
              callbackId
            })
            action = actions.shift()
          }
        }
        return false
      }
    },
    getImageData ({
      x = 0,
      y = 0,
      width,
      height,
      destWidth,
      destHeight,
      hidpi = true,
      dataType,
      quality = 1,
      type = 'png',
      callbackId
    }) {
      const canvas = this.$refs.canvas
      let data
      const maxWidth = canvas.offsetWidth - x
      width = width ? Math.min(width, maxWidth) : maxWidth
      const maxHeight = canvas.offsetHeight - y
      height = height ? Math.min(height, maxHeight) : maxHeight
      if (!hidpi) {
        if (!destWidth && !destHeight) {
          destWidth = Math.round(width * pixelRatio)
          destHeight = Math.round(height * pixelRatio)
        } else if (!destWidth) {
          destWidth = Math.round(width / height * destHeight)
        } else if (!destHeight) {
          destHeight = Math.round(height / width * destWidth)
        }
      } else {
        destWidth = width
        destHeight = height
      }
      const newCanvas = getTempCanvas(destWidth, destHeight)
      const context = newCanvas.getContext('2d')
      if (type === 'jpeg' || type === 'jpg') {
        type = 'jpeg'
        context.fillStyle = '#fff'
        context.fillRect(0, 0, destWidth, destHeight)
      }
      context.__hidpi__ = true
      context.drawImageByCanvas(canvas, x, y, width, height, 0, 0, destWidth, destHeight, false)
      let result
      try {
        let compressed
        if (dataType === 'base64') {
          data = newCanvas.toDataURL(`image/${type}`, quality)
        } else {
          const imgData = context.getImageData(0, 0, destWidth, destHeight)
          if (__PLATFORM__ === 'app-plus') {
            const pako = require('pako')
            data = pako.deflateRaw(imgData.data, { to: 'string' })
            compressed = true
          } else {
            // fix [...]展开TypedArray在低版本手机报错的问题，使用Array.prototype.slice
            data = Array.prototype.slice.call(imgData.data)
          }
        }
        result = {
          errMsg: 'canvasGetImageData:ok',
          data,
          compressed,
          width: destWidth,
          height: destHeight
        }
      } catch (error) {
        result = {
          errMsg: `canvasGetImageData:fail ${error}`
        }
      }
      newCanvas.height = newCanvas.width = 0
      context.__hidpi__ = false
      if (!callbackId) {
        return result
      } else {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: result
        }, this.$page.id)
      }
    },
    putImageData ({
      data,
      x,
      y,
      width,
      height,
      compressed,
      callbackId
    }) {
      try {
        if (__PLATFORM__ === 'app-plus' && compressed) {
          const pako = require('pako')
          data = pako.inflateRaw(data)
        }
        if (!height) {
          height = Math.round(data.length / 4 / width)
        }
        const canvas = getTempCanvas(width, height)
        const context = canvas.getContext('2d')
        context.putImageData(new ImageData(new Uint8ClampedArray(data), width, height), 0, 0)
        this.$refs.canvas.getContext('2d').drawImage(canvas, x, y, width, height)
        canvas.height = canvas.width = 0
      } catch (error) {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg: 'canvasPutImageData:fail'
          }
        }, this.$page.id)
        return
      }
      UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
        callbackId,
        data: {
          errMsg: 'canvasPutImageData:ok'
        }
      }, this.$page.id)
    },
    toTempFilePath ({
      x = 0,
      y = 0,
      width,
      height,
      destWidth,
      destHeight,
      fileType,
      quality,
      dirname,
      callbackId
    }) {
      const res = this.getImageData({
        x,
        y,
        width,
        height,
        destWidth,
        destHeight,
        hidpi: false,
        dataType: 'base64',
        type: fileType,
        quality
      })
      if (!res.data || !res.data.length) {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg: res.errMsg.replace('canvasPutImageData', 'toTempFilePath')
          }
        }, this.$page.id)
        return
      }
      saveImage(res.data, dirname, (error, tempFilePath) => {
        let errMsg = `toTempFilePath:${error ? 'fail' : 'ok'}`
        if (error) {
          errMsg += ` ${error.message}`
        }
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg,
            tempFilePath: tempFilePath
          }
        }, this.$page.id)
      })
    }
  }
}
</script>
<style>
uni-canvas {
  width: 300px;
  height: 150px;
  display: block;
  position: relative;
}

uni-canvas > canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
</style>

<template>
  <uni-canvas
    :canvas-id="canvasId"
    :disable-scroll="disableScroll"
    v-on="_listeners"
  >
    <canvas
      ref="canvas"
      width="300"
      height="150"
    />
    <div
      style="
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        overflow: hidden;
      "
    >
      <slot />
    </div>
    <v-uni-resize-sensor
      ref="sensor"
      @resize="_resize"
    />
  </uni-canvas>
</template>
<script>
import {
  subscriber
} from 'uni-mixins'

import {
  pixelRatio,
  wrapper
} from 'uni-helpers/hidpi'

import saveImage from 'uni-platform/helpers/save-image'
import { getSameOriginUrl } from 'uni-platform/helpers/file'

function resolveColor (color) {
  color = color.slice(0)
  color[3] = color[3] / 255
  return 'rgba(' + color.join(',') + ')'
}

function processTouches (target, touches) {
  return ([]).map.call(touches, (touch) => {
    var boundingClientRect = target.getBoundingClientRect()
    return {
      identifier: touch.identifier,
      x: touch.clientX - boundingClientRect.left,
      y: touch.clientY - boundingClientRect.top
    }
  })
}

var tempCanvas
function getTempCanvas (width = 0, height = 0) {
  if (!tempCanvas) {
    tempCanvas = document.createElement('canvas')
  }
  tempCanvas.width = width
  tempCanvas.height = height
  return tempCanvas
}

export default {
  name: 'Canvas',
  mixins: [subscriber],
  props: {
    canvasId: {
      type: String,
      default: ''
    },
    disableScroll: {
      type: [Boolean, String],
      default: false
    }
  },
  data () {
    return {
      actionsWaiting: false
    }
  },
  computed: {
    id () {
      return this.canvasId
    },
    _listeners () {
      var $listeners = Object.assign({}, this.$listeners)
      var events = ['touchstart', 'touchmove', 'touchend']
      events.forEach(event => {
        var existing = $listeners[event]
        var eventHandler = []
        if (existing) {
          eventHandler.push(($event) => {
            this.$trigger(event, Object.assign({}, $event, {
              touches: processTouches($event.currentTarget, $event.touches),
              changedTouches: processTouches($event.currentTarget, $event
                .changedTouches)
            }))
          })
        }
        if (this.disableScroll && event === 'touchmove') {
          eventHandler.push(this._touchmove)
        }
        $listeners[event] = eventHandler
      })
      return $listeners
    }
  },
  created () {
    this._actionsDefer = []
    this._images = {}
  },
  mounted () {
    this._resize()
  },
  beforeDestroy () {
    const canvas = this.$refs.canvas
    canvas.height = canvas.width = 0
  },
  methods: {
    _handleSubscribe ({
      type,
      data = {}
    }) {
      var method = this[type]
      if (type.indexOf('_') !== 0 && typeof method === 'function') {
        method(data)
      }
    },
    _resize (size) {
      var canvas = this.$refs.canvas
      var hasChanged = !size || (canvas.width !== Math.floor(size.width * pixelRatio) || canvas.height !== Math.floor(size.height * pixelRatio))
      if (!hasChanged) return
      if (canvas.width > 0 && canvas.height > 0) {
        var context = canvas.getContext('2d')
        var imageData = context.getImageData(0, 0, canvas.width, canvas.height)
        wrapper(canvas)
        context.putImageData(imageData, 0, 0)
      } else {
        wrapper(canvas)
      }
    },
    _touchmove (event) {
      event.preventDefault()
    },
    actionsChanged ({
      actions,
      reserve,
      callbackId
    }) {
      var self = this
      if (!actions) {
        return
      }
      if (this.actionsWaiting) {
        this._actionsDefer.push([actions, reserve, callbackId])
        return
      }
      var canvas = this.$refs.canvas
      var c2d = canvas.getContext('2d')
      if (!reserve) {
        c2d.fillStyle = '#000000'
        c2d.strokeStyle = '#000000'
        c2d.shadowColor = '#000000'
        c2d.shadowBlur = 0
        c2d.shadowOffsetX = 0
        c2d.shadowOffsetY = 0
        c2d.setTransform(1, 0, 0, 1, 0, 0)
        c2d.clearRect(0, 0, canvas.width, canvas.height)
      }
      this.preloadImage(actions)
      for (let index = 0; index < actions.length; index++) {
        const action = actions[index]
        let method = action.method
        const data = action.data
        if (/^set/.test(method) && method !== 'setTransform') {
          const method1 = method[3].toLowerCase() + method.slice(4)
          let color
          if (method1 === 'fillStyle' || method1 === 'strokeStyle') {
            if (data[0] === 'normal') {
              color = resolveColor(data[1])
            } else if (data[0] === 'linear') {
              const LinearGradient = c2d.createLinearGradient(...data[1])
              data[2].forEach(function (data2) {
                const offset = data2[0]
                const color = resolveColor(data2[1])
                LinearGradient.addColorStop(offset, color)
              })
              color = LinearGradient
            } else if (data[0] === 'radial') {
              const x = data[1][0]
              const y = data[1][1]
              const r = data[1][2]
              const LinearGradient = c2d.createRadialGradient(x, y, 0, x, y, r)
              data[2].forEach(function (data2) {
                const offset = data2[0]
                const color = resolveColor(data2[1])
                LinearGradient.addColorStop(offset, color)
              })
              color = LinearGradient
            } else if (data[0] === 'pattern') {
              const loaded = this.checkImageLoaded(data[1], actions.slice(index + 1), callbackId,
                function (image) {
                  if (image) {
                    c2d[method1] = c2d.createPattern(image, data[2])
                  }
                })
              if (!loaded) {
                break
              }
              continue
            }
            c2d[method1] = color
          } else if (method1 === 'globalAlpha') {
            c2d[method1] = data[0] / 255
          } else if (method1 === 'shadow') {
            var _ = ['shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowColor']
            data.forEach(function (color_, method_) {
              c2d[_[method_]] = _[method_] === 'shadowColor' ? resolveColor(color_) : color_
            })
          } else if (method1 === 'fontSize') {
            const font = c2d.__font__ || c2d.font
            c2d.__font__ = c2d.font = font.replace(/\d+\.?\d*px/, data[0] + 'px')
          } else if (method1 === 'lineDash') {
            c2d.setLineDash(data[0])
            c2d.lineDashOffset = data[1] || 0
          } else if (method1 === 'textBaseline') {
            if (data[0] === 'normal') {
              data[0] = 'alphabetic'
            }
            c2d[method1] = data[0]
          } else if (method1 === 'font') {
            c2d.__font__ = c2d.font = data[0]
          } else {
            c2d[method1] = data[0]
          }
        } else if (method === 'fillPath' || method === 'strokePath') {
          method = method.replace(/Path/, '')
          c2d.beginPath()
          data.forEach(function (data_) {
            c2d[data_.method].apply(c2d, data_.data)
          })
          c2d[method]()
        } else if (method === 'fillText') {
          c2d.fillText.apply(c2d, data)
        } else if (method === 'drawImage') {
          var A = (function () {
            var dataArray = [...data]
            var url = dataArray[0]
            var otherData = dataArray.slice(1)
            self._images = self._images || {}
            if (!self.checkImageLoaded(url, actions.slice(index + 1), callbackId, function (
              image) {
              if (image) {
                c2d.drawImage.apply(c2d, [image].concat([...otherData.slice(4, 8)],
                  [...otherData.slice(0, 4)]))
              }
            })) return 'break'
          }())
          if (A === 'break') {
            break
          }
        } else {
          if (method === 'clip') {
            data.forEach(function (data_) {
              c2d[data_.method].apply(c2d, data_.data)
            })
            c2d.clip()
          } else {
            c2d[method].apply(c2d, data)
          }
        }
      }
      if (!this.actionsWaiting && callbackId) {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg: 'drawCanvas:ok'
          }
        }, this.$page.id)
      }
    },
    preloadImage: function (actions) {
      var self = this
      actions.forEach(function (action) {
        var method = action.method
        var data = action.data
        var src = ''
        if (method === 'drawImage') {
          src = data[0]
          src = self.$getRealPath(src)
          data[0] = src
        } else if (method === 'setFillStyle' && data[0] === 'pattern') {
          src = data[1]
          src = self.$getRealPath(src)
          data[1] = src
        }
        if (src && !self._images[src]) {
          loadImage()
        }
        /**
         * 加载图像
         */
        function loadImage () {
          const image = self._images[src] = new Image()
          image.onload = function () {
            image.ready = true
          }

          // 安卓 WebView 除本地路径无跨域问题
          if (__PLATFORM__ === 'app-plus' && navigator.vendor === 'Google Inc.') {
            if (src.indexOf('file://') === 0) {
              image.crossOrigin = 'anonymous'
            }
            image.src = src
            return
          }
          getSameOriginUrl(src).then(src => {
            image.src = src
          }).catch(() => {
            image.src = src
          })
        }
      })
    },
    checkImageLoaded: function (src, actions, callbackId, fn) {
      var self = this
      var image = this._images[src]
      if (image.ready) {
        fn(image)
        return true
      } else {
        this._actionsDefer.unshift([actions, true])
        this.actionsWaiting = true
        image.onload = function () {
          image.ready = true
          fn(image)
          self.actionsWaiting = false
          var actions = self._actionsDefer.slice(0)
          self._actionsDefer = []
          for (var action = actions.shift(); action;) {
            self.actionsChanged({
              actions: action[0],
              reserve: action[1],
              callbackId
            })
            action = actions.shift()
          }
        }
        return false
      }
    },
    getImageData ({
      x = 0,
      y = 0,
      width,
      height,
      destWidth,
      destHeight,
      hidpi = true,
      dataType,
      quality = 1,
      type = 'png',
      callbackId
    }) {
      const canvas = this.$refs.canvas
      let data
      const maxWidth = canvas.offsetWidth - x
      width = width ? Math.min(width, maxWidth) : maxWidth
      const maxHeight = canvas.offsetHeight - y
      height = height ? Math.min(height, maxHeight) : maxHeight
      if (!hidpi) {
        if (!destWidth && !destHeight) {
          destWidth = Math.round(width * pixelRatio)
          destHeight = Math.round(height * pixelRatio)
        } else if (!destWidth) {
          destWidth = Math.round(width / height * destHeight)
        } else if (!destHeight) {
          destHeight = Math.round(height / width * destWidth)
        }
      } else {
        destWidth = width
        destHeight = height
      }
      const newCanvas = getTempCanvas(destWidth, destHeight)
      const context = newCanvas.getContext('2d')
      if (type === 'jpeg' || type === 'jpg') {
        type = 'jpeg'
        context.fillStyle = '#fff'
        context.fillRect(0, 0, destWidth, destHeight)
      }
      context.__hidpi__ = true
      context.drawImageByCanvas(canvas, x, y, width, height, 0, 0, destWidth, destHeight, false)
      let result
      try {
        let compressed
        if (dataType === 'base64') {
          data = newCanvas.toDataURL(`image/${type}`, quality)
        } else {
          const imgData = context.getImageData(0, 0, destWidth, destHeight)
          if (__PLATFORM__ === 'app-plus') {
            const pako = require('pako')
            data = pako.deflateRaw(imgData.data, { to: 'string' })
            compressed = true
          } else {
            // fix [...]展开TypedArray在低版本手机报错的问题，使用Array.prototype.slice
            data = Array.prototype.slice.call(imgData.data)
          }
        }
        result = {
          errMsg: 'canvasGetImageData:ok',
          data,
          compressed,
          width: destWidth,
          height: destHeight
        }
      } catch (error) {
        result = {
          errMsg: `canvasGetImageData:fail ${error}`
        }
      }
      newCanvas.height = newCanvas.width = 0
      context.__hidpi__ = false
      if (!callbackId) {
        return result
      } else {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: result
        }, this.$page.id)
      }
    },
    putImageData ({
      data,
      x,
      y,
      width,
      height,
      compressed,
      callbackId
    }) {
      try {
        if (!height) {
          height = Math.round(data.length / 4 / width)
        }
        const canvas = getTempCanvas(width, height)
        const context = canvas.getContext('2d')
        if (__PLATFORM__ === 'app-plus' && compressed) {
          const pako = require('pako')
          data = pako.inflateRaw(data)
        }
        context.putImageData(new ImageData(new Uint8ClampedArray(data), width, height), 0, 0)
        this.$refs.canvas.getContext('2d').drawImage(canvas, x, y, width, height)
        canvas.height = canvas.width = 0
      } catch (error) {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg: 'canvasPutImageData:fail'
          }
        }, this.$page.id)
        return
      }
      UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
        callbackId,
        data: {
          errMsg: 'canvasPutImageData:ok'
        }
      }, this.$page.id)
    },
    toTempFilePath ({
      x = 0,
      y = 0,
      width,
      height,
      destWidth,
      destHeight,
      fileType,
      quality,
      dirname,
      callbackId
    }) {
      const res = this.getImageData({
        x,
        y,
        width,
        height,
        destWidth,
        destHeight,
        hidpi: false,
        dataType: 'base64',
        type: fileType,
        quality
      })
      if (!res.data || !res.data.length) {
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg: res.errMsg.replace('canvasPutImageData', 'toTempFilePath')
          }
        }, this.$page.id)
        return
      }
      saveImage(res.data, dirname, (error, tempFilePath) => {
        let errMsg = `toTempFilePath:${error ? 'fail' : 'ok'}`
        if (error) {
          errMsg += ` ${error.message}`
        }
        UniViewJSBridge.publishHandler('onCanvasMethodCallback', {
          callbackId,
          data: {
            errMsg,
            tempFilePath: tempFilePath
          }
        }, this.$page.id)
      })
    }
  }
}
</script>
<style>
uni-canvas {
  width: 300px;
  height: 150px;
  display: block;
  position: relative;
}

uni-canvas > canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
</style>


verify and run screen now shows on single pipette protocols

<template>

      <router-link v-bind:to="task.href" :class="{'completed': completed(task)}">
        {{task.title}}
      </router-link>

          <router-link v-bind:to="placeable.href" :class="{'completed': placeable.calibrated}" exact>
            {{placeable.label}} [{{placeable.slot}}]
         </router-link>

        <router-link v-bind:to="instrument.href" :class="{'completed': instrument.calibrated}" exact>
          Calibrate {{instrument.label}}
        </router-link>

 0">
      <router-link v-bind:to="task.href">
        {{task.title}}
      </router-link>

</template>

<template>

      <router-link v-bind:to="task.href" :class="{'completed': completed(task)}">
        {{task.title}}
      </router-link>

          <router-link v-bind:to="placeable.href" :class="{'completed': placeable.calibrated}" exact>
            {{placeable.label}} [{{placeable.slot}}]
         </router-link>

        <router-link v-bind:to="instrument.href" :class="{'completed': instrument.calibrated}" exact>
          Calibrate {{instrument.label}}
        </router-link>

 1">
      <router-link v-bind:to="task.href">
        {{task.title}}
      </router-link>

</template>


Don't emit update event to close the application launcher if it is not open

<template>

    <a :id="`pf-application-launcher-${_uid}`" class="dropdown-toggle drawer-pf-trigger-icon nav-item-iconic" :class="{disabled: disabled || !$slots.default}" href="javascript:void(0)" @click.stop="toggle">

        {{label}}

    <ul class="dropdown-menu dropdown-menu-right" role="menu" @click.stop="hide"
        :aria-labelledby="`pf-application-launcher-${_uid}`">
      <slot></slot>

</template>


Update Textfield.vue

Update Textfield.vue

<template>

        {{leadingIcon}}
        <input :id="id" :class="mdc-textfield__input" :value="value" @input="updateModel" :placeholder="placeholder" :type="type" v-bind="$attrs" />
        {{label}}
        {{trailingIcon}}

</template>

<template>

        <input :id="id" :class="mdc-textfield__input" :class="classesInput" :value="value" @input="updateModel" :placeholder="placeholder" :type="type" v-bind="$attrs" />
        {{label}}

</template>


refactor: ♻ Improved styling of changelog modal


modify is_shared

modify is_shared

<template>

{{ problem.id }}.{{ problem.title }}

                		({{ item.language }})Time limit : {{ item.time_limit }}ms
                		Memory limit : {{ item.memory_limit }}KByte

Description

{{ problem.description }}

Input

{{ problem.input }}

Output

{{ problem.output }}

Sample input

{{ problem.sample_input }}

Sample output

{{ problem.sample_output }}

Hint

{{ problem.hint }}

Source

{{ problem.source }}

Source code

                                {{ item.language }}

                                    <input type="radio" id="shared" value="true" v-model="isShared">Shared
                                    <input type="radio" id="no-shared" value="false" v-model="isShared">No Shared

                                        <textarea name="code" id="code" v-model="code"></textarea>

        				Submit

        					Description

        					Input

        					Output

        					Sample input

        					Sample output

        					Hint

        					Source

                            Language

                            Source Code

        					Submit

        					Back to top

</template>


左右布局容器，可设置固定宽度

左右布局容器，可设置固定宽度

<template>
  <div @click="handleClick" class="mint-layout-lr-item" :position="position" :style="{ width: percentageWidth}">
      <slot></slot>
  </div>
</template>
<script>
    /**
     * @noteType component
     * @name LayoutLeftRightItem
     * @tagName mt-layout-left-right-item
     * @desc 左右布局容器节点
     * @type container
     * @wrapClassName mint-layout-lr-item
     * @noDisplayInComponents true
     * @html
     * <mt-layout-left-right-item slot="left" position="left"></mt-layout-left-right-item>
     */
export default {
  name: 'mt-layout-left-right-item',
    methods: {
        handleClick(evt) {
            this.$emit('click', evt);
        }
    },
  props: {
      position: String,
      percentage: String
  },
    computed: {
        percentageWidth: function() {
            let percentage = this.percentage;
            if (typeof percentage === 'undefined' || percentage === null) {
                percentage = '50';
            }
            if(/^\d+$/.test(percentage)){
                return percentage + '%';
            }else{
                return percentage;
            }
        }
    }
};
</script>
<style lang="css">
  @import "../../../src/style/var.css";
  @component-namespace mint {
    @component layout-lr-item {
      position: relative;
      display: table-cell;
      vertical-align: middle;
    }
  }

</style>

<template>
  <div @click="handleClick" class="mint-layout-lr-item" :position="position" :style="{ width: percentageWidth}">
      <slot></slot>
  </div>
</template>
<script>
    /**
     * @noteType component
     * @name LayoutLeftRightItem
     * @tagName mt-layout-left-right-item
     * @desc 左右布局容器节点
     * @type container
     * @wrapClassName mint-layout-lr-item
     * @noDisplayInComponents true
     * @html
     * <mt-layout-left-right-item slot="left" position="left"></mt-layout-left-right-item>
     */
export default {
  name: 'mt-layout-left-right-item',
    methods: {
        handleClick(evt) {
            this.$emit('click', evt);
        }
    },
  props: {
      position: String,
      percentage: String
  },
    computed: {
        percentageWidth: function() {
            let percentage = this.percentage;
            if (typeof percentage === 'undefined' || percentage === null) {
                percentage = '50';
            }
            return percentage + '%';
        }
    }
};
</script>
<style lang="css">
  @import "../../../src/style/var.css";
  @component-namespace mint {
    @component layout-lr-item {
      position: relative;
      display: table-cell;
      vertical-align: middle;
    }
  }

</style>


Use internal alert function

Use internal alert function

<template>

		<fa icon="cloud"/>{{ $t('@.drive') }}
		<template v-for="folder in hierarchyFolders">
			'"><fa icon="angle-right"/>
			<a :key="folder.id" @click.prevent="cd(folder)" :href="`/i/drive/folder/${folder.id}`">{{ folder.name }}
		</template>
		<template v-if="folder != null">
			<fa icon="angle-right"/>

{{ folder.name }}

		</template>
		<template v-if="file != null">
			<fa icon="angle-right"/>

{{ file.name }}

		</template>

	<mk-uploader ref="uploader"/>

{{ (info.usage / info.capacity * 100).toFixed(1) }}% {{ $t('used') }}

 0 || folder.filesCount > 0)">
				<template v-if="folder.foldersCount > 0">{{ folder.foldersCount }} {{ $t('folder-count') }}</template>
				<template v-if="folder.foldersCount > 0 && folder.filesCount > 0">{{ $t('count-separator') }}</template>
				<template v-if="folder.filesCount > 0">{{ folder.filesCount }} {{ $t('file-count') }}</template>

 0">
			<x-folder class="folder" v-for="folder in folders" :key="folder.id" :folder="folder"/>

{{ $t('@.load-more') }}

 0">
			<x-file class="file" v-for="file in files" :key="file.id" :file="file"/>
			<button class="more" v-if="moreFiles" @click="fetchMoreFiles">
				{{ fetchingMoreFiles ? this.$t('@.loading') : this.$t('@.load-more') }}
			</button>

{{ $t('nothing-in-drive') }}

{{ $t('folder-is-empty') }}

	<input ref="file" class="file" type="file" multiple="multiple" @change="onChangeLocalFile"/>
	<x-file-detail v-if="file != null" :file="file"/>

</template>


select interaction: add missing props

select interaction: add missing props

<template>
  <i :class="[$options.name]" style="display: none !important;">
    <slot :features="features"/>
  </i>
</template>

<script>
  /** @module select-interaction/interaction */
  import Feature from 'ol/feature'
  import SelectInteraction from 'ol/interaction/select'
  import eventCondition from 'ol/events/condition'
  import Vue from 'vue'
  import interaction from '../../mixin/interaction'
  import stylesContainer from '../../mixin/styles-container'
  import { getId as getFeatureId } from '../../ol-ext/feature'
  import { defaultEditStyle, style as createStyle } from '../../ol-ext/style'
  import observableFromOlEvent from '../../rx-ext/from-ol-event'
  import { hasInteraction, hasMap } from '../../util/assert'
  import { constant, stubArray, isFunction, forEach, mapValues, difference } from '../../util/minilo'
  import mergeDescriptors from '../../util/multi-merge-descriptors'
  import projTransforms from '../../mixin/proj-transforms'

  /**
   * @vueProps
   */
  const props = {
    /**
     * A function that takes an `ol.Feature` and an `ol.layer.Layer` and returns `true` if the feature may be selected or `false` otherwise.
     * @type {ol.SelectFilterFunction|undefined}
     */
    filter: {
      type: Function,
      default: constant(true),
    },
    /**
     * Hit-detection tolerance. Pixels inside the radius around the given position will be checked for features.
     * This only works for the canvas renderer and not for WebGL.
     * @type {number}
     */
    hitTolerance: {
      type: Number,
      default: 0,
    },
    /**
     * A boolean that determines if the default behaviour should select only single features or all (overlapping)
     * features at the clicked map position.
     * @type {boolean}
     */
    multi: {
      type: Boolean,
      default: false,
    },
    /**
     * Selected features as array of GeoJSON features with coordinates in the map view projection.
     * @type {string[]|number[]|GeoJSONFeature[]}
     */
    features: {
      type: Array,
      default: stubArray,
    },
    /**
     * Wrap the world horizontally on the selection overlay.
     * @type {boolean}
     */
    wrapX: {
      type: Boolean,
      default: true,
    },
    /**
     * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should
     * be handled. By default, this is `ol.events.condition.never`. Use this if you want to use different events
     * for `add` and `remove` instead of `toggle`.
     * @type {ol.EventsConditionType|undefined}
     */
    addCondition: {
      type: Function,
      default: eventCondition.never,
    },
    /**
     * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should be handled.
     * This is the event for the selected features as a whole. By default, this is `ol.events.condition.singleClick`.
     * Clicking on a feature selects that feature and removes any that were in the selection. Clicking outside any feature
     * removes all from the selection.
     * @type {ol.EventsConditionType|undefined}
     */
    condition: {
      type: Function,
      default: eventCondition.singleClick,
    },
    /**
     * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should be handled.
     * By default, this is `ol.events.condition.never`. Use this if you want to use different events for `add` and `remove`
     * instead of `toggle`.
     * @type {ol.EventsConditionType|undefined}
     */
    removeCondition: {
      type: Function,
      default: eventCondition.never,
    },
    /**
     * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should be handled.
     * This is in addition to the `condition` event. By default, `ol.events.condition.shiftKeyOnly`, i.e. pressing `shift`
     * as well as the `condition` event, adds that feature to the current selection if it is not currently selected,
     * and removes it if it is.
     * @type {ol.EventsConditionType|undefined}
     */
    toggleCondition: {
      type: Function,
      default: eventCondition.shiftKeyOnly,
    },
  }

  /**
   * @vueComputed
   */
  const computed = {
  }

  /**
   * @vueMethods
   */
  const methods = {
    /**
     * @return {ol.interaction.Select}
     * @protected
     */
    createInteraction () {
      return new SelectInteraction({
        multi: this.multi,
        wrapX: this.wrapX,
        filter: this.filter,
        style: this.createStyleFunc(),
      })
    },
    /**
     * @return {ol.StyleFunction}
     * @protected
     */
    getDefaultStyles () {
      const defaultStyles = mapValues(defaultEditStyle(), styles => styles.map(createStyle))

      return function __selectDefaultStyleFunc (feature) {
        if (feature.getGeometry()) {
          return defaultStyles[feature.getGeometry().getType()]
        }
      }
    },
    /**
     * @return {ol.Feature[]}
     */
    getFeatures () {
      return (this.$interaction && this.$interaction.getFeatures().getArray()) || []
    },
    /**
     * @returns {Object}
     * @protected
     */
    getServices () {
      return mergeDescriptors(
        this::interaction.methods.getServices(),
        this::stylesContainer.methods.getServices()
      )
    },
    /**
     * @return {ol.interaction.Interaction|undefined}
     * @protected
     */
    getStyleTarget () {
      return this.$interaction
    },
    /**
     * @return {void}
     * @protected
     */
    mount () {
      this::interaction.methods.mount()
      this.features.forEach(this.select)
    },
    /**
     * @return {void}
     * @protected
     */
    unmount () {
      this.unselectAll()
      this::interaction.methods.unmount()
    },
    /**
     * @param {GeoJSONFeature|Vue|ol.Feature|string|number} feature
     * @return {void}
     * @throws {Error}
     */
    select (feature) {
      hasMap(this)
      hasInteraction(this)

      let id = getFeatureId(feature)
      if (!id) {
        throw new Error('Undefined feature id')
      }
      if (feature instanceof Vue) {
        feature = feature.$feature
      }

      const selectedIds = this.$features.map(getFeatureId)
      if (selectedIds.includes(id)) return

      if (!(feature instanceof Feature)) {
        feature = undefined
        forEach(this.$map.getLayers().getArray(), layer => {
          const source = layer.getSource()

          if (source && isFunction(source.getFeatureById)) {
            feature = source.getFeatureById(id)
          }

          return !feature
        })
      }

      feature && this.$interaction.getFeatures().push(feature)
    },
    /**
     * @param {GeoJSONFeature|Vue|ol.Feature|string|number} feature
     * @return {void}
     */
    unselect (feature) {
      hasInteraction(this)

      let id = getFeatureId(feature)
      if (!id) {
        throw new Error('Undefined feature id')
      }
      if (feature instanceof Vue) {
        feature = feature.$feature
      }

      const selectedIds = this.$features.map(getFeatureId)
      const idx = selectedIds.findIndex(x => x === id)

      if (idx !== -1) {
        this.$interaction.getFeatures().removeAt(idx)
      }
    },
    /**
     * @param {Array<{style: ol.style.Style, condition: (function|boolean|undefined)}>|ol.StyleFunction|Vue|undefined} styles
     * @return {void}
     * @protected
     */
    setStyle (styles) {
      if (styles !== this._styles) {
        this._styles = styles
        this.refresh()
      }
    },
    /**
     * @return {Promise}
     */
    refresh () {
      return Promise.all([
        new Promise(resolve => {
          if (this.$interaction) {
            const featuresCollection = this.$interaction.getFeatures()
            featuresCollection.once('change', () => resolve())
            featuresCollection.changed()
          } else {
            resolve()
          }
        }),
        this::interaction.methods.refresh(),
      ])
    },
    /**
     * @return {void}
     * @protected
     */
    subscribeAll () {
      this::subscribeToInteractionChanges()
    },
    /**
     * Removes all features from selection.
     * @return {void}
     */
    unselectAll () {
      hasInteraction(this)
      this.$interaction.getFeatures().clear()
    },
  }

  const watch = {
    features (value) {
      if (!this.$interaction) return

      let diffById = (a, b) => getFeatureId(a) === getFeatureId(b)
      let forSelect = difference(value, this.$features, diffById)
      let forUnselect = difference(this.$features, value, diffById)

      forSelect.forEach(this.select)
      forUnselect.forEach(this.unselect)
    },
  }

  /**
   * @vueProto
   * @alias module:select-interaction/interaction
   * @title vl-interaction-select
   */
  export default {
    name: 'vl-interaction-select',
    mixins: [interaction, stylesContainer, projTransforms],
    props,
    computed,
    methods,
    watch,
    stubVNode: {
      empty: false,
      attrs () {
        return {
          class: this.$options.name,
        }
      },
    },
    created () {
      Object.defineProperties(this, {
        $features: {
          enumerable: true,
          get: this.getFeatures,
        },
      })
    },
  }

  /**
   * @return {void}
   * @private
   */
  function subscribeToInteractionChanges () {
    hasInteraction(this)

    const events = observableFromOlEvent(this.$interaction, 'select')

    this.subscribeTo(
      events,
      ({ selected, deselected, mapBrowserEvent }) => {
        ++this.rev

        deselected.forEach(feature => this.$emit('unselect', { feature, mapBrowserEvent }))
        selected.forEach(feature => this.$emit('select', { feature, mapBrowserEvent }))
        this.$emit('update:features', this.$features.map(::this.writeFeatureInBindProj))
      }
    )
  }
</script>

<template>
  <i :class="[$options.name]" style="display: none !important;">
    <slot :features="features"/>
  </i>
</template>

<script>
  /**
   * @module select-interaction/interaction
   */
  import Feature from 'ol/feature'
  import SelectInteraction from 'ol/interaction/select'
  import Vue from 'vue'
  import interaction from '../../mixin/interaction'
  import stylesContainer from '../../mixin/styles-container'
  import { getId as getFeatureId } from '../../ol-ext/feature'
  import { defaultEditStyle, style as createStyle } from '../../ol-ext/style'
  import observableFromOlEvent from '../../rx-ext/from-ol-event'
  import { hasInteraction, hasMap } from '../../util/assert'
  import { constant, stubArray, isFunction, forEach, mapValues, difference } from '../../util/minilo'
  import mergeDescriptors from '../../util/multi-merge-descriptors'
  import projTransforms from '../../mixin/proj-transforms'

  // todo add other options, like event modifiers
  const props = {
    filter: {
      type: Function,
      default: constant(true),
    },
    hitTolerance: {
      type: Number,
      default: 0,
    },
    multi: {
      type: Boolean,
      default: false,
    },
    /**
     * Selected features as array of GeoJSON features with coordinates in the map view projection.
     * @type {string[]|number[]|GeoJSONFeature[]}
     */
    features: {
      type: Array,
      default: stubArray,
    },
    wrapX: {
      type: Boolean,
      default: true,
    },
  }

  const computed = {
  }

  const methods = {
    /**
     * @return {ol.interaction.Select}
     * @protected
     */
    createInteraction () {
      return new SelectInteraction({
        multi: this.multi,
        wrapX: this.wrapX,
        filter: this.filter,
        style: this.createStyleFunc(),
      })
    },
    /**
     * @return {ol.StyleFunction}
     * @protected
     */
    getDefaultStyles () {
      const defaultStyles = mapValues(defaultEditStyle(), styles => styles.map(createStyle))

      return function __selectDefaultStyleFunc (feature) {
        if (feature.getGeometry()) {
          return defaultStyles[feature.getGeometry().getType()]
        }
      }
    },
    /**
     * @return {ol.Feature[]}
     */
    getFeatures () {
      return (this.$interaction && this.$interaction.getFeatures().getArray()) || []
    },
    /**
     * @returns {Object}
     * @protected
     */
    getServices () {
      return mergeDescriptors(
        this::interaction.methods.getServices(),
        this::stylesContainer.methods.getServices()
      )
    },
    /**
     * @return {ol.interaction.Interaction|undefined}
     * @protected
     */
    getStyleTarget () {
      return this.$interaction
    },
    /**
     * @return {void}
     * @protected
     */
    mount () {
      this::interaction.methods.mount()
      this.features.forEach(this.select)
    },
    /**
     * @return {void}
     * @protected
     */
    unmount () {
      this.unselectAll()
      this::interaction.methods.unmount()
    },
    /**
     * @param {GeoJSONFeature|Vue|ol.Feature|string|number} feature
     * @return {void}
     * @throws {Error}
     */
    select (feature) {
      hasMap(this)
      hasInteraction(this)

      let id = getFeatureId(feature)
      if (!id) {
        throw new Error('Undefined feature id')
      }
      if (feature instanceof Vue) {
        feature = feature.$feature
      }

      const selectedIds = this.$features.map(getFeatureId)
      if (selectedIds.includes(id)) return

      if (!(feature instanceof Feature)) {
        feature = undefined
        forEach(this.$map.getLayers().getArray(), layer => {
          const source = layer.getSource()

          if (source && isFunction(source.getFeatureById)) {
            feature = source.getFeatureById(id)
          }

          return !feature
        })
      }

      feature && this.$interaction.getFeatures().push(feature)
    },
    /**
     * @param {GeoJSONFeature|Vue|ol.Feature|string|number} feature
     * @return {void}
     */
    unselect (feature) {
      hasInteraction(this)

      let id = getFeatureId(feature)
      if (!id) {
        throw new Error('Undefined feature id')
      }
      if (feature instanceof Vue) {
        feature = feature.$feature
      }

      const selectedIds = this.$features.map(getFeatureId)
      const idx = selectedIds.findIndex(x => x === id)

      if (idx !== -1) {
        this.$interaction.getFeatures().removeAt(idx)
      }
    },
    /**
     * @param {Array<{style: ol.style.Style, condition: (function|boolean|undefined)}>|ol.StyleFunction|Vue|undefined} styles
     * @return {void}
     * @protected
     */
    setStyle (styles) {
      if (styles !== this._styles) {
        this._styles = styles
        this.refresh()
      }
    },
    /**
     * @return {Promise}
     */
    refresh () {
      return Promise.all([
        new Promise(resolve => {
          if (this.$interaction) {
            const featuresCollection = this.$interaction.getFeatures()
            featuresCollection.once('change', () => resolve())
            featuresCollection.changed()
          } else {
            resolve()
          }
        }),
        this::interaction.methods.refresh(),
      ])
    },
    /**
     * @return {void}
     * @protected
     */
    subscribeAll () {
      this::subscribeToInteractionChanges()
    },
    /**
     * Removes all features from selection.
     * @return {void}
     */
    unselectAll () {
      hasInteraction(this)
      this.$interaction.getFeatures().clear()
    },
  }

  const diffById = (a, b) => getFeatureId(a) === getFeatureId(b)
  const watch = {
    features (value) {
      if (!this.$interaction) return

      let forSelect = difference(value, this.$features, diffById)
      let forUnselect = difference(this.$features, value, diffById)

      forSelect.forEach(this.select)
      forUnselect.forEach(this.unselect)
    },
  }

  // TODO: use featuresContainer mixin
  export default {
    name: 'vl-interaction-select',
    mixins: [interaction, stylesContainer, projTransforms],
    props,
    computed,
    methods,
    watch,
    stubVNode: {
      empty: false,
      attrs () {
        return {
          class: this.$options.name,
        }
      },
    },
    created () {
      Object.defineProperties(this, {
        $features: {
          enumerable: true,
          get: this.getFeatures,
        },
      })
    },
  }

  /**
   * @return {void}
   * @private
   */
  function subscribeToInteractionChanges () {
    hasInteraction(this)

    const events = observableFromOlEvent(this.$interaction, 'select')

    this.subscribeTo(
      events,
      ({ selected, deselected, mapBrowserEvent }) => {
        ++this.rev

        deselected.forEach(feature => this.$emit('unselect', { feature, mapBrowserEvent }))
        selected.forEach(feature => this.$emit('select', { feature, mapBrowserEvent }))
        this.$emit('update:features', this.$features.map(::this.writeFeatureInBindProj))
      }
    )
  }
</script>


l10n: Unify spelling

l10n: Unify spelling

Spelling unification in Nextcloud applications.

<template>

		<input ref="signaling_server"
			type="text"
			name="signaling_server"
			placeholder="wss://signaling.example.org"
			:value="server"
			:disabled="loading"
			:aria-label="t('spreed', 'High-performance backend URL')"
			@input="updateServer">
		<input :id="'verify' + index"
			type="checkbox"
			:name="'verify' + index"
			class="checkbox verify"
			:checked="verify"
			@change="updateVerify">
		{{ t('spreed', 'Validate SSL certificate') }}

		{{ connectionState }}

</template>


Icon Buttons

Icon Buttons

<template>

        <vue-panel class="with-scroll button-panel" vue-panel-title="Flat Buttons">

<button type="button" class="btn btn-default">Default</button>

<button type="button" class="btn btn-primary">Primary</button>

<button type="button" class="btn btn-success">Success</button>

<button type="button" class="btn btn-info">Info</button>

<button type="button" class="btn btn-warning">Warning</button>

<button type="button" class="btn btn-danger">Danger</button>

        </vue-panel>

        <vue-panel class="with-scroll button-panel" vue-panel-title="Raised Buttons">

<button type="button" class="btn btn-default btn-raised">Default</button>

<button type="button" class="btn btn-primary btn-raised">Primary</button>

<button type="button" class="btn btn-success btn-raised">Success</button>

<button type="button" class="btn btn-info btn-raised">Info</button>

<button type="button" class="btn btn-warning btn-raised">Warning</button>

<button type="button" class="btn btn-danger btn-raised">Danger</button>

        </vue-panel>

        <vue-panel class="with-scroll button-panel df-size-button-panel"  vue-panel-title="Different sizes">

<button type="button" class="btn btn-default  btn-xs">Default</button>

<button type="button" class="btn btn-primary  btn-sm">Primary</button>

<button type="button" class="btn btn-success btn-mm">Success</button>

<button type="button" class="btn btn-info btn-md">Info</button>

<button type="button" class="btn btn-warning btn-xm">Warning</button>

<button type="button" class="btn btn-danger btn-lg">Danger</button>

        </vue-panel>

        <vue-panel class="with-scroll button-panel"  vue-panel-title="Disabled">

<button type="button" class="btn btn-default" disabled="disabled">Default</button>

<button type="button" class="btn btn-primary" disabled="disabled">Primary</button>

<button type="button" class="btn btn-success" disabled="disabled">Success</button>

<button type="button" class="btn btn-info" disabled="disabled">Info</button>

<button type="button" class="btn btn-warning" disabled="disabled">Warning</button>

<button type="button" class="btn btn-danger" disabled="disabled">Danger</button>

        </vue-panel>

        <vue-panel vue-panel-title="Icon Buttons" class="with-scroll">

<button type="button" class="btn btn-primary btn-icon"></button>

<button type="button" class="btn btn-default btn-icon"></button>

<button type="button" class="btn btn-success btn-icon"></button>

<button type="button" class="btn btn-info btn-icon"></button>

<button type="button" class="btn btn-warning btn-icon"></button>

<button type="button" class="btn btn-danger btn-icon"></button>

Buttons with icons

<button type="button" class="btn btn-primary btn-with-icon">Primary</button>

<button type="button" class="btn btn-default btn-with-icon">Default</button>

<button type="button" class="btn btn-success btn-with-icon">Success</button>

<button type="button" class="btn btn-info btn-with-icon">Info</button>

<button type="button" class="btn btn-warning btn-with-icon">Warning</button>

<button type="button" class="btn btn-danger btn-with-icon">Danger</button>

        </vue-panel>

</template>

<template>

        <vue-panel class="with-scroll button-panel" vue-panel-title="Flat Buttons">

<button type="button" class="btn btn-default">Default</button>

<button type="button" class="btn btn-primary">Primary</button>

<button type="button" class="btn btn-success">Success</button>

<button type="button" class="btn btn-info">Info</button>

<button type="button" class="btn btn-warning">Warning</button>

<button type="button" class="btn btn-danger">Danger</button>

        </vue-panel>

        <vue-panel class="with-scroll button-panel" vue-panel-title="Raised Buttons">

<button type="button" class="btn btn-default btn-raised">Default</button>

<button type="button" class="btn btn-primary btn-raised">Primary</button>

<button type="button" class="btn btn-success btn-raised">Success</button>

<button type="button" class="btn btn-info btn-raised">Info</button>

<button type="button" class="btn btn-warning btn-raised">Warning</button>

<button type="button" class="btn btn-danger btn-raised">Danger</button>

        </vue-panel>

        <vue-panel class="with-scroll button-panel df-size-button-panel"  vue-panel-title="Different sizes">

<button type="button" class="btn btn-default  btn-xs">Default</button>

<button type="button" class="btn btn-primary  btn-sm">Primary</button>

<button type="button" class="btn btn-success btn-mm">Success</button>

<button type="button" class="btn btn-info btn-md">Info</button>

<button type="button" class="btn btn-warning btn-xm">Warning</button>

<button type="button" class="btn btn-danger btn-lg">Danger</button>

        </vue-panel>

        <vue-panel class="with-scroll button-panel"  vue-panel-title="Disabled">

<button type="button" class="btn btn-default" disabled="disabled">Default</button>

<button type="button" class="btn btn-primary" disabled="disabled">Primary</button>

<button type="button" class="btn btn-success" disabled="disabled">Success</button>

<button type="button" class="btn btn-info" disabled="disabled">Info</button>

<button type="button" class="btn btn-warning" disabled="disabled">Warning</button>

<button type="button" class="btn btn-danger" disabled="disabled">Danger</button>

        </vue-panel>

      <vue-panel class="col-md-6">
        <vue-panel vue-panel-title="Icon Buttons" class="with-scroll">

<button type="button" class="btn btn-primary btn-icon"></button>

<button type="button" class="btn btn-default btn-icon"></button>

<button type="button" class="btn btn-success btn-icon"></button>

<button type="button" class="btn btn-info btn-icon"></button>

<button type="button" class="btn btn-warning btn-icon"></button>

<button type="button" class="btn btn-danger btn-icon"></button>

Buttons with icons

<button type="button" class="btn btn-primary btn-with-icon">Primary</button>

<button type="button" class="btn btn-default btn-with-icon">Default</button>

<button type="button" class="btn btn-success btn-with-icon">Success</button>

<button type="button" class="btn btn-info btn-with-icon">Info</button>

<button type="button" class="btn btn-warning btn-with-icon">Warning</button>

<button type="button" class="btn btn-danger btn-with-icon">Danger</button>

        </vue-panel>
        <vue-panel vue-panel-title="Large Buttons" class="with-scroll large-buttons-panel">

<button type="button" class="btn btn-primary btn-lg">Primary</button>

<button type="button" class="btn btn-success btn-lg">Success</button>

<button type="button" class="btn btn-info btn-lg">Info</button>

<button type="button" class="btn btn-default btn-lg">Default</button>

<button type="button" class="btn btn-warning btn-lg">Warning</button>

<button type="button" class="btn btn-danger btn-lg">Danger</button>

        </vue-panel>

</template>


In case of error when sending message, revert

Whenever an error occurs when sending a message, the message is now put
back into the field and the temporary one is removed from the messages list.

Whenever a conversation was locked or lobby was enabled concurrently,
posting a message before the UI update could result in a 403 or 412 error.
These now display a proper message to inform about missing permissions.

Signed-off-by: Vincent Petry <87e1f221a672a14a323e57bb65eaea19d3ed3804@nextcloud.com>

<template>

		<input id="file-upload"
			ref="fileUploadInput"
			multiple
			type="file"
			class="hidden-visually"
			@change="handleFileInput">

			<form
				class="new-message-form"
				@submit.prevent>

					<Actions
						default-icon="icon-clip-add-file"
						class="new-message-form__button"
						:aria-label="t('spreed', 'Share files to the conversation')"
						:aria-haspopup="true">
						<ActionButton
							v-if="canShareAndUploadFiles"
							:close-after-click="true"
							icon="icon-upload"
							@click.prevent="clickImportInput">
							{{ t('spreed', 'Upload new files') }}
						</ActionButton>
						<ActionButton
							v-if="canShareAndUploadFiles"
							:close-after-click="true"
							icon="icon-folder"
							@click.prevent="handleFileShare">
							{{ t('spreed', 'Share from Files') }}
						</ActionButton>
					</Actions>

					<EmojiPicker @select="addEmoji">
						<button
							type="button"
							class="new-message-form__icon new-message-form__button"
							:aria-label="t('spreed', 'Add emoji')"
							:aria-haspopup="true">
							<EmoticonOutline
								:size="20"
								decorative />
						</button>
					</EmojiPicker>

					<Quote
						v-if="messageToBeReplied"
						:is-new-message-form-quote="true"
						v-bind="messageToBeReplied" />
					<AdvancedInput
						ref="advancedInput"
						v-model="text"
						:token="token"
						@update:contentEditable="contentEditableToParsed"
						@submit="handleSubmit"
						@files-pasted="handleFiles" />

				<button
					type="submit"
					:aria-label="t('spreed', 'Send message')"
					class="new-message-form__button submit icon-confirm-fade"
					@click.prevent="handleSubmit" />
			</form>

</template>


6927: updated button for NewMessageForm

6927: updated button for NewMessageForm

Signed-off-by: Nikola <ef0921d7af2a67b1e5ab56530351d29e92ff52e1@nextcloud.com>

<template>

		<input id="file-upload"
			ref="fileUploadInput"
			multiple
			type="file"
			tabindex="-1"
			aria-hidden="true"
			class="hidden-visually"
			@change="handleFileInput">

			<form class="new-message-form"
				@submit.prevent>

					<Actions ref="uploadMenu"
						:container="container"
						:boundaries-element="containerElement"
						:disabled="disabled"
						:aria-label="t('spreed', 'Share files to the conversation')"
						:aria-haspopup="true">
						<Paperclip slot="icon"
							:size="16"
							decorative
							title="" />
						<ActionButton v-if="canUploadFiles"
							:close-after-click="true"
							icon="icon-upload"
							@click.prevent="clickImportInput">
							{{ t('spreed', 'Upload new files') }}
						</ActionButton>
						<ActionButton v-if="canShareFiles"
							:close-after-click="true"
							icon="icon-folder"
							@click.prevent="handleFileShare">
							{{ t('spreed', 'Share from Files') }}
						</ActionButton>
					</Actions>

						<EmojiPicker v-if="!disabled"
							:container="container"
							:close-on-select="false"
							@select="addEmoji">
							<Button class="emoji-picker-button"
								:disabled="disabled"
								:aria-label="t('spreed', 'Add emoji')"
								type="tertiary"
								:aria-haspopup="true">
								<EmoticonOutline :size="16"
									decorative
									title="" />
							</Button>
						</EmojiPicker>

						<Button v-else
							type="tertiary"
							:disabled="true"
							class="emoji-picker-button">
							<EmoticonOutline :size="16"
								decorative
								title="" />
						</Button>

						<Quote :is-new-message-form-quote="true"
							:parent-id="messageToBeReplied.id"
							v-bind="messageToBeReplied" />

					<AdvancedInput ref="advancedInput"
						v-model="text"
						:token="token"
						:active-input="!disabled"
						:placeholder-text="placeholderText"
						:aria-label="placeholderText"
						@update:contentEditable="contentEditableToParsed"
						@submit="handleSubmit"
						@files-pasted="handlePastedFiles" />

				<AudioRecorder v-if="!hasText && canUploadFiles"
					:disabled="disabled"
					@recording="handleRecording"
					@audio-file="handleAudioFile" />

				<Button v-else
					:disabled="disabled"
					type="tertiary"
					native-type="submit"
					:aria-label="t('spreed', 'Send message')"
					class="new-message-form__send-button"
					@click.prevent="handleSubmit">
					<Send title=""
						:size="16"
						decorative />
				</Button>
			</form>

</template>

<template>

		<input id="file-upload"
			ref="fileUploadInput"
			multiple
			type="file"
			tabindex="-1"
			aria-hidden="true"
			class="hidden-visually"
			@change="handleFileInput">

			<form class="new-message-form"
				@submit.prevent>

					<Actions ref="uploadMenu"
						:container="container"
						:boundaries-element="containerElement"
						:disabled="disabled"
						:aria-label="t('spreed', 'Share files to the conversation')"
						:aria-haspopup="true">
						<Paperclip slot="icon"
							:size="16"
							decorative
							title="" />
						<ActionButton v-if="canUploadFiles"
							:close-after-click="true"
							icon="icon-upload"
							@click.prevent="clickImportInput">
							{{ t('spreed', 'Upload new files') }}
						</ActionButton>
						<ActionButton v-if="canShareFiles"
							:close-after-click="true"
							icon="icon-folder"
							@click.prevent="handleFileShare">
							{{ t('spreed', 'Share from Files') }}
						</ActionButton>
					</Actions>

						<EmojiPicker v-if="!disabled"
							:container="container"
							:close-on-select="false"
							@select="addEmoji">
							<button type="button"
								:disabled="disabled"
								class="nc-button nc-button__main emoji-picker-button"
								:aria-label="t('spreed', 'Add emoji')"
								:aria-haspopup="true">
								<EmoticonOutline :size="16"
									decorative
									title="" />
							</button>
						</EmojiPicker>

						<button v-else
							type="button"
							:disabled="true"
							class="nc-button nc-button__main emoji-picker-button">
							<EmoticonOutline :size="16"
								decorative
								title="" />
						</button>

						<Quote :is-new-message-form-quote="true"
							:parent-id="messageToBeReplied.id"
							v-bind="messageToBeReplied" />

					<AdvancedInput ref="advancedInput"
						v-model="text"
						:token="token"
						:active-input="!disabled"
						:placeholder-text="placeholderText"
						:aria-label="placeholderText"
						@update:contentEditable="contentEditableToParsed"
						@submit="handleSubmit"
						@files-pasted="handlePastedFiles" />

				<AudioRecorder v-if="!hasText && canUploadFiles"
					:disabled="disabled"
					@recording="handleRecording"
					@audio-file="handleAudioFile" />

				<button v-else
					:disabled="disabled"
					type="submit"
					:aria-label="t('spreed', 'Send message')"
					class="nc-button nc-button__main new-message-form__send-button"
					@click.prevent="handleSubmit">
					<Send title=""
						:size="16"
						decorative />
				</button>
			</form>

</template>


style update.

style update.


Not clickable payment list

Not clickable payment list

<template>

        <v-container>

                <v-pagination v-bind:length.number="pagination.paging.total_pages"
                              v-bind:disabled="loading"
                              v-model="page"
                ></v-pagination>

            <v-row>
                <v-col xs12>
                    <v-card>
                        <v-table-overflow>

                                    {{$t('id')}}
                                    {{$t('amount')}}
                                    {{$t('created_at')}}

                                <template v-for="(item, index) in pagination.items">

                                            <v-checkbox v-bind:id="'checkbox' + index" filled
                                                        class="text-xs-center"></v-checkbox>

                                            #{{item.id}}

                                        {{item.amount}}
                                        {{item.created_at | prettyDateFrom}}

                                </template>

                        </v-table-overflow>
                    </v-card>
                </v-col>
            </v-row>

        </v-container>

</template>


Working validation finally!

Working validation finally!

<template>

        <v-container>
            <v-row class="mt-5">
                <v-col lg3></v-col>
                <v-col xs12 md6 lg6>
                    <v-card class="mp-5">
                        <v-card-text>

{{ $t('user.register.header') }}

                            <form v-on:submit.prevent="register">
                                <text-input
                                    :label="$t('user.fullname')"
                                    :placeholder="$t('user.placeholder.fullname')"
                                    name="fullname" v-model="fullname"
                                ></text-input>
                                <text-input
                                    :label="$t('user.username')"
                                    :placeholder="$t('user.placeholder.username')"
                                    name="username" v-model="username"
                                ></text-input>
                                <text-input email type="email"
                                    :label="$t('user.email')"
                                    :placeholder="$t('user.placeholder.email')"
                                    name="email" v-model="email"
                                ></text-input>
                                <text-input type="password"
                                    :label="$t('user.password')"
                                    :placeholder="$t('user.placeholder.password')"
                                    name="password" v-model="password"
                                ></text-input>
                                <text-input type="password"
                                    :label="$t('user.repeatpassword')"
                                    :placeholder="$t('user.placeholder.password')"
                                    name="repeatpassword" v-model="repeatpassword"
                                ></text-input>

                                <v-btn class="aligned" flat="flat" dark="dark" success block type="submit">
                                    <v-icon left>vpn_key</v-icon>
                                    {{$t('user.register.button')}}
                                </v-btn>
                            </form>
                        </v-card-text>
                    </v-card>
                </v-col>
            </v-row>
        </v-container>

</template>

<template>

        <v-container>
            <v-row class="mt-5">
                <v-col lg3></v-col>
                <v-col xs12 md6 lg6>
                    <v-card class="mp-5">
                        <v-card-text>

{{ $t('user.register.header') }}

                            <form v-on:submit.prevent="">
                                <text-input
                                    :label="$t('user.fullname')"
                                    :placeholder="$t('user.placeholder.fullname')"
                                    name="fullname" v-model="fullname"
                                ></text-input>
                                <text-input
                                    :label="$t('user.username')"
                                    :placeholder="$t('user.placeholder.username')"
                                    name="username" v-model="username"
                                ></text-input>
                                <text-input email="true"
                                    :label="$t('user.email')"
                                    :placeholder="$t('user.placeholder.email')"
                                    name="email" v-model="email"
                                ></text-input>
                                <text-input type="password"
                                    :label="$t('user.password')"
                                    :placeholder="$t('user.placeholder.password')"
                                    name="password" v-model="password"
                                ></text-input>
                                <text-input type="password"
                                    :label="$t('user.repeatpassword')"
                                    :placeholder="$t('user.placeholder.password')"
                                    name="repeatpassword" v-model="repeatpassword"
                                ></text-input>

                                <v-btn class="aligned" flat="flat" dark="dark" success block type="submit" v-on:click.native="register()">
                                    <v-icon left>vpn_key</v-icon>
                                    {{$t('user.register.button')}}
                                </v-btn>
                            </form>
                        </v-card-text>
                    </v-card>
                </v-col>
            </v-row>
        </v-container>

</template>