2020-08-08 23:59:13 +02:00
|
|
|
|
<template>
|
2020-08-12 15:15:44 +02:00
|
|
|
|
<v-container fluid>
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
<v-card>
|
|
|
|
|
|
<v-card-title>
|
2020-08-12 15:15:44 +02:00
|
|
|
|
<v-toolbar flat>
|
|
|
|
|
|
<v-toolbar-title>
|
2021-05-12 20:35:01 +02:00
|
|
|
|
<span class="d-none d-lg-inline">
|
|
|
|
|
|
{{
|
|
|
|
|
|
$route.params.sequence
|
|
|
|
|
|
? ($route.params.sequence.includes && $route.params.sequence.includes(";"))
|
|
|
|
|
|
? `Sequences ${$route.params.sequence.split(";").sort().join(", ")}`
|
|
|
|
|
|
: `Sequence ${$route.params.sequence}`
|
|
|
|
|
|
: $route.params.date0
|
|
|
|
|
|
? $route.params.date1
|
|
|
|
|
|
? `Between ${$route.params.date0} and ${$route.params.date1}`
|
|
|
|
|
|
: `On ${$route.params.date0}`
|
|
|
|
|
|
: "All events"
|
|
|
|
|
|
}}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="d-lg-none">
|
|
|
|
|
|
{{
|
|
|
|
|
|
$route.params.sequence
|
|
|
|
|
|
? ($route.params.sequence.includes && $route.params.sequence.includes(";"))
|
|
|
|
|
|
? `${$route.params.sequence.split(";").sort().join(", ")}`
|
|
|
|
|
|
: `${$route.params.sequence}`
|
|
|
|
|
|
: $route.params.date0
|
|
|
|
|
|
? $route.params.date1
|
|
|
|
|
|
? `${$route.params.date0} ‒ ${$route.params.date1}`
|
|
|
|
|
|
: `${$route.params.date0}`
|
|
|
|
|
|
: ""
|
|
|
|
|
|
}}
|
|
|
|
|
|
</span>
|
2020-08-12 15:15:44 +02:00
|
|
|
|
</v-toolbar-title>
|
2020-08-22 20:43:23 +02:00
|
|
|
|
|
2022-02-27 19:06:36 +01:00
|
|
|
|
<dougal-event-edit v-if="writeaccess"
|
2020-08-22 20:43:23 +02:00
|
|
|
|
v-model="eventDialog"
|
2022-02-27 19:06:36 +01:00
|
|
|
|
v-bind="editedEvent"
|
|
|
|
|
|
:available-labels="userLabels"
|
2020-08-22 20:43:23 +02:00
|
|
|
|
:preset-remarks="presetRemarks"
|
2022-02-27 19:06:36 +01:00
|
|
|
|
@new="newEvent"
|
|
|
|
|
|
@changed="saveEvent"
|
|
|
|
|
|
>
|
|
|
|
|
|
</dougal-event-edit>
|
|
|
|
|
|
|
2022-02-27 19:36:28 +01:00
|
|
|
|
<dougal-event-edit-labels v-if="writeaccess"
|
|
|
|
|
|
v-model="eventLabelsDialog"
|
|
|
|
|
|
:labels="userLabels"
|
|
|
|
|
|
:selected="contextMenuItem ? contextMenuItem.labels||[] : []"
|
|
|
|
|
|
@selectionChanged="(data) => patchEvent(contextMenuItem.id, data)"
|
|
|
|
|
|
>
|
|
|
|
|
|
</dougal-event-edit-labels>
|
|
|
|
|
|
|
2021-05-11 23:57:53 +02:00
|
|
|
|
<v-menu v-if="$route.params.sequence">
|
|
|
|
|
|
<template v-slot:activator="{on, attrs}">
|
|
|
|
|
|
<v-btn class="ml-5" small v-on="on" v-bind="attrs">
|
2021-05-12 20:35:01 +02:00
|
|
|
|
<span class="d-none d-lg-inline">Download as…</span>
|
2021-05-11 23:57:53 +02:00
|
|
|
|
<v-icon right small>mdi-cloud-download</v-icon>
|
|
|
|
|
|
</v-btn>
|
|
|
|
|
|
</template>
|
2022-03-18 15:05:08 +01:00
|
|
|
|
|
2021-05-11 23:57:53 +02:00
|
|
|
|
<v-list>
|
|
|
|
|
|
<v-list-item
|
|
|
|
|
|
:href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fvnd.seis%2Bjson`"
|
|
|
|
|
|
title="Download as a Multiseis-compatible Seis+JSON file."
|
|
|
|
|
|
>Seis+JSON</v-list-item>
|
|
|
|
|
|
<v-list-item
|
|
|
|
|
|
:href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fgeo%2Bjson`"
|
|
|
|
|
|
title="Download as a QGIS-compatible GeoJSON file"
|
|
|
|
|
|
>GeoJSON</v-list-item>
|
|
|
|
|
|
<v-list-item
|
|
|
|
|
|
:href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fjson`"
|
|
|
|
|
|
title="Download as a generic JSON file"
|
|
|
|
|
|
>JSON</v-list-item>
|
|
|
|
|
|
<v-list-item
|
|
|
|
|
|
:href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=text%2Fhtml`"
|
|
|
|
|
|
title="Download as an HTML formatted file"
|
|
|
|
|
|
>HTML</v-list-item>
|
|
|
|
|
|
<v-list-item
|
|
|
|
|
|
:href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fpdf`"
|
|
|
|
|
|
title="Download as a Portable Document File"
|
|
|
|
|
|
>PDF</v-list-item>
|
|
|
|
|
|
</v-list>
|
|
|
|
|
|
</v-menu>
|
2020-08-22 20:43:23 +02:00
|
|
|
|
|
2020-08-12 15:15:44 +02:00
|
|
|
|
<v-spacer></v-spacer>
|
|
|
|
|
|
<v-text-field
|
|
|
|
|
|
v-model="filter"
|
|
|
|
|
|
append-icon="mdi-magnify"
|
2020-08-25 13:02:56 +02:00
|
|
|
|
label="Filter"
|
2020-08-12 15:15:44 +02:00
|
|
|
|
single-line
|
|
|
|
|
|
hide-details></v-text-field>
|
2020-08-22 20:43:23 +02:00
|
|
|
|
</v-toolbar>
|
|
|
|
|
|
</v-card-title>
|
|
|
|
|
|
<v-card-text>
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
<!-- BEGIN Context menu for log entries -->
|
|
|
|
|
|
<v-menu v-if="writeaccess"
|
|
|
|
|
|
v-model="contextMenuShow"
|
|
|
|
|
|
:position-x="contextMenuX"
|
|
|
|
|
|
:position-y="contextMenuY"
|
|
|
|
|
|
absolute
|
|
|
|
|
|
offset-y
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- Add comment: goes on every entry -->
|
|
|
|
|
|
<v-list dense v-if="contextMenuItem">
|
|
|
|
|
|
<v-list-item
|
|
|
|
|
|
@click="() => addEvent(contextMenuItem.items ? contextMenuItem.items[0] : contextMenuItem)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-pencil-plus</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>Add comment</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- BEGIN These options don't apply to read-only items -->
|
|
|
|
|
|
<template v-if="!isRowReadonly(contextMenuItem)">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- BEGIN These are whole row entries NOTE: Why? TODO Reconsider this -->
|
|
|
|
|
|
<template v-if="contextMenuItem.key">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- If the item has a shotpoint, that's what we edit -->
|
|
|
|
|
|
<!-- NOTE We may use this in the future to bulk edit comments
|
|
|
|
|
|
related to the same shot / moment, but not now.
|
|
|
|
|
|
<v-list-item @click="true" v-if="contextMenuItem.sequence">
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-pencil</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>Edit shotpoint</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Otherwise, it's the timestamp -->
|
|
|
|
|
|
<!-- NOTE We may use this in the future to bulk edit comments
|
|
|
|
|
|
related to the same shot / moment, but not now.
|
|
|
|
|
|
<v-list-item @click="true" v-else>
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-pencil</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>Edit timestamp</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
-->
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- END These are whole row entries -->
|
|
|
|
|
|
|
|
|
|
|
|
<!-- BEGIN These apply to individual comments -->
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Edit comment -->
|
|
|
|
|
|
<v-list-item @click="editEvent(contextMenuItem)" v-if="contextMenuItem.remarks">
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-pencil</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>Edit comment</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Edit labels -->
|
|
|
|
|
|
<!-- NOTE The edit comment dialogue takes care of this just fine, but let's
|
|
|
|
|
|
leave it for now and see if people use it. -->
|
|
|
|
|
|
<v-list-item @click="() => eventLabelsDialog=true" v-if="contextMenuItem.labels">
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-tag-multiple</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>Edit labels</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- END These apply to individual comments -->
|
|
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- END These options don't apply to read-only items -->
|
|
|
|
|
|
|
|
|
|
|
|
<!-- View item on map if it has a geometry -->
|
|
|
|
|
|
<v-list-item v-if="viewOnMap(contextMenuItem)" :href="viewOnMap(contextMenuItem)">
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-map</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>View on map</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
|
|
|
|
|
|
<v-divider></v-divider>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Delete command: single comment -->
|
|
|
|
|
|
<v-list-item @click="() => removeEvent(contextMenuItem)" v-if="deletableEntries(contextMenuItem) == 1">
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-delete</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title class="warning--text">Delete comment</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
<!-- Delete command: read-only item (no action) -->
|
|
|
|
|
|
<v-list-item v-else-if="deletableEntries(contextMenuItem) == 0" disabled>
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-delete-off</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>This entry is read-only</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
<!-- Delete command: multiple comments -->
|
|
|
|
|
|
<v-list-item @click="() => removeEvent(contextMenuItem)" v-else>
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-delete</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title class="error--text">Delete all comments</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
2022-05-04 19:45:20 +02:00
|
|
|
|
|
|
|
|
|
|
<!-- BEGIN This section only applies to QC events -->
|
|
|
|
|
|
<template v-if="contextMenuItem.meta.qc_id">
|
|
|
|
|
|
|
|
|
|
|
|
<v-divider></v-divider>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Mark QC accepted -->
|
|
|
|
|
|
<v-list-item @click="() => acceptQc(contextMenuItem)" v-if="!isAcceptedQc(contextMenuItem)">
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-check</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>Mark QC accepted</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
<!-- Unmark QC accepted -->
|
|
|
|
|
|
<v-list-item @click="() => acceptQc(contextMenuItem, false)" v-else>
|
|
|
|
|
|
<v-list-item-icon><v-icon>mdi-restore</v-icon></v-list-item-icon>
|
|
|
|
|
|
<v-list-item-title>Unmark QC accepted</v-list-item-title>
|
|
|
|
|
|
</v-list-item>
|
|
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- END This section only applies to QC events -->
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
</v-list>
|
|
|
|
|
|
|
|
|
|
|
|
</v-menu>
|
|
|
|
|
|
<!-- END Context menu for log entries -->
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
<v-data-table
|
2022-02-27 19:51:47 +01:00
|
|
|
|
dense
|
2020-08-22 20:43:23 +02:00
|
|
|
|
:headers="headers"
|
|
|
|
|
|
:items="rows"
|
2021-05-08 21:54:55 +02:00
|
|
|
|
:items-per-page.sync="itemsPerPage"
|
2022-02-27 19:28:15 +01:00
|
|
|
|
item-key="key"
|
2022-05-03 23:42:58 +02:00
|
|
|
|
:item-class="itemClass"
|
2020-08-22 20:43:23 +02:00
|
|
|
|
sort-by="tstamp"
|
|
|
|
|
|
:sort-desc="true"
|
|
|
|
|
|
:search="filter"
|
2020-09-07 17:23:00 +02:00
|
|
|
|
:custom-filter="searchTable"
|
2020-08-22 20:43:23 +02:00
|
|
|
|
:loading="loading"
|
|
|
|
|
|
fixed-header
|
2022-05-07 14:58:16 +02:00
|
|
|
|
:footer-props='{itemsPerPageOptions: [ 10, 25, 50, 100, 500, -1 ], showFirstLastPage: true}'
|
2022-02-27 19:42:57 +01:00
|
|
|
|
@click:row="setActiveItem"
|
2022-02-27 19:45:52 +01:00
|
|
|
|
@contextmenu:row="contextMenu"
|
2020-08-22 20:43:23 +02:00
|
|
|
|
>
|
2020-08-12 15:15:44 +02:00
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
<template v-slot:item.tstamp="{value}">
|
|
|
|
|
|
<span style="white-space:nowrap;" v-if="value">
|
|
|
|
|
|
{{ value.replace(/(.{10})T(.{8}).{4}Z$/, "$1 $2") }}
|
2020-08-12 15:15:44 +02:00
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
<template v-slot:item.remarks="{item}">
|
2022-02-27 19:28:15 +01:00
|
|
|
|
<template>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div v-for="entry in item.items"
|
2022-02-27 19:45:52 +01:00
|
|
|
|
@contextmenu.stop="(e) => contextMenu(e, {entry})"
|
2022-02-27 19:28:15 +01:00
|
|
|
|
>
|
|
|
|
|
|
<span v-if="entry.labels.length">
|
|
|
|
|
|
<v-chip v-for="label in entry.labels"
|
|
|
|
|
|
class="mr-1 px-2 underline-on-hover"
|
|
|
|
|
|
x-small
|
2022-03-02 19:39:29 +01:00
|
|
|
|
:color="labels[label] && labels[label].view.colour"
|
|
|
|
|
|
:title="labels[label] && labels[label].view.description"
|
2022-04-29 12:18:09 +02:00
|
|
|
|
:dark="labels[label] && labels[label].view.dark"
|
|
|
|
|
|
:light="labels[label] && labels[label].view.light"
|
2022-02-27 19:28:15 +01:00
|
|
|
|
:key="label"
|
|
|
|
|
|
:href="$route.path+'?label='+encodeURIComponent(label)"
|
|
|
|
|
|
>{{label}}</v-chip>
|
|
|
|
|
|
</span>
|
2022-05-01 21:20:52 +02:00
|
|
|
|
<dougal-event-edit-history v-if="entry.has_edits && writeaccess"
|
2022-02-27 19:30:39 +01:00
|
|
|
|
:id="entry.id"
|
|
|
|
|
|
:disabled="loading"
|
|
|
|
|
|
:labels="labels"
|
|
|
|
|
|
></dougal-event-edit-history>
|
2022-02-27 19:28:15 +01:00
|
|
|
|
<span v-if="entry.meta.readonly"
|
|
|
|
|
|
class="entry text--secondary"
|
|
|
|
|
|
v-html="$options.filters.markdownInline(entry.remarks)">
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-else
|
|
|
|
|
|
class="entry underline-on-hover"
|
|
|
|
|
|
v-html="$options.filters.markdownInline(entry.remarks)">
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2021-05-16 19:55:31 +02:00
|
|
|
|
</template>
|
2020-08-22 20:43:23 +02:00
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2022-05-13 18:17:02 +02:00
|
|
|
|
<template v-slot:footer.prepend>
|
|
|
|
|
|
<v-checkbox v-for="label in filterableLabels"
|
|
|
|
|
|
:key="label"
|
|
|
|
|
|
class="mr-3"
|
|
|
|
|
|
v-model="shownLabels"
|
|
|
|
|
|
:value="label"
|
|
|
|
|
|
:title="`Show ${label} events`"
|
|
|
|
|
|
dense
|
|
|
|
|
|
hide-details
|
|
|
|
|
|
>
|
|
|
|
|
|
<template v-slot:label>
|
|
|
|
|
|
<v-chip
|
|
|
|
|
|
x-small
|
|
|
|
|
|
:color="labels[label] && labels[label].view.colour"
|
|
|
|
|
|
:title="labels[label] && labels[label].view.description"
|
|
|
|
|
|
:dark="labels[label] && labels[label].view.dark"
|
|
|
|
|
|
:light="labels[label] && labels[label].view.light"
|
|
|
|
|
|
>{{label}}
|
|
|
|
|
|
</v-chip>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</v-checkbox>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2020-08-12 15:15:44 +02:00
|
|
|
|
</v-data-table>
|
2020-08-22 20:43:23 +02:00
|
|
|
|
</v-card-text>
|
|
|
|
|
|
</v-card>
|
2020-08-12 15:15:44 +02:00
|
|
|
|
</v-container>
|
2020-08-08 23:59:13 +02:00
|
|
|
|
</template>
|
|
|
|
|
|
|
2022-02-27 19:28:15 +01:00
|
|
|
|
<style>
|
|
|
|
|
|
tr.align-top td:not(.align-default) {
|
|
|
|
|
|
vertical-align: top;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.hover {
|
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hover:hover {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hover.off:hover {
|
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
.underline-on-hover:hover {
|
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.entry {
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
|
|
|
|
|
.entry.underline-on-hover:hover {
|
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
2020-08-08 23:59:13 +02:00
|
|
|
|
<script>
|
2020-09-14 23:56:30 +02:00
|
|
|
|
import { mapActions, mapGetters, mapState } from 'vuex';
|
2020-08-22 20:43:23 +02:00
|
|
|
|
import DougalContextMenu from '@/components/context-menu';
|
2022-02-27 19:06:36 +01:00
|
|
|
|
import DougalEventEdit from '@/components/event-edit'
|
2022-02-27 19:36:28 +01:00
|
|
|
|
import DougalEventEditLabels from '@/components/event-edit-labels'
|
2022-02-27 19:30:39 +01:00
|
|
|
|
import DougalEventEditHistory from '@/components/event-edit-history'
|
2020-08-22 20:43:23 +02:00
|
|
|
|
|
|
|
|
|
|
function ArraysEqual (a, b) {
|
|
|
|
|
|
return a.every(i => b.includes(i)) && b.every(i => a.includes(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function EventKey (e) {
|
|
|
|
|
|
return e.type+e.id;
|
|
|
|
|
|
}
|
2020-08-08 23:59:13 +02:00
|
|
|
|
|
2022-02-27 19:06:36 +01:00
|
|
|
|
function clone (obj) {
|
|
|
|
|
|
return JSON.parse(JSON.stringify(obj));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-08 23:59:13 +02:00
|
|
|
|
export default {
|
2020-08-12 15:15:44 +02:00
|
|
|
|
name: "Log",
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
components: {
|
2022-02-27 19:06:36 +01:00
|
|
|
|
DougalEventEdit,
|
2022-02-27 19:36:28 +01:00
|
|
|
|
DougalEventEditLabels,
|
2022-02-27 19:30:39 +01:00
|
|
|
|
DougalEventEditHistory,
|
2020-08-22 20:43:23 +02:00
|
|
|
|
DougalContextMenu
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2020-08-12 15:15:44 +02:00
|
|
|
|
data () {
|
|
|
|
|
|
return {
|
|
|
|
|
|
headers: [
|
|
|
|
|
|
{
|
2020-08-22 20:43:23 +02:00
|
|
|
|
value: "tstamp",
|
2020-08-12 15:15:44 +02:00
|
|
|
|
text: "Timestamp",
|
|
|
|
|
|
width: "20ex"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: "sequence",
|
|
|
|
|
|
text: "Sequence",
|
|
|
|
|
|
align: "end",
|
|
|
|
|
|
width: "10ex"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2020-08-22 20:43:23 +02:00
|
|
|
|
value: "point",
|
2020-08-12 15:15:44 +02:00
|
|
|
|
text: "Shotpoint",
|
|
|
|
|
|
align: "end",
|
|
|
|
|
|
width: "10ex"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: "remarks",
|
|
|
|
|
|
text: "Text",
|
2022-02-27 19:28:15 +01:00
|
|
|
|
width: "100%",
|
|
|
|
|
|
cellClass: "align-default"
|
2020-08-22 20:43:23 +02:00
|
|
|
|
}
|
2020-08-12 15:15:44 +02:00
|
|
|
|
],
|
|
|
|
|
|
items: [],
|
|
|
|
|
|
labels: {},
|
|
|
|
|
|
options: {},
|
|
|
|
|
|
filter: "",
|
2022-05-13 18:17:02 +02:00
|
|
|
|
filterableLabels: [ "QC", "QCAccepted" ],
|
|
|
|
|
|
shownLabels: [ "QC", "QCAccepted" ],
|
2020-08-22 20:43:23 +02:00
|
|
|
|
eventCount: null,
|
|
|
|
|
|
eventDialog: false,
|
2022-02-27 19:36:28 +01:00
|
|
|
|
eventLabelsDialog: false,
|
2020-08-22 20:43:23 +02:00
|
|
|
|
defaultEventTimestamp: null,
|
|
|
|
|
|
presetRemarks: null,
|
|
|
|
|
|
remarksMenu: null,
|
|
|
|
|
|
remarksMenuItem: null,
|
2022-02-27 19:06:36 +01:00
|
|
|
|
editedEvent: {},
|
2020-09-14 23:56:30 +02:00
|
|
|
|
labelSearch: null,
|
2021-05-08 21:54:55 +02:00
|
|
|
|
queuedReload: false,
|
2022-02-27 19:42:57 +01:00
|
|
|
|
itemsPerPage: 25,
|
|
|
|
|
|
|
|
|
|
|
|
// Row highlighter
|
|
|
|
|
|
activeItem: null,
|
2022-02-27 19:45:52 +01:00
|
|
|
|
|
|
|
|
|
|
// Context menu stuff
|
|
|
|
|
|
contextMenuShow: false,
|
|
|
|
|
|
contextMenuX: 0,
|
|
|
|
|
|
contextMenuY: 0,
|
|
|
|
|
|
contextMenuItem: null
|
2020-08-12 15:15:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2022-03-18 15:05:08 +01:00
|
|
|
|
|
2020-08-12 15:15:44 +02:00
|
|
|
|
computed: {
|
|
|
|
|
|
rows () {
|
|
|
|
|
|
const rows = {};
|
2022-05-13 18:17:02 +02:00
|
|
|
|
this.items
|
|
|
|
|
|
.filter(i => {
|
|
|
|
|
|
for (const label of this.filterableLabels) {
|
|
|
|
|
|
if (!this.shownLabels.includes(label) && i.labels.includes(label)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
})
|
|
|
|
|
|
.forEach(i => {
|
2022-02-27 19:28:15 +01:00
|
|
|
|
const key = (i.sequence && i.point) ? (i.sequence+"@"+i.point) : i.tstamp;
|
2020-08-12 15:15:44 +02:00
|
|
|
|
if (!rows[key]) {
|
2020-08-22 20:43:23 +02:00
|
|
|
|
rows[key] = {
|
|
|
|
|
|
key,
|
|
|
|
|
|
tstamp: i.tstamp,
|
|
|
|
|
|
sequence: i.sequence,
|
|
|
|
|
|
point: i.point,
|
|
|
|
|
|
items: []
|
|
|
|
|
|
}
|
2020-08-12 15:15:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const row = rows[key];
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
row.items.push(i);
|
2020-08-12 15:15:44 +02:00
|
|
|
|
});
|
|
|
|
|
|
return Object.values(rows);
|
2020-08-22 20:43:23 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
userLabels () {
|
|
|
|
|
|
const filtered = {};
|
|
|
|
|
|
for (const key in this.labels) {
|
|
|
|
|
|
if (this.labels[key].model.user) {
|
|
|
|
|
|
filtered[key] = this.labels[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return filtered;
|
2022-02-27 19:40:07 +01:00
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
popularLabels () {
|
|
|
|
|
|
const tuples = this.items.flatMap( i => i.labels )
|
|
|
|
|
|
.filter( l => (this.labels[l]??{})?.model?.user )
|
|
|
|
|
|
.reduce( (acc, cur) => {
|
|
|
|
|
|
return cur in acc ? ++acc[cur][1] : acc[cur]=[cur,1], acc
|
|
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
|
|
return Object.values(tuples)
|
|
|
|
|
|
.sort( (a, b) => b[1]-a[1] );
|
2020-08-26 17:48:55 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
2020-09-04 01:29:00 +02:00
|
|
|
|
defaultSequence () {
|
|
|
|
|
|
if (this.$route.params.sequence) {
|
|
|
|
|
|
return Number(this.$route.params.sequence.split(";").pop());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return this.sequence;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2022-02-27 19:06:36 +01:00
|
|
|
|
...mapGetters(['user', 'writeaccess', 'loading', 'online', 'sequence', 'line', 'point', 'position', 'timestamp', 'lineName', 'serverEvent']),
|
2020-09-14 23:56:30 +02:00
|
|
|
|
...mapState({projectSchema: state => state.project.projectSchema})
|
2020-08-26 17:48:55 +02:00
|
|
|
|
|
2020-08-12 15:15:44 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
options: {
|
|
|
|
|
|
handler () {
|
2020-10-03 00:36:53 +02:00
|
|
|
|
//this.getEvents();
|
2020-08-12 15:15:44 +02:00
|
|
|
|
},
|
|
|
|
|
|
deep: true
|
2020-08-22 20:43:23 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
eventDialog (val) {
|
|
|
|
|
|
if (val) {
|
|
|
|
|
|
// If not online
|
|
|
|
|
|
this.defaultEventTimestamp = Date.now();
|
|
|
|
|
|
}
|
2020-09-14 23:56:30 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async serverEvent (event) {
|
|
|
|
|
|
if (event.channel == "event" && event.payload.schema == this.projectSchema) {
|
|
|
|
|
|
if (!this.loading && !this.queuedReload) {
|
2020-09-29 00:24:57 +02:00
|
|
|
|
// 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.
|
2020-09-14 23:56:30 +02:00
|
|
|
|
this.getEvents();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.queuedReload = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-10-02 01:33:58 +02:00
|
|
|
|
},
|
2022-03-18 15:05:08 +01:00
|
|
|
|
|
2020-10-02 01:33:58 +02:00
|
|
|
|
queuedReload (newVal, oldVal) {
|
|
|
|
|
|
if (newVal && !oldVal && !this.loading) {
|
2020-10-03 00:36:53 +02:00
|
|
|
|
this.getEvents();
|
2020-10-02 01:33:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2022-03-18 15:05:08 +01:00
|
|
|
|
|
2020-10-02 01:33:58 +02:00
|
|
|
|
loading (newVal, oldVal) {
|
|
|
|
|
|
if (!newVal && oldVal && this.queuedReload) {
|
2020-10-03 00:36:53 +02:00
|
|
|
|
this.getEvents();
|
2020-10-02 01:33:58 +02:00
|
|
|
|
}
|
2021-05-08 21:54:55 +02:00
|
|
|
|
},
|
2022-03-18 15:05:08 +01:00
|
|
|
|
|
2021-05-08 21:54:55 +02:00
|
|
|
|
itemsPerPage (newVal, oldVal) {
|
|
|
|
|
|
localStorage.setItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`, newVal);
|
|
|
|
|
|
},
|
2022-03-18 15:05:08 +01:00
|
|
|
|
|
2021-05-08 21:54:55 +02:00
|
|
|
|
user (newVal, oldVal) {
|
|
|
|
|
|
this.itemsPerPage = Number(localStorage.getItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`)) || 25;
|
2020-08-12 15:15:44 +02:00
|
|
|
|
}
|
2020-09-14 23:56:30 +02:00
|
|
|
|
|
2020-08-12 15:15:44 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
debug (value) {
|
|
|
|
|
|
console.log("DEBUG", value);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
contextMenu (e, {item, entry}) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
this.contextMenuShow = false;
|
|
|
|
|
|
this.contextMenuX = e.clientX;
|
|
|
|
|
|
this.contextMenuY = e.clientY;
|
|
|
|
|
|
this.contextMenuItem = entry ?? item;
|
|
|
|
|
|
this.$nextTick( () => this.contextMenuShow = true );
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/** Check if this item (or entry) has multiple
|
|
|
|
|
|
* deletable items, just one, or none (i.e., it is read-only)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return {Number} Number of deletable items.
|
|
|
|
|
|
*/
|
|
|
|
|
|
deletableEntries (item) {
|
|
|
|
|
|
return item.items
|
|
|
|
|
|
? item.items.filter(e => !e.meta.readonly).length
|
|
|
|
|
|
: item.meta.readonly
|
|
|
|
|
|
? 0
|
|
|
|
|
|
: 1;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isRowReadonly (item) {
|
|
|
|
|
|
return !this.deletableEntries(item);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2022-05-03 23:42:58 +02:00
|
|
|
|
itemClass (item) {
|
|
|
|
|
|
if (this.activeItem == item) {
|
|
|
|
|
|
return 'align-top blue accent-1 elevation-3';
|
|
|
|
|
|
} else if (item.sequence && item.point && item.tstamp) {
|
|
|
|
|
|
return this.$vuetify.theme.isDark
|
|
|
|
|
|
? 'align-top blue-grey darken-4'
|
|
|
|
|
|
: 'align-top blue-grey lighten-5';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return 'align-top';
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2020-08-12 15:15:44 +02:00
|
|
|
|
async getEventCount () {
|
|
|
|
|
|
//this.eventCount = await this.api([`/project/${this.$route.params.project}/event/?count`]);
|
|
|
|
|
|
this.eventCount = null;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2020-09-29 00:24:57 +02:00
|
|
|
|
async getEvents (opts = {}) {
|
2020-08-12 15:15:44 +02:00
|
|
|
|
|
|
|
|
|
|
const query = new URLSearchParams(this.options);
|
|
|
|
|
|
if (this.options.itemsPerPage < 0) {
|
|
|
|
|
|
query.delete("itemsPerPage");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.$route.params.sequence) {
|
|
|
|
|
|
query.set("sequence", this.$route.params.sequence);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.$route.params.date0) {
|
|
|
|
|
|
query.set("date0", this.$route.params.date0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.$route.params.date1) {
|
|
|
|
|
|
query.set("date1", this.$route.params.date1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const url = `/project/${this.$route.params.project}/event?${query.toString()}`;
|
|
|
|
|
|
|
2020-09-14 23:56:30 +02:00
|
|
|
|
this.queuedReload = false;
|
2020-09-29 00:24:57 +02:00
|
|
|
|
this.items = await this.api([url, opts]) || [];
|
2020-08-12 15:15:44 +02:00
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
async getLabelDefinitions () {
|
2020-08-12 15:15:44 +02:00
|
|
|
|
const url = `/project/${this.$route.params.project}/label`;
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
const labelSet = {};
|
2020-08-12 15:15:44 +02:00
|
|
|
|
const labels = await this.api([url]) || [];
|
2020-08-22 20:43:23 +02:00
|
|
|
|
labels.forEach( l => labelSet[l.name] = l.data );
|
|
|
|
|
|
this.labels = labelSet;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async getPresetRemarks () {
|
|
|
|
|
|
const url = `/project/${this.$route.params.project}/configuration/events/presetRemarks`;
|
|
|
|
|
|
|
|
|
|
|
|
this.presetRemarks = await this.api([url]);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
newItem (from = {}) {
|
|
|
|
|
|
const type = (from.sequence && from.point) ? "sequence" : "timed";
|
|
|
|
|
|
const tstamp = from.tstamp || (new Date).toISOString();
|
|
|
|
|
|
const sequence = from.sequence || null; // FIXME TODO Use vuex
|
|
|
|
|
|
const point = from.point || null; // FIXME TODO Use vuex
|
|
|
|
|
|
const geometry = from.geometry || null; // FIXME TODO Use vuex
|
|
|
|
|
|
return {
|
|
|
|
|
|
type,
|
|
|
|
|
|
id: null,
|
|
|
|
|
|
tstamp,
|
|
|
|
|
|
sequence,
|
|
|
|
|
|
point,
|
|
|
|
|
|
remarks: "",
|
|
|
|
|
|
labels: [],
|
|
|
|
|
|
geometry
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
cloneEvent (template = {}) {
|
|
|
|
|
|
this.editedEvent = clone(template);
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE Can we actually use "id", it being a reserved HTML attribute?
|
|
|
|
|
|
this.editedEvent.id = template.id;
|
|
|
|
|
|
|
|
|
|
|
|
if (template.meta?.geometry?.type == "Point") {
|
|
|
|
|
|
this.editedEvent.longitude = template.meta.geometry.coordinates[0];
|
|
|
|
|
|
this.editedEvent.latitude = template.meta.geometry.coordinates[1];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2022-02-27 19:06:36 +01:00
|
|
|
|
/** Add a brand new event.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Used when adding a new event to the database,
|
|
|
|
|
|
* assumed to have occurred in the immediate past,
|
|
|
|
|
|
* so we populate it with the last received real-time
|
|
|
|
|
|
* information (timestamp, shotpoint, position, etc.)
|
|
|
|
|
|
*/
|
|
|
|
|
|
newEvent () {
|
|
|
|
|
|
this.editedEvent = {
|
|
|
|
|
|
tstamp: this.timestamp,
|
|
|
|
|
|
sequence: this.sequence,
|
|
|
|
|
|
point: this.point,
|
|
|
|
|
|
longitude: (this.position??[])[0],
|
|
|
|
|
|
latitude: (this.position??[])[1]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
/** Add an event based on another.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Used when adding an event to a timestamp or
|
|
|
|
|
|
* point for which we already have an event.
|
|
|
|
|
|
*/
|
|
|
|
|
|
addEvent (template) {
|
|
|
|
|
|
this.cloneEvent(template);
|
|
|
|
|
|
this.editedEvent.id = null;
|
|
|
|
|
|
this.editedEvent.remarks = null;
|
|
|
|
|
|
this.editedEvent.labels = null;
|
|
|
|
|
|
this.eventDialog = true;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
editEvent (template) {
|
|
|
|
|
|
this.cloneEvent(template);
|
|
|
|
|
|
this.eventDialog = true;
|
|
|
|
|
|
},
|
2022-02-27 19:06:36 +01:00
|
|
|
|
|
|
|
|
|
|
async patchEvent (id, data) {
|
|
|
|
|
|
const callback = (err, res) => {
|
|
|
|
|
|
if (!err && res.ok) {
|
|
|
|
|
|
this.showSnack(["Event saved", "success"]);
|
|
|
|
|
|
this.queuedReload = true;
|
|
|
|
|
|
this.getEvents({cache: "reload"});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const url = `/project/${this.$route.params.project}/event/${id}`;
|
|
|
|
|
|
await this.api([url, {
|
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
|
body: data
|
|
|
|
|
|
}, callback]);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// TODO POST or PATCH depending on whether this is a new event
|
|
|
|
|
|
// (no id) or an edit (id present)
|
2020-08-22 20:43:23 +02:00
|
|
|
|
async saveEvent (event) {
|
2021-05-16 21:38:31 +02:00
|
|
|
|
const callback = (err, res) => {
|
2021-05-16 19:55:31 +02:00
|
|
|
|
if (!err && res.ok) {
|
2022-02-27 19:06:36 +01:00
|
|
|
|
this.showSnack(["Event saved", "success"]);
|
2021-05-16 19:55:31 +02:00
|
|
|
|
this.queuedReload = true;
|
|
|
|
|
|
this.getEvents({cache: "reload"});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-02-27 19:06:36 +01:00
|
|
|
|
|
|
|
|
|
|
if (event) {
|
|
|
|
|
|
if (event.id) {
|
|
|
|
|
|
const id = event.id;
|
|
|
|
|
|
delete event.id;
|
|
|
|
|
|
this.putEvent(id, event, callback); // No await
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.postEvent(event, callback); // No await
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async putEvent (id, event, callback) {
|
|
|
|
|
|
const url = `/project/${this.$route.params.project}/event/${id}`;
|
|
|
|
|
|
await this.api([url, {
|
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
|
body: event
|
|
|
|
|
|
}, callback]);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async postEvent (event, callback) {
|
2020-08-22 20:43:23 +02:00
|
|
|
|
const url = `/project/${this.$route.params.project}/event`;
|
|
|
|
|
|
await this.api([url, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
body: event
|
2021-05-16 19:55:31 +02:00
|
|
|
|
}, callback]);
|
2020-08-22 20:43:23 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
async removeEvent (target) {
|
|
|
|
|
|
if (Array.isArray(target?.items)) {
|
|
|
|
|
|
return await this.removeEvent(target.items);
|
2020-08-22 20:43:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
if (Array.isArray(target)) {
|
|
|
|
|
|
if (target.length == 1) {
|
|
|
|
|
|
return await this.removeEvent(target[0]);
|
2020-08-22 20:43:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
const ids = target.map(i => i?.id ?? i);
|
2020-08-22 20:43:23 +02:00
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
const callback = (err, res) => {
|
|
|
|
|
|
if (!err && res.ok) {
|
|
|
|
|
|
this.showSnack([`${ids.length} events deleted`, "red"]);
|
|
|
|
|
|
this.queuedReload = true;
|
|
|
|
|
|
this.getEvents({cache: "reload"});
|
2020-08-22 20:43:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
Promise.all(ids.forEach( id => {
|
|
|
|
|
|
const url = `/project/${this.$route.params.project}/event/${id}`;
|
|
|
|
|
|
return this.api([url, {method: "DELETE"}]);
|
|
|
|
|
|
})).then(callback);
|
2020-08-22 20:43:23 +02:00
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
} else {
|
|
|
|
|
|
const id = target?.id ?? target;
|
|
|
|
|
|
|
|
|
|
|
|
const callback = (err, res) => {
|
|
|
|
|
|
if (!err && res.ok) {
|
|
|
|
|
|
this.showSnack(["Event deleted", "red"]);
|
|
|
|
|
|
this.queuedReload = true;
|
|
|
|
|
|
this.getEvents({cache: "reload"});
|
|
|
|
|
|
}
|
2020-08-22 20:43:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-27 19:45:52 +01:00
|
|
|
|
const url = `/project/${this.$route.params.project}/event/${id}`;
|
|
|
|
|
|
await this.api([url, {
|
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
|
}, callback]);
|
2020-08-22 20:43:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
addPresetRemark ({text}) {
|
|
|
|
|
|
if (this.remarksMenuItem) {
|
|
|
|
|
|
this.remarksMenuItem.remarks = text;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.remarksMenuItem = null;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
availableLabels (usedLabels) {
|
|
|
|
|
|
return Object.keys(this.labels)
|
|
|
|
|
|
.filter(k => !usedLabels.includes(k) && this.labels[k].model.user);
|
2020-08-12 15:15:44 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
handleKeyboardEvent (e) {
|
|
|
|
|
|
if (e.ctrlKey && !e.altKey && !e.shiftKey && (e.keyCode == 13 || e.key == "F2")) {
|
2020-10-02 01:33:33 +02:00
|
|
|
|
// Add timed event if offline or shot event if online
|
2020-08-22 20:43:23 +02:00
|
|
|
|
this.eventDialog = true;
|
|
|
|
|
|
} else if (e.ctrlKey && !e.altKey && e.shiftKey && (e.keyCode == 13 || e.key == "F2")) {
|
2020-10-02 01:33:33 +02:00
|
|
|
|
// Add timed event (even if online)
|
2020-08-22 20:43:23 +02:00
|
|
|
|
this.eventDialog = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2020-09-07 17:23:00 +02:00
|
|
|
|
searchTable (value, search, item) {
|
|
|
|
|
|
if (!value && !search) return true;
|
|
|
|
|
|
const s = search.toLowerCase();
|
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
|
return value.toLowerCase().includes(s);
|
|
|
|
|
|
} else if (typeof value === 'number') {
|
|
|
|
|
|
return value == search;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return item.items.some( i => i.remarks.toLowerCase().includes(s) ) ||
|
|
|
|
|
|
item.items.some( i => i.labels.some( l => l.toLowerCase().includes(s) ));
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2022-02-27 19:28:15 +01:00
|
|
|
|
|
|
|
|
|
|
viewOnMap(item) {
|
|
|
|
|
|
if (item?.meta && item.meta?.geometry?.type == "Point") {
|
|
|
|
|
|
const [ lon, lat ] = item.meta.geometry.coordinates;
|
|
|
|
|
|
return `map#15/${lon.toFixed(6)}/${lat.toFixed(6)}`;
|
|
|
|
|
|
} else if (item?.items) {
|
|
|
|
|
|
return this.viewOnMap(item.items[0]);
|
2021-05-17 17:14:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2020-09-07 17:23:00 +02:00
|
|
|
|
|
2022-05-04 19:45:20 +02:00
|
|
|
|
isAcceptedQc (item) {
|
|
|
|
|
|
return item.labels.includes('QCAccepted');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async acceptQc (item, accept = true) {
|
|
|
|
|
|
|
|
|
|
|
|
const url = accept
|
|
|
|
|
|
? `/project/${this.$route.params.project}/qc/results/accept`
|
|
|
|
|
|
: `/project/${this.$route.params.project}/qc/results/unaccept`;
|
|
|
|
|
|
|
|
|
|
|
|
await this.api([url, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
body: [ item.id ]
|
|
|
|
|
|
}]);
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2022-02-27 19:42:57 +01:00
|
|
|
|
setActiveItem (item) {
|
|
|
|
|
|
// Disable setting the active item for now,
|
|
|
|
|
|
// it's kind of annoying.
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
this.activeItem = this.activeItem == item
|
|
|
|
|
|
? null
|
|
|
|
|
|
: item;
|
|
|
|
|
|
*/
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2020-08-22 20:43:23 +02:00
|
|
|
|
...mapActions(["api", "showSnack"])
|
2020-08-12 15:15:44 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async mounted () {
|
2021-05-08 21:54:55 +02:00
|
|
|
|
await this.getLabelDefinitions();
|
2020-08-12 15:15:44 +02:00
|
|
|
|
this.getEventCount();
|
|
|
|
|
|
this.getEvents();
|
2020-08-22 20:43:23 +02:00
|
|
|
|
this.getPresetRemarks();
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('keyup', this.handleKeyboardEvent);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
beforeDestroy () {
|
|
|
|
|
|
window.removeEventListener('keyup', this.handleKeyboardEvent);
|
2020-08-12 15:15:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-08-08 23:59:13 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|