Unify date/time dependencies (#2891)

Remove all date/time-related dependencies from the ui except `dayjs` and
use `dayjs` for all tasks.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
qwerty287 2023-12-04 12:46:24 +01:00 committed by GitHub
parent 6c9ff24ba6
commit 8c6738e2bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 181 additions and 146 deletions

2
web/.gitignore vendored
View file

@ -3,4 +3,4 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
src/assets/timeAgoLocales src/assets/dayjsLocales

View file

@ -23,8 +23,6 @@
"ansi_up": "^6.0.0", "ansi_up": "^6.0.0",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"humanize-duration": "^3.28.0",
"javascript-time-ago": "^2.5.9",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"node-emoji": "^2.0.0", "node-emoji": "^2.0.0",
"pinia": "^2.1.4", "pinia": "^2.1.4",
@ -35,8 +33,6 @@
}, },
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.2.131", "@iconify/json": "^2.2.131",
"@types/humanize-duration": "^3.27.1",
"@types/javascript-time-ago": "^2.0.3",
"@types/lodash": "^4.14.195", "@types/lodash": "^4.14.195",
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
"@types/node-emoji": "^1.8.2", "@types/node-emoji": "^1.8.2",
@ -56,6 +52,7 @@
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
"eslint-plugin-vue-scoped-css": "^2.5.0", "eslint-plugin-vue-scoped-css": "^2.5.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"replace-in-file": "^7.0.2",
"tinycolor2": "^1.6.0", "tinycolor2": "^1.6.0",
"typescript": "5.3.2", "typescript": "5.3.2",
"unplugin-icons": "^0.18.0", "unplugin-icons": "^0.18.0",

View file

