custom field geometry column

custom field geometry column

<template>

    <template v-if="!isDatasetLoaded">
      <SkeletonSpinner
        height-square="300px"
        squares-rows="2"
        squares="2"
        lines="5"
      />
    </template>
    <template v-else>

            {{ titleDataset }}

      <DatasetNav
        :active-dataset-tab="activeDatasetTab"
        :tabs="currentTabs"
      />

      <SummaryTab
        v-if="activeDatasetTab === 0 && attributes"
        :dataset-id="datasetId"
        :is-viz-modified="isVizModified"
        :is-viz-item-modified="isVizItemModified"
        :is-viz-saved="isVizSaved"
        :is-private-viz-loading="isPrivateVizLoading"
        :is-public-viz-loading="isPublicVizLoading"
        :public-visualizations="publicVisualizations"
        :private-visualizations="privateVisualizations"
        :enabled-viz-saved-button="enabledVizSavedButton"
        :current-viz-tab="currentVizTab"
        :enabled-fork-viz-button="enabledForkVizButton"
        :viz-input-focus="vizInputFocus"
        :viz-id="vizID"
        :user-save-viz="userSaveViz"
        :show-private-public-icon-viz="showPrivatePublicIconViz"
        :show-private="showPrivate"
        :show-private-viz="showPrivateViz"
        :show-label-edit="showLabelEdit"
        :reset-private="resetPrivate"
        :private-queries="privateQueries"
        :public-queries="publicQueries"
        :array-formats="arrayFormats"
        :object-columns="objectColumns"
        :resources-list="resourcesList"
        :dataset-attributes="attributes"
        :is-user-logged="isUserLogged"
        :items="items"
        :config-map="configMap"
      />

      <DataTab
        v-else-if="activeDatasetTab === 1"
        :private-queries="privateQueries"
        :public-queries="publicQueries"
        :recent-queries="recentQueriesFiltered"
        :object-columns="objectColumns"
        :config-map="configMap"
        :array-formats="arrayFormats"
        :items="items"
        :is-query-saved="isQuerySaved"
        :is-viz-saved="isVizSaved"
        :is-query-saving-prompt-visible="isQuerySavingPromptVisible"
        :is-query-running="isQueryRunning"
        :is-query-modified="isQueryModified"
        :is-viz-modified="isVizModified"
        :is-viz-saving-prompt-visible="isVizSavingPromptVisible"
        :is-fork-prompt-visible="isForkPromptVisible"
        :query-stored="currentQuery"
        :query-name="queryName"
        :query-duration="queryDuration"
        :query-error="queryError"
        :enabled-query-saved-button="enabledQuerySavedButton"
        :enabled-viz-saved-button="enabledVizSavedButton"
        :enabled-fork-button="enabledForkButton"
        :enabled-revert-button="enabledRevertButton"
        :show-revert-query="showRevertQuery"
        :show-private="showPrivate"
        :table-name="tableName"
        :is-user-logged="isUserLogged"
        :query-input-focus="queryInputFocus"
        :viz-input-focus="vizInputFocus"
        :viz-name="vizName"
        :viz-id="vizID"
        :reset-private="resetPrivate"
        :show-private-public-icon="showPrivatePublicIcon"
        :show-private-public-icon-viz="showPrivatePublicIconViz"
      />

      <QueriesTab
        v-else-if="activeDatasetTab === 2"
        :private-queries="privateQueries"
        :public-queries="publicQueries"
        :is-user-logged="isUserLogged"
      />

      <VisualizationsTab
        v-else-if="activeDatasetTab === 3"
        :dataset-id="datasetId"
        :is-user-logged="isUserLogged"
        :is-viz-saving-prompt-visible="isVizSavingPromptVisible"
        :is-viz-modified="isVizModified"
        :is-viz-item-modified="isVizItemModified"
        :is-viz-saved="isVizSaved"
        :is-private-viz-loading="isPrivateVizLoading"
        :is-public-viz-loading="isPublicVizLoading"
        :public-visualizations="publicVisualizations"
        :private-visualizations="privateVisualizations"
        :private-queries="privateQueries"
        :public-queries="publicQueries"
        :enabled-viz-saved-button="enabledVizSavedButton"
        :current-viz-tab="currentVizTab"
        :enabled-fork-viz-button="enabledForkVizButton"
        :viz-input-focus="vizInputFocus"
        :viz-id="vizID"
        :user-save-viz="userSaveViz"
        :show-private-public-icon-viz="showPrivatePublicIconViz"
        :show-private="showPrivate"
        :show-private-viz="showPrivateViz"
        :show-label-edit="showLabelEdit"
        :reset-private="resetPrivate"
        :object-columns="objectColumns"
        :config-map="configMap"
      />

      <DownloadsTab
        v-else-if="activeDatasetTab === 4"
        :array-formats="arrayFormats"
        :resources-list="resourcesList"
      />

      <MapTab
        v-else-if="activeDatasetTab === 5 && hasGeometryColumn"
        :items="items"
        :object-columns="objectColumns"
        :config-map="configMap"
      />
    </template>

</template>


update: 优化H5 ad组件

update: 优化H5 ad组件

<template>
  <uni-ad
    v-bind="attrs"
    v-on="$listeners"
  >
    <div
      ref="container"
      class="uni-ad-container"
      @click="onhandle"
    />
  </uni-ad>
</template>
<script>
import {
  subscriber
} from 'uni-mixins'

class AdConfig {
  static get instance () {
    if (this._instance == null) {
      this._instance = new AdConfig()
      this._instance._init()
    }
    return this._instance
  }

  constructor () {
    this._instance = null
    this._adConfig = null
    this._isLoading = false
    this._lastError = null
    this._callbacks = []
  }

  get adConfig () {
    return this._adConfig
  }

  _init () {
    var config = this._getConfig()
    if (config === null || !config.last) {
      return
    }

    var td = Math.abs(Date.now() - config.last)
    if (td < this.CACHE_TIME) {
      this._adConfig = config.data
    }
  }

  get (adpid, success, fail) {
    if (this._adConfig != null) {
      this._doCallback(adpid, success, fail)
      return
    }

    this._callbacks.push({
      adpid: adpid,
      success: success,
      fail: fail
    })

    this._loadAdConfig(adpid)
  }

  _doCallback (adpid, success, fail) {
    var data = this._adConfig
    if (data.adpids[adpid]) {
      success(data.adpids[adpid])
    } else {
      fail(this.ERROR_INVALID_ADPID)
    }
  }

  _loadAdConfig (adpid) {
    if (this._isLoading === true) {
      return
    }

    this._isLoading = true

    uni.request({
      url: this.URL,
      method: 'GET',
      timeout: 3000,
      data: {
        adpid: adpid
      },
      dataType: 'json',
      success: (res) => {
        const rd = res.data
        if (rd.ret === 0) {
          const data = rd.data

          this._adConfig = data
          this._setConfig(data)

          this._callbacks.forEach(({ adpid, success, fail }) => {
            this._doCallback(adpid, success, fail)
          })
        } else {
          this._callbacks.forEach((i) => {
            i.fail(rd.message)
          })
        }
        this._callbacks = []
      },
      fail: (err) => {
        this._callbacks.forEach((i) => {
          i.fail(err.errMsg)
        })
        this._callbacks = []
      },
      complete: (c) => {
        this._isLoading = false
      }
    })
  }

  _getConfig () {
    if (!navigator.cookieEnabled || !window.localStorage) {
      return null
    }
    var data = localStorage.getItem(this.KEY)
    return data ? JSON.parse(data) : null
  }

  _setConfig (data) {
    if (!navigator.cookieEnabled || !window.localStorage) {
      return null
    }
    localStorage.setItem(this.KEY, JSON.stringify({
      last: Date.now(),
      data: data
    }))
  }
}
Object.assign(AdConfig.prototype, {
  URL: '//stream.dcloud.net.cn/dcloud/H5Config',
  KEY: 'uni_app_ad_config',
  CACHE_TIME: 1000 * 60 * 10,
  ERROR_INVALID_ADPID: {
    '-5002': '无效adpid'
  }
})

const adProvider = {
  hx: 'hx',
  ky: 'ky'
}

const CHECK_RENDER_DELAY = 1000
const CHECK_RENDER_RETRY = 3

export default {
  name: 'Ad',
  mixins: [subscriber],
  props: {
    adpid: {
      type: [Number, String],
      default: ''
    }
  },
  watch: {
    adpid (val) {
      if (val) {
        this._loadData(val)
      }
    }
  },
  mounted () {
    this._pl = []
    this._pd = {}
    this._pi = 0
    this._checkTimer = null
    this._checkTimerCount = 0
    this._loadData()
  },
  beforeDestroy () {
    this.$refs.container.innerHTML = ''
  },
  methods: {
    onhandle (e) {
      console.log('onhandle')
    },
    _loadData (adpid) {
      AdConfig.instance.get(adpid || this.adpid, (data) => {
        this._pd = data
        this._pl = data.psp.split(',')
        this._renderAd()
      }, (err) => {
        this.$trigger('error', {}, { message: err })
      })
    },
    _renderAd () {
      if (this._pi > this._pl.length - 1) {
        return
      }

      var ap = this._pl[this._pi]
      var data = this._pd[ap]
      switch (ap) {
        case adProvider.hx:
          this._renderHX(data)
          break
        case adProvider.ky:
          this._renderKY(data)
          break
      }
    },
    _renderHX (data) {
      var ad = document.createElement('script')
      ad.src = data.src || data.url

      this.$refs.container.innerHTML = ''
      this.$refs.container.append(ad)

      this._startCheckTimer()
    },
    _renderKY (data) {
      var ad = document.createElement('script')
      ad.src = data.src || data.url

      this.$refs.container.innerHTML = ''
      this.$refs.container.append(ad)

      this._startCheckTimer()
    },
    _renderNext () {
      if (this._pi >= this._pl.length - 1) {
        return
      }

      this._pi++
      this._renderAd()
    },
    _checkRender () {
      var hasContent = (this.$refs.container.clientHeight > 40)
      return hasContent
    },
    _startCheckTimer () {
      this._clearCheckTimer()
      this._checkTimer = setInterval(() => {
        if (this._checkRender()) {
          this._clearCheckTimer()
          return
        }

        this._checkTimerCount++

        if (this._checkTimerCount >= CHECK_RENDER_RETRY) {
          this._clearCheckTimer()
          this._renderNext()
        }
      }, CHECK_RENDER_DELAY)
    },
    _clearCheckTimer () {
      this._checkTimerCount = 0
      if (this._checkTimer != null) {
        window.clearInterval(this._checkTimer)
        this._checkTimer = null
      }
    }
  }
}
</script>

<style>
  uni-ad {
    display: block;
    overflow: hidden;
  }

  uni-ad[hidden] {
    display: none;
  }
</style>

<template>
  <uni-ad
    v-bind="attrs"
    v-on="$listeners"
  >
    <div
      ref="container"
      class="uni-ad-container"
    />
  </uni-ad>
