* 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>
351 lines
8.2 KiB
Vue
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>
|