Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

T#2218/collapse groups #3072

Merged
merged 14 commits into from
Jan 10, 2025
29 changes: 25 additions & 4 deletions dashboard/src/skills-display/components/progress/SkillProgress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.
<script setup>
import SkillProgressNameRow from '@/skills-display/components/progress/skill/SkillProgressNameRow.vue'
import { useRoute } from 'vue-router'
import { computed, ref } from 'vue'
import { computed, ref, watch, onMounted } from 'vue'
import { useSkillsDisplayInfo } from '@/skills-display/UseSkillsDisplayInfo.js'
import SkillsSummaryCards from '@/skills-display/components/progress/SkillsSummaryCards.vue'
import MarkdownText from '@/common-components/utilities/markdown/MarkdownText.vue'
Expand All @@ -29,6 +29,8 @@ import CatalogImportStatus from '@/skills-display/components/progress/CatalogImp
import PartialPointsAlert from '@/skills-display/components/skill/PartialPointsAlert.vue'
import SkillVideo from '@/skills-display/components/progress/SkillVideo.vue';
import dayjs from 'dayjs';
import {useStorage} from "@vueuse/core";
import {useSkillsAnnouncer} from "@/common-components/utilities/UseSkillsAnnouncer.js";

const props = defineProps({
skill: Object,
Expand Down Expand Up @@ -65,10 +67,16 @@ const props = defineProps({
type: Boolean,
default: false,
required: false
},
expandGroups: {
type: Boolean,
default: false,
required: false
}
})
const emit = defineEmits(['add-tag-filter', 'points-earned'])
const emit = defineEmits(['add-tag-filter', 'points-earned', 'reset-group-expansion'])
const route = useRoute()
const announcer = useSkillsAnnouncer()
const skillsDisplayInfo = useSkillsDisplayInfo()
const attributes = useSkillsDisplayAttributesState()

Expand Down Expand Up @@ -120,6 +128,18 @@ const pointsEarned = (pts) => {
}

const isSkillComplete = computed(() => props.skill && props.skill.meta && props.skill.meta.complete)
const storageKey = computed(() => props.skill.projectId + '-' + props.skill.skillId + '-expanded')
const expanded = useStorage(storageKey.value, true)
const toggleRow = () => {
expanded.value = !expanded.value;
announcer.polite(`Group ${props.skill.skill} has been ${expanded.value ? 'expanded' : 'collapsed'}`);
emit('reset-group-expansion')
}
watch(() => props.expandGroups, (newValue) => {
if(newValue !== null && props.skill.type === 'SkillsGroup') {
expanded.value = newValue
}
})
</script>

<template>
Expand All @@ -142,11 +162,12 @@ const isSkillComplete = computed(() => props.skill && props.skill.meta && props.
}}</strong> {{ attributes.projectDisplayName.toLowerCase() }}! Happy playing!!
</Message>


<skill-progress-name-row
:skill="skill"
:badge-is-locked="badgeIsLocked"
:to-route="toRoute"
:is-expanded="expanded"
@toggle-row="toggleRow"
:child-skill-highlight-string="childSkillHighlightString"
:type="type" />
<div class="mt-1">
Expand Down Expand Up @@ -226,7 +247,7 @@ const isSkillComplete = computed(() => props.skill && props.skill.meta && props.
</div>
</div>

<div v-if="skill.isSkillsGroupType && childSkillsInternal" class="ml-4 mt-3">
<div v-if="skill.isSkillsGroupType && childSkillsInternal && expanded" class="ml-4 mt-3">
<div v-for="(childSkill, index) in childSkillsInternal"
:key="`group-${skill.skillId}_skill-${childSkill.skillId}`"
:id="`skillRow-${childSkill.skillId}`"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ import { useSkillsDisplayAttributesState } from '@/skills-display/stores/UseSkil
import { useRoute } from 'vue-router'
import { useThemesHelper } from '@/components/header/UseThemesHelper.js'

// subject: {
// type: Object,
// required: true
// },
const props = defineProps({
showDescriptions: {
type: Boolean,
Expand Down Expand Up @@ -211,16 +207,28 @@ const skillsToShow = computed(() => {
})
resultSkills = filteredRes
}
// this.skillsInternal = resultSkills
return resultSkills
})

const showDescriptionsInternal = ref(false)
const isLastViewedScrollSupported = computed(() => {
return !parentFrame.parentFrame || parentFrame.isLastViewedScrollSupported
})
// this.lastViewedButtonDisabled = resultSkills.findIndex((i) => i.isLastViewed || (i.children && i.children.findIndex((c) => c.isLastViewed) >= 0)) < 0

const expandGroups = ref(null)
const hasGroups = computed(() => {
return !!skillsInternal.value.find(it => it.type === 'SkillsGroup')
})

const expandAllGroups = (() => {
expandGroups.value = true
})
const collapseAllGroups = (() => {
expandGroups.value = false
})
const resetGroupExpansion = (() => {
expandGroups.value = null
})
</script>

<template>
Expand Down Expand Up @@ -275,12 +283,18 @@ const isLastViewedScrollSupported = computed(() => {


<div class="" data-cy="skillDetailsToggle">
<div class="flex flex-row align-content-center">
<span class="text-muted pr-1 align-content-center">{{ attributes.skillDisplayName }} Details:</span>
<InputSwitch v-model="showDescriptionsInternal"
@change="onDetailsToggle"
:aria-label="`Show ${attributes.skillDisplayName} Details`"
data-cy="toggleSkillDetails" />
<div class="flex flex-row flex-wrap align-content-center">
<div class="flex flex-wrap mr-3 gap-2" v-if="!route.params.badgeId && hasGroups">
<SkillsButton label="Expand Groups" icon="fas fa-plus" size="small" data-cy="expandGroupsButton" @click="expandAllGroups"></SkillsButton>
<SkillsButton label="Collapse Groups" icon="fas fa-minus" size="small" data-cy="collapseGroupsButton" @click="collapseAllGroups"></SkillsButton>
</div>
<div class="flex">
<span class="text-muted pr-1 align-content-center">{{ attributes.skillDisplayName }} Details:</span>
<InputSwitch v-model="showDescriptionsInternal"
@change="onDetailsToggle"
:aria-label="`Show ${attributes.skillDisplayName} Details`"
data-cy="toggleSkillDetails" />
</div>
</div>
</div>

Expand Down Expand Up @@ -327,6 +341,8 @@ const isLastViewedScrollSupported = computed(() => {
:ref="`skillProgress${skill.skillId}`"
:skill="skill"
:type="type"
:expand-groups="expandGroups"
@reset-group-expansion="resetGroupExpansion"
:enable-drill-down="true"
:show-description="showDescriptionsInternal"
:data-cy="`skillProgress_index-${index}`"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
<script setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
import dayjs from 'dayjs'
import { useNumberFormat } from '@/common-components/filter/UseNumberFormat.js'
import { useTimeUtils } from '@/common-components/utilities/UseTimeUtils.js'
Expand All @@ -36,8 +36,13 @@ const props = defineProps({
childSkillHighlightString: {
type: String,
default: ''
},
isExpanded: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['toggle-row']);
const numFormat = useNumberFormat()
const timeUtils = useTimeUtils()
const appConfig = useAppConfig()
Expand Down Expand Up @@ -129,11 +134,10 @@ const skillId = computed(() => {
<div class="sd-theme-primary-color font-medium flex">
<div class="mr-1">
<i v-if="skill.isSkillsGroupType" class="fas fa-layer-group"></i>
<i v-if="!skill.copiedFromProjectId && !skill.isSkillsGroupType"
class="fas fa-graduation-cap text-color-secondary"></i>
<i v-if="!skill.copiedFromProjectId && !skill.isSkillsGroupType" class="fas fa-graduation-cap text-color-secondary"></i>
<i v-if="skill.copiedFromProjectId" class="fas fa-book text-secondary"></i>
</div>
<div class="">
<div>
<div v-if="skillDisplayInfo.isGlobalBadgePage.value">
<span class="font-italic text-color-secondary">{{ attributes.projectDisplayName }}:</span> {{ skill.projectName }}
</div>
Expand All @@ -148,6 +152,15 @@ const skillId = computed(() => {
<div v-else class="inline-block" data-cy="skillProgressTitle">
<highlighted-value :value="skill.skill" :filter="childSkillHighlightString" />
</div>
<SkillsButton :icon="!isExpanded ? 'fas fa-plus' : 'fas fa-minus'"
v-if="skill.isSkillsGroupType"
outlined
:aria-label="!isExpanded ? 'Expand Group' : 'Collapse Group'"
style="padding: 0.3rem 0.3rem 0.3rem 0.1rem;"
class="ml-2"
:data-cy="`toggleGroup-${skillId}`"
@click="emit('toggle-row')">
</SkillsButton>
</div>
</div>
<div v-if="skill.copiedFromProjectId" class="ml-2"
Expand Down
111 changes: 111 additions & 0 deletions e2e-tests/cypress/e2e/client-display/client-display_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,117 @@ describe('Client Display Tests', () => {
});
});

it('ability to expand and collapse groups from subject page', () => {
for(let x = 1; x <= 2; x++) {
cy.createSkillsGroup(1, 2, x);
let startingSkill = 11 * x
for(let y = startingSkill; y < startingSkill + 3; y++) {
cy.addSkillToGroup(1, 2, x, y, {
pointIncrement: 50,
numPerformToCompletion: 2
});
}
}

cy.cdVisit('/', true);
cy.cdClickSubj(1, 'Subject 2', false);

checkGroupSkillExistence(true)

cy.get('[data-cy=collapseGroupsButton]').click()
checkGroupSkillExistence(false)

cy.get('[data-cy=expandGroupsButton]').click()
checkGroupSkillExistence(true)

cy.get('[data-cy="toggleGroup-group1Subj2"]').click()
cy.get('#skillProgressTitleLink-skill11Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill12Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill13Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill22Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill23Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill24Subj2').should('exist');

cy.get('[data-cy="toggleGroup-group2Subj2"]').click()
checkGroupSkillExistence(false)

cy.get('[data-cy="toggleGroup-group1Subj2"]').click()
cy.get('#skillProgressTitleLink-skill11Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill12Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill13Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill22Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill23Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill24Subj2').should('not.exist');
});

it('group expansion state persists', () => {
for(let x = 1; x <= 2; x++) {
cy.createSkillsGroup(1, 2, x);
let startingSkill = 11 * x
for(let y = startingSkill; y < startingSkill + 3; y++) {
cy.addSkillToGroup(1, 2, x, y, {
pointIncrement: 50,
numPerformToCompletion: 2
});
}
}

cy.cdVisit('/subjects/subj2');

checkGroupSkillExistence(true)

cy.get('[data-cy=collapseGroupsButton]').click()
checkGroupSkillExistence(false)

cy.cdVisit('/subjects/subj2');
checkGroupSkillExistence(false)

cy.get('[data-cy=expandGroupsButton]').click()

cy.cdVisit('/subjects/subj2');
checkGroupSkillExistence(true)

cy.get('[data-cy="toggleGroup-group1Subj2"]').click()
cy.get('#skillProgressTitleLink-skill11Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill12Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill13Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill22Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill23Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill24Subj2').should('exist');

cy.cdVisit('/subjects/subj2');
cy.get('#skillProgressTitleLink-skill11Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill12Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill13Subj2').should('not.exist');
cy.get('#skillProgressTitleLink-skill22Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill23Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill24Subj2').should('exist');

cy.get('[data-cy="toggleGroup-group1Subj2"]').click()
cy.get('#skillProgressTitleLink-skill11Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill12Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill13Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill22Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill23Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill24Subj2').should('exist');

cy.cdVisit('/subjects/subj2');
cy.get('#skillProgressTitleLink-skill11Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill12Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill13Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill22Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill23Subj2').should('exist');
cy.get('#skillProgressTitleLink-skill24Subj2').should('exist');
});

const checkGroupSkillExistence = (exists) => {
for(let group = 1; group <= 2; group++) {
let startingSkill = 11 * group
for(let y = startingSkill; y < startingSkill + 3; y++) {
cy.get(`#skillProgressTitleLink-skill${y}Subj2`).should(exists ? 'exist' : 'not.exist');
}
}
}
});


Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading