diff --git a/README.md b/README.md index 2d33d80..8573dd2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ This is a simple Vue application using Vite as the build system. ## How to use The configuration is specified using the URL query parameters: - `name`: the name of the meeting/stand-up -- `total`: the total duration of the meeting, in seconds +- `total`: the total duration of the meeting, in seconds. + If omitted, the sum of the time for each person will be used as the total. - any other query adds a line in the list of people. For example: - `Tom=60` adds a line with name "Tom" and duration of 60 seconds diff --git a/package-lock.json b/package-lock.json index 2b0f8a5..64f2b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,17 @@ "name": "standup-timer", "version": "0.0.0", "dependencies": { - "vue": "^3.3.11" + "vue": "^3.4.0" }, "devDependencies": { "@testing-library/vue": "^8.0.1", - "@vitejs/plugin-vue": "^4.5.2", + "@vitejs/plugin-vue": "^5.0.0", "@vue/test-utils": "^2.4.4", "happy-dom": "^13.3.8", - "typescript": "^5.2.2", - "vite": "^5.0.8", + "typescript": "^5.4.0", + "vite": "^5.2.0", "vitest": "^1.2.2", - "vue-tsc": "^1.8.25" + "vue-tsc": "^2.0.0" } }, "node_modules/@babel/code-frame": { @@ -817,15 +817,15 @@ "dev": true }, "node_modules/@vitejs/plugin-vue": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", - "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", "dev": true, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^4.0.0 || ^5.0.0", + "vite": "^5.0.0", "vue": "^3.2.25" } }, @@ -963,30 +963,30 @@ "dev": true }, "node_modules/@volar/language-core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", - "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.5.tgz", + "integrity": "sha512-2htyAuxRrAgETmFeUhT4XLELk3LiEcqoW/B8YUXMF6BrGWLMwIR09MFaZYvrA2UhbdAeSyeQ726HaWSWkexUcQ==", "dev": true, "dependencies": { - "@volar/source-map": "1.11.1" + "@volar/source-map": "2.2.5" } }, "node_modules/@volar/source-map": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", - "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.5.tgz", + "integrity": "sha512-wrOEIiZNf4E+PWB0AxyM4tfhkfldPsb3bxg8N6FHrxJH2ohar7aGu48e98bp3pR9HUA7P/pR9VrLmkTrgCCnWQ==", "dev": true, "dependencies": { - "muggle-string": "^0.3.1" + "muggle-string": "^0.4.0" } }, "node_modules/@volar/typescript": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", - "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.5.tgz", + "integrity": "sha512-eSV/n75+ppfEVugMC/salZsI44nXDPAyL6+iTYCNLtiLHGJsnMv9GwiDMujrvAUj/aLQyqRJgYtXRoxop2clCw==", "dev": true, "dependencies": { - "@volar/language-core": "1.11.1", + "@volar/language-core": "2.2.5", "path-browserify": "^1.0.1" } }, @@ -1047,18 +1047,16 @@ } }, "node_modules/@vue/language-core": { - "version": "1.8.27", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", - "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.19.tgz", + "integrity": "sha512-A9EGOnvb51jOvnCYoRLnMP+CcoPlbZVxI9gZXE/y2GksRWM6j/PrLEIC++pnosWTN08tFpJgxhSS//E9v/Sg+Q==", "dev": true, "dependencies": { - "@volar/language-core": "~1.11.1", - "@volar/source-map": "~1.11.1", - "@vue/compiler-dom": "^3.3.0", - "@vue/shared": "^3.3.0", + "@volar/language-core": "~2.2.4", + "@vue/compiler-dom": "^3.4.0", + "@vue/shared": "^3.4.0", "computeds": "^0.0.1", "minimatch": "^9.0.3", - "muggle-string": "^0.3.1", "path-browserify": "^1.0.1", "vue-template-compiler": "^2.7.14" }, @@ -2358,9 +2356,9 @@ "dev": true }, "node_modules/muggle-string": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", - "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "dev": true }, "node_modules/nanoid": { @@ -3209,13 +3207,13 @@ } }, "node_modules/vue-tsc": { - "version": "1.8.27", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", - "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.19.tgz", + "integrity": "sha512-JWay5Zt2/871iodGF72cELIbcAoPyhJxq56mPPh+M2K7IwI688FMrFKc/+DvB05wDWEuCPexQJ6L10zSwzzapg==", "dev": true, "dependencies": { - "@volar/typescript": "~1.11.1", - "@vue/language-core": "1.8.27", + "@volar/typescript": "~2.2.4", + "@vue/language-core": "2.0.19", "semver": "^7.5.4" }, "bin": { diff --git a/package.json b/package.json index 71f6f66..2dc13b8 100644 --- a/package.json +++ b/package.json @@ -10,16 +10,16 @@ "test": "vitest" }, "dependencies": { - "vue": "^3.3.11" + "vue": "^3.4.0" }, "devDependencies": { "@testing-library/vue": "^8.0.1", - "@vitejs/plugin-vue": "^4.5.2", + "@vitejs/plugin-vue": "^5.0.0", "@vue/test-utils": "^2.4.4", "happy-dom": "^13.3.8", - "typescript": "^5.2.2", - "vite": "^5.0.8", + "typescript": "^5.4.0", + "vite": "^5.2.0", "vitest": "^1.2.2", - "vue-tsc": "^1.8.25" + "vue-tsc": "^2.0.0" } } diff --git a/src/App.vue b/src/App.vue index 947d1c8..f27eb14 100644 --- a/src/App.vue +++ b/src/App.vue @@ -33,6 +33,9 @@ function toggle(index: number) { const remaining = computed(() => Math.max(0, totalTime - timers.value.reduce((sum, t) => sum + t.seconds, 0))); const totalTimerTime = computed(() => timers.value.reduce((sum, t) => sum + t.max, 0)); +const totalUsed = computed(() => timers.value.reduce((sum, t) => sum + t.seconds, 0)); +const totalExtra = computed(() => Math.max(0, totalUsed.value - timers.value.reduce((sum, t) => sum + t.max, 0))); + function randomize() { for (let i = timers.value.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); @@ -95,7 +98,10 @@ async function openDocumentPip() {

