WIP Adding support for Text entry measurements (renamed from List).

This commit is contained in:
Jonathan Bernard 2020-03-13 23:09:01 -05:00
parent 53a11b9e57
commit 3dd7169b8b
21 changed files with 179 additions and 44 deletions

View File

@ -7,14 +7,14 @@
name=measureType
v-model=value.type>
<option value=simple>Simple</option>
<option value=list>List</option>
<option value=text>Text</option>
</select>
</div>
<div>
<label for=measureIsVisible>Show by default.</label>
<input type=checkbox v-model=value.isVisible :disabled=disabled />
</div>
<!--<ListMeasureConfigForm :config=config v-show="config.type === 'list'"/>-->
<TextMeasureConfigForm v-model=value v-show="value.type === 'text'" :disabled=disabled />
</fieldset>
</template>
<script lang=ts src=./measure-config-form.ts></script>

View File

@ -0,0 +1,10 @@
<template>
<div>
<label for=textEntryShowTimestamp>Show Timestamps.</label>
<input name=textEntryShowTimestamp
:disabled=disabled
type=checkbox
v-model=value.showTimestamp />
</div>
</template>
<script lang=ts src=./text-measure-config-form.ts></script>

View File

@ -1,8 +1,13 @@
import { Component, Emit, Prop, Vue, Watch } from 'vue-property-decorator';
import { logService } from '@/services/logging';
import { Measure, MeasureConfig } from '@/models';
import TextMeasureConfigForm from './TextMeasureConfigForm.vue';
@Component({})
@Component({
components: {
TextMeasureConfigForm
}
})
export class MeasureConfigForm extends Vue {
@Prop({}) public value!: MeasureConfig;
@Prop({}) public disabled: boolean = false;

View File

@ -0,0 +1,17 @@
import { Component, Emit, Prop, Vue, Watch } from 'vue-property-decorator';
import { logService } from '@/services/logging';
import { Measure, MeasureConfig, TextMeasureConfig } from '@/models';
@Component({})
export class TextMeasureConfigForm extends Vue {
@Prop({}) public value!: MeasureConfig;
@Prop({}) public disabled: boolean = false;
@Watch('value', { immediate: true, deep: true })
@Emit('input')
private onConfigChanged(newVal: TextMeasureConfig, oldVal: TextMeasureConfig) {
return newVal;
}
}
export default TextMeasureConfigForm;

View File

@ -1,6 +0,0 @@
<template>
<ul>
<li v-for="m in top5">{{m.extData.entry}}</li>
</ul>
</template>
<script lang="ts" src="./list-summary.ts"></script>

View File

@ -5,7 +5,7 @@
{{measure.name}}</router-link></h2>
<SimpleSummaryGraph v-if="measure.config.type === 'simple'"
:measure=measure :measurements=measurements />
<ListSummary v-if="measure.config.type === 'list'"
<TextSummary v-if="measure.config.type === 'text'"
:measure=measure :measurements=measurements />
</div>
</template>

View File

@ -0,0 +1,13 @@
<template>
<ul>
<li
v-for="m in top5"
v-bind:class="{ 'show-timestamp': measure.config.showTimestamp,
'full-timestamp': !withinLastYear }">
<span class=timestamp>{{formatDate(m.timestamp)}}</span>
<span class=entry>{{m.extData.entry}}</span>
</li>
</ul>
</template>
<script lang="ts" src="./text-summary.ts"></script>
<style scoped lang="scss" src="./text-summary.scss"></script>

View File

@ -1,16 +0,0 @@
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Measure, ListMeasureConfig, Measurement, ListMeasurementMeta } from '@/models';
@Component
export class ListSummary extends Vue {
@Prop() private measure!: Measure<ListMeasureConfig>;
@Prop() private measurements!: Array<Measurement<ListMeasurementMeta>>;
private top5(): Array<Measurement<ListMeasurementMeta>> {
return this.measurements
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
.slice(0, 5);
}
}
export default ListSummary;

View File

@ -1,12 +1,12 @@
import { Component, Prop, Vue } from 'vue-property-decorator';
import { Measure, MeasureConfig, MeasureType, Measurement, MeasurementMeta } from '@/models';
import { measurementStore } from '@/store';
import ListSummary from './ListSummary.vue';
import TextSummary from './TextSummary.vue';
import SimpleSummaryGraph from './SimpleSummaryGraph.vue';
@Component({
components: {
ListSummary,
TextSummary,
SimpleSummaryGraph
}
})

View File

