feat: programming exercises
This commit is contained in:
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -77,6 +77,7 @@ declare module 'vue' {
|
|||||||
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
|
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
|
||||||
PaymentSettings: typeof import('./src/components/Settings/PaymentSettings.vue')['default']
|
PaymentSettings: typeof import('./src/components/Settings/PaymentSettings.vue')['default']
|
||||||
Play: typeof import('./src/components/Icons/Play.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']
|
ProgressBar: typeof import('./src/components/ProgressBar.vue')['default']
|
||||||
Question: typeof import('./src/components/Modals/Question.vue')['default']
|
Question: typeof import('./src/components/Modals/Question.vue')['default']
|
||||||
Quiz: typeof import('./src/components/Quiz.vue')['default']
|
Quiz: typeof import('./src/components/Quiz.vue')['default']
|
||||||
|
|||||||
53
frontend/src/components/Modals/ProgrammingExerciseModal.vue
Normal file
53
frontend/src/components/Modals/ProgrammingExerciseModal.vue
Normal 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>
|
||||||
44
frontend/src/pages/ProgrammingExercise.vue
Normal file
44
frontend/src/pages/ProgrammingExercise.vue
Normal 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>
|
||||||
75
frontend/src/utils/program.ts
Normal file
75
frontend/src/utils/program.ts
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user