Fixed the exercise submission front-end again.
This commit is contained in:
parent
2881dc5376
commit
4a6d33ffa4
|
@ -8,6 +8,7 @@ const api = axios.create({
|
|||
});
|
||||
|
||||
export enum VideoProcessingStatus {
|
||||
NOT_STARTED = 'NOT_STARTED',
|
||||
WAITING = 'WAITING',
|
||||
IN_PROGRESS = 'IN_PROGRESS',
|
||||
COMPLETED = 'COMPLETED',
|
||||
|
@ -18,24 +19,21 @@ export interface FileMetadata {
|
|||
filename: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
uploadedAt: string;
|
||||
availableForDownload: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export async function uploadVideoToCDN(file: File): Promise<string> {
|
||||
export async function uploadVideoToCDN(file: File): Promise<number> {
|
||||
const response = await api.post('/uploads/video', file, {
|
||||
headers: {
|
||||
'Content-Type': file.type,
|
||||
},
|
||||
});
|
||||
return response.data.id;
|
||||
return response.data.taskId;
|
||||
}
|
||||
|
||||
export async function getVideoProcessingStatus(
|
||||
id: string
|
||||
): Promise<VideoProcessingStatus | null> {
|
||||
export async function getVideoProcessingStatus(taskId: number): Promise<VideoProcessingStatus | null> {
|
||||
try {
|
||||
const response = await api.get(`/uploads/video/${id}/status`);
|
||||
const response = await api.get(`/uploads/video/${taskId}/status`);
|
||||
return response.data.status;
|
||||
} catch (error: any) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
|
@ -45,16 +43,14 @@ export async function getVideoProcessingStatus(
|
|||
}
|
||||
}
|
||||
|
||||
export async function waitUntilVideoProcessingComplete(
|
||||
id: string
|
||||
): Promise<VideoProcessingStatus> {
|
||||
export async function waitUntilVideoProcessingComplete(taskId: number): Promise<VideoProcessingStatus> {
|
||||
let failureCount = 0;
|
||||
let attemptCount = 0;
|
||||
while (failureCount < 5 && attemptCount < 60) {
|
||||
await sleep(1000);
|
||||
attemptCount++;
|
||||
try {
|
||||
const status = await getVideoProcessingStatus(id);
|
||||
const status = await getVideoProcessingStatus(taskId);
|
||||
failureCount = 0;
|
||||
if (
|
||||
status === VideoProcessingStatus.COMPLETED ||
|
||||
|
|
|
@ -51,8 +51,8 @@ export default {
|
|||
upload: 'Video File to Upload',
|
||||
submit: 'Submit',
|
||||
submitUploading: 'Uploading video...',
|
||||
submitUploadFailed: 'Video upload failed.',
|
||||
submitCreatingSubmission: 'Creating submission...',
|
||||
submitVideoProcessing: 'Processing...',
|
||||
submitComplete: 'Submission complete!',
|
||||
submitFailed: 'Submission processing failed. Please try again later.',
|
||||
},
|
||||
|
|
|
@ -48,7 +48,7 @@ A high-level overview of the submission process is as follows:
|
|||
</div>
|
||||
<div class="row">
|
||||
<q-input
|
||||
v-model="submissionModel.date"
|
||||
v-model="submissionModel.performedAt"
|
||||
type="date"
|
||||
:label="$t('gymPage.submitPage.date')"
|
||||
class="col-12"
|
||||
|
@ -91,28 +91,22 @@ A high-level overview of the submission process is as follows:
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, Ref } from 'vue';
|
||||
import { getGymFromRoute } from 'src/router/gym-routing';
|
||||
import {onMounted, ref, Ref} from 'vue';
|
||||
import {getGymFromRoute} from 'src/router/gym-routing';
|
||||
import SlimForm from 'components/SlimForm.vue';
|
||||
import api from 'src/api/main';
|
||||
import { Gym } from 'src/api/main/gyms';
|
||||
import { Exercise } from 'src/api/main/exercises';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { showApiErrorToast, sleep } from 'src/utils';
|
||||
import {
|
||||
uploadVideoToCDN,
|
||||
VideoProcessingStatus,
|
||||
waitUntilVideoProcessingComplete,
|
||||
} from 'src/api/cdn';
|
||||
import { useAuthStore } from 'stores/auth-store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
import {Gym} from 'src/api/main/gyms';
|
||||
import {Exercise} from 'src/api/main/exercises';
|
||||
import {useRoute, useRouter} from 'vue-router';
|
||||
import {showApiErrorToast, showWarningToast, sleep} from 'src/utils';
|
||||
import {uploadVideoToCDN,} from 'src/api/cdn';
|
||||
import {useAuthStore} from 'stores/auth-store';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const quasar = useQuasar();
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
|
@ -127,8 +121,8 @@ let submissionModel = ref({
|
|||
weight: 100,
|
||||
weightUnit: 'Kg',
|
||||
reps: 1,
|
||||
videoFileId: '',
|
||||
date: new Date().toLocaleDateString('en-CA'),
|
||||
performedAt: new Date().toLocaleDateString('en-CA'),
|
||||
taskId: -1
|
||||
});
|
||||
const selectedVideoFile: Ref<File | undefined> = ref<File>();
|
||||
const weightUnits = ['KG', 'LBS'];
|
||||
|
@ -169,59 +163,53 @@ async function onSubmitted() {
|
|||
if (!selectedVideoFile.value || !gym.value) return;
|
||||
|
||||
submitting.value = true;
|
||||
if (await uploadVideo()) {
|
||||
await createSubmission();
|
||||
}
|
||||
submitting.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the selected video and returns true if successful.
|
||||
*/
|
||||
async function uploadVideo(): Promise<boolean> {
|
||||
if (!selectedVideoFile.value) return false;
|
||||
try {
|
||||
// 1. Upload the video to the CDN.
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitUploading');
|
||||
await sleep(1000);
|
||||
submissionModel.value.videoFileId = await uploadVideoToCDN(
|
||||
selectedVideoFile.value
|
||||
);
|
||||
|
||||
// 2. Wait for the video to be processed.
|
||||
submitButtonLabel.value = i18n.t(
|
||||
'gymPage.submitPage.submitVideoProcessing'
|
||||
);
|
||||
const processingStatus = await waitUntilVideoProcessingComplete(
|
||||
submissionModel.value.videoFileId
|
||||
);
|
||||
|
||||
// 3. If successful upload, create the submission.
|
||||
if (processingStatus === VideoProcessingStatus.COMPLETED) {
|
||||
try {
|
||||
submitButtonLabel.value = i18n.t(
|
||||
'gymPage.submitPage.submitCreatingSubmission'
|
||||
);
|
||||
submissionModel.value.taskId = await uploadVideoToCDN(selectedVideoFile.value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
showApiErrorToast(error);
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitUploadFailed');
|
||||
await sleep(1000);
|
||||
const submission = await api.gyms.submissions.createSubmission(
|
||||
gym.value,
|
||||
submissionModel.value,
|
||||
authStore
|
||||
);
|
||||
selectedVideoFile.value = undefined;
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submit');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to create a new submission, and if successful, redirects the user to it.
|
||||
*/
|
||||
async function createSubmission() {
|
||||
if (!gym.value) return;
|
||||
try {
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitCreatingSubmission');
|
||||
await sleep(1000);
|
||||
const submission = await api.gyms.submissions.createSubmission(gym.value, submissionModel.value, authStore);
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitComplete');
|
||||
await sleep(2000);
|
||||
await router.push(`/submissions/${submission.id}`);
|
||||
} catch (error: any) {
|
||||
if (error.response && error.response.status === 400) {
|
||||
quasar.notify({
|
||||
message: error.response.data.message,
|
||||
type: 'warning',
|
||||
position: 'top',
|
||||
});
|
||||
showWarningToast(error.response.data.message);
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitFailed');
|
||||
await sleep(3000);
|
||||
} else {
|
||||
showApiErrorToast(error);
|
||||
}
|
||||
}
|
||||
// Otherwise, report the failed submission and give up.
|
||||
} else {
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submitFailed');
|
||||
await sleep(3000);
|
||||
}
|
||||
} catch (error: any) {
|
||||
showApiErrorToast(error);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
submitButtonLabel.value = i18n.t('gymPage.submitPage.submit');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue