diff --git a/frontend/src/components/Settings/Settings.vue b/frontend/src/components/Settings/Settings.vue
index 26fc3e8f..a4e219a3 100644
--- a/frontend/src/components/Settings/Settings.vue
+++ b/frontend/src/components/Settings/Settings.vue
@@ -130,6 +130,13 @@ const tabsStructure = computed(() => {
label: 'General',
icon: 'Wrench',
fields: [
+ {
+ label: 'Allow Guest Access',
+ name: 'allow_guest_access',
+ description:
+ 'If enabled, users can access the course and batch lists without logging in.',
+ type: 'checkbox',
+ },
{
label: 'Enable Learning Paths',
name: 'enable_learning_paths',
@@ -138,11 +145,11 @@ const tabsStructure = computed(() => {
type: 'checkbox',
},
{
- label: 'Allow Guest Access',
- name: 'allow_guest_access',
- description:
- 'If enabled, users can access the course and batch lists without logging in.',
+ label: 'Prevent Skipping Videos',
+ name: 'prevent_skipping_videos',
type: 'checkbox',
+ description:
+ 'If enabled, students cannot skip videos in a lesson.',
},
{
label: 'Send calendar invite for evaluations',
@@ -154,6 +161,14 @@ const tabsStructure = computed(() => {
{
type: 'Column Break',
},
+ {
+ label: 'Livecode URL',
+ name: 'livecode_url',
+ doctype: 'Livecode URL',
+ type: 'text',
+ description:
+ 'https://docs.frappe.io/learning/falcon-self-hosting-guide',
+ },
{
label: 'Batch Confirmation Email Template',
name: 'batch_confirmation_template',
@@ -166,14 +181,6 @@ const tabsStructure = computed(() => {
doctype: 'Email Template',
type: 'Link',
},
- {
- label: 'Livecode URL',
- name: 'livecode_url',
- doctype: 'Livecode URL',
- type: 'text',
- description:
- 'https://docs.frappe.io/learning/falcon-self-hosting-guide',
- },
{
label: 'Unsplash Access Key',
name: 'unsplash_access_key',
diff --git a/frontend/src/components/VideoBlock.vue b/frontend/src/components/VideoBlock.vue
index 42a22cd1..86ddfa87 100644
--- a/frontend/src/components/VideoBlock.vue
+++ b/frontend/src/components/VideoBlock.vue
@@ -74,6 +74,7 @@
v-model="currentTime"
@input="changeCurrentTime"
class="duration-slider h-1"
+ :disabled="preventSkippingVideos.data"
/>
@@ -155,6 +156,7 @@ import { ref, onMounted, computed, watch } from 'vue'
import { Pause, Maximize, Volume2, VolumeX } from 'lucide-vue-next'
import { Button, Dialog } from 'frappe-ui'
import { formatSeconds, formatTimestamp } from '@/utils'
+import { useSettings } from '@/stores/settings'
import Play from '@/components/Icons/Play.vue'
import QuizInVideo from '@/components/Modals/QuizInVideo.vue'
@@ -170,6 +172,7 @@ const showQuizLoader = ref(false)
const quizLoadTimer = ref(0)
const currentQuiz = ref(null)
const nextQuiz = ref({})
+const { preventSkippingVideos } = useSettings()
const props = defineProps({
file: {
diff --git a/frontend/src/pages/Lesson.vue b/frontend/src/pages/Lesson.vue
index 830cc712..afb36b4e 100644
--- a/frontend/src/pages/Lesson.vue
+++ b/frontend/src/pages/Lesson.vue
@@ -551,6 +551,7 @@ const getPlyrSource = async () => {
await nextTick()
if (plyrSources.value.length == 0) {
plyrSources.value = await enablePlyr()
+ console.log(plyrSources.value)
}
updateVideoWatchDuration()
}
diff --git a/frontend/src/stores/settings.js b/frontend/src/stores/settings.js
index 85ad5156..205354ba 100644
--- a/frontend/src/stores/settings.js
+++ b/frontend/src/stores/settings.js
@@ -9,21 +9,31 @@ export const useSettings = defineStore('settings', () => {
const activeTab = ref(null)
const learningPaths = createResource({
- url: 'lms.lms.api.is_learning_path_enabled',
+ url: 'lms.lms.api.get_lms_setting',
+ params: { field: 'enable_learning_paths' },
auto: true,
cache: ['learningPath'],
})
const allowGuestAccess = createResource({
- url: 'lms.lms.api.is_guest_allowed',
+ url: 'lms.lms.api.get_lms_setting',
+ params: { field: 'allow_guest_access' },
auto: true,
cache: ['allowGuestAccess'],
})
+ const preventSkippingVideos = createResource({
+ url: 'lms.lms.api.get_lms_setting',
+ params: { field: 'prevent_skipping_videos' },
+ auto: true,
+ cache: ['preventSkippingVideos'],
+ })
+
return {
isSettingsOpen,
activeTab,
learningPaths,
allowGuestAccess,
+ preventSkippingVideos,
}
})
diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js
index a9ce4fe2..79ec05ee 100644
--- a/frontend/src/utils/index.js
+++ b/frontend/src/utils/index.js
@@ -1,4 +1,3 @@
-import { watch } from 'vue'
import { call, toast } from 'frappe-ui'
import { useTimeAgo } from '@vueuse/core'
import { Quiz } from '@/utils/quiz'
@@ -557,17 +556,23 @@ const setupPlyrForVideo = (video, players) => {
video.setAttribute('data-plyr-embed-id', videoID)
}
+ let controls = [
+ 'play-large',
+ 'play',
+ 'progress',
+ 'current-time',
+ 'mute',
+ 'volume',
+ 'fullscreen',
+ ]
+
+ if (useSettings().preventSkippingVideos.data) {
+ controls.splice(controls.indexOf('progress'), 1)
+ }
+
const player = new Plyr(video, {
youtube: { noCookie: true },
- controls: [
- 'play-large',
- 'play',
- 'progress',
- 'current-time',
- 'mute',
- 'volume',
- 'fullscreen',
- ],
+ controls: controls,
})
players.push(player)
diff --git a/lms/lms/api.py b/lms/lms/api.py
index cbd9585d..d9d9de51 100644
--- a/lms/lms/api.py
+++ b/lms/lms/api.py
@@ -1304,13 +1304,8 @@ def get_notifications(filters):
@frappe.whitelist(allow_guest=True)
-def is_guest_allowed():
- return frappe.get_cached_value("LMS Settings", None, "allow_guest_access")
-
-
-@frappe.whitelist(allow_guest=True)
-def is_learning_path_enabled():
- return frappe.get_cached_value("LMS Settings", None, "enable_learning_paths")
+def get_lms_setting(field):
+ return frappe.get_cached_value("LMS Settings", None, field)
@frappe.whitelist()
diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json
index d1984e6e..3f3f3b4b 100644
--- a/lms/lms/doctype/lms_settings/lms_settings.json
+++ b/lms/lms/doctype/lms_settings/lms_settings.json
@@ -12,6 +12,8 @@
"column_break_zdel",
"allow_guest_access",
"enable_learning_paths",
+ "prevent_skipping_videos",
+ "column_break_bjis",
"unsplash_access_key",
"livecode_url",
"section_break_szgq",
@@ -72,7 +74,6 @@
"default": "https://livecode.dev.fossunited.org",
"fieldname": "livecode_url",
"fieldtype": "Data",
- "hidden": 1,
"label": "LiveCode URL"
},
{
@@ -405,13 +406,23 @@
"fieldname": "certified_members",
"fieldtype": "Check",
"label": "Certified Members"
+ },
+ {
+ "default": "0",
+ "fieldname": "prevent_skipping_videos",
+ "fieldtype": "Check",
+ "label": "Prevent Skipping Videos"
+ },
+ {
+ "fieldname": "column_break_bjis",
+ "fieldtype": "Column Break"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2025-05-30 19:02:51.381668",
+ "modified": "2025-07-01 17:01:58.466697",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Settings",