datatracker/client/agenda/AgendaFilter.vue
Nicolas Giard aa9490faf6
feat(ui): new dynamic agenda view (#4086)
* feat: agenda page in vue (wip)

* feat: scroll to agenda day

* fix: vue 3 composition api + eslint settings

* fix: agenda day scroll match indicator

* fix: convert vite deps to yarn

* fix: missing lodash + legacy build step

* fix: agenda - move calendar into drawer

* fix: improve agenda filter UI

* fix: download ics + move agenda into own component

* feat: use fullcalendar for agenda calendar view (wip)

* feat: add events to agenda calendar

* feat: agenda filter UI improvements

* feat: agenda add to calendar dropdown

* feat: agenda calendar filter + timezone + event coloring

* feat: agenda calendar color improvements

* chore: exclude dist-neue from git

* feat: agenda calendar event modal

* fix: rebuild yarn deps

* chore: add run migration task to vscode

* fix: agenda buttons display flag

* feat: agenda event modal component

* feat: show calendar event quick info on hover

* fix: clear calendar quick info on timezone change

* feat: agenda list view improvements

* feat: agenda list row coloring

* feat: agenda list note

* feat: agenda list icons for office hours + hackathon

* fix: agenda top links

* refactor: use pinia as store for agenda components

* feat: agenda jump to now

* fix: agenda mobile improvements

* feat: agenda search

* feat: agenda search improvements

* feat: agenda event recordings buttons for post-meeting

* fix: agenda switch to meeting timezone on load

* feat: agenda pre & live session buttons

* fix: remove agenda utc + personalize links in top menu

* feat: add pre-vue loading state on page load

* feat: filter from agenda picker mode

* fix: agenda UI improvements

* fix: django-vite non-dev mode

* chore: update yarn dependencies for vue + vite

* feat: agenda settings panel + UI improvements

* feat: agenda settings colors + import/export feature

* feat: agenda color assignments + responsive UI improvements

* feat: agenda realtime red line + debug datetime offset

* feat: agenda add aria labels for settings

* feat: add new agenda path + pages/menu

* fix: bring base/menu.html up to main

* fix: agenda various fixes

* test: add new agenda item to meetings menu for item count

* chore: restore devcontainer extensions list

* fix: agenda UI improvements + montserrat default font

* feat: agenda bolder text + hide event icons options

* feat: agenda warning badge

* fix: agenda various UI improvements + intersectionObserver fix

* feat: agenda floorplan page + various UI improvements

* feat: agenda floor plan pin

* feat: view floor plan room from agenda

* feat: agenda floor plan mobile optimization

* feat: adjust calendar options + default calendar view in settings

* feat: agenda persist picked events + change base font only on new agenda page

* feat: agenda mobile view optimizations

* fix: add .vite to cached volumes

* fix: mobile view for filters, calendar, settings panels

* test: upgrade cypress existing tests to work on bs5 + update dependencies

* fix: use named url patterns to avoid hardcoded URLs. Add rudimentary test coverage for the neue views.

Co-authored-by: Robert Sparks <rjsparks@nostrum.com>
2022-07-13 16:20:23 -05:00

351 lines
8.2 KiB
Vue

<template lang="pug">
n-drawer(v-model:show='state.isShown', placement='bottom', :height='state.drawerHeight')
n-drawer-content.agenda-personalize
template(#header)
span Filter Areas + Groups
.agenda-personalize-actions
n-button.me-2(
ghost
color='gray'
strong
@click='clearFilter'
)
i.bi.bi-slash-square.me-2
span Clear Selection
n-button.me-2(
ghost
color='gray'
strong
@click='cancelFilter'
)
i.bi.bi-x-square.me-2
span Cancel
n-button(
primary
type='success'
strong
@click='saveFilter'
)
i.bi.bi-check-circle.me-2
span Apply
.agenda-personalize-content
.agenda-personalize-category(
v-for='(cat, idx) of agendaStore.categories'
:key='`cat-` + idx'
:class='{ "col-auto": (cat.length <= 2) }'
)
.agenda-personalize-area(
v-for='area of cat'
:key='area.keyword'
)
.agenda-personalize-areamain
button(
v-if='area.keyword'
@click='toggleFilterArea(area.keyword)'
)
i.bi.bi-diagram-3
span {{area.label}}
.agenda-personalize-groups
button.agenda-personalize-group(
v-for='group of area.children'
:key='group.keyword'
:class='{"is-bof": group.is_bof, "is-checked": state.pendingSelection.includes(group.keyword)}'
@click='toggleFilterGroup(group.keyword)'
)
span {{group.label}}
n-popover(
v-if='group.is_bof'
trigger='hover'
:width='250'
)
template(#trigger)
span.badge BoF
span #[a(href='https://www.ietf.org/how/bofs/', target='_blank') Birds of a Feather] sessions (BoFs) are initial discussions about a particular topic of interest to the IETF community.
</template>
<script setup>
import { reactive, ref, unref, watch } from 'vue'
import intersection from 'lodash/intersection'
import difference from 'lodash/difference'
import union from 'lodash/union'
import {
NButton,
NDrawer,
NDrawerContent,
NPopover,
useMessage
} from 'naive-ui'
import { useAgendaStore } from './store'
// STORES
const agendaStore = useAgendaStore()
// STATE
const state = reactive({
drawerHeight: 650,
isShown: false,
pendingSelection: []
})
const message = useMessage()
// WATCHERS
watch(() => agendaStore.filterShown, (newValue) => {
if (newValue) {
state.drawerHeight = window.innerHeight > 700 ? 650 : window.innerHeight - 50
state.pendingSelection = unref(agendaStore.selectedCatSubs)
}
state.isShown = newValue
})
watch(() => state.isShown, (newValue) => {
agendaStore.$patch({ filterShown: newValue })
})
// METHODS
function cancelFilter () {
state.isShown = false
state.pendingSelection = unref(agendaStore.selectedCatSubs)
}
function saveFilter () {
agendaStore.$patch({ selectedCatSubs: state.pendingSelection })
state.isShown = false
}
function clearFilter () {
state.pendingSelection = []
}
function toggleFilterArea (areaKeyword) {
const affectedGroups = []
let isAlreadySelected = false
// -> Find affected categories / subs
for (const cat of agendaStore.categories) {
for (const area of cat) {
if (area.keyword === areaKeyword) {
isAlreadySelected = intersection(area.children.map(s => s.keyword), state.pendingSelection).length === area.children.length
}
for (const group of area.children) {
if (group.toggled_by.includes(areaKeyword)) {
affectedGroups.push(group.keyword)
}
}
}
}
// -> Toggle depending on current state
state.pendingSelection = (isAlreadySelected) ? difference(state.pendingSelection, affectedGroups) : union(state.pendingSelection, affectedGroups)
}
function toggleFilterGroup (key) {
state.pendingSelection = state.pendingSelection.includes(key) ? state.pendingSelection.filter(k => k !== key) : [...state.pendingSelection, key]
const affectedGroups = []
for (const cat of agendaStore.categories) {
for (const area of cat) {
for (const group of area.children) {
if (group.toggled_by.includes(key)) {
affectedGroups.push(group.keyword)
}
}
}
}
if (affectedGroups.length > 0) {
state.pendingSelection = (!state.pendingSelection.includes(key)) ? difference(state.pendingSelection, affectedGroups) : union(state.pendingSelection, affectedGroups)
}
}
</script>
<style lang="scss">
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "../shared/breakpoints";
.agenda-personalize {
.n-drawer-header {
padding-top: 10px !important;
padding-bottom: 10px !important;
@media screen and (max-width: $bs5-break-sm) {
padding-left: 10px !important;
padding-right: 10px !important;
}
&__main {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
@media screen and (max-width: $bs5-break-sm) {
justify-content: center;
flex-wrap: wrap;
font-size: .9em;
}
}
}
&-actions {
@media screen and (max-width: $bs5-break-sm) {
flex: 0 1 100%;
margin-top: .75rem;
display: flex;
justify-content: center;
}
}
.n-drawer-body-content-wrapper {
@media screen and (max-width: $bs5-break-sm) {
padding: 10px !important;
}
}
&-category {
background-color: $gray-200;
padding: 5px;
border-radius: 10px;
&:nth-child(2) {
background-color: $blue-100;
.agenda-personalize-areamain {
button {
color: $blue-600;
}
}
.agenda-personalize-groups {
background-color: lighten($blue-100, 7%);
}
}
&:nth-child(3) {
background-color: $orange-100;
.agenda-personalize-areamain {
button {
color: $orange-600;
}
}
.agenda-personalize-groups {
background-color: lighten($orange-100, 7%);
}
}
& + & {
margin-top: 10px;
}
}
&-area {
display: flex;
& + & {
margin-top: 5px;
}
}
&-areamain {
flex: 0 1 200px;
padding-right: 5px;
@media screen and (max-width: $bs5-break-sm) {
flex-basis: 60px;
}
button {
width: 100%;
height: 100%;
border-radius: 5px;
border: 1px solid #FFF;
background-color: #FFF;
color: $gray-600;
box-shadow: 1px 1px 0px 0px rgba(0,0,0,.1);
transition: background-color .5s ease;
position: relative;
> .bi {
margin-right: .5rem;
}
@media screen and (max-width: $bs5-break-sm) {
font-size: .8em;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> .bi {
margin-right: 0;
}
}
&:hover {
background-color: rgba(255,255,255,.4);
}
&:active {
box-shadow: none;
background-color: #FFF;
}
}
}
&-groups {
background-color: $gray-100;
padding: 0;
border-radius: 5px;
flex: 1;
display: flex;
flex-wrap: wrap;
}
&-group {
display: flex;
align-items: center;
padding: 5px 8px;
position: relative;
border: none;
border-left: 1px solid #FFF;
border-right: 1px solid rgba(0,0,0,.1);
background-color: rgba(255,255,255,.7);
color: $gray-600;
margin-right: 0px;
@media screen and (max-width: $bs5-break-sm) {
font-size: .9em;
}
&:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
&:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
&.is-bof {
border-top: 1px dotted $teal-300;
border-bottom: 2px solid $teal-300;
border-right: 2px solid $teal-300;
}
&.is-checked {
background-color: $blue;
color: #FFF;
}
.badge {
font-size: 10px;
background-color: $teal;
margin-left: 5px;
}
}
}
</style>