vl-interaction-select: add missing `hitTolerance` prop to the constructor

<template>
  <i
    :id="vmId"
    :class="vmClass"
    style="display: none !important;">
    <slot :features="featuresDataProj" />
  </i>
</template>

<script>
  import debounce from 'debounce-promise'
  import { never, shiftKeyOnly, singleClick } from 'ol/events/condition'
  import Feature from 'ol/Feature'
  import SelectInteraction from 'ol/interaction/Select'
  import Vue from 'vue'
  import { merge as mergeObs } from 'rxjs/observable'
  import { map as mapOp } from 'rxjs/operators'
  import { interaction, projTransforms, stylesContainer, featuresContainer } from '../../mixin'
  import { getFeatureId, createStyle, defaultEditStyle, getLayerId, initializeFeature } from '../../ol-ext'
  import { obsFromOlEvent } from '../../rx-ext'
  import { hasInteraction, hasMap } from '../../util/assert'
  import { constant, difference, forEach, isEqual, isFunction, mapValues, stubArray } from '../../util/minilo'
  import mergeDescriptors from '../../util/multi-merge-descriptors'
  import { makeWatchers } from '../../util/vue-helpers'

  export default {
    name: 'VlInteractionSelect',
    mixins: [interaction, featuresContainer, stylesContainer, projTransforms],
    stubVNode: {
      empty: false,
      attrs () {
        return {
          id: this.vmId,
          class: this.vmClass,
        }
      },
    },
    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 {function|undefined}
       */
      filter: {
        type: Function,
        default: constant(true),
      },
      /**
       * A list of layers from which features should be selected. Alternatively, a filter function can be provided.
       * @type {string[]|function|undefined}
       */
      layers: {
        type: [Array, Function],
        default: undefined,
      },
      /**
       * 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[]|Object[]}
       */
      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 {function|undefined}
       */
      addCondition: {
        type: Function,
        default: 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 {function|undefined}
       */
      condition: {
        type: Function,
        default: 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 {function|undefined}
       */
      removeCondition: {
        type: Function,
        default: 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 {function|undefined}
       */
      toggleCondition: {
        type: Function,
        default: shiftKeyOnly,
      },
    },
    computed: {
      layerFilter () {
        return Array.isArray(this.layers)
          ? layer => this.layers.includes(getLayerId(layer))
          : this.layers
      },
    },
    watch: {
      features: {
        deep: true,
        handler (features) {
          if (!this.$interaction || isEqual(features, this.featuresDataProj)) return

          features = features.slice().map(feature => initializeFeature({ ...feature }))
          this.addFeatures(features)

          const forUnselect = difference(this.getFeatures(), features, (a, b) => getFeatureId(a) === getFeatureId(b))
          this.removeFeatures(forUnselect)
        },
      },
      featuresDataProj: {
        deep: true,
        handler: debounce(function (features) {
          this.$emit('update:features', features)
        }, 1000 / 60),
      },
      ...makeWatchers([
        'filter',
        'hitTolerance',
        'multi',
        'wrapX',
        'addCondition',
        'condition',
        'removeCondition',
        'toggleCondition',
      ], () => function () {
        this.scheduleRecreate()
      }),
    },
    methods: {
      /**
       * @return {Select}
       * @protected
       */
      createInteraction () {
        return new SelectInteraction({
          features: this.$featuresCollection,
          multi: this.multi,
          wrapX: this.wrapX,
          filter: this.filter,
          layers: this.layerFilter,
          hitTolerance: this.hitTolerance,
          style: this.createStyleFunc(),
          addCondition: this.addCondition,
          condition: this.condition,
          removeCondition: this.removeCondition,
          toggleCondition: this.toggleCondition,
        })
      },
      /**
       * @return {function(feature: Feature): Style}
       * @protected
       */
      getDefaultStyles () {
        const defaultStyles = mapValues(defaultEditStyle(), styles => styles.map(createStyle))

        return function __selectDefaultStyleFunc (feature) {
          if (feature.getGeometry()) {
            return defaultStyles[feature.getGeometry().getType()]
          }
        }
      },
      /**
       * @returns {Object}
       * @protected
       */
      getServices () {
        return mergeDescriptors(
          this::interaction.methods.getServices(),
          this::stylesContainer.methods.getServices(),
        )
      },
      /**
       * @return {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 {Object|Vue|Feature|string|number} feature
       * @return {void}
       * @throws {Error}
       */
      select (feature) {
        hasMap(this)

        if (feature instanceof Vue) {
          feature = feature.$feature
        }

        if (!(feature instanceof Feature)) {
          const featureId = getFeatureId(feature)
          if (!featureId) {
            throw new Error('Undefined feature id')
          }

          feature = undefined
          forEach(this.$map.getLayers().getArray(), layer => {
            if (this.layerFilter && !this.layerFilter(layer)) {
              return false
            }

            const source = layer.getSource()
            if (source && isFunction(source.getFeatureById)) {
              feature = source.getFeatureById(featureId)
            }

            return !feature
          })
        }

        if (!feature) return

        this.addFeature(feature)
      },
      /**
       * @param {Object|Vue|Feature|string|number} feature
       * @return {void}
       */
      unselect (feature) {
        this.removeFeature(feature)
      },
      /**
       * Removes all features from selection.
       * @return {void}
       */
      unselectAll () {
        this.clearFeatures()
      },
      /**
       * @param {Array<{style: Style, condition: (function|boolean|undefined)}>|function(feature: Feature): Style|Vue|undefined} styles
       * @return {void}
       * @protected
       */
      setStyle (styles) {
        if (styles !== this._styles) {
          this._styles = styles
          this.scheduleRefresh()
        }
      },
      /**
       * @return {void}
       * @protected
       */
      subscribeAll () {
        this::interaction.methods.subscribeAll()
        this::subscribeToInteractionChanges()
      },
    },
  }

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

    const select = obsFromOlEvent(this.$featuresCollection, 'add')
      .pipe(
        mapOp(({ element }) => ({ type: 'select', feature: element }))
      )
    const unselect = obsFromOlEvent(this.$featuresCollection, 'remove')
      .pipe(
        mapOp(({ element }) => ({ type: 'unselect', feature: element }))
      )
    const events = mergeObs(select, unselect)

    this.subscribeTo(events, evt => {
      this.$nextTick(() => {
        this.$emit(evt.type, evt.feature)
      })
    })
  }
</script>

<template>
  <i
    :id="vmId"
    :class="vmClass"
    style="display: none !important;">
    <slot :features="featuresDataProj" />
  </i>
</template>

<script>
  import debounce from 'debounce-promise'
  import { never, shiftKeyOnly, singleClick } from 'ol/events/condition'
  import Feature from 'ol/Feature'
  import SelectInteraction from 'ol/interaction/Select'
  import Vue from 'vue'
  import { merge as mergeObs } from 'rxjs/observable'
  import { map as mapOp } from 'rxjs/operators'
  import { interaction, projTransforms, stylesContainer, featuresContainer } from '../../mixin'
  import { getFeatureId, createStyle, defaultEditStyle, getLayerId, initializeFeature } from '../../ol-ext'
  import { obsFromOlEvent } from '../../rx-ext'
  import { hasInteraction, hasMap } from '../../util/assert'
  import { constant, difference, forEach, isEqual, isFunction, mapValues, stubArray } from '../../util/minilo'
  import mergeDescriptors from '../../util/multi-merge-descriptors'
  import { makeWatchers } from '../../util/vue-helpers'

  export default {
    name: 'VlInteractionSelect',
    mixins: [interaction, featuresContainer, stylesContainer, projTransforms],
    stubVNode: {
      empty: false,
      attrs () {
        return {
          id: this.vmId,
          class: this.vmClass,
        }
      },
    },
    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 {function|undefined}
       */
      filter: {
        type: Function,
        default: constant(true),
      },
      /**
       * A list of layers from which features should be selected. Alternatively, a filter function can be provided.
       * @type {string[]|function|undefined}
       */
      layers: {
        type: [Array, Function],
        default: undefined,
      },
      /**
       * 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[]|Object[]}
       */
      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 {function|undefined}
       */
      addCondition: {
        type: Function,
        default: 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 {function|undefined}
       */
      condition: {
        type: Function,
        default: 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 {function|undefined}
       */
      removeCondition: {
        type: Function,
        default: 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 {function|undefined}
       */
      toggleCondition: {
        type: Function,
        default: shiftKeyOnly,
      },
    },
    computed: {
      layerFilter () {
        return Array.isArray(this.layers)
          ? layer => this.layers.includes(getLayerId(layer))
          : this.layers
      },
    },
    watch: {
      features: {
        deep: true,
        handler (features) {
          if (!this.$interaction || isEqual(features, this.featuresDataProj)) return

          features = features.slice().map(feature => initializeFeature({ ...feature }))
          this.addFeatures(features)

          const forUnselect = difference(this.getFeatures(), features, (a, b) => getFeatureId(a) === getFeatureId(b))
          this.removeFeatures(forUnselect)
        },
      },
      featuresDataProj: {
        deep: true,
        handler: debounce(function (features) {
          this.$emit('update:features', features)
        }, 1000 / 60),
      },
      ...makeWatchers([
        'filter',
        'hitTolerance',
        'multi',
        'wrapX',
        'addCondition',
        'condition',
        'removeCondition',
        'toggleCondition',
      ], () => function () {
        this.scheduleRecreate()
      }),
    },
    methods: {
      /**
       * @return {Select}
       * @protected
       */
      createInteraction () {
        return new SelectInteraction({
          features: this.$featuresCollection,
          multi: this.multi,
          wrapX: this.wrapX,
          filter: this.filter,
          layers: this.layerFilter,
          style: this.createStyleFunc(),
          addCondition: this.addCondition,
          condition: this.condition,
          removeCondition: this.removeCondition,
          toggleCondition: this.toggleCondition,
        })
      },
      /**
       * @return {function(feature: Feature): Style}
       * @protected
       */
      getDefaultStyles () {
        const defaultStyles = mapValues(defaultEditStyle(), styles => styles.map(createStyle))

        return function __selectDefaultStyleFunc (feature) {
          if (feature.getGeometry()) {
            return defaultStyles[feature.getGeometry().getType()]
          }
        }
      },
      /**
       * @returns {Object}
       * @protected
       */
      getServices () {
        return mergeDescriptors(
          this::interaction.methods.getServices(),
          this::stylesContainer.methods.getServices(),
        )
      },
      /**
       * @return {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 {Object|Vue|Feature|string|number} feature
       * @return {void}
       * @throws {Error}
       */
      select (feature) {
        hasMap(this)

        if (feature instanceof Vue) {
          feature = feature.$feature
        }

        if (!(feature instanceof Feature)) {
          const featureId = getFeatureId(feature)
          if (!featureId) {
            throw new Error('Undefined feature id')
          }

          feature = undefined
          forEach(this.$map.getLayers().getArray(), layer => {
            if (this.layerFilter && !this.layerFilter(layer)) {
              return false
            }

            const source = layer.getSource()
            if (source && isFunction(source.getFeatureById)) {
              feature = source.getFeatureById(featureId)
            }

            return !feature
          })
        }

        if (!feature) return

        this.addFeature(feature)
      },
      /**
       * @param {Object|Vue|Feature|string|number} feature
       * @return {void}
       */
      unselect (feature) {
        this.removeFeature(feature)
      },
      /**
       * Removes all features from selection.
       * @return {void}
       */
      unselectAll () {
        this.clearFeatures()
      },
      /**
       * @param {Array<{style: Style, condition: (function|boolean|undefined)}>|function(feature: Feature): Style|Vue|undefined} styles
       * @return {void}
       * @protected
       */
      setStyle (styles) {
        if (styles !== this._styles) {
          this._styles = styles
          this.scheduleRefresh()
        }
      },
      /**
       * @return {void}
       * @protected
       */
      subscribeAll () {
        this::interaction.methods.subscribeAll()
        this::subscribeToInteractionChanges()
      },
    },
  }

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

    const select = obsFromOlEvent(this.$featuresCollection, 'add')
      .pipe(
        mapOp(({ element }) => ({ type: 'select', feature: element }))
      )
    const unselect = obsFromOlEvent(this.$featuresCollection, 'remove')
      .pipe(
        mapOp(({ element }) => ({ type: 'unselect', feature: element }))
      )
    const events = mergeObs(select, unselect)

    this.subscribeTo(events, evt => {
      this.$nextTick(() => {
        this.$emit(evt.type, evt.feature)
      })
    })
  }
</script>


7138: updated reaction row style

7138: updated reaction row style

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

<template>

		<template v-if="!isReactionsMenuOpen">
			<Button v-if="acceptsReactions"
				type="tertiary"
				@click="openReactionsMenu">
				<template #icon>
					<EmoticonOutline :size="20" />
				</template>
			</Button>
			<Actions v-show="isReplyable">
				<ActionButton icon="icon-reply"
					@click.stop="handleReply">
					{{ t('spreed', 'Reply') }}
				</ActionButton>
			</Actions>
			<Actions :force-menu="true"
				:container="`#message_${id}`"
				:boundaries-element="containerElement"
				@open="onMenuOpen"
				@close="onMenuClose">
				<ActionButton v-if="isPrivateReplyable"
					icon="icon-user"
					:close-after-click="true"
					@click.stop="handlePrivateReply">
					{{ t('spreed', 'Reply privately') }}
				</ActionButton>
				<ActionButton icon="icon-external"
					:close-after-click="true"
					@click.stop.prevent="handleCopyMessageLink">
					{{ t('spreed', 'Copy message link') }}
				</ActionButton>
				<ActionButton :close-after-click="true"
					@click.stop="handleMarkAsUnread">
					<template #icon>
						<EyeOffOutline decorative
							title=""
							:size="16" />
					</template>
					{{ t('spreed', 'Mark as unread') }}
				</ActionButton>
				<ActionLink v-if="linkToFile"
					icon="icon-text"
					:href="linkToFile">
					{{ t('spreed', 'Go to file') }}
				</ActionLink>
				<ActionButton v-if="!isCurrentGuest && !isFileShare && !isDeletedMessage"
					:close-after-click="true"
					@click.stop="showForwarder = true">
					<Share slot="icon"
						:size="16"
						decorative
						title="" />
					{{ t('spreed', 'Forward message') }}
				</ActionButton>
				<ActionSeparator v-if="messageActions.length > 0" />
				<template v-for="action in messageActions">
					<ActionButton :key="action.label"
						:icon="action.icon"
						:close-after-click="true"
						@click="action.callback(messageApiData)">
						{{ action.label }}
					</ActionButton>
				</template>
				<template v-if="isDeleteable">
					<ActionSeparator />
					<ActionButton icon="icon-delete"
						:close-after-click="true"
						@click.stop="handleDelete">
						{{ t('spreed', 'Delete') }}
					</ActionButton>
				</template>
			</Actions>
		</template>

		<template v-if="isReactionsMenuOpen">
			<Button type="tertiary"
				@click="closeReactionsMenu">
				<template #icon>
					<ArrowLeft :size="20" />
				</template>
			</Button>
			<Button type="tertiary"
				@click="handleReactionClick('👍')">
				<template #icon>
					👍
				</template>
			</Button>
			<Button type="tertiary"
				@click="handleReactionClick('❤')">
				<template #icon>
					❤
				</template>
			</Button>
			<EmojiPicker :container="`#message_${id} .message-buttons-bar`"
				@select="handleReactionClick"
				@after-show="onEmojiPickerOpen"
				@after-hide="onEmojiPickerClose">
				<Button type="tertiary">
					<template #icon>
						<Plus :size="20" />
					</template>
				</Button>
			</EmojiPicker>
		</template>
		<Forwarder v-if="showForwarder"
			:message-object="messageObject"
			@close="showForwarder = false" />

</template>


实现向下拖动歌手歌曲列表时，歌手图片随之变大效果，调整eslint相关

实现向下拖动歌手歌曲列表时，歌手图片随之变大效果，调整eslint相关

<template>

    <scroll class="list" :data="songs" ref="list"
            @scroll="scroll"
            :probe-type="probeType"
            :listen-scroll="listenScroll">

        <song-list :songs="songs"></song-list>

    </scroll>

</template>


show columns | caret

show columns | caret

<template>

          {{ labelCategories }}

          {{ labelSets }}

          {{ labelQueries }}

          {{ item.attributes.name }}

              {{ column }}

</template>


Some small formatting changes

Some small formatting changes

User profile updates

<template>

{{ user.username }}

{{ user.level }} at {{ user.organization }}

Stats Summary

Points: {{ Math.round(user.score * 100) / 100 }}

Games Played:  {{ user.total_games_played }}

Submissions:  {{ user.total_submissions }}

Recent Games

                            Played On
                            Opponents
                            Map Size
                            Watch

                            {{ game.time_played | moment("MMMM Do YYYY") }}

                            {{ game.map_width }} by {{ game.map_height }}
                            {{ game.game_id }}

</template>

<template>

{{ user.username }}

{{ user.level }} at {{ user.organization }}

Stats Summary

{{ user.score }} points

{{ user.total_games_played }} games played

{{ user.total_submissions }} submissions

Recent Games

                            Time Played
                            Opponents
                            Map Specification
                            Watch

                            {{ game.time_played | moment("MMMM Do YYYY") }}

                            {{ game.map_width }} by {{ game.map_height }}
                            {{ game.game_id }}

</template>


修改用户搜索组件

修改用户搜索组件

修改用户搜索组件

<template>

	  <input type="text"
	    class="form-control dropdown-toggle"
	    data-toggle="dropdown"
	    aria-haspopup="true"
	    aria-expanded="false"
	    v-model="username"
	    @input="searchUser"
	    placeholder="请输入用户名">

	      <template v-if="users.length">

	            <img :src="`${user.avatar}?w=40&height=40`" class="img-circle avatar" v-if="user.avatar">

	            {{ user.name }} #ID {{ user.id }}

	      </template>
	      <template v-else>

无相关用户

	      </template>

</template>

<template>

	  <input type="text"
	    class="form-control dropdown-toggle"
	    data-toggle="dropdown"
	    aria-haspopup="true"
	    aria-expanded="false"
	    v-model="username"
	    @input="searchUser"
	    placeholder="请输入用户名">

	      <template v-if="users.length">

	            <img :src="`${user.avatar}?w=40&height=40`" class="img-circle avatar" v-if="user.avatar">

	            {{ user.name }} # ID {{ user.id }}

	      </template>
	      <template v-else>

无相关用户

	      </template>

</template>


updated breadcrumb fontsize to 0.9em

updated breadcrumb fontsize to 0.9em


Reference kLinearLoader component

Reference kLinearLoader component

<template v-if="itemId">

          <k-linear-loader
            v-show="loading"
            :delay="false"
            type="indeterminate"
          />

        <k-button
          v-if="anyHints && availableHints > 0"
          :primary="false"
          :raised="false"
          @click="takeHint"
          class="hint-btn"
          :text="$tr('hint', {hintsLeft: availableHints})"
        />
        <k-button
          v-else-if="anyHints"
          :primary="false"
          :raised="false"
          class="hint-btn"
          disabled
          :text="$tr('noMoreHint')"
        />

{{ $tr("hintLabel") }}

      <transition name="expand">

{{ message }}

      </transition>

      <k-button
        v-if="scratchpad"
        :primary="false"
        :raised="false"
        id="scratchpad-show"
        :text="$tr('showScratch')"
      />
      <k-button
        v-else
        :primary="false"
        :raised="false"
        disabled
        id="scratchpad-not-available"
        :text="$tr('notAvailable')"
      />

</template>

<template v-if="itemId">

          <k-indeterminate-linear-indicator
            v-show="loading"
            :delay="false"
          />

        <k-button
          v-if="anyHints && availableHints > 0"
          :primary="false"
          :raised="false"
          @click="takeHint"
          class="hint-btn"
          :text="$tr('hint', {hintsLeft: availableHints})"
        />
        <k-button
          v-else-if="anyHints"
          :primary="false"
          :raised="false"
          class="hint-btn"
          disabled
          :text="$tr('noMoreHint')"
        />

{{ $tr("hintLabel") }}

      <transition name="expand">

{{ message }}

      </transition>

      <k-button
        v-if="scratchpad"
        :primary="false"
        :raised="false"
        id="scratchpad-show"
        :text="$tr('showScratch')"
      />
      <k-button
        v-else
        :primary="false"
        :raised="false"
        disabled
        id="scratchpad-not-available"
        :text="$tr('notAvailable')"
      />

</template>


Remove the plus icon on project creation button

<template>
  <app-page v-if="resourcesReady" name="projects" layout="application">
    <projects-header slot="header"></projects-header>

    <ly-section
      v-if="projectsInProgress.length > 0"
      :title="$t('projects.page.projectsInProgress')"
    >
      <ly-list>
        <project-item
          v-for="project in projectsInProgress"
          :key="project.id"
          :project="project"
        ></project-item>
      </ly-list>
    </ly-section>

    <ly-section :title="$t('projects.page.futureProjects')">
      <template slot="actions">
        <ly-button
          v-if="!createFormEnabled"
          type="primary"
          size="large"
          @click="createFormEnabled = true"
        >
          {{ $t('projects.page.createProject') }}
        </ly-button>
        <project-create-form
          v-else
          @cancel="disableCreateForm"
          autofocus
        ></project-create-form>
      </template>

      <ly-list :placeholder="$t('projects.page.projectsPlaceholder')">
        <ly-list-group
          v-for="(projects, firstCharacter) in futureProjects"
          :key="firstCharacter"
          :name="firstCharacter"
        >
          <project-item
            v-for="project in projects"
            :key="project.id"
            :project="project"
          ></project-item>
        </ly-list-group>
      </ly-list>
    </ly-section>

    <ly-section v-if="finishedProjects.length > 0" :title="$t('projects.page.finishedProjects')">
      <ly-list stripped>
        <project-item-finished
          v-for="project in finishedProjects"
          :key="project.id"
          :project="project"
        ></project-item-finished>
      </ly-list>
    </ly-section>
  </app-page>
  <loading-page v-else></loading-page>
</template>

<script>
  import { mapGetters } from 'vuex'

  import ResourcesLoader from '@/components/mixins/ResourcesLoader'

  import ProjectsHeader from './ProjectsHeader'
  import ProjectCreateForm from './ProjectCreateForm'
  import ProjectItem from './ProjectItem'
  import ProjectItemFinished from './ProjectItemFinished'

  export default {
    mixins: [ResourcesLoader],

    components: {
      ProjectsHeader,
      ProjectCreateForm,
      ProjectItem,
      ProjectItemFinished,
    },

    data () {
      return {
        createFormEnabled: false,
      }
    },

    computed: {
      ...mapGetters({
        futureProjects: 'projects/listFutureByFirstCharacter',
        finishedProjects: 'projects/listFinished',
        projectsInProgress: 'projects/listInProgress',
      }),
    },

    methods: {
      disableCreateForm () {
        this.createFormEnabled = false
      },
    },
  }
</script>

<template>
  <app-page v-if="resourcesReady" name="projects" layout="application">
    <projects-header slot="header"></projects-header>

    <ly-section
      v-if="projectsInProgress.length > 0"
      :title="$t('projects.page.projectsInProgress')"
    >
      <ly-list>
        <project-item
          v-for="project in projectsInProgress"
          :key="project.id"
          :project="project"
        ></project-item>
      </ly-list>
    </ly-section>

    <ly-section :title="$t('projects.page.futureProjects')">
      <template slot="actions">
        <ly-button
          icon="plus"
          v-if="!createFormEnabled"
          type="primary"
          size="large"
          @click="createFormEnabled = true"
        >
          {{ $t('projects.page.createProject') }}
        </ly-button>
        <project-create-form
          v-else
          @cancel="disableCreateForm"
          autofocus
        ></project-create-form>
      </template>

      <ly-list :placeholder="$t('projects.page.projectsPlaceholder')">
        <ly-list-group
          v-for="(projects, firstCharacter) in futureProjects"
          :key="firstCharacter"
          :name="firstCharacter"
        >
          <project-item
            v-for="project in projects"
            :key="project.id"
            :project="project"
          ></project-item>
        </ly-list-group>
      </ly-list>
    </ly-section>

    <ly-section v-if="finishedProjects.length > 0" :title="$t('projects.page.finishedProjects')">
      <ly-list stripped>
        <project-item-finished
          v-for="project in finishedProjects"
          :key="project.id"
          :project="project"
        ></project-item-finished>
      </ly-list>
    </ly-section>
  </app-page>
  <loading-page v-else></loading-page>
</template>

<script>
  import { mapGetters } from 'vuex'

  import ResourcesLoader from '@/components/mixins/ResourcesLoader'

  import ProjectsHeader from './ProjectsHeader'
  import ProjectCreateForm from './ProjectCreateForm'
  import ProjectItem from './ProjectItem'
  import ProjectItemFinished from './ProjectItemFinished'

  export default {
    mixins: [ResourcesLoader],

    components: {
      ProjectsHeader,
      ProjectCreateForm,
      ProjectItem,
      ProjectItemFinished,
    },

    data () {
      return {
        createFormEnabled: false,
      }
    },

    computed: {
      ...mapGetters({
        futureProjects: 'projects/listFutureByFirstCharacter',
        finishedProjects: 'projects/listFinished',
        projectsInProgress: 'projects/listInProgress',
      }),
    },

    methods: {
      disableCreateForm () {
        this.createFormEnabled = false
      },
    },
  }
</script>


devop: 🔧 added catch for outside of wallet

<template>

    <v-row
      v-if="!isSwapPage && hasNetworks"
      class="align-end justify-center justify-sm-space-between pa-0"
    >

        <v-btn-toggle
          v-model="toggleType"
          mandatory
          active-class="textDark white--text alig-end"
        >
          <v-btn small>Main</v-btn>
          <v-btn small>Test</v-btn>
          <v-btn small>All</v-btn>
        </v-btn-toggle>

      <v-col cols="12" sm="7" class="order-sm-1">
        <mew-search
          placeholder="Find Network"
          :value="searchInput"
          @input="setSearch"
        />
      </v-col>
    </v-row>

    <app-user-msg-block
      v-if="showEmptySearch || isSwapPage"
      :message="emptySearchMes"
      :is-alert="isSwapPage"
      class="mt-5"
    />

    <v-radio-group v-model="networkSelected">
      <v-container
        v-for="(network, i) in networks"
        :key="network.name"
        :class="[
          { 'network-border-first': i === 0 },
          { 'network-border-last': i + 1 === networks.length },
          'py-4 px-5 network-border'
        ]"
      >
        <v-row class="pa-0 mew-body align-center justify-start">

          <v-img
            :class="network.name === 'MINTME' ? 'mint-me-color' : ''"
            :src="network.icon"
            :lazy-src="require('@/assets/images/currencies/icon-eth-grey.svg')"
            contain
            max-height="24px"
            max-width="24px"
          />

            {{ network.name }}

-

            {{ network.name_long }}

          <v-spacer />

          <v-radio
            :value="network.name"
            :class="['py-2 mb-0']"
            :disabled="networkLoading"
          >
          </v-radio>
        </v-row>
      </v-container>
    </v-radio-group>

</template>


devop: 🔧 added filter to mintme

devop: 🔧 added filter to mintme

<template>

    <v-row
      v-if="!isSwapPage && hasNetworks"
      class="align-end justify-center justify-sm-space-between pa-0"
    >

        <v-btn-toggle
          v-model="toggleType"
          mandatory
          active-class="textDark white--text alig-end"
        >
          <v-btn small>Main</v-btn>
          <v-btn small>Test</v-btn>
          <v-btn small>All</v-btn>
        </v-btn-toggle>

      <v-col cols="12" sm="7" class="order-sm-1">
        <mew-search
          placeholder="Find Network"
          :value="searchInput"
          @input="setSearch"
        />
      </v-col>
    </v-row>

    <app-user-msg-block
      v-if="showEmptySearch || isSwapPage"
      :message="emptySearchMes"
      :is-alert="isSwapPage"
      class="mt-5"
    />

    <v-radio-group v-model="networkSelected">
      <v-container
        v-for="(network, i) in networks"
        :key="network.name"
        :class="[
          { 'network-border-first': i === 0 },
          { 'network-border-last': i + 1 === networks.length },
          'py-4 px-5 network-border'
        ]"
      >
        <v-row class="pa-0 mew-body align-center justify-start">

          <v-img
            :class="network.name === 'MINTME' ? 'mint-me-color' : ''"
            :src="network.icon"
            :lazy-src="require('@/assets/images/currencies/icon-eth-grey.svg')"
            contain
            max-height="24px"
            max-width="24px"
          />

            {{ network.name }}

