Skip to content

Commit d6476e7

Browse files
authored
Merge PR #366: Consolidate audio/video components
Consolidates 6 media recording components into a single MediaRecord component, reducing code complexity by 80% while maintaining full functionality for research assessments.
2 parents 9f7b6b9 + b36ccd0 commit d6476e7

File tree

38 files changed

+19726
-991
lines changed

38 files changed

+19726
-991
lines changed

package-lock.json

Lines changed: 387 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'"
1212
},
1313
"dependencies": {
14+
"@adapttive/vue-markdown": "^4.0.2",
1415
"axios": "^1.7.4",
1516
"bootstrap-vue": "^2.23.1",
1617
"bowser": "^2.11.0",
@@ -23,7 +24,6 @@
2324
"msr": "^1.3.4",
2425
"vue": "^2.7.16",
2526
"vue-i18n": "^8.28.2",
26-
"@adapttive/vue-markdown": "^4.0.2",
2727
"vue-multiselect": "^2.1.9",
2828
"vue-router": "^3.6.5",
2929
"vue-slider-component": "^3.2.24",
@@ -33,6 +33,7 @@
3333
"vuex": "^3.6.2"
3434
},
3535
"devDependencies": {
36+
"@babel/eslint-parser": "^7.24.1",
3637
"@intlify/vue-i18n-loader": "^3.3.0",
3738
"@vue/cli-plugin-babel": "^5.0.8",
3839
"@vue/cli-plugin-e2e-cypress": "^5.0.8",
@@ -42,9 +43,9 @@
4243
"@vue/cli-plugin-vuex": "~5.0.8",
4344
"@vue/cli-service": "^5.0.8",
4445
"@vue/test-utils": "^1.0.3",
45-
"@babel/eslint-parser": "^7.24.1",
46-
"eslint": "^7.32.0",
46+
"@vue/vue2-jest": "^27.0.0",
4747
"cypress": "^13.7.2",
48+
"eslint": "^7.32.0",
4849
"eslint-plugin-vue": "^7.20.0",
4950
"vue-cli-plugin-i18n": "^2.3.2",
5051
"vue-template-compiler": "^2.7.16"

src/App.vue

Lines changed: 83 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ export default {
299299
// but there will be a change in responses that needs to trigger
300300
// this.setVisibility().
301301
if ((oldP !== newP) && newP === 100) {
302-
// console.log('time to check for branching activities!');
302+
//
303303
this.setVisbility();
304304
}
305305
},
@@ -518,11 +518,13 @@ export default {
518518
_.map(eachActivityList, (itemObj) => {
519519
const newObj = { ...itemObj };
520520
if (itemObj['@type'] === 'reproschema:Response') {
521-
if (itemObj.value instanceof Blob) {
521+
if (itemObj.value instanceof Blob && (itemObj.mimeType === "audio/wav" || itemObj.mimeType === "video/mp4")) {
522522
const keyStrings = (itemObj.isAbout.split('/'));
523523
const rId = itemObj['@id'].split('uuid:')[1];
524-
jszip.folder(fileName).file(`${keyStrings[keyStrings.length-1]}-${rId}.wav`, itemObj.value);
525-
newObj.value = `${keyStrings[keyStrings.length-1]}-${rId}.wav`;
524+
const extension = itemObj.mimeType === "audio/wav" ? "wav" : "mp4";
525+
const filename = `${keyStrings[keyStrings.length-1]}-${rId}.${extension}`;
526+
jszip.folder(fileName).file(filename, itemObj.value);
527+
newObj.value = filename;
526528
}
527529
}
528530
activityData.push(newObj);
@@ -552,81 +554,6 @@ export default {
552554
});
553555
},
554556
},
555-
watch: {
556-
$route() {
557-
if (this.$route.params.id !== undefined) {
558-
this.$store.dispatch('setActivityIndex', this.$route.params.id);
559-
}
560-
},
561-
visibilityConditions: {
562-
handler(newC) {
563-
if (!_.isEmpty(newC)) {
564-
this.setVisbility();
565-
}
566-
},
567-
deep: true,
568-
},
569-
},
570-
created() {
571-
const url = this.$route.query.url;
572-
if (url) {
573-
this.protocolUrl = url;
574-
}
575-
this.$store.dispatch('getBaseSchema', url).then(() => this.getPrefLabel());
576-
// this.$store.dispatch('getBaseSchema', url);
577-
},
578-
mounted() {
579-
new EmailDecoder('[data-email]');
580-
this.clientSpecs = JSON.stringify(Bowser.parse(window.navigator.userAgent));
581-
this.browserType = Bowser.parse(window.navigator.userAgent).browser.name;
582-
if (config.checkMediaPermission) {
583-
this.checkPermission();
584-
}
585-
if (this.$route.query.lang) {
586-
this.selected_language = this.$route.query.lang;
587-
i18n.locale = this.selected_language;
588-
} else this.selected_language = 'en';
589-
590-
if (this.$route.query.uid) {
591-
// console.log(407, this.$route.query.uid);
592-
this.$store.dispatch('saveParticipantId', this.$route.query.uid);
593-
} else if (config.generateRandomUid) {
594-
this.$store.dispatch('saveParticipantId', uuidv4());
595-
}
596-
if (this.$route.params.id) {
597-
this.$store.dispatch('setActivityIndex', this.$route.params.id);
598-
}
599-
axios.get('https://raw.githubusercontent.com/ReproNim/reproschema-library/master/resources/languages.json').then((resp) => {
600-
this.langMap = resp.data;
601-
});
602-
this.$store.dispatch('setParticipantUUID', uuidv4()); // set participant UUID for the current user
603-
if (this.$route.query.expiry_time) {
604-
this.$store.dispatch('setExpiryMinutes', this.$route.query.expiry_time);
605-
}
606-
if (this.$route.query.auth_token) {
607-
this.$store.dispatch('setAuthToken', this.$route.query.auth_token);
608-
}
609-
if (!_.isEmpty(this.$route.query)) {
610-
this.$store.dispatch('setQueryParameters', this.$route.query);
611-
}
612-
613-
// const formData = new FormData();
614-
// const TOKEN = this.$store.getters.getAuthToken;
615-
// if (TOKEN) {
616-
// formData.append('file', null);
617-
// formData.append('auth_token', `${TOKEN}`);
618-
// axios.post(`${config.backendServer}/submit`, formData, {
619-
// 'Content-Type': 'multipart/form-data',
620-
// }).then((res) => {
621-
// // console.log('SUCCESS!!', res.status);
622-
// })
623-
// .catch((e) => {
624-
// if (e.response.status === 403) {
625-
// this.invalidToken = true;
626-
// }
627-
// });
628-
// }
629-
},
630557
computed: {
631558
accessDeniedPath() {
632559
let path = require('./assets/403-Access-Forbidden-HTML-Template.gif');
@@ -655,7 +582,7 @@ export default {
655582
'$1-$2-$3T$4:$5:$6Z'
656583
);
657584
const time = Date.parse(formattedTime);
658-
console.log(537, timestamp, formattedTime, time);
585+
// console.log(537, timestamp, formattedTime, time);
659586
return time;
660587
},
661588
showTimer() {
@@ -704,6 +631,7 @@ export default {
704631
if (!_.isEmpty(this.$store.state.schema)) {
705632
const order = _.map(this.$store.state.schema['http://schema.repronim.org/order'][0]['@list'],
706633
u => u['@id']);
634+
// console.log(order)
707635
return order;
708636
}
709637
return [];
@@ -817,6 +745,81 @@ export default {
817745
return this.$store.getters.getParticipantId;
818746
},
819747
},
748+
mounted() {
749+
new EmailDecoder('[data-email]');
750+
this.clientSpecs = JSON.stringify(Bowser.parse(window.navigator.userAgent));
751+
this.browserType = Bowser.parse(window.navigator.userAgent).browser.name;
752+
if (config.checkMediaPermission) {
753+
this.checkPermission();
754+
}
755+
if (this.$route.query.lang) {
756+
this.selected_language = this.$route.query.lang;
757+
i18n.locale = this.selected_language;
758+
} else this.selected_language = 'en';
759+
760+
if (this.$route.query.uid) {
761+
// console.log(407, this.$route.query.uid);
762+
this.$store.dispatch('saveParticipantId', this.$route.query.uid);
763+
} else if (config.generateRandomUid) {
764+
this.$store.dispatch('saveParticipantId', uuidv4());
765+
}
766+
if (this.$route.params.id) {
767+
this.$store.dispatch('setActivityIndex', this.$route.params.id);
768+
}
769+
axios.get('https://raw.githubusercontent.com/ReproNim/reproschema-library/master/resources/languages.json').then((resp) => {
770+
this.langMap = resp.data;
771+
});
772+
this.$store.dispatch('setParticipantUUID', uuidv4()); // set participant UUID for the current user
773+
if (this.$route.query.expiry_time) {
774+
this.$store.dispatch('setExpiryMinutes', this.$route.query.expiry_time);
775+
}
776+
if (this.$route.query.auth_token) {
777+
this.$store.dispatch('setAuthToken', this.$route.query.auth_token);
778+
}
779+
if (!_.isEmpty(this.$route.query)) {
780+
this.$store.dispatch('setQueryParameters', this.$route.query);
781+
}
782+
783+
// const formData = new FormData();
784+
// const TOKEN = this.$store.getters.getAuthToken;
785+
// if (TOKEN) {
786+
// formData.append('file', null);
787+
// formData.append('auth_token', `${TOKEN}`);
788+
// axios.post(`${config.backendServer}/submit`, formData, {
789+
// 'Content-Type': 'multipart/form-data',
790+
// }).then((res) => {
791+
// // console.log('SUCCESS!!', res.status);
792+
// })
793+
// .catch((e) => {
794+
// if (e.response.status === 403) {
795+
// this.invalidToken = true;
796+
// }
797+
// });
798+
// }
799+
},
800+
watch: {
801+
$route() {
802+
if (this.$route.params.id !== undefined) {
803+
this.$store.dispatch('setActivityIndex', this.$route.params.id);
804+
}
805+
},
806+
visibilityConditions: {
807+
handler(newC) {
808+
if (!_.isEmpty(newC)) {
809+
this.setVisbility();
810+
}
811+
},
812+
deep: true,
813+
},
814+
},
815+
created() {
816+
const url = this.$route.query.url;
817+
if (url) {
818+
this.protocolUrl = url;
819+
}
820+
this.$store.dispatch('getBaseSchema', url).then(() => this.getPrefLabel());
821+
// this.$store.dispatch('getBaseSchema', url);
822+
}
820823
};
821824
</script>
822825

@@ -943,5 +946,3 @@ export default {
943946
944947
945948
</style>
946-
947-

src/components/InputSelector/InputSelector.vue

Lines changed: 39 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,67 +22,50 @@
2222
:init="init" v-on:valueChanged="sendData"/>
2323
</div>
2424

25-
<div v-else-if="inputType === 'audioCheck'">
26-
<AudioRecord
25+
<div v-else-if="inputType === 'audioCheck' || inputType === 'audioVideoCheck'">
26+
<MediaRecord
2727
:constraints="valueConstraints"
2828
:selected_language="selected_language"
29-
:init="init" v-on:valueChanged="sendData"/>
29+
:init="init"
30+
:audio="true"
31+
:visualizer="true"
32+
v-on:valueChanged="sendData"/>
3033
</div>
3134

32-
<!-- If type is audioRecord -->
33-
<div v-else-if="inputType === 'audioRecord'">
34-
<AudioRecord
35+
<div v-else-if="inputType === 'videoCheck'">
36+
<MediaRecord
3537
:constraints="valueConstraints"
3638
:selected_language="selected_language"
37-
:init="init" v-on:valueChanged="sendData"/>
39+
:init="init"
40+
:audio="getVideoAudioSetting('videoCheck')"
41+
:visualizer="false"
42+
v-on:valueChanged="sendData"/>
3843
</div>
3944

40-
<div v-else-if="inputType === 'audioPassageRecord'">
41-
<AudioRecord
45+
<div v-else-if="inputType.startsWith('audio')">
46+
<MediaRecord
4247
:constraints="valueConstraints"
4348
:selected_language="selected_language"
44-
:init="init" v-on:valueChanged="sendData"/>
45-
</div>
46-
47-
<!-- If type is audioImageRecord -->
48-
<div v-else-if="inputType === 'audioImageRecord'">
49-
<AudioRecord
50-
:constraints="valueConstraints"
49+
:init="init"
50+
:audio="true"
51+
:visualizer="true"
52+
:mode="inputType"
5153
:fieldData="fieldData"
52-
:selected_language="selected_language"
53-
:init="init" v-on:valueChanged="sendData"
54-
mode="audioImageRecord" />
55-
</div>
56-
57-
<!-- If type is audioRecordNumberTask -->
58-
<div v-else-if="inputType === 'audioRecordNumberTask'">
59-
<AudioRecord
60-
:constraints="valueConstraints"
61-
:selected_language="selected_language"
62-
:init="init" v-on:valueChanged="sendData"
63-
mode="audioRecordNumberTask" />
54+
v-on:valueChanged="sendData"/>
6455
</div>
6556

66-
<!-- If type is audioRecordAudioTask -->
67-
<div v-else-if="inputType === 'audioRecordAudioTask'">
68-
<AudioRecord
57+
<div v-else-if="inputType.startsWith('video')">
58+
<MediaRecord
6959
:constraints="valueConstraints"
7060
:selected_language="selected_language"
71-
:init="init" v-on:valueChanged="sendData"
61+
:init="init"
62+
:audio="getVideoAudioSetting(inputType)"
63+
:visualizer="false"
64+
:mode="inputType"
7265
:fieldData="fieldData"
73-
mode="audioRecordAudioTask" />
74-
</div>
75-
76-
<!-- If type is audioRecordNoStop -->
77-
<div v-else-if="inputType === 'audioRecordNoStop'">
78-
<AudioRecord
79-
:constraints="valueConstraints"
80-
:selected_language="selected_language"
81-
:init="init" v-on:valueChanged="sendData"
82-
mode="audioRecordNoStop" />
66+
v-on:valueChanged="sendData"/>
8367
</div>
8468

85-
8669
<!-- If type is text -->
8770
<div v-else-if="inputType === 'text'">
8871
<TextInput
@@ -153,7 +136,7 @@
153136
:constraints="valueConstraints"
154137
:selected_language="selected_language"
155138
:init="init" v-on:valueChanged="sendData"/>
156-
</div>
139+
</div>
157140

158141
<!-- If type is date -->
159142
<div v-else-if="inputType === 'date' || inputType === 'year'">
@@ -258,7 +241,7 @@
258241

259242
<script>
260243
import Radio from '../Inputs/WebRadio/';
261-
import AudioRecord from '../Inputs/WebAudioRecord/';
244+
import MediaRecord from '../Inputs/MediaRecord/';
262245
import TextInput from '../Inputs/WebTextInput/';
263246
import TextArea from '../Inputs/TextArea/';
264247
import IntegerInput from '../Inputs/WebIntegerInput/';
@@ -328,7 +311,7 @@ export default {
328311
StudySign,
329312
SaveData,
330313
Radio,
331-
AudioRecord,
314+
MediaRecord,
332315
TextInput,
333316
TextArea,
334317
EmailInput,
@@ -350,6 +333,15 @@ export default {
350333
};
351334
},
352335
methods: {
336+
getVideoAudioSetting(inputType) {
337+
// Check if there's a specific setting to disable audio for video recording
338+
// Look in valueConstraints for a disableAudio flag
339+
if (this.valueConstraints && this.valueConstraints['http://schema.repronim.org/disableAudio']) {
340+
return !this.valueConstraints['http://schema.repronim.org/disableAudio'][0]['@value'];
341+
}
342+
// Default: include audio for all video recordings
343+
return true;
344+
},
353345
skip() {
354346
this.$emit('skip');
355347
},
@@ -386,3 +378,4 @@ export default {
386378
}
387379
388380
</style>
381+

0 commit comments

Comments
 (0)