Social: Implement shared user profiles - refs BT#21101

pull/5149/head
christianbeeznst 2 years ago
parent d931dfe9f8
commit 72b4d9d256
  1. 2
      assets/vue/components/message/MessageLayout.vue
  2. 2
      assets/vue/components/personalfile/Layout.vue
  3. 11
      assets/vue/components/social/MyFriendsCard.vue
  4. 29
      assets/vue/components/social/MyGroupsCard.vue
  5. 4
      assets/vue/components/social/MySkillsCard.vue
  6. 17
      assets/vue/components/social/SocialSideMenu.vue
  7. 5
      assets/vue/components/social/SocialWallPost.vue
  8. 2
      assets/vue/components/social/UserProfileCard.vue
  9. 2
      assets/vue/components/user/Layout.vue
  10. 11
      assets/vue/components/userreluser/Layout.vue
  11. 2
      assets/vue/views/account/Home.vue
  12. 11
      assets/vue/views/social/SocialLayout.vue
  13. 11
      assets/vue/views/userreluser/UserRelUserList.vue
  14. 2
      public/main/social/map.php
  15. 5
      public/main/social/skills_wheel.php
  16. 279
      public/main/template/default/skill/skill_wheel_student.html.twig
  17. 2
      public/main/template/default/social/map.html.twig
  18. 710
      src/CoreBundle/Resources/views/Skill/skill_wheel.js.html.twig