-

            {{ network.name_long }}

          <v-spacer />

          <v-radio :value="network.name" :class="['py-2 mb-0']"> </v-radio>
        </v-row>
      </v-container>
    </v-radio-group>

</template>

<template>

    <v-row
      v-if="!isSwapPage && hasNetworks"
      class="align-end justify-center justify-sm-space-between pa-0"
    >

        <v-btn-toggle
          v-model="toggleType"
          mandatory
          active-class="textDark white--text alig-end"
        >
          <v-btn small>Main</v-btn>
          <v-btn small>Test</v-btn>
          <v-btn small>All</v-btn>
        </v-btn-toggle>

      <v-col cols="12" sm="7" class="order-sm-1">
        <mew-search
          placeholder="Find Network"
          :value="searchInput"
          @input="setSearch"
        />
      </v-col>
    </v-row>

    <app-user-msg-block
      v-if="showEmptySearch || isSwapPage"
      :message="emptySearchMes"
      :is-alert="isSwapPage"
      class="mt-5"
    />

    <v-radio-group v-model="networkSelected">
      <v-container
        v-for="(network, i) in networks"
        :key="network.name"
        :class="[
          { 'network-border-first': i === 0 },
          { 'network-border-last': i + 1 === networks.length },
          'py-4 px-5 network-border'
        ]"
      >
        <v-row class="pa-0 mew-body align-center justify-start">

          <v-img
            :src="network.icon"
            :lazy-src="require('@/assets/images/currencies/icon-eth-grey.svg')"
            contain
            max-height="24px"
            max-width="24px"
          />

            {{ network.name }}