</template>
<script>
import {
  subscriber
} from 'uni-mixins'

class AdConfig {
  static get instance () {
    if (this._instance == null) {
      this._instance = new AdConfig()
      this._instance._init()
    }
    return this._instance
  }

  constructor () {
    this._instance = null
    this._adConfig = null
    this._isLoading = false
    this._lastError = null
    this._callbacks = []
  }

  get (adpid, callback) {
    if (this._adConfig != null) {
      callback(this._adConfig.adpids[adpid])
      return
    }

    this._callbacks.push({ adpid: adpid, callback: callback })
    this._loadAdConfig()
  }

  _init () {
    var config = this._getConfig()
    if (config != null && config.last) {
      var td = Math.abs(Date.now() - config.last)
      if (td < this.CACHE_TIME) {
        this._adConfig = config.data
      }
    }
  }

  _loadAdConfig (adpid, callback) {
    if (this._isLoading === true) {
      return
    }

    this._isLoading = true

    uni.request({
      url: this.URL,
      timeout: 3000,
      method: 'GET',
      data: {
        appid: '__UNI__ADD1C32'
      },
      dataType: 'json',
      success: (res) => {
        const rd = res.data
        if (rd.ret === 0) {
          const data = rd.data
          this._adConfig = data
          this._setConfig(data)
          this._callbacks.forEach((i) => {
            i.callback(data.adpids[i.adpid])
          })
        }
      },
      complete: (c) => {
        this._isLoading = false
      }
    })
  }

  _getConfig () {
    if (!navigator.cookieEnabled || !window.localStorage) {
      return null
    }
    var data = localStorage.getItem(this.KEY)
    return data ? JSON.parse(data) : null
  }

  _setConfig (data) {
    if (!navigator.cookieEnabled || !window.localStorage) {
      return null
    }
    localStorage.setItem(this.KEY, JSON.stringify({
      last: Date.now(),
      data: data
    }))
  }
}
Object.assign(AdConfig.prototype, {
  URL: '//stream.dcloud.net.cn/dcloud/H5Config',
  KEY: 'UNI_APP_AD',
  CACHE_TIME: 1000 * 60 * 10
})

const adProvider = {
  hx: 'hx',
  ky: 'ky'
}

const CHECK_RENDER_DELAY = 1000
const CHECK_RENDER_RETRY = 3

export default {
  name: 'Ad',
  mixins: [subscriber],
  props: {
    adpid: {
      type: [Number, String],
      default: ''
    }
  },
  data () {
    return {
      hidden: false
    }
  },
  watch: {
    hidden (val) {
    },
    adpid (val) {
      if (val) {
        this._loadData(val)
      }
    }
  },
  mounted () {
    this._pl = []
    this._pd = {}
    this._pi = 0
    this._loadData()
    this._checkTimer = null
    this._checkTimerCount = 0
  },
  beforeDestroy () {
    this.$refs.container.innerHTML = ''
  },
  methods: {
    _loadData (adpid) {
      AdConfig.instance.get(adpid || this.adpid, (data) => {
        this._pd = data
        this._pl = data.psp.split(',')
        this._renderAd()
      })
    },
    _renderAd () {
      if (this._pi > this._pl.length - 1) {
        return
      }
      var ap = this._pl[this._pi]
      var data = this._pd[ap]
      switch (ap) {
        case adProvider.hx:
          this._renderHX(data)
          break
        case adProvider.ky:
          this._renderKY(data)
          break
      }
    },
    _renderHX (data) {
      var ad = document.createElement('script')
      ad.src = data.src || data.url

      var adView = document.createElement('div')
      adView.setAttribute('id', this._randomId())
      adView.appendChild(ad)

      this.$refs.container.innerHTML = ''
      this.$refs.container.append(adView)

      // this._startCheckTimer()
    },
    _renderKY (data) {
      var ad = document.createElement('script')
      ad.src = data.src || data.url

      this.$refs.container.innerHTML = ''
      this.$refs.container.append(ad)

      // this._startCheckTimer()
    },
    _renderNext () {
      if (this._pi >= this._pl.length - 1) {
        return
      }

      this._pi++
      this._renderAd()
    },
    _checkRender () {
      var hasContent = this.$refs.container.querySelector('a') || this.$refs.container.querySelector('iframe')
      return hasContent
    },
    _startCheckTimer () {
      this._clearCheckTimer()
      this._checkTimer = setInterval(() => {
        if (this._checkRender()) {
          this._clearCheckTimer()
          return
        }
        this._checkTimerCount++
        if (this._checkTimerCount >= CHECK_RENDER_RETRY) {
          this._clearCheckTimer()
          this._renderNext()
        }
      }, CHECK_RENDER_DELAY)
    },
    _clearCheckTimer () {
      this._checkTimerCount = 0
      if (this._checkTimer != null) {
        window.clearInterval(this._checkTimer)
      }
    },
    _randomId () {
      return 'ad' + Date.now() + '' + parseInt(Math.random() * 1000)
    }
  }
}
</script>

<style>
  uni-ad {
    display: block;
    overflow: hidden;
  }

  uni-ad[hidden] {
    display: none;
  }

  uni-ad .uni-ad-container {
    min-height: 1px;
  }
</style>


Add checkboxradioswitch component to poll

Signed-off-by: Marco Ambrosini <3397c7e210a9d1588196cfebb12b2c6b133f6934@pm.me>

<template>

		Poll

{{ pollName }}

		<template v-if="vote !== undefined">
			<template v-if="checkboxRadioSwitchType === 'radio'">
				<CheckboxRadioSwitch v-for="option, index in options"
					:key="'radio' + index"
					:checked.sync="vote"
					:value="option"
					:type="checkboxRadioSwitchType"
					name="answerType">
					{{ option }}
				</CheckboxRadioSwitch>
			</template>
			<template v-else>
				<CheckboxRadioSwitch v-for="option, index in options"
					:key="'checkbox' + index"
					:checked.sync="vote"
					:value="option"
					:type="checkboxRadioSwitchType"
					name="answerType">
					{{ option }}
				</CheckboxRadioSwitch>
			</template>
		</template>

</template>

<template>

		Poll

{{ pollName }}

		<template v-if="pollLoaded">
			<CheckboxRadioSwitch v-for="answer, index in answers"
				:key="index"
				:checked.sync="sharingPermission"
				:value="answer"
				name="answerType"
				type="radio" />
		</template>

</template>


fix bug in which axes are scaled only to first data set

<script>
  import d3 from 'd3';
  import GraphLegend from './graph/legend.vue';
  import GraphFlag from './graph/flag.vue';
  import GraphDeployment from './graph/deployment.vue';
  import GraphPath from './graph/path.vue';
  import MonitoringMixin from '../mixins/monitoring_mixins';
  import eventHub from '../event_hub';
  import measurements from '../utils/measurements';
  import { timeScaleFormat, bisectDate } from '../utils/date_time_formatters';
  import createTimeSeries from '../utils/multiple_time_series';
  import bp from '../../breakpoints';

  export default {
    props: {
      graphData: {
        type: Object,
        required: true,
      },
      updateAspectRatio: {
        type: Boolean,
        required: true,
      },
      deploymentData: {
        type: Array,
        required: true,
      },
      hoverData: {
        type: Object,
        required: false,
        default: () => ({}),
      },
    },

    mixins: [MonitoringMixin],

    data() {
      return {
        baseGraphHeight: 450,
        baseGraphWidth: 600,
        graphHeight: 450,
        graphWidth: 600,
        graphHeightOffset: 120,
        margin: {},
        unitOfDisplay: '',
        yAxisLabel: '',
        legendTitle: '',
        reducedDeploymentData: [],
        measurements: measurements.large,
        currentData: {
          time: new Date(),
          value: 0,
        },
        currentDataIndex: 0,
        currentXCoordinate: 0,
        currentFlagPosition: 0,
        showFlag: false,
        showFlagContent: false,
        showDeployInfo: true,
        timeSeries: [],
      };
    },

    components: {
      GraphLegend,
      GraphFlag,
      GraphDeployment,
      GraphPath,
    },

    computed: {
      outerViewBox() {
        return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
      },

      innerViewBox() {
        if ((this.baseGraphWidth - 150) > 0) {
          return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
        }
        return '0 0 0 0';
      },

      axisTransform() {
        return `translate(70, ${this.graphHeight - 100})`;
      },

      paddingBottomRootSvg() {
        return {
          paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
        };
      },
    },

    methods: {
      draw() {
        const breakpointSize = bp.getBreakpointSize();
        const query = this.graphData.queries[0];
        this.margin = measurements.large.margin;
        if (breakpointSize === 'xs' || breakpointSize === 'sm') {
          this.graphHeight = 300;
          this.margin = measurements.small.margin;
          this.measurements = measurements.small;
        }
        this.unitOfDisplay = query.unit || '';
        this.yAxisLabel = this.graphData.y_label || 'Values';
        this.legendTitle = query.label || 'Average';
        this.graphWidth = this.$refs.baseSvg.clientWidth -
                     this.margin.left - this.margin.right;
        this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
        this.baseGraphHeight = this.graphHeight;
        this.baseGraphWidth = this.graphWidth;
        this.renderAxesPaths();
        this.formatDeployments();
      },

      handleMouseOverGraph(e) {
        let point = this.$refs.graphData.createSVGPoint();
        point.x = e.clientX;
        point.y = e.clientY;
        point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
        point.x = point.x += 7;
        const firstTimeSeries = this.timeSeries[0];
        const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
        const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
        const d0 = firstTimeSeries.values[overlayIndex - 1];
        const d1 = firstTimeSeries.values[overlayIndex];
        if (d0 === undefined || d1 === undefined) return;
        const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
        const hoveredDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
        const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
        const currentDeployXPos = this.mouseOverDeployInfo(point.x);

        eventHub.$emit('hoverChanged', {
          hoveredDate,
          currentDeployXPos,
        });
      },

      renderAxesPaths() {
        this.timeSeries = createTimeSeries(
          this.graphData.queries[0],
          this.graphWidth,
          this.graphHeight,
          this.graphHeightOffset,
        );

        if (this.timeSeries.length > 3) {
          this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
        }

        const axisXScale = d3.time.scale()
          .range([0, this.graphWidth - 70]);
        const axisYScale = d3.scale.linear()
          .range([this.graphHeight - this.graphHeightOffset, 0]);

        const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []);
        axisXScale.domain(d3.extent(allValues, d => d.time));
        axisYScale.domain([0, d3.max(allValues.map(d => d.value))]);

        const xAxis = d3.svg.axis()
          .scale(axisXScale)
          .ticks(d3.time.minute, 60)
          .tickFormat(timeScaleFormat)
          .orient('bottom');

        const yAxis = d3.svg.axis()
          .scale(axisYScale)
          .ticks(measurements.yTicks)
          .orient('left');

        d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);

        const width = this.graphWidth;
        d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
          .selectAll('.tick')
          .each(function createTickLines(d, i) {
            if (i > 0) {
              d3.select(this).select('line')
                .attr('x2', width)
                .attr('class', 'axis-tick');
            } // Avoid adding the class to the first tick, to prevent coloring
          }); // This will select all of the ticks once they're rendered
      },
    },

    watch: {
      updateAspectRatio() {
        if (this.updateAspectRatio) {
          this.graphHeight = 450;
          this.graphWidth = 600;
          this.measurements = measurements.large;
          this.draw();
          eventHub.$emit('toggleAspectRatio');
        }
      },

      hoverData() {
        this.positionFlag();
      },
    },

    mounted() {
      this.draw();
    },
  };