@ -0,0 +1,39 @@
@import '~@/styles/vars';
ul {
list-style: none;
padding: 0.5rem 0;
li {
span {
display: inline-block;
vertical-align: bottom;
&.timestamp {
color: $color2;
font-weight: bold;
}
&.entry {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&:not(.show-timestamp) {
span.timestamp { display: none; }
span.entry { width: 100%; }
}
&.show-timestamp {
span.timestamp { width: 5rem; }
span.entry { width: calc(100% - 5rem); }
}
&.show-timestamp.full-timestamp {
span.timestamp { width: 6rem; }
span.entry { width: calc(100% - 6rem); }
}
}
}

View File

@ -0,0 +1,33 @@
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import moment from 'moment';
import { Measure, TextMeasureConfig, Measurement, TextMeasurementMeta } from '@/models';
import { byTimestampComparator } from '@/util';
const YEAR_START = moment().startOf('year');
@Component
export class TextSummary extends Vue {
@Prop() private measure!: Measure<TextMeasureConfig>;
@Prop() private measurements!: Array<Measurement<TextMeasurementMeta>>;
private top5: Array<Measurement<TextMeasurementMeta>> = [];
private withinLastYear: boolean = true;
@Watch('measurements')
private onMeasurementsChanged() {
this.top5 = this.measurements
.slice(0)
.sort(byTimestampComparator)
.slice(0, 5);
this.withinLastYear = this.top5.every((entry) => YEAR_START.isBefore(entry.timestamp));
}
private formatDate(ts: Date) {
if (this.withinLastYear) { return moment(ts).format('MMM. Do'); }
else { return moment(ts).format('YYYY-MM-DD'); }
}
}
export default TextSummary;

View File

@ -2,6 +2,8 @@
<div>
<SimpleEntry v-if="measure.config.type === 'simple'"
:measure=measure v-model=value />
<TextEntry v-if="measure.config.type === 'text'"
:measure=measure v-model=value />
</div>
</template>
<script lang="ts" src="./measurement-entry.ts"></script>

View File

@ -2,7 +2,9 @@
<fieldset>
<div>
<label for=timestamp>Timestamp</label>
<input type=datetime-local
<input
name=timestamp
type=datetime-local
v-model=value.timestamp
v-show=editTimestamp
:disabled=disabled />

View File

@ -0,0 +1,26 @@
<template>
<fieldset>
<div>
<label for=timestamp>Timestamp</label>
<input
name=timestamp
type=datetime-local
v-model=value.timestamp
v-show=editTimestamp
:disabled=disabled />
<span v-show="!editTimestamp">
now <a href="#" v-on:click.stop.prevent="editTimestamp = true"> (set a time)</a>
</span>
</div>
<div>
<label for=measurementEntry>{{measure.name}}</label>
<input
name=measurementEntry
required
type=text
v-model=value.extData.entry
:disabled=disabled />
</div>
</fieldset>
</template>
<script lang="ts" src="./text-entry.ts"></script>

View File

@ -1,9 +1,13 @@
import { Component, Emit, Prop, Vue, Watch } from 'vue-property-decorator';
import { Measure, MeasureConfig, MeasureType, Measurement, MeasurementMeta } from '@/models';
import SimpleEntry from './SimpleEntry.vue';
import TextEntry from './TextEntry.vue';
@Component({
components: { SimpleEntry }
components: {
SimpleEntry,
TextEntry
}
})
export class MeasurementEntry extends Vue {
@Prop() private measure!: Measure<MeasureConfig>;

View File

@ -11,8 +11,6 @@ export class SimpleEntry extends Vue {
@Watch('value', { immediate: true, deep: true })
@Emit('input')
private onMeasurementChanged(newVal: Measurement<MeasurementMeta>, oldVal: Measurement<MeasurementMeta>) {
newVal.extData.measureType = 'simple' as MeasureType;
if (typeof(newVal.value) === 'string' ) {
newVal.value = parseInt(newVal.value, 10);
}

View File

@ -0,0 +1,13 @@
import { Component, Emit, Prop, Vue, Watch } from 'vue-property-decorator';
import { Measure, MeasureConfig, MeasureType, Measurement, MeasurementMeta } from '@/models';
@Component({})
export class TextEntry extends Vue {
@Prop() public measure!: Measure<MeasureConfig>;
@Prop() public value!: Measurement<MeasurementMeta>;
@Prop() public disabled!: boolean;
private editTimestamp: boolean = false;
}
export default TextEntry;

7
web/src/models.d.ts vendored
View File

@ -1,4 +1,4 @@
export enum MeasureType { List = 'list', Simple = 'simple' }
export enum MeasureType { Text = 'text', Simple = 'simple' }
export interface ApiToken {
id: string;
@ -19,7 +19,7 @@ export interface MeasureConfig {
isVisible: boolean;
}
export interface ListMeasureConfig extends MeasureConfig {
export interface TextMeasureConfig extends MeasureConfig {
showTimestamp: boolean;
}
@ -33,10 +33,9 @@ export interface Measure<C extends MeasureConfig> {
}
export interface MeasurementMeta {
measureType: MeasureType;
}
export interface ListMeasurementMeta extends MeasurementMeta {
export interface TextMeasurementMeta extends MeasurementMeta {
entry: string;
}

View File

@ -6,6 +6,7 @@ import {
MutationAction,
VuexModule
} from 'vuex-module-decorators';
import assign from 'lodash.assign';
import keyBy from 'lodash.keyby';
import { User, Measure, MeasureConfig } from '@/models';
import api from '@/services/pm-api-client';
@ -35,6 +36,6 @@ export class MeasureStoreModule extends VuexModule {
}
@Mutation private SET_MEASURE<T extends MeasureConfig>(measure: Measure<T>) {
this.measures[measure.slug] = measure;
this.measures = assign({}, this.measures, {[measure.slug]: measure});
}
}

View File

@ -12,12 +12,9 @@
<div class=measure-list>
<MeasureSummary
v-for="(measure, slug) in measures"
v-bind:key="measure.id"
v-show="measure.slug.startsWith(filter)"
:measure=measure />
<!--<MeasureSummary
v-for="(measure, slug) in measures"
:key="slug"
:measure=measure />-->
</div>
</div>
</template>

View File

@ -22,9 +22,7 @@ export class NewMeasurement extends Vue {
measureId: '',
value: 0,
timestamp: new Date(),
extData: {
measureType: 'simple' as MeasureType
}
extData: { }
};
private async mounted() {