-

            {{ network.name_long }}

          <v-spacer />

          <v-radio :value="network.name" :class="['py-2 mb-0']"> </v-radio>
        </v-row>
      </v-container>
    </v-radio-group>

</template>


Don't use an expression inside one of the strings

<template>

          info
         {{ $tr('aboutChannelSets') }}

          add
         {{ $tr('addChannelSetButton') }}

      {{ $tr('loading') }}

      {{ $tr('noChannelSetsFound') }}

      <ChannelSetItem
        v-for="channelSet in channelSets"
        :key="channelSet.id"
        :channelSetID="channelSet.id"
      />

</template>


fix(inspector): display tooltip on abstract field key

<template>

    <VTooltip
      :style="{ marginLeft: depth * 14 + 'px' }"
      :disabled="!field.meta"
      :class="{
        'force-toolbar z-10': contextMenuOpen || editing,
      }"
      class="self"
      placement="left"
      :offset="[0, 24]"
      @click.native="onClick"
      @mouseenter.native="onContextMenuMouseEnter"
      @mouseleave.native="onContextMenuMouseLeave"
    >

        <input
          ref="keyInput"
          v-model="editedKey"
          class="edit-input key-input"
          :class="{ error: !keyValid }"
          @keydown.esc.capture.stop.prevent="cancelEdit()"
          @keydown.enter="submitEdit()"
        >

      {{ displayedKey }}:

        <input
          ref="editInput"
          v-model="editedValue"
          class="edit-input value-input text-black"
          :class="{ error: !valueValid }"
          list="special-tokens"
          @keydown.esc.capture.stop.prevent="cancelEdit()"
          @keydown.enter="submitEdit()"
        >

          <VueIcon
            v-if="!editValid"
            v-tooltip="editErrorMessage"
            class="small-icon warning"
            icon="warning"
          />
          <template v-else>
            <VueButton
              v-tooltip="{
                content: $t('DataField.edit.cancel.tooltip'),
                html: true
              }"
              class="icon-button flat"
              icon-left="cancel"
              @click="cancelEdit()"
            />
            <VueButton
              v-tooltip="{
                content: $t('DataField.edit.submit.tooltip'),
                html: true
              }"
              class="icon-button flat"
              icon-left="save"
              @click="submitEdit()"
            />
          </template>

      <template v-else>

          <VueDropdown
            v-if="valueDetails"
          >
            <template #trigger>
              <VueButton
                v-tooltip="'More details'"
                class="edit-value icon-button flat"
                icon-left="info"
              />
            </template>

            <template #default>

                  <VueButton
                    v-close-popper
                    class="edit-value icon-button flat"
                    icon-left="close"
                  />