</script>

<template>
  <div
    class="prometheus-graph"
    @mouseover="showFlagContent = true"
    @mouseleave="showFlagContent = false">
    <h5 class="text-center graph-title">
      {{graphData.title}}
    </h5>
    <div
      class="prometheus-svg-container"
      :style="paddingBottomRootSvg">
      <svg
        :viewBox="outerViewBox"
        ref="baseSvg">
        <g
          class="x-axis"
          :transform="axisTransform">
        </g>
        <g
          class="y-axis"
          transform="translate(70, 20)">
        </g>
        <graph-legend
          :graph-width="graphWidth"
          :graph-height="graphHeight"
          :margin="margin"
          :measurements="measurements"
          :legend-title="legendTitle"
          :y-axis-label="yAxisLabel"
          :time-series="timeSeries"
          :unit-of-display="unitOfDisplay"
          :current-data-index="currentDataIndex"
        />
        <svg
          class="graph-data"
          :viewBox="innerViewBox"
          ref="graphData">
            <graph-path
              v-for="(path, index) in timeSeries"
              :key="index"
              :generated-line-path="path.linePath"
              :generated-area-path="path.areaPath"
              :line-color="path.lineColor"
              :area-color="path.areaColor"
            />
            <graph-deployment
              :show-deploy-info="showDeployInfo"
              :deployment-data="reducedDeploymentData"
              :graph-width="graphWidth"
              :graph-height="graphHeight"
              :graph-height-offset="graphHeightOffset"
            />
            <graph-flag
              v-if="showFlag"
              :current-x-coordinate="currentXCoordinate"
              :current-data="currentData"
              :current-flag-position="currentFlagPosition"
              :graph-height="graphHeight"
              :graph-height-offset="graphHeightOffset"
              :show-flag-content="showFlagContent"
            />
            <rect
              class="prometheus-graph-overlay"
              :width="(graphWidth - 70)"
              :height="(graphHeight - 100)"
              transform="translate(-5, 20)"
              ref="graphOverlay"
              @mousemove="handleMouseOverGraph($event)">
            </rect>
        </svg>
      </svg>
    </div>
  </div>
</template>

<script>
  import d3 from 'd3';
  import GraphLegend from './graph/legend.vue';
  import GraphFlag from './graph/flag.vue';
  import GraphDeployment from './graph/deployment.vue';
  import GraphPath from './graph/path.vue';
  import MonitoringMixin from '../mixins/monitoring_mixins';
  import eventHub from '../event_hub';
  import measurements from '../utils/measurements';
  import { timeScaleFormat, bisectDate } from '../utils/date_time_formatters';
  import createTimeSeries from '../utils/multiple_time_series';
  import bp from '../../breakpoints';

  export default {
    props: {
      graphData: {
        type: Object,
        required: true,
      },
      updateAspectRatio: {
        type: Boolean,
        required: true,
      },
      deploymentData: {
        type: Array,
        required: true,
      },
      hoverData: {
        type: Object,
        required: false,
        default: () => ({}),
      },
    },

    mixins: [MonitoringMixin],

    data() {
      return {
        baseGraphHeight: 450,
        baseGraphWidth: 600,
        graphHeight: 450,
        graphWidth: 600,
        graphHeightOffset: 120,
        margin: {},
        unitOfDisplay: '',
        yAxisLabel: '',
        legendTitle: '',
        reducedDeploymentData: [],
        measurements: measurements.large,
        currentData: {
          time: new Date(),
          value: 0,
        },
        currentDataIndex: 0,
        currentXCoordinate: 0,
        currentFlagPosition: 0,
        showFlag: false,
        showFlagContent: false,
        showDeployInfo: true,
        timeSeries: [],
      };
    },

    components: {
      GraphLegend,
      GraphFlag,
      GraphDeployment,
      GraphPath,
    },

    computed: {
      outerViewBox() {
        return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
      },

      innerViewBox() {
        if ((this.baseGraphWidth - 150) > 0) {
          return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
        }
        return '0 0 0 0';
      },

      axisTransform() {
        return `translate(70, ${this.graphHeight - 100})`;
      },

      paddingBottomRootSvg() {
        return {
          paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
        };
      },
    },

    methods: {
      draw() {
        const breakpointSize = bp.getBreakpointSize();
        const query = this.graphData.queries[0];
        this.margin = measurements.large.margin;
        if (breakpointSize === 'xs' || breakpointSize === 'sm') {
          this.graphHeight = 300;
          this.margin = measurements.small.margin;
          this.measurements = measurements.small;
        }
        this.unitOfDisplay = query.unit || '';
        this.yAxisLabel = this.graphData.y_label || 'Values';
        this.legendTitle = query.label || 'Average';
        this.graphWidth = this.$refs.baseSvg.clientWidth -
                     this.margin.left - this.margin.right;
        this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
        this.baseGraphHeight = this.graphHeight;
        this.baseGraphWidth = this.graphWidth;
        this.renderAxesPaths();
        this.formatDeployments();
      },

      handleMouseOverGraph(e) {
        let point = this.$refs.graphData.createSVGPoint();
        point.x = e.clientX;
        point.y = e.clientY;
        point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
        point.x = point.x += 7;
        const firstTimeSeries = this.timeSeries[0];
        const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
        const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
        const d0 = firstTimeSeries.values[overlayIndex - 1];
        const d1 = firstTimeSeries.values[overlayIndex];
        if (d0 === undefined || d1 === undefined) return;
        const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
        const hoveredDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
        const hoveredDate = firstTimeSeries.values[hoveredDataIndex].time;
        const currentDeployXPos = this.mouseOverDeployInfo(point.x);

        eventHub.$emit('hoverChanged', {
          hoveredDate,
          currentDeployXPos,
        });
      },

      renderAxesPaths() {
        this.timeSeries = createTimeSeries(
          this.graphData.queries[0],
          this.graphWidth,
          this.graphHeight,
          this.graphHeightOffset,
        );

        if (this.timeSeries.length > 3) {
          this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
        }

        const axisXScale = d3.time.scale()
          .range([0, this.graphWidth - 70]);
        const axisYScale = d3.scale.linear()
          .range([this.graphHeight - this.graphHeightOffset, 0]);

        axisXScale.domain(d3.extent(this.timeSeries[0].values, d => d.time));
        axisYScale.domain([0, d3.max(this.timeSeries[0].values.map(d => d.value))]);

        const xAxis = d3.svg.axis()
          .scale(axisXScale)
          .ticks(d3.time.minute, 60)
          .tickFormat(timeScaleFormat)
          .orient('bottom');

        const yAxis = d3.svg.axis()
          .scale(axisYScale)
          .ticks(measurements.yTicks)
          .orient('left');

        d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);

        const width = this.graphWidth;
        d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
          .selectAll('.tick')
          .each(function createTickLines(d, i) {
            if (i > 0) {
              d3.select(this).select('line')
                .attr('x2', width)
                .attr('class', 'axis-tick');
            } // Avoid adding the class to the first tick, to prevent coloring
          }); // This will select all of the ticks once they're rendered
      },
    },

    watch: {
      updateAspectRatio() {
        if (this.updateAspectRatio) {
          this.graphHeight = 450;
          this.graphWidth = 600;
          this.measurements = measurements.large;
          this.draw();
          eventHub.$emit('toggleAspectRatio');
        }
      },

      hoverData() {
        this.positionFlag();
      },
    },

    mounted() {
      this.draw();
    },
  };
</script>

<template>
  <div
    class="prometheus-graph"
    @mouseover="showFlagContent = true"
    @mouseleave="showFlagContent = false">
    <h5 class="text-center graph-title">
      {{graphData.title}}
    </h5>
    <div
      class="prometheus-svg-container"
      :style="paddingBottomRootSvg">
      <svg
        :viewBox="outerViewBox"
        ref="baseSvg">
        <g
          class="x-axis"
          :transform="axisTransform">
        </g>
        <g
          class="y-axis"
          transform="translate(70, 20)">
        </g>
        <graph-legend
          :graph-width="graphWidth"
          :graph-height="graphHeight"
          :margin="margin"
          :measurements="measurements"
          :legend-title="legendTitle"
          :y-axis-label="yAxisLabel"
          :time-series="timeSeries"
          :unit-of-display="unitOfDisplay"
          :current-data-index="currentDataIndex"
        />
        <svg
          class="graph-data"
          :viewBox="innerViewBox"
          ref="graphData">
            <graph-path
              v-for="(path, index) in timeSeries"
              :key="index"
              :generated-line-path="path.linePath"
              :generated-area-path="path.areaPath"
              :line-color="path.lineColor"
              :area-color="path.areaColor"
            />
            <graph-deployment
              :show-deploy-info="showDeployInfo"
              :deployment-data="reducedDeploymentData"
              :graph-width="graphWidth"
              :graph-height="graphHeight"
              :graph-height-offset="graphHeightOffset"
            />
            <graph-flag
              v-if="showFlag"
              :current-x-coordinate="currentXCoordinate"
              :current-data="currentData"
              :current-flag-position="currentFlagPosition"
              :graph-height="graphHeight"
              :graph-height-offset="graphHeightOffset"
              :show-flag-content="showFlagContent"
            />
            <rect
              class="prometheus-graph-overlay"
              :width="(graphWidth - 70)"
              :height="(graphHeight - 100)"
              transform="translate(-5, 20)"
              ref="graphOverlay"
              @mousemove="handleMouseOverGraph($event)">
            </rect>
        </svg>
      </svg>
    </div>
  </div>
</template>


remove leftover modal

remove leftover modal

