Merge pull request #1490 from frappe/develop

chore: merge 'develop' into 'main'
This commit is contained in:
Jannat Patel
2025-05-07 12:56:56 +05:30
committed by GitHub
30 changed files with 3607 additions and 3522 deletions

Submodule frappe-ui deleted from 175be05a92

View File

@@ -3,7 +3,7 @@
<template #target="{ togglePopover }">
<button
:class="[
'group w-full flex h-7 items-center justify-between rounded px-2 text-base text-ink-gray-8 hover:bg-surface-gray-2',
'group w-full flex h-7 items-center justify-between rounded px-2 text-base text-ink-gray-7 hover:bg-surface-gray-2',
]"
@click.prevent="togglePopover()"
>

View File

@@ -28,9 +28,7 @@
</template>
<template #body="{ isOpen }">
<div v-show="isOpen">
<div
class="mt-1 rounded-lg bg-surface-white py-1 text-base shadow-2xl"
>
<div class="mt-1 rounded-lg bg-surface-white py-1 text-base border-2">
<div class="relative px-1.5 pt-0.5">
<ComboboxInput
ref="search"
@@ -49,7 +47,7 @@
class="absolute right-1.5 inline-flex h-7 w-7 items-center justify-center"
@click="selectedValue = null"
>
<X class="h-4 w-4 stroke-1.5" />
<X class="h-4 w-4 stroke-1.5 text-ink-gray-7" />
</button>
</div>
<ComboboxOptions
@@ -89,7 +87,7 @@
name="item-label"
v-bind="{ active, selected, option }"
>
<div class="flex flex-col space-y-1">
<div class="flex flex-col space-y-1 text-ink-gray-8">
<div>
{{ option.label }}
</div>

View File

@@ -4,7 +4,7 @@
{{ label }}
<span class="text-ink-red-3" v-if="required">*</span>
</label>
<div class="grid grid-cols-3 gap-1">
<div class="grid grid-cols-3 gap-2">
<Button
ref="emails"
v-for="value in values"
@@ -12,7 +12,7 @@
:label="value"
theme="gray"
variant="subtle"
class="rounded-md"
class="rounded-md word-break-all"
@keydown.delete.capture.stop="removeLastValue"
>
<template #suffix>
@@ -42,7 +42,7 @@
<template #body="{ isOpen }">
<div v-show="isOpen">
<div
class="mt-1 rounded-lg bg-surface-white py-1 text-base shadow-2xl"
class="mt-1 rounded-lg bg-surface-white py-1 text-base border-2"
>
<ComboboxOptions
class="my-1 max-h-[12rem] overflow-y-auto px-1.5"
@@ -61,7 +61,7 @@
]"
>
<div class="flex flex-col gap-1 p-1">
<div class="text-base font-medium">
<div class="text-base font-medium text-ink-gray-8">
{{ option.description }}
</div>
<div class="text-sm text-ink-gray-5">

View File

