diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index de252f78..76464f03 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -3,7 +3,7 @@
-
+
@@ -13,6 +13,7 @@ import { Dialogs } from '@/utils/dialogs'
import { computed, onUnmounted, ref, watch } from 'vue'
import { useScreenSize } from './utils/composables'
import { usersStore } from '@/stores/user'
+import { useSettings } from '@/stores/settings'
import { useRouter } from 'vue-router'
import { posthogSettings } from '@/telemetry'
import DesktopLayout from './components/DesktopLayout.vue'
@@ -24,6 +25,7 @@ const { isMobile } = useScreenSize()
const router = useRouter()
const noSidebar = ref(false)
const { userResource } = usersStore()
+const { settings } = useSettings()
router.beforeEach((to, from, next) => {
if (to.query.fromLesson || to.path === '/persona') {
diff --git a/frontend/src/components/ContactUsEmail.vue b/frontend/src/components/ContactUsEmail.vue
index 04079d06..422ba847 100644
--- a/frontend/src/components/ContactUsEmail.vue
+++ b/frontend/src/components/ContactUsEmail.vue
@@ -48,7 +48,7 @@ const settingsStore = useSettings()
const sendMail = (close: Function) => {
call('frappe.core.doctype.communication.email.make', {
- recipients: settingsStore.contactUsEmail?.data,
+ recipients: settingsStore.settings?.data?.contact_us_email,
subject: subject.value,
content: message.value,
send_email: true,
diff --git a/frontend/src/components/Settings/Settings.vue b/frontend/src/components/Settings/Settings.vue
index 91a7fc2d..dc2bef49 100644
--- a/frontend/src/components/Settings/Settings.vue
+++ b/frontend/src/components/Settings/Settings.vue
@@ -120,6 +120,13 @@ const tabsStructure = computed(() => {
description:
'If enabled, users will no able to move forward in a video',
},
+ {
+ label: 'Disable PWA',
+ name: 'disable_pwa',
+ type: 'checkbox',
+ description:
+ 'If checked, users will not be able to install the application as a Progressive Web App.',
+ },
{
label: 'Send calendar invite for evaluations',
name: 'send_calendar_invite_for_evaluations',
diff --git a/frontend/src/components/Sidebar/AppSidebar.vue b/frontend/src/components/Sidebar/AppSidebar.vue
index 4ae257e2..7e9387e4 100644
--- a/frontend/src/components/Sidebar/AppSidebar.vue
+++ b/frontend/src/components/Sidebar/AppSidebar.vue
@@ -239,8 +239,7 @@ const showPageModal = ref(false)
const isModerator = ref(false)
const isInstructor = ref(false)
const pageToEdit = ref(null)
-const settingsStore = useSettings()
-const { sidebarSettings } = settingsStore
+const { settings, sidebarSettings, activeTab, isSettingsOpen } = useSettings()
const showOnboarding = ref(false)
const showIntermediateModal = ref(false)
const currentStep = ref({})
@@ -393,7 +392,7 @@ const addPrograms = async () => {
}
const addContactUsDetails = () => {
- if (!settingsStore.contactUsEmail?.data && !settingsStore.contactUsURL?.data)
+ if (!settings?.data?.contact_us_email && !settings?.data?.contact_us_url)
return
const contactUsLinkExists = sidebarLinks.value.some(
@@ -403,10 +402,10 @@ const addContactUsDetails = () => {
sidebarLinks.value.push({
label: 'Contact Us',
- icon: settingsStore.contactUsURL?.data ? 'Headset' : 'Mail',
- to: settingsStore.contactUsURL?.data
- ? settingsStore.contactUsURL.data
- : settingsStore.contactUsEmail?.data,
+ icon: settings.data?.contact_us_url ? 'Headset' : 'Mail',
+ to: settings.data?.contact_us_url
+ ? settings.data?.contact_us_url
+ : settings.data?.contact_us_email,
})
}
@@ -540,8 +539,8 @@ const steps = reactive([
completed: false,
onClick: () => {
minimize.value = true
- settingsStore.activeTab = 'Members'
- settingsStore.isSettingsOpen = true
+ activeTab.value = 'Members'
+ isSettingsOpen.value = true
},
},
{
diff --git a/frontend/src/components/VideoBlock.vue b/frontend/src/components/VideoBlock.vue
index 24410499..4c783957 100644
--- a/frontend/src/components/VideoBlock.vue
+++ b/frontend/src/components/VideoBlock.vue
@@ -171,7 +171,7 @@ const showQuizLoader = ref(false)
const quizLoadTimer = ref(0)
const currentQuiz = ref(null)
const nextQuiz = ref({})
-const { preventSkippingVideos } = useSettings()
+const { settings } = useSettings()
const props = defineProps({
file: {
@@ -299,7 +299,7 @@ const toggleMute = () => {
const changeCurrentTime = () => {
if (
- preventSkippingVideos.data &&
+ settings.data?.prevent_skipping_videos &&
currentTime.value > videoRef.value.currentTime
)
return
diff --git a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue
index 2b75d9c3..d11adcea 100644
--- a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue
+++ b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue
@@ -168,7 +168,7 @@ const testCaseSection = ref(null)
const testCases = ref([])
const boilerplate = ref('')
const { brand } = sessionStore()
-const { livecodeURL } = useSettings()
+const { settings } = useSettings()
const router = useRouter()
const fromLesson = ref(false)
const falconURL = ref('https://falcon.frappe.io/')
@@ -291,8 +291,8 @@ watch(
)
const loadFalcon = () => {
- if (livecodeURL.data) {
- falconURL.value = livecodeURL.data
+ if (settings.data) {
+ falconURL.value = settings.data.livecode_url
}
return new Promise((resolve, reject) => {
const script = document.createElement('script')
diff --git a/frontend/src/router.js b/frontend/src/router.js
index 20546979..08f86e74 100644
--- a/frontend/src/router.js
+++ b/frontend/src/router.js
@@ -270,7 +270,7 @@ let router = createRouter({
router.beforeEach(async (to, from, next) => {
const { userResource } = usersStore()
let { isLoggedIn } = sessionStore()
- const { allowGuestAccess } = useSettings()
+ const { settings } = useSettings()
try {
if (isLoggedIn) {
@@ -283,8 +283,8 @@ router.beforeEach(async (to, from, next) => {
if (!isLoggedIn) {
if (to.name == 'Home') router.push({ name: 'Courses' })
- await allowGuestAccess.promise
- if (!allowGuestAccess.data) {
+ await settings.promise
+ if (!settings.data.allow_guest_access) {
window.location.href = '/login'
return
}
diff --git a/frontend/src/stores/settings.js b/frontend/src/stores/settings.js
index 539a292f..605ed1b1 100644
--- a/frontend/src/stores/settings.js
+++ b/frontend/src/stores/settings.js
@@ -7,32 +7,10 @@ export const useSettings = defineStore('settings', () => {
const isSettingsOpen = ref(false)
const activeTab = ref(null)
- const allowGuestAccess = createResource({
- url: 'lms.lms.api.get_lms_setting',
- params: { field: 'allow_guest_access' },
+ const settings = createResource({
+ url: 'lms.lms.api.get_lms_settings',
auto: true,
- cache: ['allowGuestAccess'],
- })
-
- const preventSkippingVideos = createResource({
- url: 'lms.lms.api.get_lms_setting',
- params: { field: 'prevent_skipping_videos' },
- auto: true,
- cache: ['preventSkippingVideos'],
- })
-
- const contactUsEmail = createResource({
- url: 'lms.lms.api.get_lms_setting',
- params: { field: 'contact_us_email' },
- auto: true,
- cache: ['contactUsEmail'],
- })
-
- const contactUsURL = createResource({
- url: 'lms.lms.api.get_lms_setting',
- params: { field: 'contact_us_url' },
- auto: true,
- cache: ['contactUsURL'],
+ cache: 'LMS Settings',
})
const sidebarSettings = createResource({
@@ -41,21 +19,10 @@ export const useSettings = defineStore('settings', () => {
auto: false,
})
- const livecodeURL = createResource({
- url: 'lms.lms.api.get_lms_setting',
- params: { field: 'livecode_url' },
- auto: true,
- cache: ['livecodeURL'],
- })
-
return {
isSettingsOpen,
activeTab,
- allowGuestAccess,
- preventSkippingVideos,
- contactUsEmail,
- contactUsURL,
+ settings,
sidebarSettings,
- livecodeURL,
}
})
diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js
index 334b3405..9a567af7 100644
--- a/frontend/src/utils/index.js
+++ b/frontend/src/utils/index.js
@@ -612,7 +612,7 @@ const setupPlyrForVideo = (video, players) => {
const current_time = player.currentTime
const newTime = getTargetTime(player, e)
if (
- useSettings().preventSkippingVideos.data &&
+ useSettings().settings.data?.prevent_skipping_videos &&
parseFloat(newTime) > current_time
) {
e.preventDefault()
diff --git a/lms/lms/api.py b/lms/lms/api.py
index 66fb97c8..b7a405c4 100644
--- a/lms/lms/api.py
+++ b/lms/lms/api.py
@@ -1304,25 +1304,21 @@ def get_notifications(filters):
@frappe.whitelist(allow_guest=True)
-def get_lms_setting(field=None):
- if not field:
- frappe.throw(_("Field name is required"))
- frappe.log_error("Field name is missing when accessing LMS Settings {0} {1} {2}").format(
- frappe.local.request_ip, frappe.get_request_header("Referer"), frappe.get_request_header("Origin")
- )
-
+def get_lms_settings():
allowed_fields = [
"allow_guest_access",
"prevent_skipping_videos",
"contact_us_email",
"contact_us_url",
"livecode_url",
+ "disable_pwa",
]
- if field not in allowed_fields:
- frappe.throw(_("You are not allowed to access this field"))
+ settings = frappe._dict()
+ for field in allowed_fields:
+ settings[field] = frappe.get_cached_value("LMS Settings", None, field)
- return frappe.get_cached_value("LMS Settings", None, field)
+ return settings
@frappe.whitelist()
diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json
index fa035c00..af046650 100644
--- a/lms/lms/doctype/lms_settings/lms_settings.json
+++ b/lms/lms/doctype/lms_settings/lms_settings.json
@@ -12,6 +12,7 @@
"column_break_zdel",
"allow_guest_access",
"prevent_skipping_videos",
+ "disable_pwa",
"column_break_bjis",
"unsplash_access_key",
"livecode_url",
@@ -438,13 +439,19 @@
"fieldname": "certifications",
"fieldtype": "Check",
"label": "Certifications"
+ },
+ {
+ "default": "0",
+ "fieldname": "disable_pwa",
+ "fieldtype": "Check",
+ "label": "Disable PWA"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2025-12-02 12:21:15.832799",
+ "modified": "2025-12-10 17:36:15.689695",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Settings",