/*
Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/
<template>

          <submission-preview
            v-if="preview"
            :form="form"
            :activity="activityType"
            :sections="displayFormSection"
            :errors="errors"
            :reportSubmitted="formSubmitSuccess"
            :formSubmitLoading="formSubmitLoading"
            :uploadedFiles="uploadedFiles"
            v-on:back="handlePreviewBackButton"
            v-on:startNewReport="handleExitPreviewAfterSubmit"
            v-on:fetchFiles="fetchFiles"
            />

          <activity-submission-form
            v-else
            :form="form"
            :activityType.sync="activityType"
            :sections="displayFormSection"
            :formSteps="formSteps"
            :errors="errors"
            :formIsFlat.sync="formIsFlat"
            :trackValueChanges="trackValueChanges"
            :formSubmitLoading="formSubmitLoading"
            :isStaffEdit="isStaffEdit"
            :loading="loading"
            :uploadedFiles="uploadedFiles"
            v-on:preview="handlePreviewButton"
            v-on:submit_edit="formSubmit"
            v-on:resetForm="resetForm"
            v-on:fetchFiles="fetchFiles"
            />

            Are you sure you want to submit this activity report?

                Save

                Cancel

</template>

/*
Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/
<template>

        <submission-preview
          v-if="preview"
          :form="form"
          :activity="activityType"
          :sections="displayFormSection"
          :errors="errors"
          :reportSubmitted="formSubmitSuccess"
          :formSubmitLoading="formSubmitLoading"
          :uploadedFiles="uploadedFiles"
          v-on:back="handlePreviewBackButton"
          v-on:startNewReport="handleExitPreviewAfterSubmit"
          v-on:fetchFiles="fetchFiles"
          />

        <activity-submission-form
          v-else
          :form="form"
          :activityType.sync="activityType"
          :sections="displayFormSection"
          :formSteps="formSteps"
          :errors="errors"
          :formIsFlat.sync="formIsFlat"
          :trackValueChanges="trackValueChanges"
          :formSubmitLoading="formSubmitLoading"
          :isStaffEdit="isStaffEdit"
          :loading="loading"
          :uploadedFiles="uploadedFiles"
          v-on:preview="handlePreviewButton"
          v-on:submit_edit="formSubmit"
          v-on:resetForm="resetForm"
          v-on:fetchFiles="fetchFiles"
          />

          Successfully uploaded all files

            Are you sure you want to submit this activity report?

                Save

                Cancel

</template>


order queries by ID

order queries by ID

<template>
  <div class="pure-u-1 pure-u-lg-3-4 gobierto-data-layout-column">
    <NavDatasets
      :active-tab="activeTabIndex"
      :array-queries="arrayQueries"
      :array-formats="arrayFormats"
      :table-name="tableName"
      :dataset-id="datasetId"
      :title-dataset="titleDataset"
      @active-tab="activeTabIndex = $event"
    />
  </div>
</template>
<script>
import axios from 'axios'
import { baseUrl } from "./../../lib/commons"
import { getUserId } from "./../../lib/helpers"
import NavDatasets from "./../components/sets/Nav.vue"

export default {
  name: "DataSets",
  components: {
    NavDatasets
  },
  data() {
    return {
      activeTabIndex: 0,
      rawData: '',
      titleDataset: '',
      arrayQueries: [],
      datasetId: 0,
      tableName: '',
      arrayFormats:{}
    }
  },
  created() {
    this.getData()
    this.$root.$on('reloadQueries', this.getQueries)
    this.userId = getUserId()
  },
  methods: {
    getQueries() {
      this.endPoint = `${baseUrl}/queries?filter[dataset_id]=${this.datasetId}&filter[user_id]=${this.userId}`
      axios
        .get(this.endPoint)
        .then(response => {
          this.rawData = response.data
          this.items = this.rawData.data
          this.arrayQueries = this.items.sort((a, b) => parseFloat(a.id) - parseFloat(b.id));
        })
        .catch(error => {
          const messageError = error.response
          console.error(messageError)
        })
    },
    getData() {
      this.url = `${baseUrl}/datasets/${this.$route.params.id}/meta`
      axios
        .get(this.url)
        .then(response => {
          this.rawData = response.data
          this.titleDataset = this.rawData.data.attributes.name
          this.datasetId = parseInt(this.rawData.data.id)
          this.slugDataset = this.rawData.data.attributes.slug
          this.tableName = this.rawData.data.attributes.table_name
          this.arrayFormats = this.rawData.data.attributes.formats

          this.$root.$emit('nameDataset', this.titleDataset)

          this.getQueries()
        })
        .catch(error => {
          console.error(error)
        })
    }
  }
}

</script>

<template>
  <div class="pure-u-1 pure-u-lg-3-4 gobierto-data-layout-column">
    <NavDatasets
      :active-tab="activeTabIndex"
      :array-queries="arrayQueries"
      :array-formats="arrayFormats"
      :table-name="tableName"
      :dataset-id="datasetId"
      :title-dataset="titleDataset"
      @active-tab="activeTabIndex = $event"
    />
  </div>
</template>
<script>
import axios from 'axios'
import { baseUrl } from "./../../lib/commons"
import { getUserId } from "./../../lib/helpers"
import NavDatasets from "./../components/sets/Nav.vue"

export default {
  name: "DataSets",
  components: {
    NavDatasets
  },
  data() {
    return {
      activeTabIndex: 0,
      rawData: '',
      titleDataset: '',
      arrayQueries: [],
      datasetId: 0,
      tableName: '',
      arrayFormats:{}
    }
  },
  created() {
    this.getData()
    this.$root.$on('reloadQueries', this.getQueries)
    this.userId = getUserId()
  },
  methods: {
    getQueries() {
      this.endPoint = `${baseUrl}/queries?filter[dataset_id]=${this.datasetId}&filter[user_id]=${this.userId}`
      axios
        .get(this.endPoint)
        .then(response => {
          this.rawData = response.data
          this.items = this.rawData.data
          this.arrayQueries = this.items
        })
        .catch(error => {
          const messageError = error.response
          console.error(messageError)
        })
    },
    getData() {
      this.url = `${baseUrl}/datasets/${this.$route.params.id}/meta`
      axios
        .get(this.url)
        .then(response => {
          this.rawData = response.data
          this.titleDataset = this.rawData.data.attributes.name
          this.datasetId = parseInt(this.rawData.data.id)
          this.slugDataset = this.rawData.data.attributes.slug
          this.tableName = this.rawData.data.attributes.table_name
          this.arrayFormats = this.rawData.data.attributes.formats

          this.$root.$emit('nameDataset', this.titleDataset)

          this.getQueries()
        })
        .catch(error => {
          console.error(error)
        })
    }
  }
}

</script>


Adding swap block

Adding swap block


Fix https://github.com/firefly-iii/firefly-iii/issues/6379

<template>

= 1" class="form-group" v-bind:class="{ 'has-error': hasError()}">

      {{ $t('form.foreign_amount') }}

      <select ref="currency_select" class="form-control" name="foreign_currency[]" @input="handleInput">
        <option
            v-for="currency in this.enabledCurrencies"
            :label="currency.attributes.name"
            :selected="parseInt(value.currency_id) === parseInt(currency.id)"
            :value="currency.id"

        >
          {{ currency.attributes.name }}
        </option>
      </select>

        <input v-if="this.enabledCurrencies.length > 0" ref="amount" :placeholder="this.title" :title="this.title" :value="value.amount" autocomplete="off"
               class="form-control" name="foreign_amount[]"
               step="any" type="number" @input="handleInput">

                <button
                    class="btn btn-default"
                    tabIndex="-1"
                    type="button"
                    v-on:click="clearAmount"></button>

{{ error }}

</template>


Fix user context menus in in search results view

<template>

					<SidebarToggle />
					Search results for "{{ $route.params.term }}" in
						{{ $route.params.target }}

					<MessageSearchForm :network="network" :channel="channel" />

							<button
								ref="loadMoreButton"
								:disabled="
									$store.state.messageSearchInProgress ||
									!$store.state.isConnected
								"
								class="btn"
								@click="onShowMoreClick"
							>
								Loading…
								Show older messages
							</button>

							Searching…

							No results found.

							<template v-for="(message, id) in messages">

									<DateMarker
										v-if="shouldDisplayDateMarker(message, id)"
										:key="message.date"
										:message="message"
									/>
									<Message
										:key="message.id"
										:channel="channel"
										:network="network"
										:message="message"
										:data-id="message.id"
									/>

							</template>

</template>


Using action of store to change language.

<template>

                <v-menu bottom left>
                    <v-btn icon slot="activator">
                        <v-icon>more_vert</v-icon>
                    </v-btn>
                    <v-list>
                        <v-list-tile @click="">
                            <v-list-tile-title>Dein Profil</v-list-tile-title>
                        </v-list-tile>
                        <v-divider></v-divider>
                        <v-list-tile @click="">
                            <v-list-tile-title>Frage erstellen</v-list-tile-title>
                        </v-list-tile>
                        <v-list-tile @click="">
                            <v-list-tile-title>Organisation erstellen</v-list-tile-title>
                        </v-list-tile>
                        <v-divider></v-divider>
                        <v-list-tile @click="">
                            <v-list-tile-title>Über Elyoos</v-list-tile-title>
                        </v-list-tile>
                        <v-list-tile @click="logout()">
                            <v-list-tile-title>Logout</v-list-tile-title>
                        </v-list-tile>
                    </v-list>
                </v-menu>

                <v-btn outline v-on:click="$router.push({name: 'login'})">{{$t("common:toolbar.login")}}</v-btn>

                <v-menu bottom>
                    <v-btn outline slot="activator">
                        {{selectedLanguage.description}}
                    </v-btn>
                    <v-list>
                        <v-list-tile @click="changeLanguage(lang.key)" v-for="lang in getLanguages" :key="lang.key">
                            <v-list-tile-title>{{lang.description}}</v-list-tile-title>
                        </v-list-tile>
                    </v-list>
                </v-menu>

                <v-btn flat icon>
                    <v-icon>notifications_none</v-icon>
                </v-btn>

                <v-btn flat icon v-on:click="$router.push({name: 'index'})"
                       :class="{active: $route.path === '/'}">
                    <v-icon>home</v-icon>
                </v-btn>

</template>


Fix sporadic rounding on message search bar. (#4333)

This is fixed in the same way as #4328.

<template>
	<form :class="['message-search', {opened: searchOpened}]" @submit.prevent="searchMessages">
		<div class="input-wrapper">
			<input
				ref="searchInputField"
				v-model="searchInput"
				type="search"
				name="search"
				class="input"
				placeholder="Search messages…"
				@blur="closeSearch"
			/>
		</div>
		<button
			v-if="!onSearchPage"
			class="search"
			type="button"
			aria-label="Search messages in this channel"
			@mousedown.prevent="toggleSearch"
		/>
	</form>
</template>

<style>
form.message-search {
	display: flex;
}

form.message-search .input-wrapper {
	display: flex;
}

form.message-search input {
	width: 100%;
	height: auto !important;
	margin: 7px 0;
	border: 0;
	color: inherit;
	background-color: #fafafa;
	appearance: none;
}

form.message-search input::placeholder {
	color: rgba(0, 0, 0, 0.35);
}

@media (min-width: 480px) {
	form.message-search input {
		min-width: 140px;
	}

	form.message-search input:focus {
		min-width: 220px;
	}
}

form.message-search .input-wrapper {
	position: absolute;
	top: 45px;
	left: 0;
	right: 0;
	z-index: 1;
	height: 0;
	overflow: hidden;
	background: var(--window-bg-color);
}

form.message-search .input-wrapper input {
	margin: 7px;
}

form.message-search.opened .input-wrapper {
	height: 50px;
}

#chat form.message-search button {
	display: flex;
	color: #607992;
}
</style>

<script>
export default {
	name: "MessageSearchForm",
	props: {
		network: Object,
		channel: Object,
	},
	data() {
		return {
			searchOpened: false,
			searchInput: "",
		};
	},
	computed: {
		onSearchPage() {
			return this.$route.name === "SearchResults";
		},
	},
	watch: {
		"$route.query.q"() {
			this.searchInput = this.$route.query.q;
		},
	},
	mounted() {
		this.searchInput = this.$route.query.q;
		this.searchOpened = this.onSearchPage;

		if (!this.searchInput && this.searchOpened) {
			this.$refs.searchInputField.focus();
		}
	},
	methods: {
		closeSearch() {
			if (!this.onSearchPage) {
				this.searchOpened = false;
			}
		},
		toggleSearch() {
			if (this.searchOpened) {
				this.$refs.searchInputField.blur();
				return;
			}

			this.searchOpened = true;
			this.$refs.searchInputField.focus();
		},
		searchMessages(event) {
			event.preventDefault();

			if (!this.searchInput) {
				return;
			}

			this.$router
				.push({
					name: "SearchResults",
					params: {
						id: this.channel.id,
					},
					query: {
						q: this.searchInput,
					},
				})
				.catch((err) => {
					if (err.name === "NavigationDuplicated") {
						// Search for the same query again
						this.$root.$emit("re-search");
					}
				});
		},
	},
};
</script>

<template>
	<form :class="['message-search', {opened: searchOpened}]" @submit.prevent="searchMessages">
		<div class="input-wrapper">
			<input
				ref="searchInputField"
				v-model="searchInput"
				type="search"
				name="search"
				class="input"
				placeholder="Search messages…"
				@blur="closeSearch"
			/>
		</div>
		<button
			v-if="!onSearchPage"
			class="search"
			type="button"
			aria-label="Search messages in this channel"
			@mousedown.prevent="toggleSearch"
		/>
	</form>
</template>

<style>
form.message-search {
	display: flex;
}

form.message-search .input-wrapper {
	display: flex;
}

form.message-search input {
	width: 100%;
	height: auto !important;
	margin: 7px 0;
	border: 0;
	color: inherit;
	background-color: #fafafa;
}

form.message-search input::placeholder {
	color: rgba(0, 0, 0, 0.35);
}

@media (min-width: 480px) {
	form.message-search input {
		min-width: 140px;
	}

	form.message-search input:focus {
		min-width: 220px;
	}
}

form.message-search .input-wrapper {
	position: absolute;
	top: 45px;
	left: 0;
	right: 0;
	z-index: 1;
	height: 0;
	overflow: hidden;
	background: var(--window-bg-color);
}

form.message-search .input-wrapper input {
	margin: 7px;
}

form.message-search.opened .input-wrapper {
	height: 50px;
}

#chat form.message-search button {
	display: flex;
	color: #607992;
}
</style>

<script>
export default {
	name: "MessageSearchForm",
	props: {
		network: Object,
		channel: Object,
	},
	data() {
		return {
			searchOpened: false,
			searchInput: "",
		};
	},
	computed: {
		onSearchPage() {
			return this.$route.name === "SearchResults";
		},
	},
	watch: {
		"$route.query.q"() {
			this.searchInput = this.$route.query.q;
		},
	},
	mounted() {
		this.searchInput = this.$route.query.q;
		this.searchOpened = this.onSearchPage;

		if (!this.searchInput && this.searchOpened) {
			this.$refs.searchInputField.focus();
		}
	},
	methods: {
		closeSearch() {
			if (!this.onSearchPage) {
				this.searchOpened = false;
			}
		},
		toggleSearch() {
			if (this.searchOpened) {
				this.$refs.searchInputField.blur();
				return;
			}

			this.searchOpened = true;
			this.$refs.searchInputField.focus();
		},
		searchMessages(event) {
			event.preventDefault();

			if (!this.searchInput) {
				return;
			}

			this.$router
				.push({
					name: "SearchResults",
					params: {
						id: this.channel.id,
					},
					query: {
						q: this.searchInput,
					},
				})
				.catch((err) => {
					if (err.name === "NavigationDuplicated") {
						// Search for the same query again
						this.$root.$emit("re-search");
					}
				});
		},
	},
};
</script>


fixed spelling misstake (#1473)

fixed spelling misstake (#1473)

https://vuematerial.io/components/menu

<template>
  <div>
    <md-menu md-direction="bottom-start">
      <md-button md-menu-trigger>Bottom Start</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>

    <md-menu md-direction="bottom-end">
      <md-button md-menu-trigger>Bottom End</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>

    <md-menu md-direction="top-start">
      <md-button md-menu-trigger>Top Start</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>

    <md-menu md-direction="top-end">
      <md-button md-menu-trigger>Top End</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>
  </div>
</template>

<script>
  export default {
    name: 'Directions'
  }
</script>

<style lang="scss" scoped>
  .md-menu {
    margin: 24px;
  }
</style>

<template>
  <div>
    <md-menu md-direction="bottom-start">
      <md-button md-menu-trigger>Bottom Start</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>

    <md-menu md-direction="bottom-end">
      <md-button md-menu-trigger>Bottom End</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>

    <md-menu md-direction="top-start">
      <md-button md-menu-trigger>Top Start</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>

    <md-menu md-direction="top-end">
      <md-button md-menu-trigger>Bottom End</md-button>

      <md-menu-content>
        <md-menu-item>My Item 1</md-menu-item>
        <md-menu-item>My Item 2</md-menu-item>
        <md-menu-item>My Item 3</md-menu-item>
      </md-menu-content>
    </md-menu>
  </div>
</template>

<script>
  export default {
    name: 'Directions'
  }
</script>

<style lang="scss" scoped>
  .md-menu {
    margin: 24px;
  }
</style>


use provided roles instead

use provided roles instead

<style scoped>
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  [hidden] {
    display: none !important;
  }
  body,
  html {
    position: relative;
    overflow: hidden;
    -webkit-user-select: none;
  }
  #nav {
    width: 100vw;
  }
</style>

<template lang="pug">
  div
    #nav
      tabs
      navbar
    page(v-for="(page, i) in pages", :isActive="i == currentPageIndex", :pageIndex="i", :ref="`page-${i}`", :key="`page-${page.pid}`")
</template>

<script>
  import Tabs from './BrowserMainView/Tabs';
  import Navbar from './BrowserMainView/Navbar';
  import Page from './BrowserMainView/Page';

  export default {
    components: {
      Tabs,
      Navbar,
      Page,
    },
    name: 'browser-main',
    computed: {
      pages() {
        return this.$store.getters.pages;
      },
      currentPageIndex() {
        return this.$store.getters.currentPageIndex;
      },
    },
    methods: {
      getWebView(i) {
        i = (typeof i === 'undefined') ? this.$store.getters.currentPageIndex : i;
        return this.$refs[`page-${i}`][0].$refs.webview;
      },
      getPage(i) {
        i = (typeof i === 'undefined') ? this.$store.getters.currentPageIndex : i;
        return this.$refs[`page-${i}`][0];
      },
      getPageObject(i) {
        i = (typeof i === 'undefined') ? this.$store.getters.currentPageIndex : i;
        return this.$store.getters.pages[i];
      },
      // pageHandlers
      onDidStartLoading(event, pageIndex) {
        this.$store.dispatch('didStartLoading', pageIndex);
      },
      onDomReady(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('domReady', {
          pageIndex,
          webview,
        });
      },
      onDidStopLoading(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('didStopLoading', {
          pageIndex,
          webview,
        });
      },
      onPageTitleSet(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('pageTitleSet', {
          pageIndex,
          webview,
        });
      },
      onUpdateTargetUrl(event, pageIndex) {
        this.$store.dispatch('updateTargetUrl', {
          pageIndex,
          url: event.url,
        });
      },
      onMediaStartedPlaying(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('mediaStartedPlaying', {
          pageIndex,
          webview,
        });
      },
      onMediaPaused(event, pageIndex) {
        this.$store.dispatch('mediaPaused', pageIndex);
      },
      onToggleAudio(event, pageIndex, muted) {
        this.getWebView(pageIndex).setAudioMuted(muted);
        this.$store.dispatch('toggleAudio', {
          pageIndex,
          muted,
        });
      },
      onPageFaviconUpdated(event, pageIndex) {
        this.$store.dispatch('pageFaviconUpdated', {
          pageIndex,
          url: event.favicons[0],
        });
      },
      onEnterHtmlFullScreen() {
        this.$el.querySelector('#nav').style.height = 0;
        this.getWebView().style.height = '100vh';
      },
      onLeaveHtmlFullScreen() {
        this.$el.querySelector('#nav').style.height = '62px';
        this.getWebView().style.height = 'calc(100vh - 62px)';
      },
      // tabHandlers
      onNewTab() {
        this.$store.dispatch('incrementPid');
        this.$store.dispatch('createTab');
      },
      onTabClick(event, pageIndex) {
        this.$store.dispatch('clickTab', pageIndex);
      },
      onTabClose(event, pageIndex) {
        this.$store.dispatch('closeTab', pageIndex);
      },
      // navHandlers
      onClickHome() {
        this.getPage().navigateTo();
      },
      onClickBack() {
        this.getWebView().goBack();
      },
      onClickForward() {
        this.getWebView().goForward();
      },
      onClickRefresh() {
        this.getWebView().reload();
      },
      onEnterLocation(location) {
        this.getPage().navigateTo(location);
      },
      // onTabContextMenu
      onTabContextMenu(event, pageIndex) {
        const { Menu, MenuItem } = this.$electron.remote;
        const menu = new Menu();

        menu.append(new MenuItem({
          label: 'New Tab',
          click: () => {
            this.$store.dispatch('incrementPid');
            this.$store.dispatch('createTab');
          },
        }));
        menu.append(new MenuItem({
          label: 'Duplicate',
          click: () => {
            this.$store.dispatch('incrementPid');
            this.$store.dispatch('createTab', this.pages[pageIndex].location);
          },
        }));
        menu.append(new MenuItem({ type: 'separator' }));
        menu.append(new MenuItem({
          label: 'Close Tab',
          click: () => {
            this.$store.dispatch('closeTab', pageIndex);
          },
        }));

        menu.popup(this.$electron.remote.getCurrentWindow());
      },
      // onNavContextMenu
      onNavContextMenu(event) {
        const { Menu, MenuItem } = this.$electron.remote;
        const menu = new Menu();
        const el = event.target;
        const clipboard = this.$electron.clipboard;

        menu.append(new MenuItem({
          label: 'Cut',
          role: 'cut',
        }));
        menu.append(new MenuItem({
          label: 'Copy',
          role: 'copy',
        }));
        menu.append(new MenuItem({
          label: 'Paste',
          role: 'paste',
        }));
        menu.append(new MenuItem({
          label: 'Paste and Go',
          click: () => {
            let location = el.value.slice(0, el.selectionStart);
            location += clipboard.readText();
            location += el.value.slice(el.selectionEnd);
            this.$store.dispatch('updateLocation', location);
            this.getPage().navigateTo(location);
          },
        }));

        menu.popup(this.$electron.remote.getCurrentWindow());
      },
      // onWebviewContextMenu
      onWebviewContextMenu(event) {
        const { Menu, MenuItem } = this.$electron.remote;
        const menu = new Menu();
        const clipboard = this.$electron.clipboard;

        if (event.params.editFlags.canUndo) {
          menu.append(new MenuItem({
            label: 'Undo',
            accelerator: 'CmdOrCtrl+Z',
            role: 'undo',
          }));
        }

        if (event.params.editFlags.canRedo) {
          menu.append(new MenuItem({
            label: 'Redo',
            accelerator: 'Shift+CmdOrCtrl+Z',
            role: 'redo',
          }));
        }

        menu.append(new MenuItem({ type: 'separator' }));

        if (event.params.editFlags.canCut) {
          menu.append(new MenuItem({
            label: 'Cut',
            accelerator: 'CmdOrCtrl+X',
            role: 'cut',
          }));
        }

        if (event.params.editFlags.canCopy) {
          menu.append(new MenuItem({
            label: 'Copy',
            accelerator: 'CmdOrCtrl+C',
            role: 'copy',
          }));
        }

        if (event.params.editFlags.canPaste) {
          menu.append(new MenuItem({
            label: 'Paste',
            accelerator: 'CmdOrCtrl+V',
            role: 'paste',
          }));
          menu.append(new MenuItem({
            label: 'Paste Without Formatting',
            accelerator: 'Shift+CmdOrCtrl+V',
            role: 'pasteandmatchstyle',
          }));
        }

        menu.append(new MenuItem({
          label: 'Select All',
          accelerator: 'CmdOrCtrl+A',
          role: 'selectall',
        }));
        menu.append(new MenuItem({ type: 'separator' }));

        if (event.params.linkURL) {
          menu.append(new MenuItem({
            label: 'Open Link in New Tab',
            click: () => {
              this.$store.dispatch('incrementPid');
              this.$store.dispatch('createTab', event.params.linkURL);
            },
          }));
          menu.append(new MenuItem({
            label: 'Copy Link Address',
            click: () => {
              clipboard.writeText(event.params.linkURL);
            },
          }));
        }

        if (event.params.hasImageContents) {
          menu.append(new MenuItem({
            label: 'Save Image As...',
            click: () => {
              alert('ToDo...');
            },
          }));
          menu.append(new MenuItem({
            label: 'Copy Image URL',
            click: () => {
              clipboard.writeText(event.params.srcURL);
            },
          }));
          menu.append(new MenuItem({
            label: 'Open Image in New Tab',
            click: () => {
              this.$store.dispatch('incrementPid');
              this.$store.dispatch('createTab', event.params.srcURL);
            },
          }));
        }

        menu.append(new MenuItem({ type: 'separator' }));
        const macOS = /^darwin/.test(process.platform);
        if (macOS) {
          if (event.params.selectionText) {
            menu.append(new MenuItem({
              label: `Look up "${event.params.selectionText}"`,
              click: () => {
                this.getWebView().showDefinitionForSelection();
              },
            }));
            menu.append(new MenuItem({ type: 'separator' }));
          }
        }
        menu.append(new MenuItem({
          label: 'Inspect Element',
          click: () => {
            this.getWebView().inspectElement(event.params.x, event.params.y);
          },
        }));

        menu.popup(this.$electron.remote.getCurrentWindow());
      },
    },
    mounted() {
      this.onDidStartLoading.bind(this);
      this.onDomReady.bind(this);
      this.onDidStopLoading.bind(this);
      this.onPageTitleSet.bind(this);
      this.onUpdateTargetUrl.bind(this);
      this.onMediaStartedPlaying.bind(this);
      this.onMediaPaused.bind(this);
      this.onToggleAudio.bind(this);
      this.onPageFaviconUpdated.bind(this);
      this.onNewTab.bind(this);
      this.onTabClick.bind(this);
      this.onTabClose.bind(this);
      this.onTabContextMenu.bind(this);
      this.onWebviewContextMenu.bind(this);
    },
  };
</script>

<style scoped>
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  [hidden] {
    display: none !important;
  }
  body,
  html {
    position: relative;
    overflow: hidden;
    -webkit-user-select: none;
  }
  #nav {
    width: 100vw;
  }
</style>

<template lang="pug">
  div
    #nav
      tabs
      navbar
    page(v-for="(page, i) in pages", :isActive="i == currentPageIndex", :pageIndex="i", :ref="`page-${i}`", :key="`page-${page.pid}`")
</template>

<script>
  import Tabs from './BrowserMainView/Tabs';
  import Navbar from './BrowserMainView/Navbar';
  import Page from './BrowserMainView/Page';

  export default {
    components: {
      Tabs,
      Navbar,
      Page,
    },
    name: 'browser-main',
    computed: {
      pages() {
        return this.$store.getters.pages;
      },
      currentPageIndex() {
        return this.$store.getters.currentPageIndex;
      },
    },
    methods: {
      getWebView(i) {
        i = (typeof i === 'undefined') ? this.$store.getters.currentPageIndex : i;
        return this.$refs[`page-${i}`][0].$refs.webview;
      },
      getPage(i) {
        i = (typeof i === 'undefined') ? this.$store.getters.currentPageIndex : i;
        return this.$refs[`page-${i}`][0];
      },
      getPageObject(i) {
        i = (typeof i === 'undefined') ? this.$store.getters.currentPageIndex : i;
        return this.$store.getters.pages[i];
      },
      // pageHandlers
      onDidStartLoading(event, pageIndex) {
        this.$store.dispatch('didStartLoading', pageIndex);
      },
      onDomReady(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('domReady', {
          pageIndex,
          webview,
        });
      },
      onDidStopLoading(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('didStopLoading', {
          pageIndex,
          webview,
        });
      },
      onPageTitleSet(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('pageTitleSet', {
          pageIndex,
          webview,
        });
      },
      onUpdateTargetUrl(event, pageIndex) {
        this.$store.dispatch('updateTargetUrl', {
          pageIndex,
          url: event.url,
        });
      },
      onMediaStartedPlaying(event, pageIndex) {
        const webview = this.getWebView(pageIndex);
        this.$store.dispatch('mediaStartedPlaying', {
          pageIndex,
          webview,
        });
      },
      onMediaPaused(event, pageIndex) {
        this.$store.dispatch('mediaPaused', pageIndex);
      },
      onToggleAudio(event, pageIndex, muted) {
        this.getWebView(pageIndex).setAudioMuted(muted);
        this.$store.dispatch('toggleAudio', {
          pageIndex,
          muted,
        });
      },
      onPageFaviconUpdated(event, pageIndex) {
        this.$store.dispatch('pageFaviconUpdated', {
          pageIndex,
          url: event.favicons[0],
        });
      },
      onEnterHtmlFullScreen() {
        this.$el.querySelector('#nav').style.height = 0;
        this.getWebView().style.height = '100vh';
      },
      onLeaveHtmlFullScreen() {
        this.$el.querySelector('#nav').style.height = '62px';
        this.getWebView().style.height = 'calc(100vh - 62px)';
      },
      // tabHandlers
      onNewTab() {
        this.$store.dispatch('incrementPid');
        this.$store.dispatch('createTab');
      },
      onTabClick(event, pageIndex) {
        this.$store.dispatch('clickTab', pageIndex);
      },
      onTabClose(event, pageIndex) {
        this.$store.dispatch('closeTab', pageIndex);
      },
      // navHandlers
      onClickHome() {
        this.getPage().navigateTo();
      },
      onClickBack() {
        this.getWebView().goBack();
      },
      onClickForward() {
        this.getWebView().goForward();
      },
      onClickRefresh() {
        this.getWebView().reload();
      },
      onEnterLocation(location) {
        this.getPage().navigateTo(location);
      },
      // onTabContextMenu
      onTabContextMenu(event, pageIndex) {
        const { Menu, MenuItem } = this.$electron.remote;
        const menu = new Menu();

        menu.append(new MenuItem({
          label: 'New Tab',
          click: () => {
            this.$store.dispatch('incrementPid');
            this.$store.dispatch('createTab');
          },
        }));
        menu.append(new MenuItem({
          label: 'Duplicate',
          click: () => {
            this.$store.dispatch('incrementPid');
            this.$store.dispatch('createTab', this.pages[pageIndex].location);
          },
        }));
        menu.append(new MenuItem({ type: 'separator' }));
        menu.append(new MenuItem({
          label: 'Close Tab',
          click: () => {
            this.$store.dispatch('closeTab', pageIndex);
          },
        }));

        menu.popup(this.$electron.remote.getCurrentWindow());
      },
      // onNavContextMenu
      onNavContextMenu(event) {
        const { Menu, MenuItem } = this.$electron.remote;
        const menu = new Menu();
        const el = event.target;
        const clipboard = this.$electron.clipboard;

        menu.append(new MenuItem({
          label: 'Cut',
          click: () => {
            const location = el.value.slice(0, el.selectionStart) + el.value.slice(el.selectionEnd);
            clipboard.writeText(el.value.slice(el.selectionStart, el.selectionEnd));
            this.$store.dispatch('updateLocation', location);
          },
        }));
        menu.append(new MenuItem({
          label: 'Copy',
          click: () => {
            clipboard.writeText(el.value.slice(el.selectionStart, el.selectionEnd));
          },
        }));
        menu.append(new MenuItem({
          label: 'Paste',
          click: () => {
            let location = el.value.slice(0, el.selectionStart);
            location += clipboard.readText();
            location += el.value.slice(el.selectionEnd);
            this.$store.dispatch('updateLocation', location);
          },
        }));
        menu.append(new MenuItem({
          label: 'Paste and Go',
          click: () => {
            let location = el.value.slice(0, el.selectionStart);
            location += clipboard.readText();
            location += el.value.slice(el.selectionEnd);
            this.$store.dispatch('updateLocation', location);
            this.getPage().navigateTo(location);
          },
        }));

        menu.popup(this.$electron.remote.getCurrentWindow());
      },
      // onWebviewContextMenu
      onWebviewContextMenu(event) {
        const { Menu, MenuItem } = this.$electron.remote;
        const menu = new Menu();
        const clipboard = this.$electron.clipboard;

        if (event.params.editFlags.canUndo) {
          menu.append(new MenuItem({
            label: 'Undo',
            click: () => {
              this.getWebView().undo();
            },
          }));
        }

        if (event.params.editFlags.canRedo) {
          menu.append(new MenuItem({
            label: 'Redo',
            click: () => {
              this.getWebView().redo();
            },
          }));
        }

        menu.append(new MenuItem({ type: 'separator' }));

        if (event.params.editFlags.canCut) {
          menu.append(new MenuItem({
            label: 'Cut',
            click: () => {
              this.getWebView().cut();
            },
          }));
        }

        if (event.params.editFlags.canCopy) {
          menu.append(new MenuItem({
            label: 'Copy',
            click: () => {
              this.getWebView().copy();
            },
          }));
        }

        if (event.params.editFlags.canPaste) {
          menu.append(new MenuItem({
            label: 'Paste',
            click: () => {
              this.getWebView().pasteAndMatchStyle();
            },
          }));
          menu.append(new MenuItem({
            label: 'Paste Without Formatting',
            click: () => {
              this.getWebView().paste();
            },
          }));
        }

        menu.append(new MenuItem({
          label: 'Select All',
          click: () => {
            this.getWebView().selectAll();
          },
        }));
        menu.append(new MenuItem({ type: 'separator' }));

        if (event.params.linkURL) {
          menu.append(new MenuItem({
            label: 'Open Link in New Tab',
            click: () => {
              this.$store.dispatch('incrementPid');
              this.$store.dispatch('createTab', event.params.linkURL);
            },
          }));
          menu.append(new MenuItem({
            label: 'Copy Link Address',
            click: () => {
              clipboard.writeText(event.params.linkURL);
            },
          }));
        }

        if (event.params.hasImageContents) {
          menu.append(new MenuItem({
            label: 'Save Image As...',
            click: () => {
              alert('ToDo...');
            },
          }));
          menu.append(new MenuItem({
            label: 'Copy Image URL',
            click: () => {
              clipboard.writeText(event.params.srcURL);
            },
          }));
          menu.append(new MenuItem({
            label: 'Open Image in New Tab',
            click: () => {
              this.$store.dispatch('incrementPid');
              this.$store.dispatch('createTab', event.params.srcURL);
            },
          }));
        }

        menu.append(new MenuItem({ type: 'separator' }));
        const macOS = /^darwin/.test(process.platform);
        if (macOS) {
          if (event.params.selectionText) {
            menu.append(new MenuItem({
              label: `Look up "${event.params.selectionText}"`,
              click: () => {
                this.getWebView().showDefinitionForSelection();
              },
            }));
            menu.append(new MenuItem({ type: 'separator' }));
          }
        }
        menu.append(new MenuItem({
          label: 'Inspect Element',
          click: () => {
            this.getWebView().inspectElement(event.params.x, event.params.y);
          },
        }));

        menu.popup(this.$electron.remote.getCurrentWindow());
      },
    },
    mounted() {
      this.onDidStartLoading.bind(this);
      this.onDomReady.bind(this);
      this.onDidStopLoading.bind(this);
      this.onPageTitleSet.bind(this);
      this.onUpdateTargetUrl.bind(this);
      this.onMediaStartedPlaying.bind(this);
      this.onMediaPaused.bind(this);
      this.onToggleAudio.bind(this);
      this.onPageFaviconUpdated.bind(this);
      this.onNewTab.bind(this);
      this.onTabClick.bind(this);
      this.onTabClose.bind(this);
      this.onTabContextMenu.bind(this);
      this.onWebviewContextMenu.bind(this);
    },
  };
</script>


remove whitespace

remove whitespace

<template>

        Administrator options

            Add new entry

            Manage companies

      To update contact information or for general enquiries email groundwater@gov.bc.ca.

        Learn more about registering as a well driller or well pump installer in B.C.

Search for a Well Driller or Well Pump Installer

                  Well Driller
                  Well Pump Installer

                    <option value="">All</option>
                    <optgroup
                      v-for="prov in cityList[formatActivityForCityList]"
                      v-if="prov.cities && prov.cities.length"
                      :key="prov.prov"
                      :label="prov.prov"
                    >
                      <option v-for="city in prov.cities" :key="`${city} ${prov.prov}`" :value="city">{{ city }}</option>
                    </optgroup>

                  <option value="">All</option>
                  <option value="PENDING">Pending</option>
                  <option value="INACTIVE">Not registered</option>
                  <option value="ACTIVE">Registered</option>
                  <option value="REMOVED">Removed</option>

                <select
                    v-model="searchParams.limit"
                    id="registriesResultsNumberSelect">
                  <option>10</option>
                  <option>25</option>
                </select>

                  <button
                    type="submit"
                    class="btn btn-primary registries-search-btn mr-md-1"
                    id="personSearchSubmit"
                    :disabled="searchLoading">
                    Search

                  </button>
                  <button type="reset" class="btn btn-default" id="personSearchReset">Reset</button>

        <template v-if="!searchLoading">

              No results were found.

              <api-error :error="listError" resetter="SET_LIST_ERROR"></api-error>

{{ activityTitle }} Results

              To update contact information email groundwater@gov.bc.ca.

              <registry-table @sort="sortTable" :activity="lastSearchedActivity"/>

              <register-legal-text class="register-legal" :activity="lastSearchedActivity"/>

        </template>

</template>


temporarily comment out diff code with side effect

/*
Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/
<template>

            <submission-preview
              v-if="preview"
              :form="form"
              :activity="activityType"
              :sections="displayFormSection"
              :errors="errors"
              :reportSubmitted="formSubmitSuccess"
              :formSubmitLoading="formSubmitLoading"
              :uploadedFiles="uploadedFiles"
              v-on:back="handlePreviewBackButton"
              v-on:startNewReport="handleExitPreviewAfterSubmit"
              v-on:fetchFiles="fetchFiles"
              />

Not Found

The page you are looking for was not found.

              <activity-submission-form
                v-else
                :form="form"
                :events="events"
                :submissionsHistory="submissionsHistory"
                :activityType.sync="activityType"
                :sections="displayFormSection"
                :formSteps="formSteps"
                :errors="errors"
                :formIsFlat.sync="formIsFlat"
                :trackValueChanges="trackValueChanges"
                :formSubmitLoading="formSubmitLoading"
                :isStaffEdit="isStaffEdit"
                :loading="loading"
                :uploadedFiles="uploadedFiles"
                :formChanges="formChanges"
                v-on:preview="handlePreviewButton"
                v-on:submit_edit="formSubmit"
                v-on:resetForm="resetForm"
                v-on:fetchFiles="fetchFiles"
                />

              Are you sure you want to submit this activity report?

                  Save

                  Cancel

</template>


Update layout of ContentPage

Update layout of ContentPage

<template>

    <PageHeader
      :title="content.title"
      :progress="progress"
      dir="auto"
    />
    <CoachContentLabel
      class="coach-content-label"
      :value="content.coach_content ? 1 : 0"
      :isTopic="isTopic"
    />

      <mat-svg
        ref="folderIcon"
        class="folder-svg"
        category="file"
        name="folder"
      />
      <KTooltip
        reference="folderIcon"
        :refs="$refs"
      >
        {{ $tr('topicLocationTooltip') }}
      </KTooltip>
      <KRouterLink
        :text="parentTopic.title"
        :to="$router.getRoute('TOPICS_TOPIC', { id: parentTopic.id })"
      />

    <ContentRenderer
      v-if="!content.assessment"
      :id="content.id"
      class="content-renderer"
      :kind="content.kind"
      :lang="content.lang"
      :files="content.files"
      :contentId="contentId"
      :channelId="channelId"
      :available="content.available"
      :extraFields="extraFields"
      :initSession="initSession"
      @sessionInitialized="setWasIncomplete"
      @startTracking="startTracking"
      @stopTracking="stopTracking"
      @updateProgress="updateProgress"
      @updateContentState="updateContentState"
    />

    <AssessmentWrapper
      v-else
      :id="content.id"
      class="content-renderer"
      :kind="content.kind"
      :files="content.files"
      :lang="content.lang"
      :randomize="content.randomize"
      :masteryModel="content.masteryModel"
      :assessmentIds="content.assessmentIds"
      :contentId="contentId"
      :channelId="channelId"
      :available="content.available"
      :extraFields="extraFields"
      :initSession="initSession"
      @sessionInitialized="setWasIncomplete"
      @startTracking="startTracking"
      @stopTracking="stopTracking"
      @updateProgress="updateExerciseProgress"
      @updateContentState="updateContentState"
    />

        {{ $tr('author', {author: content.author}) }}

        {{ $tr('license', {license: content.license_name}) }}

        <template v-if="content.license_description">
          <UiIconButton
            :ariaLabel="$tr('toggleLicenseDescription')"
            size="small"
            type="secondary"
            @click="licenceDescriptionIsVisible = !licenceDescriptionIsVisible"
          >
            <mat-svg v-if="licenceDescriptionIsVisible" name="expand_less" category="navigation" />
            <mat-svg v-else name="expand_more" category="navigation" />
          </UiIconButton>

            <template v-if="translatedLicense">

{{ licenseName }}

{{ licenseDescription }}

            </template>

{{ content.license_description }}

        </template>

        {{ $tr('copyrightHolder', {copyrightHolder: content.license_owner}) }}

    <DownloadButton
      v-if="canDownload"
      :files="downloadableFiles"
      class="download-button"
    />

    <slot name="below_content">
      <template v-if="progress >= 1 && content.next_content">

{{ $tr('nextResource') }}

        <ContentCardGroupCarousel
          :genContentLink="genContentLink"
          :contents="[content.next_content]"
        />
      </template>
      <template v-if="showRecommended">

{{ $tr('recommended') }}

        <ContentCardGroupCarousel
          :genContentLink="genContentLink"
          :header="recommendedText"
          :contents="recommended"
        />
      </template>
    </slot>

    <MasteredSnackbars
      v-if="progress >= 1 && wasIncomplete"
      :nextContent="content.next_content"
      :nextContentLink="nextContentLink"
      @close="markAsComplete"
    />

</template>

<template>

    <PageHeader
      :title="content.title"
      :progress="progress"
      dir="auto"
    />
    <CoachContentLabel
      class="coach-content-label"
      :value="content.coach_content ? 1 : 0"
      :isTopic="isTopic"
    />

    <ContentRenderer
      v-if="!content.assessment"
      :id="content.id"
      class="content-renderer"
      :kind="content.kind"
      :lang="content.lang"
      :files="content.files"
      :contentId="contentId"
      :channelId="channelId"
      :available="content.available"
      :extraFields="extraFields"
      :initSession="initSession"
      @sessionInitialized="setWasIncomplete"
      @startTracking="startTracking"
      @stopTracking="stopTracking"
      @updateProgress="updateProgress"
      @updateContentState="updateContentState"
    />

    <AssessmentWrapper
      v-else
      :id="content.id"
      class="content-renderer"
      :kind="content.kind"
      :files="content.files"
      :lang="content.lang"
      :randomize="content.randomize"
      :masteryModel="content.masteryModel"
      :assessmentIds="content.assessmentIds"
      :contentId="contentId"
      :channelId="channelId"
      :available="content.available"
      :extraFields="extraFields"
      :initSession="initSession"
      @sessionInitialized="setWasIncomplete"
      @startTracking="startTracking"
      @stopTracking="stopTracking"
      @updateProgress="updateExerciseProgress"
      @updateContentState="updateContentState"
    />

        {{ $tr('author', {author: content.author}) }}

        {{ $tr('license', {license: content.license_name}) }}

        <template v-if="content.license_description">
          <UiIconButton
            :ariaLabel="$tr('toggleLicenseDescription')"
            size="small"
            type="secondary"
            @click="licenceDescriptionIsVisible = !licenceDescriptionIsVisible"
          >
            <mat-svg v-if="licenceDescriptionIsVisible" name="expand_less" category="navigation" />
            <mat-svg v-else name="expand_more" category="navigation" />
          </UiIconButton>

            <template v-if="translatedLicense">

{{ licenseName }}

{{ licenseDescription }}

            </template>

{{ content.license_description }}

        </template>

        {{ $tr('copyrightHolder', {copyrightHolder: content.license_owner}) }}

    <DownloadButton
      v-if="canDownload"
      :files="downloadableFiles"
      class="download-button"
    />

    <slot name="below_content">
      <template v-if="progress >= 1 && content.next_content">

{{ $tr('nextResource') }}

        <ContentCardGroupCarousel
          :genContentLink="genContentLink"
          :contents="[content.next_content]"
        />
      </template>
      <template v-if="showRecommended">

{{ $tr('recommended') }}

        <ContentCardGroupCarousel
          :genContentLink="genContentLink"
          :header="recommendedText"
          :contents="recommended"
        />
      </template>
    </slot>

    <MasteredSnackbars
      v-if="progress >= 1 && wasIncomplete"
      :nextContent="content.next_content"
      :nextContentLink="nextContentLink"
      @close="markAsComplete"
    />

</template>


Added unique validation to client.name

Added unique validation to client.name

<template>

        {{ trans('client.create') }}

        <loading></loading>

            <form v-form name="myform" class="form-horizontal" role="form" v-on:submit.prevent="attemptCreate">

Name is required.

MAC Address is required.

MAC Address is not valid.

Screengroup is required.

                    {{ trans('general.name') }}

                        <input class="form-control"
                               id="name"
                               name="name"
                               type="text"
                               v-model="client.name"
                               v-form-ctrl
                               v-is-unique:client
                               required
                        >

                    {{ trans('general.mac_address') }}

                        <input class="form-control"
                               id="address"
                               name="address"
                               type="text"
                               placeholder="hh:hh:hh:hh:hh:hh"
                               v-model="client.address"
                               v-form-ctrl
                               custom-validator="isMacAddress"
                               v-is-unique:client
                               required
                        >

                        <model-selector :selected.sync="client.screen_group_id"
                                        model="screengroup"
                                        classes="model_input"
                        >

                                {{ trans('screengroup.model') }}

                    </model-selector>

                        <button type="" class="btn" v-link="{ path: '/clients/' }" v-if="myform.$pristine">
                            {{ trans('general.back') }}
                        </button>
                        <button type="" class="btn" v-link="{ path: '/clients/' }" v-if="!myform.$pristine">
                            {{ trans('general.cancel') }}
                        </button>
                        <button type="submit" class="btn btn-primary" :disabled="myform.$invalid">
                            {{ trans('general.save') }}
                        </button>

            </form>

</template>

<template>

        {{ trans('client.create') }}

        <loading></loading>

            <form v-form name="myform" class="form-horizontal" role="form" v-on:submit.prevent="attemptCreate">

Name is required.

MAC Address is required.

MAC Address is not valid.

Screengroup is required.

                    {{ trans('general.name') }}

                        <input class="form-control"
                               id="name"
                               name="name"
                               type="text"
                               v-model="client.name"
                               v-form-ctrl
                               required
                        >

                    {{ trans('general.mac_address') }}

                        <input class="form-control"
                               id="address"
                               name="address"
                               type="text"
                               placeholder="hh:hh:hh:hh:hh:hh"
                               v-model="client.address"
                               v-form-ctrl
                               required
                               custom-validator="isMacAddress"
                               v-is-unique:client
                        >

                        <model-selector :selected.sync="client.screen_group_id"
                                        model="screengroup"
                                        classes="model_input"
                        >

                                {{ trans('screengroup.model') }}

                    </model-selector>

                        <button type="" class="btn" v-link="{ path: '/clients/' }" v-if="myform.$pristine">
                            {{ trans('general.back') }}
                        </button>
                        <button type="" class="btn" v-link="{ path: '/clients/' }" v-if="!myform.$pristine">
                            {{ trans('general.cancel') }}
                        </button>
                        <button type="submit" class="btn btn-primary" :disabled="myform.$invalid">
                            {{ trans('general.save') }}
                        </button>

            </form>

</template>


fixed accesibility for iframe

fixed accesibility for iframe

<template>

    <iframe rel="dns-prefetch" :src="this.url" title="cowork360" height="500" frameborder="0" style="border:0" allowfullscreen></iframe>

</template>

<script>
    export default {
        name: 'ImageFrame',
        props: ['url'],
    }
</script>

<style scoped>

  @media only screen and (max-width: 768px) {
    iframe {
      width: 85vw;
    }
  }

  iframe {
    width: 100%;
  }

</style>

<template>

    <iframe rel="dns-prefetch" :src="this.url" height="500" frameborder="0" style="border:0" allowfullscreen></iframe>

</template>

<script>
    export default {
        name: 'ImageFrame',
        props: ['url'],
    }
</script>

<style scoped>

  @media only screen and (max-width: 768px) {
    iframe {
      width: 85vw;
    }
  }

  iframe {
    width: 100%;
  }

</style>


Update course offerings subtext style (#6087)

<template>

    <modal-timetap-schedule
        v-if="type !== 'parents'"
        :show="showTimetapModal"
        :class-type="timetapModalClassType"
        @close="showTimetapModal = false"
        @booked="onClassBooked"
    />
    <modal-timetap-confirmation
        v-if="type === 'thank-you'"
        :show="showTimetapConfirmationModal"
        @close="showTimetapConfirmationModal = false"
    />

    <page-parents-jumbotron :type="type" @cta-clicked="onScheduleAFreeClass"/>

                  CodeCombat combines the power of gameplay and personalized instruction to deliver online coding classes that your child will look forward to every week!

Remote Learning That Works

              Invest in Your Child’s Future While They Have Fun!

Personalized Learning

Our dedicated teachers offer individualized attention through a structured curriculum.  Your child will become a confident life-long coder as they conquer challenges and celebrate successes together!

The Power of Play

Our students type real Python and JavaScript while playing a game from beginning to end! Our engaging curriculum ensures that you’ll never have to force your child to show up to class. We promise.

Early Exposure to Coding

Coding is a highly sought-after job skill, and this demand will only grow. Exposing your child to coding at an early age increases the chance that they’ll pursue Computer Science later in life.

    <button-schedule-free-class @click="onScheduleAFreeClass" />

Even though the class starts every Saturday at 7am, my son is so excited that he wakes up before me! CodeCombat creates a pathway for my son to advance his coding skills.

Latthaphon Pohpon, Parent

Course Offerings

              With individual or small group class options and flexible scheduling available, this is the easiest way to get started in computer science.

Most popular

Best Value

Self-Paced

Small-Group

Private

Private

Subscription plan

$99 per year per student

$159 per month per student

$219 per month per student

$399 per month per student

60-minute sessions via Zoom

N/A

1 Group session per week

1 Private session per week

2 Private sessions per week

Student to Instructor Ratio

N/A

4:1*

1:1

1:1

CodeCombat premium license for the duration of online classes

<icon-gem />

<icon-gem />

<icon-gem />

<icon-gem />

Immediate and personalized feedback

<icon-gem />

<icon-gem />

<icon-gem />

Monthly progress updates from instructor

<icon-gem />

<icon-gem />

<icon-gem />

Weekly bonus activities, rewards, and course completion certificates

<icon-gem />

<icon-gem />

<icon-gem />

End of course projects that demonstrate concepts learned

<icon-gem />

<icon-gem />

<icon-gem />

Flexible schedule adapted to each student's learning needs

<icon-gem />

<icon-gem />

One-on-one engagement to help students stay motivated through difficult concepts

<icon-gem />

<icon-gem />

Automatic qualification into CodeCombat's AI League along with coding tournaments

<icon-gem />

*Maximum 4 students per small group session.

All subscriptions are billed on a monthly basis and may be paused, cancelled, or resumed with no fees.

    <button-schedule-free-class @click="onScheduleAFreeClass" />
    <page-parents-section-premium />

Our Curriculum

              Trusted by over 140,000 educators worldwide, our scaffolded curriculum is critical to helping students persevere. Our students type real code from the start, become more confident with each class, and learn how Computer Science is relevant to their daily lives.

Student Outcomes

Grit

CodeCombat challenges students to embrace and learn from their mistakes. Built-in hints help students identify and fix bugs, leading to greater coding confidence.

Problem Solving Skills

Through an iterative problem solving process, our students solve coding challenges and flex their creativity to build their own games, websites, and apps for end-of-course projects.

Technological Literacy

Students gain a deep understanding of Computer Science and technological literacy skills that will transfer to other disciplines as well as future careers.

            <button-arrow
                :point-left="true"
                @click="onCarouselLeft"
            />

 onCarouselDirectMove(0)">

 onCarouselDirectMove(1)">

 onCarouselDirectMove(2)">

            <button-arrow
                @click="onCarouselRight"
            />

Concepts Covered

Beginner

For students with little to no text-based coding experience.

Sequences

Algorithms

Syntax

Strings

Arguments

Loops

Variables

Cartesian coordinates

Intermediate

For students with some text-based coding experience.

Boolean logic

Properties

Advanced loops

Conditionals

Nested conditionals

Functions

Events

Basic game AI

Advanced

For students with substantial experience in text-based code and a good grasp of complex concepts.

Advanced strings

Computer arithmetic

Functions with returns

Complex conditionals

Arrays

Break/continue statements

Recursion

Stacks and Queues

    <button-schedule-free-class @click="onScheduleAFreeClass" />

FAQs

              How are instructors matched with my child?

              After the initial trial class, our team carefully matches our online instructors to each student based on their experience level, personality, interests, and schedule. Our team will work with you to improve your experience if you or your child doesn’t love learning with your instructor.

              What if I need to reschedule a class?

              We understand, and are happy to find a new date and time that works for you. Simply let your instructor or
              Education Advisor know you need to make a change. We greatly appreciate 24 hours notice for any scheduling changes.

              What kind of technology do I need to join the class?

              Any internet-connected device with a full keyboard. Laptops, PCs, and Chromebooks are recommended, however iPads will work with an external keyboard.

              What is the cancellation policy?

              You can freeze your subscription at any time and re-activate when it works for you. If you want to cancel, we hate to see you go but will process your cancellation within 24 hours.

              Are there any student age limits?

              We recommend our online classes for students aged 7 through 16. However, if your child can type, use a mouse, and is comfortable using a computer, they are welcome to join our class. If you are an adult, we simply ask you to join our private classes to ensure you get the most out of your experience.

              Can I choose my child’s courses? I know where I want my child to start.

              Yes! When enrolling in classes, you can choose whether your child should be in the beginner, intermediate, or advanced class. If you’re not sure where to start, we recommend starting with the beginner course so that your child can master fundamental concepts before advancing to more difficult courses. After the first class, the instructor will confirm whether your child is in the appropriate course.

            If you have any other questions about our online classes, please contact us.

</template>