@@ -2,57 +2,73 @@
<Dialog
v-model="show"
:options="{
title:
assignmentID === 'new'
? __('Create an Assignment')
: __('Edit Assignment'),
size: 'lg',
actions: [
{
label: __('Save'),
variant: 'solid',
onClick: (close) => saveAssignment(close),
},
],
}"
>
<template #body-content>
<div class="space-y-4 text-base max-h-[65vh] overflow-y-auto">
<FormControl
v-model="assignment.title"
:label="__('Title')"
:required="true"
/>
<FormControl
v-model="assignment.type"
type="select"
:options="assignmentOptions"
:label="__('Submission Type')"
:required="true"
/>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Question') }}
<span class="text-ink-red-3">*</span>
</div>
<TextEditor
:content="assignment.question"
@change="(val) => (assignment.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
<template #body>
<div class="p-5 text-base max-h-[75vh] overflow-y-auto">
<div class="text-lg text-ink-gray-9 font-semibold mb-5">
{{
assignmentID === 'new'
? __('Create an Assignment')
: __('Edit Assignment')
}}
</div>
<div class="space-y-4">
<FormControl
v-model="assignment.title"
:label="__('Title')"
:required="true"
/>
<FormControl
v-model="assignment.type"
type="select"
:options="assignmentOptions"
:label="__('Submission Type')"
:required="true"
/>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Question') }}
<span class="text-ink-red-3">*</span>
</div>
<TextEditor
:content="assignment.question"
@change="(val) => (assignment.question = val)"
:editable="true"
:fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
</div>
<div class="flex justify-end space-x-2 mt-5">
<router-link
:to="{
name: 'AssignmentSubmissionList',
query: {
assignmentID: assignmentID,
},
}"
>
<Button v-if="assignmentID !== 'new'" variant="subtle">
{{ __('Check Submissions') }}
</Button>
</router-link>
<Button variant="solid" @click="saveAssignment">
{{ __('Save') }}
</Button>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { Dialog, FormControl, TextEditor } from 'frappe-ui'
import { Button, Dialog, FormControl, TextEditor } from 'frappe-ui'
import { computed, reactive, watch } from 'vue'
import { showToast } from '@/utils'
const show = defineModel('show')
const show = defineModel()
const assignments = defineModel<Assignments>('assignments')
interface Assignment {
@@ -98,7 +114,7 @@ watch(
{ flush: 'post' }
)
const saveAssignment = (close) => {
const saveAssignment = () => {
if (props.assignmentID == 'new') {
assignments.value.insert.submit(
{
@@ -106,7 +122,7 @@ const saveAssignment = (close) => {
},
{
onSuccess() {
close()
show.value = false
showToast(
__('Success'),
__('Assignment created successfully'),
@@ -123,7 +139,7 @@ const saveAssignment = (close) => {
},
{
onSuccess() {
close()
show.value = false
showToast(
__('Success'),
__('Assignment updated successfully'),

View File

@@ -1,38 +1,27 @@
<template>
<Dialog v-model="show" :options="dialogOptions">
<template #body-content>
<div class="space-y-4">
<Dialog
v-model="show"
:options="{
size: '3xl',
}"
>
<template #body>
<div class="p-5 space-y-5">
<div class="text-lg font-semibold text-ink-gray-9 mb-5">
{{ __(props.title) }}
</div>
<div
v-if="!editMode"
class="flex items-center text-xs text-ink-gray-7 space-x-5"
>
<div class="flex items-center space-x-2">
<input
type="radio"
id="existing"
value="existing"
v-model="questionType"
class="w-3 h-3 cursor-pointer"
/>
<label for="existing" class="cursor-pointer">
{{ __('Add an existing question') }}
</label>
</div>
<div class="flex items-center space-x-2">
<input
type="radio"
id="new"
value="new"
v-model="questionType"
class="w-3 h-3 cursor-pointer"
/>
<label for="new" class="cursor-pointer">
{{ __('Create a new question') }}
</label>
</div>
<Switch
size="sm"
:label="__('Choose an existing question')"
v-model="chooseFromExisting"
class="!p-0"
/>
</div>
<div v-if="questionType == 'new' || editMode" class="space-y-2">
<div v-if="!chooseFromExisting || editMode" class="space-y-2">
<div>
<label class="block text-xs text-ink-gray-5 mb-1">
{{ __('Question') }}
@@ -45,20 +34,34 @@
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
<FormControl
v-model="question.marks"
:label="__('Marks')"
type="number"
/>
<FormControl
:label="__('Type')"
v-model="question.type"
type="select"
:options="['Choices', 'User Input', 'Open Ended']"
class="pb-2"
:required="true"
/>
<div v-if="question.type == 'Choices'" class="divide-y border-t">
<div class="grid grid-cols-2 gap-4">
<FormControl
v-model="question.marks"
:label="__('Marks')"
type="number"
/>
<FormControl
:label="__('Type')"
v-model="question.type"
type="select"
:options="['Choices', 'User Input', 'Open Ended']"
class="pb-2"
:required="true"
/>
</div>
<div
v-if="question.type == 'Choices'"
class="text-base font-semibold text-ink-gray-9 mb-5 mt-5"
>
{{ __('Options') }}
</div>
<div
v-else-if="question.type == 'User Input'"
class="text-base font-semibold text-ink-gray-9 mb-5 mt-5"
>
{{ __('Possibilities') }}
</div>
<div v-if="question.type == 'Choices'" class="grid grid-cols-2 gap-4">
<div v-for="n in 4" class="space-y-4 py-2">
<FormControl
:label="__('Option') + ' ' + n"
@@ -78,17 +81,18 @@
</div>
<div
v-else-if="question.type == 'User Input'"
v-for="n in 4"
class="space-y-2"
class="grid grid-cols-2 gap-4 py-2"
>
<FormControl
:label="__('Possibility') + ' ' + n"
v-model="question[`possibility_${n}`]"
:required="n == 1 ? true : false"
/>
<div v-for="n in 4">
<FormControl
:label="__('Possibility') + ' ' + n"
v-model="question[`possibility_${n}`]"
:required="n == 1 ? true : false"
/>
</div>
</div>
</div>
<div v-else-if="questionType == 'existing'" class="space-y-2">
<div v-else-if="chooseFromExisting" class="space-y-2">
<Link
v-model="existingQuestion.question"
:label="__('Select a question')"
@@ -100,12 +104,24 @@
type="number"
/>
</div>
<div class="flex items-center justify-end space-x-2 mt-5">
<Button variant="solid" @click="submitQuestion()">
{{ __('Submit') }}
</Button>
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog, FormControl, TextEditor, createResource } from 'frappe-ui'
import {
Dialog,
FormControl,
TextEditor,
createResource,
Switch,
Button,
} from 'frappe-ui'
import { computed, watch, reactive, ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { showToast } from '@/utils'
@@ -113,7 +129,7 @@ import { useOnboarding } from 'frappe-ui/frappe'
const show = defineModel()
const quiz = defineModel('quiz')
const questionType = ref(null)
const chooseFromExisting = ref(false)
const editMode = ref(false)
const user = inject('$user')
const { updateOnboardingStep } = useOnboarding('learning')
@@ -182,11 +198,12 @@ watch(show, () => {
editMode.value = false
if (props.questionDetail.question) questionData.fetch()
else {
;(question.question = ''), (question.marks = 0)
question.question = ''
question.marks = 1
question.type = 'Choices'
existingQuestion.question = ''
existingQuestion.marks = 0
questionType.value = null
existingQuestion.marks = 1
chooseFromExisting.value = false
populateFields()
}
@@ -221,32 +238,26 @@ const questionCreation = createResource({
},
})
const submitQuestion = (close) => {
if (props.questionDetail?.question) updateQuestion(close)
else addQuestion(close)
const submitQuestion = () => {
if (props.questionDetail?.question) updateQuestion()
else addQuestion()
}
const addQuestion = (close) => {
if (questionType.value == 'existing') {
addQuestionRow(
{
question: existingQuestion.question,
marks: existingQuestion.marks,
},
close
)
const addQuestion = () => {
if (chooseFromExisting.value) {
addQuestionRow({
question: existingQuestion.question,
marks: existingQuestion.marks,
})
} else {
questionCreation.submit(
{},
{
onSuccess(data) {
addQuestionRow(
{
question: data.name,
marks: question.marks,
},
close
)
addQuestionRow({
question: data.name,
marks: question.marks,
})
},
onError(err) {
showToast(__('Error'), __(err.messages?.[0] || err), 'x')
@@ -256,7 +267,7 @@ const addQuestion = (close) => {
}
}
const addQuestionRow = (question, close) => {
const addQuestionRow = (question) => {
questionRow.submit(
{
...question,
@@ -269,11 +280,11 @@ const addQuestionRow = (question, close) => {
show.value = false
showToast(__('Success'), __('Question added successfully'), 'check')
quiz.value.reload()
close()
show.value = false
},
onError(err) {
showToast(__('Error'), __(err.messages?.[0] || err), 'x')
close()
show.value = false
},
}
)
@@ -307,7 +318,7 @@ const marksUpdate = createResource({
},
})
const updateQuestion = (close) => {
const updateQuestion = () => {
questionUpdate.submit(
{},
{
@@ -323,7 +334,6 @@ const updateQuestion = (close) => {
'check'
)
quiz.value.reload()
close()
},
}
)
@@ -334,22 +344,6 @@ const updateQuestion = (close) => {
}
)
}
const dialogOptions = computed(() => {
return {
title: __(props.title),
size: 'xl',
actions: [
{
label: __('Submit'),
variant: 'solid',
onClick: (close) => {
submitQuestion(close)
},
},
],
}
})
</script>
<style>
input[type='radio']:checked {

View File

@@ -10,11 +10,11 @@
</header>
<div class="w-3/4 mx-auto py-5">
<div class="">
<div class="text-lg font-semibold mb-4">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Details') }}
</div>
<div class="space-y-10 mb-4">
<div class="grid grid-cols-2 gap-10">
<div class="space-y-4">
<FormControl
v-model="batch.title"
:label="__('Title')"
@@ -107,7 +107,7 @@
</div>
<div class="my-10">
<div class="text-lg font-semibold mb-4">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Date and Time') }}
</div>
<div class="grid grid-cols-3 gap-10">
@@ -157,7 +157,7 @@
</div>
<div class="mb-10">
<div class="text-lg font-semibold mb-4">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Settings') }}
</div>
<div class="grid grid-cols-3 gap-10">
@@ -210,7 +210,7 @@
</div>
<div class="">
<div class="text-lg font-semibold mb-4">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Payment') }}
</div>
<FormControl
@@ -234,7 +234,7 @@
</div>
<div class="my-10">
<div class="text-lg font-semibold mb-4">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Description') }}
</div>
<FormControl

View File

@@ -10,56 +10,32 @@
</span>
</div>
<div
class="mx-auto space-y-5 w-full h-fit bg-white px-4 py-8 sm:mt-6 sm:w-96 sm:rounded-lg sm:px-8 sm:shadow-xl"
class="mx-auto w-full h-fit bg-white py-8 sm:mt-6 sm:w-96 sm:rounded-lg sm:px-8 sm:shadow-xl"
>
<div>
<div class="font-medium text-center mb-8">
{{ __('Help us understand your needs') }}
</div>
<div class="mb-5">
<div class="text-sm text-gray-700 mb-2">
{{ __('1. What best describes your role?') }}
{{ __('What is your main use case for Frappe Learning?') }}
</div>
<FormControl
v-model="persona.role"
v-model="persona.useCase"
type="select"
:options="roleOptions"
:options="useCaseOptions"
/>
</div>
<div>
<div>
<div class="text-sm text-gray-700 mb-2">
{{ __('2. How many students are you planning to teach?') }}
</div>
<FormControl
v-model="persona.noOfStudents"
type="select"
:options="noOfStudentsOptions"
/>
</div>
</div>
<div>
<div>
<div class="text-sm text-gray-700 mb-2">
{{ __('3. What is your main use case for Frappe Learning?') }}
</div>
<FormControl
v-model="persona.useCase"
type="select"
:options="useCaseOptions"
/>
</div>
</div>
<div>
<div>
<div class="text-sm text-gray-700 mb-2">
{{ __('4. Are you currently using any Frappe products?') }}
</div>
<FormControl
v-model="persona.frappeProducts"
type="select"
:options="frappeProductsOptions"
/>
<div class="mb-5">
<div class="text-sm text-gray-700 mb-2">
{{ __('How many students are you planning to teach?') }}
</div>
<FormControl
v-model="persona.noOfStudents"
type="select"
:options="noOfStudentsOptions"
/>
</div>
<div class="flex w-full">
@@ -89,19 +65,15 @@ const router = useRouter()
const { brand } = sessionStore()
const persona = reactive({
role: null,
noOfStudents: null,
useCase: null,
frappeProducts: null,
})
const submitPersona = () => {
let responses = {
site: user.data?.sitename,
role: persona.role,
no_of_students: persona.noOfStudents,
use_case: persona.useCase,
frappe_products: persona.frappeProducts,
}
call('lms.lms.api.capture_user_persona', {
responses: JSON.stringify(responses),
@@ -125,24 +97,6 @@ const skipPersonaForm = () => {
})
}
const roleOptions = computed(() => {
const options = [
'Trainer / Instructor',
'Freelancer / Consultant',
'HR / L&D Professional',
'School / University Admin',
'Software Developer',
'Community Manager',
'Business Owner / Team Lead',
'Other',
]
return options.map((option) => ({
label: option,
value: option,
}))
})
const noOfStudentsOptions = computed(() => {
const options = [
'Less than 50',
@@ -173,22 +127,6 @@ const useCaseOptions = computed(() => {
}))
})
const frappeProductsOptions = computed(() => {
const options = [
'Frappe Framework',
'ERPNext / Frappe HR',
'Frappe CRM / Helpdesk',
'Custom Frappe App',
'Other',
'Not using any Frappe product',
]
return options.map((option) => ({
label: option,
value: option,
}))
})
usePageMeta(() => {
return {
title: 'Persona',

View File

@@ -13,7 +13,7 @@
<!-- Courses -->
<div>
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('Program Courses') }}
</div>
<Button
@@ -75,7 +75,7 @@
<!-- Members -->
<div>
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('Program Members') }}
</div>
<Button

View File

@@ -38,7 +38,7 @@
<div class="w-3/4 mx-auto py-5">
<!-- Details -->
<div class="mb-8">
<div class="font-semibold mb-4">
<div class="font-semibold text-ink-gray-9 mb-4">
{{ __('Details') }}
</div>
<FormControl
@@ -75,7 +75,7 @@
<!-- Settings -->
<div class="mb-8">
<div class="font-semibold mb-4">
<div class="font-semibold text-ink-gray-9 mb-4">
{{ __('Settings') }}
</div>
<div class="grid grid-cols-3 gap-5 my-4">
@@ -93,7 +93,7 @@
</div>
<div class="mb-8">
<div class="font-semibold mb-4">
<div class="font-semibold text-ink-gray-9 mb-4">
{{ __('Shuffle Settings') }}
</div>
<div class="grid grid-cols-3">
@@ -113,7 +113,7 @@
<!-- Questions -->
<div>
<div class="flex items-center justify-between mb-4">
<div class="font-semibold">
<div class="font-semibold text-ink-gray-9">
{{ __('Questions') }}
</div>
<Button v-if="!readOnlyMode" @click="openQuestionModal()">
@@ -445,11 +445,7 @@ const breadcrumbs = computed(() => {
},
},
]
/* if (quizDetails.data) {
crumbs.push({
label: quiz.title,
})
} */
crumbs.push({
label: props.quizID == 'new' ? __('New Quiz') : quizDetails.data?.title,
route: { name: 'QuizForm', params: { quizID: props.quizID } },

View File

@@ -312,7 +312,7 @@ def get_lesson_index(lesson_name):
def get_lesson_url(course, lesson_number):
if not lesson_number:
return
return f"/courses/{course}/learn/{lesson_number}"
return f"/lms/courses/{course}/learn/{lesson_number}"
def get_batch(course, batch_name):

View File

@@ -128,7 +128,7 @@
<a class="stretched-link" href="{{ get_lesson_url(course.name, lesson_index) }}{{ query_parameter }}"></a>
{% else %}
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
<a class="stretched-link" href="/lms/courses/{{ course.name }}"></a>
{% endif %}
{% endif %}
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff