datatracker/client/agenda/AgendaSettings.vue
Nicolas Giard 9d055f8dd8
test: add agenda-neue cypress tests (#4293)
* fix: move vue app to parent dir

* test: prepare vue for cypress testing

* test: add basic cypress test for vue app

* test: add agenda-neue header tests

* test: meeting generator for cypress tests (wip)

* test: agenda-neue meeting nav + settings button

* test: agenda-neue check settings button position against viewport size

* test: agenda-neue fix viewport + add schedule list header tests

* test: agenda-neue timezone tests + future meeting template

* test: agenda-neue add test floor plan image fixtures

* test: floor-plan-neue - handle floor plan images

* test: floor-plan-neue room selection + pin drop tests

* test: floor-plan-neue - test all viewports

* test: floor-plan-neue add missing header tests

* test: agenda-neue - generate areas + groups

* test: agenda-neue - test filter areas + groups dialog

* test: agenda-neue code cleanup + generate schedule (wip)

* test: agenda-neue - generate schedule (wip)

* test: agenda-neue - generate schedule + test settings dialog (wip)

* test: agenda-neue - settings timezone controls

* test: agenda-neue - settings toggles + agenda table headers tests

* test: agenda-neue - table events + memory fixes

* test - agenda-neue - table events remaining columns tests

* test: agenda-neue - meeting materials dialog + future schedule list table buttons

* test: agenda-neue - remove skips

* test: agenda-neue - search

* test: agenda-neue - remove skips

* test: agenda-neue - use random seed for deterministic results

* test: agenda-neue - set constant clock

* test: add percy to cypress + upgrade cypress

* test: agenda-neue - jump to day tests

* test: agenda-neue - add to calendar tests

* test: agenda-neue - add pick sessions tests

* test: agenda-neue - calendar view tests

* test: agenda-neue - color assignment tests

* test: agenda-neue - skip test not supported in firefox/safari

* test: agenda-neue - live sessions tests

* test: agenda-neue - smaller screens tests

* chore: update workflows to handle modern + legacy cypress tests

* test: fix legacy agenda weekview test + cypress legacy config
2022-09-07 14:14:12 -04:00

486 lines
13 KiB
Vue

<template lang="pug">
n-drawer(v-model:show='isShown', placement='right', :width='panelWidth')
n-drawer-content.agenda-settings
template(#header)
span Agenda Settings
.agenda-settings-actions.d-flex.justify-content-end
n-dropdown(
:options='actionOptions'
size='large'
:show-arrow='true'
trigger='click'
@select='actionClick'
)
n-button.me-2(
ghost
color='#6c757d'
strong
)
i.bi.bi-three-dots-vertical
n-button(
ghost
color='gray'
strong
@click='close'
)
i.bi.bi-x-lg.me-2
span Close
.agenda-settings-content
n-divider(title-placement='left')
i.bi.bi-globe.me-2
small Timezone
n-button-group.mt-2(style='justify-content: stretch; width: 100%;')
n-button(
style='flex-grow: 1;'
:type='agendaStore.isTimezoneMeeting ? `primary` : `default`'
@click='setTimezone(`meeting`)'
) Meeting
n-button(
style='flex-grow: 1;'
:type='agendaStore.isTimezoneLocal ? `primary` : `default`'
@click='setTimezone(`local`)'
) Local
n-button(
style='flex-grow: 1;'
:type='agendaStore.timezone === `UTC` ? `primary` : `default`'
@click='setTimezone(`UTC`)'
) UTC
n-select.mt-2(
v-model:value='agendaStore.timezone'
:options='timezones'
placeholder='Select Time Zone'
filterable
@update:value='() => { agendaStore.persistMeetingPreferences() }'
)
n-divider(title-placement='left')
i.bi.bi-sliders.me-2
small Display
//- .d-flex.align-items-center.mt-3
//- n-switch.me-3(v-model:value='agendaStore.listDayCollapse', disabled)
//- span.small Collapse Days by Default
.d-flex.align-items-center.mt-3
n-switch.me-3(
v-model:value='agendaStore.colorLegendShown'
aria-label='Display Color Legend'
)
span.small.me-2 Display Color Legend
n-popover
template(#trigger)
i.bi.bi-info-circle
span Only displayed when a color is assigned to at least 1 event.
.d-flex.align-items-center.mt-3
n-switch.me-3(
v-model:value='agendaStore.infoNoteShown'
aria-label='Display Current Meeting Info Note'
)
span.small.me-2 Display Current Meeting Info Note
n-popover
template(#trigger)
i.bi.bi-info-circle
span Any update to the note will result in this setting being turned back on.
.d-flex.align-items-center.mt-3
n-switch.me-3(
v-model:value='agendaStore.eventIconsShown'
aria-label='Display Event Icons'
)
span.small Display Event Icons
.d-flex.align-items-center.mt-3
n-switch.me-3(
v-model:value='agendaStore.floorIndicatorsShown'
aria-label='Display Floor Indicators'
)
span.small Display Floor Indicators
.d-flex.align-items-center.mt-3
n-switch.me-3(
v-model:value='agendaStore.areaIndicatorsShown'
aria-label='Display Group Area Indicators'
)
span.small.me-2 Display Group Area Indicators
n-popover
template(#trigger)
i.bi.bi-info-circle
span Will not be shown on smaller screens, regardless of this setting.
.d-flex.align-items-center.mt-3
n-switch.me-3(
v-model:value='agendaStore.redhandShown'
aria-label='Display Realtime Red Line'
)
span.small.me-2 Display Realtime Red Line
n-popover
template(#trigger)
i.bi.bi-info-circle
span Only shown during live events. Updated every 5 seconds.
.d-flex.align-items-center.mt-3
n-switch.me-3(
v-model:value='agendaStore.bolderText'
aria-label='Use Bolder Text'
)
span.small.me-2 Use Bolder Text
n-divider(title-placement='left')
i.bi.bi-calendar3.me-2
small Calendar View
span.small.me-2 Default View
n-button-group.mt-2(style='justify-content: stretch; width: 100%;')
n-button(
style='flex-grow: 1;'
:type='agendaStore.defaultCalendarView === `week` ? `primary` : `default`'
@click='agendaStore.defaultCalendarView = `week`'
) Week
n-button(
style='flex-grow: 1;'
:type='agendaStore.defaultCalendarView === `day` ? `primary` : `default`'
@click='agendaStore.defaultCalendarView = `day`'
) Day
n-divider#agenda-settings-colors-header(title-placement='left')
i.bi.bi-palette.me-2
small Custom Colors / Tags
.d-flex.align-items-center.mt-3(v-for='(cl, idx) of state.colors')
n-color-picker.me-3(
:modes='[`hex`]'
:render-label='() => {}'
:show-alpha='false'
size='small'
:swatches='swatches'
v-model:value='cl.hex'
:aria-label='"Color for " + cl.tag'
)
n-input(
type='text'
v-model:value='cl.tag'
:placeholder='"Color " + (idx + 1)'
:aria-label='"Color Name " + (idx + 1)'
)
.agenda-settings-note.mt-3 #[strong Note:] You can hide a color by entering an empty name.
.agenda-settings-debug(v-if='agendaStore.debugTools')
n-divider(title-placement='left')
i.bi.bi-clock-history.me-2
small Override Local DateTime
n-date-picker(
v-model:value='state.currentDateTime'
type='datetime'
style='width: 100%;'
aria-label='Override Local DateTime'
)
template(#date-icon)
i.bi.bi-calendar-check
.agenda-settings-calcoffset
span Calculated Offset: {{ calcOffset }}
</template>
<script setup>
import { computed, h, onMounted, ref, reactive, watch } from 'vue'
import { DateTime } from 'luxon'
import cloneDeep from 'lodash/cloneDeep'
import debounce from 'lodash/debounce'
import { fileOpen } from 'browser-fs-access'
import FileSaver from 'file-saver'
import {
NButton,
NButtonGroup,
NColorPicker,
NDatePicker,
NDivider,
NDrawer,
NDrawerContent,
NDropdown,
NInput,
NPopover,
NSelect,
NSwitch,
useMessage
} from 'naive-ui'
import { useAgendaStore } from './store'
import timezones from '../shared/timezones'
// MESSAGE PROVIDER
const message = useMessage()
// STORES
const agendaStore = useAgendaStore()
// STATE
const isShown = ref(false)
const state = reactive({
currentDateTime: null,
colors: []
})
const swatches = [
'#0d6efd',
'#6610f2',
'#6f42c1',
'#d63384',
'#dc3545',
'#fd7e14',
'#ffc107',
'#198754',
'#20c997',
'#0dcaf0',
'#adb5bd',
'#000000'
]
const actionOptions = [
{
label: 'Export Configuration...',
key: 'export',
icon: () => h('i', { class: 'bi bi-box-arrow-down' })
},
{
label: 'Import Configuration...',
key: 'import',
icon: () => h('i', { class: 'bi bi-box-arrow-in-down' })
},
{
type: 'divider',
key: 'divider1'
},
{
label: 'Clear Color Assignments',
key: 'clearColors',
icon: () => h('i', { class: 'bi bi-palette' })
},
{
type: 'divider',
key: 'divider1'
},
{
label: 'Toggle Debugging Controls',
key: 'toggleDebug',
icon: () => h('i', { class: 'bi bi-tools' })
}
]
// COMPUTED
const calcOffset = computed(() => {
return agendaStore.nowDebugDiff ? JSON.stringify(agendaStore.nowDebugDiff.toObject()) : 'None'
})
const panelWidth = computed(() => {
return agendaStore.viewport > 500 ? 500 : agendaStore.viewport
})
// WATCHERS
watch(() => agendaStore.settingsShown, (newValue) => {
isShown.value = newValue
})
watch(isShown, (newValue) => {
agendaStore.$patch({ settingsShown: newValue })
})
watch(() => agendaStore.infoNoteShown, () => {
agendaStore.persistMeetingPreferences()
})
watch(() => state.colors, debounce(() => {
agendaStore.$patch({
colors: cloneDeep(state.colors)
})
}, 1000), { deep: true })
watch(() => state.currentDateTime, (newValue) => {
if (!newValue) {
agendaStore.$patch({ nowDebugDiff: null })
} else {
const newDiff = DateTime.now().diff(DateTime.fromMillis(newValue))
if (newDiff.as('minutes') <= 2 && newDiff.as('minutes') >= -2) { // Set to 0 if almost current time
agendaStore.$patch({ nowDebugDiff: null })
} else {
agendaStore.$patch({ nowDebugDiff: newDiff })
}
}
})
// METHODS
function close () {
isShown.value = false
}
async function actionClick (key) {
switch (key) {
/**
* EXPORT CONFIGURATION
*/
case 'export': {
try {
const configBlob = new Blob([
JSON.stringify({
areaIndicatorsShown: agendaStore.areaIndicatorsShown,
bolderText: agendaStore.bolderText,
colorLegendShown: agendaStore.colorLegendShown,
colors: agendaStore.colors,
defaultCalendarView: agendaStore.defaultCalendarView,
eventIconsShown: agendaStore.eventIconsShown,
floorIndicatorsShown: agendaStore.floorIndicatorsShown,
listDayCollapse: agendaStore.listDayCollapse,
redhandShown: agendaStore.redhandShown
}, null, 2)
], {
type: 'application/json;charset=utf-8'
})
FileSaver.saveAs(configBlob, 'agenda-settings.json')
} catch (err) {
console.warn(err)
message.error('Failed to generate JSON config for download.')
}
break
}
/**
* IMPORT CONFIGURATION
*/
case 'import': {
try {
const blob = await fileOpen({
mimeTypes: ['application/json'],
extensions: ['.json'],
startIn: 'downloads',
excludeAcceptAllOption: true
})
const configRaw = await blob.text()
const configJson = JSON.parse(configRaw)
if (!Array.isArray(configJson.colors) || configJson.colors.length !== agendaStore.colors.length) {
throw new Error('Config contains invalid colors array.')
}
agendaStore.$patch({
areaIndicatorsShown: configJson.areaIndicatorsShown === true,
bolderText: configJson.bolderText === true,
colorLegendShown: configJson.colorLegendShown === true,
colors: configJson.colors.map(c => ({
hex: c.hex || '#FF0000',
tag: c.tag || 'Unknown Color'
})),
defaultCalendarView: configJson.defaultCalendarView === 'day' ? 'day' : 'week',
eventIconsShown: configJson.eventIconsShown === true,
floorIndicatorsShown: configJson.floorIndicatorsShown === true,
listDayCollapse: configJson.listDayCollapse === true,
redhandShown: configJson.redhandShown === true
})
state.colors = cloneDeep(agendaStore.colors)
message.success('Config imported successfully.')
} catch (err) {
console.warn(err)
message.error('Failed to import JSON config.')
}
break
}
/**
* CLEAR COLOR ASSIGNMENTS
*/
case 'clearColors': {
agendaStore.colorAssignments = {}
agendaStore.persistMeetingPreferences()
message.info('All color assignments cleared.')
close()
break
}
/**
* TOGGLE DEBUG TOOLS
*/
case 'toggleDebug': {
agendaStore.$patch({ debugTools: !agendaStore.debugTools })
break
}
}
}
function setTimezone (tz) {
switch (tz) {
case 'meeting':
agendaStore.$patch({ timezone: agendaStore.meeting.timezone })
break
case 'local':
agendaStore.$patch({ timezone: DateTime.local().zoneName })
break
default:
agendaStore.$patch({ timezone: tz })
break
}
agendaStore.persistMeetingPreferences()
}
// MOUNTED
onMounted(() => {
state.currentDateTime = (agendaStore.nowDebugDiff ? DateTime.now().minus(agendaStore.nowDebugDiff) : DateTime.now()).toMillis()
state.colors = cloneDeep(agendaStore.colors)
})
</script>
<style lang="scss">
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
.agenda-settings {
.n-drawer-header {
padding-top: 10px !important;
padding-bottom: 10px !important;
&__main {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
}
.n-divider {
margin-top: 24px;
margin-bottom: 12px;
&:first-child {
margin-top: 0;
}
}
.n-color-picker {
width: 40px;
}
&-note {
background-color: $gray-100;
border-bottom: 1px solid $gray-200;
border-right: 1px solid $gray-200;
border-radius: 5px;;
padding: 6px 12px;
font-size: .8rem;
color: $gray-700;
text-shadow: 1px 1px 0 #FFF;
}
&-calcoffset {
padding: 5px 0 0 12px;
font-size: 11px;
}
&-debug {
margin-top: 25px;
border: 2px dotted $pink-500;
border-radius: 12px;
padding: 15px;
position: relative;
&::before {
content: 'DEBUG';
position: absolute;
background-color: $pink-500;
color: #FFF;
top: -13px;
right: 25px;
font-size: 12px;
font-weight: 500;
padding: 3px 8px;
border-radius: 12px;
}
}
}
</style>