ZAPP-569 Add clearable property to filter input

<template>

    <q-card flat square>

        <q-toolbar>
          <q-toolbar-title style="font-weight: bolder">
            Endpoint
            {{ this.endpointId[this.selectedEndpointId] }} Clusters
          </q-toolbar-title>
        </q-toolbar>

            Show

            <q-select
              outlined
              :value="filter"
              :options="filterOptions"
              dense
              class="col-2"
              @input="changeDomainFilter($event)"
            />

        <q-space />
        <q-input
          dense
          outlined
          clearable
          class="col-4"
          placeholder="Search Clusters"
          @input="changeFilterString($event)"
          @clear="changeFilterString('')"
          :value="filterString"
        >
          <template v-slot:prepend>
            <q-icon name="search" />
          </template>
        </q-input>

      <q-list>

 0">
            <q-expansion-item
              switch-toggle-side
              :label="domainName"
              @input="setOpenDomain(domainName, $event)"
              :value="getDomainOpenState(domainName)"
            >
              <zcl-domain-cluster-view
                :domainName="domainName"
                :clusters="clusterDomains(domainName)"
              />
            </q-expansion-item>
            <q-separator />

      </q-list>
    </q-card>

</template>

<template>

    <q-card flat square>

        <q-toolbar>
          <q-toolbar-title style="font-weight: bolder">
            Endpoint
            {{ this.endpointId[this.selectedEndpointId] }} Clusters
          </q-toolbar-title>
        </q-toolbar>

            Show

            <q-select
              outlined
              :value="filter"
              :options="filterOptions"
              dense
              class="col-2"
              @input="changeDomainFilter($event)"
            />

        <q-space />
        <q-input
          dense
          outlined
          class="col-4"
          placeholder="Search Clusters"
          @input="changeFilterString($event)"
          :value="filterString"
        >
          <template v-slot:prepend>
            <q-icon name="search" />
          </template>
        </q-input>

      <q-list>

 0">
            <q-expansion-item
              switch-toggle-side
              :label="domainName"
              @input="setOpenDomain(domainName, $event)"
              :value="getDomainOpenState(domainName)"
            >
              <zcl-domain-cluster-view
                :domainName="domainName"
                :clusters="clusterDomains(domainName)"
              />
            </q-expansion-item>
            <q-separator />

      </q-list>
    </q-card>

</template>


Importing from a directory CASS-447

Importing from a directory CASS-447

<template>

                <slot name="import-server-title">

Nothing send from import.vue

                </slot>

                        <ImportTabs />

                            <template v-if="importTransition === 'upload'">

                                        {{ item.label }}

                                            <input
                                                class="input is-large"
                                                v-model="importServerUrl"
                                                type="url">

                                                    connect to CASE endpoint

                                                    connect to CaSS endpoint

                                        Looking for an example? Try out an example from the OpenSalt.net endpoint to test the workflow.

                            </template>

                                        {{ importStatus }}

 0"
                                class="column has-text-danger">

                                            {{ error }}

                                        start over

                                    Found Frameworks

                                    Select the framework you would like to import.

                                    <input
                                        class="is-checkradio is-small"
                                        type="checkbox"
                                        :checked="doc.checked"
                                        :id="'check' + doc.id"
                                        :name="'check' + doc.id"
                                        v-model="doc.checked"
                                        v-if="!doc.loading && !doc.success && !doc.error">

                                    {{ doc.name }}

                                        Cancel

                                        Import

                                        Found Directories

                                        Select a directory with frameworks you would like to import.

                                        {{ directory.getName() }}

                                    Found Frameworks

                                    Select the framework you would like to import.

                                    <input
                                        class="is-checkradio is-small"
                                        type="checkbox"
                                        :checked="doc.checked"
                                        :id="'check' + doc.id"
                                        :name="'check' + doc.id"
                                        v-model="doc.checked"
                                        v-if="!doc.loading && !doc.success && !doc.error">

                                    {{ doc.getName() }}

                                        Cancel

                                        Import

                        <slot name="import-framework" />

        <RightAside v-if="importInfoVisible">
            <template slot="right-aside-content">

                        Remote Server Import

                        If you know the URL of a IMS CASE Repository, such as OpenSalt, you can import published frameworks from that repository.

                            This import maintains the URLs of the CASE frameworks and changes both the format and schema used to store the CASE frameworks in CaSS, but does not change any of the data.

                            After entering the endpoint below, you can select which frameworks you would like to import.

                            If you wish to edit the frameworks after importing, please be sure you are signed in.

            </template>
        </RightAside>

</template>

<template>

                <slot name="import-server-title">

Nothing send from import.vue

                </slot>

                        <ImportTabs />

                            <template v-if="importTransition === 'upload'">

                                        {{ item.label }}

                                            <input
                                                class="input is-large"
                                                v-model="importServerUrl"
                                                type="url">

                                                    connect to CASE endpoint

                                                    connect to CaSS endpoint

                                        Looking for an example? Try out an example from the OpenSalt.net endpoint to test the workflow.

                            </template>

                                        {{ importStatus }}

 0"
                                class="column has-text-danger">

                                            {{ error }}

                                        start over

                                    Found Frameworks

                                    Select the framework you would like to import.

                                    <input
                                        class="is-checkradio is-small"
                                        type="checkbox"
                                        :checked="doc.checked"
                                        :id="'check' + doc.id"
                                        :name="'check' + doc.id"
                                        v-model="doc.checked"
                                        v-if="!doc.loading && !doc.success && !doc.error">

                                    {{ doc.name }}

                                        Cancel

                                        Import

                                    Found Frameworks

                                    Select the framework you would like to import.

                                    <input
                                        class="is-checkradio is-small"
                                        type="checkbox"
                                        :checked="doc.checked"
                                        :id="'check' + doc.id"
                                        :name="'check' + doc.id"
                                        v-model="doc.checked"
                                        v-if="!doc.loading && !doc.success && !doc.error">

                                    {{ doc.getName() }}

                                        Cancel

                                        Import

                        <slot name="import-framework" />

        <RightAside v-if="importInfoVisible">
            <template slot="right-aside-content">

                        Remote Server Import

                        If you know the URL of a IMS CASE Repository, such as OpenSalt, you can import published frameworks from that repository.

                            This import maintains the URLs of the CASE frameworks and changes both the format and schema used to store the CASE frameworks in CaSS, but does not change any of the data.

                            After entering the endpoint below, you can select which frameworks you would like to import.

                            If you wish to edit the frameworks after importing, please be sure you are signed in.

            </template>
        </RightAside>

</template>


[todos] Make timesheet order match the todolist

