-
Notifications
You must be signed in to change notification settings - Fork 727
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
462 additions
and
0 deletions.
There are no files selected for viewing
268 changes: 268 additions & 0 deletions
268
kolibri/plugins/learn/assets/src/views/LearningActivityBar.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
<template> | ||
|
||
<UiToolbar> | ||
<KLabeledIcon :style="{ 'margin-top': '8px' }"> | ||
<template #icon> | ||
<LearningActivityIcon | ||
v-if="learningActivityKind" | ||
data-test="learningActivityIcon" | ||
:kind="learningActivityKind" | ||
/> | ||
</template> | ||
<TextTruncator | ||
:text="resourceTitle" | ||
:maxHeight="30" | ||
/> | ||
</KLabeledIcon> | ||
|
||
<template #icon> | ||
<KIconButton | ||
icon="back" | ||
data-test="backButton" | ||
@click="onBackButtonClick" | ||
/> | ||
</template> | ||
|
||
<template #actions> | ||
<KIconButton | ||
v-for="action in barActions" | ||
:key="action.id" | ||
:data-test="`bar_${action.dataTest}`" | ||
:icon="action.icon" | ||
:color="action.iconColor" | ||
:tooltip="action.label" | ||
@click="onActionClick(action.event)" | ||
/> | ||
|
||
<span class="menu-wrapper"> | ||
<KIconButton | ||
v-if="menuActions.length" | ||
ref="menuButton" | ||
data-test="menuButton" | ||
icon="optionsHorizontal" | ||
tooltip="More options" | ||
@click="toggleMenu" | ||
/> | ||
<CoreMenu | ||
v-show="isMenuOpen" | ||
ref="menu" | ||
class="menu" | ||
:raised="true" | ||
:isOpen="isMenuOpen" | ||
:containFocus="true" | ||
@close="closeMenu" | ||
> | ||
<template #options> | ||
<CoreMenuOption | ||
v-for="action in menuActions" | ||
:key="action.id" | ||
:data-test="`menu_${action.dataTest}`" | ||
:style="{ 'cursor': 'pointer' }" | ||
@select="onActionClick(action.event)" | ||
> | ||
<KLabeledIcon> | ||
<template #icon> | ||
<KIcon | ||
:icon="action.icon" | ||
:color="action.iconColor" | ||
/> | ||
</template> | ||
<div>{{ action.label }}</div> | ||
</KLabeledIcon> | ||
</CoreMenuOption> | ||
</template> | ||
</CoreMenu> | ||
</span> | ||
</template> | ||
</UiToolbar> | ||
|
||
</template> | ||
|
||
|
||
<script> | ||
import difference from 'lodash/difference'; | ||
import KResponsiveWindowMixin from 'kolibri-design-system/lib/KResponsiveWindowMixin'; | ||
import CoreMenu from 'kolibri.coreVue.components.CoreMenu'; | ||
import CoreMenuOption from 'kolibri.coreVue.components.CoreMenuOption'; | ||
import UiToolbar from 'kolibri.coreVue.components.UiToolbar'; | ||
import TextTruncator from 'kolibri.coreVue.components.TextTruncator'; | ||
import { LearningActivityKinds } from 'kolibri.coreVue.vuex.constants'; | ||
import LearningActivityIcon from './LearningActivityIcon.vue'; | ||
export default { | ||
name: 'LearningActivityBar', | ||
components: { | ||
CoreMenu, | ||
CoreMenuOption, | ||
UiToolbar, | ||
TextTruncator, | ||
LearningActivityIcon, | ||
}, | ||
mixins: [KResponsiveWindowMixin], | ||
props: { | ||
resourceTitle: { | ||
type: String, | ||
required: true, | ||
}, | ||
/** | ||
* Learning activity constant | ||
*/ | ||
learningActivityKind: { | ||
type: String, | ||
required: true, | ||
validator(value) { | ||
return Object.values(LearningActivityKinds).includes(value); | ||
}, | ||
}, | ||
/** | ||
* Is the bar used in the context of a lesson? | ||
* There are slight differences in rendering | ||
* related to the context, e.g. action buttons labels. | ||
*/ | ||
isLessonContext: { | ||
type: Boolean, | ||
required: false, | ||
default: false, | ||
}, | ||
isBookmarked: { | ||
type: Boolean, | ||
required: false, | ||
default: false, | ||
}, | ||
/** | ||
* Does a resource have the option to be | ||
* manually marked as complete? | ||
* Used to determine if a button for this action | ||
* should be displayed. | ||
*/ | ||
allowMarkComplete: { | ||
type: Boolean, | ||
required: false, | ||
default: false, | ||
}, | ||
}, | ||
data() { | ||
return { | ||
isMenuOpen: false, | ||
}; | ||
}, | ||
computed: { | ||
allActions() { | ||
const actions = [ | ||
{ | ||
id: 'view-resource-list', | ||
icon: 'resourceList', | ||
label: this.isLessonContext ? 'View lesson plan' : 'View topic resources', | ||
event: 'viewResourceList', | ||
dataTest: this.isLessonContext ? 'viewLessonPlanButton' : 'viewTopicResourcesButton', | ||
}, | ||
{ | ||
id: 'bookmark', | ||
icon: this.isBookmarked ? 'bookmark' : 'bookmarkEmpty', | ||
label: this.isBookmarked ? 'Remove from bookmarks' : 'Save to bookmarks', | ||
event: 'toogleBookmark', | ||
dataTest: this.isBookmarked ? 'removeBookmarkButton' : 'addBookmarkButton', | ||
}, | ||
]; | ||
if (this.allowMarkComplete) { | ||
actions.push({ | ||
id: 'mark-complete', | ||
icon: 'star', | ||
iconColor: this.$themePalette.yellow.v_700, | ||
label: 'Mark resource as finished', | ||
event: 'markComplete', | ||
dataTest: 'markCompleteButton', | ||
}); | ||
} | ||
actions.push({ | ||
id: 'view-info', | ||
icon: 'info', | ||
label: 'View information', | ||
event: 'viewInfo', | ||
dataTest: 'viewInfoButton', | ||
}); | ||
return actions; | ||
}, | ||
barActions() { | ||
const actions = []; | ||
if (this.windowBreakpoint >= 1) { | ||
actions.push(this.allActions.find(action => action.id === 'view-resource-list')); | ||
} | ||
if (this.windowBreakpoint >= 2) { | ||
actions.push(this.allActions.find(action => action.id === 'bookmark')); | ||
if (!this.allowMarkComplete) { | ||
actions.push(this.allActions.find(action => action.id === 'view-info')); | ||
} | ||
} | ||
return actions; | ||
}, | ||
menuActions() { | ||
return difference(this.allActions, this.barActions); | ||
}, | ||
}, | ||
created() { | ||
window.addEventListener('click', this.onWindowClick); | ||
}, | ||
beforeDestroy() { | ||
window.removeEventListener('click', this.onWindowClick); | ||
}, | ||
methods: { | ||
closeMenu({ focusMenuButton = true } = {}) { | ||
this.isMenuOpen = false; | ||
if (!focusMenuButton) { | ||
return; | ||
} | ||
this.$nextTick(() => { | ||
this.$refs.menuButton.$el.focus(); | ||
}); | ||
}, | ||
toggleMenu() { | ||
this.isMenuOpen = !this.isMenuOpen; | ||
if (!this.isMenuOpen) { | ||
return; | ||
} | ||
this.$nextTick(() => { | ||
this.$refs.menu.$el.focus(); | ||
}); | ||
}, | ||
onBackButtonClick() { | ||
this.$emit('navigateBack'); | ||
}, | ||
onActionClick(actionEvent) { | ||
this.$emit(actionEvent); | ||
}, | ||
onWindowClick(event) { | ||
if (!this.isMenuOpen) { | ||
return; | ||
} | ||
// close menu on outside click | ||
if ( | ||
!this.$refs.menu.$el.contains(event.target) && | ||
!this.$refs.menuButton.$el.contains(event.target) | ||
) { | ||
this.closeMenu({ focusMenuButton: false }); | ||
} | ||
}, | ||
}, | ||
}; | ||
</script> | ||
|
||
|
||
<style lang="scss" scoped> | ||
.menu-wrapper { | ||
position: relative; | ||
} | ||
.menu { | ||
position: absolute; | ||
top: 50%; | ||
right: 10px; // right-align to the menu icon | ||
min-width: 270px; | ||
transform: translateY(16px); | ||
} | ||
</style> |
Oops, something went wrong.