Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions assets/vue/components/basecomponents/BaseDropdownMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue'

const props = defineProps({
dropdownId: { type: [String, Number], required: true }
})

const emit = defineEmits(['open', 'close'])

// Used to track if the dropdown is open
const isOpen = ref(false)

function toggleMenu(event) {

// Close all the dropdowns
closeAllOtherDropdowns(event.target)

isOpen.value = !isOpen.value

if (isOpen.value) {
emit('open', props.dropdownId)
} else {
emit('close')
}
}

function closeMenu() {
if (isOpen.value) {
isOpen.value = false
emit('close')
}
}

function closeAllOtherDropdowns(clickedElement) {
const allDropdowns = document.querySelectorAll('.dropdown-menu')

allDropdowns.forEach(dropdown => {
if (!dropdown.contains(clickedElement)) {
dropdown.dispatchEvent(new CustomEvent('close-dropdown'))
}
})
}

function handleCloseDropdown() {
closeMenu()
}

function handleClickOutside(e) {
if (isOpen.value && !e.target.closest('.dropdown-menu')) {
closeMenu()
}
}

onMounted(() => {
document.addEventListener('mousedown', handleClickOutside)
const dropdownElement = document.querySelector(`[data-dropdown-id="${props.dropdownId}"]`)
if (dropdownElement) {
dropdownElement.addEventListener('close-dropdown', handleCloseDropdown)
}
})

onBeforeUnmount(() => {
document.removeEventListener('mousedown', handleClickOutside)
const dropdownElement = document.querySelector(`[data-dropdown-id="${props.dropdownId}"]`)
if (dropdownElement) {
dropdownElement.removeEventListener('close-dropdown', handleCloseDropdown)
}
})
</script>

<template>
<div
class="dropdown-menu absolute right-0"
style="position:relative;"
:data-dropdown-id="dropdownId"
>
<span @click="toggleMenu">
<slot name="button">

</slot>
</span>
<div v-if="isOpen" class="menu-content absolute right-0 mt-2 top-full ">
<slot name="menu" />
</div>
</div>
</template>
104 changes: 65 additions & 39 deletions assets/vue/components/lp/LpCardItem.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
<script setup>
import { computed } from "vue"
import { useI18n } from "vue-i18n"
import BaseDropdownMenu from "../basecomponents/BaseDropdownMenu.vue"

const { t } = useI18n()

const props = defineProps({
lp: { type: Object, required: true },
canEdit: { type: Boolean, default: false },
canExportScorm: {type: Boolean, default: false},
ringDash: { type: Function, required: true },
ringValue: { type: Function, required: true },
})
const emit = defineEmits([
"open","edit","report","settings","build",
"toggle-visible","toggle-publish","delete"
"open","edit","report","settings", "build",
"toggle-visible","toggle-publish","delete","export-scorm"
])

const dateText = computed(() => {
const v = props.lp?.dateText ?? ""
return typeof v === "string" ? v.trim() : ""
})

const progressBgClass = computed(() => {
return props.ringValue(props.lp.progress) === 100 ? 'bg-success' : 'bg-support-5'
})

const progressTextClass = computed(() => {
return props.ringValue(props.lp.progress) === 100 ? 'text-success' : 'text-support-5'
})
</script>

