From 80a217e6460b4b60a66abecd84b7440f77a41ebc Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 10 Dec 2025 18:05:50 +0530 Subject: [PATCH] feat: configuration to disable pwa --- frontend/src/App.vue | 4 +- frontend/src/components/ContactUsEmail.vue | 2 +- frontend/src/components/Settings/Settings.vue | 7 ++++ .../src/components/Sidebar/AppSidebar.vue | 17 ++++---- frontend/src/components/VideoBlock.vue | 4 +- .../ProgrammingExerciseSubmission.vue | 6 +-- frontend/src/router.js | 6 +-- frontend/src/stores/settings.js | 41 ++----------------- frontend/src/utils/index.js | 2 +- lms/lms/api.py | 16 +++----- .../doctype/lms_settings/lms_settings.json | 9 +++- 11 files changed, 46 insertions(+), 68 deletions(-) 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",