Compare commits

..

No commits in common. 'main' and 'mic_level' have entirely different histories.

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Practice Tracker</title>
<link rel="stylesheet" href="app.css">
</head>

1055
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -9,7 +9,7 @@
},
"dependencies": {
"axios": "^1.3.4",
"vue": "^3.4.38"
"vue": "^3.2.47"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",

@ -2,76 +2,18 @@ body {
margin:0;
}
@media (prefers-color-scheme: dark) {
body {
background-color: black;
color: white;
}
a {
color:white;
}
tbody tr:nth-child(even) td,
tbody tr:nth-child(even) td a {
color:black;
}
}
td,th {
border:1px solid black;
padding:1em;
}
td ul {
padding-inline-start:0;
margin:0;
}
td li {
padding-bottom:1em;
}
th {
background:lightgrey;
color:black;
}
tbody tr:nth-child(even) td{
background:#eeeeee;
color:black;
}
nav {
text-align:right;
position:fixed;
width:100%;
pointer-events:none;
}
nav ul {
list-style:none;
}
nav ul li {
padding: 0.5em;
}
nav ul li a {
padding: 0.5em;
background-color:rgba(100,100,100, 1);
pointer-events:visible;
display:inline-block;
color:white;
border:1px solid black;
}
main > section {
overflow:auto;
}
@media (min-width: 1400px){
nav {
display:none;
}
@media (min-width: 900px){
main {
height:100vh;
@ -80,6 +22,10 @@ main > section {
grid-template-rows: repeat(2, minmax(10px, 1fr));
}
section {
overflow:auto;
}
#timer { grid-area: 1 / 1 / 3 / 2; }
#summary { grid-area: 1 / 2 / 3 / 3; }
#category { grid-area: 1 / 3 / 2 / 4; }

@ -1,79 +1,22 @@
<script setup>
import { watch, onMounted, ref } from 'vue'
<script setup lang="ts">
import Summary from './components/summary.vue'
import MinsLeftToPracticeToday from './components/mins_left_to_practice_today.vue'
import ShowCategory from './components/show_category.vue'
import Songs from './components/songs.vue'
import Timer from './components/timer.vue'
import Status from './components/status.vue'
import {
currentWorkingCategory,
currentWorkingInstrument,
instruments,
categories
} from './libs/state.js'
const timerRunning = ref(false)
const micThresholdExceeded = ref(false)
const summaryRef = ref(null)
const statusRef = ref(null)
const showCategoryRef = ref(null)
const refreshPage = (session) => {
summaryRef.value.loadData()
statusRef.value.loadData()
showCategoryRef.value.loadCategory(session.practice_category_id)
}
const loadData = async () => {
let response = await fetch(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'instruments')
instruments.value = await response.json()
response = await fetch(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'categories')
categories.value = await response.json()
currentWorkingCategory.value = categories.value[0]
currentWorkingInstrument.value = instruments.value.find(instrument => instrument.name === currentWorkingCategory.value.instrument)
}
onMounted(loadData)
</script>
<template>
<nav>
<ul>
<li>
<a href="#timer">Timer</a>
</li>
<li>
<a href="#summary">Summary</a>
</li>
<li>
<a href="#category">Category</a>
</li>
<li>
<a href="#songs">Songs</a>
</li>
</ul>
</nav>
<main>
<section id="timer" :class="{timerRunning:timerRunning, micThresholdExceeded:micThresholdExceeded}">
<Status ref="statusRef"/>
<Timer
v-if="currentWorkingInstrument"
@loggedTime="refreshPage"
:instruments="instruments"
:categories="categories"
v-model:timerRunning="timerRunning"
v-model:micThresholdExceeded="micThresholdExceeded"
/>
<section id="timer">
<MinsLeftToPracticeToday/>
<Timer/>
</section>
<section id="summary">
<Summary ref="summaryRef" />
<Summary/>
</section>
<section id="category">
<ShowCategory
v-if="currentWorkingInstrument"
ref="showCategoryRef"
/>
<ShowCategory/>
</section>
<section id="songs">
<Songs/>
@ -82,12 +25,4 @@
</template>
<style scoped>
.timerRunning {
background:lightgreen;
}
.timerRunning.micThresholdExceeded {
background:darkgreen;
color:white;
}
</style>

@ -1,47 +0,0 @@
<script setup>
import { onMounted, defineProps, ref } from 'vue';
import {
currentWorkingCategory,
currentWorkingInstrument,
instruments,
categories
} from '../libs/state.js'
const selectFirstInstrumentOfCategory = () => {
currentWorkingCategory.value = categories.value.find(category => category.instrument === currentWorkingInstrument.value.name)
}
const categoryInput = ref(null)
onMounted(()=>{
window.addEventListener('keydown', (event)=>{
if(event.ctrlKey === true){
if(event.key === 'c') {
categoryInput.value.focus();
}
}
})
})
</script>
<template>
<select ref="categoryInput" @change="selectFirstInstrumentOfCategory" v-model="currentWorkingInstrument">
<option :value="instrument" v-for="instrument in instruments">
{{instrument.name}}
</option>
</select>
<select v-model="currentWorkingCategory">
<option
v-for="category in categories.filter(currentCategory => currentCategory.instrument === currentWorkingInstrument.name)"
:value="category">
{{category.category}}
</option>
</select>
</template>
<style scoped>
select {
padding:1em;
}
</style>

@ -0,0 +1,37 @@
<script lang="ts">
import axios from 'axios';
export default {
data() {
return {
time_left:0,
mins_practiced_today:0
}
},
methods: {
refresh(){
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'mins-left-to-practice-today').then((response)=>{
this.time_left= response.data.time_left;
this.mins_practiced_today = response.data.mins_practiced_today;
})
}
},
mounted() {
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'mins-left-to-practice-today').then((response)=>{
this.time_left= response.data.time_left;
this.mins_practiced_today = response.data.mins_practiced_today;
})
}
}
</script>
<template>
<h2 @click="refresh">Time Summary</h2>
<h3>Mins Left to Practice Today</h3>
{{time_left}}
<h3>Mins Practiced Today</h3>
{{mins_practiced_today}}
</template>
<style scoped>
</style>

@ -1,76 +1,64 @@
<script setup>
import { formatSeconds } from '../libs/time.js'
import CategoryChooser from './category_chooser.vue'
import { onMounted, ref, watch, defineProps } from 'vue'
import {
description,
comments,
currentWorkingCategory,
currentWorkingInstrument
} from '../libs/state.js'
const categorySessions = ref([])
const copy = (event) => {
description.value = event.target.innerText
}
const copyComments = (event) => {
comments.value = event.target.innerText
}
onMounted(()=>{
loadCategory()
window.addEventListener('keydown', (event)=>{
if(event.ctrlKey === true){
if(event.key === 'l') {
description.value = categorySessions.value[0].description
} else if (event.key === 'm') {
comments.value = categorySessions.value[0].comments
}
<script lang="ts">
import axios from 'axios';
export default {
data() {
return {
categories:[],
categorySessions:[]
}
})
})
const loadCategory = async () => {
const response = await fetch(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'show-category/'+currentWorkingCategory.value.id)
categorySessions.value = await response.json()
},
methods: {
change(event){
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'show-category/'+event.target.value).then((response)=>{
this.categorySessions = response.data;
})
}
},
mounted() {
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'categories').then((response)=>{
this.categories = response.data
})
}
}
watch(currentWorkingCategory, loadCategory)
defineExpose({loadCategory})
</script>
<template>
<h2>Show Category</h2>
<CategoryChooser />
<select @change="change">
<option v-for="category in categories" v-bind:value="category.id">
{{category.id}}.
{{category.instrument}}
:
{{category.category}}
</option>
</select>
<table>
<thead>
<tr>
<th>description</th>
<th>comments</th>
<th>songs</th>
<th>duration</th>
<th>created at</th>
</tr>
<th>id</th>
<th>description</th>
<th>seconds</th>
<th>comments</th>
<th>created at</th>
<th>category id</th>
<th>category</th>
<th>instrument</th>
</tr>
</thead>
<tbody>
<tr v-for="session in categorySessions">
<td @click="copy">{{session.description}}</td>
<td @click="copyComments">{{session.comments}}</td>
<td>
<ul>
<li v-for="song in session.songs">
<a v-bind:href="'#/songs/' + song.id">
{{song.title}}
</a>
</li>
</ul>
</td>
<td>{{formatSeconds(session.seconds)}}</td>
<td>{{new Date(session.created_at).toLocaleString("en-US")}}</td>
<td>{{session.id}}</td>
<td>{{session.description}}</td>
<td>{{session.seconds}}</td>
<td>{{session.comments}}</td>
<td>{{session.created_at}}</td>
<td>{{session.category_id}}</td>
<td>{{session.category}}</td>
<td>{{session.instrument}}</td>
</tr>
</tbody>
</table>
</template>
<style scoped>
</style>

@ -1,25 +1,20 @@
<script setup>
import { onMounted, ref } from 'vue'
import { copiedSongs } from '../libs/state.js'
<script lang="ts">
import axios from 'axios';
const songs = ref([])
onMounted(async ()=>{
const response = await fetch(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'songs')
songs.value = await response.json()
})
const copy = (title) => {
navigator.clipboard.writeText(title);
}
const link = (song) => {
if(copiedSongs.value.findIndex(copiedSong=>copiedSong.id === song.id) === -1){
copiedSongs.value.push(song)
export default {
data() {
return {
songs:[]
}
},
mounted() {
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'songs').then((response)=>{
this.songs = response.data
})
}
}
const unlink = (song) => {
copiedSongs.value = copiedSongs.value.filter(currSong => currSong.id !== song.id)
}
</script>
<template>
<h2>Songs</h2>
<table>
@ -28,39 +23,17 @@
<th>id</th>
<th>title</th>
<th>notes</th>
<th>lyrics</th>
<th>original</th>
</tr>
</thead>
<tbody>
<tr v-for="song in songs">
<td>{{song.id}}</td>
<td>
<a v-bind:name="'/songs/' + song.id">
{{song.title}}
</a>
<br/>
<button @click="link(song)" v-if="copiedSongs.findIndex(currSong => currSong.id === song.id) === -1">Link</button>
<button @click="unlink(song)" v-if="copiedSongs.findIndex(currSong => currSong.id === song.id) !== -1">X</button>
</td>
<td>{{song.title}}</td>
<td>{{song.notes}}</td>
<td>
<a v-if="song.lyrics_link" target="lyrics" v-bind:href="song.lyrics_link">lyrics</a>
</td>
<td>
<a v-if="song.original_link" target="original" v-bind:href="song.original_link">original</a>
</td>
</tr>
</tbody>
</table>
</template>
<style scoped>
button {
padding: 1em 2em;
margin: 1em;
}
td:nth-child(2){
text-align:center;
}
</style>

@ -1,23 +0,0 @@
<script setup>
import { formatSeconds } from '../libs/time.js'
import { onMounted, ref, defineExpose } from 'vue'
const practiceStatus = ref({})
const loadData = async () => {
const response = await fetch(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'status')
practiceStatus.value = await response.json()
}
onMounted(loadData)
defineExpose({loadData})
</script>
<template>
<h2 @click="loadData">Practice Status</h2>
<h3>Time Left to Practice Today</h3>
{{formatSeconds(practiceStatus.seconds_left_to_practice_today)}}
<h3>Extra Time to Accumulate</h3>
{{formatSeconds(practiceStatus.seconds_left_to_get_ahead)}}
<h3>Time Practiced Today</h3>
{{formatSeconds(practiceStatus.seconds_practiced)}}
</template>

@ -1,32 +1,29 @@
<script setup>
import { onMounted, ref, defineExpose } from 'vue'
import {
currentWorkingCategory,
currentWorkingInstrument,
instruments,
categories
} from '../libs/state.js'
<script lang="ts">
import axios from 'axios';
const summary = ref([])
const loadData = async () => {
const response = await fetch(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'summary')
summary.value = await response.json()
}
onMounted(loadData)
const setWorkingCategory = (choice) => {
const chosenCategory = categories.value.find(category => category.id === choice.category_id)
currentWorkingCategory.value = chosenCategory
currentWorkingInstrument.value = instruments.value.find(instrument => instrument.name === chosenCategory.instrument)
export default {
data() {
return {
categories:[]
}
},
methods: {
refresh(){
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'summary').then((response)=>{
this.categories = response.data
})
}
},
mounted() {
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'summary').then((response)=>{
this.categories = response.data
})
}
}
defineExpose({loadData})
</script>
<template>
<h2 @click="loadData">Summary</h2>
<h2 @click="refresh">Summary</h2>
<table>
<thead>
<tr>
@ -38,7 +35,7 @@
</tr>
</thead>
<tbody>
<tr v-for="category in summary" @click="setWorkingCategory(category)">
<tr v-for="category in categories">
<td>{{category.category_id}}</td>
<td>{{category.category}}</td>
<td>{{category.chunks_practiced}}</td>
@ -48,3 +45,6 @@
</tbody>
</table>
</template>
<style scoped>
</style>

@ -1,258 +1,179 @@
<script setup>
import { onMounted, getCurrentInstance, ref } from 'vue'
const micThresholdExceeded = defineModel('micThresholdExceeded')
const timerRunning = defineModel('timerRunning')
<script lang="ts">
import axios from 'axios';
const { proxy } = getCurrentInstance();
const padDigits = (value) => {
if(value < 10){
return '0'+value;
} else {
return value;
}
}
const descriptionInput = ref(null)
const getMinutes = (seconds) => {
return Math.floor( seconds / 60 );
}
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';
const getAccumulatedSeconds = (newerTime, olderTime) => {
return Math.floor((newerTime-olderTime)/1000);
}
import CategoryChooser from './category_chooser.vue'
import { getHours, getMinutes, createTimeObj, getAccumulatedSeconds, formatSeconds } from '../libs/time.js'
import {
copiedSongs,
currentWorkingCategory,
currentWorkingInstrument,
description,
comments
} from '../libs/state.js'
const formatSeconds = (total) => {
const minutes = getMinutes(total);
const seconds = total - minutes*60;
return `${minutes}:${padDigits(seconds)}`;
}
export default {
data() {
return {
submitting:false,
categories:[],
running: false,
startTime:0,
savedPreviousSeconds:0,
totalSeconds:0,
description:'',
practice_category_id:0,
comments:'',
manualHours:0,
manualMinutes:0,
manualSeconds:0,
analyser:null,
secondsToSubtract:0,
micThreshold:-1,
micThreshold:0,
micLevel:0,
practice_category_id:0
micThresholdExceeded:false
}
},
emits: ['loggedTime'],
components: {
CategoryChooser
},
methods: {
formatSeconds,
formatTime(seconds){
return formatSeconds(seconds)
},
submit(event){
event.preventDefault();
if(this.submitting){
return
} else {
this.submitting = true
}
const reqBody = {
description:description.value,
description:this.description,
seconds: this.totalSeconds,
practice_category_id: currentWorkingCategory.value.id,
}
if(copiedSongs.value.length > 0){
reqBody.songs = copiedSongs.value
practice_category_id: this.practice_category_id
}
if(comments.value){
reqBody.comments = comments.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.description = null;
this.totalSeconds = 0;
this.savedPreviousSeconds = 0;
comments.value = 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
this.comments = null;
});
},
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.startTime = Date.now();
this.running = true;
this.intervalID = setInterval(()=>{
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.micThresholdExceeded = true;
} else {
this.secondsToSubtract++;
this.micThresholdExceeded = false;
}
this.totalSeconds = getAccumulatedSeconds(Date.now(), this.startTime) - this.secondsToSubtract + this.savedPreviousSeconds;
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
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
}, 1000);
},
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);
}
this.running = 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) => {
}
},
mounted() {
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'categories').then((response)=>{
this.categories = response.data
})
const audioContext = new AudioContext();
this.analyser = audioContext.createAnalyser();
const microphone = audioContext.createMediaStreamSource(stream);
const scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1);
navigator.mediaDevices.getUserMedia({
audio: true
}).then((stream) => {
this.analyser.smoothingTimeConstant = 0.8;
this.analyser.fftSize = 1024;
const audioContext = new AudioContext();
this.analyser = audioContext.createAnalyser();
const microphone = audioContext.createMediaStreamSource(stream);
const scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1);
microphone.connect(this.analyser);
this.analyser.connect(scriptProcessor);
scriptProcessor.connect(audioContext.destination);
this.analyser.smoothingTimeConstant = 0.8;
this.analyser.fftSize = 1024;
})
.catch((err) => {
console.error(err);
});
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>
<div :class="{running:running, micThresholdExceeded:micThresholdExceeded}">
<em>
{{formatSeconds(totalSeconds)}}
{{formatTime(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>
Mic Threshold (current level: {{Math.round(micLevel)}}): <input type="number" 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" 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>
<input v-model="description" type="text" maxlength="128"/>
<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>Practice Category</label>
<select v-model="practice_category_id">
<option v-for="category in categories" v-bind:value="category.id">
{{category.id}}.
{{category.instrument}}
:
{{category.category}}
</option>
</select>
<input type="Submit"/>
<label>Hours:</label>
<input type="number" @change="setSecondsManually" v-model="manualHours"/>
<label>Minutes:</label>
@ -267,11 +188,15 @@ label, [type="submit"] {
display:block;
}
input[type="number"] {
width: 5em;
.running {
background:lightgreen;
}
button, em, [type="submit"]{
.running.micThresholdExceeded {
background:green;
}
button, em {
margin: 1em 0;
display:block;
font-size:3em;
@ -282,5 +207,4 @@ button, em, [type="submit"]{
select {
width: 100%;
}
</style>

@ -1,8 +0,0 @@
import { ref } from 'vue'
export const copiedSongs = ref([])
export const currentWorkingCategory = ref(null)
export const currentWorkingInstrument = ref(null)
export const instruments = ref([])
export const categories = ref([])
export const description = ref(null)
export const comments = ref(null)

@ -1,33 +0,0 @@
const getHours = (seconds) => {
return Math.floor( seconds / 60 / 60 );
}
const getMinutes = (seconds) => {
return Math.floor( seconds / 60 );
}
const createTimeObj = (seconds) => {
const hours = getHours(seconds)
let remainingSeconds = seconds - hours * 60 * 60
const minutes = getMinutes(remainingSeconds)
remainingSeconds -= minutes * 60
return { hours, minutes, seconds: remainingSeconds }
}
const getAccumulatedSeconds = (newerTime, olderTime) => {
return Math.floor((newerTime-olderTime)/1000);
}
const formatSeconds = (total, includeHours = true) => {
const timeObj = createTimeObj(Math.abs(total))
let result = (total < 0) ? '-' : ''
if(includeHours) {
result += `${timeObj.hours}:${timeObj.minutes.toString().padStart(2,'0')}`
} else {
result += `${timeObj.hours*60 + timeObj.minutes}`
}
result += `:${timeObj.seconds.toString().padStart(2,'0')}`
return result
}
export { getHours, getMinutes, createTimeObj, getAccumulatedSeconds, formatSeconds }
Loading…
Cancel
Save