feat: prevent skipping videos
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,17 +556,23 @@ const setupPlyrForVideo = (video, players) => {
|
|||||||
video.setAttribute('data-plyr-embed-id', videoID)
|
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, {
|
const player = new Plyr(video, {
|
||||||
youtube: { noCookie: true },
|
youtube: { noCookie: true },
|
||||||
controls: [
|
controls: controls,
|
||||||
'play-large',
|
|
||||||
'play',
|
|
||||||
'progress',
|
|
||||||
'current-time',
|
|
||||||
'mute',
|
|
||||||
'volume',
|
|
||||||
'fullscreen',
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
players.push(player)
|
players.push(player)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user