@ -26,12 +26,6 @@ dependencies:
fuse.js: fuse.js:
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.0.0 version: 7.0.0
humanize-duration:
specifier: ^3.28.0
version: 3.31.0
javascript-time-ago:
specifier: ^2.5.9
version: 2.5.9
lodash: lodash:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
@ -58,12 +52,6 @@ devDependencies:
'@iconify/json': '@iconify/json':
specifier: ^2.2.131 specifier: ^2.2.131
version: 2.2.143 version: 2.2.143
'@types/humanize-duration':
specifier: ^3.27.1
version: 3.27.3
'@types/javascript-time-ago':
specifier: ^2.0.3
version: 2.0.7
'@types/lodash': '@types/lodash':
specifier: ^4.14.195 specifier: ^4.14.195
version: 4.14.201 version: 4.14.201
@ -121,6 +109,9 @@ devDependencies:
prettier: prettier:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.1.0 version: 3.1.0
replace-in-file:
specifier: ^7.0.2
version: 7.0.2
tinycolor2: tinycolor2:
specifier: ^1.6.0 specifier: ^1.6.0
version: 1.6.0 version: 1.6.0
@ -922,14 +913,6 @@ packages:
/@types/estree@1.0.5: /@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
/@types/humanize-duration@3.27.3:
resolution: {integrity: sha512-wiiiFYjnrYDJE/ujU7wS/NShqp12IKrejozjDtcejP0zYi+cjyjVcfZHwcFUDKVJ7tHGsmgeW2ED92ABIIjfpg==}
dev: true
/@types/javascript-time-ago@2.0.7:
resolution: {integrity: sha512-+sZQnKxkGeDHtX7jJ/iVucZ8Gg8CTnJLpNwynHX+V/G6Z9n6V93+6DO/sv8QU6STAcq04xPw+btFf/OKjCkT0A==}
dev: true
/@types/json-schema@7.0.15: /@types/json-schema@7.0.15:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true dev: true
@ -1528,6 +1511,15 @@ packages:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
/cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/color-convert@1.9.3: /color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies: dependencies:
@ -1766,6 +1758,10 @@ packages:
resolution: {integrity: sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==} resolution: {integrity: sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==}
dev: true dev: true
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
/emojilib@2.4.0: /emojilib@2.4.0:
resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==}
dev: false dev: false
@ -2367,6 +2363,11 @@ packages:
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/get-intrinsic@1.2.2: /get-intrinsic@1.2.2:
resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
dependencies: dependencies:
@ -2418,6 +2419,17 @@ packages:
path-is-absolute: 1.0.1 path-is-absolute: 1.0.1
dev: true dev: true
/glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.1.6
once: 1.4.0
dev: true
/globals@11.12.0: /globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -2523,10 +2535,6 @@ packages:
engines: {node: '>=16.17.0'} engines: {node: '>=16.17.0'}
dev: true dev: true
/humanize-duration@3.31.0:
resolution: {integrity: sha512-fRrehgBG26NNZysRlTq1S+HPtDpp3u+Jzdc/d5A4cEzOD86YLAkDaJyJg8krSdCi7CJ+s7ht3fwRj8Dl+Btd0w==}
dev: false
/ignore@5.3.0: /ignore@5.3.0:
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -2627,6 +2635,11 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: true
/is-glob@4.0.3: /is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2728,12 +2741,6 @@ packages:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true dev: true
/javascript-time-ago@2.5.9:
resolution: {integrity: sha512-pQ8mNco/9g9TqWXWWjP0EWl6i/lAQScOyEeXy5AB+f7MfLSdgyV9BJhiOD1zrIac/lrxPYOWNbyl/IW8CW5n0A==}
dependencies:
relative-time-format: 1.1.6
dev: false
/jiti@1.21.0: /jiti@1.21.0:
resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
hasBin: true hasBin: true
@ -2905,6 +2912,13 @@ packages:
brace-expansion: 1.1.11 brace-expansion: 1.1.11
dev: true dev: true
/minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: true
/minimatch@9.0.3: /minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@ -3279,9 +3293,20 @@ packages:
set-function-name: 2.0.1 set-function-name: 2.0.1
dev: true dev: true
/relative-time-format@1.1.6: /replace-in-file@7.0.2:
resolution: {integrity: sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ==} resolution: {integrity: sha512-tPG+Qmqf+x2Rf1WVdb/9B5tFIf6KJ5hs3fgxh1OTzPRUugPPvyAva7NvCJtnSpmyq6r+ABYcuUOqZkm6yzGSUw==}
dev: false engines: {node: '>=10'}
hasBin: true
dependencies:
chalk: 4.1.2
glob: 8.1.0
yargs: 17.7.2
dev: true
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/resolve-from@4.0.0: /resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
@ -3454,6 +3479,15 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: true dev: true
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: true
/string.prototype.trim@1.2.8: /string.prototype.trim@1.2.8:
resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -3982,6 +4016,15 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/wrappy@1.0.2: /wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true dev: true
@ -3991,6 +4034,11 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yallist@3.1.1: /yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: true dev: true
@ -4012,6 +4060,24 @@ packages:
engines: {node: '>= 14'} engines: {node: '>= 14'}
dev: false dev: false
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
dependencies:
cliui: 8.0.1
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
dev: true
/yocto-queue@0.1.0: /yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}

View file

@ -24,7 +24,7 @@
<Badge v-if="agent.capacity" :label="$t('admin.settings.agents.capacity.badge')" :value="agent.capacity" /> <Badge v-if="agent.capacity" :label="$t('admin.settings.agents.capacity.badge')" :value="agent.capacity" />
</span> </span>
<span class="ml-2">{{ <span class="ml-2">{{
agent.last_contact ? timeAgo.format(agent.last_contact * 1000) : $t('admin.settings.agents.never') agent.last_contact ? date.timeAgo(agent.last_contact * 1000) : $t('admin.settings.agents.never')
}}</span> }}</span>
</span> </span>
<IconButton <IconButton
@ -98,7 +98,7 @@
<TextField <TextField
:model-value=" :model-value="
selectedAgent.last_contact selectedAgent.last_contact
? timeAgo.format(selectedAgent.last_contact * 1000) ? date.timeAgo(selectedAgent.last_contact * 1000)
: $t('admin.settings.agents.never') : $t('admin.settings.agents.never')
" "
disabled disabled
@ -135,14 +135,14 @@ import TextField from '~/components/form/TextField.vue';
import Settings from '~/components/layout/Settings.vue'; import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import { useDate } from '~/compositions/useDate';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import { usePagination } from '~/compositions/usePaginate'; import { usePagination } from '~/compositions/usePaginate';
import useTimeAgo from '~/compositions/useTimeAgo';
import { Agent } from '~/lib/api/types'; import { Agent } from '~/lib/api/types';
const apiClient = useApiClient(); const apiClient = useApiClient();
const notifications = useNotifications(); const notifications = useNotifications();
const timeAgo = useTimeAgo(); const date = useDate();
const { t } = useI18n(); const { t } = useI18n();
const selectedAgent = ref<Partial<Agent>>(); const selectedAgent = ref<Partial<Agent>>();