<template>

      <datepicker
        wrapper-class="datepicker"
        input-class="date-field input"
        :language="locale"
        :disabled-dates="disabledDates"
        :monday-first="true"
        format="yyyy-MM-dd"
        v-model="selectedDate"
      />

    -
    {{ timeSpentTotal }} {{ $t('timesheets.hours') }}

            {{ $t('tasks.fields.production') }}

            {{ $t('tasks.fields.task_type') }}

            {{ $t('tasks.fields.entity') }}

            {{ $t('timesheets.time_spents') }}

 0 && !isLoading">

            <production-name-cell
              :entry="productionMap[task.project_id]"
              :only-avatar="true"
            />

          <task-type-cell
            class="type datatable-row-header datatable-row-header--nobd"
            :production-id="task.project_id"
            :task-type="taskTypeMap[task.task_type_id]"
            :style="{left: colTypePosX}"
          />

            <router-link :to="entityPath(task)">
              {{ task.full_entity_name }}
            </router-link>

          <time-slider-cell
            :duration="timeSpentMap[task.id] ? timeSpentMap[task.id].duration / 60 : 0"
            class="time-spent"
            :task-id="task.id"
            @change="onSliderChange"
          />

              <page-subtitle
                :text="$t('timesheets.done_tasks')"
              />

           <production-name-cell
             :entry="productionMap[task.project_id]"
             :only-avatar="true"
           />

          <task-type-cell
            class="type datatable-row-header datatable-row-header--nobd"
            :production-id="task.project_id"
            :task-type="{
              id: task.task_type_id,
              name: task.task_type_name,
              color: task.task_type_color
            }"
            :style="{left: colTypePosX}"
          />

           <router-link :to="task.entity_path">
             {{ task.full_entity_name }}
           </router-link>

          <time-slider-cell
           :duration="timeSpentMap[task.id] ? timeSpentMap[task.id].duration / 60 : 0"
           class="time-spent"
           :task-id="task.id"
           @change="onSliderChange"
          />

    {{ tasks.length }} {{ $tc('tasks.tasks', tasks.length) }}

</template>

<template>

      <datepicker
        wrapper-class="datepicker"
        input-class="date-field input"
        :language="locale"
        :disabled-dates="disabledDates"
        :monday-first="true"
        format="yyyy-MM-dd"
        v-model="selectedDate"
      />

    -
    {{ timeSpentTotal }} {{ $t('timesheets.hours') }}

            {{ $t('tasks.fields.production') }}

            {{ $t('tasks.fields.task_type') }}

            {{ $t('tasks.fields.entity') }}

            {{ $t('timesheets.time_spents') }}

 0 && !isLoading">

            <production-name-cell
              :entry="productionMap[task.project_id]"
              :only-avatar="true"
            />

          <task-type-cell
            class="type datatable-row-header datatable-row-header--nobd"
            :production-id="task.project_id"
            :task-type="taskTypeMap[task.task_type_id]"
            :style="{left: colTypePosX}"
          />

            <router-link :to="entityPath(task)">
              {{ task.full_entity_name }}
            </router-link>

          <time-slider-cell
            :duration="timeSpentMap[task.id] ? timeSpentMap[task.id].duration / 60 : 0"
            class="time-spent"
            :task-id="task.id"
            @change="onSliderChange"
          />

              <page-subtitle
                :text="$t('timesheets.done_tasks')"
              />

           <production-name-cell
             :entry="productionMap[task.project_id]"
             :only-avatar="true"
           />

          <task-type-name
           class="type datatable-row-header datatable-row-header--nobd"
           :production-id="task.project_id"
           :entry="{
             id: task.task_type_id,
             name: task.task_type_name,
             color: task.task_type_color
           }"
           :style="{left: colTypePosX}"
          />

           <router-link :to="task.entity_path">
             {{ task.full_entity_name }}
           </router-link>

          <time-slider-cell
           :duration="timeSpentMap[task.id] ? timeSpentMap[task.id].duration / 60 : 0"
           class="time-spent"
           :task-id="task.id"
           @change="onSliderChange"
          />

    {{ tasks.length }} {{ $tc('tasks.tasks', tasks.length) }}

</template>


