commit f3cf19ae77d63eb7f9de6a562af72f29177e5cca
parent 7a9ffcd827c2e0603c8486d49e190c0e5e222513
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sun, 19 May 2024 19:05:57 -0700
Merge pull request #8 from ashermorgan/vue-composition-api
Migrate to Vue Composition API
Diffstat:
21 files changed, 2003 insertions(+), 2143 deletions(-)
diff --git a/package-lock.json b/package-lock.json
@@ -8,22 +8,22 @@
"name": "running-tools",
"version": "1.3.0",
"dependencies": {
- "feather-icons": "^4.29.0",
- "vue": "^3.3.4",
+ "feather-icons": "^4.29.2",
+ "vue": "^3.4.27",
"vue-feather": "^2.0.0",
- "vue-router": "^4.2.2"
+ "vue-router": "^4.3.2"
},
"devDependencies": {
"@playwright/test": "^1.44.0",
- "@types/node": "^20.12.11",
- "@vitejs/plugin-vue": "^4.2.3",
- "@vue/test-utils": "^2.4.0",
- "eslint": "^8.39.0",
- "eslint-plugin-vue": "^9.11.0",
+ "@types/node": "^20.12.12",
+ "@vitejs/plugin-vue": "^4.6.2",
+ "@vue/test-utils": "^2.4.6",
+ "eslint": "^8.57.0",
+ "eslint-plugin-vue": "^9.26.0",
"jsdom": "^22.1.0",
"pwa-asset-generator": "^6.3.1",
- "vite": "^4.3.9",
- "vite-plugin-pwa": "^0.16.4",
+ "vite": "^4.5.3",
+ "vite-plugin-pwa": "^0.16.7",
"vitest": "^0.32.4"
}
},
@@ -646,9 +646,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.23.6",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
- "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
+ "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -2295,23 +2295,23 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.5.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz",
- "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==",
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
+ "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
"dev": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/eslintrc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
- "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.5.2",
+ "espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -2327,22 +2327,22 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.43.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
- "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
- "version": "0.11.10",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
- "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
+ "version": "0.11.14",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"dependencies": {
- "@humanwhocodes/object-schema": "^1.2.1",
- "debug": "^4.1.1",
+ "@humanwhocodes/object-schema": "^2.0.2",
+ "debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
@@ -2363,11 +2363,55 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
- "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"dev": true
},
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/@jest/schemas": {
"version": "29.6.0",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz",
@@ -2487,6 +2531,22 @@
"node": ">= 8"
}
},
+ "node_modules/@one-ini/wasm": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
+ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+ "dev": true
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@playwright/test": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz",
@@ -2566,9 +2626,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.12.11",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
- "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
+ "version": "20.12.12",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
+ "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -2605,16 +2665,22 @@
"@types/node": "*"
}
},
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
+ },
"node_modules/@vitejs/plugin-vue": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz",
- "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==",
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
+ "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
"dev": true,
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
- "vite": "^4.0.0",
+ "vite": "^4.0.0 || ^5.0.0",
"vue": "^3.2.25"
}
},
@@ -2714,133 +2780,108 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
- "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz",
+ "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==",
"dependencies": {
- "@babel/parser": "^7.21.3",
- "@vue/shared": "3.3.4",
+ "@babel/parser": "^7.24.4",
+ "@vue/shared": "3.4.27",
+ "entities": "^4.5.0",
"estree-walker": "^2.0.2",
- "source-map-js": "^1.0.2"
+ "source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
- "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz",
+ "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==",
"dependencies": {
- "@vue/compiler-core": "3.3.4",
- "@vue/shared": "3.3.4"
+ "@vue/compiler-core": "3.4.27",
+ "@vue/shared": "3.4.27"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
- "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
- "dependencies": {
- "@babel/parser": "^7.20.15",
- "@vue/compiler-core": "3.3.4",
- "@vue/compiler-dom": "3.3.4",
- "@vue/compiler-ssr": "3.3.4",
- "@vue/reactivity-transform": "3.3.4",
- "@vue/shared": "3.3.4",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz",
+ "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==",
+ "dependencies": {
+ "@babel/parser": "^7.24.4",
+ "@vue/compiler-core": "3.4.27",
+ "@vue/compiler-dom": "3.4.27",
+ "@vue/compiler-ssr": "3.4.27",
+ "@vue/shared": "3.4.27",
"estree-walker": "^2.0.2",
- "magic-string": "^0.30.0",
- "postcss": "^8.1.10",
- "source-map-js": "^1.0.2"
+ "magic-string": "^0.30.10",
+ "postcss": "^8.4.38",
+ "source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
- "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz",
+ "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==",
"dependencies": {
- "@vue/compiler-dom": "3.3.4",
- "@vue/shared": "3.3.4"
+ "@vue/compiler-dom": "3.4.27",
+ "@vue/shared": "3.4.27"
}
},
"node_modules/@vue/devtools-api": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz",
- "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
+ "version": "6.6.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
+ "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
},
"node_modules/@vue/reactivity": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
- "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz",
+ "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==",
"dependencies": {
- "@vue/shared": "3.3.4"
- }
- },
- "node_modules/@vue/reactivity-transform": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
- "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
- "dependencies": {
- "@babel/parser": "^7.20.15",
- "@vue/compiler-core": "3.3.4",
- "@vue/shared": "3.3.4",
- "estree-walker": "^2.0.2",
- "magic-string": "^0.30.0"
+ "@vue/shared": "3.4.27"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
- "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz",
+ "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==",
"dependencies": {
- "@vue/reactivity": "3.3.4",
- "@vue/shared": "3.3.4"
+ "@vue/reactivity": "3.4.27",
+ "@vue/shared": "3.4.27"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
- "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz",
+ "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==",
"dependencies": {
- "@vue/runtime-core": "3.3.4",
- "@vue/shared": "3.3.4",
- "csstype": "^3.1.1"
+ "@vue/runtime-core": "3.4.27",
+ "@vue/shared": "3.4.27",
+ "csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
- "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz",
+ "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==",
"dependencies": {
- "@vue/compiler-ssr": "3.3.4",
- "@vue/shared": "3.3.4"
+ "@vue/compiler-ssr": "3.4.27",
+ "@vue/shared": "3.4.27"
},
"peerDependencies": {
- "vue": "3.3.4"
+ "vue": "3.4.27"
}
},
"node_modules/@vue/shared": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
- "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz",
+ "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA=="
},
"node_modules/@vue/test-utils": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.0.tgz",
- "integrity": "sha512-BKB9aj1yky63/I3IwSr1FjUeHYsKXI7D6S9F378AHt7a5vC0dLkOBtSsFXoRGC/7BfHmiB9HRhT+i9xrUHoAKw==",
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz",
+ "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==",
"dev": true,
"dependencies": {
- "js-beautify": "1.14.6",
- "vue-component-type-helpers": "1.6.5"
- },
- "peerDependencies": {
- "@vue/compiler-dom": "^3.0.1",
- "@vue/server-renderer": "^3.0.1",
- "vue": "^3.0.1"
- },
- "peerDependenciesMeta": {
- "@vue/compiler-dom": {
- "optional": true
- },
- "@vue/server-renderer": {
- "optional": true
- }
+ "js-beautify": "^1.14.9",
+ "vue-component-type-helpers": "^2.0.0"
}
},
"node_modules/abab": {
@@ -2850,10 +2891,13 @@
"dev": true
},
"node_modules/abbrev": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+ "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+ "dev": true,
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
},
"node_modules/acorn": {
"version": "8.9.0",
@@ -3582,9 +3626,9 @@
}
},
"node_modules/csstype": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
- "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/data-urls": {
"version": "4.0.0",
@@ -3803,50 +3847,67 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
"node_modules/editorconfig": {
- "version": "0.15.3",
- "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
- "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
+ "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
"dev": true,
"dependencies": {
- "commander": "^2.19.0",
- "lru-cache": "^4.1.5",
- "semver": "^5.6.0",
- "sigmund": "^1.0.1"
+ "@one-ini/wasm": "0.1.1",
+ "commander": "^10.0.0",
+ "minimatch": "9.0.1",
+ "semver": "^7.5.3"
},
"bin": {
"editorconfig": "bin/editorconfig"
+ },
+ "engines": {
+ "node": ">=14"
}
},
- "node_modules/editorconfig/node_modules/lru-cache": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
- "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+ "node_modules/editorconfig/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
- "pseudomap": "^1.0.2",
- "yallist": "^2.1.2"
+ "balanced-match": "^1.0.0"
}
},
- "node_modules/editorconfig/node_modules/semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "node_modules/editorconfig/node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
- "bin": {
- "semver": "bin/semver"
+ "engines": {
+ "node": ">=14"
}
},
- "node_modules/editorconfig/node_modules/yallist": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
- "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
- "dev": true
+ "node_modules/editorconfig/node_modules/minimatch": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
+ "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
},
"node_modules/ejs": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
- "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dev": true,
"dependencies": {
"jake": "^10.8.5"
@@ -3864,6 +3925,12 @@
"integrity": "sha512-kKiHnbrHME7z8E6AYaw0ehyxY5+hdaRmeUbjBO22LZMdqTYCO29EvF0T1cQ3pJ1RN5fyMcHl1Lmcsdt9WWJpJQ==",
"dev": true
},
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -3877,7 +3944,6 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
"engines": {
"node": ">=0.12"
},
@@ -4033,27 +4099,28 @@
}
},
"node_modules/eslint": {
- "version": "8.43.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
- "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+ "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.4.0",
- "@eslint/eslintrc": "^2.0.3",
- "@eslint/js": "8.43.0",
- "@humanwhocodes/config-array": "^0.11.10",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.0",
+ "@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
- "ajv": "^6.10.0",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.0",
- "eslint-visitor-keys": "^3.4.1",
- "espree": "^9.5.2",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -4063,7 +4130,6 @@
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
- "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
@@ -4073,9 +4139,8 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
+ "optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
"bin": {
@@ -4089,30 +4154,31 @@
}
},
"node_modules/eslint-plugin-vue": {
- "version": "9.15.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz",
- "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==",
+ "version": "9.26.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.26.0.tgz",
+ "integrity": "sha512-eTvlxXgd4ijE1cdur850G6KalZqk65k1JKoOI2d1kT3hr8sPD07j1q98FRFdNnpxBELGPWxZmInxeHGF/GxtqQ==",
"dev": true,
"dependencies": {
- "@eslint-community/eslint-utils": "^4.3.0",
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
"natural-compare": "^1.4.0",
- "nth-check": "^2.0.1",
- "postcss-selector-parser": "^6.0.9",
- "semver": "^7.3.5",
- "vue-eslint-parser": "^9.3.0",
+ "nth-check": "^2.1.1",
+ "postcss-selector-parser": "^6.0.15",
+ "semver": "^7.6.0",
+ "vue-eslint-parser": "^9.4.2",
"xml-name-validator": "^4.0.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
},
"peerDependencies": {
- "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
"node_modules/eslint-scope": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
- "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
@@ -4126,9 +4192,9 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
- "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -4138,12 +4204,12 @@
}
},
"node_modules/espree": {
- "version": "9.5.2",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
- "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"dependencies": {
- "acorn": "^8.8.0",
+ "acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
},
@@ -4240,9 +4306,9 @@
"dev": true
},
"node_modules/fast-glob": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
- "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -4298,9 +4364,9 @@
}
},
"node_modules/feather-icons": {
- "version": "4.29.0",
- "resolved": "https://registry.npmjs.org/feather-icons/-/feather-icons-4.29.0.tgz",
- "integrity": "sha512-Y7VqN9FYb8KdaSF0qD1081HCkm0v4Eq/fpfQYQnubpqi0hXx14k+gF9iqtRys1SIcTEi97xDi/fmsPFZ8xo0GQ==",
+ "version": "4.29.2",
+ "resolved": "https://registry.npmjs.org/feather-icons/-/feather-icons-4.29.2.tgz",
+ "integrity": "sha512-0TaCFTnBTVCz6U+baY2UJNKne5ifGh7sMG4ZC2LoBWCZdIyPa+y6UiR4lEYGws1JOFWdee8KAsAIvu0VcXqiqA==",
"dependencies": {
"classnames": "^2.2.5",
"core-js": "^3.1.3"
@@ -4427,6 +4493,22 @@
"is-callable": "^1.1.3"
}
},
+ "node_modules/foreground-child": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+ "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -4627,9 +4709,9 @@
}
},
"node_modules/globals": {
- "version": "13.20.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
- "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -4879,9 +4961,9 @@
]
},
"node_modules/ignore": {
- "version": "5.2.4",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
- "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"engines": {
"node": ">= 4"
@@ -5083,6 +5165,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -5307,6 +5398,24 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "node_modules/jackspeak": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+ "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+ "dev": true,
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
"node_modules/jake": {
"version": "10.8.7",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
@@ -5340,15 +5449,16 @@
}
},
"node_modules/js-beautify": {
- "version": "1.14.6",
- "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.6.tgz",
- "integrity": "sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==",
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
+ "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
"dev": true,
"dependencies": {
"config-chain": "^1.1.13",
- "editorconfig": "^0.15.3",
- "glob": "^8.0.3",
- "nopt": "^6.0.0"
+ "editorconfig": "^1.0.4",
+ "glob": "^10.3.3",
+ "js-cookie": "^3.0.5",
+ "nopt": "^7.2.0"
},
"bin": {
"css-beautify": "js/bin/css-beautify.js",
@@ -5356,7 +5466,7 @@
"js-beautify": "js/bin/js-beautify.js"
},
"engines": {
- "node": ">=10"
+ "node": ">=14"
}
},
"node_modules/js-beautify/node_modules/brace-expansion": {
@@ -5369,34 +5479,49 @@
}
},
"node_modules/js-beautify/node_modules/glob": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
- "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "version": "10.3.15",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz",
+ "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==",
"dev": true,
"dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^5.0.1",
- "once": "^1.3.0"
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.3.6",
+ "minimatch": "^9.0.1",
+ "minipass": "^7.0.4",
+ "path-scurry": "^1.11.0"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
},
"engines": {
- "node": ">=12"
+ "node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-beautify/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+ "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
}
},
"node_modules/js-tokens": {
@@ -5690,14 +5815,11 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.0",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
- "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+ "version": "0.30.10",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
+ "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.13"
- },
- "engines": {
- "node": ">=12"
+ "@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"node_modules/map-obj": {
@@ -5840,6 +5962,15 @@
"node": ">= 6"
}
},
+ "node_modules/minipass": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
+ "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -5936,18 +6067,18 @@
"dev": true
},
"node_modules/nopt": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
- "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==",
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
+ "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
"dev": true,
"dependencies": {
- "abbrev": "^1.0.0"
+ "abbrev": "^2.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
- "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/normalize-package-data": {
@@ -6172,6 +6303,31 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
+ "dev": true,
+ "engines": {
+ "node": "14 || >=16.14"
+ }
+ },
"node_modules/pathe": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
@@ -6316,9 +6472,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.32",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
- "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
@@ -6336,16 +6492,16 @@
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-selector-parser": {
- "version": "6.0.13",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
- "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
+ "version": "6.0.16",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
+ "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
@@ -6379,9 +6535,9 @@
}
},
"node_modules/pretty-bytes": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.0.tgz",
- "integrity": "sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
+ "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==",
"dev": true,
"engines": {
"node": "^14.13.1 || >=16.0.0"
@@ -6437,12 +6593,6 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
- "node_modules/pseudomap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
- "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==",
- "dev": true
- },
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -7000,13 +7150,10 @@
}
},
"node_modules/semver": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
- "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
"bin": {
"semver": "bin/semver.js"
},
@@ -7064,11 +7211,17 @@
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
"dev": true
},
- "node_modules/sigmund": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
- "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==",
- "dev": true
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
},
"node_modules/slash": {
"version": "3.0.0",
@@ -7092,9 +7245,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"engines": {
"node": ">=0.10.0"
}
@@ -7204,6 +7357,71 @@
"safe-buffer": "~5.2.0"
}
},
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/string.prototype.matchall": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
@@ -7294,6 +7512,19 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz",
@@ -7787,9 +8018,9 @@
}
},
"node_modules/vite": {
- "version": "4.5.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
- "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
+ "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
@@ -7865,14 +8096,14 @@
}
},
"node_modules/vite-plugin-pwa": {
- "version": "0.16.4",
- "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.16.4.tgz",
- "integrity": "sha512-lmwHFIs9zI2H9bXJld/zVTbCqCQHZ9WrpyDMqosICDV0FVnCJwniX1NMDB79HGTIZzOQkY4gSZaVTJTw6maz/Q==",
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.16.7.tgz",
+ "integrity": "sha512-4WMA5unuKlHs+koNoykeuCfTcqEGbiTRr8sVYUQMhc6tWxZpSRnv9Ojk4LKmqVhoPGHfBVCdGaMo8t9Qidkc1Q==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
- "fast-glob": "^3.2.12",
- "pretty-bytes": "^6.0.0",
+ "fast-glob": "^3.3.1",
+ "pretty-bytes": "^6.1.1",
"workbox-build": "^7.0.0",
"workbox-window": "^7.0.0"
},
@@ -7883,7 +8114,7 @@
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
- "vite": "^3.1.0 || ^4.0.0",
+ "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0",
"workbox-build": "^7.0.0",
"workbox-window": "^7.0.0"
}
@@ -7966,27 +8197,35 @@
}
},
"node_modules/vue": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
- "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
+ "version": "3.4.27",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz",
+ "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==",
"dependencies": {
- "@vue/compiler-dom": "3.3.4",
- "@vue/compiler-sfc": "3.3.4",
- "@vue/runtime-dom": "3.3.4",
- "@vue/server-renderer": "3.3.4",
- "@vue/shared": "3.3.4"
+ "@vue/compiler-dom": "3.4.27",
+ "@vue/compiler-sfc": "3.4.27",
+ "@vue/runtime-dom": "3.4.27",
+ "@vue/server-renderer": "3.4.27",
+ "@vue/shared": "3.4.27"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
"node_modules/vue-component-type-helpers": {
- "version": "1.6.5",
- "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.6.5.tgz",
- "integrity": "sha512-iGdlqtajmiqed8ptURKPJ/Olz0/mwripVZszg6tygfZSIL9kYFPJTNY6+Q6OjWGznl2L06vxG5HvNvAnWrnzbg==",
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.19.tgz",
+ "integrity": "sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==",
"dev": true
},
"node_modules/vue-eslint-parser": {
- "version": "9.3.1",
- "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz",
- "integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==",
+ "version": "9.4.2",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
+ "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
@@ -8017,11 +8256,11 @@
}
},
"node_modules/vue-router": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.2.tgz",
- "integrity": "sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",
+ "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==",
"dependencies": {
- "@vue/devtools-api": "^6.5.0"
+ "@vue/devtools-api": "^6.5.1"
},
"funding": {
"url": "https://github.com/sponsors/posva"
@@ -8513,6 +8752,100 @@
"workbox-core": "7.0.0"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/package.json b/package.json
@@ -12,22 +12,22 @@
"test:e2e": "npx playwright test"
},
"dependencies": {
- "feather-icons": "^4.29.0",
- "vue": "^3.3.4",
+ "feather-icons": "^4.29.2",
+ "vue": "^3.4.27",
"vue-feather": "^2.0.0",
- "vue-router": "^4.2.2"
+ "vue-router": "^4.3.2"
},
"devDependencies": {
"@playwright/test": "^1.44.0",
- "@types/node": "^20.12.11",
- "@vitejs/plugin-vue": "^4.2.3",
- "@vue/test-utils": "^2.4.0",
- "eslint": "^8.39.0",
- "eslint-plugin-vue": "^9.11.0",
+ "@types/node": "^20.12.12",
+ "@vitejs/plugin-vue": "^4.6.2",
+ "@vue/test-utils": "^2.4.6",
+ "eslint": "^8.57.0",
+ "eslint-plugin-vue": "^9.26.0",
"jsdom": "^22.1.0",
"pwa-asset-generator": "^6.3.1",
- "vite": "^4.3.9",
- "vite-plugin-pwa": "^0.16.4",
+ "vite": "^4.5.3",
+ "vite-plugin-pwa": "^0.16.7",
"vitest": "^0.32.4"
}
}
diff --git a/src/components/DecimalInput.vue b/src/components/DecimalInput.vue
@@ -1,110 +1,101 @@
<template>
- <input ref="input" type="number" step="any" required @blur="onblur" v-model="stringValue">
+ <input ref="inputElement" type="number" step="any" required @blur="onblur" v-model="stringValue">
</template>
-<script>
+<script setup>
+import { ref, watch } from 'vue';
import formatUtils from '@/utils/format';
-export default {
- name: 'DecimalInput',
+/**
+ * The component value
+ */
+const model = defineModel({
+ type: Number,
+ default: 0,
+});
- props: {
- /**
- * The input value
- */
- modelValue: {
- type: Number,
- default: 0,
- },
-
- /**
- * The number of digits to show before the decimal point
- */
- padding: {
- type: Number,
- default: 0,
- validator(value) {
- return value >= 0;
- },
+const props = defineProps({
+ /**
+ * The number of digits to show before the decimal point
+ */
+ padding: {
+ type: Number,
+ default: 0,
+ validator(value) {
+ return value >= 0;
},
+ },
- /**
- * The number of digits to show after the decimal point
- */
- digits: {
- type: Number,
- default: 1,
- validator(value) {
- return value > 0;
- },
+ /**
+ * The number of digits to show after the decimal point
+ */
+ digits: {
+ type: Number,
+ default: 1,
+ validator(value) {
+ return value > 0;
},
},
+});
- data() {
- return {
- /**
- * The internal float value
- */
- internalValue: this.modelValue,
+/**
+ * The internal float value
+ */
+const internalValue = ref(model.value);
- /**
- * The raw string value (empty if input is currently invalid)
- */
- stringValue: this.format(this.modelValue),
- };
- },
+/**
+ * The raw string value (empty if input is currently invalid)
+ */
+const stringValue = ref(format(model.value));
- watch: {
- /**
- * Update the component value when the modelValue prop changes
- * @param {Number} newValue The new prop value
- */
- modelValue(newValue) {
- if (newValue !== this.internalValue) {
- this.internalValue = newValue;
- this.stringValue = this.format(this.internalValue);
- }
- },
+/**
+ * The input element
+ */
+const inputElement = ref(null);
- /**
- * Emit the input event when the internal value changes
- * @param {Number} newValue The new internal float value
- */
- internalValue(newValue) {
- this.$emit('update:modelValue', newValue);
- },
+/*
+ * Update the internal value when the component value changes
+ */
+watch(model, (newValue) => {
+ if (newValue !== internalValue.value) {
+ internalValue.value = newValue;
+ stringValue.value = format(internalValue.value);
+ }
+});
- /**
- * Update the float value when the raw string value changes
- * @param {Number} newValue The new raw string value
- */
- stringValue(newValue) {
- if (this.$refs.input.validity.valid) {
- this.internalValue = Number(newValue);
- }
- },
- },
+/**
+ * Update the component value when the internal value changes
+ */
+watch(internalValue, (newValue) => {
+ model.value = newValue;
+});
- methods: {
- /**
- * Reformat display value if not invalid
- */
- onblur() {
- if (this.$refs.input.validity.valid) {
- this.stringValue = this.format(this.internalValue);
- }
- },
+/**
+ * Update the internal value when the raw string value changes
+ */
+watch(stringValue, (newValue) => {
+ if (inputElement.value.validity.valid) {
+ internalValue.value = Number(newValue);
+ }
+});
- /**
- * Format a decimal as a string
- * @param {Number} value The decimal
- * @returns {String} The formated string
- */
- format(value) {
- return formatUtils.formatNumber(value, this.padding, this.digits, true);
- },
- },
-};
+/**
+ * Reformat display value if not invalid
+ */
+function onblur() {
+ if (inputElement.value.validity.valid) {
+ stringValue.value = format(internalValue.value);
+ }
+}
+
+/**
+ * Format a decimal as a string
+ * @param {Number} value The decimal
+ * @returns {String} The formated string
+ */
+function format(value) {
+ return formatUtils.formatNumber(value, props.padding, props.digits, true);
+}
</script>
<style scoped>
diff --git a/src/components/IntegerInput.vue b/src/components/IntegerInput.vue
@@ -1,97 +1,89 @@
<template>
- <input ref="input" type="number" step="1" required @blur="onblur" v-model="stringValue">
+ <input ref="inputElement" type="number" step="1" required @blur="onblur" v-model="stringValue">
</template>
-<script>
-export default {
- name: 'IntegerInput',
+<script setup>
+import { ref, watch } from 'vue';
- props: {
- /**
- * The input value
- */
- modelValue: {
- type: Number,
- default: 0,
- },
+/**
+ * The component value
+ */
+const model = defineModel({
+ type: Number,
+ default: 0,
+});
- /**
- * The number of digits to show before the decimal point
- */
- padding: {
- type: Number,
- default: 0,
- validator(value) {
- return value >= 0;
- },
+const props = defineProps({
+ /**
+ * The number of digits to show before the decimal point
+ */
+ padding: {
+ type: Number,
+ default: 0,
+ validator(value) {
+ return value >= 0;
},
},
+});
- data() {
- return {
- /**
- * The internal integer value
- */
- internalValue: this.modelValue,
+/**
+ * The internal integer value
+ */
+const internalValue = ref(model.value);
- /**
- * The raw string value (empty if input is currently invalid)
- */
- stringValue: this.format(this.modelValue),
- };
- },
+/**
+ * The raw string value (empty if input is currently invalid)
+ */
+const stringValue = ref(format(model.value));
- watch: {
- /**
- * Update the component value when the modelValue prop changes
- * @param {Number} newValue The new prop value
- */
- modelValue(newValue) {
- if (newValue !== this.internalValue) {
- this.internalValue = newValue;
- this.stringValue = this.format(this.internalValue);
- }
- },
+/**
+ * The input element
+ */
+const inputElement = ref(null);
- /**
- * Emit the input event when the internal value changes
- * @param {Number} newValue The new internal integer value
- */
- internalValue(newValue) {
- this.$emit('update:modelValue', newValue);
- },
+/**
+ * Update the internal value when the component value changes
+ */
+watch(model, (newValue) => {
+ if (newValue !== internalValue.value) {
+ internalValue.value = newValue;
+ stringValue.value = format(internalValue.value);
+ }
+});
- /**
- * Update the integer value when the raw string value changes
- * @param {Number} newValue The new raw string value
- */
- stringValue(newValue) {
- if (this.$refs.input.validity.valid) {
- this.internalValue = Number(newValue);
- }
- },
- },
+/**
+ * Update the component value when the internal value changes
+ */
+watch(internalValue, (newValue) => {
+ model.value = newValue;
+});
- methods: {
- /**
- * Reformat display value if not invalid
- */
- onblur() {
- if (this.$refs.input.validity.valid) {
- this.stringValue = this.format(this.internalValue);
- }
- },
+/**
+ * Update the internal value when the raw string value changes
+ */
+watch(stringValue, (newValue) => {
+ if (inputElement.value.validity.valid) {
+ internalValue.value = Number(newValue);
+ }
+});
- /**
- * Format an integer as a string
- * @param {Number} value The integer
- * @returns {String} The formated string
- */
- format(value) {
- return value.toString().padStart(this.padding, '0');
- },
- },
-};
+/**
+ * Reformat display value if not invalid
+ */
+function onblur() {
+ if (inputElement.value.validity.valid) {
+ stringValue.value = format(internalValue.value);
+ }
+}
+
+/**
+ * Format an integer as a string
+ * @param {Number} value The integer
+ * @returns {String} The formated string
+ */
+function format(value) {
+ return value.toString().padStart(props.padding, '0');
+}
</script>
<style scoped>
diff --git a/src/components/SimpleTargetTable.vue b/src/components/SimpleTargetTable.vue
@@ -14,17 +14,18 @@
<tbody>
<tr v-for="(item, index) in results" :key="index">
<td :class="item.result === 'distance' ? 'result' : ''">
- {{ formatNumber(item.distanceValue, 0, 2, item.result === 'distance') }}
- {{ distanceUnits[item.distanceUnit].symbol }}
+ {{ formatUtils.formatNumber(item.distanceValue, 0, 2, item.result === 'distance') }}
+ {{ unitUtils.DISTANCE_UNITS[item.distanceUnit].symbol }}
</td>
<td :class="item.result === 'time' ? 'result' : ''">
- {{ formatDuration(item.time, 3, 2, item.result === 'time') }}
+ {{ formatUtils.formatDuration(item.time, 3, 2, item.result === 'time') }}
</td>
<td v-if="showPace">
- {{ formatDuration(getPace(item), 3, 0, true) }}
- / {{ distanceUnits[getDefaultDistanceUnit(defaultUnitSystem)].symbol }}
+ {{ formatUtils.formatDuration(getPace(item), 3, 0, true) }}
+ / {{ unitUtils.DISTANCE_UNITS[unitUtils.getDefaultDistanceUnit(defaultUnitSystem)]
+ .symbol }}
</td>
</tr>
@@ -38,102 +39,71 @@
</div>
</template>
-<script>
+<script setup>
+import { computed } from 'vue';
import formatUtils from '@/utils/format';
import unitUtils from '@/utils/units';
-export default {
- name: 'SimpleTargetTable',
-
- props: {
- /**
- * The method that generates the target table rows
- */
- calculateResult: {
- type: Function,
- required: true,
- },
-
- /**
- * The target set
- */
- targets: {
- type: Array,
- default: () => [],
- },
-
- /**
- * Whether to show result paces
- */
- showPace: {
- type: Boolean,
- default: false,
- },
-
- /**
- * The unit system to use when showing result paces
- */
- defaultUnitSystem: {
- type: String,
- default: 'metric',
- },
+const props = defineProps({
+ /**
+ * The method that generates the target table rows
+ */
+ calculateResult: {
+ type: Function,
+ required: true,
},
- data() {
- return {
- /**
- * The distance units
- */
- distanceUnits: unitUtils.DISTANCE_UNITS,
-
- /**
- * The formatDuration method
- */
- formatDuration: formatUtils.formatDuration,
-
- /**
- * The formatNumber method
- */
- formatNumber: formatUtils.formatNumber,
-
- /**
- * The getDefaultDistanceUnit method
- */
- getDefaultDistanceUnit: unitUtils.getDefaultDistanceUnit,
- };
+ /**
+ * The target set
+ */
+ targets: {
+ type: Array,
+ default: () => [],
},
- computed: {
- /**
- * The target table results
- */
- results() {
- // Calculate results
- const result = [];
- this.targets.forEach((row) => {
- // Add result
- result.push(this.calculateResult(row));
- });
-
- // Sort results by time
- result.sort((a, b) => a.time - b.time);
-
- // Return results
- return result;
- },
+ /**
+ * Whether to show result paces
+ */
+ showPace: {
+ type: Boolean,
+ default: false,
},
- methods: {
- /**
- * Get the pace of a result
- * @param {Object} result The result
- */
- getPace(result) {
- return result.time / unitUtils.convertDistance(result.distanceValue, result.distanceUnit,
- unitUtils.getDefaultDistanceUnit(this.defaultUnitSystem));
- },
+ /**
+ * The unit system to use when showing result paces
+ */
+ defaultUnitSystem: {
+ type: String,
+ default: 'metric',
},
-};
+});
+
+/**
+ * The target table results
+ */
+const results = computed(() => {
+ // Calculate results
+ const result = [];
+ props.targets.forEach((row) => {
+ // Add result
+ result.push(props.calculateResult(row));
+ });
+
+ // Sort results by time
+ result.sort((a, b) => a.time - b.time);
+
+ // Return results
+ return result;
+});
+
+/**
+ * Get the pace of a result
+ * @param {Object} result The result
+ */
+function getPace(result) {
+ return result.time / unitUtils.convertDistance(result.distanceValue, result.distanceUnit,
+ unitUtils.getDefaultDistanceUnit(props.defaultUnitSystem));
+}
</script>
<style scoped>
diff --git a/src/components/TargetEditor.vue b/src/components/TargetEditor.vue
@@ -7,13 +7,13 @@
<input v-model="internalValue.name" placeholder="Target set label"
aria-label="Target set label"/>
<button class="icon" :title="isCustomSet ? 'Delete target set' : 'Revert target set'"
- @click="revert">
+ @click="emit('revert')">
<vue-feather :type="isCustomSet ? 'trash-2' : 'rotate-ccw'" aria-hidden="true"/>
</button>
</th>
<th>
- <button class="icon" title="Close" @click="close">
+ <button class="icon" title="Close" @click="emit('close')">
<vue-feather type="x" aria-hidden="true"/>
</button>
</th>
@@ -26,7 +26,7 @@
<decimal-input v-model="item.distanceValue" aria-label="Target distance value"
:min="0" :digits="2"/>
<select v-model="item.distanceUnit" aria-label="Target distance unit">
- <option v-for="(value, key) in distanceUnits" :key="key" :value="key">
+ <option v-for="(value, key) in unitUtils.DISTANCE_UNITS" :key="key" :value="key">
{{ value.name }}
</option>
</select>
@@ -67,7 +67,9 @@
</table>
</template>
-<script>
+<script setup>
+import { watch, ref } from 'vue';
+
import VueFeather from 'vue-feather';
import targetUtils from '@/utils/targets';
@@ -76,126 +78,82 @@ import unitUtils from '@/utils/units';
import DecimalInput from '@/components/DecimalInput.vue';
import TimeInput from '@/components/TimeInput.vue';
-export default {
- name: 'TargetEditor',
-
- components: {
- DecimalInput,
- TimeInput,
- VueFeather,
+/**
+ * The component value
+ */
+const model = defineModel({
+ type: Object,
+ default: JSON.parse(JSON.stringify(targetUtils.defaultTargetSet)),
+});
+
+const props = defineProps({
+ /**
+ * Whether the target set is a custom or default set
+ */
+ isCustomSet: {
+ type: Boolean,
+ default: false,
},
- props: {
- /**
- * The targets
- */
- modelValue: {
- type: Object,
- default: JSON.parse(JSON.stringify(targetUtils.defaultTargetSet)),
- },
-
- /**
- * Whether the target set is a custom or default set
- */
- isCustomSet: {
- type: Boolean,
- default: false,
- },
-
- /**
- * The unit system to use when creating distance targets
- */
- defaultUnitSystem: {
- type: String,
- default: 'metric',
- },
- },
-
- data() {
- return {
- /**
- * The internal value
- */
- internalValue: this.modelValue,
-
- /**
- * The distance units
- */
- distanceUnits: unitUtils.DISTANCE_UNITS,
- };
+ /**
+ * The unit system to use when creating distance targets
+ */
+ defaultUnitSystem: {
+ type: String,
+ default: 'metric',
},
+});
+
+// Declare emitted events
+const emit = defineEmits(['revert', 'close']);
+
+/**
+ * The internal value
+ */
+const internalValue = ref(model.value);
+
+/**
+ * Update the internal value when the component value changes
+ */
+watch(model, (newValue) => {
+ internalValue.value = newValue;
+}, { deep: true });
+
+/**
+ * Update the component value when the internal value changes
+ */
+watch(internalValue, (newValue) => {
+ model.value = newValue;
+}, { deep: true });
+
+/**
+ * Add a new distance based target
+ */
+function addDistanceTarget() {
+ internalValue.value.targets.push({
+ result: 'time',
+ distanceValue: 1,
+ distanceUnit: unitUtils.getDefaultDistanceUnit(props.defaultUnitSystem),
+ });
+}
- watch: {
- /**
- * Update the component value when the modelValue prop changes
- * @param {Number} newValue The new prop value
- */
- modelValue: {
- deep: true,
- handler(newValue) {
- this.internalValue = newValue;
- },
- },
-
- /**
- * Emit the input event when the component value changes
- * @param {Number} newValue The new component value
- */
- internalValue: {
- deep: true,
- handler(newValue) {
- this.$emit('update:modelValue', newValue);
- },
- },
- },
+/**
+ * Add a new time based target
+ */
+function addTimeTarget() {
+ internalValue.value.targets.push({
+ result: 'distance',
+ time: 600,
+ });
+}
- methods: {
- /**
- * Revert the target set
- */
- revert() {
- // Emit revert event
- this.$emit('revert');
- },
-
- /**
- * Close the target editor
- */
- close() {
- // Emit close event
- this.$emit('close');
- },
-
- /**
- * Add a new distance based target
- */
- addDistanceTarget() {
- this.internalValue.targets.push({
- result: 'time',
- distanceValue: 1,
- distanceUnit: unitUtils.getDefaultDistanceUnit(this.defaultUnitSystem),
- });
- },
-
- /**
- * Add a new time based target
- */
- addTimeTarget() {
- this.internalValue.targets.push({
- result: 'distance',
- time: 600,
- });
- },
-
- /**
- * Remove a target
- * @param {Number} index The index of the target
- */
- removeTarget(index) {
- this.internalValue.targets.splice(index, 1);
- },
- },
-};
+/**
+ * Remove a target
+ * @param {Number} index The index of the target
+ */
+function removeTarget(index) {
+ internalValue.value.targets.splice(index, 1);
+}
</script>
<style scoped>
diff --git a/src/components/TargetSetSelector.vue b/src/components/TargetSetSelector.vue
@@ -8,19 +8,21 @@
</select>
<button class="icon" title="Edit target set"
- @click="reloadTargetSets(); sortTargetSet(); $refs.dialog.showModal()">
+ @click="reloadTargetSets(); sortTargetSet(); dialogElement.showModal()">
<vue-feather type="edit" aria-hidden="true"/>
</button>
- <dialog ref="dialog" class="target-set-editor-dialog" aria-label="Edit target set">
- <target-editor @close="sortTargetSet(); $refs.dialog.close()" v-model="targetSets[internalValue]"
+ <dialog ref="dialogElement" class="target-set-editor-dialog" aria-label="Edit target set">
+ <target-editor @close="sortTargetSet(); dialogElement.close()" v-model="targetSets[internalValue]"
@revert="revertTargetSet" :default-unit-system="defaultUnitSystem"
:isCustomSet="!internalValue.startsWith('_')"/>
</dialog>
</span>
</template>
-<script>
+<script setup>
+import { onActivated, ref, watch } from 'vue';
+
import VueFeather from 'vue-feather';
import storage from '@/utils/localStorage';
@@ -28,126 +30,111 @@ import targetUtils from '@/utils/targets';
import TargetEditor from '@/components/TargetEditor.vue';
-export default {
- name: 'TargetSetSelector',
-
- components: {
- TargetEditor,
- VueFeather,
- },
-
- props: {
- /**
- * The selected target set
- */
- modelValue: {
- type: String,
- default: '_new',
- },
-
- /**
- * The unit system to use when creating distance targets
- */
- defaultUnitSystem: {
- type: String,
- default: 'metric',
- },
+/**
+ * The selected target set
+ */
+const model = defineModel({
+ type: String,
+ default: '_new',
+});
+
+const props = defineProps({
+ /**
+ * The unit system to use when creating distance targets
+ */
+ defaultUnitSystem: {
+ type: String,
+ default: 'metric',
},
-
- data() {
- return {
- /**
- * The internal value
- */
- internalValue: this.modelValue,
-
- /**
- * The target sets
- */
- targetSets: storage.get('target-sets', targetUtils.defaultTargetSets),
+});
+
+// Declare emitted events
+const emit = defineEmits(['targets-updated']);
+
+/**
+ * The internal value
+ */
+const internalValue = ref(model.value);
+
+/**
+ * The target sets
+ */
+const targetSets = ref(storage.get('target-sets', targetUtils.defaultTargetSets));
+
+/**
+ * The dialog element
+ */
+const dialogElement = ref(null);
+
+/**
+ * Update the internal value when the component value changes
+ */
+watch(model, (newValue) => {
+ if (newValue !== internalValue.value) {
+ internalValue.value = newValue;
+ }
+});
+
+/**
+ * Update the component vvalue when the internal value changes and create a new set if necessary
+ */
+watch(internalValue, (newValue) => {
+ if (newValue == '_new') {
+ reloadTargetSets();
+ let key = Date.now().toString();
+ targetSets.value[key] = {
+ name: 'New target set',
+ targets: [],
};
- },
-
- watch: {
- /**
- * Update the component value when the modelValue prop changes
- */
- modelValue(newValue) {
- if (newValue !== this.internalValue) {
- this.internalValue = newValue;
- }
- },
-
- /**
- * Emit the input event when the component value changes and create a new set if necessary
- */
- internalValue: {
- immediate: true,
- handler(newValue) {
- if (newValue == '_new') {
- this.reloadTargetSets();
- let key = Date.now().toString();
- this.targetSets[key] = {
- name: 'New target set',
- targets: [],
- };
- this.internalValue = key;
- } else {
- this.$emit('update:modelValue', newValue);
- }
- },
- },
-
- /**
- * Save target sets
- */
- targetSets: {
- deep: true,
- handler(newValue) {
- storage.set('target-sets', newValue);
- this.$emit('targets-updated');
- },
- },
- },
+ internalValue.value = key;
+ } else {
+ model.value = newValue;
+ }
+}, { immediate: true });
+
+/**
+ * Save target sets
+ */
+watch(targetSets, (newValue) => {
+ storage.set('target-sets', newValue);
+ emit('targets-updated');
+}, { deep: true });
+
+/**
+ * Revert or remove the current target set
+ */
+function revertTargetSet() {
+ if (internalValue.value.startsWith('_')) {
+ // Revert default set
+ targetSets.value[internalValue.value] =
+ JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[internalValue.value]));
+ sortTargetSet();
+ } else {
+ // Remove custom set
+ delete targetSets.value[internalValue.value];
+ internalValue.value = [...Object.keys(targetSets.value), '_new'][0];
+ if (dialogElement.value.close) dialogElement.value.close();
+ }
+};
- methods: {
- /**
- * Revert or remove the current target set
- */
- revertTargetSet() {
- if (this.internalValue.startsWith('_')) {
- // Revert default set
- this.targetSets[this.internalValue] =
- JSON.parse(JSON.stringify(targetUtils.defaultTargetSets[this.internalValue]));
- this.sortTargetSet();
- } else {
- // Remove custom set
- delete this.targetSets[this.internalValue];
- this.internalValue = [...Object.keys(this.targetSets), '_new'][0];
- if (this.$refs.dialog.close) this.$refs.dialog.close();
- }
- },
-
- /**
- * Sort the current target set
- */
- sortTargetSet() {
- this.targetSets[this.internalValue].targets =
- targetUtils.sort(this.targetSets[this.internalValue].targets);
- },
-
- /**
- * Reload the target sets
- */
- reloadTargetSets() {
- this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
- },
- },
+/**
+ * Sort the current target set
+ */
+function sortTargetSet() {
+ targetSets.value[internalValue.value].targets =
+ targetUtils.sort(targetSets.value[internalValue.value].targets);
+};
- activated() {
- this.reloadTargetSets();
- },
+/**
+ * Reload the target sets
+ */
+function reloadTargetSets() {
+ targetSets.value = storage.get('target-sets', targetUtils.defaultTargetSets);
};
+
+onActivated(() => {
+ reloadTargetSets();
+});
</script>
<style scoped>
diff --git a/src/components/TimeInput.vue b/src/components/TimeInput.vue
@@ -13,145 +13,126 @@
</div>
</template>
-<script>
+<script setup>
+import { computed, ref, watch } from 'vue';
+
import IntegerInput from '@/components/IntegerInput.vue';
import DecimalInput from '@/components/DecimalInput.vue';
-export default {
- name: 'TimeInput',
-
- components: {
- IntegerInput,
- DecimalInput,
+/**
+ * The component value
+ */
+const model = defineModel({
+ type: Number,
+ default: 0,
+ validator(value) {
+ return value >= 0 && value <= 359999.99;
},
+});
- props: {
- /**
- * The input value
- */
- modelValue: {
- type: Number,
- default: 0,
- validator(value) {
- return value >= 0 && value <= 359999.99;
- },
- },
-
- /**
- * Whether to show the hour field
- */
- showHours: {
- type: Boolean,
- default: true,
- },
-
- /**
- * The prefix for each field's aria-label
- */
- label: {
- type: String,
- default: '',
- },
+const props = defineProps({
+ /**
+ * Whether to show the hour field
+ */
+ showHours: {
+ type: Boolean,
+ default: true,
},
- data() {
- return {
- /**
- * The internal value
- */
- internalValue: this.modelValue,
- };
+ /**
+ * The prefix for each field's aria-label
+ */
+ label: {
+ type: String,
+ default: '',
},
+});
- computed: {
- /**
- * The maximum value
- */
- max() {
- return this.showHours ? 359999.99 : 3599.99;
- },
+/**
+ * The internal value
+ */
+const internalValue = ref(model.value);
- /**
- * The value of the hours field
- */
- hours: {
- get() {
- return Math.floor(this.modelValue / 3600);
- },
- set(newValue) {
- this.internalValue = (newValue * 3600) + (this.minutes * 60) + this.seconds;
- },
- },
+/**
+ * The maximum value
+ */
+const max = computed(() => {
+ return props.showHours ? 359999.99 : 3599.99;
+});
- /**
- * The value of the minutes field
- */
- minutes: {
- get() {
- return Math.floor((this.modelValue % 3600) / 60);
- },
- set(newValue) {
- this.internalValue = (this.hours * 3600) + (newValue * 60) + this.seconds;
- },
- },
-
- /**
- * The value of the seconds field
- */
- seconds: {
- get() {
- return this.modelValue % 60;
- },
- set(newValue) {
- this.internalValue = (this.hours * 3600) + (this.minutes * 60) + newValue;
- },
- },
+/**
+ * The value of the hours field
+ */
+const hours = computed({
+ get() {
+ return Math.floor(model.value / 3600);
},
+ set(newValue) {
+ internalValue.value = (newValue * 3600) + (minutes.value * 60) + seconds.value;
+ },
+});
- watch: {
- /**
- * Update the component value when the modelValue prop changes
- * @param {Number} newValue The new prop value
- */
- modelValue(newValue) {
- if (newValue !== this.internalValue) {
- this.internalValue = newValue;
- }
- },
-
- /**
- * Emit the input event when the component value changes
- * @param {Number} newValue The new component value
- */
- internalValue(newValue) {
- this.$emit('update:modelValue', newValue);
- },
+/**
+ * The value of the minutes field
+ */
+const minutes = computed({
+ get() {
+ return Math.floor((model.value % 3600) / 60);
},
+ set(newValue) {
+ internalValue.value = (hours.value * 3600) + (newValue * 60) + seconds.value;
+ },
+});
- methods: {
- /**
- * Process up and down arrow presses
- * @param {Object} e The keydown event args
- */
- onkeydown(e, step = 1) {
- if (e.key === 'ArrowUp') {
- if (Math.floor(this.internalValue) + step > this.max) {
- this.internalValue = this.max;
- } else {
- this.internalValue = Math.floor(this.internalValue) + step;
- }
- e.preventDefault();
- } else if (e.key === 'ArrowDown') {
- if (Math.ceil(this.internalValue) - step < 0) {
- this.internalValue = 0;
- } else {
- this.internalValue = Math.ceil(this.internalValue) - step;
- }
- e.preventDefault();
- }
- },
+/**
+ * The value of the seconds field
+ */
+const seconds = computed({
+ get() {
+ return model.value % 60;
+ },
+ set(newValue) {
+ internalValue.value = (hours.value * 3600) + (minutes.value * 60) + newValue;
},
-};
+});
+
+/**
+ * Update the internal value when the component value changes
+ */
+watch(model, (newValue) => {
+ if (newValue !== internalValue.value) {
+ internalValue.value = newValue;
+ }
+});
+
+/**
+ * Update the component value when the internal value changes
+ */
+watch(internalValue, (newValue) => {
+ model.value = newValue;
+});
+
+/**
+ * Process up and down arrow presses
+ * @param {Object} e The keydown event args
+ */
+function onkeydown(e, step = 1) {
+ if (e.key === 'ArrowUp') {
+ if (Math.floor(internalValue.value) + step > max.value) {
+ internalValue.value = max.value;
+ } else {
+ internalValue.value = Math.floor(internalValue.value) + step;
+ }
+ e.preventDefault();
+ } else if (e.key === 'ArrowDown') {
+ if (Math.ceil(internalValue.value) - step < 0) {
+ internalValue.value = 0;
+ } else {
+ internalValue.value = Math.ceil(internalValue.value) - step;
+ }
+ e.preventDefault();
+ }
+}
</script>
<style scoped>
diff --git a/src/views/AboutPage.vue b/src/views/AboutPage.vue
@@ -123,25 +123,12 @@
</div>
</template>
-<script>
+<script setup>
import { version } from '/package.json';
import VueFeather from 'vue-feather';
-export default {
- name: 'AboutPage',
-
- components: {
- VueFeather
- },
-
- data() {
- return {
- version,
- development: process.env.NODE_ENV === 'development',
- };
- },
-};
+const development = process.env.NODE_ENV === 'development';
</script>
<style scoped>
diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue
@@ -33,12 +33,6 @@
</div>
</template>
-<script>
-export default {
- name: 'HomePage',
-};
-</script>
-
<style scoped>
.home-page {
text-align: center;
diff --git a/src/views/NotFoundPage.vue b/src/views/NotFoundPage.vue
@@ -5,12 +5,6 @@
</div>
</template>
-<script>
-export default {
- name: 'NotFoundPage',
-};
-</script>
-
<style scoped>
h1 {
font-size: 1.5em;
diff --git a/src/views/PaceCalculator.vue b/src/views/PaceCalculator.vue
@@ -7,7 +7,7 @@
<decimal-input v-model="inputDistance" aria-label="Input distance value"
:min="0" :digits="2"/>
<select v-model="inputUnit" aria-label="Input distance unit">
- <option v-for="(value, key) in distanceUnits" :key="key" :value="key">
+ <option v-for="(value, key) in unitUtils.DISTANCE_UNITS" :key="key" :value="key">
{{ value.name }}
</option>
</select>
@@ -42,7 +42,9 @@
</div>
</template>
-<script>
+<script setup>
+import { computed, onActivated, ref, watch } from 'vue';
+
import paceUtils from '@/utils/paces';
import storage from '@/utils/localStorage';
import targetUtils from '@/utils/targets';
@@ -53,164 +55,138 @@ import SimpleTargetTable from '@/components/SimpleTargetTable.vue';
import TargetSetSelector from '@/components/TargetSetSelector.vue';
import TimeInput from '@/components/TimeInput.vue';
-export default {
- name: 'PaceCalculator',
-
- components: {
- DecimalInput,
- SimpleTargetTable,
- TargetSetSelector,
- TimeInput,
- },
-
- data() {
- return {
- /**
- * The input distance value
- */
- inputDistance: storage.get('pace-calculator-input-distance', 5),
-
- /**
- * The input distance unit
- */
- inputUnit: storage.get('pace-calculator-input-unit', 'kilometers'),
-
- /**
- * The input time value
- */
- inputTime: storage.get('pace-calculator-input-time', 20 * 60),
-
- /**
- * The default unit system
- *
- * Loaded in activate() method
- */
- defaultUnitSystem: null,
-
- /**
- * The names of the distance units
- */
- distanceUnits: unitUtils.DISTANCE_UNITS,
-
- /**
- * The current selected target set
- */
- selectedTargetSet: storage.get('pace-calculator-target-set', '_pace_targets'),
-
- /**
- * The target sets
- *
- * Loaded in activate() method
- */
- targetSets: {},
- };
- },
-
- watch: {
- /**
- * Save input distance value
- */
- inputDistance(newValue) {
- storage.set('pace-calculator-input-distance', newValue);
- },
-
- /**
- * Save input distance unit
- */
- inputUnit(newValue) {
- storage.set('pace-calculator-input-unit', newValue);
- },
-
- /**
- * Save input time value
- */
- inputTime(newValue) {
- storage.set('pace-calculator-input-time', newValue);
- },
-
- /**
- * Save default unit system
- */
- defaultUnitSystem(newValue) {
- storage.set('default-unit-system', newValue);
- },
-
- /**
- * Save the current selected target set
- */
- selectedTargetSet(newValue) {
- storage.set('pace-calculator-target-set', newValue);
- },
- },
-
- computed: {
- /**
- * The input pace (in seconds per meter)
- */
- pace() {
- const distance = unitUtils.convertDistance(this.inputDistance, this.inputUnit, 'meters');
- return paceUtils.getPace(distance, this.inputTime);
- },
- },
-
- methods: {
- /**
- * Reload the target sets
- */
- reloadTargets() {
- this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
- },
-
- /**
- * Calculate paces from a target
- * @param {Object} target The target
- * @returns {Object} The result
- */
- calculatePace(target) {
- // Initialize result
- const result = {
- distanceValue: target.distanceValue,
- distanceUnit: target.distanceUnit,
- time: target.time,
- result: target.result,
- };
-
- // Add missing value to result
- if (target.result === 'time') {
- // Convert target distance into meters
- const d2 = unitUtils.convertDistance(target.distanceValue, target.distanceUnit, 'meters');
-
- // Calculate time to travel distance at input pace
- const time = paceUtils.getTime(this.pace, d2);
-
- // Update result
- result.time = time;
- } else {
- // Calculate distance traveled in time at input pace
- let distance = paceUtils.getDistance(this.pace, target.time);
-
- // Convert output distance into default distance unit
- distance = unitUtils.convertDistance(distance, 'meters',
- unitUtils.getDefaultDistanceUnit(this.defaultUnitSystem));
-
- // Update result
- result.distanceValue = distance;
- result.distanceUnit = unitUtils.getDefaultDistanceUnit(this.defaultUnitSystem);
- }
-
- // Return result
- return result;
- },
- },
-
- /**
- * (Re)load settings used in multiple calculators
- */
- activated() {
- this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
- this.defaultUnitSystem = storage.get('default-unit-system', unitUtils.detectDefaultUnitSystem());
- },
-};
+/**
+ * The input distance value
+ */
+const inputDistance = ref(storage.get('pace-calculator-input-distance', 5));
+
+/**
+ * The input distance unit
+ */
+const inputUnit = ref(storage.get('pace-calculator-input-unit', 'kilometers'));
+
+/**
+ * The input time value
+ */
+const inputTime = ref(storage.get('pace-calculator-input-time', 20 * 60));
+
+/**
+ * The default unit system
+ *
+ * Loaded in onActivated() hook
+ */
+const defaultUnitSystem = ref(null);
+
+/**
+ * The current selected target set
+ */
+const selectedTargetSet = ref(storage.get('pace-calculator-target-set', '_pace_targets'));
+
+/**
+ * The target sets
+ *
+ * Loaded in onActivated() hook
+ */
+const targetSets = ref({});
+
+/**
+ * Save input distance value
+ */
+watch(inputDistance, (newValue) => {
+ storage.set('pace-calculator-input-distance', newValue);
+});
+
+/**
+ * Save input distance unit
+ */
+watch(inputUnit, (newValue) => {
+ storage.set('pace-calculator-input-unit', newValue);
+});
+
+/**
+ * Save input time value
+ */
+watch(inputTime, (newValue) => {
+ storage.set('pace-calculator-input-time', newValue);
+});
+
+/**
+ * Save default unit system
+ */
+watch(defaultUnitSystem, (newValue) => {
+ storage.set('default-unit-system', newValue);
+});
+
+/**
+ * Save the current selected target set
+ */
+watch(selectedTargetSet, (newValue) => {
+ storage.set('pace-calculator-target-set', newValue);
+});
+
+/**
+ * The input pace (in seconds per meter)
+ */
+const pace = computed(() => {
+ const distance = unitUtils.convertDistance(inputDistance.value, inputUnit.value, 'meters');
+ return paceUtils.getPace(distance, inputTime.value);
+});
+
+/**
+ * Reload the target sets
+ */
+function reloadTargets() {
+ targetSets.value = storage.get('target-sets', targetUtils.defaultTargetSets);
+}
+
+/**
+ * Calculate paces from a target
+ * @param {Object} target The target
+ * @returns {Object} The result
+ */
+function calculatePace(target) {
+ // Initialize result
+ const result = {
+ distanceValue: target.distanceValue,
+ distanceUnit: target.distanceUnit,
+ time: target.time,
+ result: target.result,
+ };
+
+ // Add missing value to result
+ if (target.result === 'time') {
+ // Convert target distance into meters
+ const d2 = unitUtils.convertDistance(target.distanceValue, target.distanceUnit, 'meters');
+
+ // Calculate time to travel distance at input pace
+ const time = paceUtils.getTime(pace.value, d2);
+
+ // Update result
+ result.time = time;
+ } else {
+ // Calculate distance traveled in time at input pace
+ let distance = paceUtils.getDistance(pace.value, target.time);
+
+ // Convert output distance into default distance unit
+ distance = unitUtils.convertDistance(distance, 'meters',
+ unitUtils.getDefaultDistanceUnit(defaultUnitSystem.value));
+
+ // Update result
+ result.distanceValue = distance;
+ result.distanceUnit = unitUtils.getDefaultDistanceUnit(defaultUnitSystem.value);
+ }
+
+ // Return result
+ return result;
+}
+
+/**
+ * (Re)load settings used in multiple calculators
+ */
+onActivated(() => {
+ targetSets.value = storage.get('target-sets', targetUtils.defaultTargetSets);
+ defaultUnitSystem.value = storage.get('default-unit-system', unitUtils.detectDefaultUnitSystem());
+});
</script>
<style scoped>
diff --git a/src/views/RaceCalculator.vue b/src/views/RaceCalculator.vue
@@ -6,7 +6,7 @@
Distance:
<decimal-input v-model="inputDistance" aria-label="Input distance value" :min="0" :digits="2"/>
<select v-model="inputUnit" aria-label="Input distance unit">
- <option v-for="(value, key) in distanceUnits" :key="key" :value="key">
+ <option v-for="(value, key) in unitUtils.DISTANCE_UNITS" :key="key" :value="key">
{{ value.name }}
</option>
</select>
@@ -22,14 +22,14 @@
<h2>Race Statistics</h2>
</summary>
<div>
- Purdy Points: <b>{{ formatNumber(purdyPoints, 0, 1, true) }}</b>
+ Purdy Points: <b>{{ formatUtils.formatNumber(purdyPoints, 0, 1, true) }}</b>
</div>
<div>
- V̇O₂: <b>{{ formatNumber(vo2, 0, 1, true) }}</b> ml/kg/min
- (<b>{{ formatNumber(vo2Percentage, 0, 1, true) }}%</b> of max)
+ V̇O₂: <b>{{ formatUtils.formatNumber(vo2, 0, 1, true) }}</b> ml/kg/min
+ (<b>{{ formatUtils.formatNumber(vo2Percentage, 0, 1, true) }}%</b> of max)
</div>
<div>
- V̇O₂ Max: <b>{{ formatNumber(vo2Max, 0, 1, true) }}</b> ml/kg/min
+ V̇O₂ Max: <b>{{ formatUtils.formatNumber(vo2Max, 0, 1, true) }}</b> ml/kg/min
</div>
</details>
@@ -73,7 +73,9 @@
</div>
</template>
-<script>
+<script setup>
+import { computed, onActivated, ref, watch } from 'vue';
+
import formatUtils from '@/utils/format';
import raceUtils from '@/utils/races';
import storage from '@/utils/localStorage';
@@ -85,265 +87,234 @@ import SimpleTargetTable from '@/components/SimpleTargetTable.vue';
import TargetSetSelector from '@/components/TargetSetSelector.vue';
import TimeInput from '@/components/TimeInput.vue';
-export default {
- name: 'RaceCalculator',
-
- components: {
- DecimalInput,
- SimpleTargetTable,
- TargetSetSelector,
- TimeInput,
- },
-
- data() {
- return {
- /**
- * The input distance value
- */
- inputDistance: storage.get('race-calculator-input-distance', 5),
-
- /**
- * The input distance unit
- */
- inputUnit: storage.get('race-calculator-input-unit', 'kilometers'),
-
- /**
- * The input time value
- */
- inputTime: storage.get('race-calculator-input-time', 20 * 60),
-
- /**
- * The default unit system
- *
- * Loaded in activate() method
- */
- defaultUnitSystem: null,
-
- /**
- * The race prediction model
- */
- model: storage.get('race-calculator-model', 'AverageModel'),
-
- /**
- * The value of the exponent in Riegel's Model
- */
- riegelExponent: storage.get('race-calculator-riegel-exponent', 1.06),
-
- /**
- * The names of the distance units
- */
- distanceUnits: unitUtils.DISTANCE_UNITS,
-
- /**
- * The formatNumber method
- */
- formatNumber: formatUtils.formatNumber,
-
- /**
- * The current selected target set
- */
- selectedTargetSet: storage.get('race-calculator-target-set', '_race_targets'),
-
- /**
- * The target sets
- *
- * Loaded in activate() method
- */
- targetSets: {},
- };
- },
-
- methods: {
- /**
- * Reload the target sets
- */
- reloadTargets() {
- this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
- },
-
- /**
- * Predict race results from a target
- * @param {Object} target The target
- * @returns {Object} The result
- */
- predictResult(target) {
- // Initialize result
- const result = {
- distanceValue: target.distanceValue,
- distanceUnit: target.distanceUnit,
- time: target.time,
- result: target.result,
- };
-
- // Add missing value to result
- if (target.result === 'time') {
- // Convert target distance into meters
- const d2 = unitUtils.convertDistance(target.distanceValue, target.distanceUnit, 'meters');
-
- // Get prediction
- let time;
- switch (this.model) {
- default:
- case 'AverageModel':
- time = raceUtils.AverageModel.predictTime(this.d1, this.inputTime, d2,
- this.riegelExponent);
- break;
- case 'PurdyPointsModel':
- time = raceUtils.PurdyPointsModel.predictTime(this.d1, this.inputTime, d2);
- break;
- case 'VO2MaxModel':
- time = raceUtils.VO2MaxModel.predictTime(this.d1, this.inputTime, d2);
- break;
- case 'RiegelModel':
- time = raceUtils.RiegelModel.predictTime(this.d1, this.inputTime, d2,
- this.riegelExponent);
- break;
- case 'CameronModel':
- time = raceUtils.CameronModel.predictTime(this.d1, this.inputTime, d2);
- break;
- }
-
- // Update result
- result.time = time;
- } else {
- // Get prediction
- let distance;
- switch (this.model) {
- default:
- case 'AverageModel':
- distance = raceUtils.AverageModel.predictDistance(this.inputTime, this.d1, target.time,
- this.riegelExponent);
- break;
- case 'PurdyPointsModel':
- distance = raceUtils.PurdyPointsModel.predictDistance(this.inputTime, this.d1,
- target.time);
- break;
- case 'VO2MaxModel':
- distance = raceUtils.VO2MaxModel.predictDistance(this.inputTime, this.d1, target.time);
- break;
- case 'RiegelModel':
- distance = raceUtils.RiegelModel.predictDistance(this.inputTime, this.d1, target.time,
- this.riegelExponent);
- break;
- case 'CameronModel':
- distance = raceUtils.CameronModel.predictDistance(this.inputTime, this.d1, target.time);
- break;
- }
-
- // Convert output distance into default distance unit
- distance = unitUtils.convertDistance(distance, 'meters',
- unitUtils.getDefaultDistanceUnit(this.defaultUnitSystem));
-
- // Update result
- result.distanceValue = distance;
- result.distanceUnit = unitUtils.getDefaultDistanceUnit(this.defaultUnitSystem);
- }
-
- // Return result
- return result;
- },
- },
-
- computed: {
- /**
- * The input distance in meters
- */
- d1() {
- return unitUtils.convertDistance(this.inputDistance, this.inputUnit, 'meters');
- },
-
- /**
- * The Purdy Points for the input race
- */
- purdyPoints() {
- const result = raceUtils.PurdyPointsModel.getPurdyPoints(this.d1, this.inputTime);
- return result;
- },
-
- /**
- * The VO2 Max calculated from the input race
- */
- vo2Max() {
- const result = raceUtils.VO2MaxModel.getVO2Max(this.d1, this.inputTime);
- return result;
- },
-
- /**
- * The VO2 calculated from the input race
- */
- vo2() {
- const result = raceUtils.VO2MaxModel.getVO2(this.d1, this.inputTime);
- return result;
- },
-
- /**
- * The percentage of VO2 Max calculated from the input race
- */
- vo2Percentage() {
- const result = raceUtils.VO2MaxModel.getVO2Percentage(this.inputTime) * 100;
- return result;
- },
- },
-
- watch: {
- /**
- * Save input distance value
- */
- inputDistance(newValue) {
- storage.set('race-calculator-input-distance', newValue);
- },
-
- /**
- * Save input distance unit
- */
- inputUnit(newValue) {
- storage.set('race-calculator-input-unit', newValue);
- },
-
- /**
- * Save input time value
- */
- inputTime(newValue) {
- storage.set('race-calculator-input-time', newValue);
- },
-
- /**
- * Save default unit system
- */
- defaultUnitSystem(newValue) {
- storage.set('default-unit-system', newValue);
- },
-
- /**
- * Save prediction model
- */
- model(newValue) {
- storage.set('race-calculator-model', newValue);
- },
-
- /**
- * Save Riegel Model exponent
- */
- riegelExponent(newValue) {
- storage.set('race-calculator-riegel-exponent', newValue);
- },
-
- /**
- * Save the current selected target set
- */
- selectedTargetSet(newValue) {
- storage.set('race-calculator-target-set', newValue);
- },
- },
-
- /**
- * (Re)load settings used in multiple calculators
- */
- activated() {
- this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
- this.defaultUnitSystem = storage.get('default-unit-system', unitUtils.detectDefaultUnitSystem());
- },
-};
+/**
+ * The input distance value
+ */
+const inputDistance = ref(storage.get('race-calculator-input-distance', 5));
+
+/**
+ * The input distance unit
+ */
+const inputUnit = ref(storage.get('race-calculator-input-unit', 'kilometers'));
+
+/**
+ * The input time value
+ */
+const inputTime = ref(storage.get('race-calculator-input-time', 20 * 60));
+
+/**
+ * The default unit system
+ *
+ * Loaded in onActivated() hook
+ */
+const defaultUnitSystem = ref(null);
+
+/**
+* The race prediction model
+*/
+const model = ref(storage.get('race-calculator-model', 'AverageModel'));
+
+/**
+* The value of the exponent in Riegel's Model
+*/
+const riegelExponent = ref(storage.get('race-calculator-riegel-exponent', 1.06));
+
+/**
+ * The current selected target set
+ */
+const selectedTargetSet = ref(storage.get('race-calculator-target-set', '_race_targets'));
+
+/**
+ * The target sets
+ *
+ * Loaded in onActivated() hook
+ */
+let targetSets = ref({});
+
+/**
+ * Reload the target sets
+ */
+function reloadTargets() {
+ targetSets.value = storage.get('target-sets', targetUtils.defaultTargetSets);
+}
+
+/**
+ * Predict race results from a target
+ * @param {Object} target The target
+ * @returns {Object} The result
+ */
+function predictResult(target) {
+ // Initialize result
+ const result = {
+ distanceValue: target.distanceValue,
+ distanceUnit: target.distanceUnit,
+ time: target.time,
+ result: target.result,
+ };
+
+ // Add missing value to result
+ if (target.result === 'time') {
+ // Convert target distance into meters
+ const d2 = unitUtils.convertDistance(target.distanceValue, target.distanceUnit, 'meters');
+
+ // Get prediction
+ let time;
+ switch (model.value) {
+ default:
+ case 'AverageModel':
+ time = raceUtils.AverageModel.predictTime(d1.value, inputTime.value, d2,
+ riegelExponent.value);
+ break;
+ case 'PurdyPointsModel':
+ time = raceUtils.PurdyPointsModel.predictTime(d1.value, inputTime.value, d2);
+ break;
+ case 'VO2MaxModel':
+ time = raceUtils.VO2MaxModel.predictTime(d1.value, inputTime.value, d2);
+ break;
+ case 'RiegelModel':
+ time = raceUtils.RiegelModel.predictTime(d1.value, inputTime.value, d2,
+ riegelExponent.value);
+ break;
+ case 'CameronModel':
+ time = raceUtils.CameronModel.predictTime(d1.value, inputTime.value, d2);
+ break;
+ }
+
+ // Update result
+ result.time = time;
+ } else {
+ // Get prediction
+ let distance;
+ switch (model.value) {
+ default:
+ case 'AverageModel':
+ distance = raceUtils.AverageModel.predictDistance(inputTime.value, d1.value, target.time,
+ riegelExponent.value);
+ break;
+ case 'PurdyPointsModel':
+ distance = raceUtils.PurdyPointsModel.predictDistance(inputTime.value, d1.value,
+ target.time);
+ break;
+ case 'VO2MaxModel':
+ distance = raceUtils.VO2MaxModel.predictDistance(inputTime.value, d1.value, target.time);
+ break;
+ case 'RiegelModel':
+ distance = raceUtils.RiegelModel.predictDistance(inputTime.value, d1.value, target.time,
+ riegelExponent.value);
+ break;
+ case 'CameronModel':
+ distance = raceUtils.CameronModel.predictDistance(inputTime.value, d1.value, target.time);
+ break;
+ }
+
+ // Convert output distance into default distance unit
+ distance = unitUtils.convertDistance(distance, 'meters',
+ unitUtils.getDefaultDistanceUnit(defaultUnitSystem.value));
+
+ // Update result
+ result.distanceValue = distance;
+ result.distanceUnit = unitUtils.getDefaultDistanceUnit(defaultUnitSystem.value);
+ }
+
+ // Return result
+ return result;
+}
+
+/**
+ * The input distance in meters
+ */
+const d1 = computed(() => {
+ return unitUtils.convertDistance(inputDistance.value, inputUnit.value, 'meters');
+});
+
+/**
+ * The Purdy Points for the input race
+ */
+const purdyPoints = computed(() => {
+ const result = raceUtils.PurdyPointsModel.getPurdyPoints(d1.value, inputTime.value);
+ return result;
+});
+
+/**
+ * The VO2 Max calculated from the input race
+ */
+const vo2Max = computed(() => {
+ const result = raceUtils.VO2MaxModel.getVO2Max(d1.value, inputTime.value);
+ return result;
+});
+
+/**
+ * The VO2 calculated from the input race
+ */
+const vo2 = computed(() => {
+ const result = raceUtils.VO2MaxModel.getVO2(d1.value, inputTime.value);
+ return result;
+});
+
+/**
+ * The percentage of VO2 Max calculated from the input race
+ */
+const vo2Percentage = computed(() => {
+ const result = raceUtils.VO2MaxModel.getVO2Percentage(inputTime.value) * 100;
+ return result;
+});
+
+/**
+ * Save input distance value
+ */
+watch(inputDistance, (newValue) => {
+ storage.set('race-calculator-input-distance', newValue);
+});
+
+/**
+ * Save input distance unit
+ */
+watch(inputUnit, (newValue) => {
+ storage.set('race-calculator-input-unit', newValue);
+});
+
+/**
+ * Save input time value
+ */
+watch(inputTime, (newValue) => {
+ storage.set('race-calculator-input-time', newValue);
+});
+
+/**
+ * Save default unit system
+ */
+watch(defaultUnitSystem, (newValue) => {
+ storage.set('default-unit-system', newValue);
+});
+
+/**
+ * Save prediction model
+ */
+watch(model, (newValue) => {
+ storage.set('race-calculator-model', newValue);
+});
+
+/**
+ * Save Riegel Model exponent
+ */
+watch(riegelExponent, (newValue) => {
+ storage.set('race-calculator-riegel-exponent', newValue);
+});
+
+/**
+ * Save the current selected target set
+ */
+watch(selectedTargetSet, (newValue) => {
+ storage.set('race-calculator-target-set', newValue);
+});
+
+/**
+* (Re)load settings used in multiple calculators
+*/
+onActivated(() => {
+ targetSets.value = storage.get('target-sets', targetUtils.defaultTargetSets);
+ defaultUnitSystem.value = storage.get('default-unit-system', unitUtils.detectDefaultUnitSystem());
+});
</script>
<style scoped>
diff --git a/src/views/SplitCalculator.vue b/src/views/SplitCalculator.vue
@@ -34,12 +34,12 @@
<tbody>
<tr v-for="(item, index) in results" :key="index">
<td>
- {{ formatNumber(item.distanceValue, 0, 2, false) }}
- {{ distanceUnits[item.distanceUnit].symbol }}
+ {{ formatUtils.formatNumber(item.distanceValue, 0, 2, false) }}
+ {{ unitUtils.DISTANCE_UNITS[item.distanceUnit].symbol }}
</td>
<td>
- {{ formatDuration(item.totalTime, 3, 2, true) }}
+ {{ formatUtils.formatDuration(item.totalTime, 3, 2, true) }}
</td>
<td v-if="targetSets[selectedTargetSet]">
@@ -48,8 +48,9 @@
</td>
<td>
- {{ formatDuration(item.pace, 3, 0, true) }}
- / {{ distanceUnits[getDefaultDistanceUnit(defaultUnitSystem)].symbol }}
+ {{ formatUtils.formatDuration(item.pace, 3, 0, true) }}
+ / {{ unitUtils.DISTANCE_UNITS[unitUtils.getDefaultDistanceUnit(defaultUnitSystem)]
+ .symbol }}
</td>
</tr>
@@ -64,7 +65,9 @@
</div>
</template>
-<script>
+<script setup>
+import { computed, onActivated, ref, watch } from 'vue';
+
import formatUtils from '@/utils/format';
import storage from '@/utils/localStorage';
import targetUtils from '@/utils/targets';
@@ -73,146 +76,104 @@ import unitUtils from '@/utils/units';
import TargetSetSelector from '@/components/TargetSetSelector.vue';
import TimeInput from '@/components/TimeInput.vue';
-export default {
- name: 'SplitCalculator',
-
- components: {
- TargetSetSelector,
- TimeInput,
- },
-
- data() {
- return {
- /**
- * The default unit system
- *
- * Loaded in activate() method
- */
- defaultUnitSystem: null,
-
- /**
- * The distance units
- */
- distanceUnits: unitUtils.DISTANCE_UNITS,
-
- /**
- * The formatDuration method
- */
- formatDuration: formatUtils.formatDuration,
-
- /**
- * The formatNumber method
- */
- formatNumber: formatUtils.formatNumber,
-
- /**
- * The getDefaultDistanceUnit method
- */
- getDefaultDistanceUnit: unitUtils.getDefaultDistanceUnit,
-
- /**
- * The current selected target set
- */
- selectedTargetSet: storage.get('split-calculator-target-set', '_split_targets'),
-
- /**
- * The default output targets
- *
- * Loaded in activate() method
- */
- targetSets: {},
- };
- },
-
- watch: {
- /**
- * Save default unit system
- */
- defaultUnitSystem(newValue) {
- storage.set('default-unit-system', newValue);
- },
-
- /**
- * Save the current selected target set
- */
- selectedTargetSet(newValue) {
- storage.set('split-calculator-target-set', newValue);
- },
-
- /**
- * Save target sets
- */
- targetSets: {
- deep: true,
- handler(newValue) {
- storage.set('target-sets', newValue);
- },
- },
- },
-
- computed: {
- /**
- * The target table results
- */
- results() {
- // Initialize results array
- const results = [];
-
- // Check for missing target set
- if (!this.targetSets[this.selectedTargetSet]) return [];
-
- let targets = targetUtils.sort(this.targetSets[this.selectedTargetSet].targets.filter(x =>
- x.result === 'time'));
-
- for (let i = 0; i < targets.length; i += 1) {
- // Calculate split and total times
- const splitTime = targets[i].split || 0;
- const totalTime = i === 0 ? splitTime : results[i - 1].totalTime + splitTime;
-
- // Calculate split and total distances
- const totalDistance = unitUtils.convertDistance(
- targets[i].distanceValue,
- targets[i].distanceUnit, 'meters',
- );
- const splitDistance = i === 0 ? totalDistance : totalDistance - results[i - 1].distance;
-
- // Calculate pace
- const pace = splitTime / unitUtils.convertDistance(splitDistance, 'meters',
- unitUtils.getDefaultDistanceUnit(this.defaultUnitSystem));
-
- // Add row to results array
- results.push({
- distance: totalDistance,
- distanceValue: targets[i].distanceValue,
- distanceUnit: targets[i].distanceUnit,
- totalTime,
- splitTime,
- pace,
- });
- }
-
- // Return results array
- return results;
- },
- },
-
- methods: {
- /**
- * Reload the target sets
- */
- reloadTargets() {
- this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
- },
- },
-
- /**
- * (Re)load settings used in multiple calculators
- */
- activated() {
- this.targetSets = storage.get('target-sets', targetUtils.defaultTargetSets);
- this.defaultUnitSystem = storage.get('default-unit-system', unitUtils.detectDefaultUnitSystem());
- },
-};
+/**
+ * The default unit system
+ *
+ * Loaded in onActivated() hook
+ */
+const defaultUnitSystem = ref(null);
+
+/**
+ * The current selected target set
+ */
+const selectedTargetSet = ref(storage.get('split-calculator-target-set', '_split_targets'));
+
+/**
+ * The default output targets
+ *
+ * Loaded in onActivated() hook
+ */
+const targetSets = ref({});
+
+/**
+ * Save default unit system
+ */
+watch(defaultUnitSystem, (newValue) => {
+ storage.set('default-unit-system', newValue);
+});
+
+/**
+ * Save the current selected target set
+ */
+watch(selectedTargetSet, (newValue) => {
+ storage.set('split-calculator-target-set', newValue);
+});
+
+/**
+ * Save target sets
+ */
+watch(targetSets, (newValue) => {
+ storage.set('target-sets', newValue);
+}, { deep: true });
+
+/**
+ * The target table results
+ */
+const results = computed(() => {
+ // Initialize results array
+ const results = [];
+
+ // Check for missing target set
+ if (!targetSets.value[selectedTargetSet.value]) return [];
+
+ let targets = targetUtils.sort(targetSets.value[selectedTargetSet.value].targets.filter(x =>
+ x.result === 'time'));
+
+ for (let i = 0; i < targets.length; i += 1) {
+ // Calculate split and total times
+ const splitTime = targets[i].split || 0;
+ const totalTime = i === 0 ? splitTime : results[i - 1].totalTime + splitTime;
+
+ // Calculate split and total distances
+ const totalDistance = unitUtils.convertDistance(
+ targets[i].distanceValue,
+ targets[i].distanceUnit, 'meters',
+ );
+ const splitDistance = i === 0 ? totalDistance : totalDistance - results[i - 1].distance;
+
+ // Calculate pace
+ const pace = splitTime / unitUtils.convertDistance(splitDistance, 'meters',
+ unitUtils.getDefaultDistanceUnit(defaultUnitSystem.value));
+
+ // Add row to results array
+ results.push({
+ distance: totalDistance,
+ distanceValue: targets[i].distanceValue,
+ distanceUnit: targets[i].distanceUnit,
+ totalTime,
+ splitTime,
+ pace,
+ });
+ }
+
+ // Return results array
+ return results;
+});
+
+/**
+ * Reload the target sets
+ */
+function reloadTargets() {
+ targetSets.value = storage.get('target-sets', targetUtils.defaultTargetSets);
+}
+
+/**
+ * (Re)load settings used in multiple calculators
+ */
+onActivated(() => {
+ targetSets.value = storage.get('target-sets', targetUtils.defaultTargetSets);
+ defaultUnitSystem.value = storage.get('default-unit-system', unitUtils.detectDefaultUnitSystem());
+});
</script>
<style scoped>
diff --git a/src/views/UnitCalculator.vue b/src/views/UnitCalculator.vue
@@ -20,10 +20,10 @@
<span class="equals"> = </span>
<span v-if="getUnitType(outputUnit) === 'time'" class="output-value" aria-label="Output value">
- {{ formatDuration(outputValue, 6, 3, true) }}
+ {{ formatUtils.formatDuration(outputValue, 6, 3, true) }}
</span>
<span v-else class="output-value" aria-label="Output value">
- {{ formatNumber(outputValue, 0, 3, true) }}
+ {{ formatUtils.formatNumber(outputValue, 0, 3, true) }}
</span>
<select v-model="outputUnit" class="output-units" aria-label="Output units">
@@ -34,7 +34,9 @@
</div>
</template>
-<script>
+<script setup>
+ import { computed, ref, watch } from 'vue';
+
import formatUtils from '@/utils/format';
import storage from '@/utils/localStorage';
import unitUtils from '@/utils/units';
@@ -42,227 +44,198 @@ import unitUtils from '@/utils/units';
import DecimalInput from '@/components/DecimalInput.vue';
import TimeInput from '@/components/TimeInput.vue';
-export default {
- name: 'UnitCalculator',
-
- components: {
- DecimalInput,
- TimeInput,
- },
-
- data() {
- return {
- /**
- * The input value
- */
- inputValue: storage.get('unit-calculator-distance-input-value', 1.0),
-
- /**
- * The unit of the input
- */
- inputUnit: storage.get('unit-calculator-distance-input-unit', 'miles'),
+/**
+ * The input value
+ */
+const inputValue = ref(storage.get('unit-calculator-distance-input-value', 1.0));
- /**
- * The unit of the output
- */
- outputUnit: storage.get('unit-calculator-distance-output-unit', 'kilometers'),
+/**
+ * The unit of the input
+ */
+const inputUnit = ref(storage.get('unit-calculator-distance-input-unit', 'miles'));
- /**
- * The unit category
- */
- category: 'distance',
+/**
+ * The unit of the output
+ */
+const outputUnit = ref(storage.get('unit-calculator-distance-output-unit', 'kilometers'));
- /**
- * The formatDuration method
- */
- formatDuration: formatUtils.formatDuration,
+/**
+ * The unit category
+ */
+const category = ref('distance');
- /**
- * The formatNumber method
- */
- formatNumber: formatUtils.formatNumber,
- };
- },
-
- computed: {
- /**
- * The names of the units in the current category
- */
- units() {
- switch (this.category) {
- case 'distance': {
- return unitUtils.DISTANCE_UNITS;
- }
- case 'time': {
- return {
- ...unitUtils.TIME_UNITS,
- 'hh:mm:ss': {
- name: 'hh:mm:ss',
- symbol: '',
- value: null,
- },
- };
- }
- case 'speed_and_pace': {
- return { ...unitUtils.PACE_UNITS, ...unitUtils.SPEED_UNITS };
- }
- default: {
- return {};
- }
- }
- },
+/**
+ * The names of the units in the current category
+ */
+const units = computed(() => {
+ switch (category.value) {
+ case 'distance': {
+ return unitUtils.DISTANCE_UNITS;
+ }
+ case 'time': {
+ return {
+ ...unitUtils.TIME_UNITS,
+ 'hh:mm:ss': {
+ name: 'hh:mm:ss',
+ symbol: '',
+ value: null,
+ },
+ };
+ }
+ case 'speed_and_pace': {
+ return { ...unitUtils.PACE_UNITS, ...unitUtils.SPEED_UNITS };
+ }
+ default: {
+ return {};
+ }
+ }
+});
- /**
- * The output value
- */
- outputValue() {
- switch (this.category) {
- case 'distance': {
- return unitUtils.convertDistance(this.inputValue, this.inputUnit, this.outputUnit);
- }
- case 'time': {
- // Correct input and output units for 'hh:mm:ss' unit
- const realInput = this.inputUnit === 'hh:mm:ss' ? 'seconds' : this.inputUnit;
- const realOutput = this.outputUnit === 'hh:mm:ss' ? 'seconds' : this.outputUnit;
+/**
+ * The output value
+ */
+const outputValue = computed(() => {
+ switch (category.value) {
+ case 'distance': {
+ return unitUtils.convertDistance(inputValue.value, inputUnit.value, outputUnit.value);
+ }
+ case 'time': {
+ // Correct input and output units for 'hh:mm:ss' unit
+ const realInput = inputUnit.value === 'hh:mm:ss' ? 'seconds' : inputUnit.value;
+ const realOutput = outputUnit.value === 'hh:mm:ss' ? 'seconds' : outputUnit.value;
- // Calculate conversion
- return unitUtils.convertTime(this.inputValue, realInput, realOutput);
- }
- case 'speed_and_pace': {
- return unitUtils.convertSpeedPace(this.inputValue, this.inputUnit, this.outputUnit);
- }
- default: {
- return null;
- }
- }
- },
- },
+ // Calculate conversion
+ return unitUtils.convertTime(inputValue.value, realInput, realOutput);
+ }
+ case 'speed_and_pace': {
+ return unitUtils.convertSpeedPace(inputValue.value, inputUnit.value, outputUnit.value);
+ }
+ default: {
+ return null;
+ }
+ }
+});
- watch: {
- /**
- * Reset inputValue, inputUnit, and outputUnit
- */
- category(newValue) {
- switch (newValue) {
- case 'distance': {
- this.inputValue = storage.get('unit-calculator-distance-input-value', 1);
- this.inputUnit = storage.get('unit-calculator-distance-input-unit', 'miles');
- this.outputUnit = storage.get('unit-calculator-distance-output-unit', 'kilometers');
- break;
- }
- case 'time': {
- this.inputValue = storage.get('unit-calculator-time-input-value', 1);
- this.inputUnit = storage.get('unit-calculator-time-input-unit', 'seconds');
- this.outputUnit = storage.get('unit-calculator-time-output-unit', 'hh:mm:ss');
- break;
- }
- case 'speed_and_pace': {
- this.inputValue = storage.get('unit-calculator-speed-input-value', 600);
- this.inputUnit = storage.get('unit-calculator-speed-input-unit',
- 'seconds_per_mile');
- this.outputUnit = storage.get('unit-calculator-speed-output-unit',
- 'miles_per_hour');
- break;
- }
- default: {
- break;
- }
- }
- },
+/**
+ * Reset inputValue, inputUnit, and outputUnit
+ */
+watch(category, (newValue) => {
+ switch (newValue) {
+ case 'distance': {
+ inputValue.value = storage.get('unit-calculator-distance-input-value', 1);
+ inputUnit.value = storage.get('unit-calculator-distance-input-unit', 'miles');
+ outputUnit.value = storage.get('unit-calculator-distance-output-unit', 'kilometers');
+ break;
+ }
+ case 'time': {
+ inputValue.value = storage.get('unit-calculator-time-input-value', 1);
+ inputUnit.value = storage.get('unit-calculator-time-input-unit', 'seconds');
+ outputUnit.value = storage.get('unit-calculator-time-output-unit', 'hh:mm:ss');
+ break;
+ }
+ case 'speed_and_pace': {
+ inputValue.value = storage.get('unit-calculator-speed-input-value', 600);
+ inputUnit.value = storage.get('unit-calculator-speed-input-unit',
+ 'seconds_per_mile');
+ outputUnit.value = storage.get('unit-calculator-speed-output-unit',
+ 'miles_per_hour');
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+});
- /**
- * Save input value
- */
- inputValue(newValue) {
- switch (this.category) {
- case 'distance': {
- storage.set('unit-calculator-distance-input-value', newValue);
- break;
- }
- case 'time': {
- storage.set('unit-calculator-time-input-value', newValue);
- break;
- }
- case 'speed_and_pace': {
- storage.set('unit-calculator-speed-input-value', newValue);
- break;
- }
- default: {
- break;
- }
- }
- },
+/**
+ * Save input value
+ */
+watch(inputValue, (newValue) => {
+ switch (category.value) {
+ case 'distance': {
+ storage.set('unit-calculator-distance-input-value', newValue);
+ break;
+ }
+ case 'time': {
+ storage.set('unit-calculator-time-input-value', newValue);
+ break;
+ }
+ case 'speed_and_pace': {
+ storage.set('unit-calculator-speed-input-value', newValue);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+});
- /**
- * Save input unit
- */
- inputUnit(newValue) {
- switch (this.category) {
- case 'distance': {
- storage.set('unit-calculator-distance-input-unit', newValue);
- break;
- }
- case 'time': {
- storage.set('unit-calculator-time-input-unit', newValue);
- break;
- }
- case 'speed_and_pace': {
- storage.set('unit-calculator-speed-input-unit', newValue);
- break;
- }
- default: {
- break;
- }
- }
- },
+/**
+ * Save input unit
+ */
+watch(inputUnit, (newValue) => {
+ switch (category.value) {
+ case 'distance': {
+ storage.set('unit-calculator-distance-input-unit', newValue);
+ break;
+ }
+ case 'time': {
+ storage.set('unit-calculator-time-input-unit', newValue);
+ break;
+ }
+ case 'speed_and_pace': {
+ storage.set('unit-calculator-speed-input-unit', newValue);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+});
- /**
- * Save output unit
- */
- outputUnit(newValue) {
- switch (this.category) {
- case 'distance': {
- storage.set('unit-calculator-distance-output-unit', newValue);
- break;
- }
- case 'time': {
- storage.set('unit-calculator-time-output-unit', newValue);
- break;
- }
- case 'speed_and_pace': {
- storage.set('unit-calculator-speed-output-unit', newValue);
- break;
- }
- default: {
- break;
- }
- }
- },
- },
+/**
+ * Save output unit
+ */
+watch(outputUnit, (newValue) => {
+ switch (category.value) {
+ case 'distance': {
+ storage.set('unit-calculator-distance-output-unit', newValue);
+ break;
+ }
+ case 'time': {
+ storage.set('unit-calculator-time-output-unit', newValue);
+ break;
+ }
+ case 'speed_and_pace': {
+ storage.set('unit-calculator-speed-output-unit', newValue);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+});
- methods: {
- /**
- * Get the type of a unit
- * @param {String} unit The unit
- * @returns {String} The type ('decimal' or 'time')
- */
- getUnitType(unit) {
- if (unit in unitUtils.DISTANCE_UNITS) {
- return 'decimal';
- }
- if (unit in unitUtils.TIME_UNITS) {
- return 'decimal';
- }
- if (unit === 'hh:mm:ss') {
- return 'time';
- }
- if (['seconds_per_kilometer', 'seconds_per_mile'].includes(unit)) {
- return 'time';
- }
- return 'decimal';
- },
- },
-};
+/**
+ * Get the type of a unit
+ * @param {String} unit The unit
+ * @returns {String} The type ('decimal' or 'time')
+ */
+function getUnitType(unit) {
+ if (unit in unitUtils.DISTANCE_UNITS) {
+ return 'decimal';
+ }
+ if (unit in unitUtils.TIME_UNITS) {
+ return 'decimal';
+ }
+ if (unit === 'hh:mm:ss') {
+ return 'time';
+ }
+ if (['seconds_per_kilometer', 'seconds_per_mile'].includes(unit)) {
+ return 'time';
+ }
+ return 'decimal';
+}
</script>
<style scoped>
diff --git a/tests/unit/components/TargetSetSelector.spec.js b/tests/unit/components/TargetSetSelector.spec.js
@@ -230,7 +230,7 @@ test('edit button should open target editor with the correct props for default s
});
// Mock showModal function
- wrapper.vm.$refs.dialog.showModal = vi.fn();
+ wrapper.vm.dialogElement.showModal = vi.fn();
// Click edit button
await wrapper.find('button').trigger('click');
@@ -265,7 +265,7 @@ test('edit button should open target editor with the correct props for custom se
});
// Mock showModal function
- wrapper.vm.$refs.dialog.showModal = vi.fn();
+ wrapper.vm.dialogElement.showModal = vi.fn();
// Click edit button
await wrapper.find('button').trigger('click');
@@ -305,7 +305,7 @@ test('should reload and sort target set before target editor is opened', async (
localStorage.setItem('running-tools.target-sets', JSON.stringify(targetSets));
// Mock showModal function
- wrapper.vm.$refs.dialog.showModal = vi.fn();
+ wrapper.vm.dialogElement.showModal = vi.fn();
// Click edit button
await wrapper.find('button').trigger('click');
diff --git a/tests/unit/components/TimeInput.spec.js b/tests/unit/components/TimeInput.spec.js
@@ -42,13 +42,13 @@ test('should emit input event when value changes', async () => {
const wrapper = shallowMount(TimeInput);
// Change value to 1:00:00.00
- await wrapper.setData({ internalValue: 3600 });
+ await wrapper.findAllComponents({ name: 'integer-input' })[0].setValue(1);
// Assert input event was emitted
expect(wrapper.emitted()['update:modelValue']).to.deep.equal([[3600.00]]);
// Change value to 1:00:01.50
- await wrapper.setData({ internalValue: 3601.5 });
+ await wrapper.findComponent({ name: 'decimal-input' }).setValue(1.5);
// Assert another input event was emitted
expect(wrapper.emitted()['update:modelValue']).to.deep.equal([[3600.00], [3601.50]]);
diff --git a/tests/unit/views/PaceCalculator.spec.js b/tests/unit/views/PaceCalculator.spec.js
@@ -1,6 +1,7 @@
import { beforeEach, test, expect } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import PaceCalculator from '@/views/PaceCalculator.vue';
+import targetUtils from '@/utils/targets';
beforeEach(() => {
localStorage.clear();
@@ -34,19 +35,16 @@ test('should correctly calculate time results', async () => {
test('should correctly calculate distance results according to default units setting', async () => {
// Initialize component
- const wrapper = shallowMount(PaceCalculator, {
- data() {
- return {
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(PaceCalculator);
// Enter input pace data
await wrapper.findComponent({ name: 'decimal-input' }).setValue(2);
await wrapper.find('select[aria-label="Input distance unit"]').setValue('miles');
await wrapper.findComponent({ name: 'time-input' }).setValue(1200);
+ // Set default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('metric');
+
// Get calculate result function
const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult;
@@ -76,27 +74,11 @@ test('should not show paces in results table', async () => {
test('should correctly handle null target set', async () => {
// Initialize component
- const paceTargets = [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- ];
- const wrapper = shallowMount(PaceCalculator, {
- data() {
- return {
- targetSets: {
- '_pace_targets': {
- name: 'Common pace targets',
- targets: paceTargets,
- },
- '_race_targets': null,
- },
- };
- },
- });
+ const wrapper = shallowMount(PaceCalculator);
+ await wrapper.vm.reloadTargets(); // onActivated method not called in tests
// Switch to invalid target set
- await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_race_targets');
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('does_not_exist');
// Assert empty array passed to SimpleTargetTable component
expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal([]);
@@ -105,6 +87,7 @@ test('should correctly handle null target set', async () => {
await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_pace_targets');
// Assert valid targets passed to SimpleTargetTable component
+ const paceTargets = targetUtils.defaultTargetSets._pace_targets.targets;
expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(paceTargets);
});
@@ -143,34 +126,12 @@ test('should load selected target set from localStorage', async () => {
localStorage.setItem('running-tools.pace-calculator-target-set', '"_race_targets"');
// Initialize component
- const raceTargets = [
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 6, distanceUnit: 'kilometers' },
- ];
- const wrapper = shallowMount(PaceCalculator, {
- data() {
- return {
- targetSets: {
- '_pace_targets': {
- name: 'Common pace targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- },
- '_race_targets': {
- name: 'Common race targets',
- targets: raceTargets,
- },
- },
- };
- },
- });
+ const wrapper = shallowMount(PaceCalculator);
+ await wrapper.vm.reloadTargets();
// Assert selection is loaded
expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('_race_targets');
+ const raceTargets = targetUtils.defaultTargetSets._race_targets.targets;
expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(raceTargets);
});
@@ -187,15 +148,10 @@ test('should save selected target set to localStorage when modified', async () =
test('should save default units setting to localStorage when modified', async () => {
// Initialize component
- const wrapper = shallowMount(PaceCalculator, {
- data() {
- return {
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(PaceCalculator);
// Change default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('metric');
await wrapper.find('select[aria-label="Default units"]').setValue('imperial');
// New default units should be saved to localStorage
diff --git a/tests/unit/views/RaceCalculator.spec.js b/tests/unit/views/RaceCalculator.spec.js
@@ -1,6 +1,7 @@
import { beforeEach, test, expect } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import RaceCalculator from '@/views/RaceCalculator.vue';
+import targetUtils from '@/utils/targets';
beforeEach(() => {
localStorage.clear();
@@ -32,19 +33,16 @@ test('should correctly predict race times', async () => {
test('should correctly calculate distance results according to default units setting', async () => {
// Initialize component
- const wrapper = shallowMount(RaceCalculator, {
- data() {
- return {
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(RaceCalculator);
// Enter input pace data
await wrapper.findComponent({ name: 'decimal-input' }).setValue(5);
await wrapper.find('select[aria-label="Input distance unit"]').setValue('kilometers');
await wrapper.findComponent({ name: 'time-input' }).setValue(1200);
+ // Set default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('metric');
+
// Get calculate result function
const calculateResult = wrapper.findComponent({ name: 'simple-target-table' }).vm.calculateResult;
@@ -76,27 +74,11 @@ test('should show paces in results table', async () => {
test('should correctly handle null target set', async () => {
// Initialize component
- const raceTargets = [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- ];
- const wrapper = shallowMount(RaceCalculator, {
- data() {
- return {
- targetSets: {
- '_pace_targets': null,
- '_race_targets': {
- name: 'Common race targets',
- targets: raceTargets,
- },
- },
- };
- },
- });
+ const wrapper = shallowMount(RaceCalculator);
+ await wrapper.vm.reloadTargets(); // onActivated method not called in tests
// Switch to invalid target set
- await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_pace_targets');
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('does_not_exist');
// Assert empty array passed to SimpleTargetTable component
expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal([]);
@@ -105,6 +87,7 @@ test('should correctly handle null target set', async () => {
await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_race_targets');
// Assert valid targets passed to SimpleTargetTable component
+ const raceTargets = targetUtils.defaultTargetSets._race_targets.targets;
expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(raceTargets);
});
@@ -202,34 +185,12 @@ test('should load selected target set from localStorage', async () => {
localStorage.setItem('running-tools.race-calculator-target-set', '"_pace_targets"');
// Initialize component
- const paceTargets = [
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 3, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 6, distanceUnit: 'kilometers' },
- ];
- const wrapper = shallowMount(RaceCalculator, {
- data() {
- return {
- targetSets: {
- '_pace_targets': {
- name: 'Common pace targets',
- targets: paceTargets,
- },
- '_race_targets': {
- name: 'Common race targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- },
- },
- };
- },
- });
+ const wrapper = shallowMount(RaceCalculator);
+ await wrapper.vm.reloadTargets();
// Assert selection is loaded
expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('_pace_targets');
+ const paceTargets = targetUtils.defaultTargetSets._pace_targets.targets;
expect(wrapper.findComponent({ name: 'simple-target-table' }).vm.targets).to.deep.equal(paceTargets);
});
@@ -246,15 +207,10 @@ test('should save selected target set to localStorage when modified', async () =
test('should save default units setting to localStorage when modified', async () => {
// Initialize component
- const wrapper = shallowMount(RaceCalculator, {
- data() {
- return {
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(RaceCalculator);
// Change default units
+ await wrapper.find('select[aria-label="Default units"]').setValue('metric');
await wrapper.find('select[aria-label="Default units"]').setValue('imperial');
// New default units should be saved to localStorage
diff --git a/tests/unit/views/SplitCalculator.spec.js b/tests/unit/views/SplitCalculator.spec.js
@@ -8,105 +8,73 @@ beforeEach(() => {
test('should initialize undefined splits to 0:00.00', async () => {
// Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters' },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets(); // onActivated method not called in tests
// Assert results are correct
const rows = wrapper.findAll('tbody tr');
- expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 mi');
expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[0].findAll('td').length).to.equal(4);
- expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 mi');
expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[1].findAll('td').length).to.equal(4);
- expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('5 km');
expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[2].findAll('td').length).to.equal(4);
expect(rows.length).to.equal(3);
});
test('should correctly load split times from split targets', async () => {
- // Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
+ // Initialize localStorage
+ localStorage.setItem('running-tools.target-sets', JSON.stringify({
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
},
- });
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Assert results are correct
const rows = wrapper.findAll('tbody tr');
expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('4:50 / mi');
expect(rows[0].findAll('td').length).to.equal(4);
expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('5:06 / mi');
expect(rows[1].findAll('td').length).to.equal(4);
expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('5:22 / mi');
expect(rows[2].findAll('td').length).to.equal(4);
expect(rows.length).to.equal(3);
});
test('should correctly handle null target set', async () => {
+ // Initialize localStorage
+ localStorage.setItem('running-tools.split-calculator-target-set', '"does_not_exist"');
+
// Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
- ],
- },
- 'B': null,
- },
- selectedTargetSet: 'B',
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Assert results are empty
let rows = wrapper.findAll('tbody tr');
@@ -119,168 +87,133 @@ test('should correctly handle null target set', async () => {
// Assert results are correct
rows = wrapper.findAll('tbody tr');
- expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
- expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
- expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
- expect(rows[0].findAll('td').length).to.equal(4);
- expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
- expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
- expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
- expect(rows[1].findAll('td').length).to.equal(4);
- expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
- expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
- expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
- expect(rows[2].findAll('td').length).to.equal(4);
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 mi');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows.length).to.equal(3);
});
-test('should correctly calculate paces and cululative times from entered split times', async () => {
+test('should correctly calculate paces and cumulative times from entered split times', async () => {
// Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 180 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Update split times
- await wrapper.findAllComponents({ name: 'time-input' })[1].setValue(190);
- await wrapper.findAllComponents({ name: 'time-input' })[2].setValue(200);
+ await wrapper.findAllComponents({ name: 'time-input' })[0].setValue(420);
+ await wrapper.findAllComponents({ name: 'time-input' })[1].setValue(390);
+ await wrapper.findAllComponents({ name: 'time-input' })[2].setValue(390);
// Assert results are correct
const rows = wrapper.findAll('tbody tr');
- expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
- expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
- expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 mi');
+ expect(rows[0].findAll('td')[1].element.textContent).to.equal('7:00.00');
+ expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(420);
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('7:00 / mi');
expect(rows[0].findAll('td').length).to.equal(4);
- expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
- expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
- expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 mi');
+ expect(rows[1].findAll('td')[1].element.textContent).to.equal('13:30.00');
+ expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(390);
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('6:30 / mi');
expect(rows[1].findAll('td').length).to.equal(4);
- expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
- expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
- expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td')[0].element.textContent).to.equal('5 km');
+ expect(rows[2].findAll('td')[1].element.textContent).to.equal('20:00.00');
+ expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(390);
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('5:52 / mi');
expect(rows[2].findAll('td').length).to.equal(4);
expect(rows.length).to.equal(3);
});
test('should correctly sort split targets', async () => {
- // Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
+ // Initialize localStorage (targets are mis-ordered)
+ localStorage.setItem('running-tools.target-sets', JSON.stringify({
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
+ ],
},
- });
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator)
+ await wrapper.vm.reloadTargets();
// Assert results are correct
const rows = wrapper.findAll('tbody tr');
expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[0].findAll('td').length).to.equal(4);
expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[1].findAll('td').length).to.equal(4);
expect(rows[2].findAll('td')[0].element.textContent).to.equal('2 mi');
expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[2].findAll('td').length).to.equal(4);
expect(rows.length).to.equal(3);
});
test('should ignore time based targets', async () => {
- // Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
- { result: 'distance', time: 600 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters' },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
+ // Initialize localStorage
+ localStorage.setItem('running-tools.target-sets', JSON.stringify({
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers' },
+ { result: 'distance', time: 600 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers' },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters' },
+ ],
},
- });
+ }));
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Assert results are correct
const rows = wrapper.findAll('tbody tr');
expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
expect(rows[0].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[0].findAll('td').length).to.equal(4);
expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
expect(rows[1].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[1].findAll('td').length).to.equal(4);
expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
expect(rows[2].findAll('td')[1].element.textContent).to.equal('0:00.00');
expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(0);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / km');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('0:00 / mi');
expect(rows[2].findAll('td').length).to.equal(4);
expect(rows.length).to.equal(3);
});
test('should correctly save split times with split targets in localStorage', async () => {
- // Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 180 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
+ // Initialize localStorage
+ localStorage.setItem('running-tools.target-sets', JSON.stringify({
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 180 },
+ ],
},
- });
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Update split times
await wrapper.findAllComponents({ name: 'time-input' })[1].setValue(190);
@@ -300,32 +233,29 @@ test('should correctly save split times with split targets in localStorage', asy
});
test('should update results when a new target set is selected', async () => {
- // Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- },
- 'B': {
- name: 'Split targets #2',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
+ // Initialize localStorage
+ localStorage.setItem('running-tools.target-sets', JSON.stringify({
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
},
- });
+ 'B': {
+ name: 'Split targets #2',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
+ },
+ }));
+
+ // Initialize component
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Assert default split targets are initially loaded
expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('_split_targets');
@@ -339,17 +269,17 @@ test('should update results when a new target set is selected', async () => {
expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('4:50 / mi');
expect(rows[0].findAll('td').length).to.equal(4);
expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('5:06 / mi');
expect(rows[1].findAll('td').length).to.equal(4);
expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('5:22 / mi');
expect(rows[2].findAll('td').length).to.equal(4);
expect(rows.length).to.equal(3);
});
@@ -357,33 +287,29 @@ test('should update results when a new target set is selected', async () => {
test('should load selected target set from localStorage', async () => {
// Initialize localStorage
localStorage.setItem('running-tools.split-calculator-target-set', '"B"');
+ localStorage.setItem('running-tools.target-sets', JSON.stringify({
+ '_split_targets': {
+ name: 'Split targets',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
+ { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
+ ],
+ },
+ 'B': {
+ name: 'Split targets #2',
+ targets: [
+ { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
+ { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
+ { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
+ ],
+ },
+ }));
+
// Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- },
- 'B': {
- name: 'Split targets #2',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Assert selection is loaded
expect(wrapper.findComponent({ name: 'target-set-selector' }).vm.modelValue).to.equal('B');
@@ -393,75 +319,45 @@ test('should load selected target set from localStorage', async () => {
expect(rows[0].findAll('td')[0].element.textContent).to.equal('1 km');
expect(rows[0].findAll('td')[1].element.textContent).to.equal('3:00.00');
expect(rows[0].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(180);
- expect(rows[0].findAll('td')[3].element.textContent).to.equal('3:00 / km');
+ expect(rows[0].findAll('td')[3].element.textContent).to.equal('4:50 / mi');
expect(rows[0].findAll('td').length).to.equal(4);
expect(rows[1].findAll('td')[0].element.textContent).to.equal('2 km');
expect(rows[1].findAll('td')[1].element.textContent).to.equal('6:10.00');
expect(rows[1].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(190);
- expect(rows[1].findAll('td')[3].element.textContent).to.equal('3:10 / km');
+ expect(rows[1].findAll('td')[3].element.textContent).to.equal('5:06 / mi');
expect(rows[1].findAll('td').length).to.equal(4);
expect(rows[2].findAll('td')[0].element.textContent).to.equal('3000 m');
expect(rows[2].findAll('td')[1].element.textContent).to.equal('9:30.00');
expect(rows[2].findAll('td')[2].findComponent({ name: 'time-input' }).vm.modelValue).to.equal(200);
- expect(rows[2].findAll('td')[3].element.textContent).to.equal('3:20 / km');
+ expect(rows[2].findAll('td')[3].element.textContent).to.equal('5:22 / mi');
expect(rows[2].findAll('td').length).to.equal(4);
expect(rows.length).to.equal(3);
});
test('should save selected target set to localStorage when modified', async () => {
// Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles' },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- },
- 'B': {
- name: 'Split targets #2',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'kilometers', split: 180 },
- { result: 'time', distanceValue: 2, distanceUnit: 'kilometers', split: 190 },
- { result: 'time', distanceValue: 3000, distanceUnit: 'meters', split: 200 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
// Select a new target set
- await wrapper.findComponent({ name: 'target-set-selector' }).setValue('B');
+ await wrapper.findComponent({ name: 'target-set-selector' }).setValue('_race_targets');
// New selected target set should be saved to localStorage
- expect(localStorage.getItem('running-tools.split-calculator-target-set')).to.equal('"B"');
+ expect(localStorage.getItem('running-tools.split-calculator-target-set')).to.equal('"_race_targets"');
});
test('should update paces according to default units setting', async () => {
// Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles', split: 300 },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles', split: 300 },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers', split: 330 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(SplitCalculator);
+ await wrapper.vm.reloadTargets();
+
+ // Enter split times
+ await wrapper.findAllComponents({ name: 'time-input' })[0].setValue(300);
+ await wrapper.findAllComponents({ name: 'time-input' })[1].setValue(300);
+ await wrapper.findAllComponents({ name: 'time-input' })[2].setValue(330);
+
+ // Set default units setting
+ await wrapper.find('select', { name: 'Default units' }).setValue('metric');
// Assert paces are correct
let rows = wrapper.findAll('tbody tr');
@@ -481,23 +377,7 @@ test('should update paces according to default units setting', async () => {
test('should save default units setting to localStorage when modified', async () => {
// Initialize component
- const wrapper = shallowMount(SplitCalculator, {
- data() {
- return {
- targetSets: {
- '_split_targets': {
- name: 'Split targets',
- targets: [
- { result: 'time', distanceValue: 1, distanceUnit: 'miles', split: 300 },
- { result: 'time', distanceValue: 2, distanceUnit: 'miles', split: 300 },
- { result: 'time', distanceValue: 5, distanceUnit: 'kilometers', split: 330 },
- ],
- },
- },
- defaultUnitSystem: 'metric',
- };
- },
- });
+ const wrapper = shallowMount(SplitCalculator);
// Change default units
await wrapper.find('select').setValue('imperial');
diff --git a/tests/unit/views/UnitCalculator.spec.js b/tests/unit/views/UnitCalculator.spec.js
@@ -27,7 +27,7 @@ test('should correctly update controls when category changes', async () => {
expect(wrapper.find('select[aria-label="Output units"]').element.value).to.equal('miles_per_hour');
// Change category
- await wrapper.setData({ category: 'distance' });
+ await wrapper.find('select[aria-label="Selected unit category"]').setValue('distance');
// Assert controls are correct
expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(1);
@@ -121,7 +121,7 @@ test('should correctly load saved inputs', async () => {
expect(wrapper.find('select[aria-label="Output units"]').element.value).to.equal('seconds_per_mile');
// Change category
- await wrapper.setData({ category: 'distance' });
+ await wrapper.find('select[aria-label="Selected unit category"]').setValue('distance');
// Assert inputs are correct
expect(wrapper.findComponent({ name: 'decimal-input' }).vm.modelValue).to.equal(5);