Client - display info and errors on workout edition
This commit is contained in:
parent
6bfcb24133
commit
997469d959
@ -9,7 +9,7 @@
|
||||
<form @submit.prevent="updateWorkout">
|
||||
<div class="form-items">
|
||||
<div class="form-item-radio" v-if="isCreation">
|
||||
<div class="radio">
|
||||
<div>
|
||||
<input
|
||||
id="withGpx"
|
||||
type="radio"
|
||||
@ -19,7 +19,7 @@
|
||||
/>
|
||||
<label for="withGpx">{{ t('workouts.WITH_GPX') }}</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<div>
|
||||
<input
|
||||
id="withoutGpx"
|
||||
type="radio"
|
||||
@ -52,13 +52,7 @@
|
||||
<div class="form-item" v-if="isCreation && withGpx">
|
||||
<label for="gpxFile">
|
||||
{{ t('workouts.GPX_FILE') }}
|
||||
<sup>
|
||||
<i class="fa fa-question-circle" aria-hidden="true" />
|
||||
</sup>
|
||||
{{ t('workouts.ZIP_FILE') }}
|
||||
<sup
|
||||
><i class="fa fa-question-circle" aria-hidden="true" /></sup
|
||||
>:
|
||||
{{ t('workouts.ZIP_ARCHIVE_DESCRIPTION') }}:
|
||||
</label>
|
||||
<input
|
||||
id="gpxFile"
|
||||
@ -68,6 +62,25 @@
|
||||
:disabled="loading"
|
||||
@input="updateFile"
|
||||
/>
|
||||
<div class="files-help">
|
||||
<div>
|
||||
<strong>{{ t('workouts.GPX_FILE') }}:</strong>
|
||||
<ul>
|
||||
<li>{{ t('workouts.MAX_SIZE') }}: {{ fileSizeLimit }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>{{ t('workouts.ZIP_ARCHIVE') }}:</strong>
|
||||
<ul>
|
||||
<li>{{ t('workouts.NO_FOLDER') }}</li>
|
||||
<li>
|
||||
{{ t('workouts.MAX_FILES') }}: {{ gpx_limit_import }}
|
||||
</li>
|
||||
<li>{{ t('workouts.MAX_SIZE') }}: {{ zipSizeLimit }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item" v-else>
|
||||
<label for="title"> {{ t('workouts.TITLE') }}: </label>
|
||||
@ -206,11 +219,13 @@
|
||||
import ErrorMessage from '@/components/Common/ErrorMessage.vue'
|
||||
import Loader from '@/components/Common/Loader.vue'
|
||||
import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants'
|
||||
import { IAppConfig } from '@/types/application'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { IWorkout, IWorkoutForm } from '@/types/workouts'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
@ -257,6 +272,16 @@
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const appConfig: ComputedRef<IAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
const gpx_limit_import = appConfig.value.gpx_limit_import
|
||||
const zipSizeLimit = appConfig.value.max_zip_file_size
|
||||
? getReadableFileSize(appConfig.value.max_zip_file_size)
|
||||
: ''
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
@ -374,10 +399,14 @@
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
errorMessages,
|
||||
fileSizeLimit,
|
||||
gpx_limit_import,
|
||||
t,
|
||||
translatedSports,
|
||||
withGpx,
|
||||
zipSizeLimit,
|
||||
workoutDataObject: workoutForm,
|
||||
onCancel,
|
||||
updateFile,
|
||||
@ -450,15 +479,16 @@
|
||||
.form-item-radio {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.radio {
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
input {
|
||||
margin-top: -2px;
|
||||
vertical-align: middle;
|
||||
label {
|
||||
font-weight: normal;
|
||||
@media screen and (max-width: $small-limit) {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
input {
|
||||
margin-top: -2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,6 +499,29 @@
|
||||
margin: $default-padding * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.files-help {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
background-color: var(--info-background-color);
|
||||
border-radius: $border-radius;
|
||||
color: var(--info-color);
|
||||
font-size: 0.8em;
|
||||
|
||||
margin-top: $default-margin;
|
||||
padding: $default-padding;
|
||||
div {
|
||||
display: flex;
|
||||
@media screen and (max-width: $small-limit) {
|
||||
flex-direction: column;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0 $default-padding * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,18 @@
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Error. Please try again or contact the administrator.",
|
||||
"Error, Please try again or contact the administrator": "Error. Please try again or contact the administrator.",
|
||||
"File extension not allowed": "File extension not allowed.",
|
||||
"File size is greater than the allowed size": "File size is greater than the allowed size.",
|
||||
"Invalid credentials": "Invalid credentials.",
|
||||
"Network Error": "Network Error",
|
||||
"Invalid payload": "Invalid data.",
|
||||
"Invalid token, Please log in again": "Invalid token. Please log in again.",
|
||||
"Network Error": "Network Error.",
|
||||
"No file part": "No file provided.",
|
||||
"No selected file": "No selected file.",
|
||||
"Provide a valid auth token": "Provide a valid auth token.",
|
||||
"Password and password confirmation don't match": "Password and password confirmation don't match.",
|
||||
"Password: 8 characters required": "Password: 8 characters required.",
|
||||
"Signature expired, Please log in again": "Signature expired. Please log in again.",
|
||||
"Username: 3 to 12 characters required": "Username: 3 to 12 characters required.",
|
||||
"Valid email must be provided": "Valid email must be provided."
|
||||
}
|
||||
|
@ -10,16 +10,19 @@
|
||||
"EDIT_WORKOUT": "Edit the workout",
|
||||
"ELEVATION": "elevation",
|
||||
"END": "end",
|
||||
"GPX_FILE": "fichier .gpx",
|
||||
"GPX_FILE": ".gpx file",
|
||||
"KM": "km",
|
||||
"LATEST_WORKOUTS": "Latest workouts",
|
||||
"LOAD_MORE_WORKOUT": "Load more workouts",
|
||||
"MAX_ALTITUDE": "max. altitude",
|
||||
"MAX_FILES": "max files",
|
||||
"MAX_SIZE": "max size",
|
||||
"MAX_SPEED": "max. speed",
|
||||
"MIN_ALTITUDE": "min. altitude",
|
||||
"NEXT_SEGMENT": "No next segment",
|
||||
"NEXT_WORKOUT": "Next workout",
|
||||
"NO_DATA_CLEANING": "data from gpx, without any cleaning",
|
||||
"NO_FOLDER": "no folder inside",
|
||||
"NO_MAP": "No map",
|
||||
"NO_NEXT_SEGMENT": "No next segment",
|
||||
"NO_NEXT_WORKOUT": "No next workout",
|
||||
@ -66,5 +69,6 @@
|
||||
"WORKOUT": "workout | workouts",
|
||||
"WORKOUT_DATE": "workout date",
|
||||
"WORKOUT_DELETION_CONFIRMATION": "Are you sure you want to delete this workout?",
|
||||
"ZIP_FILE": "or .zip file containing .gpx files"
|
||||
"ZIP_ARCHIVE": ".zip file",
|
||||
"ZIP_ARCHIVE_DESCRIPTION": "or .zip file containing .gpx files"
|
||||
}
|
||||
|
@ -2,10 +2,18 @@
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
|
||||
"Error, Please try again or contact the administrator": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
|
||||
"File extension not allowed": "Extension de fichier non autorisée.",
|
||||
"File size is greater than the allowed size": "La taille du fichier est supérieure à la limite autorisée.",
|
||||
"Invalid credentials": "Identifiants invalides.",
|
||||
"Network Error": "Erreur Réseau",
|
||||
"Invalid payload": "Données incorrectes.",
|
||||
"Invalid token, Please log in again": "Jeton invalide. Merci de vous reconnecter.",
|
||||
"No file part": "Pas de fichier fourni.",
|
||||
"No selected file": "Pas de fichier sélectionné.",
|
||||
"Network Error": "Erreur Réseau.",
|
||||
"Provide a valid auth token": "Merci de fournir un jeton valide.",
|
||||
"Password and password confirmation don't match": "Les mots de passe saisis sont différents.",
|
||||
"Password: 8 characters required": "8 caractères minimum pour le mot de passe.",
|
||||
"Signature expired, Please log in again": "Signature expirée. Merci de vous reconnecter.",
|
||||
"Username: 3 to 12 characters required": "3 à 12 caractères requis pour le nom.",
|
||||
"Valid email must be provided": "L'email fourni n'est pas valide."
|
||||
}
|
||||
|
@ -10,16 +10,19 @@
|
||||
"EDIT_WORKOUT": "Modifier la séance",
|
||||
"ELEVATION": "altitude",
|
||||
"END": "fin",
|
||||
"GPX_FILE": ".gpx file",
|
||||
"GPX_FILE": "fichier .gpx",
|
||||
"KM": "km",
|
||||
"LATEST_WORKOUTS": "Séances récentes",
|
||||
"LOAD_MORE_WORKOUT": "Charger les séances suivantes",
|
||||
"MAX_ALTITUDE": "altitude max",
|
||||
"MAX_FILES": "fichiers max. ",
|
||||
"MAX_SIZE": "taille max. ",
|
||||
"MAX_SPEED": "vitesse max",
|
||||
"MIN_ALTITUDE": "altitude min",
|
||||
"NEXT_SEGMENT": "Segment suivant",
|
||||
"NEXT_WORKOUT": "Séance suivante",
|
||||
"NO_DATA_CLEANING": "données issues du fichier gpx, sans correction",
|
||||
"NO_FOLDER": "pas de répertoire",
|
||||
"NO_MAP": "Pas de carte",
|
||||
"NO_NEXT_SEGMENT": "Pas de segment suivant",
|
||||
"NO_NEXT_WORKOUT": "Pas de séance suivante",
|
||||
@ -66,5 +69,6 @@
|
||||
"WORKOUT": "séance | séances",
|
||||
"WORKOUT_DATE": "date de la séance",
|
||||
"WORKOUT_DELETION_CONFIRMATION": "Etes-vous sûr de vouloir supprimer cette séance ?",
|
||||
"ZIP_FILE": "ou une archive .zip contenant des fichiers .gpx"
|
||||
"ZIP_ARCHIVE": "archive .zip",
|
||||
"ZIP_ARCHIVE_DESCRIPTION": "ou une archive .zip contenant des fichiers .gpx"
|
||||
}
|
||||
|
@ -32,7 +32,8 @@
|
||||
|
||||
--alert-background-color: #c8cdd3;
|
||||
--alert-color: #3f3f3f;
|
||||
|
||||
--info-background-color: #e5e7ea;
|
||||
--info-color: var(--app-color);
|
||||
--error-background-color: #ffd2d2;
|
||||
--error-color: #db1924;
|
||||
|
||||
|
@ -168,7 +168,7 @@ export const actions: ActionTree<IWorkoutsState, IRootState> &
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
context.commit(WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_LOADING, true)
|
||||
if (!payload.file) {
|
||||
throw new Error('No gpx file provided')
|
||||
throw new Error('No file part')
|
||||
}
|
||||
const form = new FormData()
|
||||
form.append('file', payload.file)
|
||||
|
14
fittrackee_client/src/utils/files.ts
Normal file
14
fittrackee_client/src/utils/files.ts
Normal file
@ -0,0 +1,14 @@
|
||||
const suffixes = ['bytes', 'KB', 'MB', 'GB', 'TB']
|
||||
|
||||
export const getReadableFileSize = (
|
||||
fileSize: number,
|
||||
asText = true
|
||||
): string | Record<string, string> => {
|
||||
const i = Math.floor(Math.log(fileSize) / Math.log(1024))
|
||||
if (!fileSize) {
|
||||
return asText ? '0 bytes' : { size: '0', suffix: 'bytes' }
|
||||
}
|
||||
const size = (fileSize / Math.pow(1024, i)).toFixed(1)
|
||||
const suffix = suffixes[i]
|
||||
return asText ? `${size}${suffix}` : { size, suffix }
|
||||
}
|
@ -17,8 +17,7 @@ export const getApiUrl = (): string => {
|
||||
// TODO: update api error messages to remove these workarounds
|
||||
const removeLastEndOfLine = (text: string): string => text.replace(/\n$/gm, '')
|
||||
const removeLastDot = (text: string): string => text.replace(/\.$/gm, '')
|
||||
const removeErrorDot = (text: string): string =>
|
||||
text.replace(/^Error\./gm, 'Error,')
|
||||
const replaceInternalDots = (text: string): string => text.replace(/\./gm, ',')
|
||||
|
||||
export const handleError = (
|
||||
context:
|
||||
@ -33,14 +32,16 @@ export const handleError = (
|
||||
let errorMessages = !error
|
||||
? msg
|
||||
: error.response
|
||||
? error.response.data.message
|
||||
? error.response.status === 413
|
||||
? 'File size is greater than the allowed size'
|
||||
: error.response.data.message
|
||||
? error.response.data.message
|
||||
: msg
|
||||
: error.message
|
||||
? error.message
|
||||
: msg
|
||||
errorMessages = removeLastEndOfLine(errorMessages)
|
||||
errorMessages = removeErrorDot(errorMessages)
|
||||
errorMessages = replaceInternalDots(errorMessages)
|
||||
context.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
errorMessages.includes('\n')
|
||||
|
61
fittrackee_client/tests/unit/utils/files.spec.ts
Normal file
61
fittrackee_client/tests/unit/utils/files.spec.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
|
||||
describe('getReadableFileSize (as text)', () => {
|
||||
const testsParams = [
|
||||
{
|
||||
description: 'returns 0 bytes if provided file size is 0',
|
||||
inputFileSize: 0,
|
||||
expectedReadableFileSize: '0 bytes',
|
||||
},
|
||||
{
|
||||
description: 'returns 1.0KB if provided file size is 1024',
|
||||
inputFileSize: 1024,
|
||||
expectedReadableFileSize: '1.0KB',
|
||||
},
|
||||
{
|
||||
description: 'returns 43.5MB if provided file size is 45654654',
|
||||
inputFileSize: 45654654,
|
||||
expectedReadableFileSize: '43.5MB',
|
||||
},
|
||||
]
|
||||
|
||||
testsParams.map((testParams) => {
|
||||
it(testParams.description, () => {
|
||||
assert.equal(
|
||||
getReadableFileSize(testParams.inputFileSize, true),
|
||||
testParams.expectedReadableFileSize
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getReadableFileSize (as object)', () => {
|
||||
const testsParams = [
|
||||
{
|
||||
description: 'returns 0 bytes if provided file size is 0',
|
||||
inputFileSize: 0,
|
||||
expectedReadableFileSize: { size: '0', suffix: 'bytes' },
|
||||
},
|
||||
{
|
||||
description: 'returns 1.0KB if provided file size is 1024',
|
||||
inputFileSize: 1024,
|
||||
expectedReadableFileSize: { size: '1.0', suffix: 'KB' },
|
||||
},
|
||||
{
|
||||
description: 'returns 43.5MB if provided file size is 45654654',
|
||||
inputFileSize: 45654654,
|
||||
expectedReadableFileSize: { size: '43.5', suffix: 'MB' },
|
||||
},
|
||||
]
|
||||
|
||||
testsParams.map((testParams) => {
|
||||
it(testParams.description, () => {
|
||||
assert.deepEqual(
|
||||
getReadableFileSize(testParams.inputFileSize, false),
|
||||
testParams.expectedReadableFileSize
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user