{{ valueDetails }}

            </template>
          </VueDropdown>
          <VueButton
            v-if="valueType === 'native Error'"
            v-tooltip="'Log error to console'"
            class="edit-value icon-button flat"
            icon-left="input"
            @click="logToConsole('error')"
          />
          <VueButton
            v-if="isValueEditable"
            v-tooltip="'Edit value'"
            class="edit-value icon-button flat"
            icon-left="edit"
            @click="openEdit()"
          />
          <template v-if="quickEdits">
            <VueButton
              v-for="(info, index) of quickEdits"
              :key="index"
              v-tooltip="{
                content: info.title || 'Quick edit',
                html: true,
              }"
              :class="info.class"
              :icon-left="info.icon"
              class="quick-edit icon-button flat"
              @click="quickEdit(info, $event)"
            />
          </template>
          <VueButton
            v-if="isSubfieldsEditable && !addingValue"
            v-tooltip="'Add new value'"
            class="add-value icon-button flat"
            icon-left="add_circle"
            @click="addNewValue()"
          />
          <VueButton
            v-if="removable"
            v-tooltip="'Remove value'"
            class="remove-field icon-button flat"
            icon-left="delete"
            @click="removeField()"
          />

          <VueDropdown
            :open.sync="contextMenuOpen"
          >
            <VueButton
              slot="trigger"
              icon-left="more_vert"
              class="icon-button flat"
            />

              <VueDropdownButton
                icon-left="flip_to_front"
                @click="copyValue"
              >
                {{ $t('DataField.contextMenu.copyValue') }}
              </VueDropdownButton>
              <VueDropdownButton
                icon-left="flip_to_front"
                @click="copyPath"
              >
                {{ $t('DataField.contextMenu.copyPath') }}
              </VueDropdownButton>

          </VueDropdown>

      </template>

      <template #popper>

            {{ key }}
            {{ val }}

      </template>
    </VTooltip>

      <DataField
        v-for="subField in formattedSubFields"
        :key="subField.key"
        :field="subField"
        :parent-field="field"
        :depth="depth + 1"
        :path="`${path}.${subField.key}`"
        :editable="isEditable"
        :removable="isSubfieldsEditable"
        :renamable="editable && valueType === 'plain-object'"
        :force-collapse="forceCollapse"
        :is-state-field="isStateField"
        @edit-state="(path, payload) => $emit('edit-state', path, payload)"
      />
      <VueButton
        v-if="subFieldCount > limit"
        v-tooltip="'Show more'"
        :style="{ marginLeft: depthMargin + 'px' }"
        icon-left="more_horiz"
        class="icon-button flat more"
        @click="showMoreSubfields()"
      />
      <DataField
        v-if="isSubfieldsEditable && addingValue"
        ref="newField"
        :field="newField"
        :depth="depth + 1"
        :path="`${path}.${newField.key}`"
        :renamable="valueType === 'plain-object'"
        :force-collapse="forceCollapse"
        editable
        removable
        :is-state-field="isStateField"
        @cancel-edit="addingValue = false"
        @submit-edit="addingValue = false"
        @edit-state="(path, payload) => $emit('edit-state', path, payload)"
      />

