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.
240 lines
6.4 KiB
240 lines
6.4 KiB
<script setup>
|
|
const currentWorkingCategory = defineModel('currentWorkingCategory')
|
|
const currentWorkingInstrument = defineModel('currentWorkingInstrument')
|
|
const props = defineProps(['categories', 'instruments'])
|
|
</script>
|
|
<script>
|
|
import axios from 'axios';
|
|
import CategoryChooser from './category_chooser.vue'
|
|
import { getHours, getMinutes, createTimeObj, getAccumulatedSeconds, formatSeconds } from '../libs/time.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
running: false,
|
|
startTime:0,
|
|
savedPreviousSeconds:0,
|
|
totalSeconds:0,
|
|
description:'',
|
|
comments:'',
|
|
manualHours:0,
|
|
manualMinutes:0,
|
|
manualSeconds:0,
|
|
analyser:null,
|
|
secondsToSubtract:0,
|
|
micThreshold:-1,
|
|
micLevel:0,
|
|
micThresholdExceeded:false,
|
|
practice_category_id:0
|
|
}
|
|
},
|
|
emits: ['loggedTime'],
|
|
components: {
|
|
CategoryChooser
|
|
},
|
|
methods: {
|
|
formatSeconds,
|
|
submit(event){
|
|
event.preventDefault();
|
|
const reqBody = {
|
|
description:this.description,
|
|
seconds: this.totalSeconds,
|
|
practice_category_id: this.practice_category_id
|
|
}
|
|
|
|
if(this.comments){
|
|
reqBody.comments = this.comments
|
|
}
|
|
|
|
axios.post(
|
|
import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'sessions',
|
|
reqBody
|
|
).then(()=>{
|
|
this.description = null;
|
|
this.totalSeconds = 0;
|
|
this.savedPreviousSeconds = 0;
|
|
this.comments = null;
|
|
this.secondsToSubtract = 0;
|
|
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
|
|
this.$emit('loggedTime', reqBody)
|
|
});
|
|
|
|
},
|
|
start(event){
|
|
this.startTime = Date.now();
|
|
this.running = 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.micThresholdExceeded = true;
|
|
} else {
|
|
this.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){
|
|
this.running = false;
|
|
this.savedPreviousSeconds += getAccumulatedSeconds(Date.now(), this.startTime);
|
|
clearInterval(this.intervalID);
|
|
},
|
|
reset(event){
|
|
this.savedPreviousSeconds = 0;
|
|
this.totalSeconds = 0;
|
|
this.secondsToSubtract = 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
|
|
}
|
|
},
|
|
handleCategoryChange(chosenCategoryID){
|
|
this.practice_category_id = chosenCategoryID
|
|
}
|
|
},
|
|
mounted() {
|
|
this.practice_category_id = this.currentWorkingCategory.id
|
|
this.totalSeconds = parseInt(window.localStorage.getItem('lastTotalSeconds'))
|
|
this.setManualTime(this.totalSeconds)
|
|
this.savedPreviousSeconds = this.totalSeconds
|
|
window.onbeforeunload = this.confirmClose
|
|
},
|
|
watch: {
|
|
currentWorkingCategory(chosenCategory){
|
|
this.practice_category_id = chosenCategory.id
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<h2>Timer</h2>
|
|
<div :class="{running:running, micThresholdExceeded:micThresholdExceeded}">
|
|
<em>
|
|
{{formatSeconds(totalSeconds)}}
|
|
</em>
|
|
Mic Threshold (current level: {{Math.round(micLevel)}}):
|
|
<input type="number" @change="activateMic" v-model="micThreshold"/>
|
|
<button :disabled="running" @click="start">Start</button>
|
|
<button :disabled="!running" @click="stop">Stop</button>
|
|
<button :disabled="running || totalSeconds === 0" @click="reset">Reset</button>
|
|
</div>
|
|
<form @submit="submit">
|
|
<label>Description</label>
|
|
<input v-model="description" type="text" maxlength="128"/>
|
|
|
|
<label>Seconds</label>
|
|
<input @change="updateSavedPreviousSeconds" v-model="totalSeconds" type="number"/>
|
|
|
|
<label>Practice Category</label>
|
|
<CategoryChooser
|
|
v-model:currentWorkingCategory="currentWorkingCategory"
|
|
v-model:currentWorkingInstrument="currentWorkingInstrument"
|
|
:instruments="instruments"
|
|
:categories="categories" />
|
|
<input 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;
|
|
}
|
|
|
|
.running {
|
|
background:lightgreen;
|
|
}
|
|
|
|
.running.micThresholdExceeded {
|
|
background:green;
|
|
}
|
|
|
|
button, em {
|
|
margin: 1em 0;
|
|
display:block;
|
|
font-size:3em;
|
|
font-style:normal;
|
|
font-weight:bold;
|
|
width:100%;
|
|
}
|
|
select {
|
|
width: 100%;
|
|
}
|
|
</style>
|