feat: programming exercises

This commit is contained in:
Jannat Patel
2025-06-18 11:06:44 +05:30
parent 5af3580987
commit 0edf78b7fd
4 changed files with 173 additions and 0 deletions

View File

@@ -77,6 +77,7 @@ declare module 'vue' {
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
PaymentSettings: typeof import('./src/components/Settings/PaymentSettings.vue')['default']
Play: typeof import('./src/components/Icons/Play.vue')['default']
ProgrammingExerciseModal: typeof import('./src/components/Modals/ProgrammingExerciseModal.vue')['default']
ProgressBar: typeof import('./src/components/ProgressBar.vue')['default']
Question: typeof import('./src/components/Modals/Question.vue')['default']
Quiz: typeof import('./src/components/Quiz.vue')['default']

View File

@@ -0,0 +1,53 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Add a programming exercise to your lesson'),
size: 'xl',
actions: [
{
label: __('Save'),
variant: 'solid',
onClick: () => {
saveExercise()
},
},
],
}"
>
<template #body-content>
<div class="text-base">
<Link
v-model="exercise"
doctype="LMS Exercise"
:label="__('Programming Exercise')"
/>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { Dialog } from 'frappe-ui'
import { onMounted, nextTick, ref } from 'vue'
import Link from '@/components/Controls/Link.vue'
const show = ref(false)
const exercise = ref(null)
const props = defineProps({
onSave: {
type: Function,
required: true,
},
})
onMounted(async () => {
await nextTick()
show.value = true
})
const saveExercise = () => {
props.onSave(exercise.value)
show.value = false
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
{{ exercise }}
<Button @click="runCode"> Run Code </Button>
</template>
<script setup lang="ts">
import { Button, createDocumentResource } from 'frappe-ui'
import { onMounted, ref } from 'vue'
const props = defineProps<{
exerciseID: string
}>()
onMounted(() => {
loadFalcon()
})
const exercise = createDocumentResource({
doctype: 'LMS Exercise',
name: props.exerciseID,
fields: ['name', 'title', 'description'],
auto: true,
})
const loadFalcon = () => {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = 'https://falcon.frappe.io/static/livecode.js'
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
const runCode = () => {
var session = new LiveCodeSession({
base_url: 'https://falcon.frappe.io',
runtime: 'python',
code: "print('hello, world!')",
onMessage: function (msg: any) {
console.log(msg)
},
})
}
</script>

View File

@@ -0,0 +1,75 @@
import { createApp, h } from 'vue'
import { Code } from 'lucide-vue-next'
import translationPlugin from '@/translation'
import ProgrammingExerciseModal from '@/components/Modals/ProgrammingExerciseModal.vue';
export class Program {
data: any;
api: any;
readOnly: boolean;
wrapper: HTMLDivElement;
constructor({ data, api, readOnly }: { data: any; api: any; readOnly: boolean }) {
this.data = data;
this.api = api;
this.readOnly = readOnly;
}
static get toolbox() {
const app = createApp({
render: () => h(Code, { size: 5, strokeWidth: 1.5 }),
})
const div = document.createElement('div')
app.mount(div)
return {
title: __('Programming Exercise'),
icon: div.innerHTML,
}
}
static get isReadOnlySupported() {
return true
}
render() {
this.wrapper = document.createElement('div')
if (Object.keys(this.data).length) {
this.renderExercise(this.data.exercise)
} else {
this.renderModal()
}
return this.wrapper
}
renderModal() {
if (this.readOnly) {
return
}
const app = createApp(ProgrammingExerciseModal, {
onSave: (exercise: string) => {
this.data.exercise = exercise
this.renderExercise(exercise)
},
})
app.use(translationPlugin)
app.mount(this.wrapper)
}
renderExercise(exercise: string) {
this.wrapper.innerHTML = `<div class='border rounded-md p-10 text-center bg-surface-menu-bar mb-2'>
<span class="font-medium">
Programming Exercise: ${exercise}
</span>
</div>`
return
}
save() {
return {
exercise: this.data.exercise,
}
}
}