</template>


docs(text-and-typography): fix typo in opacity example

resolves #11946

<template>

High-emphasis has an opacity of 87%.

Medium-emphasis text and hint text have opacities of 60%.

Disabled text has an opacity of 38%.

</template>

<template>

High-emphasis has an opacity of 87%.

Medium-emphasis text and hint text have opacities of 60%.

Disabled text has an opacity of 37%.

</template>


refactor:dropdown menu

refactor:dropdown menu

<template>

      {{title}}

        {{item.title}}
        {{item.title}}

</template>


fix: spelling in props

fix: spelling in props

<template>
  <v-sheet
    color="transparent"
    :max-width="maxWidth"
    :class="[
      data.centered ? 'text-center' : '',
      !noPageTitle ? 'page-title' : ''
    ]"
  >

      {{ data.toptitle }}

    <v-sheet
      :max-width="data.titleMaxWidth ? data.titleMaxWidth : '450px'"
      color="transparent"
      :class="data.centered ? 'mx-auto' : ''"
    >

        {{ data.title }}

    </v-sheet>

    <v-sheet
      :max-width="data.descriptionMaxWidth"
      color="transparent"
      :class="data.centered ? 'mx-auto' : ''"
    >

        {{ data.description }}

<slot />

    </v-sheet>

  </v-sheet>
</template>


Fix view mode switcher state

Fix view mode switcher state


Defaut à 0

Defaut à 0

<template>

                <select v-model="selected" class="form-select">
                    <option value="-1">Consulter les exercices</option>
                    <option v-for="level in levels" :value="level">Jour {{level}}</option>
                </select>

        <template v-if="selected > 0 && isValid">

                <img :src="`/urdle/${selected}/uml.png`" alt="">

Dans cet exercice vous devez :

Écrire le code (ou pseudo-code) permettant de définir les propriétés des classes proposées

Écrire 2 constructeurs (autre que celui par défaut) des classes proposés

Mise en pratique :

        </template>

                <input v-if="canNext" @click="selected++" type="button" value="Suivant" class="btn btn-primary" />
                <input v-else @click="selected--" type="button" value="Précédent" class="btn btn-primary" />

</template>


Improve usability of the toggler component

<template>
  <div
    class="v-map-toggler"
    tabindex="0"
    :class="{ 'v-map-toggler--active': active }"
    @keyup.enter.stop.prevent="toggle"
    @click.stop="toggle"
  >
    <div class="v-map-toggler__switch">
      {{ actionText }}
    </div>
  </div>
</template>
<script>
export default {
  model: {
    event: 'change',
    prop: 'active'
  },
  props: {
    active: {
      type: Boolean,
      required: true
    },
    onText: {
      type: String,
      default: 'ON',
    },
    offText: {
      type: String,
      default: 'OFF'
    }
  },
  computed: {
    actionText () {
      if (this.active) {
        return this.onText
      }

      return this.offText
    }
  },
  methods: {
    toggle (newState) {
      if (typeof newState === 'boolean') {
        this.$emit('change', newState)
      } else {
        this.$emit('change', !this.active)
      }
    }
  }
}
</script>

<template>
  <div
    class="v-map-toggler"
    :class="{ 'v-map-toggler--active': active }"
    @click.stop="$emit('change', !active)"
  >
    <div class="v-map-toggler__switch">
      {{ actionText }}
    </div>
  </div>
</template>
<script>
export default {
  model: {
    event: 'change',
    prop: 'active'
  },
  props: {
    active: {
      type: Boolean,
      required: true
    },
    onText: {
      type: String,
      default: 'ON',
    },
    offText: {
      type: String,
      default: 'OFF'
    }
  },
  computed: {
    actionText () {
      if (this.active) {
        return this.onText
      }

      return this.offText
    }
  }
}
</script>


fix: public key only wallet in paper wallet

<template>

    <v-container>
      <v-row>
        <v-col cols="12" md="6" class="d-flex align-center mr-auto">

            |Paper Wallet

        </v-col>
        <v-col cols="auto">

support@myetherwallet.com

https://www.myetherwallet.com

        </v-col>
      </v-row>
    </v-container>

    <v-row class="mt-3 mt-md-12 d-flex align-center">
      <v-col cols="12" md="2">

      </v-col>
      <v-col col="auto">

            My address icon

            Always look for the icon when sending to this wallet. And please
            keep your paper wallet at a

              Safe Place!

      </v-col>
    </v-row>

    <v-divider class="my-6"></v-divider>

    <v-container>
      <v-row class="align-center mb-6">
        <v-col cols="12" md="8" class="mr-auto">

            My wallet address

            {{ getChecksumAddressString }}

        </v-col>
        <v-col cols="auto">
          <qr-code :data="address" :height="140" :width="140" />
        </v-col>
      </v-row>

      <v-row v-if="showPrivateKey">
        <v-col cols="12" md="8" class="mr-auto">

            <v-icon color="error">mdi-alert</v-icon>
            My Private Key

            You might LOSE your MONEY if you share this Private Key with anyone!
            KEEP YOUR PRIVATE KEY IN SAFE PLACE!

            {{ key }}

        </v-col>
        <v-col cols="auto">
          <qr-code :data="key" :height="140" :width="140" :type-number="10" />
        </v-col>
      </v-row>
    </v-container>

    <v-divider class="my-6"></v-divider>

</template>

<template>

    <v-container>
      <v-row>
        <v-col cols="12" md="6" class="d-flex align-center mr-auto">

            |Paper Wallet

        </v-col>
        <v-col cols="auto">

support@myetherwallet.com

https://www.myetherwallet.com

        </v-col>
      </v-row>
    </v-container>

    <v-row class="mt-3 mt-md-12 d-flex align-center">
      <v-col cols="12" md="2">

      </v-col>
      <v-col col="auto">

            My address icon

            Always look for the icon when sending to this wallet. And please
            keep your paper wallet at a

              Safe Place!

      </v-col>
    </v-row>

    <v-divider class="my-6"></v-divider>

    <v-container>
      <v-row class="align-center mb-6">
        <v-col cols="12" md="8" class="mr-auto">

            My wallet address

            {{ getChecksumAddressString }}

        </v-col>
        <v-col cols="auto">
          <qr-code :data="address" :height="140" :width="140" />
        </v-col>
      </v-row>

      <v-row v-if="!isHardware">
        <v-col cols="12" md="8" class="mr-auto">

            <v-icon color="error">mdi-alert</v-icon>
            My Private Key

            You might LOSE your MONEY if you share this Private Key with anyone!
            KEEP YOUR PRIVATE KEY IN SAFE PLACE!

            {{ key }}

        </v-col>
        <v-col cols="auto">
          <qr-code :data="key" :height="140" :width="140" :type-number="10" />
        </v-col>
      </v-row>
    </v-container>

    <v-divider class="my-6"></v-divider>

</template>


Fix icon width

Fix icon width

<template>

      <mat-svg
        v-if="!taskHasFailed"
        category="action"
        name="autorenew"
        class="inprogress"
      />
      <mat-svg
        v-else
        category="alert"
        name="error"
        class="error"
      />

        {{ stageText }}

      <ui-progress-linear
        class="progress-linear"
        :progress="formattedPercentage"
        :type="progressBarType"
        color="primary"
      />

      {{ progressMessage }}

      <k-button
        :text="taskHasCompleted ? $tr('close') : $tr('cancel')"
        :primary="false"
        :raised="false"
        @click="endTask()"
      />

</template>


Resolve MUWM-4958

Resolve MUWM-4958

<template>
  <uw-card
    v-if="!isReady || hasRegisterNotices"
    :loaded="isReady" :errored="isErrored"
    :errored-show="showError"
  >
    <template #card-heading>
    </template>

    <template #card-body>

          Review Critical Summer Registration Info

        Consider Early Fall Start

        Early Fall Start is a single 5-credit intensive course held
        over four weeks before autumn quarter begins. Benefit from a small
        class size, an early introduction to college life, and a lighter
        course load during autumn quarter. The language courses may be
        particularly helpful to international students!
        Learn more about Early Fall Start and register.

    </template>
  </uw-card>
