mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 11:57:08 +00:00
Add <dougal-event-edit-dialog/> component.
Used to enter new events.
This commit is contained in:
391
lib/www/client/source/src/components/event-edit-dialog.vue
Normal file
391
lib/www/client/source/src/components/event-edit-dialog.vue
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<v-dialog
|
||||||
|
v-model="show"
|
||||||
|
max-width="600px"
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ on, attrs }">
|
||||||
|
<v-btn
|
||||||
|
class="mx-2"
|
||||||
|
fab dark
|
||||||
|
x-small
|
||||||
|
color="primary"
|
||||||
|
title="Add event"
|
||||||
|
v-bind="attrs"
|
||||||
|
v-on="on"
|
||||||
|
>
|
||||||
|
<v-icon dark>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<span class="headline">{{ formTitle }}</span>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-container>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-textarea
|
||||||
|
v-model="remarks"
|
||||||
|
label="Description"
|
||||||
|
rows="1"
|
||||||
|
auto-grow
|
||||||
|
clearable
|
||||||
|
autofocus
|
||||||
|
filled
|
||||||
|
:hint="presetRemarks ? 'Enter your own comment or select a preset one from the menu on the left' : 'Enter a comment'"
|
||||||
|
@keyup="handleKeys"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend v-if="presetRemarks">
|
||||||
|
<v-icon
|
||||||
|
title="Select predefined comments"
|
||||||
|
color="primary"
|
||||||
|
@click="showRemarksMenu"
|
||||||
|
>
|
||||||
|
mdi-dots-vertical
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:prepend v-else>
|
||||||
|
<v-icon
|
||||||
|
color="disabled"
|
||||||
|
>
|
||||||
|
mdi-dots-vertical
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-textarea>
|
||||||
|
|
||||||
|
<dougal-context-menu
|
||||||
|
:value="remarksMenu"
|
||||||
|
@input="addRemark"
|
||||||
|
:items="presetRemarks"
|
||||||
|
absolute
|
||||||
|
></dougal-context-menu>
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row dense>
|
||||||
|
<v-col>
|
||||||
|
<v-autocomplete
|
||||||
|
ref="labels"
|
||||||
|
v-model="labels"
|
||||||
|
:items="Object.keys(allowedLabels)"
|
||||||
|
chips
|
||||||
|
deletable-chips
|
||||||
|
multiple
|
||||||
|
label="Labels"
|
||||||
|
@input="labelSearch=null; $refs.labels.isMenuActive=false"
|
||||||
|
:search-input.sync="labelSearch"
|
||||||
|
>
|
||||||
|
<template v-slot:selection="data">
|
||||||
|
<v-chip
|
||||||
|
v-bind="data.attrs"
|
||||||
|
:input-value="data.selected"
|
||||||
|
close
|
||||||
|
@click="data.select"
|
||||||
|
@click:close="remove(data.item)"
|
||||||
|
:color="allowedLabels[data.item].view.colour"
|
||||||
|
:title="allowedLabels[data.item].view.description"
|
||||||
|
>{{data.item}}</v-chip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:prepend v-if="presetLabels">
|
||||||
|
<v-icon
|
||||||
|
title="Select labels"
|
||||||
|
color="primary"
|
||||||
|
@click="showLabelsMenu"
|
||||||
|
>
|
||||||
|
mdi-dots-vertical
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:prepend v-else>
|
||||||
|
<v-icon
|
||||||
|
color="disabled"
|
||||||
|
>
|
||||||
|
mdi-dots-vertical
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</v-autocomplete>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row dense>
|
||||||
|
<v-col>
|
||||||
|
<v-switch label="Change time" v-model="timeInput" :disabled="shotInput"></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-switch label="Enter shotpoint" v-model="shotInput" :disabled="timeInput"></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row dense>
|
||||||
|
<v-col :style="{visibility: timeInput ? 'visible' : 'hidden'}">
|
||||||
|
<v-text-field v-model="tsTime" type="time" step="1" label="Time">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col :style="{visibility: timeInput ? 'visible' : 'hidden'}">
|
||||||
|
<v-text-field v-model="tsDate" type="date" label="Date">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col :style="{visibility: shotInput ? 'visible' : 'hidden'}">
|
||||||
|
<v-autocomplete
|
||||||
|
:items="sequenceList"
|
||||||
|
v-model="sequence"
|
||||||
|
label="Sequence"
|
||||||
|
></v-autocomplete>
|
||||||
|
</v-col>
|
||||||
|
<v-col :style="{visibility: shotInput ? 'visible' : 'hidden'}">
|
||||||
|
<v-text-field v-model="point" type="number" label="Shot">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue darken-1" text @click="close">Cancel</v-btn>
|
||||||
|
<v-btn color="blue darken-1" text @click="save" :disabled="!isValid">Save</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import DougalContextMenu from '@/components/context-menu';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DougalEventEditDialog',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
DougalContextMenu
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: Boolean,
|
||||||
|
allowedLabels: { type: Object, default: () => {} },
|
||||||
|
sequences: { type: Object, default: null },
|
||||||
|
defaultTimestamp: { type: [ Date, String, Number, Function ], default: null },
|
||||||
|
defaultSequence: { type: Number, default: null },
|
||||||
|
defaultShotNumber: { type: Number, default: null },
|
||||||
|
presetRemarks: { type: [ Object, Array ], default: null },
|
||||||
|
presetLabels: { type: [ Object, Array ], default: null }
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
const tsNow = new Date;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
tsDate: tsNow.toISOString().substring(0, 10),
|
||||||
|
tsTime: tsNow.toISOString().substring(11, 19),
|
||||||
|
sequenceData: null,
|
||||||
|
sequence: null,
|
||||||
|
point: null,
|
||||||
|
remarks: "",
|
||||||
|
labels: [],
|
||||||
|
labelSearch: null,
|
||||||
|
timer: null,
|
||||||
|
timeInput: false,
|
||||||
|
shotInput: false,
|
||||||
|
|
||||||
|
remarksMenu: false,
|
||||||
|
menuX: 0,
|
||||||
|
menuY: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
formTitle () {
|
||||||
|
if (this.timeInput) {
|
||||||
|
return "New event at time";
|
||||||
|
} else if (this.shotInput) {
|
||||||
|
return "New event at shotpoint";
|
||||||
|
} else if (this.defaultTimestamp) {
|
||||||
|
return "New event at " +
|
||||||
|
this.defaultTimestampAsDate.toISOString().replace(/(.{10})T(.{8}).{4}Z$/, "$1 $2");
|
||||||
|
} else if (this.defaultShotNumber) {
|
||||||
|
return "New event on shotpoint " + this.defaultShotNumber;
|
||||||
|
}
|
||||||
|
return "New event";
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultTimestampAsDate () {
|
||||||
|
if (this.defaultTimestamp instanceof Date) {
|
||||||
|
return this.defaultTimestamp;
|
||||||
|
} else if (typeof this.defaultTimestamp == 'string') {
|
||||||
|
return new Date(this.defaultTimestamp);
|
||||||
|
} else if (typeof this.defaultTimestamp == 'number') {
|
||||||
|
return new Date(this.defaultTimestamp);
|
||||||
|
} else if (typeof this.defaultTimestamp == 'function') {
|
||||||
|
return new Date(this.defaultTimestamp());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
tstamp () {
|
||||||
|
return this.timeInput
|
||||||
|
? new Date(this.tsDate+"T"+this.tsTime+"Z")
|
||||||
|
: this.defaultTimestampAsDate;
|
||||||
|
},
|
||||||
|
|
||||||
|
shot () {
|
||||||
|
return this.shotInput
|
||||||
|
? { sequence: this.sequence, point: Number(this.point) }
|
||||||
|
: { sequence: this.defaultSequence, point: this.defaultShotNumber };
|
||||||
|
},
|
||||||
|
|
||||||
|
isTimedEvent () {
|
||||||
|
return Boolean((this.timeInput && this.tstamp) ||
|
||||||
|
(this.defaultTimestampAsDate && !this.shotInput));
|
||||||
|
},
|
||||||
|
|
||||||
|
isShotEvent () {
|
||||||
|
return Boolean((this.shotInput && this.shot.sequence && this.shot.point) ||
|
||||||
|
(this.defaultSequence && this.defaultShotNumber && !this.timeInput));
|
||||||
|
},
|
||||||
|
|
||||||
|
isValid () {
|
||||||
|
if (this.isTimedEvent) {
|
||||||
|
return !isNaN(this.tstamp) &&
|
||||||
|
((this.remarks && this.remarks.trim()) || this.labels.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isShotEvent) {
|
||||||
|
return Number(this.sequence) && Number(this.point) &&
|
||||||
|
((this.remarks && this.remarks.trim()) || this.labels.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
sequenceList () {
|
||||||
|
const seq = this.sequences || this.sequenceData || [];
|
||||||
|
return seq.map(s => s.sequence).sort((a,b) => b-a);
|
||||||
|
},
|
||||||
|
|
||||||
|
eventData () {
|
||||||
|
if (!this.isValid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {}
|
||||||
|
|
||||||
|
data.remarks = this.remarks.trim();
|
||||||
|
if (this.labels) {
|
||||||
|
data.labels = this.labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isTimedEvent) {
|
||||||
|
data.tstamp = this.tstamp;
|
||||||
|
} else if (this.isShotEvent) {
|
||||||
|
data.sequence = this.shot.sequence;
|
||||||
|
data.point = this.shot.point;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
async show (value) {
|
||||||
|
this.$emit('input', value);
|
||||||
|
if (value) {
|
||||||
|
this.updateTimeFields();
|
||||||
|
await this.updateSequences();
|
||||||
|
this.sequence = this.defaultSequence;
|
||||||
|
this.point = this.defaultShotNumber;
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
value (v) {
|
||||||
|
if (v != this.show) {
|
||||||
|
this.show = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
clear () {
|
||||||
|
this.timeInput = false;
|
||||||
|
this.shotInput = false;
|
||||||
|
this.remarks = "";
|
||||||
|
this.labels = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
close () {
|
||||||
|
this.show = false;
|
||||||
|
this.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
save () {
|
||||||
|
this.$emit('save', this.eventData);
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
remove (item) {
|
||||||
|
this.labels.splice(this.labels.indexOf(item), 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTimeFields () {
|
||||||
|
const tsNow = new Date;
|
||||||
|
this.tsDate = tsNow.toISOString().substring(0, 10);
|
||||||
|
this.tsTime = tsNow.toISOString().substring(11, 19);
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateSequences () {
|
||||||
|
if (this.sequences == null) {
|
||||||
|
const url = `/project/${this.$route.params.project}/sequence`;
|
||||||
|
this.sequenceData = await this.api([url]) || null
|
||||||
|
}
|
||||||
|
this.sequence = this.sequenceList.reduce( (a, b) => Math.max(a, b) );
|
||||||
|
},
|
||||||
|
|
||||||
|
showRemarksMenu (e) {
|
||||||
|
this.remarksMenu = e;
|
||||||
|
},
|
||||||
|
|
||||||
|
addRemark ({text}) {
|
||||||
|
if (text) {
|
||||||
|
if (this.remarks === null) {
|
||||||
|
this.remarks = "";
|
||||||
|
}
|
||||||
|
if (this.remarks.length && this.remarks[this.remarks.length-1] != "\n") {
|
||||||
|
this.remarks += "\n";
|
||||||
|
}
|
||||||
|
this.remarks += text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
handleKeys (e) {
|
||||||
|
if (e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && e.keyCode == 13) {
|
||||||
|
// Ctrl+Enter
|
||||||
|
if (this.isValid) {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
...mapActions(["api"])
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user