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