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']
|
||||
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']
|
||||
|
||||
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