mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 11:17:08 +00:00
Add planner frontend component
This commit is contained in:
337
lib/www/client/source/src/views/Plan.vue
Normal file
337
lib/www/client/source/src/views/Plan.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>Plan</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="filter"
|
||||
append-icon="mdi-magnify"
|
||||
label="Filter"
|
||||
single-line
|
||||
clearable
|
||||
></v-text-field>
|
||||
</v-toolbar>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
|
||||
<v-menu
|
||||
v-model="contextMenuShow"
|
||||
:position-x="contextMenuX"
|
||||
:position-y="contextMenuY"
|
||||
absolute
|
||||
offset-y
|
||||
>
|
||||
<v-list dense v-if="contextMenuItem">
|
||||
<v-list-item @click="deletePlannedSequence">
|
||||
<v-list-item-icon><v-icon>mdi-delete</v-icon></v-list-item-icon>
|
||||
<v-list-item-title class="warning--text">Delete planned sequence</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:search="filter"
|
||||
:loading="loading"
|
||||
:fixed-header="true"
|
||||
:item-class="(item) => (activeItem == item && !edit) ? 'blue accent-1 elevation-3' : ''"
|
||||
@click:row="setActiveItem"
|
||||
@contextmenu:row="contextMenu"
|
||||
>
|
||||
|
||||
<template v-slot:item.ts0="{value}">
|
||||
<span>{{ value.slice(0, 16) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.ts1="{value}">
|
||||
<span>{{ value.slice(0, 16) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.length="props">
|
||||
<span>{{ Math.round(props.value) }} m</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.azimuth="props">
|
||||
<span>{{ props.value.toFixed(1) }} °</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.remarks="{item}">
|
||||
<v-text-field v-if="edit && edit.line == item.line && edit.key == 'remarks'"
|
||||
type="text"
|
||||
v-model="edit.value"
|
||||
prepend-icon="mdi-restore"
|
||||
append-outer-icon="mdi-content-save-edit-outline"
|
||||
clearable
|
||||
@click:prepend="edit.value = item.remarks; edit = null"
|
||||
@click:append-outer="edit = null"
|
||||
>
|
||||
</v-text-field>
|
||||
<div v-else>
|
||||
{{item.remarks}}
|
||||
<v-btn v-if="edit === null"
|
||||
icon
|
||||
small
|
||||
title="Edit"
|
||||
:disabled="loading"
|
||||
@click="editItem(item, 'remarks')"
|
||||
>
|
||||
<v-icon small>mdi-square-edit-outline</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-slot:item.lag="{item}">
|
||||
<span>{{ Math.round(lagAfter(item) / (60*1000)) }} min</span>
|
||||
</template>
|
||||
|
||||
</v-data-table>
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "LineList",
|
||||
|
||||
components: {
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
value: "sequence",
|
||||
text: "Sequence"
|
||||
},
|
||||
{
|
||||
value: "name",
|
||||
text: "Name"
|
||||
},
|
||||
{
|
||||
value: "line",
|
||||
text: "Line"
|
||||
},
|
||||
{
|
||||
value: "fsp",
|
||||
text: "FSP",
|
||||
align: "end"
|
||||
},
|
||||
{
|
||||
value: "lsp",
|
||||
text: "LSP",
|
||||
align: "end"
|
||||
},
|
||||
{
|
||||
value: "ts0",
|
||||
text: "Start"
|
||||
},
|
||||
{
|
||||
value: "ts1",
|
||||
text: "End"
|
||||
},
|
||||
{
|
||||
value: "num_points",
|
||||
text: "Num. points",
|
||||
align: "end"
|
||||
},
|
||||
{
|
||||
value: "length",
|
||||
text: "Length",
|
||||
align: "end"
|
||||
},
|
||||
{
|
||||
value: "azimuth",
|
||||
text: "Azimuth",
|
||||
align: "end"
|
||||
},
|
||||
{
|
||||
value: "remarks",
|
||||
text: "Remarks"
|
||||
},
|
||||
{
|
||||
text: "Line change after",
|
||||
value: "lag",
|
||||
sortable: false
|
||||
}
|
||||
],
|
||||
items: [],
|
||||
filter: null,
|
||||
num_lines: null,
|
||||
activeItem: null,
|
||||
edit: null, // {line, key, value}
|
||||
queuedReload: false,
|
||||
|
||||
plannerConfig: null,
|
||||
|
||||
// Context menu stuff
|
||||
contextMenuShow: false,
|
||||
contextMenuX: 0,
|
||||
contextMenuY: 0,
|
||||
contextMenuItem: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['loading', 'serverEvent'])
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
||||
async edit (newVal, oldVal) {
|
||||
if (newVal === null && oldVal !== null) {
|
||||
const item = this.items.find(i => i.line == oldVal.line);
|
||||
|
||||
// Get around this Vuetify ‘feature’
|
||||
// https://github.com/vuetifyjs/vuetify/issues/4144
|
||||
if (oldVal.value === null) oldVal.value = "";
|
||||
|
||||
if (item && item[oldVal.key] != oldVal.value) {
|
||||
if (await this.saveItem(oldVal)) {
|
||||
item[oldVal.key] = oldVal.value;
|
||||
} else {
|
||||
this.edit = oldVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async serverEvent (event) {
|
||||
if (event.channel == "preplot_lines" && event.payload.pid == this.$route.params.project) {
|
||||
if (!this.loading && !this.queuedReload) {
|
||||
// Do not force a non-cached response if refreshing as a result
|
||||
// of an event notification. We will assume that the server has
|
||||
// already had time to update the cache by the time our request
|
||||
// gets back to it.
|
||||
this.getPlannedLines();
|
||||
} else {
|
||||
this.queuedReload = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
queuedReload (newVal, oldVal) {
|
||||
if (newVal && !oldVal && !this.loading) {
|
||||
this.getPlannedLines();
|
||||
}
|
||||
},
|
||||
|
||||
loading (newVal, oldVal) {
|
||||
if (!newVal && oldVal && this.queuedReload) {
|
||||
this.getPlannedLines();
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
lagAfter (item) {
|
||||
const pos = this.items.indexOf(item);
|
||||
if (pos != -1) {
|
||||
const nextItem = this.items[pos+1];
|
||||
if (nextItem) {
|
||||
return new Date(nextItem.ts0) - new Date(item.ts1);
|
||||
}
|
||||
} else {
|
||||
console.warn("Item not found in list", item);
|
||||
}
|
||||
return this.plannerConfig.defaultLineChangeDuration * 60*1000;
|
||||
},
|
||||
|
||||
contextMenu (e, {item}) {
|
||||
e.preventDefault();
|
||||
this.contextMenuShow = false;
|
||||
this.contextMenuX = e.clientX;
|
||||
this.contextMenuY = e.clientY;
|
||||
this.contextMenuItem = item;
|
||||
this.$nextTick( () => this.contextMenuShow = true );
|
||||
},
|
||||
|
||||
async deletePlannedSequence () {
|
||||
console.log("Delete sequence", this.contextMenuItem.sequence);
|
||||
const url = `/project/${this.$route.params.project}/plan/${this.contextMenuItem.sequence}`;
|
||||
const init = {method: "DELETE"};
|
||||
await this.api([url, init]);
|
||||
await this.getPlannedLines();
|
||||
},
|
||||
|
||||
editItem (item, key) {
|
||||
this.edit = {
|
||||
line: item.line,
|
||||
key,
|
||||
value: item[key]
|
||||
}
|
||||
},
|
||||
|
||||
async saveItem (edit) {
|
||||
if (!edit) return;
|
||||
|
||||
try {
|
||||
const url = `/project/${this.$route.params.project}/plan/${edit.line}`;
|
||||
const init = {
|
||||
method: "PATCH",
|
||||
body: {
|
||||
[edit.key]: edit.value
|
||||
}
|
||||
};
|
||||
|
||||
let res;
|
||||
await this.api([url, init, (e, r) => res = r]);
|
||||
return res && res.ok;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async getPlannedLines () {
|
||||
|
||||
const url = `/project/${this.$route.params.project}/plan`;
|
||||
|
||||
this.queuedReload = false;
|
||||
this.items = await this.api([url]) || [];
|
||||
|
||||
},
|
||||
|
||||
async getPlannerConfig () {
|
||||
const url = `/project/${this.$route.params.project}/configuration/planner`;
|
||||
this.plannerConfig = await this.api([url]) || {
|
||||
"overlapAfter": 0,
|
||||
"overlapBefore": 0,
|
||||
"defaultAcquisitionSpeed": 5,
|
||||
"defaultLineChangeDuration": 36
|
||||
}
|
||||
},
|
||||
|
||||
async getSequences () {
|
||||
const url = `/project/${this.$route.params.project}/sequence`;
|
||||
this.sequences = await this.api([url]) || [];
|
||||
},
|
||||
|
||||
setActiveItem (item) {
|
||||
this.activeItem = this.activeItem == item
|
||||
? null
|
||||
: item;
|
||||
},
|
||||
|
||||
...mapActions(["api"])
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.getPlannedLines();
|
||||
this.getPlannerConfig();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user