sotlas-frontend/src/components/EditSpot.vue

280 wiersze
8.6 KiB
Vue

<template>
<div class="modal-card" style="width: auto">
<header class="modal-card-head">
<p class="modal-card-title">{{ this.spot ? 'Edit' : 'Add' }} Spot</p>
</header>
<section class="modal-card-body">
<b-field label="Callsign" :message="isOwnCallsign ? '' : 'You are spotting someone else\'s callsign'" :type="isOwnCallsign ? '' : 'is-info'">
<b-input type="text" class="callsign" v-model="callsign" pattern="[a-zA-Z0-9/]{3,}" validation-message="Invalid callsign" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required />
</b-field>
<b-field label="Summit reference" :message="summitDisplay" :type="summitType" :class="summitLabelClass" expanded>
<b-field>
<b-input type="text" ref="summitCode" v-model="summitCode" placeholder="XX/YY-000" :loading="summitLoading" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required />
<p class="control">
<NearbySummitsList @summitSelected="onSummitSelected" />
</p>
</b-field>
</b-field>
<b-field label="Frequency" :message="maybeKhz ? 'Do you really mean ' + frequency + ' MHz, or are you missing a dot?' : ''" :type="maybeKhz ? 'is-warning' : ''">
<b-field :type="maybeKhz ? 'is-warning' : ''">
<b-input v-model="frequency" type="number" step="any" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required />
<p class="control">
<span class="button is-static">MHz</span>
</p>
</b-field>
</b-field>
<b-field label="Mode">
<b-field>
<b-radio-button v-for="(curModeDisp, curMode) in allModes()" :key="curMode" v-model="mode" :size="$mq.mobile ? 'is-small' : ''" :native-value="curMode">{{ curModeDisp }}</b-radio-button>
</b-field>
</b-field>
<b-field label="Comments">
<b-input v-model="comments" type="text" maxlength="60" />
</b-field>
</section>
<footer class="modal-card-foot">
<b-button @click="$parent.close()">Cancel</b-button>
<b-button type="is-info" :disabled="!isInputValid" :loading="posting" @click="postSpot">{{ (this.spot && this.spot.id) ? 'Edit' : 'Add' }} Spot</b-button>
</footer>
</div>
</template>
<script>
import axios from 'axios'
import utils from '../mixins/utils.js'
import prefs from '../mixins/prefs.js'
import sotawatch from '../mixins/sotawatch.js'
import NearbySummitsList from './NearbySummitsList.vue'
export default {
components: {
NearbySummitsList
},
mixins: [utils, prefs, sotawatch],
props: {
defaultSummitCode: String,
spot: Object
},
prefs: {
key: 'spotPrefs',
props: ['lastCallsign', 'lastSummitCode', 'defaultComments']
},
mounted () {
if (!this.callsign) {
if (this.lastCallsign) {
this.callsign = this.lastCallsign
} else if (this.myCallsign) {
this.callsign = this.myCallsign
if (!/\/P$/.test(this.callsign)) {
this.callsign += '/P'
}
}
}
if (!this.summitCode) {
if (this.lastSummitCode) {
this.summitCode = this.lastSummitCode
}
}
if (!this.comments && this.defaultComments) {
this.comments = this.defaultComments
}
},
computed: {
summitDisplay () {
if (this.summit) {
if (this.$store.state.altitudeUnits === 'ft') {
return this.summit.name + ' (' + Math.round(this.summit.altitude * 3.28084) + ' ft)'
} else {
return this.summit.name + ' (' + this.summit.altitude + ' m)'
}
} else if (this.summitInvalid) {
return 'Summit not found'
} else {
return 'You can enter spaces instead of / and -'
}
},
summitType () {
if (this.summitInvalid) {
return 'is-danger'
} else {
return ''
}
},
isInputValid () {
return /^[a-zA-Z0-9/]{3,}$/.test(this.callsign) && this.summit !== null && this.isSummitValid(this.summit) && this.frequency && this.mode
},
summitLabelClass () {
if (!this.summit || this.isSummitValid(this.summit)) {
return { summitref: true }
} else {
return { summitref: true, invalid: true }
}
},
isOwnCallsign () {
return (!this.callsign || !this.myCallsign || (this.homeCallsign(this.callsign) === this.homeCallsign(this.myCallsign)))
},
maybeKhz () {
return (this.frequency && this.frequency > 1500)
}
},
watch: {
defaultSummitCode: {
immediate: true,
handler () {
if (!this.summitCode) {
this.summitCode = this.defaultSummitCode
}
}
},
summitCode: {
immediate: true,
handler () {
if (this.summitCode) {
// Shorthand input
let summitRegex = /^([A-Z0-9]{1,8})[/ ]([A-Z]{2})[- ]?([0-9]{3})$/i
let matches = this.summitCode.match(summitRegex)
if (matches) {
this.summitCode = (matches[1] + '/' + matches[2] + '-' + matches[3]).toUpperCase()
this.summitLoading = true
axios.get('https://api.sotl.as/summits/' + this.summitCode)
.then(response => {
this.summitLoading = false
this.summitInvalid = false
this.summit = response.data
})
.catch(() => {
this.summitLoading = false
this.summitInvalid = true
this.summit = null
})
} else {
this.summit = null
this.summitInvalid = false
}
} else {
this.summit = null
this.summitInvalid = false
}
}
},
spot: {
immediate: true,
handler () {
if (this.spot) {
this.callsign = this.spot.activatorCallsign
this.summitCode = this.spot.summit.code
this.frequency = this.spot.frequency
this.mode = this.spot.mode.toLowerCase()
this.comments = (this.spot.comments ? this.spot.comments.replace('[sotl.as]', '').trim() : '')
}
}
}
},
methods: {
postSpot () {
this.lastCallsign = this.callsign.toUpperCase()
this.lastSummitCode = this.summitCode
// Advertise in comments :)
let commentsTag = '[sotl.as]'
let comments = this.comments
if ((comments.length + commentsTag.length) < 60 && !comments.endsWith(commentsTag)) {
if (comments.length > 0) {
comments += ' '
}
comments += commentsTag
}
let params = {
callsign: this.myCallsign,
activatorCallsign: this.callsign.toUpperCase(),
associationCode: this.summitCode.substring(0, this.summitCode.indexOf('/')),
summitCode: this.summitCode.substring(this.summitCode.indexOf('/') + 1),
frequency: this.frequency,
mode: this.allModes()[this.mode],
comments
}
if (this.spot && this.spot.id) {
params.id = this.spot.id
}
this.posting = true
this.postSotaWatchSpot(params)
.then(response => {
this.$store.commit('updateSpot', {
id: (this.spot && this.spot.id) ? this.spot.id : response.data.id,
timeStamp: response.data.timeStamp,
frequency: response.data.frequency,
mode: response.data.mode,
summit: this.summit,
activatorCallsign: response.data.activatorCallsign,
callsign: response.data.callsign,
comments: response.data.comments
})
this.$parent.close()
})
.finally(() => {
this.posting = false
})
},
onSummitSelected (summit) {
this.summitCode = summit.code
this.$nextTick(() => {
this.$refs.summitCode.checkHtml5Validity()
})
}
},
data () {
return {
callsign: '',
lastCallsign: null,
defaultComments: '',
summitCode: '',
lastSummitCode: null,
frequency: '',
mode: '',
comments: '',
summit: null,
summitInvalid: false,
summitLoading: false,
posting: false
}
}
}
</script>
<style scoped>
.callsign >>> input {
text-transform: uppercase;
}
.invalid >>> .help {
text-decoration: line-through;
}
>>> input::-webkit-outer-spin-button,
>>> input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
>>> input[type=number] {
-moz-appearance:textfield;
}
.summitref .field {
margin-bottom: 0;
}
>>> .help.is-warning {
color: #cda400;
}
/* Fix from https://github.com/buefy/buefy/issues/1932#issuecomment-551453842 */
>>> .field.has-addons {
flex-wrap: wrap;
}
>>> .field.has-addons .help {
width: 100%;
}
</style>