Improved Client implementation
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 824b9b8..66ac5fe 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -55,7 +55,7 @@
"stock/dashboard/item_dashboard_list.html",
"stock/dashboard/item_dashboard.js"
],
- "js/web-academy.min.js": [
- "public/js/education/web-academy.js"
+ "js/academy.min.js": [
+ "public/js/education/academy/academy.js"
]
}
diff --git a/erpnext/public/js/education/web-academy/AcademyRoot.vue b/erpnext/public/js/education/academy/AcademyRoot.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/AcademyRoot.vue
rename to erpnext/public/js/education/academy/AcademyRoot.vue
diff --git a/erpnext/public/js/education/web-academy.js b/erpnext/public/js/education/academy/academy.js
similarity index 79%
rename from erpnext/public/js/education/web-academy.js
rename to erpnext/public/js/education/academy/academy.js
index 8088a50..08cfc37 100644
--- a/erpnext/public/js/education/web-academy.js
+++ b/erpnext/public/js/education/academy/academy.js
@@ -1,21 +1,20 @@
import Vue from 'vue/dist/vue.js';
import VueRouter from 'vue-router/dist/vue-router.js'
-import AcademyRoot from "./web-academy/AcademyRoot.vue";
-import AcademyHome from "./web-academy/pages/AcademyHome.vue";
-import AcademyProgramPage from "./web-academy/pages/AcademyProgramPage.vue";
-import AcademyCoursePage from "./web-academy/pages/AcademyCoursePage.vue";
+import AcademyRoot from "./AcademyRoot.vue";
+import routes from './routes';
+import './call';
Vue.use(VueRouter)
-const routes = [
- {name: 'home', path: '', component: AcademyHome},
- {name: 'program', path: '/Program/:code', component: AcademyProgramPage, props: true},
- {name: 'content', path: '/Program/:code/:course/:type/:content', component: AcademyCoursePage, props: true},
-];
-var store = {
+
+frappe.provide('academy')
+
+frappe.utils.make_event_emitter(academy);
+
+academy.store = {
debug: true,
isLogin: false,
completedCourses: new Set(),
@@ -104,19 +103,13 @@
this.updateEnrolledPrograms()
this.updateEnrolledCourses()
this.checkLogin()
-
},
}
-const router = new VueRouter({
- routes: routes,
-});
-
frappe.ready(() => {
window.v = new Vue({
el: "#academy",
- router: router,
- data: store,
+ router: new VueRouter({ routes }),
template: "<academy-root/>",
components: { AcademyRoot },
created: function() {
@@ -125,4 +118,20 @@
}
}
});
+ academy.store = new Vue({
+ data: store,
+ methods: {
+ checkLogin (){
+ if(frappe.session.user === "Guest"){
+ if (this.debug) console.log('No Session')
+ this.isLogin = false
+ }
+ else {
+ if (this.debug) console.log('Current User: ', frappe.session.user)
+ this.isLogin = true
+ }
+ return this.isLogin
+ }
+ }
+ });
})
\ No newline at end of file
diff --git a/erpnext/public/js/education/academy/call.js b/erpnext/public/js/education/academy/call.js
new file mode 100644
index 0000000..108a6e1
--- /dev/null
+++ b/erpnext/public/js/education/academy/call.js
@@ -0,0 +1,13 @@
+frappe.provide('academy');
+
+academy.call = (method, args) => {
+ const method_path = 'erpnext.www.academy.' + method;
+ return new Promise((resolve, reject) => {
+ return frappe.call({
+ method: method_path,
+ args,
+ })
+ .then(r => resolve(r.message))
+ .fail(reject)
+ });
+}
\ No newline at end of file
diff --git a/erpnext/public/js/education/academy/components/AcademyCourseCard.vue b/erpnext/public/js/education/academy/components/AcademyCourseCard.vue
new file mode 100644
index 0000000..eda382f
--- /dev/null
+++ b/erpnext/public/js/education/academy/components/AcademyCourseCard.vue
@@ -0,0 +1,94 @@
+<template>
+<div class="card mt-3" data-list="getting-started">
+ <div class='card-body'>
+ <div class="row">
+ <div class="course-details col-xs-8 col-sm-9 col-md-10">
+ <h5 class="card-title">{{ course.course_name }}</h5>
+ <span class="course-list text-muted" id="getting-started">
+ Course Content
+ <ul class="mb-0 mt-1">
+ <li v-for="content in course.course_content" :key="content.name">{{ content.content }}</li>
+ </ul>
+ </span>
+ </div>
+ <div v-if="$root.$data.isLogin" class='course-buttons text-center col-xs-4 col-sm-3 col-md-2'>
+ <!-- <AcademyCourseCardButton
+ v-if="this.$root.$data.checkProgramEnrollment(this.$route.params.code)"
+ :course="course.name"
+ :nextContent="nextContent"
+ :nextContentType="nextContentType"
+ /> -->
+
+ <a-button
+ v-if="showCompleted"
+ type="success"
+ size="sm"
+ :route="firstContentRoute"
+ >
+ Completed
+ </a-button>
+ <a-button
+ v-if="showStart"
+ type="primary"
+ size="sm"
+ :route="firstContentRoute"
+ >
+ Start
+ </a-button>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
+
+<script>
+import AButton from './Button';
+import AcademyCourseCardButton from './AcademyCourseCardButton.vue'
+
+export default {
+ props: ['course'],
+ name: "AcademyCourseCard",
+ data() {
+ return {
+ nextContent: '',
+ nextContentType: ''
+ }
+ },
+ mounted() {
+ if(this.$root.$data.checkLogin()) {
+ frappe.call({
+ method: "erpnext.www.academy.get_starting_content",
+ args: {
+ course_name: this.course.name
+ }
+ }).then(r => {
+ this.nextContent = r.message.content,
+ this.nextContentType = r.message.content_type
+ });
+ }
+ },
+ components: {
+ AcademyCourseCardButton,
+ AButton
+ },
+ computed: {
+ showStart() {
+ return academy.loggedIn && !this.course.completed;
+ },
+ showCompleted() {
+ return academy.loggedIn && this.course.completed;
+ },
+ firstContentRoute() {
+ return `${course.name}/${course.content_type}/${data.content}`
+ }
+ }
+};
+</script>
+
+<style scoped>
+ @media only screen and (max-width: 576px) {
+ .course-buttons {
+ margin-top: 1em;
+ }
+}
+</style>
\ No newline at end of file
diff --git a/erpnext/public/js/education/web-academy/components/AcademyCourseCardButton.vue b/erpnext/public/js/education/academy/components/AcademyCourseCardButton.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/AcademyCourseCardButton.vue
rename to erpnext/public/js/education/academy/components/AcademyCourseCardButton.vue
diff --git a/erpnext/public/js/education/web-academy/components/AcademyList.vue b/erpnext/public/js/education/academy/components/AcademyList.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/AcademyList.vue
rename to erpnext/public/js/education/academy/components/AcademyList.vue
diff --git a/erpnext/public/js/education/web-academy/components/AcademyProgramCard.vue b/erpnext/public/js/education/academy/components/AcademyProgramCard.vue
similarity index 92%
rename from erpnext/public/js/education/web-academy/components/AcademyProgramCard.vue
rename to erpnext/public/js/education/academy/components/AcademyProgramCard.vue
index 605dec2..ef3ac08 100644
--- a/erpnext/public/js/education/web-academy/components/AcademyProgramCard.vue
+++ b/erpnext/public/js/education/academy/components/AcademyProgramCard.vue
@@ -38,14 +38,14 @@
methods: {
primaryAction(){
if(this.$root.$data.isLogin){
- if(this.$root.$data.checkProgramEnrollment(program_code)){
+ if(this.$root.$data.checkProgramEnrollment(this.program_code)){
this.$router.push('/Program/' + program.name)
}
else {
this.enroll()
}
}
- }
+ },
enroll() {
frappe.call({
method: "erpnext.www.academy.enroll_in_program",
@@ -57,11 +57,11 @@
this.$root.$data.enrolledPrograms.add(this.program_code)
this.$root.$data.updateEnrolledPrograms()
}
- }
+ },
computed: {
buttonName() {
if(this.$root.$data.isLogin){
- if(this.$root.$data.checkProgramEnrollment(program_code)){
+ if(this.$root.$data.checkProgramEnrollment(this.program_code)){
return "Start Course"
}
else {
diff --git a/erpnext/public/js/education/web-academy/components/AcademyTopSection.vue b/erpnext/public/js/education/academy/components/AcademyTopSection.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/AcademyTopSection.vue
rename to erpnext/public/js/education/academy/components/AcademyTopSection.vue
diff --git a/erpnext/public/js/education/web-academy/components/AcademyTopSectionButton.vue b/erpnext/public/js/education/academy/components/AcademyTopSectionButton.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/AcademyTopSectionButton.vue
rename to erpnext/public/js/education/academy/components/AcademyTopSectionButton.vue
diff --git a/erpnext/public/js/education/academy/components/Button.vue b/erpnext/public/js/education/academy/components/Button.vue
new file mode 100644
index 0000000..4d8df4b
--- /dev/null
+++ b/erpnext/public/js/education/academy/components/Button.vue
@@ -0,0 +1,25 @@
+<template>
+ <button :class="classList" v-on="$listeners" v-bind="$attrs" @click="goToRoute">
+ <slot></slot>
+ </button>
+</template>
+<script>
+export default {
+ name: 'AButton',
+ props: ['type', 'size', 'route'],
+ computed: {
+ classList() {
+ return [
+ 'btn',
+ 'btn-' + this.type,
+ 'btn-' + this.size
+ ]
+ }
+ },
+ methods: {
+ goToRoute() {
+ this.$router.push(this.route);
+ }
+ }
+}
+</script>
diff --git a/erpnext/public/js/education/web-academy/components/ContentArticle.vue b/erpnext/public/js/education/academy/components/ContentArticle.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/ContentArticle.vue
rename to erpnext/public/js/education/academy/components/ContentArticle.vue
diff --git a/erpnext/public/js/education/web-academy/components/ContentNavigation.vue b/erpnext/public/js/education/academy/components/ContentNavigation.vue
similarity index 92%
rename from erpnext/public/js/education/web-academy/components/ContentNavigation.vue
rename to erpnext/public/js/education/academy/components/ContentNavigation.vue
index 28f5a70..7fa20a5 100644
--- a/erpnext/public/js/education/web-academy/components/ContentNavigation.vue
+++ b/erpnext/public/js/education/academy/components/ContentNavigation.vue
@@ -41,9 +41,12 @@
enrollment: this.$root.$data.enrolledCourses[this.$route.params.course]
}
})
- this.$root.$data.addCompletedCourses(this.$route.params.course)
+ // this.$root.$data.addCompletedCourses(this.$route.params.course)
this.$root.$data.updateCompletedCourses()
this.$router.push({ name: 'program', params: { code: this.$route.params.code}})
+
+ //
+ academy.trigger('course-completed', course_name);
}
}
};
diff --git a/erpnext/public/js/education/web-academy/components/ContentQuiz.vue b/erpnext/public/js/education/academy/components/ContentQuiz.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/ContentQuiz.vue
rename to erpnext/public/js/education/academy/components/ContentQuiz.vue
diff --git a/erpnext/public/js/education/web-academy/components/ContentTitle.vue b/erpnext/public/js/education/academy/components/ContentTitle.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/ContentTitle.vue
rename to erpnext/public/js/education/academy/components/ContentTitle.vue
diff --git a/erpnext/public/js/education/web-academy/components/ContentVideo.vue b/erpnext/public/js/education/academy/components/ContentVideo.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/ContentVideo.vue
rename to erpnext/public/js/education/academy/components/ContentVideo.vue
diff --git a/erpnext/public/js/education/web-academy/components/Quiz/QuizSingleChoice.vue b/erpnext/public/js/education/academy/components/Quiz/QuizSingleChoice.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/components/Quiz/QuizSingleChoice.vue
rename to erpnext/public/js/education/academy/components/Quiz/QuizSingleChoice.vue
diff --git a/erpnext/public/js/education/web-academy/pages/AcademyCoursePage.vue b/erpnext/public/js/education/academy/pages/AcademyCoursePage.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/pages/AcademyCoursePage.vue
rename to erpnext/public/js/education/academy/pages/AcademyCoursePage.vue
diff --git a/erpnext/public/js/education/web-academy/pages/AcademyHome.vue b/erpnext/public/js/education/academy/pages/AcademyHome.vue
similarity index 100%
rename from erpnext/public/js/education/web-academy/pages/AcademyHome.vue
rename to erpnext/public/js/education/academy/pages/AcademyHome.vue
diff --git a/erpnext/public/js/education/academy/pages/AcademyProgramPage.vue b/erpnext/public/js/education/academy/pages/AcademyProgramPage.vue
new file mode 100644
index 0000000..7bea1ed
--- /dev/null
+++ b/erpnext/public/js/education/academy/pages/AcademyProgramPage.vue
@@ -0,0 +1,73 @@
+<template>
+<div>
+ <AcademyTopSection v-bind:title="program.program_name" v-bind:description="program.description">
+ <!-- <AcademyTopSectionButton/> -->
+ <a-button @click="startCourse">Start Course</a-button>
+ <a-button @click="continueCourse">Continue Course</a-button>
+ </AcademyTopSection>
+ <AcademyList :title="'Courses'" :description="''">
+ <AcademyCourseCard v-for="course in course_list" :course="course" :key="course.name"/>
+ </AcademyList>
+</div>
+</template>
+<script>
+import Button from '../components/Button.vue';
+import AcademyTopSection from "../components/AcademyTopSection.vue"
+import AcademyList from "../components/AcademyList.vue"
+import AcademyCourseCard from "../components/AcademyCourseCard.vue"
+import AcademyTopSectionButton from "../components/AcademyTopSectionButton.vue";
+
+
+export default {
+ props: ['program_name'],
+ name: "AcademyProgramPage",
+ components: {
+ AButton: Button,
+ AcademyTopSection,
+ AcademyList,
+ AcademyCourseCard,
+ AcademyTopSectionButton
+ },
+ data() {
+ return {
+ program: {},
+ course_list: []
+ }
+ },
+ beforeMount() {
+ if(this.$root.$data.isLogin) this.$root.$data.updateCompletedCourses()
+ },
+ mounted() {
+ this.getProgramDetails().then(data => this.program = data);
+ this.getCourses().then(data => this.course_list = data);
+
+ academy.on(`course-completed`, (course_name) => {
+ const course = this.course_list.findIndex(c => c.name === course_name);
+ this.course_list[course].completed = true;
+ });
+ },
+ methods: {
+ startCourse() {
+ this.getContentForNextCourse()
+ .then((data) =>
+ this.$router.push(`/Program/${this.program_name}/${data.course}/${data.content_type}/${data.content}`)
+ )
+ },
+ getContentForNextCourse() {
+ return academy.call('get_continue_data', {
+ program_name: this.program_name
+ });
+ },
+ getProgramDetails() {
+ return academy.call('get_program_details', {
+ program_name: this.program_name
+ });
+ },
+ getCourses() {
+ return academy.call('get_courses', {
+ program_name: this.program_name
+ })
+ }
+ }
+};
+</script>
\ No newline at end of file
diff --git a/erpnext/public/js/education/academy/routes.js b/erpnext/public/js/education/academy/routes.js
new file mode 100644
index 0000000..51c280d
--- /dev/null
+++ b/erpnext/public/js/education/academy/routes.js
@@ -0,0 +1,11 @@
+import AcademyHome from "./academy/pages/AcademyHome.vue";
+import AcademyProgramPage from "./academy/pages/AcademyProgramPage.vue";
+import AcademyCoursePage from "./academy/pages/AcademyCoursePage.vue";
+
+const routes = [
+ {name: 'home', path: '', component: AcademyHome},
+ {name: 'program', path: '/Program/:program_name', component: AcademyProgramPage, props: true},
+ {name: 'content', path: '/Program/:code/:course/:type/:content', component: AcademyCoursePage, props: true},
+];
+
+export default routes;
\ No newline at end of file
diff --git a/erpnext/public/js/education/web-academy/components/AcademyCourseCard.vue b/erpnext/public/js/education/web-academy/components/AcademyCourseCard.vue
deleted file mode 100644
index 9c47473..0000000
--- a/erpnext/public/js/education/web-academy/components/AcademyCourseCard.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<template>
-<div class="card mt-3" data-list="getting-started">
- <div class='card-body'>
- <div class="row">
- <div class="course-details col-xs-8 col-sm-9 col-md-10">
- <h5 class="card-title">{{ course.course_name }}</h5>
- <span class="course-list text-muted" id="getting-started">
- Course Content
- <ul class="mb-0 mt-1">
- <li v-for="content in course.course_content" :key="content.name">{{ content.content }}</li>
- </ul>
- </span>
- </div>
- <div v-if="$root.$data.isLogin" class='course-buttons text-center col-xs-4 col-sm-3 col-md-2'>
- <AcademyCourseCardButton v-if="this.$root.$data.checkProgramEnrollment(this.$route.params.code)" :course="course.name" :nextContent="nextContent" :nextContentType="nextContentType"/>
- </div>
- </div>
- </div>
-</div>
-</template>
-
-<script>
-import AcademyCourseCardButton from './AcademyCourseCardButton.vue'
-
-export default {
- props: ['course'],
- name: "AcademyCourseCard",
- data() {
- return {
- nextContent: '',
- nextContentType: ''
- }
- },
- mounted() {
- if(this.$root.$data.checkLogin()){
- frappe.call({
- method: "erpnext.www.academy.get_starting_content",
- args: {
- course_name: this.course.name
- }
- }).then(r => {
- this.nextContent = r.message.content,
- this.nextContentType = r.message.content_type
- });
- }
- },
- components: {
- AcademyCourseCardButton
- }
-};
-</script>
-
-<style scoped>
- @media only screen and (max-width: 576px) {
- .course-buttons {
- margin-top: 1em;
- }
-}
-</style>
\ No newline at end of file
diff --git a/erpnext/public/js/education/web-academy/pages/AcademyProgramPage.vue b/erpnext/public/js/education/web-academy/pages/AcademyProgramPage.vue
deleted file mode 100644
index 030c3ed..0000000
--- a/erpnext/public/js/education/web-academy/pages/AcademyProgramPage.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<template>
-<div>
- <AcademyTopSection v-bind:title="program.program_name" v-bind:description="program.description">
- <AcademyTopSectionButton/>
- </AcademyTopSection>
- <AcademyList :title="'Courses'" :description="''">
- <AcademyCourseCard v-for="course in course_list" :course="course" :key="course.name"/>
- </AcademyList>
-</div>
-</template>
-<script>
-import AcademyTopSection from "../components/AcademyTopSection.vue"
-import AcademyList from "../components/AcademyList.vue"
-import AcademyCourseCard from "../components/AcademyCourseCard.vue"
-import AcademyTopSectionButton from "../components/AcademyTopSectionButton.vue"
-
-export default {
- props: ['code'],
- name: "AcademyProgramPage",
- components: {
- AcademyTopSection,
- AcademyList,
- AcademyCourseCard,
- AcademyTopSectionButton
- },
- data() {
- return {
- program: '',
- course_list: []
- }
- },
- beforeMount(){
- if(this.$root.$data.isLogin) this.$root.$data.updateCompletedCourses()
- },
- mounted() {
- frappe.call({
- method: "erpnext.www.academy.get_program_details",
- args: {
- program_name: this.code
- }
- }).then(r => {
- this.program = r.message
- });
- frappe.call({
- method: "erpnext.www.academy.get_courses",
- args: {
- program_name: this.code
- }
- }).then(r => {
- this.course_list = r.message
- })
- },
-};
-</script>
\ No newline at end of file