Fixing nested path searching issue (#32)

<template>

			{{title}}

					{{button.icon}}

					print

					description

					search

					<input type="search" id="search-input" class="form-control" :placeholder="lang['search_data']"
						:value="searchInput"
						@input="(e) => {this.searchInput = e.target.value}">

						{{column.label}}

					<slot name="thead-tr"></slot>

 {{ collect(row, column.field) }}

					<slot name="tbody-tr" :row="row"></slot>

					{{lang['rows_per_page']}}:
					<select class="browser-default" @change="onTableLength">
						<option v-for="option in perPageOptions" :value="option" :selected="option == currentPerPage">
					    {{ option === -1 ? lang['all'] : option }}
					  </option>
					</select>

				{{(currentPage - 1) * currentPerPage ? (currentPage - 1) * currentPerPage : 1}}
					-{{Math.min(processedRows.length, currentPerPage * currentPage)}}

					{{lang['out_of_pages']}}

					{{processedRows.length}}

							chevron_left

							chevron_right

</template>


2017-09-04

<template>

        皮肤测试

    <clear-float></clear-float>

      <treatmentlist></treatmentlist>

    <transition name="slide">
      <skintest @closeSkintest="closeSkintest" :showSkintest="showSkintest" v-show="showSkintest"></skintest>
    </transition>
    <transition name="slide">
      <treat-bannerpage v-show="showTreatbannerPage" @closeTreatbannerPage="closeTreatbannerPage"></treat-bannerpage>
    </transition>
    <loading title="正在加载护理数据..." v-show="isTreatLoad"></loading>

</template>


move barometer gauge options to js

move barometer gauge options to js

<template lang="jade">
paper-material.barometer
  h1 Ciśnienie
  gauge(:measure='measure' v-bind:value='pressure' unit-name='hPa')
</template>

<script>
  import gauge from './gauge';

  export default {
    components: {
      gauge,
    },

    data() {
      return {
        pressure: 960,
        measure: {
          from: 960,
          unit: 40,
        },
      };
    },

    ready() {
      this.$parent.api.basic.on('updated', api => {
        this.pressure = api.barometer.current.value;
      });
    },
  };

</script>

<style lang="stylus">
@import '~styles/section'

.barometer
  @extend .section
  margin 0 .5em

</style>

<template lang="jade">
paper-material.barometer
  h1 Ciśnienie
  gauge(:measure='{ from: 960, unit: 40 }' v-bind:value='pressure' unit-name='hPa')
</template>

<script>
  import gauge from './gauge';

  export default {
    components: {
      gauge,
    },

    data() {
      return {
        pressure: 960,
      };
    },

    ready() {
      this.$parent.api.basic.on('updated', api => {
        this.pressure = api.barometer.current.value;
      });
    },
  };

</script>

<style lang="stylus">
@import '~styles/section'

.barometer
  @extend .section
  margin 0 .5em

</style>


Wrap strings.

Wrap strings.

<template>

    <form @submit.prevent="submitSearch">
      <ui-textbox
        name="search"
        :label="$tr('search')"
        :placeholder="`${$tr('searchWithin')} ${channelName}`"
        type="search"
        :autofocus="true"
        v-model="searchInput"
        class="search-input"/>
      <ui-icon-button
        primary="true"
        color="primary"
        buttonType="submit"
        icon="search"
        :ariaLabel="$tr('submitSearch')">
      </ui-icon-button>
    </form>

{{ $tr('noSearch') }}

{{ $tr('showingResultsFor') }} "{{ searchTerm }}"

{{ $tr('noResults') }}

{{ $tr('results', {count: numResults}) }}

{{ $tr('topics') }}

            <card-list class="card-list">
              <topic-list-item
                v-for="topic in topics"
                class="card"
                :id="topic.id"
                :channelId="channelId"
                :title="topic.title"/>
            </card-list>

{{ $tr('content') }}

            <card-grid>
              <content-grid-item
                v-for="content in contents"
                class="card"
                :title="content.title"
                :thumbnail="content.thumbnail"
                :kind="content.kind"
                :progress="content.progress"
                :id="content.id"/>
            </card-grid>

</template>

<template>

    <form @submit.prevent="submitSearch">
      <ui-textbox
        name="search"
        :label="$tr('search')"
        :placeholder="`${$tr('searchWithin')} ${channelName}`"
        type="search"
        :autofocus="true"
        v-model="searchInput"
        class="search-input"/>
      <ui-icon-button
        primary="true"
        color="primary"
        buttonType="submit"
        icon="search"
        :ariaLabel="$tr('submitSearch')">
      </ui-icon-button>
    </form>

{{ $tr('noSearch') }}

{{ $tr('showingResultsFor') }} "{{ searchTerm }}"

{{ $tr('noResults') }}

{{ $tr('results', {count: numResults}) }}

Topics

            <card-list class="card-list">
              <topic-list-item
                v-for="topic in topics"
                class="card"
                :id="topic.id"
                :channelId="channelId"
                :title="topic.title"/>
            </card-list>

Content

            <card-grid>
              <content-grid-item
                v-for="content in contents"
                class="card"
                :title="content.title"
                :thumbnail="content.thumbnail"
                :kind="content.kind"
                :progress="content.progress"
                :id="content.id"/>
            </card-grid>

</template>


Clean up additional global variables.

Clean up additional global variables.

<template v-if="itemId">

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

{{ $tr("hintLabel") }}

      <transition name="expand">

{{ message }}

      </transition>

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

</template>


Serialize the hints ourselves to prevent errors from hints that replace previous hints.


检测射线事件，拖动时不触发射线

检测射线事件，拖动时不触发射线


Improved bucket view with icons and bucket meta

<template lang="pug">

div
  h3 {{ bucket.id }}
  table
    tr
      td Type:
      td {{ bucket.type }}
    tr
      td Client:
      td {{ bucket.client }}
    tr
      td Hostname:
      td {{ bucket.hostname }}
    tr
      td Created:
      td {{ bucket.created }}

  hr

  h4 Events

  div.pagination-header
    | Showing {{ events.length }} out of ? events

  div.well.well-sm(style="margin-bottom: 0;")
    b-button(v-on:click="expandList")
      span(v-if="expandList")
        | Expand list
      span(v-else)
        | Condense list

  div.scrollbar-flipped
    ul.event-list(v-bind:class="{ 'expand': isListExpanded }")
      li(v-for="event in events")
        div
          span.event
            span.field(v-bind:title="event.timestamp")
              icon(name="calendar-o")
              | {{ event.timestamp | friendlytime }}
            span.field
              icon(name="clock-o")
              | {{ event.duration | friendlyduration }}
            span(v-for="(val, key) in event.data").field
              icon(name="tags")
              // TODO: Add some kind of highlighting to key
              | {{ key }}: {{ val }}

</template>

<style scoped lang="scss">

$border-color: #ddd;

.event-list {
  list-style-type: none;
  padding: 0;
  border: 1px solid $border-color;
  border-radius: 3px;
  height: 300px;
  overflow-y: auto;
  white-space: nowrap;

  li {
    border: 0 solid $border-color;
    border-width: 0 0 1px 0;
    border-radius: 4px;
    padding: 2px;

    &:last-child {
      border-width: 0;
    }
  }

  &.expand {
    height: 100%;
  }
}

.event {
  display: inline-block;
  padding: 6px;
  clear: both;
}

.pagination-header {
  font-size: 12pt;
  color: #666;
  margin-bottom: 10px;
}

.field {
  margin: 0 5px 0 0;
  font-size: 11pt;
  padding: 3px 5px 3px 5px;
  background-color: #ddd;
  border: 1px solid #ccc;
  border-radius: 2px;

  .glyphicon {
    font-size: 10pt;
    margin-right: 5px;
  }

  &:last-child {
    margin-right: 0;
  }
}

/* Flips the outer element once, then all direct children once,
   leaving the scrollbar in the first flipped yet the content correct */
.scrollbar-flipped, .scrollbar-flipped > * {
  transform: rotateX(180deg);
  -ms-transform: rotateX(180deg); /* IE 9 */
  -webkit-transform: rotateX(180deg); /* Safari and Chrome */
}
</style>

<script>
import Resources from '../resources.js';

import 'vue-awesome/icons/tags'
import 'vue-awesome/icons/clock-o'
import 'vue-awesome/icons/calendar-o'

let $Bucket = Resources.$Bucket;
let $Event = Resources.$Event;

export default {
  name: "Bucket",
  data: () => {
    return {
      id: String,
      bucket: Object,
      events: [],
      isListExpanded: false,
    }
  },
  methods: {
    getBucketInfo: function(bucket_id) {
      $Bucket.get({"id": bucket_id}).then((response) => {
        this.bucket = response.json();
      });
    },

    getEvents: function(bucket_id) {
      $Event.get({"id": bucket_id}).then((response) => {
        this.events = response.json();
      });
    },

    expandList: function() {
      this.isListExpanded = !this.isListExpanded;
      console.log("List should be expanding: ", this.isListExpanded);
    }
  },
  mounted: function() {
    this.id = this.$route.params.id;
    this.getBucketInfo(this.id);
    this.getEvents(this.id);
  },
}
</script>

<template lang="pug">
div
  h3 Events

  div.pagination-header
    | Showing {{ events.length }} out of ? events

  div.well.well-sm(style="margin-bottom: 0;")
    b-button(v-on:click="expandList")
      span(v-if="expandList")
        | Expand list
      span(v-else)
        | Condense list

  div.scrollbar-flipped
    ul.event-list(v-bind:class="{ 'expand': isListExpanded }")
      li(v-for="event in events")
        div
          span.event
            span.field(v-bind:title="event.timestamp")
              span.glyphicon.glyphicon-time
              | {{ event.timestamp | friendlytime }}
            span.field
              span.glyphicon.glyphicon-hourglass
              | {{ event.duration | friendlyduration }}
            span(v-for="(val, key) in event.data").field
              span.glyphicon.glyphicon-tags
              // TODO: Add some kind of highlighting to key
              | {{ key }}: {{ val }}

</template>

<style scoped lang="scss">

$border-color: #ddd;

.event-list {
  list-style-type: none;
  padding: 0;
  border: 1px solid $border-color;
  border-radius: 3px;
  height: 300px;
  overflow-y: auto;
  white-space: nowrap;

  li {
    border: 0 solid $border-color;
    border-width: 0 0 1px 0;
    border-radius: 4px;
    padding: 2px;

    &:last-child {
      border-width: 0;
    }
  }

  &.expand {
    height: 100%;
  }
}

.event {
  display: inline-block;
  padding: 6px;
  clear: both;
}

.pagination-header {
  font-size: 12pt;
  color: #666;
  margin-bottom: 10px;
}

.field {
  margin: 0 5px 0 0;
  font-size: 11pt;
  padding: 3px 5px 3px 5px;
  background-color: #ddd;
  border: 1px solid #ccc;
  border-radius: 2px;

  .glyphicon {
    font-size: 10pt;
    margin-right: 5px;
  }

  &:last-child {
    margin-right: 0;
  }
}

/* Flips the outer element once, then all direct children once,
   leaving the scrollbar in the first flipped yet the content correct */
.scrollbar-flipped, .scrollbar-flipped > * {
  transform: rotateX(180deg);
  -ms-transform: rotateX(180deg); /* IE 9 */
  -webkit-transform: rotateX(180deg); /* Safari and Chrome */
}
</style>

<script>
import Resources from '../resources.js';

let $Bucket = Resources.$Bucket;
let $Event = Resources.$Event;

export default {
  name: "Bucket",
  data: () => {
    return {
      id: String,
      bucket: Object,
      events: [],
      isListExpanded: false,
    }
  },
  methods: {
    getBucketInfo: function(bucket_id) {
      $Bucket.get({"id": bucket_id}).then((response) => {
        this.bucket = response.json();
      });
    },

    getEvents: function(bucket_id) {
      $Event.get({"id": bucket_id}).then((response) => {
        this.events = response.json();
      });
    },

    expandList: function() {
      this.isListExpanded = !this.isListExpanded;
      console.log("List should be expanding: ", this.isListExpanded);
    }
  },
  mounted: function() {
    this.id = this.$route.params.id;
    this.getBucketInfo(this.id);
    this.getEvents(this.id);
  },
}
</script>


fixed vuejs dropdown placeholder;

fixed vuejs dropdown placeholder;

<template>

      <select
        :id="question.inputId"
        v-model="value"
        :class="question.cssClasses.control"
        v-bind:aria-label="question.locTitle.renderedHtml"
      >
        <option v-if="question.showOptionsCaption" value>{{question.optionsCaption}}</option>
        <option
          v-for="item in question.visibleChoices"
          :value="item.value"
          :disabled="!item.isEnabled"
        >{{item.text}}</option>
      </select>

{{isOtherSelected ? question.otherText : question.displayValue}}

    <survey-other-choice v-show="isOtherSelected" :question="question" />

</template>

<template>

      <select
        :id="question.inputId"
        v-model="question.renderedValue"
        :class="question.cssClasses.control"
        v-bind:aria-label="question.locTitle.renderedHtml"
      >
        <option v-if="question.showOptionsCaption" value>{{question.optionsCaption}}</option>
        <option
          v-for="item in question.visibleChoices"
          :value="item.value"
          :disabled="!item.isEnabled"
        >{{item.text}}</option>
      </select>

{{isOtherSelected ? question.otherText : question.displayValue}}

    <survey-other-choice v-show="isOtherSelected" :question="question"/>

</template>


modify

modify

<template>
<div id='bd'>
    <app-header></app-header>
    <transition name="router-switch">
        <router-view></router-view>
    </transition>
</div>
</template>
<script>
    import AppHeader from './components/header';
    import store from './store';
    export default {
        store,
        components: {
            AppHeader
        }
    }
</script>
<style lang=less>
    html {
        background-color: #f5f5f5;
    }
    body {
        margin: 0px;
    }
    .router-switch-enter {
        transform: translateX(100%);
    }
    .router-switch-leave-active {
        transform: translateX(-100%);
    }
    .router-switch-enter-active, .router-switch-leave-active{
        position: absolute;
        top: 87px;
        bottom: 0;
        left: 0;
        right: 0;
        transition: all .3s;
    }
</style>

<template>
<div id='bd'>
    <app-header></app-header>
    <transition name="router-switch">
        <router-view></router-view>
    </transition>
</div>
</template>
<script>
    import AppHeader from './components/header';
    import store from './store';
    export default {
        store,
        components: {
            AppHeader
        }
    }
</script>
<style lang=less>
    html {
        background-color: #f5f5f5;
    }
    body {
        margin: 0px;
    }
    .router-switch-enter {
        transform: translateX(-500px);
    }
    .router-switch-leave-active {
        transform: translateX(500px);
    }
    .router-switch-enter-active, .router-switch-leave-active{
        position: absolute;
        top: 87px;
        bottom: 0;
        left: 0;
        right: 0;
        transition: all .5s;
    }
</style>


Modify setup instructions

Modify setup instructions

<template>

      Vue.js version 2.4.2+ is required.

1

Install

2

Import into your project

3

Reference in your component templates

1

Load external resources

2

Use the global VCalendar object

3

Reference in your component templates

</template>


Update HTML5 viewer to receive progress update events from hashi.

<template>

  <CoreFullscreen
    ref="html5Renderer"
    class="html5-renderer"
    @changeFullscreen="isInFullscreen = $event"
  >
    <UiIconButton
      class="btn"
      :style="{ fill: $themeTokens.textInverted }"
      :ariaLabel="isInFullscreen ? $tr('exitFullscreen') : $tr('enterFullscreen')"
      color="primary"
      size="large"
      @click="$refs.html5Renderer.toggleFullscreen()"
    >
      <mat-svg v-if="isInFullscreen" name="fullscreen_exit" category="navigation" />
      <mat-svg v-else name="fullscreen" category="navigation" />
    </UiIconButton>
    <div class="iframe-container">
      <iframe
        ref="iframe"
        class="iframe"
        :style="{ backgroundColor: $themePalette.grey.v_100 }"
        :sandbox="sandbox"
        frameBorder="0"
        :name="name"
        :src="rooturl"
      >
      </iframe>
    </div>
  </CoreFullscreen>

</template>

<script>

  import { now } from 'kolibri.utils.serverClock';
  import UiIconButton from 'kolibri.coreVue.components.UiIconButton';
  import CoreFullscreen from 'kolibri.coreVue.components.CoreFullscreen';
  import Hashi from 'hashi';
  import { nameSpace } from 'hashi/src/hashiBase';
  import plugin_data from 'plugin_data';

  export default {
    name: 'Html5AppRendererIndex',
    components: {
      UiIconButton,
      CoreFullscreen,
    },
    data() {
      return {
        isInFullscreen: false,
      };
    },
    computed: {
      name() {
        return nameSpace;
      },
      rooturl() {
        return this.defaultFile.storage_url;
      },
      sandbox() {
        return plugin_data.html5_sandbox_tokens;
      },
    },
    mounted() {
      this.hashi = new Hashi({ iframe: this.$refs.iframe, now });
      this.hashi.onStateUpdate(data => {
        this.$emit('updateContentState', data);
      });
      this.hashi.onProgressUpdate(progress => {
        // If hashi does some progress updating, stop our automatic
        // progress updating.
        if (this.timeout) {
          clearTimeout(this.timeout);
          this.timeout = null;
        }
        this.$emit('updateProgress', progress);
      });
      this.hashi.initialize((this.extraFields && this.extraFields.contentState) || {}, {
        userId: this.userId,
        userFullName: this.userFullName,
        progress: this.progress,
        complete: this.progress >= 1,
        language: this.lang.id,
        timeSpent: this.timeSpent,
      });
      this.$emit('startTracking');
      this.startTime = now();
      this.pollProgress();
    },
    beforeDestroy() {
      if (this.timeout) {
        clearTimeout(this.timeout);
      }
      this.$emit('stopTracking');
    },
    methods: {
      recordProgress() {
        const totalTime = now() - this.startTime;
        this.$emit('updateProgress', Math.max(0, totalTime / 3000000));
        this.pollProgress();
      },
      pollProgress() {
        this.timeout = setTimeout(() => {
          this.recordProgress();
        }, 15000);
      },
    },
    $trs: {
      exitFullscreen: 'Exit fullscreen',
      enterFullscreen: 'Enter fullscreen',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .btn {
    position: absolute;
    top: 8px;
    right: 21px;
    z-index: 1;
  }

  .html5-renderer {
    position: relative;
    height: 500px;
    text-align: center;
  }

  .iframe {
    width: 100%;
    height: 100%;
  }

  .iframe-container {
    @extend %momentum-scroll;

    position: absolute;
    top: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
  }

</style>

<template>

  <CoreFullscreen
    ref="html5Renderer"
    class="html5-renderer"
    @changeFullscreen="isInFullscreen = $event"
  >
    <UiIconButton
      class="btn"
      :style="{ fill: $themeTokens.textInverted }"
      :ariaLabel="isInFullscreen ? $tr('exitFullscreen') : $tr('enterFullscreen')"
      color="primary"
      size="large"
      @click="$refs.html5Renderer.toggleFullscreen()"
    >
      <mat-svg v-if="isInFullscreen" name="fullscreen_exit" category="navigation" />
      <mat-svg v-else name="fullscreen" category="navigation" />
    </UiIconButton>
    <div class="iframe-container">
      <iframe
        ref="iframe"
        class="iframe"
        :style="{ backgroundColor: $themePalette.grey.v_100 }"
        :sandbox="sandbox"
        frameBorder="0"
        :name="name"
        :src="rooturl"
      >
      </iframe>
    </div>
  </CoreFullscreen>

</template>

<script>

  import { now } from 'kolibri.utils.serverClock';
  import UiIconButton from 'kolibri.coreVue.components.UiIconButton';
  import CoreFullscreen from 'kolibri.coreVue.components.CoreFullscreen';
  import Hashi from 'hashi';
  import { nameSpace } from 'hashi/src/hashiBase';
  import plugin_data from 'plugin_data';

  export default {
    name: 'Html5AppRendererIndex',
    components: {
      UiIconButton,
      CoreFullscreen,
    },
    data() {
      return {
        isInFullscreen: false,
        useScormScoreAsProgress: true,
      };
    },
    computed: {
      name() {
        return nameSpace;
      },
      rooturl() {
        return this.defaultFile.storage_url;
      },
      sandbox() {
        return plugin_data.html5_sandbox_tokens;
      },
    },
    mounted() {
      this.hashi = new Hashi({ iframe: this.$refs.iframe, now });
      this.hashi.onStateUpdate(data => {
        this.$emit('updateContentState', data);
        if (data.SCORM && data.SCORM.version) {
          if (this.timeout) {
            clearTimeout(this.timeout);
            this.timeout = null;
          }
          if (data.SCORM.cmi && data.SCORM.cmi.core) {
            const core = data.SCORM.cmi.core;
            if (this.useScormScoreAsProgress) {
              let score = core.score;
              if (score) {
                // If min and max are not set, raw will be a value in the range 0-100. Source:
                // https://support.scorm.com/hc/en-us/articles/206166466-cmi-score-raw-whole-numbers-
                let rawPercent = 1;
                let rawScore = score.raw;

                // TODO: Needs testing with ranges outside 0-100, all content so far specifying
                // min and max have these values, so behavior is the same as if they were not set.
                if (score.min && score.max) {
                  // rawPercent = percent per range increment
                  // preMin = highest value before min, what to subtract from the range
                  // rawScore = value from 1 to (max - preMin), assumes min and max are
                  // positive numbers
                  // this way we can calculate progress percent as value * incrementPercent
                  const preMin = Math.min(0, score.min - 1);
                  rawPercent = (score.max - preMin) / 100;
                  rawScore = rawScore - preMin;
                }

                // Convert the raw value to a percentage increment.
                let percent = (rawScore * rawPercent) / 100;
                this.$emit('updateProgress', Math.min(1, Math.max(0, percent)));
              }
            }
          }
        }
      });
      this.hashi.initialize((this.extraFields && this.extraFields.contentState) || {}, {
        userId: this.userId,
        userFullName: this.userFullName,
        progress: this.progress,
        complete: this.progress >= 1,
        language: this.lang.id,
        timeSpent: this.timeSpent,
      });
      this.$emit('startTracking');
      this.startTime = now();
      this.pollProgress();
    },
    beforeDestroy() {
      if (this.timeout) {
        clearTimeout(this.timeout);
      }
      this.$emit('stopTracking');
    },
    methods: {
      recordProgress() {
        const totalTime = now() - this.startTime;
        this.$emit('updateProgress', Math.max(0, totalTime / 300000));
        this.pollProgress();
      },
      pollProgress() {
        this.timeout = setTimeout(() => {
          this.recordProgress();
        }, 15000);
      },
    },
    $trs: {
      exitFullscreen: 'Exit fullscreen',
      enterFullscreen: 'Enter fullscreen',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .btn {
    position: absolute;
    top: 8px;
    right: 21px;
    z-index: 1;
  }

  .html5-renderer {
    position: relative;
    height: 500px;
    text-align: center;
  }

  .iframe {
    width: 100%;
    height: 100%;
  }

  .iframe-container {
    @extend %momentum-scroll;

    position: absolute;
    top: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
  }

</style>


Add space between sections on a class page

so it's consistent with spaces between sections
on the home page

<template>

      <KLabeledIcon icon="classes" :label="classroomName" />

    <AssignedLessonsCards :items="lessons" />
    <AssignedQuizzesCards :items="exams" :style="{ marginTop: '44px' }" />

</template>

<template>

      <KLabeledIcon icon="classes" :label="classroomName" />

    <AssignedLessonsCards :items="lessons" />
    <AssignedQuizzesCards :items="exams" />

</template>


Make body the scolling element

Make body the scolling element

<template>

    <ScrollingHeader
      :scrollPosition="scrollPosition"
      :alwaysVisible="fixedAppBar"
      :mainWrapperScrollHeight="mainWrapperScrollHeight"
      :isHidden.sync="headerIsHidden"
    >
      <ImmersiveToolbar
        v-if="immersivePage && !fullScreen"
        :appBarTitle="toolbarTitle || appBarTitle"
        :icon="immersivePageIcon"
        :route="immersivePageRoute"
        :primary="immersivePagePrimary"
        :height="headerHeight"
        @nav-icon-click="$emit('navIconClick')"
      />
      <AppBar
        v-else-if="!immersivePage && !fullScreen"
        ref="appBar"
        class="app-bar"
        :title="toolbarTitle || appBarTitle"
        :height="headerHeight"
        :navShown="navShown"
        @toggleSideNav="navShown = !navShown"
        @showLanguageModal="languageModalShown = true"
      >
        <template v-slot:totalPointsMenuItem>
          <slot name="totalPointsMenuItem"></slot>
        </template>
        <template v-slot:app-bar-actions>

            <slot name="app-bar-actions"></slot>

        </template>
        <template v-if="showSubNav" v-slot:sub-nav>
          <slot name="sub-nav"></slot>
        </template>
      </AppBar>
      <KLinearLoader
        v-if="loading && !fullScreen"
        class="loader"
        :style="{top: `${appbarHeight}px`}"
        type="indeterminate"
        :delay="false"
      />
    </ScrollingHeader>

    <SideNav
      :navShown="navShown"
      :headerHeight="headerHeight"
      :width="navWidth"
      @toggleSideNav="navShown=!navShown"
    />

      <CoreBanner v-if="demoBannerVisible">
        <template slot-scope="props">
          <component :is="coreBannerComponent" :bannerClosed="props.bannerClosed" />
        </template>
      </CoreBanner>

{{ contentComponentName }}

{{ routePath }}

      <KPageContainer v-if="notAuthorized">
        <AuthMessage
          :authorizedRole="authorizedRole"
          :header="authorizationErrorHeader"
          :details="authorizationErrorDetails"
        />
      </KPageContainer>
      <KPageContainer v-else-if="error">
        <AppError />
      </KPageContainer>

        <slot></slot>

    <GlobalSnackbar />
    <UpdateNotification
      v-if="!loading && showNotification && !busy"
      :id="mostRecentNotification.id"
      :title="mostRecentNotification.title"
      :msg="mostRecentNotification.msg"
      :linkText="mostRecentNotification.linkText"
      :linkUrl="mostRecentNotification.linkUrl"
      @submit="dismissUpdateModal"
    />
    <LanguageSwitcherModal
      v-if="languageModalShown"
      :style="{ color: $themeTokens.text }"
      @cancel="languageModalShown = false"
    />

</template>


Remove dead method

Remove dead method

Signed-off-by: Christoph Wurst <7abde52c298496fa169fb750f91f213a282142af@winzerhof-wurst.at>

<template>
	<router-link class="app-content-list-item" :to="{
			name: 'message',
			params: {
							accountId: this.$route.params.accountId,
							folderId: this.$route.params.folderId,
							messageId: this.data.id
			},
			exact: true}">

			<Avatar :label="data.from"/>

			{{data.from}}

			{{data.subject}}

			<Moment timestamp="1536048354000"/>

	</router-link>
</template>


fix(breadcrumb): fixed console error where element was not available in resize event

affects: @rei/cdr-breadcrumb

added check to prevent failed attempt to retrieve offsetWidth of null

ISSUES CLOSED: PLIB-3659

<template>
  <!-- disable lint errors on line length in template -->
  <!-- eslint-disable max-len -->
  <!-- eslint-disable vue/require-v-for-key -->

  <nav
    ref="container"
    :class="modifierClass"
    aria-label="Breadcrumb">
    <ol
      ref="cdrBreadcrumbList"
      :class="$style['cdr-breadcrumb__list']">
      <li
        v-if="truncate"
        @click="shouldTruncate = false"
        :class="$style['cdr-breadcrumb__item']">
        <cdr-button
          :icon-only="true"
          aria-expanded="false"
          aria-label="ellipsis"
          :class="[$style['cdr-breadcrumb__link'], $style['cdr-breadcrumb__ellipses']]">
          <template name="icon">
            <svg
              :class="$style['cdr-breadcrumb__ellipses-icon']"
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24">
              <title>
                ellipsis
              </title>
              <path d="M17.5 22a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM12 22a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm-5.5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
            </svg>
          </template>
        </cdr-button>
        <span
          :class="$style['cdr-breadcrumb__delimiter']"
          aria-hidden="true">
          /
        </span>
      </li>
      <li
        :class="$style['cdr-breadcrumb__item']"
        v-for="(breadcrumb, index) in items"
        v-show="!truncate || (index >= items.length - 2)"
      >
        <a
          :class="$style['cdr-breadcrumb__link']"
          :href="breadcrumb.item.url">
          {{ breadcrumb.item.name }}
        </a>
        <span
          :class="$style['cdr-breadcrumb__delimiter']"
          aria-hidden="true"
          v-if="index < items.length - 1">
          /
        </span>
      </li>
    </ol>
  </nav>
</template>

<script>
/**
 * Cedar 2 component for breadcrumb
 *
 * @version 0.0.1
 * @author [REI Software Engineering](https://rei.github.io/rei-cedar/)
 */

import modifier from 'mixinsdir/modifier';
import breakpoints from 'mixinsdir/breakpoints';
// import CdrIcon from 'componentsdir/icon/CdrIcon';
import CdrButton from 'componentsdir/button/CdrButton';
import debounce from 'lodash/debounce';

export default {
  name: 'CdrBreadcrumb',
  components: {
    CdrButton,
    // CdrIcon,
  },
  mixins: [modifier, breakpoints],
  props: {
    /**
     * Required. List of source breadcrumb property objects
     *
     * {
     *   displayText: Breadcrumb Display Text
     *   url: location breadcrumb should navigate on click
     * }
     */
    items: {
      type: Array,
      default: () => [],
      validator: (value) => {
        if (value.length && value.length > 0) {
          for (let i = 0; i < value.length; i += 1) {
            if (!(typeof value[i].item === 'object')) {
              console.error('Breadcrumb items array missing item key at index ', i); // eslint-disable-line no-console
              return false;
            }
            if (!Object.hasOwnProperty.call(value[i].item, 'name')) {
              console.error('Breadcrumb items array is missing item.name value at index ', i); // eslint-disable-line no-console
              return false;
            }
          }
        }
        return true;
      },
    },
    /**
     * Flag to track container width threshold exceeded
     */
    truncationEnabled: {
      type: Boolean,
      default: true,
    },
    /**
     * Value for max breadcrumb width to container width
     * ratio before truncation is performed
     */
    truncationThreshold: {
      type: Number,
      default: 0.8,
    },
    /**
     * Value for max breadcrumb width to container width
     * ratio before truncation is performed on XS breakpoint
     */
    truncationXSThreshold: {
      type: Number,
      default: 1,
    },
  },
  data() {
    return {
      thresholdExceeded: false,
      shouldTruncate: this.truncationEnabled,
      breadcrumbWidth: 0,
    };
  },
  computed: {
    baseClass() {
      return 'cdr-breadcrumb';
    },
    truncate() {
      return this.shouldTruncate && this.thresholdExceeded;
    },
  },
  mounted() {
    this.breadcrumbWidth = this.getBreadcrumbWidth();
    this.thresholdExceeded = this.calculateTruncation();
    window.addEventListener('resize', debounce(() => {
      this.thresholdExceeded = this.calculateTruncation();
    }, 250));
  },
  methods: {
    getBreadcrumbWidth() {
      const breadcrumbsElements = Array.from(this.$refs.cdrBreadcrumbList.children);
      let totalWidth = 0;
      breadcrumbsElements.forEach((element) => {
        totalWidth += element.offsetWidth || 0;
      });
      return totalWidth;
    },
    calculateTruncation() {
      if (!this.$refs.container) return false;
      const containerWidth = this.$refs.container.offsetWidth || 0;
      const ratio = this.breadcrumbWidth / containerWidth || 0;
      if (this.isXS()) {
        return (ratio > this.truncationXSThreshold);
      }
      return (ratio > this.truncationThreshold);
    },
  },
};
</script>

<style module>
  @import '../../css/settings/_index.pcss';
  @import './styles/CdrBreadcrumb.pcss';
</style>

<template>
  <!-- disable lint errors on line length in template -->
  <!-- eslint-disable max-len -->
  <!-- eslint-disable vue/require-v-for-key -->

  <nav
    ref="container"
    :class="modifierClass"
    aria-label="Breadcrumb">
    <ol
      ref="cdrBreadcrumbList"
      :class="$style['cdr-breadcrumb__list']">
      <li
        v-if="truncate"
        @click="shouldTruncate = false"
        :class="$style['cdr-breadcrumb__item']">
        <cdr-button
          :icon-only="true"
          aria-expanded="false"
          aria-label="ellipsis"
          :class="[$style['cdr-breadcrumb__link'], $style['cdr-breadcrumb__ellipses']]">
          <!-- <cdr-icon :class="$style['cdr-breadcrumb__ellipses-icon']">
            <path d=""/>
          </cdr-icon> -->
          <template name="icon">
            <svg
              :class="$style['cdr-breadcrumb__ellipses-icon']"
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24">
              <title>
                ellipsis
              </title>
              <path d="M17.5 22a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM12 22a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm-5.5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
            </svg>
          </template>
        </cdr-button>
        <span
          :class="$style['cdr-breadcrumb__delimiter']"
          aria-hidden="true">
          /
        </span>
      </li>
      <li
        :class="$style['cdr-breadcrumb__item']"
        v-for="(breadcrumb, index) in items"
        v-show="!truncate || (index >= items.length - 2)"
      >
        <a
          :class="$style['cdr-breadcrumb__link']"
          :href="breadcrumb.item.url">
          {{ breadcrumb.item.name }}
        </a>
        <span
          :class="$style['cdr-breadcrumb__delimiter']"
          aria-hidden="true"
          v-if="index < items.length - 1">
          /
        </span>
      </li>
    </ol>
  </nav>
</template>

<script>
/**
 * Cedar 2 component for breadcrumb
 *
 * @version 0.0.1
 * @author [REI Software Engineering](https://rei.github.io/rei-cedar/)
 */

import modifier from 'mixinsdir/modifier';
import breakpoints from 'mixinsdir/breakpoints';
// import CdrIcon from 'componentsdir/icon/CdrIcon';
import CdrButton from 'componentsdir/button/CdrButton';
import debounce from 'lodash/debounce';

export default {
  name: 'CdrBreadcrumb',
  components: {
    CdrButton,
    // CdrIcon,
  },
  mixins: [modifier, breakpoints],
  props: {
    /**
     * Required. List of source breadcrumb property objects
     *
     * {
     *   displayText: Breadcrumb Display Text
     *   url: location breadcrumb should navigate on click
     * }
     */
    items: {
      type: Array,
      default: () => [],
      validator: (value) => {
        if (value.length && value.length > 0) {
          for (let i = 0; i < value.length; i += 1) {
            if (!(typeof value[i].item === 'object')) {
              console.error('Breadcrumb items array missing item key at index ', i); // eslint-disable-line no-console
              return false;
            }
            if (!Object.hasOwnProperty.call(value[i].item, 'name')) {
              console.error('Breadcrumb items array is missing item.name value at index ', i); // eslint-disable-line no-console
              return false;
            }
          }
        }
        return true;
      },
    },
    /**
     * Flag to track container width threshold exceeded
     */
    truncationEnabled: {
      type: Boolean,
      default: true,
    },
    /**
     * Value for max breadcrumb width to container width
     * ratio before truncation is performed
     */
    truncationThreshold: {
      type: Number,
      default: 0.8,
    },
    /**
     * Value for max breadcrumb width to container width
     * ratio before truncation is performed on XS breakpoint
     */
    truncationXSThreshold: {
      type: Number,
      default: 1,
    },
  },
  data() {
    return {
      thresholdExceeded: false,
      shouldTruncate: this.truncationEnabled,
      breadcrumbWidth: 0,
    };
  },
  computed: {
    baseClass() {
      return 'cdr-breadcrumb';
    },
    truncate() {
      return this.shouldTruncate && this.thresholdExceeded;
    },
  },
  mounted() {
    this.breadcrumbWidth = this.getBreadcrumbWidth();
    this.thresholdExceeded = this.calculateTruncation();
    window.addEventListener('resize', debounce(() => {
      this.thresholdExceeded = this.calculateTruncation();
    }, 250));
  },
  methods: {
    getBreadcrumbWidth() {
      const breadcrumbsElements = Array.from(this.$refs.cdrBreadcrumbList.children);
      let totalWidth = 0;
      breadcrumbsElements.forEach((element) => {
        totalWidth += element.offsetWidth || 0;
      });
      return totalWidth;
    },
    calculateTruncation() {
      const containerWidth = this.$refs.container.offsetWidth || 0;
      const ratio = this.breadcrumbWidth / containerWidth || 0;
      if (this.isXS()) {
        return (ratio > this.truncationXSThreshold);
      }
      return (ratio > this.truncationThreshold);
    },
  },
};
</script>

<style module>
  @import '../../css/settings/_index.pcss';
  @import './styles/CdrBreadcrumb.pcss';
</style>


Removed unused stuff

Removed unused stuff

<template>

						<Sidebar/>

						<Grid v-if="files.length"
							:files="files"/>

</template>


minor css fixes for color picker

minor css fixes for color picker

<template>

    <button class="color-picker-btn" :disabled="disabled" @click="toggle">
      <div class="color-picker-color" :style="{ background: `${value}` }" />
    </button>

    <chrome-picker
      v-if="enabled"
      :value="value"
      class="color-picker"
      @input="debouncedInput"
    />

</template>


Update Announcement-sweetalert.vue

Update Announcement-sweetalert.vue

<template></template>

<script>
    export default {

        data: function () {
            return {
                announcement: {}
            };
        },

        created() {
            this.listen();
        },

        methods: {
            listen() {
                window.Echo
                .channel('public-announcement-channel')
                .listen('NewAnnouncement', (e) => {
                    this.announcement = e.data;

                    swal({
                        type: this.announcement.type,
                        title: this.announcement.message,
                        text: this.announcement.title,
                        allowOutsideClick: true,
                        showConfirmButton: true,
                        confirmButtonText: "Got it!",
                        timer: this.announcement.ttl
                    });
                });
            }
        },
    }
</script>

<template></template>

<script>
    export default {

        data: function () {
            return {
                announcement: {}
            };
        },

        ready() {
            this.listen();
        },

        methods: {
            listen() {
                window.Echo
                .channel('public-announcement-channel')
                .listen('NewAnnouncement', (e) => {
                    this.announcement = e.data;

                    swal({
                        type: this.announcement.type,
                        title: this.announcement.message,
                        text: this.announcement.title,
                        allowOutsideClick: true,
                        showConfirmButton: true,
                        confirmButtonText: "Got it!",
                        timer: this.announcement.ttl
                    });
                });
            }
        },
    }
</script>


do not exit Endpoint 'Create' dialog unless all fields are validated

BUG: ZAPP-587

<template>

    <q-card>
      <q-card-section>

          {{ this.endpointReference ? 'Edit Endpoint' : 'Create New Endpoint' }}

        <q-form>
          <q-input
            label="Endpoint"
            type="number"
            v-model="shownEndpoint.endpointIdentifier"
            ref="endpoint"
            filled
            class="col"
            :rules="[reqPosInt]"
          />
          <q-input
            label="Profile ID"
            type="number"
            v-model="zclProfileIdString"
            ref="profileId"
            outlined
            filled
            class="col"
            disable
            :rules="[reqValue]"
          />
          <q-select
            label="Device"
            ref="device"
            outlined
            filled
            class="col"
            use-input
            hide-selected
            fill-input
            :options="deviceTypeOptions"
            v-model="shownEndpoint.deviceTypeRef"
            :rules="[reqValue]"
            :option-label="
              (item) =>
                item == null
                  ? ''
                  : zclDeviceTypes[item].description +
                    ' (' +
                    asHex(zclDeviceTypes[item].code, 4) +
                    ')'
            "
            @filter="filterDeviceTypes"
          >
          </q-select>

            <q-input
              label="Network"
              type="number"
              v-model="shownEndpoint.networkIdentifier"
              ref="network"
              outlined
              filled
              stack-label
              :rules="[reqPosInt]"
            />

            <q-input
              label="Version"
              type="number"
              v-model="shownEndpoint.deviceVersion"
              ref="version"
              outlined
              filled
              stack-label
              :rules="[reqPosInt]"
            />

        </q-form>
      </q-card-section>
      <q-card-actions>
        <q-btn label="Cancel" v-close-popup class="col" />
        <q-btn
          :label="endpointReference ? 'Save' : 'Create'"
          color="primary"
          class="col"
          v-close-popup="saveOrCreateCloseFlag"
          @click="saveOrCreateHandler()"
        />
      </q-card-actions>
    </q-card>

</template>

<template>

    <q-card>
      <q-card-section>

          {{ this.endpointReference ? 'Edit Endpoint' : 'Create New Endpoint' }}

        <q-form>
          <q-input
            v-model="shownEndpoint.endpointIdentifier"
            label="Endpoint"
            filled
            type="number"
            class="col"
            :rules="[
              (val) => val.toString().length > 0 || '* Required',
              (val) => val >= 0 || '* Positive integer required',
            ]"
          />
          <q-input
            outlined
            filled
            type="number"
            v-model="zclProfileIdString"
            label="Profile ID"
            class="col"
            disable
            :rules="[
              (val) => val.toString().length > 0 || '* Required',
              (val) => val >= 0 || '* Positive integer required',
            ]"
          />
          <q-select
            outlined
            filled
            class="col"
            label="Device"
            use-input
            hide-selected
            fill-input
            :options="deviceTypeOptions"
            v-model="shownEndpoint.deviceTypeRef"
            :rules="[
              (val) => val.toString().length > 0 || '* Required',
            ]"
            :option-label="
              (item) =>
                item == null
                  ? ''
                  : zclDeviceTypes[item].description +
                    ' (' +
                    asHex(zclDeviceTypes[item].code, 4) +
                    ')'
            "
            @filter="filterDeviceTypes"
          >
          </q-select>

            <q-input
              v-model="shownEndpoint.networkIdentifier"
              outlined
              label="Network"
              type="number"
              filled
              stack-label
              :rules="[
                (val) => val.toString().length > 0 || '* Required',
                (val) => val >= 0 || '* Positive integer required',
              ]"
            />

            <q-input
              v-model="shownEndpoint.deviceVersion"
              outlined
              filled
              type="number"
              label="Version"
              stack-label
              :rules="[
                (val) => val.toString().length > 0 || '* Required',
                (val) => val >= 0 || '* Positive integer required',
              ]"
            />

        </q-form>
      </q-card-section>
      <q-card-actions>
        <q-btn label="Cancel" v-close-popup class="col" />
        <q-btn
          :label="endpointReference ? 'Save' : 'Create'"
          color="primary"
          v-close-popup
          class="col"
          @click="
            endpointReference
              ? editEpt(shownEndpoint, endpointReference)
              : newEpt(shownEndpoint)
          "
        />
      </q-card-actions>
    </q-card>

</template>


Timeline: add filters for dom loading, complete, interactive, ftp

<template>

      JavaScript bundle size
      <input v-model='bundleSize' id='input_jsbundlesize'>KB
      {{computeGZippedSize}}KB gzipped (estimate)

      Network
        <select v-model='networkSelected' @change='changeNetwork'>
          <option v-for='option in network' v-bind:value='option.download'>
            {{ option.title }}
          </option>
        </select>
      <input v-model='downloadSpeed' id='input_downloadspeed'>Kbps
      {{computeDownloadTime}}ms for {{computeGZippedSize}}KB gz

      Time-To-Interactive
      <input id='tti' v-model='timeToInteractive'>ms

      <input type='file' id='selectFile' v-on:change='fileSelected'/>

{{item.name}}

<template v-if="traceStats.get('JS Frame') === undefined">

            {{computeSum(item)}}ms

            Total Scripting

            {{computeValue('parse', item)}}ms

            Parse

            {{computeValue('eval',item)}}ms

            Evaluate

            {{computeTTIRemainder(item)}}ms

            TTI Budget remaining

         <progress :max='timeToInteractive' :value='computeTimeSum(item)'>
        </progress>
</template>

<template v-else-if="traceStats.get('JS Frame') > 0">

            {{estimateDeviceTotalScriptingTime(item)}}ms

            Total Scripting

            {{estimateDeviceStatsFromTrace(item, 'JS Frame')}}ms

            JS Frame

            {{estimateDeviceStatsFromTrace(item, 'Compile Script')}}ms

            Parse

            {{estimateDeviceStatsFromTrace(item, 'Evaluate Script')}}ms

            Evaluate

            {{estimateDeviceStatsFromTrace(item, 'Major GC')}}ms

            Major GC

            {{estimateDeviceStatsFromTrace(item, 'Minor GC')}}ms

            Minor GC

            {{estimateDeviceStatsFromTrace(item, 'Run Microtasks')}}ms

            Microtasks

            {{estimateDeviceTotalTTIRemaining(item)}}ms

            TTI Budget Remaining

        <progress :max='timeToInteractive' :value='estimateDeviceTimeSum(item)'>
        </progress>
</template>

</template>