View file

@ -5,9 +5,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, toRef } from 'vue'; import { computed, toRef } from 'vue';
import { useDate } from '~/compositions/useDate';
import { useElapsedTime } from '~/compositions/useElapsedTime'; import { useElapsedTime } from '~/compositions/useElapsedTime';
import { PipelineStep, PipelineWorkflow } from '~/lib/api/types'; import { PipelineStep, PipelineWorkflow } from '~/lib/api/types';
import { durationAsNumber } from '~/utils/duration';
const props = defineProps<{ const props = defineProps<{
step?: PipelineStep; step?: PipelineStep;
@ -16,6 +16,7 @@ const props = defineProps<{
const step = toRef(props, 'step'); const step = toRef(props, 'step');
const workflow = toRef(props, 'workflow'); const workflow = toRef(props, 'workflow');
const { durationAsNumber } = useDate();
const durationRaw = computed(() => { const durationRaw = computed(() => {
const start = (step.value ? step.value?.start_time : workflow.value?.start_time) || 0; const start = (step.value ? step.value?.start_time : workflow.value?.start_time) || 0;

View file

@ -18,8 +18,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useLocalStorage } from '@vueuse/core'; import { useLocalStorage } from '@vueuse/core';
import dayjs from 'dayjs';
import TimeAgo from 'javascript-time-ago';
import { SUPPORTED_LOCALES } from 'virtual:vue-i18n-supported-locales'; import { SUPPORTED_LOCALES } from 'virtual:vue-i18n-supported-locales';
import { computed } from 'vue'; import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -44,8 +42,6 @@ const selectedLocale = computed<string>({
async set(_selectedLocale) { async set(_selectedLocale) {
await setI18nLanguage(_selectedLocale); await setI18nLanguage(_selectedLocale);
storedLocale.value = _selectedLocale; storedLocale.value = _selectedLocale;
dayjs.locale(_selectedLocale);
TimeAgo.setDefaultLocale(_selectedLocale);
}, },
get() { get() {
return storedLocale.value; return storedLocale.value;

View file

@ -1,5 +1,7 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat'; import advancedFormat from 'dayjs/plugin/advancedFormat';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -7,13 +9,43 @@ import { useI18n } from 'vue-i18n';
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(advancedFormat); dayjs.extend(advancedFormat);
dayjs.extend(relativeTime);
dayjs.extend(duration);
export function useDate() { export function useDate() {
function toLocaleString(date: Date) { function toLocaleString(date: Date) {
return dayjs(date).format(useI18n().t('time.tmpl')); return dayjs(date).format(useI18n().t('time.tmpl'));
} }
function timeAgo(date: Date | string | number) {
return dayjs().to(dayjs(date));
}
function prettyDuration(durationMs: number) {
return dayjs.duration(durationMs).humanize();
}
const addedLocales = ['en'];
async function setDayjsLocale(locale: string) {
if (!addedLocales.includes(locale)) {
const l = await import(`~/assets/dayjsLocales/${locale}.js`);
dayjs.locale(l.default);
} else {
dayjs.locale(locale);
}
}
function durationAsNumber(durationMs: number): string {
const dur = dayjs.duration(durationMs);
return dur.format(dur.hours() > 1 ? 'HH:mm:ss' : 'mm:ss');
}
return { return {
toLocaleString, toLocaleString,
timeAgo,
prettyDuration,
setDayjsLocale,
durationAsNumber,
}; };
} }

View file

@ -3,8 +3,9 @@ import { createI18n } from 'vue-i18n';
import { getUserLanguage } from '~/utils/locale'; import { getUserLanguage } from '~/utils/locale';
import { loadTimeAgoLocale } from './useTimeAgo'; import { useDate } from './useDate';
const { setDayjsLocale } = useDate();
const userLanguage = getUserLanguage(); const userLanguage = getUserLanguage();
const fallbackLocale = 'en'; const fallbackLocale = 'en';
export const i18n = createI18n({ export const i18n = createI18n({
@ -14,13 +15,11 @@ export const i18n = createI18n({
fallbackLocale, fallbackLocale,
}); });
export const loadLocaleMessages = async (locale: string) => { const loadLocaleMessages = async (locale: string) => {
const { default: messages } = await import(`~/assets/locales/${locale}.json`); const { default: messages } = await import(`~/assets/locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages); i18n.global.setLocaleMessage(locale, messages);
loadTimeAgoLocale(locale);
return nextTick(); return nextTick();
}; };
@ -29,7 +28,9 @@ export const setI18nLanguage = async (lang: string): Promise<void> => {
await loadLocaleMessages(lang); await loadLocaleMessages(lang);
} }
i18n.global.locale.value = lang; i18n.global.locale.value = lang;
await setDayjsLocale(lang);
}; };
loadLocaleMessages(fallbackLocale); loadLocaleMessages(fallbackLocale);
loadLocaleMessages(userLanguage); loadLocaleMessages(userLanguage);
setDayjsLocale(userLanguage);

View file

@ -4,12 +4,9 @@ import { useI18n } from 'vue-i18n';
import { useDate } from '~/compositions/useDate'; import { useDate } from '~/compositions/useDate';
import { useElapsedTime } from '~/compositions/useElapsedTime'; import { useElapsedTime } from '~/compositions/useElapsedTime';
import { Pipeline } from '~/lib/api/types'; import { Pipeline } from '~/lib/api/types';
import { prettyDuration } from '~/utils/duration';
import { convertEmojis } from '~/utils/emoji'; import { convertEmojis } from '~/utils/emoji';
import useTimeAgo from './useTimeAgo'; const { toLocaleString, timeAgo, prettyDuration } = useDate();
const { toLocaleString } = useDate();
export default (pipeline: Ref<Pipeline | undefined>) => { export default (pipeline: Ref<Pipeline | undefined>) => {
const sinceRaw = computed(() => { const sinceRaw = computed(() => {
@ -28,7 +25,6 @@ export default (pipeline: Ref<Pipeline | undefined>) => {
const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw); const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw);
const i18n = useI18n(); const i18n = useI18n();
const timeAgo = useTimeAgo();
const since = computed(() => { const since = computed(() => {
if (sinceRaw.value === 0) { if (sinceRaw.value === 0) {
return i18n.t('time.not_started'); return i18n.t('time.not_started');
@ -38,7 +34,8 @@ export default (pipeline: Ref<Pipeline | undefined>) => {
return null; return null;
} }
return timeAgo.format(sinceElapsed.value); // TODO check whetehr elapsed works
return timeAgo(sinceElapsed.value);
}); });
const durationRaw = computed(() => { const durationRaw = computed(() => {

View file

@ -1,17 +0,0 @@
import TimeAgo from 'javascript-time-ago';
import en from 'javascript-time-ago/locale/en.json';
import { getUserLanguage } from '~/utils/locale';
TimeAgo.addDefaultLocale(en);
const addedLocales = ['en'];
export default () => new TimeAgo(getUserLanguage());
export async function loadTimeAgoLocale(locale: string) {
if (!addedLocales.includes(locale)) {
const { default: timeAgoLocale } = await import(`~/assets/timeAgoLocales/${locale}.js`);
TimeAgo.addLocale(timeAgoLocale);
addedLocales.push(locale);
}
}

View file

@ -1,44 +0,0 @@
import humanizeDuration from 'humanize-duration';
import { useI18n } from 'vue-i18n';
export function prettyDuration(durationMs: number): string {
const i18n = useI18n();
const short = {
w: () => i18n.t('time.weeks_short'),
d: () => i18n.t('time.days_short'),
h: () => i18n.t('time.hours_short'),
m: () => i18n.t('time.min_short'),
s: () => i18n.t('time.sec_short'),
};
const durationOptions: humanizeDuration.HumanizerOptions = {
round: true,
languages: { short },
language: 'short',
};
if (durationMs < 1000 * 60 * 60) {
return humanizeDuration(durationMs, durationOptions);
}
return humanizeDuration(durationMs, { ...durationOptions, units: ['y', 'mo', 'd', 'h', 'm'] });
}
function leadingZeros(n: number, length: number): string {
let res = n.toString();
while (res.length < length) {
res = `0${res}`;
}
return res;
}
export function durationAsNumber(durationMs: number): string {
const durationSeconds = durationMs / 1000;
const seconds = leadingZeros(Math.floor(durationSeconds % 60), 2);
const minutes = leadingZeros(Math.floor(durationSeconds / 60) % 60, 2);
const hours = Math.floor(durationSeconds / 3600);
if (hours !== 0) {
return `${hours}:${minutes}:${seconds}`;
}
return `${minutes}:${seconds}`;
}

View file

@ -3,6 +3,7 @@ import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { copyFile, existsSync, mkdirSync, readdirSync } from 'fs'; import { copyFile, existsSync, mkdirSync, readdirSync } from 'fs';
import path from 'path'; import path from 'path';
import replace from 'replace-in-file';
import IconsResolver from 'unplugin-icons/resolver'; import IconsResolver from 'unplugin-icons/resolver';
import Icons from 'unplugin-icons/vite'; import Icons from 'unplugin-icons/vite';
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
@ -56,37 +57,42 @@ export default defineConfig({
const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', '')); const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', ''));
if (!existsSync('src/assets/timeAgoLocales')) { if (!existsSync('src/assets/dayjsLocales')) {
mkdirSync('src/assets/timeAgoLocales'); mkdirSync('src/assets/dayjsLocales');
} }
filenames.forEach((name) => { filenames.forEach(async (name) => {
// copy timeAgo language // English is always directly loaded (compiled by Vite) and thus not copied
if (name === 'zh-Hans') { if (name === 'en') {
// zh-Hans is called zh in javascript-time-ago, so we need to rename this return;
copyFile(
'node_modules/javascript-time-ago/locale/zh.json.js',
'src/assets/timeAgoLocales/zh-Hans.js',
// eslint-disable-next-line promise/prefer-await-to-callbacks
(err) => {
if (err) {
throw err;
}
},
);
} else if (name !== 'en') {
// English is always directly loaded (compiled by Vite) and thus not copied
copyFile(
`node_modules/javascript-time-ago/locale/${name}.json.js`,
`src/assets/timeAgoLocales/${name}.js`,
// eslint-disable-next-line promise/prefer-await-to-callbacks
(err) => {
if (err) {
throw err;
}
},
);
} }
let langName = name;
// copy dayjs language
if (name === 'zh-Hans') {
// zh-Hans is called zh in dayjs
langName = 'zh';
} else if (name === 'zh-Hant') {
// zh-Hant is called zh-cn in dayjs
langName = 'zh-cn';
}
copyFile(
`node_modules/dayjs/esm/locale/${langName}.js`,
`src/assets/dayjsLocales/${name}.js`,
// eslint-disable-next-line promise/prefer-await-to-callbacks
(err) => {
if (err) {
throw err;
}
},
);
});
replace.sync({
files: 'src/assets/dayjsLocales/*.js',
// remove any dayjs import and any dayjs.locale call
from: /(?:import dayjs.*'|dayjs\.locale.*);/g,
to: '',
}); });
return { return {