<template>
Expand All @@ -34,23 +44,6 @@ const dateText = computed(() => {
</svg>
</button>

<details v-if="canEdit" class="absolute right-3 top-3">
<summary
class="w-8 h-8 grid place-content-center rounded-lg border border-gray-25 hover:bg-gray-15 cursor-pointer"
:title="t('More')" :aria-label="t('More')"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="5" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="12" cy="19" r="1.6"/>
</svg>
</summary>
<div class="absolute right-0 mt-2 w-44 bg-white border border-gray-25 rounded-xl shadow p-1 z-10">
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="emit('settings', lp)">{{ t('Settings') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="emit('toggle-visible', lp)">{{ t('Toggle visibility') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="emit('toggle-publish', lp)">{{ t('Publish / Unpublish') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15 text-danger" @click="emit('delete', lp)">{{ t('Delete') }}</button>
</div>
</details>

<div class="mt-2 grid grid-cols-[80px_1fr] gap-3 items-start pr-10 pl-8">
<div class="w-20 h-20 rounded-xl overflow-hidden ring-1 ring-gray-25 bg-gray-15 shrink-0">
<img v-if="lp.coverUrl" :src="lp.coverUrl" alt="" class="w-full h-full object-cover" />
Expand Down Expand Up @@ -91,19 +84,23 @@ const dateText = computed(() => {
<div class="mt-auto pt-3 flex items-center pl-8">
<div class="flex items-center gap-2">
<div class="relative w-10 h-10">
<svg viewBox="0 0 40 40" class="w-10 h-10">
<circle cx="20" cy="20" r="16" stroke-width="3.5" class="text-gray-25" fill="none" stroke="currentColor" />
<svg viewBox="0 0 37 37" class="w-10 h-10">
<circle cx="18.5" cy="19" r="16" stroke-width="3.5" class="text-gray-25" fill="none" stroke="currentColor" />
<circle
cx="20" cy="20" r="16" stroke-width="3.5" fill="none"
cx="21" cy="18.5" r="16" stroke-width="3.5" fill="none"
:stroke-dasharray="ringDash(lp.progress)"
stroke-linecap="round"
class="text-support-5"
:class="progressTextClass"
stroke="currentColor"
transform="rotate(-90 20 20)"
/>
</svg>
<span class="absolute -top-0.5 left-1/2 -translate-x-1/2 w-1.5 h-1.5 rounded-full bg-support-5 ring-2 ring-white" aria-hidden/>
<div class="absolute inset-0 grid place-content-center text-tiny font-semibold text-gray-90">
<span
class="absolute -top-0.5 left-1/2 -translate-x-1/2 w-1.5 h-1.5 rounded-full ring-2 ring-white"
:class="progressBgClass"
aria-hidden
/>
<div class="absolute inset-0 grid place-content-center text-tiny font-semibold text-gray-90 ">
{{ ringValue(lp.progress) }}%
</div>
</div>
Expand All @@ -112,26 +109,55 @@ const dateText = computed(() => {
</span>
</div>

<div v-if="canEdit" class="ml-auto flex items-center gap-4">
<div v-if="canEdit" class="ml-auto flex items-center gap-3">
<button class="opacity-80 hover:opacity-100" :title="t('Reports')" :aria-label="t('Reports')" @click="emit('report', lp)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M4 19h16M6 17V7m6 10V5m6 12v-8" stroke-width="1.7" stroke-linecap="round"/>
</svg>
<i class="mdi mdi-chart-box-outline text-xl" />
</button>

<button class="opacity-80 hover:opacity-100" :title="t('Build')" :aria-label="t('Build')" @click="emit('build', lp)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="9" y="9" width="10" height="10" rx="2" stroke-width="1.7"/>
<rect x="5" y="5" width="10" height="10" rx="2" stroke-width="1.7"/>
</svg>
<button class="opacity-80 hover:opacity-100" :title="t('Visibility')" :aria-label="t('Visibility')" @click="emit('toggle-visible', lp)">
<i class="mdi mdi-eye-outline text-xl" />
</button>

<button class="opacity-80 hover:opacity-100" :title="t('Visibility')" :aria-label="t('Visibility')" @click="emit('toggle-visible', lp)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12Z" stroke-width="1.7"/>
<circle cx="12" cy="12" r="3" stroke-width="1.7"/>
</svg>
<button
class="row-start-1 col-start-3 opacity-70 hover:opacity-100"
:title="t('Settings')"
:aria-label="t('Settings')"
@click="emit('settings', lp)"
>
<i class="mdi mdi-cog-outline text-xl" />
</button>
<button
v-if="canExportScorm"
class="row-start-1 col-start-4 opacity-70 hover:opacity-100"
:title="t('SCORM Export')"
:aria-label="t('SCORM Export')"
@click="emit('export-scorm', lp)"
>
<i class="mdi mdi-archive-arrow-down text-xl" />
</button>
<div class="relative w-8 h-8">
<BaseDropdownMenu v-if="canEdit"
:dropdown-id="`card-${lp.iid}`"
class="absolute"
>
<template #button>
<span
class="w-8 h-8 grid place-content-center rounded-lg border border-gray-25 hover:bg-gray-15 cursor-pointer"
:title="t('More')" :aria-label="t('More')"
>
<i class="mdi mdi-dots-vertical text-lg" aria-hidden></i>
</span>
</template>
<template #menu>
<div class="absolute right-0 w-44 bg-white border border-gray-25 rounded-xl shadow-xl p-1 z-10 mb-2" style="bottom: calc(-100% + 2.5rem)">
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="emit('open', lp)">{{ t('Open') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="emit('toggle-publish', lp)">{{ t('Publish / Unpublish') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="emit('build', lp)">{{ t('Edit items (Build)') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15 text-danger" @click="emit('delete', lp)">{{ t('Delete') }}</button>
</div>
</template>
</BaseDropdownMenu>
</div>
</div>
</div>
</div>
Expand Down
50 changes: 30 additions & 20 deletions assets/vue/components/lp/LpCategorySection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { computed, ref, watch, onMounted } from "vue"
import { useRoute } from "vue-router"
import Draggable from "vuedraggable"
import LpCardItem from "./LpCardItem.vue"
import BaseDropdownMenu from "../basecomponents/BaseDropdownMenu.vue"
import lpService from "../../services/lpService"
import { useI18n } from "vue-i18n"

Expand All @@ -13,12 +14,13 @@ const props = defineProps({
category: { type: Object, required: true },
list: { type: Array, default: () => [] },
canEdit: { type: Boolean, default: false },
canExportScorm: { type: Boolean, default: false },
ringDash: { type: Function, required: true },
ringValue: { type: Function, required: true },
})
const emit = defineEmits([
"open","edit","report","settings","build",
"toggle-visible","toggle-publish","delete",
"toggle-visible","toggle-publish","delete","export-scorm",
"reorder"
])

Expand Down Expand Up @@ -105,25 +107,32 @@ function onChangeCat() {

<div class="flex items-center gap-2">
<div class="text-tiny text-gray-50">{{ localList.length }} {{ t('Lessons') }}</div>
<details v-if="canEdit" class="relative z-30">
<summary
class="list-none w-8 h-8 grid place-content-center rounded-lg border border-gray-25 hover:bg-gray-15 cursor-pointer"
:title="t('Category options')" :aria-label="t('Category options')"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="5" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="12" cy="19" r="1.6"/>
</svg>
</summary>
<div class="absolute right-0 top-full mt-2 w-60 bg-white border border-gray-25 rounded-xl shadow-xl p-1 z-50" @mousedown.stop @click.stop>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatEdit">{{ t('Edit category') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatAddUsers">{{ t('Add users to category') }}</button>
<div class="my-1 h-px bg-gray-15"></div>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatToggleVisibility">{{ t('Toggle visibility') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatTogglePublish">{{ t('Publish / Unpublish') }}</button>
<div class="my-1 h-px bg-gray-15"></div>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15 text-danger" @click="onCatDelete">{{ t('Delete') }}</button>
</div>
</details>

<BaseDropdownMenu v-if="canEdit"
:dropdown-id="`category-${category.iid}`"
class="relative z-30"
>
<template #button>
<span
class="list-none w-8 h-8 grid place-content-center rounded-lg border border-gray-25 hover:bg-gray-15 cursor-pointer"
:title="t('Category options')"
:aria-label="t('Category options')"
>
<i class="mdi mdi-dots-vertical text-lg" aria-hidden></i>
</span>
</template>
<template #menu>
<div class="absolute right-0 top-full mt-2 w-60 bg-white border border-gray-25 rounded-xl shadow-xl p-1 z-50">
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatEdit">{{ t('Edit category') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatAddUsers">{{ t('Add users to category') }}</button>
<div class="my-1 h-px bg-gray-15"></div>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatToggleVisibility">{{ t('Toggle visibility') }}</button>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatTogglePublish">{{ t('Publish / Unpublish') }}</button>
<div class="my-1 h-px bg-gray-15"></div>
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15 text-danger" @click="onCatDelete">{{ t('Delete') }}</button>
</div>
</template>
</BaseDropdownMenu>

<button
v-if="localList.length"
Expand Down Expand Up @@ -167,6 +176,7 @@ function onChangeCat() {
:canEdit="canEdit"
:ringDash="ringDash"
:ringValue="ringValue"
:canExportScorm="canExportScorm"
@open="$emit('open', element)"
@edit="$emit('edit', element)"
@report="$emit('report', element)"
Expand Down
Loading
Loading