{{ standupName }}

- {{ secToTime(totalTime) }} ({{ secToTime(remaining) }} remaining) + {{ secToTime(totalTime) }}  + ({{ secToTime(remaining) }} remaining) + +{{ secToTime(totalExtra) }} +
@@ -119,7 +125,7 @@ async function openDocumentPip() { diff --git a/src/components/TimerLine.vue b/src/components/TimerLine.vue index e9535cc..0818978 100644 --- a/src/components/TimerLine.vue +++ b/src/components/TimerLine.vue @@ -39,7 +39,7 @@ const overtime = computed(() => props.seconds > props.max); .remove-line { display: none; color: var(--color3); - + position: absolute; left: -30px; width: 30px; diff --git a/src/composables/urlQueryParams.ts b/src/composables/urlQueryParams.ts index de1a890..bd2352c 100644 --- a/src/composables/urlQueryParams.ts +++ b/src/composables/urlQueryParams.ts @@ -1,17 +1,18 @@ export interface TimerItem { name: string; - + seconds: number; max: number; } export function getUrlQueryParameters() { const searchParams = new URLSearchParams(window.location.search); - + const timers: TimerItem[] = []; let standupName = "Stand-up timer"; let totalTime = 0; - + let timersTotal = 0; + for (const p of searchParams) { const paramName = p[0].trim(); @@ -28,9 +29,13 @@ export function getUrlQueryParameters() { totalTime = seconds; } else { timers.push({name: paramName, seconds: 0, max: seconds}); + timersTotal += seconds; } } } + // If the totalTime was not specified, then put the sum of the timers. + if (totalTime === 0) totalTime = timersTotal; + return {standupName, totalTime, timers} }