</template>

<template>
  <uw-card
    v-if="!isReady || hasRegisterNotices"
    :loaded="isReady" :errored="isErrored"
    :errored-show="showError"
  >
    <template #card-heading>

        Summer & Early Fall Start

    </template>

    <template #card-body>

          Review Critical Summer Registration Info

        Consider Early Fall Start

        Early Fall Start is a single 5-credit intensive course held
        over four weeks before autumn quarter begins. Benefit from a small
        class size, an early introduction to college life, and a lighter
        course load during autumn quarter. The language courses may be
        particularly helpful to international students!
        Learn more about Early Fall Start and register.

    </template>
  </uw-card>
</template>


Set documents to list view on mobile view

<template>
  <div>
    <userInputs
      v-on:changeViewMode="changeViewMode"
      v-on:execute="executeAction"
      v-on:changeFolder="changeFolder"
          :folder-path="folderPath"
    ></userInputs>
    <documents
      v-on:changeFolder="changeFolder"
          :view-mode="viewMode"
          :docs="docs"
    ></documents>
  </div>
</template>

<script>
import fileManager from 'src/logic/filemanager';
import documents from './Documents';
import userInputs from './UserInputs';

let fileManagerVue = this;

function constructFolderPath(folderObj) {
  const folderPath = [];

  folderPath.push(folderObj);
  while (folderObj.parentFolder !== null) {
    folderPath.push(folderObj.parentFolder);
    folderObj = folderObj.parentFolder;
  }

  return folderPath.reverse();
}

export default {
  components: {
    documents,
    userInputs,
  },
  data() {
    return {
      viewMode: 'iconView',
      docs: {},
      history: null,
      folderPath: [],
      changeDocViewOnResize: function() {
        if (window.innerWidth < 768) {
          this.viewMode = 'listView';
        }
      }
    };
  },
  watch: {
  },
  methods: {
    changeFolder(folderObj) {
      this.folderPath = constructFolderPath(folderObj);
      this.updateUrlPath(folderObj);
      this.docs = folderObj;
      try {
        this.history.update(this.docs);
      } catch (error) {
        if (error.message === 'Attempting to update the same folder') {
          return;
        }
        throw error;
      }
    },
    changeViewMode(viewMode) {
      this.viewMode = viewMode;
    },
    updateUrlPath(folderObj) {
      if (folderObj.id === 0) {
        this.$router.push({ path: '' });
      } else {
        this.$router.push({ path: '', query: { folder: folderObj.id } });
      }
    },
    executeAction(action, data) {
      switch (action) {
        case 'createFolder': {
          this.docs.createFolder('Folder');
          break;
        }
        case 'createFile': {
          this.docs.createFile('File');
          break;
        }
        case 'history back': {
          this.docs = this.history.previous();
          this.folderPath = constructFolderPath(this.docs);
          break;
        }
        case 'history forward': {
          this.docs = this.history.next();
          this.folderPath = constructFolderPath(this.docs);
          break;
        }
        case 'download': {
          this.download(data);
          break;
        }
        case 'delete': {
          data.remove();
          break;
        }
        case 'search': {
          this.search(data);
          break;
        }
        default: break;
      }
    },
    download(doc) {
      doc.load()
      .then((docContent) => {
        const element = document.createElement('a');
        const href = 'data:text/plain;charset=utf-8,';
        const content = encodeURIComponent(docContent);
        element.setAttribute('href', href+content, docContent);
        const fileName = doc.name;
        element.setAttribute('download', fileName);

        element.style.display = 'none';
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
      });
    },
    search(searchStr) {
      if (searchStr === '') {
        this.docs = this.history.currFolder;
      } else {
        fileManager.searchAll(searchStr)
        .then((result) => { this.docs = result; })
        .catch((error) => {
          // Simple alert box for message
          // TODO: Use a less obstrusive alert message
          alert('Opps, I have problem finding your file');
          console.log(error);
        });
        // Intentionally not updating the history stack.
        // Since, there may be multiple incomplete searching,
        // updating the history stack may unnecessary populate the stack.
      }
    }
  },
  mounted() {
    fileManagerVue = this;
    const ROOT_FOLDER_ID = 0;
    this.docs = fileManager.getFolderByID(ROOT_FOLDER_ID);
    this.history = fileManager.createHistory(this.docs);

    window.addEventListener('resize', this.changeDocViewOnResize.bind(fileManagerVue));
  },
  beforeDestroy() {
    fileManagerVue = this;

    window.removeEventListener('resize', this.changeDocViewOnResize.bind(fileManagerVue));
  }
};

</script>

<style scoped lang="scss">
@import 'src/assets/styles/variables.scss';

</style>

<template>
  <div>
    <userInputs
      v-on:changeViewMode="changeViewMode"
      v-on:execute="executeAction"
      v-on:changeFolder="changeFolder"
          :folder-path="folderPath"
    ></userInputs>
    <documents
      v-on:changeFolder="changeFolder"
          :view-mode="viewMode"
          :docs="docs"
    ></documents>
  </div>
</template>

<script>
import fileManager from 'src/logic/filemanager';
import documents from './Documents';
import userInputs from './UserInputs';

function constructFolderPath(folderObj) {
  const folderPath = [];

  folderPath.push(folderObj);
  while (folderObj.parentFolder !== null) {
    folderPath.push(folderObj.parentFolder);
    folderObj = folderObj.parentFolder;
  }

  return folderPath.reverse();
}

export default {
  components: {
    documents,
    userInputs,
  },
  data() {
    return {
      viewMode: 'iconView',
      docs: {},
      history: null,
      folderPath: []
    };
  },
  watch: {
  },
  methods: {
    changeFolder(folderObj) {
      this.folderPath = constructFolderPath(folderObj);
      this.updateUrlPath(folderObj);
      this.docs = folderObj;
      try {
        this.history.update(this.docs);
      } catch (error) {
        if (error.message === 'Attempting to update the same folder') {
          return;
        }
        throw error;
      }
    },
    changeViewMode(viewMode) {
      this.viewMode = viewMode;
    },
    updateUrlPath(folderObj) {
      if (folderObj.id === 0) {
        this.$router.push({ path: '' });
      } else {
        this.$router.push({ path: '', query: { folder: folderObj.id } });
      }
    },
    executeAction(action, data) {
      switch (action) {
        case 'createFolder': {
          this.docs.createFolder('Folder');
          break;
        }
        case 'createFile': {
          this.docs.createFile('File');
          break;
        }
        case 'history back': {
          this.docs = this.history.previous();
          this.folderPath = constructFolderPath(this.docs);
          break;
        }
        case 'history forward': {
          this.docs = this.history.next();
          this.folderPath = constructFolderPath(this.docs);
          break;
        }
        case 'download': {
          this.download(data);
          break;
        }
        case 'delete': {
          data.remove();
          break;
        }
        case 'search': {
          this.search(data);
          break;
        }
        default: break;
      }
    },
    download(doc) {
      doc.load()
      .then((docContent) => {
        const element = document.createElement('a');
        const href = 'data:text/plain;charset=utf-8,';
        const content = encodeURIComponent(docContent);
        element.setAttribute('href', href+content, docContent);
        const fileName = doc.name;
        element.setAttribute('download', fileName);

        element.style.display = 'none';
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
      });
    },
    search(searchStr) {
      if (searchStr === '') {
        this.docs = this.history.currFolder;
      } else {
        fileManager.searchAll(searchStr)
        .then((result) => { this.docs = result; })
        .catch((error) => {
          // Simple alert box for message
          // TODO: Use a less obstrusive alert message
          alert('Opps, I have problem finding your file');
          console.log(error);
        });
        // Intentionally not updating the history stack.
        // Since, there may be multiple incomplete searching,
        // updating the history stack may unnecessary populate the stack.
      }
    }
  },
  mounted() {
    const ROOT_FOLDER_ID = 0;
    this.docs = fileManager.getFolderByID(ROOT_FOLDER_ID);
    this.history = fileManager.createHistory(this.docs);
  }
};

