feat: prevent skipping videos

This commit is contained in:
Jannat Patel
2025-07-01 17:27:43 +05:30
parent 2837ed16a7
commit 94cbbf169a
7 changed files with 65 additions and 33 deletions

View File

@@ -130,6 +130,13 @@ const tabsStructure = computed(() => {
label: 'General', label: 'General',
icon: 'Wrench', icon: 'Wrench',
fields: [ 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', label: 'Enable Learning Paths',
name: 'enable_learning_paths', name: 'enable_learning_paths',
@@ -138,11 +145,11 @@ const tabsStructure = computed(() => {
type: 'checkbox', type: 'checkbox',
}, },
{ {
label: 'Allow Guest Access', label: 'Prevent Skipping Videos',
name: 'allow_guest_access', name: 'prevent_skipping_videos',
description:
'If enabled, users can access the course and batch lists without logging in.',
type: 'checkbox', type: 'checkbox',
description:
'If enabled, students cannot skip videos in a lesson.',
}, },
{ {
label: 'Send calendar invite for evaluations', label: 'Send calendar invite for evaluations',
@@ -154,6 +161,14 @@ const tabsStructure = computed(() => {
{ {
type: 'Column Break', 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', label: 'Batch Confirmation Email Template',
name: 'batch_confirmation_template', name: 'batch_confirmation_template',
@@ -166,14 +181,6 @@ const tabsStructure = computed(() => {
doctype: 'Email Template', doctype: 'Email Template',
type: 'Link', 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', label: 'Unsplash Access Key',
name: 'unsplash_access_key', name: 'unsplash_access_key',

View File

@@ -74,6 +74,7 @@
v-model="currentTime" v-model="currentTime"
@input="changeCurrentTime" @input="changeCurrentTime"
class="duration-slider h-1" class="duration-slider h-1"
:disabled="preventSkippingVideos.data"
/> />
<!-- QUIZ MARKERS --> <!-- QUIZ MARKERS -->
<div class="absolute top-0 left-0 w-full h-full pointer-events-none"> <div class="absolute top-0 left-0 w-full h-full pointer-events-none">
@@ -155,6 +156,7 @@ import { ref, onMounted, computed, watch } from 'vue'
import { Pause, Maximize, Volume2, VolumeX } from 'lucide-vue-next' import { Pause, Maximize, Volume2, VolumeX } from 'lucide-vue-next'
import { Button, Dialog } from 'frappe-ui' import { Button, Dialog } from 'frappe-ui'
import { formatSeconds, formatTimestamp } from '@/utils' import { formatSeconds, formatTimestamp } from '@/utils'
import { useSettings } from '@/stores/settings'
import Play from '@/components/Icons/Play.vue' import Play from '@/components/Icons/Play.vue'
import QuizInVideo from '@/components/Modals/QuizInVideo.vue' import QuizInVideo from '@/components/Modals/QuizInVideo.vue'
@@ -170,6 +172,7 @@ const showQuizLoader = ref(false)
const quizLoadTimer = ref(0) const quizLoadTimer = ref(0)
const currentQuiz = ref(null) const currentQuiz = ref(null)
const nextQuiz = ref({}) const nextQuiz = ref({})
const { preventSkippingVideos } = useSettings()
const props = defineProps({ const props = defineProps({
file: { file: {

View File

@@ -551,6 +551,7 @@ const getPlyrSource = async () => {
await nextTick() await nextTick()
if (plyrSources.value.length == 0) { if (plyrSources.value.length == 0) {
plyrSources.value = await enablePlyr() plyrSources.value = await enablePlyr()
console.log(plyrSources.value)
} }
updateVideoWatchDuration() updateVideoWatchDuration()
} }

View File

@@ -9,21 +9,31 @@ export const useSettings = defineStore('settings', () => {
const activeTab = ref(null) const activeTab = ref(null)
const learningPaths = createResource({ 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, auto: true,
cache: ['learningPath'], cache: ['learningPath'],
}) })
const allowGuestAccess = createResource({ const allowGuestAccess = createResource({
url: 'lms.lms.api.is_guest_allowed', url: 'lms.lms.api.get_lms_setting',
params: { field: 'allow_guest_access' },
auto: true, auto: true,
cache: ['allowGuestAccess'], cache: ['allowGuestAccess'],
}) })
const preventSkippingVideos = createResource({
url: 'lms.lms.api.get_lms_setting',
params: { field: 'prevent_skipping_videos' },
auto: true,
cache: ['preventSkippingVideos'],
})
return { return {
isSettingsOpen, isSettingsOpen,
activeTab, activeTab,
learningPaths, learningPaths,
allowGuestAccess, allowGuestAccess,
preventSkippingVideos,
} }
}) })

View File

@@ -1,4 +1,3 @@
import { watch } from 'vue'
import { call, toast } from 'frappe-ui' import { call, toast } from 'frappe-ui'
import { useTimeAgo } from '@vueuse/core' import { useTimeAgo } from '@vueuse/core'
import { Quiz } from '@/utils/quiz' import { Quiz } from '@/utils/quiz'
@@ -557,9 +556,7 @@ const setupPlyrForVideo = (video, players) => {
video.setAttribute('data-plyr-embed-id', videoID) video.setAttribute('data-plyr-embed-id', videoID)
} }
const player = new Plyr(video, { let controls = [
youtube: { noCookie: true },
controls: [
'play-large', 'play-large',
'play', 'play',
'progress', 'progress',
@@ -567,7 +564,15 @@ const setupPlyrForVideo = (video, players) => {
'mute', 'mute',
'volume', 'volume',
'fullscreen', 'fullscreen',
], ]
if (useSettings().preventSkippingVideos.data) {
controls.splice(controls.indexOf('progress'), 1)
}
const player = new Plyr(video, {
youtube: { noCookie: true },
controls: controls,
}) })
players.push(player) players.push(player)

View File

@@ -1304,13 +1304,8 @@ def get_notifications(filters):
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def is_guest_allowed(): def get_lms_setting(field):
return frappe.get_cached_value("LMS Settings", None, "allow_guest_access") return frappe.get_cached_value("LMS Settings", None, field)
@frappe.whitelist(allow_guest=True)
def is_learning_path_enabled():
return frappe.get_cached_value("LMS Settings", None, "enable_learning_paths")
@frappe.whitelist() @frappe.whitelist()

View File

@@ -12,6 +12,8 @@
"column_break_zdel", "column_break_zdel",
"allow_guest_access", "allow_guest_access",
"enable_learning_paths", "enable_learning_paths",
"prevent_skipping_videos",
"column_break_bjis",
"unsplash_access_key", "unsplash_access_key",
"livecode_url", "livecode_url",
"section_break_szgq", "section_break_szgq",
@@ -72,7 +74,6 @@
"default": "https://livecode.dev.fossunited.org", "default": "https://livecode.dev.fossunited.org",
"fieldname": "livecode_url", "fieldname": "livecode_url",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1,
"label": "LiveCode URL" "label": "LiveCode URL"
}, },
{ {
@@ -405,13 +406,23 @@
"fieldname": "certified_members", "fieldname": "certified_members",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Certified Members" "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, "grid_page_length": 50,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2025-05-30 19:02:51.381668", "modified": "2025-07-01 17:01:58.466697",
"modified_by": "sayali@frappe.io", "modified_by": "sayali@frappe.io",
"module": "LMS", "module": "LMS",
"name": "LMS Settings", "name": "LMS Settings",