@ -25,7 +25,7 @@ provide("social-user", readonly(user))
async function loadUser() {
try {
user.value = route.query.id ? await store.dispatch("user/load", route.query.id) : store.getters["security/getUser"]
user.value = route.query.id ? await store.dispatch("user/load", '/api/users/' + route.query.id) : store.getters["security/getUser"]
} catch (e) {
user.value = {}
}

@ -27,7 +27,7 @@ provide("social-user", readonly(user))
async function loadUser() {
try {
user.value = route.query.id ? await store.dispatch("user/load", route.query.id) : store.getters["security/getUser"]
user.value = route.query.id ? await store.dispatch("user/load", '/api/users/' + route.query.id) : store.getters["security/getUser"]
} catch (e) {
user.value = {}
}

@ -7,7 +7,7 @@
</template>
<hr class="-mt-2 mb-4 -mx-4">
<div>
<div class="input-group mb-3">
<div v-if="isCurrentUser" class="input-group mb-3">
<input
type="search"
class="form-control"
@ -33,7 +33,7 @@
<a href="#" @click="viewAll">{{ t('View All Friends') }}</a>
</div>
</div>
<div v-if="allowSocialMap" class="text-center mt-3">
<div v-if="allowSocialMap && isCurrentUser" class="text-center mt-3">
<BaseButton
:label="t('Search user by geolocalization')"
type="primary"
@ -59,6 +59,7 @@ const { t } = useI18n()
const friends = ref([])
const searchQuery = ref('')
const user = inject('social-user')
const isCurrentUser = inject('is-current-user')
const router = useRouter()
const platformConfigStore = usePlatformConfig()
@ -87,7 +88,11 @@ async function fetchFriends(userId) {
}
const viewAll = () => {
router.push('/resources/friends')
if (isCurrentUser) {
router.push('/resources/friends')
} else {
router.push('/resources/friends?id=' + user.value.id)
}
}
watchEffect(() => {
if (user.value && user.value.id) {

@ -21,19 +21,21 @@
<a :href="goToUrl" class="btn btn-primary">{{ t('See all communities') }}</a>
</div>
<div v-else class="input-group mb-3">
<input
type="search"
class="form-control"
placeholder="Search"
v-model="searchQuery"
>
<button
class="btn btn-outline-secondary"
type="button"
@click="search"
>
<i class="mdi mdi-magnify"></i>
</button>
<div v-if="isCurrentUser">
<input
type="search"
class="form-control"
placeholder="Search"
v-model="searchQuery"
>
<button
class="btn btn-outline-secondary"
type="button"
@click="search"
>
<i class="mdi mdi-magnify"></i>
</button>
</div>
</div>
</div>
</BaseCard>
@ -51,6 +53,7 @@ const searchQuery = ref('')
const groups = ref([])
const goToUrl = ref('')
const user = inject('social-user')
const isCurrentUser = inject('is-current-user')
const platformConfigStore = usePlatformConfig()
const globalForumsCourse = computed(() => platformConfigStore.getSetting("forum.global_forums_course_id"))
const isValidGlobalForumsCourse = computed(() => {

@ -13,10 +13,10 @@
<div class="skill-name">{{ skill.name }}</div>
</div>
</div>
<div class="skills-links mt-2">
<!--div class="skills-links mt-2">
<a href="/main/social/skills_wheel.php">{{ t('Skills Wheel') }}</a> |
<a href="/main/social/skills_ranking.php">{{ t('Your skill ranking') }}</a>
</div>
</div-->
</div>
<div v-else>
<p>{{ t('Without achieved skills') }}</p>

@ -6,7 +6,7 @@
</div>
</template>
<hr class="-mt-2 mb-4 -mx-4">
<ul class="menu-list">
<ul v-if="isCurrentUser" class="menu-list">
<li :class="['menu-item', { 'active': isActive('/social') }]">
<router-link to="/social">
<i class="mdi mdi-home" aria-hidden="true"></i>
@ -62,6 +62,20 @@
</router-link>
</li>
</ul>
<ul v-else class="menu-list">
<li class="menu-item">
<router-link to="/social">
<i class="mdi mdi-home" aria-hidden="true"></i>
{{ t("Home") }}
</router-link>
</li>
<li class="menu-item">
<a href="/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id={{user.id}}" class="ajax" rel="noopener noreferrer">
<i class="mdi mdi-email" aria-hidden="true"></i>
{{ t("Send message") }}
</a>
</li>
</ul>
</BaseCard>
</template>
@ -85,6 +99,7 @@ const messageRelUserStore = useMessageRelUserStore()
const unreadMessagesCount = computed(() => messageRelUserStore.countUnread)
const user = inject('social-user')
const isCurrentUser = inject('is-current-user')
const groupLink = ref({ name: 'UserGroupShow' })
const platformConfigStore = usePlatformConfig()
const globalForumsCourse = computed(() => platformConfigStore.getSetting("forum.global_forums_course_id"))

@ -80,7 +80,7 @@
<script setup>
import WallCommentForm from "./SocialWallCommentForm.vue";
import {ref, computed, onMounted, reactive} from "vue";
import { ref, computed, onMounted, reactive, inject } from "vue"
import WallComment from "./SocialWallComment.vue";
import WallActions from "./Actions";
import axios from "axios";
@ -107,7 +107,8 @@ let comments = reactive([]);
const attachments = ref([]);
const securityStore = useSecurityStore()
const currentUser = computed(() => securityStore.user)
const currentUser = inject('social-user')
const isCurrentUser = inject('is-current-user')
const isOwner = computed(() => currentUser['@id'] === props.post.sender['@id'])
onMounted(async () => {

@ -27,6 +27,7 @@
</p>
</div>
<BaseButton
v-if="isCurrentUser"
:label="t('Edit profile')"
type="primary"
class="mt-4"
@ -49,6 +50,7 @@ const { t } = useI18n()
const store = useStore()
const route = useRoute()
const user = inject('social-user')
const isCurrentUser = inject('is-current-user')
const editProfile = () => {
window.location = "/account/edit"

@ -27,7 +27,7 @@ provide("social-user", readonly(user))
async function loadUser() {
try {
user.value = route.query.id ? await store.dispatch("user/load", route.query.id) : store.getters["security/getUser"]
user.value = route.query.id ? await store.dispatch("user/load", '/api/users/' + route.query.id) : store.getters["security/getUser"]
} catch (e) {
user.value = {}
}

@ -20,14 +20,23 @@ const store = useStore()
const route = useRoute()
const user = ref({})
const isCurrentUser = ref(true)
provide("social-user", readonly(user))
provide("is-current-user", readonly(isCurrentUser))
async function loadUser() {
try {
user.value = route.query.id ? await store.dispatch("user/load", route.query.id) : store.getters["security/getUser"]
if (route.query.id) {
user.value = await store.dispatch("user/load", '/api/users/' + route.query.id)
isCurrentUser.value = false
} else {
user.value = store.getters["security/getUser"]
isCurrentUser.value = true
}
} catch (e) {
user.value = {}
isCurrentUser.value = true
}
}

@ -53,7 +53,7 @@ provide("social-user", readonly(user))
async function loadUser() {
try {
user.value = route.query.id ? await store.dispatch("user/load", route.query.id) : store.getters["security/getUser"]
user.value = route.query.id ? await store.dispatch("user/load", '/api/users/' + route.query.id) : store.getters["security/getUser"]
} catch (e) {
user.value = {}
}

@ -32,14 +32,23 @@ const store = useStore()
const route = useRoute()
const user = ref({})
const isCurrentUser = ref(true)
provide("social-user", readonly(user))
provide("is-current-user", readonly(isCurrentUser))
async function loadUser() {
try {
user.value = route.query.id ? await store.dispatch("user/load", route.query.id) : store.getters["security/getUser"]
if (route.query.id) {
user.value = await store.dispatch("user/load", '/api/users/' + route.query.id)
isCurrentUser.value = false
} else {
user.value = store.getters["security/getUser"]
isCurrentUser.value = true
}
} catch (e) {
user.value = {}
isCurrentUser.value = true
}
}

@ -1,7 +1,7 @@
<template>
<h2 v-t="'Friends'" class="mr-auto" />
<hr />
<BaseToolbar>
<BaseToolbar v-if="isCurrentUser">
<BaseButton
:disabled="loadingFriends"
:label="t('Add friend')"
@ -75,7 +75,7 @@
/>
</div>
<div class="friend-options">
<div class="friend-options" v-if="isCurrentUser">
<span
class="friend-options__time"
v-text="relativeDatetime(item.createdAt)"
@ -92,7 +92,7 @@
</template>
</DataView>
</div>
<div class="basis-auto lg:basis-1/4">
<div v-if="isCurrentUser" class="basis-auto lg:basis-1/4">
<UserRelUserRequestsList
ref="requestList"
@accept-friend="reloadHandler"
@ -103,7 +103,7 @@
<script setup>
import { useStore } from "vuex"
import { onMounted, ref } from "vue"
import { inject, onMounted, ref } from "vue"
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import Skeleton from "primevue/skeleton"
@ -119,7 +119,8 @@ import UserRelUserRequestsList from "../../components/userreluser/UserRelUserReq
const store = useStore()
const router = useRouter()
const { t } = useI18n()
const user = store.getters["security/getUser"]
const user = inject('social-user')
const isCurrentUser = inject('is-current-user')
const items = ref([])
const notification = useNotification()

@ -121,7 +121,7 @@ $htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRAR
$htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_JS_PATH).'map/oms.min.js"></script>';
$tpl = new Template(null);
$tpl->assign('url', api_get_path(WEB_CODE_PATH).'social/profile.php');
$tpl->assign('url', api_get_path(WEB_PATH).'social');
$tpl->assign(
'image_city',
Display::return_icon(

@ -72,7 +72,10 @@ $tpl->assign('user_info', $userInfo);
$tpl->assign('ranking', $ranking);
$tpl->assign('skills', $skills);
$template = $tpl->get_template('skill/skill_wheel_student.tpl');
$skillId = isset($_GET['skill_id']) ? (int) $_GET['skill_id'] : 0;
$tpl->assign('skill_id_to_load', $skillId);
$template = $tpl->get_template('skill/skill_wheel_student.html.twig');
$content = $tpl->fetch($template);
$tpl->assign('content', $content);
$tpl->display_no_layout_template();

@ -1,4 +1,4 @@
{% include template ~ '/skill/skill_wheel.js.html.twig' %}
{% include '@ChamiloCore/Skill/skill_wheel.js.html.twig' %}
{% autoescape false %}
<script>
/* Skill search input in the left menu */
@ -155,169 +155,170 @@
});
</script>
<div id="page-back" class="page-skill">
<div class="container-fluid">
<div class="row">
<div class="col-md-3 skill-options">
<p class="skill-home">
<a class="btn btn-large btn-block btn--primary" href="{{ _p.web }}user_portal.php">
<em class="fa fa-home"></em> {{ "ReturnToCourseList"|trans }}
</a>
</p>
<div class="panel panel-default">
<div class="panel-body">
<figure class="text-center">
<img width="100px" src="{{ user_info.avatar }}" class="img-circle center-block">
<figcaption class="avatar-author">{{ user_info.complete_name }}</figcaption>
</figure>
<p class="text-center">
<a href="{{ _p.web_main }}social/skills_ranking.php" class="btn btn--plain" target="_blank">
{{ 'YourSkillRankingX'|trans|format(ranking) }}
</a>
</p>
<div class="text-center">
{% if skills is not empty %}
{% for skill in skills %}
{{ skill.img_small }}
{% endfor %}
{% endif %}
<div class="skill-wheel">
<div id="page-back" class="page-skill">
<div class="container-fluid">
<div class="row">
<div class="col-md-3 skill-options">
<p class="skill-home">
<a class="btn btn-large btn-block btn--primary" href="{{ _p.web }}social">
<em class="fa fa-home"></em> {{ "Return "|trans }}
</a>
</p>
<div class="panel panel-default">
<div class="panel-body">
<figure class="text-center">
<img width="100px" src="{{ user_info.avatar }}" class="img-circle center-block">
<figcaption class="avatar-author">{{ user_info.complete_name }}</figcaption>
</figure>
<p class="text-center">
<a href="{{ _p.web_main }}social/skills_ranking.php" class="btn btn--plain" target="_blank">
{{ 'YourSkillRankingX'|trans|format(ranking) }}
</a>
</p>
<div class="text-center">
{% if skills is not empty %}
{% for skill in skills %}
{{ skill.img_small }}
{% endfor %}
{% endif %}
{% for i in 1..(5 - ranking) %}
<img src="{{ 'badges-default.png'|icon(64) }}" width="64" height="64">
{% endfor %}
{% for i in 1..(5 - ranking) %}
<img src="{{ 'badges-default.png'|icon(64) }}" width="64" height="64">
{% endfor %}
</div>
</div>
</div>
</div>
<!-- ACCORDION -->
<div class="accordion" id="accordion2">
<div class="panel panel-default">
<div class="panel-heading">
<a data-toggle="collapse" data-parent="#accordion2" href="#collapseTwo">
{{ 'GetNewSkills'|trans }}
</a>
</div>
<div id="collapseTwo" class="panel-collapse collapse">
<div class="panel-body">
<!-- SEARCH -->
<div class="search-skill">
<h5 class="page-header">{{ 'SkillsSearch'|trans }}</h5>
<form id="skill_search" class="form-search">
<select id="skill_id" name="skill_id" multiple style="width: 100%;"></select>
<table id="skill_holder" class="table table-condensed"></table>
</form>
<!-- ACCORDION -->
<div class="accordion" id="accordion2">
<div class="panel panel-default">
<div class="panel-heading">
<a data-toggle="collapse" data-parent="#accordion2" href="#collapseTwo">
{{ 'GetNewSkills'|trans }}
</a>
</div>
<div id="collapseTwo" class="panel-collapse collapse">
<div class="panel-body">
<!-- SEARCH -->
<div class="search-skill">
<h5 class="page-header">{{ 'SkillsSearch'|trans }}</h5>
<form id="skill_search" class="form-search">
<select id="skill_id" name="skill_id" multiple style="width: 100%;"></select>
<table id="skill_holder" class="table table-condensed"></table>
</form>
</div>
<!-- END SEARCH -->
<!-- INFO SKILL -->
<h5 class="page-header">{{ 'SkillInfo'|trans }}</h5>
<div id="skill_info"></div>
<!-- END INFO SKILL -->
<p>
<a class="btn btn--plain btn-block load_root" rel="0" href="#">
<em class="fa fa-eye"></em> {{ "ViewSkillsWheel"|trans }}
</a>
</p>
</div>
<!-- END SEARCH -->
<!-- INFO SKILL -->
<h5 class="page-header">{{ 'SkillInfo'|trans }}</h5>
<div id="skill_info"></div>
<!-- END INFO SKILL -->
<p>
<a class="btn btn--plain btn-block load_root" rel="0" href="#">
<em class="fa fa-eye"></em> {{ "ViewSkillsWheel"|trans }}
</a>
</p>
</div>
</div>
</div>
</div>
<div class="panel-group" id="wheel-second-accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="wheel-legend-heading">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#wheel-second-accordion" href="#wheel-legend-collapse" aria-expanded="true" aria-controls="wheel-legend-collapse">
{{ "Legend"|trans }}
</a>
</h4>
</div>
<div id="wheel-legend-collapse" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="wheel-legend-heading">
<div class="panel-body">
<ul class="fa-ul">
<li>
<em class="fa fa-li fa-square skill-legend-basic"></em> {{ "BasicSkills"|trans }}
</li>
<li>
<em class="fa fa-li fa-square skill-legend-badges"></em> {{ "SkillsYouAcquired"|trans }}
</li>
<li>
<em class="fa fa-li fa-square skill-legend-add"></em> {{ "SkillsYouCanLearn"|trans }}
</li>
<li>
<em class="fa fa-li fa-square skill-legend-search"></em> {{ "SkillsSearchedFor"|trans }}
</li>
</ul>
<div class="panel-group" id="wheel-second-accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="wheel-legend-heading">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#wheel-second-accordion" href="#wheel-legend-collapse" aria-expanded="true" aria-controls="wheel-legend-collapse">
{{ "Legend"|trans }}
</a>
</h4>
</div>
<div id="wheel-legend-collapse" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="wheel-legend-heading">
<div class="panel-body">
<ul class="fa-ul">
<li>
<em class="fa fa-li fa-square skill-legend-basic"></em> {{ "BasicSkills"|trans }}
</li>
<li>
<em class="fa fa-li fa-square skill-legend-badges"></em> {{ "SkillsYouAcquired"|trans }}
</li>
<li>
<em class="fa fa-li fa-square skill-legend-add"></em> {{ "SkillsYouCanLearn"|trans }}
</li>
<li>
<em class="fa fa-li fa-square skill-legend-search"></em> {{ "SkillsSearchedFor"|trans }}
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="wheel-display-heading">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#wheel-second-accordion" href="#wheel-display-collapse" aria-expanded="false" aria-controls="wheel-display-collapse">
{{ 'DisplayOptions'|trans }}
</a>
</h4>
</div>
<div id="wheel-display-collapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="wheel-display-heading">
<div class="panel-body">
<p>{{ 'ChooseABackgroundColor'|trans }}</p>
<ul class="list-unstyled" id="skill-change-background-options">
<li><a href="#" data-color="#FFFFFF">{{ 'White'|trans }}</a></li>
<li><a href="#" data-color="#000000">{{ 'Black'|trans }}</a></li>
<li><a href="#" data-color="#A9E2F3">{{ 'LightBlue' }}</a></li>
<li><a href="#" data-color="#848484">{{ 'Gray'|trans }}</a></li>
<li><a href="#" data-color="#F7F8E0">{{ 'Corn'|trans }}</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="wheel-display-heading">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#wheel-second-accordion" href="#wheel-display-collapse" aria-expanded="false" aria-controls="wheel-display-collapse">
{{ 'DisplayOptions'|trans }}
</a>
</h4>
</div>
<div id="wheel-display-collapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="wheel-display-heading">
<div class="panel-body">
<p>{{ 'ChooseABackgroundColor'|trans }}</p>
<ul class="list-unstyled" id="skill-change-background-options">
<li><a href="#" data-color="#FFFFFF">{{ 'White'|trans }}</a></li>
<li><a href="#" data-color="#000000">{{ 'Black'|trans }}</a></li>
<li><a href="#" data-color="#A9E2F3">{{ 'LightBlue' }}</a></li>
<li><a href="#" data-color="#848484">{{ 'Gray'|trans }}</a></li>
<li><a href="#" data-color="#F7F8E0">{{ 'Corn'|trans }}</a></li>
</ul>
</div>
</div>
</div>
</div>
<!-- END ACCORDEON -->
</div>
<!-- END ACCORDEON -->
</div>
<div id="wheel_container" class="col-md-9">
<div id="skill_wheel">
<img src="">
<div id="wheel_container" class="col-md-9">
<div id="skill_wheel">
<img src="">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="frm-skill" tabindex="-1" role="dialog" aria-labelledby="form-skill-title" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="{{ "Close" | trans }}">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="form-skill-title">{{ "Skill" | trans }}</h4>
</div>
<div class="modal-body">
{{ dialogForm }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn--primary" data-dismiss="modal">
{{ "Close" | trans }}
</button>
<div class="modal fade" id="frm-skill" tabindex="-1" role="dialog" aria-labelledby="form-skill-title" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="{{ "Close" | trans }}">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="form-skill-title">{{ "Skill" | trans }}</h4>
</div>
<div class="modal-body">
{{ dialogForm }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn--primary" data-dismiss="modal">
{{ "Close" | trans }}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="frm-course-info" tabindex="-1" role="dialog" aria-labelledby="form-course-info-title" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="{{ "Close" | trans }}">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="form-course-info-title">{{ "ChooseCourse" | trans }}</h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn--primary" data-dismiss="modal">{{ "Close" | trans }}</button>
<div class="modal fade" id="frm-course-info" tabindex="-1" role="dialog" aria-labelledby="form-course-info-title" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="{{ "Close" | trans }}">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="form-course-info-title">{{ "ChooseCourse" | trans }}</h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn--primary" data-dismiss="modal">{{ "Close" | trans }}</button>
</div>
</div>
</div>
</div>
</div>
{% endautoescape %}

@ -46,7 +46,7 @@
marker.addListener('click', function() {
var infoWindow = new google.maps.InfoWindow({
content: '<a href="{{ url }}?u=' + city['id'] + '">' + city['complete_name'] + '</a>'
content: '<a href="{{ url }}?id=' + city['id'] + '">' + city['complete_name'] + '</a>'
});
infoWindow.open(map, marker);
});

@ -0,0 +1,710 @@
<script>
var SkillWheel = {
currentSkill: null,
getSkillInfo: function(skillId) {
return $.getJSON(
'{{ url }}',
{
a: 'get_skill_info',
id: parseInt(skillId)
}
);
},
showFormSkill: function(skillId) {
skillId = parseInt(skillId);
var formSkill = $('form[name="form"]');
var getSkillInfo = SkillWheel.getSkillInfo(skillId);
$.when(getSkillInfo).done(function(skillInfo) {
SkillWheel.currentSkill = skillInfo;
var getSkillParentInfo = SkillWheel.getSkillInfo(skillInfo.extra.parent_id);
$.when(getSkillParentInfo).done(function(skillParentInfo) {
formSkill.find('p#parent').text(skillParentInfo.title);
});
formSkill.find('p#name').text(skillInfo.title);
if (skillInfo.short_code.length > 0) {
formSkill.find('p#short_code').text(skillInfo.short_code).parent().parent().show();
} else {
formSkill.find('p#short_code').parent().parent().hide();
}
if (skillInfo.description.length > 0) {
formSkill.find('p#description').text(skillInfo.description).parent().parent().show();
} else {
formSkill.find('p#description').parent().parent().hide();
}
if (skillInfo.gradebooks.length > 0) {
formSkill.find('ul#gradebook').empty().parent().parent().show();
$.each(skillInfo.gradebooks, function(index, gradebook) {
$('<li>').text(gradebook.title).appendTo(formSkill.find('ul#gradebook'));
});
} else {
formSkill.find('ul#gradebook').parent().parent().hide();
}
$('#frm-skill').modal('show');
});
}
};
/* Skill wheel settings */
var debug = true;
var url = '{{ url }}';
var skill_to_load_from_get = '{{ skill_id_to_load }}';
//Just in case we want to use it
var main_depth = 4;
var main_parent_id = 0;
// Used to split in two word or not
var max_size_text_length = 20;
/* ColorBrewer settings */
var my_domain = [1,2,3,4,5,6,7,8,9];
var col = 9;
var color_patterns = [];
/*
See colorbrewer documentation
color_patterns[1] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.Blues[col]);
color_patterns[2] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.Purples[col]);
color_patterns[2] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.Blues[6]);
color_patterns[3] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.Greens[col]);
color_patterns[4] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.Reds[col]);
color_patterns[5] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.Oranges[col]);
color_patterns[6] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.YlOrBr[col]);
color_patterns[7] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.YlGn[col]);
color_patterns[8] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.YlGnBu[col]);
color_patterns[9] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.GnBu[col]);
color_patterns[10] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.BuGn[col]);
color_patterns[11] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.PuBuGn[col]);
color_patterns[12] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.PuBu[col]);
color_patterns[13] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.BuPu[col]);
color_patterns[14] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.RdPu[col]);
color_patterns[15] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.PuRd[col]);
color_patterns[16] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.OrRd[col]);
color_patterns[17] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.YlOrRd[col]);*/
//Too make the gray tones lighter
col = 3;
color_patterns[18] = d3.scale.ordinal().domain(my_domain).range(colorbrewer.Blues[col]);
//If you want to use the category10()
//var normal_fill = d3.scale.category10().domain(my_domain);
//First 8 colors
var colors = $.xcolor.analogous('#da0'); //8 colors
//How long will be the array of colors?
var color_loops = 4;
// Generating array of colors thanks to the "$.xcolor.analogous" function we can create a rainbow style!
for (i= 0; i < color_loops; i++) {
//Getting the latest color hex of the 8 colors loaded
last_color = colors[colors.length-1].getHex();
//Getting the complementary
glue_color = $.xcolor.complementary(last_color);
//Generating 8 more colors
temp_color_array = $.xcolor.analogous(glue_color);
//Adding the color to the main array
colors = $.merge(colors, temp_color_array);
}
/* The partiton name will have 1 or 2 lines? */
function is_multiline(word) {
if (word) {
if (word.length > max_size_text_length) {
return (word).split(" ").length > 1;
}
}
return false;
}
/* Interpolate the scales! */
function arcTween(d, arc, x, y, r) {
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, r]);
return function(d) {
return function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
/* Calculate maxY */
function maxY(d) {
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
/* Use a formula for contrasting colour
http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
*/
function brightness(rgb) {
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
}
/* Returns whether p is parent of c */
function isParentOf(p, c) {
if (p === c) return true;
if (p.children) {
return p.children.some(function(d) {
return isParentOf(d, c);
});
}
return false;
}
function get_color(d) {
depth = d.depth;
if (d.family_id) {
/*var p = color_patterns[d.family_id];
color = p(depth -1 + d.counter);
d.color = color;*/
if (depth > 1) {
family1 = colors[d.family_id];
family2 = colors[d.family_id + 2];
position = d.depth*d.counter;
//part_color = $.xcolor.gradientlevel(family1, family2, position, 100);
part_color = $.xcolor.lighten(family1, position, 15);
color = part_color.getHex();
//console.log(d.depth + " - " + d.title + " + "+ color+ "+ " +d.counter);
} else {
color = colors[d.family_id];
}
return color;
}
color = '#fefefe';
return color; //missing colors
}
/*
gray tones for all skills that have no particular property ("Basic skills wheel" view)
yellow tones for skills that are provided by courses in Chamilo ("Teachable skills" view)
bright blue tones for personal skills already acquired by the student currently looking at the weel ("My skills" view)
dark blue tones for skills already acquired by a series of students, when looking at the will in the "Owned skills" view.
bright green for skills looked for by a HR director ("Profile search" view)
dark green for skills most searched for, summed up from the different saved searches from HR directors ("Most wanted skills")
bright red for missing skills, in the "Required skills" view for a student when looking at the "Most wanted skills" (or later, when we will have developed that, for the "Matching position" view)
*/
var userSkills;
/**
Manage the partition background colors
**/
function set_skill_style(d, attribute, searched_skill_id) {
//Default border color (stroke)
return_stroke = '#000';
//0. Nice rainbow colors (Comment 1.0 to see the rainbow!)
return_fill = get_color(d);
//1. Grey colors using colorbrewer
var p = color_patterns[18];
color = p(depth -1 + d.counter);
return_fill = color;
//2. Yellow - If the skill has a gradebook attached
if (d.skill_has_gradebook) {
return_fill = '#F89406';
//return_stroke = 'grey';
}
//3. Red - if you search that skill
if (d.isSearched) {
return_fill = '#B94A48';
}
if (!userSkills) {
$.ajax({
url: url + '&a=get_all_user_skills',
async: false,
success: function (skills) {
userSkills = jQuery.parseJSON(skills);
}
});
}
// Old way (it makes a lot of ajax calls)
//4. Blue - if user achieved that skill
//var skill = false;
/*$.ajax({
url: url+'&a=get_user_skill&profile_id='+d.id,
async: false,
success: function(skill) {
if (skill == 1) {
return_fill = '#3A87AD';
}
}
});*/
// New way (Only 1 ajax call)
// 4. Blue - if user achieved that skill
if (userSkills[d.id]) {
return_fill = '#A1D99B';
}
// 5. Grey / Black if the skill is disabled
if (d.status < 1) {
return_fill = '#48616C';
}
switch (attribute) {
case 'fill':
//In order to identify the color of the text (white, black) used in other function
d.color = return_fill;
return return_fill;
break;
case 'stroke':
return return_stroke;
break;
}
}
/* When you click a skill partition */
function click_partition(d, path, text, icon, arc, x, y, r, p, vis) {
if (debug) {
console.log('Clicking a partition skill id: '+d.id);
console.log(d);
console.log('real parent_id: '+d.real_parent_id + ' parent_id: ' +d.parent_id);
console.log('depth ' + d.depth);
console.log('main_depth ' + main_depth);
console.log('main_parent_id: ' + main_parent_id);
}
if (d.depth >= main_depth) {
//main_depth += main_depth;
if (main_parent_id) {
load_nodes(main_parent_id, main_depth);
} else {
load_nodes(d.id, main_depth);
}
}
if (d.id) {
console.log('Getting skill info');
skill_info = get_skill_info(d.parent_id);
main_parent_id = skill_info.extra.parent_id;
main_parent_id = d.parent_id;
console.log('Setting main_parent_id: ' + main_parent_id);
}
//console.log(main_parent_id);
/* "No id" means that we reach the center of the wheel go to the root*/
if (!d.id) {
load_nodes(main_parent_id, main_depth);
}
if (debug) console.log('Continue to click_partition');
//console.log(main_parent_id);
//Duration of the transition
var duration = 1000;
path.transition()
.duration(duration)
.attrTween("d", arcTween(d, arc, x, y, r));
/* Updating text position */
// Somewhat of a hack as we rely on arcTween updating the scales.
text.style("visibility", function(e) {
return isParentOf(d, e) ? null : d3.select(this).style("visibility");
})
.transition().duration(duration)
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d) {
var multiline = is_multiline(d.title); //(d.title || "").split(" ").length > 1;
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e) {
return isParentOf(d, e) ? 1 : 1e-6;
})
.each("end", function(e) {
d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
});
//Add an icon in the partition
/* Updating icon position */
/*
icon.transition().duration(duration)
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d) {
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle;
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e) {
//return isParentOf(d, e) ? 1 : 1e-6;
})
.each("end", function(e) {
//d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
});*/
}
/* Handles mouse clicks */
function handle_mousedown_event(d, path, text, icon, arc, x, y, r, padding, vis) {
switch (d3.event.which) {
case 1:
//alert('Left mouse button pressed');
click_partition(d, path, text, icon, arc, x, y, r, padding, vis);
break;
case 2:
//alert('Middle mouse button pressed');
break;
case 3:
if (typeof d.id === 'undefined') {
break;
}
SkillWheel.showFormSkill(d.id);
//alert('Right mouse button pressed');
break;
default:
//alert('You have a strange mouse :D '); //
}
}
/*
Loads the skills partitions thanks to a json call
*/
function load_nodes(load_skill_id, main_depth, extra_parent_id) {
if (debug) {
console.log('Load nodes ----->');
console.log('Loading skill id: '+load_skill_id+' with depth ' + main_depth);
console.log('main_parent_id before: ' + main_parent_id);
}
// "Root partition" on click switch
if (main_parent_id && load_skill_id) {
skill_info = get_skill_info(load_skill_id);
if (skill_info && skill_info.extra) {
main_parent_id = skill_info.extra.parent_id;
} else {
main_parent_id = 0;
}
console.log('main_parent_id after: ' + main_parent_id);
}
if (load_skill_id && load_skill_id == 1) {
main_parent_id = 0;
}
/** Define constants and size of the wheel */
/** Total width of the wheel (also counts for the height) */
var w = 900,
h = w,
r = w / 2,
/** x/y positionning of the center of the wheel */
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.1).domain([0, 1]).range([0, r]),
/** Padding in pixels before the string starts */
padding = 3,
/** Levels to show */
levels_to_show = 3;
reduce_top = 1;
/* Locate the #div id element */
$("#skill_wheel").remove();
$("#wheel_container").html('');
$("#wheel_container").append('<div id="skill_wheel"></div>');
var div = d3.select("#skill_wheel");
/* Remove the image (make way for the dynamic stuff */
div.select("img").remove();
/* Append an element "svg" to the #vis section */
var vis = div.append("svg")
//.attr("class", "Blues")
.attr("width", w + padding * 2)
.attr("height", h + padding * 2)
.append("g")
.attr("transform", "translate(" + (r + padding) + "," + (r/reduce_top + padding) + ")");
/* ...update translate variables to change coordinates of wheel's center */
/* Add a small label to help the user */
div.append("p")
.attr("id", "intro")
.text("{{ "Click to zoom"|trans }}");
/* Generate the partition layout */
var partition = d3.layout.partition()
.sort(null)
/** Value here seems to be a calculation of the size of the elements
depending on the level of depth they're at. Changing it makes
elements pass over the limits of others... */
//.size([1, 2])
.value(function(d) {
//return 5.8 - d.depth;
//When having more than 4 children seems that the code above does not work
return 1;
});
/* Generate an arc which will define the whole wheel context */
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, d.y ? y(d.y) : d.y);
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
load_skill_condition = '';
//First the $_GET value
if (skill_to_load_from_get != 0) {
load_skill_condition = 'skill_id=' + skill_to_load_from_get;
}
//The JS load
if (load_skill_id != 0) {
load_skill_condition = 'skill_id=' + load_skill_id;
}
d3.json("{{ wheel_url }}&main_depth="+main_depth+"&"+load_skill_condition, function(json) {
/** Define the list of nodes based on the JSON */
var nodes = partition.nodes({
children: json
});
/* Setting all skills */
var path = vis.selectAll("path").data(nodes);
/* Setting all texts */
var text = vis.selectAll("text").data(nodes);
/* Setting icons */
var icon = vis.selectAll("icon").data(nodes);
/* Path settings */
path.enter().append("path")
.attr("id", function(d, i) {
return "path-" + i;
})
.attr("d", arc)
.attr("fill-rule", "evenodd")
.attr("class", "skill_partition skill_background")
// .style("fill", colour)
.style("fill", function(d) {
return set_skill_style(d, 'fill', load_skill_id);
})
.style("stroke", function(d) {
return set_skill_style(d, 'stroke');
})
.on("mouseover", function(d, i) {
//$("#icon-" + i).show();
})
.on("mouseout", function(d, i) {
//$("#icon-" + i).hide();
})
.on("contextmenu", function(d, i) {
//Handles mouse clicks
handle_mousedown_event(d, path, text, icon, arc, x, y, r, padding, vis);
//Blocks "right click menu"
d3.event.preventDefault();
return false;
})
.on("mousedown", function(d, i) {
})
.on("click", function(d) {
//Simple click
handle_mousedown_event(d, path, text, icon, arc, x, y, r, padding, vis);
});
/*//Redefine the root
path_zero = vis.selectAll("#path-0").on("mousedown", function(d){
d = get_skill_info(extra_parent_id);
d.parent_id = d.extra.parent_id;
click_partition(d, path, text, icon, arc, x, y, r, padding, vis);
});*/
/* End setting skills */
/* Text settings */
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d) {
return brightness(d3.rgb(d.color)) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("rel", "tooltip_skill")
.attr("title", function(d) {
return d.title;
})
.attr("dy", ".2em")
.attr("transform", function(d) {
/** Get the text details and define the rotation and general position */
var multiline = is_multiline(d.title); //(d.title || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("mouseover", function(d, i) {
//$("#icon-" + i).show();
})
.on("mouseout", function(d, i) {
//$("#icon-" + i).hide();
})
.on("contextmenu", function(d, i) {
handle_mousedown_event(d, path, text, icon, arc, x, y, r, padding, vis);
d3.event.preventDefault();
})
.on("mousedown", function(d, i) {
})
.on("click", function(d) {
handle_mousedown_event(d, path, text, icon, arc, x, y, r, padding, vis);
});
/** Managing text - maximum two words */
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) {
if (d.short_code) {
return d.short_code;
}
if (d.depth && d.title) {
var nameParts = d.title.split(' ');
if (nameParts[0].length > max_size_text_length) {
return nameParts[0].substring(0, max_size_text_length - 3) + '...';
}
return nameParts[0];
}
return d.depth ? d.title : '';
});
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) {
if (d.short_code) {
return null;
}
if (d.depth && d.title) {
var nameParts = d.title.split(' ');
if (nameParts.length >= 2) {
if (nameParts[1].length > max_size_text_length) {
return nameParts[1].substring(0, max_size_text_length - 3) + '...';
}
return nameParts[1];
}
return '';
}
return d.depth ? d.title : '';
});
/* Icon settings */
/*
var icon_click = icon.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d) {
//return "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
///Get the text details and define the rotation and general position
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle;
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding +80) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", function(d){
SkillWheel.showFormSkill(d.id);
});
icon_click.append("tspan")
.attr("id", function(d, i) {
return "icon-" + i;
})
.attr("x", 0)
.attr("display", 'none')
.text(function(d) {
//return "Click";
});*/
});
if (debug) {
console.log('<------ End load nodes ----->');
}
}
/* Skill AJAX calls */
function get_skill_info(my_id) {
var skill = false;
$.ajax({
url: url+'&a=get_skill_info&id='+my_id,
async: false,
success: function(json) {
skill = jQuery.parseJSON(json);
return skill;
}
});
return skill;
}
function get_gradebook_info(id) {
var item = false;
$.ajax({
url: url+'&a=get_gradebook_info&id='+id,
async: false,
success: function(json) {
item = jQuery.parseJSON(json);
return item;
}
});
return item;
}
</script>
Loading…
Cancel
Save