You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
7.3 KiB

<script setup>
import { onMounted, getCurrentInstance, ref } from 'vue'
const micThresholdExceeded = defineModel('micThresholdExceeded')
const timerRunning = defineModel('timerRunning')
const { proxy } = getCurrentInstance();
const descriptionInput = ref(null)
onMounted(()=>{
window.addEventListener('keydown', (event)=>{
if(event.key === 'ArrowDown'){
event.preventDefault()
proxy.start()
} else if (event.key === 'ArrowUp'){
event.preventDefault()
proxy.stop()
}
if(event.ctrlKey === true){
if(event.key === 'Enter'){
proxy.submit(event)
} else if(event.key === 'd') {
descriptionInput.value.focus()
}
}
})
})
</script>
<script>
import axios from 'axios';
import CategoryChooser from './category_chooser.vue'
import { getHours, getMinutes, createTimeObj, getAccumulatedSeconds, formatSeconds } from '../libs/time.js'
import {
copiedSongs,
currentWorkingCategory,
currentWorkingInstrument,
description
} from '../libs/state.js'
export default {
data() {
return {
submitting:false,
startTime:0,
savedPreviousSeconds:0,
totalSeconds:0,
comments:'',
manualHours:0,
manualMinutes:0,
manualSeconds:0,
analyser:null,
secondsToSubtract:0,
micThreshold:-1,
micLevel:0,
practice_category_id:0
}
},
emits: ['loggedTime'],
components: {
CategoryChooser
},
methods: {
formatSeconds,
submit(event){
event.preventDefault();
if(this.submitting){
return
} else {
this.submitting = true
}
const reqBody = {
description:description.value,
seconds: this.totalSeconds,
practice_category_id: currentWorkingCategory.value.id,
}
if(copiedSongs.value.length > 0){
reqBody.songs = copiedSongs.value
}
if(this.comments){
reqBody.comments = this.comments
}
axios.post(
import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'sessions',
reqBody
).then(()=>{
description.value = null;
copiedSongs.value = []
this.totalSeconds = 0;
this.savedPreviousSeconds = 0;
this.comments = null;
this.secondsToSubtract = 0;
this.manualHours = 0
this.manualMinutes = 0
this.manualSeconds = 0
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
this.$emit('loggedTime', reqBody)
this.submitting = false
},()=>{
this.submitting = false
});
},
start(event){
if(this.timerRunning === false){
this.startTime = Date.now();
this.$emit('update:timerRunning', true)
this.intervalID = setInterval(()=>{
if(this.micThreshold > -1){
const array = new Uint8Array(this.analyser.frequencyBinCount);
this.analyser.getByteFrequencyData(array);
const arraySum = array.reduce((a, value) => a + value, 0);
this.micLevel = arraySum / array.length;
if(this.micLevel < this.micThreshold){
this.secondsToSubtract++;
}
}
if(this.micLevel >= this.micThreshold){
this.$emit('update:micThresholdExceeded', true)
} else {
this.$emit('update:micThresholdExceeded', false)
}
this.totalSeconds = getAccumulatedSeconds(Date.now(), this.startTime) - this.secondsToSubtract + this.savedPreviousSeconds;
this.setManualTime(this.totalSeconds)
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
}, 1000);
}
},
setManualTime(seconds){
const timeObj = createTimeObj(this.totalSeconds)
this.manualHours = timeObj.hours
this.manualMinutes = timeObj.minutes
this.manualSeconds = timeObj.seconds
},
updateSavedPreviousSeconds(event){
this.savedPreviousSeconds = parseInt(event.target.value);
this.setManualTime(this.totalSeconds)
},
stop(event){
if(this.timerRunning === true){
this.$emit('update:timerRunning', false)
this.savedPreviousSeconds += getAccumulatedSeconds(Date.now(), this.startTime);
clearInterval(this.intervalID);
}
},
reset(event){
this.savedPreviousSeconds = 0;
this.totalSeconds = 0;
this.secondsToSubtract = 0;
this.manualHours = 0
this.manualMinutes = 0
this.manualSeconds = 0
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
},
setSecondsManually(){
this.totalSeconds = this.manualHours*60*60 + this.manualMinutes*60 + this.manualSeconds;
this.savedPreviousSeconds = this.totalSeconds;
this.secondsToSubtract = 0;
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
},
activateMic(){
if(this.micThreshold > -1){
navigator.mediaDevices.getUserMedia({
audio: true
}).then((stream) => {
const audioContext = new AudioContext();
this.analyser = audioContext.createAnalyser();
const microphone = audioContext.createMediaStreamSource(stream);
const scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1);
this.analyser.smoothingTimeConstant = 0.8;
this.analyser.fftSize = 1024;
microphone.connect(this.analyser);
this.analyser.connect(scriptProcessor);
scriptProcessor.connect(audioContext.destination);
})
.catch((err) => {
console.error(err);
});
}
},
confirmClose(event){
if(this.totalSeconds > 0){
event.returnValue = "Clear or save your time"
return false
}
},
},
mounted() {
this.practice_category_id = currentWorkingCategory.value.id
if(window.localStorage.getItem('lastTotalSeconds')){
this.totalSeconds = parseInt(window.localStorage.getItem('lastTotalSeconds'))
} else {
this.totalSeconds = 0
}
this.setManualTime(this.totalSeconds)
this.savedPreviousSeconds = this.totalSeconds
window.onbeforeunload = this.confirmClose
},
}
</script>
<template>
<h2>Timer</h2>
<div>
<em>
{{formatSeconds(totalSeconds)}}
</em>
Mic Threshold (current level: {{Math.round(micLevel)}}):
<input type="number" @change="activateMic" v-model="micThreshold"/>
<button :disabled="timerRunning" @click="start">Start</button>
<button :disabled="!timerRunning" @click="stop">Stop</button>
<button :disabled="timerRunning || totalSeconds === 0" @click="reset">Reset</button>
</div>
<form @submit="submit">
<label>Description</label>
<input v-model="description" ref="descriptionInput" type="text" maxlength="128"/>
<label>Songs</label>
<ul v-if="copiedSongs.length > 0">
<li v-for="song in copiedSongs">
<a v-bind:href="'#/songs/' + song.id">
{{song.title}}
</a>
</li>
</ul>
<label>Seconds</label>
<input @change="updateSavedPreviousSeconds" v-model="totalSeconds" type="number"/>
<label>Practice Category</label>
<CategoryChooser />
<input :disabled="submitting" type="submit"/>
<label>Comments</label>
<textarea v-model="comments"/>
<label>Hours:</label>
<input type="number" @change="setSecondsManually" v-model="manualHours"/>
<label>Minutes:</label>
<input type="number" @change="setSecondsManually" v-model="manualMinutes"/>
<label>Seconds:</label>
<input type="number" @change="setSecondsManually" v-model="manualSeconds"/>
</form>
</template>
<style scoped>
label, [type="submit"] {
display:block;
}
input[type="number"] {
width: 5em;
}
button, em, [type="submit"]{
margin: 1em 0;
display:block;
font-size:3em;
font-style:normal;
font-weight:bold;
width:100%;
}
select {
width: 100%;
}
</style>