Added global controls
This commit is contained in:
5
src/assets/reset.svg
Normal file
5
src/assets/reset.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#f7f7f7" class="bi bi-clock-history" viewBox="0 0 16 16">
|
||||||
|
<path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022l-.074.997zm2.004.45a7.003 7.003 0 0 0-.985-.299l.219-.976c.383.086.76.2 1.126.342l-.36.933zm1.37.71a7.01 7.01 0 0 0-.439-.27l.493-.87a8.025 8.025 0 0 1 .979.654l-.615.789a6.996 6.996 0 0 0-.418-.302zm1.834 1.79a6.99 6.99 0 0 0-.653-.796l.724-.69c.27.285.52.59.747.91l-.818.576zm.744 1.352a7.08 7.08 0 0 0-.214-.468l.893-.45a7.976 7.976 0 0 1 .45 1.088l-.95.313a7.023 7.023 0 0 0-.179-.483zm.53 2.507a6.991 6.991 0 0 0-.1-1.025l.985-.17c.067.386.106.778.116 1.17l-1 .025zm-.131 1.538c.033-.17.06-.339.081-.51l.993.123a7.957 7.957 0 0 1-.23 1.155l-.964-.267c.046-.165.086-.332.12-.501zm-.952 2.379c.184-.29.346-.594.486-.908l.914.405c-.16.36-.345.706-.555 1.038l-.845-.535zm-.964 1.205c.122-.122.239-.248.35-.378l.758.653a8.073 8.073 0 0 1-.401.432l-.707-.707z"/>
|
||||||
|
<path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0v1z"/>
|
||||||
|
<path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
3
src/assets/stop.svg
Normal file
3
src/assets/stop.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stop-fill" viewBox="0 0 16 16">
|
||||||
|
<path d="M5 3.5h6A1.5 1.5 0 0 1 12.5 5v6a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 11V5A1.5 1.5 0 0 1 5 3.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 248 B |
@@ -84,9 +84,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState(['categories']),
|
||||||
categories: (state) => state.categories,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -104,6 +102,7 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 64px;
|
padding: 0 64px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
z-index: 900;
|
||||||
|
|
||||||
.add-category {
|
.add-category {
|
||||||
color: $darker;
|
color: $darker;
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ const routes = [
|
|||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: Home,
|
component: Home,
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// path: '/about',
|
|
||||||
// name: 'About',
|
|
||||||
// // route level code-splitting
|
|
||||||
// // this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// // which is lazy-loaded when the route is visited.
|
|
||||||
// component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
|
|
||||||
// },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: $dark;
|
color: $dark;
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export default createStore({
|
|||||||
removeTask(state, name) {
|
removeTask(state, name) {
|
||||||
state.tasks = state.tasks.filter((task) => task.name !== name);
|
state.tasks = state.tasks.filter((task) => task.name !== name);
|
||||||
},
|
},
|
||||||
|
removeAllTasks(state) {
|
||||||
|
state.tasks = [];
|
||||||
|
},
|
||||||
startTask(state, name) {
|
startTask(state, name) {
|
||||||
state.tasks = state.tasks.map((task) => {
|
state.tasks = state.tasks.map((task) => {
|
||||||
const newTask = task;
|
const newTask = task;
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="category-dropdown">
|
|
||||||
<div class="title">Assign Category</div>
|
|
||||||
<div
|
|
||||||
v-for="category in categories"
|
|
||||||
:key="category"
|
|
||||||
class="category"
|
|
||||||
:style="{ background: stringToColor(category) }"
|
|
||||||
@click="$emit('selected', category)"
|
|
||||||
>
|
|
||||||
{{ category }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'vuex';
|
|
||||||
import toColor from '@/stringToColor';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
methods: {
|
|
||||||
stringToColor(str) {
|
|
||||||
return toColor(str);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
categories: (state) => state.categories,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.category-dropdown {
|
|
||||||
position: absolute;
|
|
||||||
background: $light;
|
|
||||||
padding: 48px 12px 12px 12px;
|
|
||||||
border-radius: 16px;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: max-content;
|
|
||||||
left: 50%;
|
|
||||||
top: -12px;
|
|
||||||
z-index: 100;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
|
|
||||||
.title {
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
width: fit-content;
|
|
||||||
color: $light;
|
|
||||||
background: $dark;
|
|
||||||
padding: 4px 12px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category {
|
|
||||||
margin-top: 8px;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
143
src/views/Home/GlobalTaskControls.vue
Normal file
143
src/views/Home/GlobalTaskControls.vue
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<template>
|
||||||
|
<div class="global-controls">
|
||||||
|
<Time
|
||||||
|
:time="totalTime"
|
||||||
|
:pulsing="atLeastOneRunning"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
Total
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<transition name="slide-up">
|
||||||
|
<div
|
||||||
|
v-if="atLeastOneRunning"
|
||||||
|
class="stop"
|
||||||
|
@click="stop"
|
||||||
|
>
|
||||||
|
<img src="@/assets/stop.svg" />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<div @click="$refs.resetAllModal.open()">
|
||||||
|
<img src="@/assets/reset.svg" />
|
||||||
|
</div>
|
||||||
|
<div @click="$refs.removeAllModal.open()">
|
||||||
|
<img src="@/assets/trash.svg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ResetAllModal ref="resetAllModal"/>
|
||||||
|
<RemoveAllModal ref="removeAllModal"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
|
||||||
|
import Time from './Time.vue';
|
||||||
|
import RemoveAllModal from './RemoveAllModal.vue';
|
||||||
|
import ResetAllModal from './ResetAllModal.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Time,
|
||||||
|
RemoveAllModal,
|
||||||
|
ResetAllModal,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
totalTime: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['tasks']),
|
||||||
|
atLeastOneRunning() {
|
||||||
|
return this.tasks.reduce(
|
||||||
|
(atLeastOneRunning, task) => task.running || atLeastOneRunning,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
setInterval(() => {
|
||||||
|
this.totalTime = this.getTotalTime();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTotalTime() {
|
||||||
|
return this.tasks.reduce((acc, task) => {
|
||||||
|
if (task.startedAt) {
|
||||||
|
const timePassed = Date.now() - task.startedAt;
|
||||||
|
return acc + task.totalTime + timePassed;
|
||||||
|
}
|
||||||
|
return acc + task.totalTime;
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
this.tasks.forEach((task) => {
|
||||||
|
if (task.running) {
|
||||||
|
this.$store.commit('stopTask', task.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.global-controls {
|
||||||
|
max-width: $max-width;
|
||||||
|
height: 32px;
|
||||||
|
margin: 32px auto;
|
||||||
|
padding: 0 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.time {
|
||||||
|
min-width: 104px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-left: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: $light;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-from,
|
||||||
|
.slide-up-leave-to {
|
||||||
|
transform: translateY(-64px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $max-width) {
|
||||||
|
.global-controls {
|
||||||
|
margin: 16px auto -16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<TheCategoryBar @select="select" />
|
<TheCategoryBar @select="select" />
|
||||||
|
<GlobalTaskControls />
|
||||||
<TheTaskList :selectedCategory="selectedCategory" />
|
<TheTaskList :selectedCategory="selectedCategory" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -9,12 +10,14 @@
|
|||||||
import { mapState, mapMutations } from 'vuex';
|
import { mapState, mapMutations } from 'vuex';
|
||||||
|
|
||||||
import TheCategoryBar from '@/components/TheCategoryBar.vue';
|
import TheCategoryBar from '@/components/TheCategoryBar.vue';
|
||||||
|
import GlobalTaskControls from './GlobalTaskControls.vue';
|
||||||
import TheTaskList from './TheTaskList.vue';
|
import TheTaskList from './TheTaskList.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
components: {
|
components: {
|
||||||
TheCategoryBar,
|
TheCategoryBar,
|
||||||
|
GlobalTaskControls,
|
||||||
TheTaskList,
|
TheTaskList,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
55
src/views/Home/RemoveAllModal.vue
Normal file
55
src/views/Home/RemoveAllModal.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<Modal ref="modal">
|
||||||
|
<div class="title">Are you sure you want to delete all of your tasks?</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button @click="close">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button @click="removeAllTasksAndClose">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapMutations } from 'vuex';
|
||||||
|
|
||||||
|
import Modal from '@/components/Modal.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations(['removeAllTasks']),
|
||||||
|
removeAllTasksAndClose() {
|
||||||
|
this.removeAllTasks();
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
this.$refs.modal.open();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$refs.modal.close();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.title {
|
||||||
|
margin-right: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
src/views/Home/ResetAllModal.vue
Normal file
55
src/views/Home/ResetAllModal.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<Modal ref="modal">
|
||||||
|
<div class="title">Are you sure you want to reset all of your tasks to 0?</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button @click="close">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button @click="resetTasksAndClose">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapMutations } from 'vuex';
|
||||||
|
|
||||||
|
import Modal from '@/components/Modal.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations(['resetTasks']),
|
||||||
|
resetTasksAndClose() {
|
||||||
|
this.resetTasks();
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
this.$refs.modal.open();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$refs.modal.close();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.title {
|
||||||
|
margin-right: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="task" :class="{ active: task.running }">
|
<div class="task">
|
||||||
<div class="time">
|
<Time
|
||||||
{{ formattedTime }}
|
:time="time"
|
||||||
</div>
|
:pulsing="task.running"
|
||||||
|
/>
|
||||||
<div class="toggle-state">
|
<div class="toggle-state">
|
||||||
<div v-if="task.running" @click="stopTask">
|
<div v-if="task.running" @click="stopTask">
|
||||||
<img src="@/assets/pause.svg" />
|
<img src="@/assets/pause.svg" />
|
||||||
@@ -44,10 +45,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import toColor from '@/stringToColor';
|
import toColor from '@/stringToColor';
|
||||||
|
|
||||||
|
import Time from './Time.vue';
|
||||||
import CategoryModal from './CategoryModal.vue';
|
import CategoryModal from './CategoryModal.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Time,
|
||||||
CategoryModal,
|
CategoryModal,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -86,23 +89,8 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.task.totalTime;
|
return this.task.totalTime;
|
||||||
},
|
},
|
||||||
formattedTime() {
|
|
||||||
let { time } = this;
|
|
||||||
const msInASec = 1000;
|
|
||||||
const msInAMin = msInASec * 60;
|
|
||||||
const msInAHour = msInAMin * 60;
|
|
||||||
const msInADay = msInAHour * 24;
|
|
||||||
|
|
||||||
const days = Math.floor(time / msInADay);
|
|
||||||
time -= days * msInADay;
|
|
||||||
const hours = Math.floor(time / msInAHour);
|
|
||||||
time -= hours * msInAHour;
|
|
||||||
const mins = Math.floor(time / msInAMin);
|
|
||||||
|
|
||||||
return `${days}d ${hours}h ${mins}m`;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
beforeMount() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.timePassed = Date.now() - this.task.startedAt;
|
this.timePassed = Date.now() - this.task.startedAt;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -156,16 +144,6 @@ export default {
|
|||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
// .dropdown {
|
|
||||||
// display: none;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// &:hover {
|
|
||||||
// .dropdown {
|
|
||||||
// display: block;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete {
|
.delete {
|
||||||
@@ -174,21 +152,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
from {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.task.active {
|
|
||||||
.time {
|
|
||||||
animation: pulse 1s infinite alternate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $max-width) {
|
@media screen and (max-width: $max-width) {
|
||||||
.task {
|
.task {
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState(['tasks']),
|
||||||
tasks: (state) => state.tasks,
|
|
||||||
}),
|
|
||||||
filteredTasks() {
|
filteredTasks() {
|
||||||
if (this.selectedCategory) {
|
if (this.selectedCategory) {
|
||||||
return this.tasks.filter((task) => task.category === this.selectedCategory);
|
return this.tasks.filter((task) => task.category === this.selectedCategory);
|
||||||
|
|||||||
55
src/views/Home/Time.vue
Normal file
55
src/views/Home/Time.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="time"
|
||||||
|
:class="{ pulsing }"
|
||||||
|
>
|
||||||
|
{{ formattedTime }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
time: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
pulsing: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formattedTime() {
|
||||||
|
let { time } = this;
|
||||||
|
const msInASec = 1000;
|
||||||
|
const msInAMin = msInASec * 60;
|
||||||
|
const msInAHour = msInAMin * 60;
|
||||||
|
const msInADay = msInAHour * 24;
|
||||||
|
|
||||||
|
const days = Math.floor(time / msInADay);
|
||||||
|
time -= days * msInADay;
|
||||||
|
const hours = Math.floor(time / msInAHour);
|
||||||
|
time -= hours * msInAHour;
|
||||||
|
const mins = Math.floor(time / msInAMin);
|
||||||
|
|
||||||
|
return `${days}d ${hours}h ${mins}m`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@keyframes pulse {
|
||||||
|
from {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulsing {
|
||||||
|
animation: pulse 1s infinite alternate;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user