</script>

<style scoped lang="scss">
@import 'src/assets/styles/variables.scss';

</style>


add mobile friendly side drawer

add mobile friendly side drawer

<template>
  <v-app
    style="background: #90A4AE">
    <!-- left side drawer (mobile) -->
    <v-navigation-drawer
      v-if="$vuetify.breakpoint.smAndDown"
      v-model="drawer"
      app
      clipped
      temporary
      color="grey lighten-2">
      <v-list-item>
        <v-list-item-title>context/sidebar</v-list-item-title>

        <v-btn
          icon
          @click.stop="drawer = !drawer">
          <v-icon>mdi-chevron-left</v-icon>
        </v-btn>
      </v-list-item>

      <v-divider />

      <router-view
        name="aside" />
    </v-navigation-drawer>

    <!-- left side drawer (desktop)-->
    <v-navigation-drawer
      v-if="$vuetify.breakpoint.mdAndUp"
      v-model="drawer"
      app
      clipped
      :mini-variant.sync="mini"
      mini-variant-width="40"
      permanent
      color="grey lighten-2">
      <v-list-item>
        <v-btn
          v-if="mini"
          icon>
          <v-icon>mdi-chevron-right</v-icon>
        </v-btn>

        <v-list-item-title>context/sidebar</v-list-item-title>

        <v-btn
          icon
          @click.stop="mini = !mini">
          <v-icon>mdi-chevron-left</v-icon>
        </v-btn>
      </v-list-item>

      <v-divider />

      <router-view
        v-if="!mini"
        name="aside" />
    </v-navigation-drawer>

    <!-- main application menu/tool bar -->
    <v-system-bar
      app
      color="blue-grey darken-4"
      dark
      height="60">
      <v-toolbar-title class="pl-4 pr-4">
        <i>🏕</i>
        eCamp
      </v-toolbar-title>
      <v-toolbar-items>
        <v-btn
          text
          exact
          :to="{ name: 'home'}">
          Home
        </v-btn>
        <v-btn
          text
          :to="{ name: 'camps', params: { groupName: encodeURI('Pfadi Bewegung Schweiz') } }">
          Camps
        </v-btn>
      </v-toolbar-items>

      <v-spacer />

      <v-btn
        v-if="! loggedIn"
        text
        :to="{ name: 'login' }">
        Log in
      </v-btn>
      <v-btn
        v-if="loggedIn"
        text
        :to="{ name: 'logout' }">
        Log out
      </v-btn>
      <v-btn icon>
        <v-icon>
          mdi-account
        </v-icon>
      </v-btn>
    </v-system-bar>

    <!-- second menu/tool bar -->
    <v-app-bar
      app
      clipped-left
      color="white">
      <v-btn
        v-if="$vuetify.breakpoint.smAndDown"
        icon
        @click.stop="drawer = !drawer">
        <v-icon>mdi-chevron-down</v-icon>
      </v-btn>

      <v-toolbar-title class="pl-4 pr-4">
        SoLa 2019
      </v-toolbar-title>

      <v-toolbar-items>
        <v-btn text>
          Test
        </v-btn>
      </v-toolbar-items>
    </v-app-bar>

    <!-- main content -->
    <v-content>
      <v-container
        fluid>
        <router-view />
      </v-container>
    </v-content>

    <!-- footer -->
    <v-footer
      color="grey lighten-5"
      app>
      v0.0.1
    </v-footer>
  </v-app>
</template>

<script>

export default {
  data () {
    return {
      loggedIn: null,
      drawer: true,
      mini: true
    }
  },
  created () {
    this.$auth.subscribe(this.checkLoginStatus)
    this.checkLoginStatus()
  },
  methods: {
    async checkLoginStatus () {
      this.loggedIn = await this.$auth.isLoggedIn()
    }
  }
}
</script>

<style lang="scss">
  .v-navigation-drawer--temporary.v-navigation-drawer--clipped {
    z-index: 5;
    margin-top: 116px;
}
</style>

<template>
  <v-app
    style="background: #90A4AE">
    <!-- left side drawer -->
    <v-navigation-drawer
      app
      clipped
      :mini-variant.sync="mini"
      mini-variant-width="40"
      permanent
      color="grey lighten-2">
      <v-list-item>
        <v-btn
          v-if="mini"
          icon>
          <v-icon>mdi-chevron-right</v-icon>
        </v-btn>

        <v-list-item-title>context/sidebar</v-list-item-title>

        <v-btn
          icon
          @click.stop="mini = !mini">
          <v-icon>mdi-chevron-left</v-icon>
        </v-btn>
      </v-list-item>

      <v-divider />

      <router-view
        v-if="!mini"
        name="aside" />
    </v-navigation-drawer>

    <!-- main application menu/tool bar -->
    <v-system-bar
      app
      color="blue-grey darken-4"
      dark
      height="60">
      <v-toolbar-title class="pl-4 pr-4">
        <i>🏕</i>
        eCamp
      </v-toolbar-title>
      <v-toolbar-items>
        <v-btn
          text
          exact
          :to="{ name: 'home'}">
          Home
        </v-btn>
        <v-btn
          text
          :to="{ name: 'camps', params: { groupName: encodeURI('Pfadi Bewegung Schweiz') } }">
          Camps
        </v-btn>
      </v-toolbar-items>

      <v-spacer />

      <v-btn
        v-if="! loggedIn"
        text
        :to="{ name: 'login' }">
        Log in
      </v-btn>
      <v-btn
        v-if="loggedIn"
        text
        :to="{ name: 'logout' }">
        Log out
      </v-btn>
      <v-btn icon>
        <v-icon>
          mdi-account
        </v-icon>
      </v-btn>
    </v-system-bar>

    <!-- second menu/tool bar -->
    <v-app-bar
      app
      clipped-left
      color="white">
      <v-toolbar-title class="pl-4 pr-4">
        SoLa 2019
      </v-toolbar-title>

      <v-toolbar-items>
        <v-btn text>
          Test
        </v-btn>
      </v-toolbar-items>
    </v-app-bar>

    <!-- main content -->
    <v-content>
      <v-container
        fluid>
        <router-view />
      </v-container>
    </v-content>

    <!-- footer -->
    <v-footer
      color="grey lighten-5"
      app>
      v0.0.1
    </v-footer>
  </v-app>
</template>

<script>

export default {
  data () {
    return {
      loggedIn: null,
      mini: true
    }
  },
  created () {
    this.$auth.subscribe(this.checkLoginStatus)
    this.checkLoginStatus()
  },
  methods: {
    async checkLoginStatus () {
      this.loggedIn = await this.$auth.isLoggedIn()
    }
  }
}
</script>

<style lang="scss">

</style>