mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-07-16 09:38:51 +02:00
Merge branch 'refs/heads/main' into 3-semi-offline-room-finder
# Conflicts: # frontend/src/components/RoomOccupationOffline.vue # frontend/src/model/roomOccupancyList.ts
This commit is contained in:
8
.gitattributes
vendored
Normal file
8
.gitattributes
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
*.ts text eol=lf
|
||||
*.go text eol=lf
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
*.vue text eol=lf
|
||||
*.md text eol=lf
|
||||
*.css text eol=lf
|
||||
*.scss text eol=lf
|
@ -4,13 +4,19 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>HTWKalender</title>
|
||||
<meta name="description" content="Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.">
|
||||
<meta name="keywords" content="HTWK, calendar, iCal, dates, events, schedule">
|
||||
<meta
|
||||
name="description"
|
||||
content="Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="HTWK, calendar, iCal, dates, events, schedule"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||
<link rel="icon" type="image/svg+xml" href="/htwk.svg" />
|
||||
<link rel="mask-icon" href="/htwk-mask.svg" color="#00494c" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<meta name="theme-color" content="#1b2022">
|
||||
<meta name="theme-color" content="#1b2022" />
|
||||
<link
|
||||
id="theme-link"
|
||||
rel="stylesheet"
|
||||
|
2
frontend/package-lock.json
generated
2
frontend/package-lock.json
generated
@ -4477,6 +4477,7 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
@ -4486,6 +4487,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
|
||||
"integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"date-fns": "^3.0.0"
|
||||
}
|
||||
|
BIN
frontend/public/1280x720.png
Normal file
BIN
frontend/public/1280x720.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
frontend/public/390x844.png
Normal file
BIN
frontend/public/390x844.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
@ -3,15 +3,14 @@
|
||||
@each $name, $color in $colors {
|
||||
@for $i from 0 through 5 {
|
||||
@if ($i == 0) {
|
||||
--#{$name}-50:#{tint($color, (5 - $i) * 19%)};
|
||||
}
|
||||
@else {
|
||||
--#{$name}-#{$i * 100}:#{tint($color, (5 - $i) * 19%)};
|
||||
--#{$name}-50: #{tint($color, (5 - $i) * 19%)};
|
||||
} @else {
|
||||
--#{$name}-#{$i * 100}: #{tint($color, (5 - $i) * 19%)};
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 4 {
|
||||
--#{$name}-#{($i + 5) * 100}:#{shade($color, $i * 15%)};
|
||||
--#{$name}-#{($i + 5) * 100}: #{shade($color, $i * 15%)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
// core
|
||||
.p-component, .p-component * {
|
||||
.p-component,
|
||||
.p-component * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -17,7 +18,8 @@
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.p-disabled, .p-disabled * {
|
||||
.p-disabled,
|
||||
.p-disabled * {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
@ -66,7 +68,9 @@
|
||||
.p-connected-overlay {
|
||||
opacity: 0;
|
||||
transform: scaleY(0.8);
|
||||
transition: transform .12s cubic-bezier(0, 0, 0.2, 1), opacity .12s cubic-bezier(0, 0, 0.2, 1);
|
||||
transition:
|
||||
transform 0.12s cubic-bezier(0, 0, 0.2, 1),
|
||||
opacity 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.p-connected-overlay-visible {
|
||||
@ -77,7 +81,7 @@
|
||||
.p-connected-overlay-hidden {
|
||||
opacity: 0;
|
||||
transform: scaleY(1);
|
||||
transition: opacity .1s linear;
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
|
||||
/* Vue based overlay animations */
|
||||
@ -91,11 +95,13 @@
|
||||
}
|
||||
|
||||
.p-connected-overlay-enter-active {
|
||||
transition: transform .12s cubic-bezier(0, 0, 0.2, 1), opacity .12s cubic-bezier(0, 0, 0.2, 1);
|
||||
transition:
|
||||
transform 0.12s cubic-bezier(0, 0, 0.2, 1),
|
||||
opacity 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.p-connected-overlay-leave-active {
|
||||
transition: opacity .1s linear;
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
|
||||
/* Toggleable Content */
|
||||
@ -136,7 +142,8 @@
|
||||
transition-duration: $transitionDuration;
|
||||
}
|
||||
|
||||
.p-disabled, .p-component:disabled {
|
||||
.p-disabled,
|
||||
.p-component:disabled {
|
||||
opacity: $disabledOpacity;
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,8 @@
|
||||
color: $menuitemTextActiveColor;
|
||||
}
|
||||
|
||||
.p-menuitem-icon, .p-submenu-icon {
|
||||
.p-menuitem-icon,
|
||||
.p-submenu-icon {
|
||||
color: $menuitemIconActiveColor;
|
||||
}
|
||||
}
|
||||
@ -103,7 +104,8 @@
|
||||
color: $menuitemTextHoverColor;
|
||||
}
|
||||
|
||||
.p-menuitem-icon, .p-submenu-icon {
|
||||
.p-menuitem-icon,
|
||||
.p-submenu-icon {
|
||||
color: $menuitemTextHoverColor;
|
||||
}
|
||||
}
|
||||
@ -117,7 +119,8 @@
|
||||
color: $menuitemTextHoverColor;
|
||||
}
|
||||
|
||||
.p-menuitem-icon, .p-submenu-icon {
|
||||
.p-menuitem-icon,
|
||||
.p-submenu-icon {
|
||||
color: $menuitemIconHoverColor;
|
||||
}
|
||||
}
|
||||
@ -135,7 +138,8 @@
|
||||
color: $menuitemTextHoverColor;
|
||||
}
|
||||
|
||||
.p-menuitem-icon, .p-submenu-icon {
|
||||
.p-menuitem-icon,
|
||||
.p-submenu-icon {
|
||||
color: $menuitemIconHoverColor;
|
||||
}
|
||||
}
|
||||
@ -181,7 +185,8 @@
|
||||
color: $horizontalMenuRootMenuitemTextHoverColor;
|
||||
}
|
||||
|
||||
.p-menuitem-icon, .p-submenu-icon {
|
||||
.p-menuitem-icon,
|
||||
.p-submenu-icon {
|
||||
color: $horizontalMenuRootMenuitemIconHoverColor;
|
||||
}
|
||||
}
|
||||
@ -192,16 +197,16 @@
|
||||
|
||||
@mixin placeholder {
|
||||
::-webkit-input-placeholder {
|
||||
@content
|
||||
@content;
|
||||
}
|
||||
:-moz-placeholder {
|
||||
@content
|
||||
@content;
|
||||
}
|
||||
::-moz-placeholder {
|
||||
@content
|
||||
@content;
|
||||
}
|
||||
:-ms-input-placeholder {
|
||||
@content
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +225,6 @@
|
||||
.p-menuitem-link {
|
||||
padding-left: $val * ($index + 1);
|
||||
}
|
||||
|
||||
}
|
||||
@if $index < $length {
|
||||
@include nested-submenu-indents($val, $index + 2, $length);
|
||||
|
@ -44,7 +44,8 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.p-button-group .p-button:not(:last-child), .p-button-group .p-button:not(:last-child):hover {
|
||||
.p-button-group .p-button:not(:last-child),
|
||||
.p-button-group .p-button:not(:last-child):hover {
|
||||
border-right: 0 none;
|
||||
}
|
||||
|
||||
@ -258,7 +259,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-button.p-button-secondary, .p-button-group.p-button-secondary > .p-button, .p-splitbutton.p-button-secondary > .p-button {
|
||||
.p-button.p-button-secondary,
|
||||
.p-button-group.p-button-secondary > .p-button,
|
||||
.p-splitbutton.p-button-secondary > .p-button {
|
||||
color: $secondaryButtonTextColor;
|
||||
background: $secondaryButtonBg;
|
||||
border: $secondaryButtonBorder;
|
||||
@ -316,7 +319,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-button.p-button-info, .p-button-group.p-button-info > .p-button, .p-splitbutton.p-button-info > .p-button {
|
||||
.p-button.p-button-info,
|
||||
.p-button-group.p-button-info > .p-button,
|
||||
.p-splitbutton.p-button-info > .p-button {
|
||||
color: $infoButtonTextColor;
|
||||
background: $infoButtonBg;
|
||||
border: $infoButtonBorder;
|
||||
@ -374,7 +379,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-button.p-button-success, .p-button-group.p-button-success > .p-button, .p-splitbutton.p-button-success > .p-button {
|
||||
.p-button.p-button-success,
|
||||
.p-button-group.p-button-success > .p-button,
|
||||
.p-splitbutton.p-button-success > .p-button {
|
||||
color: $successButtonTextColor;
|
||||
background: $successButtonBg;
|
||||
border: $successButtonBorder;
|
||||
@ -432,7 +439,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-button.p-button-warning, .p-button-group.p-button-warning > .p-button, .p-splitbutton.p-button-warning > .p-button {
|
||||
.p-button.p-button-warning,
|
||||
.p-button-group.p-button-warning > .p-button,
|
||||
.p-splitbutton.p-button-warning > .p-button {
|
||||
color: $warningButtonTextColor;
|
||||
background: $warningButtonBg;
|
||||
border: $warningButtonBorder;
|
||||
@ -490,7 +499,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-button.p-button-help, .p-button-group.p-button-help > .p-button, .p-splitbutton.p-button-help > .p-button {
|
||||
.p-button.p-button-help,
|
||||
.p-button-group.p-button-help > .p-button,
|
||||
.p-splitbutton.p-button-help > .p-button {
|
||||
color: $helpButtonTextColor;
|
||||
background: $helpButtonBg;
|
||||
border: $helpButtonBorder;
|
||||
@ -548,7 +559,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.p-button.p-button-danger, .p-button-group.p-button-danger > .p-button, .p-splitbutton.p-button-danger > .p-button {
|
||||
.p-button.p-button-danger,
|
||||
.p-button-group.p-button-danger > .p-button,
|
||||
.p-splitbutton.p-button-danger > .p-button {
|
||||
color: $dangerButtonTextColor;
|
||||
background: $dangerButtonBg;
|
||||
border: $dangerButtonBorder;
|
||||
@ -607,7 +620,9 @@
|
||||
}
|
||||
|
||||
@if variable-exists(contrastButtonTextColor) {
|
||||
.p-button.p-button-contrast, .p-button-group.p-button-contrast > .p-button, .p-splitbutton.p-button-contrast > .p-button {
|
||||
.p-button.p-button-contrast,
|
||||
.p-button-group.p-button-contrast > .p-button,
|
||||
.p-splitbutton.p-button-contrast > .p-button {
|
||||
color: $contrastButtonTextColor;
|
||||
background: $contrastButtonBg;
|
||||
border: $contrastButtonBorder;
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
// core
|
||||
.p-speeddial {
|
||||
@ -25,7 +25,9 @@
|
||||
.p-speeddial-item {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, opacity 0.8s;
|
||||
transition:
|
||||
transform 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,
|
||||
opacity 0.8s;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,8 @@
|
||||
.p-splitbutton .p-splitbutton-defaultbutton,
|
||||
.p-splitbutton.p-button-rounded > .p-splitbutton-defaultbutton.p-button,
|
||||
.p-splitbutton.p-button-outlined > .p-splitbutton-defaultbutton.p-button,
|
||||
.p-splitbutton.p-button-outlined > .p-splitbutton-defaultbutton.p-button-outlined.p-button:hover {
|
||||
.p-splitbutton.p-button-outlined
|
||||
> .p-splitbutton-defaultbutton.p-button-outlined.p-button:hover {
|
||||
flex: 1 1 auto;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
@ -84,12 +84,19 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.p-datatable-resizable-table > .p-datatable-thead > tr > th.p-resizable-column:not(.p-frozen-column) {
|
||||
.p-datatable-resizable-table
|
||||
> .p-datatable-thead
|
||||
> tr
|
||||
> th.p-resizable-column:not(.p-frozen-column) {
|
||||
background-clip: padding-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.p-datatable-resizable-table-fit > .p-datatable-thead > tr > th.p-resizable-column:last-child .p-column-resizer {
|
||||
.p-datatable-resizable-table-fit
|
||||
> .p-datatable-thead
|
||||
> tr
|
||||
> th.p-resizable-column:last-child
|
||||
.p-column-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -399,7 +406,10 @@
|
||||
}
|
||||
|
||||
&.p-datatable-scrollable > .p-datatable-wrapper > .p-datatable-table,
|
||||
&.p-datatable-scrollable > .p-datatable-wrapper > .p-virtualscroller > .p-datatable-table {
|
||||
&.p-datatable-scrollable
|
||||
> .p-datatable-wrapper
|
||||
> .p-virtualscroller
|
||||
> .p-datatable-table {
|
||||
> .p-datatable-thead,
|
||||
> .p-datatable-tfoot {
|
||||
background-color: $tableHeaderCellBg;
|
||||
|
@ -36,7 +36,6 @@
|
||||
&:focus-visible {
|
||||
@include focused();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.p-column-filter-clear-button {
|
||||
|
@ -80,7 +80,9 @@
|
||||
border: $inputListItemBorder;
|
||||
color: $inputListItemTextColor;
|
||||
background: $inputListItemBg;
|
||||
transition: transform $transitionDuration, $listItemTransition;
|
||||
transition:
|
||||
transform $transitionDuration,
|
||||
$listItemTransition;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
|
@ -76,7 +76,9 @@
|
||||
border: $inputListItemBorder;
|
||||
color: $inputListItemTextColor;
|
||||
background: $inputListItemBg;
|
||||
transition: transform $transitionDuration, $listItemTransition;
|
||||
transition:
|
||||
transform $transitionDuration,
|
||||
$listItemTransition;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
|
@ -29,19 +29,27 @@
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.p-timeline-vertical.p-timeline-alternate .p-timeline-event:nth-child(odd) .p-timeline-event-opposite {
|
||||
.p-timeline-vertical.p-timeline-alternate
|
||||
.p-timeline-event:nth-child(odd)
|
||||
.p-timeline-event-opposite {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.p-timeline-vertical.p-timeline-alternate .p-timeline-event:nth-child(odd) .p-timeline-event-content {
|
||||
.p-timeline-vertical.p-timeline-alternate
|
||||
.p-timeline-event:nth-child(odd)
|
||||
.p-timeline-event-content {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.p-timeline-vertical.p-timeline-alternate .p-timeline-event:nth-child(even) .p-timeline-event-opposite {
|
||||
.p-timeline-vertical.p-timeline-alternate
|
||||
.p-timeline-event:nth-child(even)
|
||||
.p-timeline-event-opposite {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.p-timeline-vertical.p-timeline-alternate .p-timeline-event:nth-child(even) .p-timeline-event-content {
|
||||
.p-timeline-vertical.p-timeline-alternate
|
||||
.p-timeline-event:nth-child(even)
|
||||
.p-timeline-event-content {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -146,6 +154,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -150,7 +150,14 @@
|
||||
.p-treetable-scrollable-both .p-treetable-thead > tr > th,
|
||||
.p-treetable-scrollable-both .p-treetable-tbody > tr > td,
|
||||
.p-treetable-scrollable-both .p-treetable-tfoot > tr > td,
|
||||
.p-treetable-scrollable-horizontal .p-treetable-thead > tr > th .p-treetable-scrollable-horizontal .p-treetable-tbody > tr > td,
|
||||
.p-treetable-scrollable-horizontal
|
||||
.p-treetable-thead
|
||||
> tr
|
||||
> th
|
||||
.p-treetable-scrollable-horizontal
|
||||
.p-treetable-tbody
|
||||
> tr
|
||||
> td,
|
||||
.p-treetable-scrollable-horizontal .p-treetable-tfoot > tr > td {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
@ -299,7 +306,6 @@
|
||||
&:hover {
|
||||
color: $highlightTextColor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.p-fileupload > input[type='file'],
|
||||
.p-fileupload-basic input[type='file'] {
|
||||
.p-fileupload > input[type="file"],
|
||||
.p-fileupload-basic input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
}
|
||||
|
||||
.p-fileupload-row {
|
||||
>div {
|
||||
> div {
|
||||
padding: $tableBodyCellPadding;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
// core
|
||||
.p-colorpicker-panel .p-colorpicker-color {
|
||||
background: linear-gradient(to top, #000 0%, rgb(0 0 0 / 0) 100%), linear-gradient(to right, #fff 0%, rgb(255 255 255 / 0) 100%)
|
||||
background: linear-gradient(to top, #000 0%, rgb(0 0 0 / 0) 100%),
|
||||
linear-gradient(to right, #fff 0%, rgb(255 255 255 / 0) 100%);
|
||||
}
|
||||
|
||||
.p-colorpicker-panel .p-colorpicker-hue {
|
||||
background: linear-gradient(0deg, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red)
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
red 0,
|
||||
#ff0 17%,
|
||||
#0f0 33%,
|
||||
#0ff 50%,
|
||||
#00f 67%,
|
||||
#f0f 83%,
|
||||
red
|
||||
);
|
||||
}
|
||||
|
||||
// theme
|
||||
|
@ -48,8 +48,8 @@
|
||||
|
||||
.ql-picker-options {
|
||||
background: $inputOverlayBg;
|
||||
border:$inputOverlayBorder;
|
||||
box-shadow:$inputOverlayShadow;
|
||||
border: $inputOverlayBorder;
|
||||
box-shadow: $inputOverlayShadow;
|
||||
border-radius: $borderRadius;
|
||||
padding: $inputListPadding;
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 50%;
|
||||
margin-top: -.5rem;
|
||||
margin-top: -0.5rem;
|
||||
transition-property: all;
|
||||
transition-timing-function: ease;
|
||||
line-height: 1;
|
||||
@ -25,7 +25,7 @@
|
||||
.p-float-label:has(textarea.p-filled) label,
|
||||
.p-float-label:has(.p-inputwrapper-focus) label,
|
||||
.p-float-label:has(.p-inputwrapper-filled) label {
|
||||
top: -.75rem;
|
||||
top: -0.75rem;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
@ -6,5 +6,5 @@
|
||||
.p-icon-field > .p-input-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -.5rem;
|
||||
margin-top: -0.5rem;
|
||||
}
|
@ -11,7 +11,9 @@
|
||||
}
|
||||
|
||||
.p-inputnumber-buttons-stacked .p-button.p-inputnumber-button .p-button-label,
|
||||
.p-inputnumber-buttons-horizontal .p-button.p-inputnumber-button .p-button-label {
|
||||
.p-inputnumber-buttons-horizontal
|
||||
.p-button.p-inputnumber-button
|
||||
.p-button-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -39,7 +41,9 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.p-inputnumber-buttons-stacked .p-inputnumber-button-group .p-button.p-inputnumber-button {
|
||||
.p-inputnumber-buttons-stacked
|
||||
.p-inputnumber-button-group
|
||||
.p-button.p-inputnumber-button {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
// core
|
||||
.p-inputswitch {
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
.p-inputswitch-slider:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
content: "";
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
|
@ -175,8 +175,7 @@
|
||||
&.p-multiselect {
|
||||
&.p-multiselect-chip {
|
||||
.p-multiselect-label {
|
||||
padding: math.div(nth($inputPadding, 1), 2)
|
||||
nth($inputPadding, 2);
|
||||
padding: math.div(nth($inputPadding, 1), 2) nth($inputPadding, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,4 +274,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,13 +19,13 @@
|
||||
.p-radiobutton-icon {
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
transform: translateZ(0) scale(.1);
|
||||
transform: translateZ(0) scale(0.1);
|
||||
border-radius: 50%;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.p-radiobutton.p-highlight .p-radiobutton-icon {
|
||||
transform: translateZ(0) scale(1.0, 1.0);
|
||||
transform: translateZ(0) scale(1, 1);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
// core
|
||||
.p-rating {
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
// core
|
||||
.p-slider {
|
||||
|
@ -116,8 +116,7 @@
|
||||
&.p-treeselect {
|
||||
&.p-treeselect-chip {
|
||||
.p-treeselect-label {
|
||||
padding: math.div(nth($inputPadding, 1), 2)
|
||||
nth($inputPadding, 2);
|
||||
padding: math.div(nth($inputPadding, 1), 2) nth($inputPadding, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,12 +82,20 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.p-megamenu-vertical:not(.p-megamenu-mobile) .p-megamenu-root-list > .p-menuitem-active > .p-megamenu-panel {
|
||||
.p-megamenu-vertical:not(.p-megamenu-mobile)
|
||||
.p-megamenu-root-list
|
||||
> .p-menuitem-active
|
||||
> .p-megamenu-panel {
|
||||
left: 100%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.p-megamenu-vertical .p-megamenu-root-list > .p-menuitem > .p-menuitem-content > .p-menuitem-link > .p-submenu-icon {
|
||||
.p-megamenu-vertical
|
||||
.p-megamenu-root-list
|
||||
> .p-menuitem
|
||||
> .p-menuitem-content
|
||||
> .p-menuitem-link
|
||||
> .p-submenu-icon {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@ -266,7 +274,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.p-menuitem-active {
|
||||
> .p-menuitem-content {
|
||||
> .p-menuitem-link {
|
||||
|
@ -54,7 +54,12 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.p-menubar .p-submenu-list .p-menuitem .p-menuitem-content .p-menuitem-link .p-submenu-icon {
|
||||
.p-menubar
|
||||
.p-submenu-list
|
||||
.p-menuitem
|
||||
.p-menuitem-content
|
||||
.p-menuitem-link
|
||||
.p-submenu-icon {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@ -187,7 +192,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.p-menuitem-active {
|
||||
> .p-menuitem-content {
|
||||
> .p-menuitem-link {
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
// core
|
||||
.p-steps {
|
||||
@ -99,7 +99,7 @@
|
||||
}
|
||||
|
||||
&:before {
|
||||
content:' ';
|
||||
content: " ";
|
||||
border-top: $divider;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
|
@ -41,7 +41,10 @@
|
||||
|
||||
.p-message-leave-active {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s cubic-bezier(0, 1, 0, 1), opacity 0.3s, margin 0.15s;
|
||||
transition:
|
||||
max-height 0.3s cubic-bezier(0, 1, 0, 1),
|
||||
opacity 0.3s,
|
||||
margin 0.15s;
|
||||
}
|
||||
|
||||
.p-message-leave-active .p-message-close {
|
||||
|
@ -63,13 +63,23 @@
|
||||
}
|
||||
|
||||
.p-toast-message-enter-active {
|
||||
-webkit-transition: transform 0.3s, opacity 0.3s;
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
-webkit-transition:
|
||||
transform 0.3s,
|
||||
opacity 0.3s;
|
||||
transition:
|
||||
transform 0.3s,
|
||||
opacity 0.3s;
|
||||
}
|
||||
|
||||
.p-toast-message-leave-active {
|
||||
-webkit-transition: max-height 0.45s cubic-bezier(0, 1, 0, 1), opacity 0.3s, margin-bottom 0.3s;
|
||||
transition: max-height 0.45s cubic-bezier(0, 1, 0, 1), opacity 0.3s, margin-bottom 0.3s;
|
||||
-webkit-transition:
|
||||
max-height 0.45s cubic-bezier(0, 1, 0, 1),
|
||||
opacity 0.3s,
|
||||
margin-bottom 0.3s;
|
||||
transition:
|
||||
max-height 0.45s cubic-bezier(0, 1, 0, 1),
|
||||
opacity 0.3s,
|
||||
margin-bottom 0.3s;
|
||||
}
|
||||
|
||||
// theme
|
||||
|
@ -3,7 +3,7 @@
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
padding: 0 .5rem;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.p-overlay-badge {
|
||||
@ -14,15 +14,15 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(50%,-50%);
|
||||
transform: translate(50%, -50%);
|
||||
transform-origin: 100% 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.p-badge.p-badge-dot {
|
||||
width: .5rem;
|
||||
min-width: .5rem;
|
||||
height: .5rem;
|
||||
width: 0.5rem;
|
||||
min-width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
// core
|
||||
.p-chip {
|
||||
|
@ -35,4 +35,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,27 +25,31 @@
|
||||
}
|
||||
|
||||
.p-progressbar-indeterminate .p-progressbar-value::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: inherit;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
will-change: left, right;
|
||||
-webkit-animation: p-progressbar-indeterminate-anim 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
animation: p-progressbar-indeterminate-anim 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
-webkit-animation: p-progressbar-indeterminate-anim 2.1s
|
||||
cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
animation: p-progressbar-indeterminate-anim 2.1s
|
||||
cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
}
|
||||
|
||||
.p-progressbar-indeterminate .p-progressbar-value::after {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: inherit;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
will-change: left, right;
|
||||
-webkit-animation: p-progressbar-indeterminate-anim-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
|
||||
animation: p-progressbar-indeterminate-anim-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
|
||||
-webkit-animation: p-progressbar-indeterminate-anim-short 2.1s
|
||||
cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
|
||||
animation: p-progressbar-indeterminate-anim-short 2.1s
|
||||
cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
|
||||
-webkit-animation-delay: 1.15s;
|
||||
animation-delay: 1.15s;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.p-progress-spinner::before {
|
||||
content: '';
|
||||
content: "";
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
@ -34,7 +34,9 @@
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: 0;
|
||||
stroke: $progressSpinnerStrokeColor;
|
||||
animation: p-progress-spinner-dash 1.5s ease-in-out infinite, p-progress-spinner-color 6s ease-in-out infinite;
|
||||
animation:
|
||||
p-progress-spinner-dash 1.5s ease-in-out infinite,
|
||||
p-progress-spinner-color 6s ease-in-out infinite;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
.p-skeleton::after {
|
||||
content: '';
|
||||
content: "";
|
||||
animation: p-skeleton-animation 1.2s infinite;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
@ -38,6 +38,11 @@
|
||||
border-radius: $borderRadius;
|
||||
|
||||
&:after {
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0), $skeletonAnimationBg, rgba(255, 255, 255, 0));
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0),
|
||||
$skeletonAnimationBg,
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
}
|
||||
}
|
@ -52,12 +52,16 @@
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.p-galleria-item-nav-onhover .p-galleria-item-wrapper:hover .p-galleria-item-nav {
|
||||
.p-galleria-item-nav-onhover
|
||||
.p-galleria-item-wrapper:hover
|
||||
.p-galleria-item-nav {
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.p-galleria-item-nav-onhover .p-galleria-item-wrapper:hover .p-galleria-item-nav.p-disabled {
|
||||
.p-galleria-item-nav-onhover
|
||||
.p-galleria-item-wrapper:hover
|
||||
.p-galleria-item-nav.p-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@ -206,14 +210,16 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.p-galleria-indicator-onitem.p-galleria-indicators-right .p-galleria-indicators {
|
||||
.p-galleria-indicator-onitem.p-galleria-indicators-right
|
||||
.p-galleria-indicators {
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.p-galleria-indicator-onitem.p-galleria-indicators-bottom .p-galleria-indicators {
|
||||
.p-galleria-indicator-onitem.p-galleria-indicators-bottom
|
||||
.p-galleria-indicators {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
@ -22,7 +22,9 @@
|
||||
}
|
||||
|
||||
.p-confirm-popup-enter-active {
|
||||
transition: transform 0.12s cubic-bezier(0, 0, 0.2, 1), opacity 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||
transition:
|
||||
transform 0.12s cubic-bezier(0, 0, 0.2, 1),
|
||||
opacity 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.p-confirm-popup-leave-active {
|
||||
@ -33,7 +35,7 @@
|
||||
.p-confirm-popup:before {
|
||||
bottom: 100%;
|
||||
left: calc(var(--overlayArrowLeft, 0) + 1.25rem);
|
||||
content: ' ';
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
@ -104,13 +106,15 @@
|
||||
&:before {
|
||||
border-style: solid;
|
||||
|
||||
@if (nth($overlayContentBorder, 2) == 'none') {
|
||||
@if (nth($overlayContentBorder, 2) == "none") {
|
||||
border-color: rgba($overlayContentBg, 0);
|
||||
border-bottom-color: scale-color($overlayContentBg, $lightness: -5%);
|
||||
}
|
||||
@else {
|
||||
} @else {
|
||||
border-color: rgba(nth($overlayContentBorder, 3), 0);
|
||||
border-bottom-color: scale-color(nth($overlayContentBorder, 3), $lightness: -5%);
|
||||
border-bottom-color: scale-color(
|
||||
nth($overlayContentBorder, 3),
|
||||
$lightness: -5%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,10 +124,9 @@
|
||||
}
|
||||
|
||||
&:before {
|
||||
@if (nth($overlayContentBorder, 2) == 'none') {
|
||||
@if (nth($overlayContentBorder, 2) == "none") {
|
||||
border-top-color: $overlayContentBg;
|
||||
}
|
||||
@else {
|
||||
} @else {
|
||||
border-top-color: nth($overlayContentBorder, 3);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
// core
|
||||
.p-overlaypanel {
|
||||
@ -29,7 +29,9 @@
|
||||
}
|
||||
|
||||
.p-overlaypanel-enter-active {
|
||||
transition: transform 0.12s cubic-bezier(0, 0, 0.2, 1), opacity 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||
transition:
|
||||
transform 0.12s cubic-bezier(0, 0, 0.2, 1),
|
||||
opacity 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.p-overlaypanel-leave-active {
|
||||
@ -40,7 +42,7 @@
|
||||
.p-overlaypanel:before {
|
||||
bottom: 100%;
|
||||
left: calc(var(--overlayArrowLeft, 0) + 1.25rem);
|
||||
content: ' ';
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
@ -109,13 +111,15 @@
|
||||
&:before {
|
||||
border-style: solid;
|
||||
|
||||
@if (nth($overlayContentBorder, 2) == 'none') {
|
||||
@if (nth($overlayContentBorder, 2) == "none") {
|
||||
border-color: rgba($overlayContentBg, 0);
|
||||
border-bottom-color: scale-color($overlayContentBg, $lightness: -5%);
|
||||
}
|
||||
@else {
|
||||
} @else {
|
||||
border-color: rgba(nth($overlayContentBorder, 3), 0);
|
||||
border-bottom-color: scale-color(nth($overlayContentBorder, 3), $lightness: -5%);
|
||||
border-bottom-color: scale-color(
|
||||
nth($overlayContentBorder, 3),
|
||||
$lightness: -5%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,10 +129,9 @@
|
||||
}
|
||||
|
||||
&:before {
|
||||
@if (nth($overlayContentBorder, 2) == 'none') {
|
||||
@if (nth($overlayContentBorder, 2) == "none") {
|
||||
border-top-color: $overlayContentBg;
|
||||
}
|
||||
@else {
|
||||
} @else {
|
||||
border-top-color: nth($overlayContentBorder, 3);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
// core
|
||||
.p-tooltip {
|
||||
position:absolute;
|
||||
display:none;
|
||||
padding: .25em .5rem;
|
||||
position: absolute;
|
||||
display: none;
|
||||
padding: 0.25em 0.5rem;
|
||||
max-width: 12.5rem;
|
||||
}
|
||||
|
||||
.p-tooltip.p-tooltip-right,
|
||||
.p-tooltip.p-tooltip-left {
|
||||
padding: 0 .25rem;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.p-tooltip.p-tooltip-top,
|
||||
.p-tooltip.p-tooltip-bottom {
|
||||
padding:.25em 0;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
.p-tooltip .p-tooltip-text {
|
||||
@ -31,27 +31,27 @@
|
||||
}
|
||||
|
||||
.p-tooltip-right .p-tooltip-arrow {
|
||||
margin-top: -.25rem;
|
||||
border-width: .25em .25em .25em 0;
|
||||
margin-top: -0.25rem;
|
||||
border-width: 0.25em 0.25em 0.25em 0;
|
||||
}
|
||||
|
||||
.p-tooltip-left .p-tooltip-arrow {
|
||||
margin-top: -.25rem;
|
||||
border-width: .25em 0 .25em .25rem;
|
||||
margin-top: -0.25rem;
|
||||
border-width: 0.25em 0 0.25em 0.25rem;
|
||||
}
|
||||
|
||||
.p-tooltip.p-tooltip-top {
|
||||
padding: .25em 0;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
.p-tooltip-top .p-tooltip-arrow {
|
||||
margin-left: -.25rem;
|
||||
border-width: .25em .25em 0;
|
||||
margin-left: -0.25rem;
|
||||
border-width: 0.25em 0.25em 0;
|
||||
}
|
||||
|
||||
.p-tooltip-bottom .p-tooltip-arrow {
|
||||
margin-left: -.25rem;
|
||||
border-width: 0 .25em .25rem;
|
||||
margin-left: -0.25rem;
|
||||
border-width: 0 0.25em 0.25rem;
|
||||
}
|
||||
|
||||
// theme
|
||||
|
@ -12,7 +12,7 @@
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.p-divider-content {
|
||||
@ -33,7 +33,7 @@
|
||||
top: 0;
|
||||
left: 50%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.p-divider {
|
||||
|
@ -1,4 +1,4 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:math";
|
||||
|
||||
//core
|
||||
.p-stepper .p-stepper-nav {
|
||||
|
@ -1,5 +1,8 @@
|
||||
@mixin focused-ring($ring-color) {
|
||||
box-shadow: 0 0 0 2px #1c2127, 0 0 0 4px $ring-color, 0 1px 2px 0 rgba(0, 0, 0, 0.0);
|
||||
box-shadow:
|
||||
0 0 0 2px #1c2127,
|
||||
0 0 0 4px $ring-color,
|
||||
0 1px 2px 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@layer primevue {
|
||||
@ -9,13 +12,19 @@
|
||||
|
||||
.p-selectbutton > .p-button,
|
||||
.p-togglebutton.p-button {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
|
||||
.p-accordion {
|
||||
.p-accordion-header {
|
||||
.p-accordion-header-link {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,7 +33,10 @@
|
||||
.p-tabview-nav {
|
||||
li {
|
||||
.p-tabview-nav-link {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,7 +46,10 @@
|
||||
.p-tabmenu-nav {
|
||||
.p-tabmenuitem {
|
||||
.p-menuitem-link {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,31 +69,31 @@
|
||||
|
||||
.p-button {
|
||||
&:focus {
|
||||
@include focused-ring(rgba($buttonBg, .7));
|
||||
@include focused-ring(rgba($buttonBg, 0.7));
|
||||
}
|
||||
|
||||
&.p-button-secondary:enabled:focus {
|
||||
@include focused-ring(rgba($secondaryButtonBg, .7));
|
||||
@include focused-ring(rgba($secondaryButtonBg, 0.7));
|
||||
}
|
||||
|
||||
&.p-button-success:enabled:focus {
|
||||
@include focused-ring(rgba($successButtonBg, .7));
|
||||
@include focused-ring(rgba($successButtonBg, 0.7));
|
||||
}
|
||||
|
||||
&.p-button-info:enabled:focus {
|
||||
@include focused-ring(rgba($infoButtonBg, .7));
|
||||
@include focused-ring(rgba($infoButtonBg, 0.7));
|
||||
}
|
||||
|
||||
&.p-button-warning:enabled:focus {
|
||||
@include focused-ring(rgba($warningButtonBg, .7));
|
||||
@include focused-ring(rgba($warningButtonBg, 0.7));
|
||||
}
|
||||
|
||||
&.p-button-help:enabled:focus {
|
||||
@include focused-ring(rgba($helpButtonBg, .7));
|
||||
@include focused-ring(rgba($helpButtonBg, 0.7));
|
||||
}
|
||||
|
||||
&.p-button-danger:enabled:focus {
|
||||
@include focused-ring(rgba($dangerButtonBg, .7));
|
||||
@include focused-ring(rgba($dangerButtonBg, 0.7));
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +113,7 @@
|
||||
|
||||
.p-speeddial-item {
|
||||
&.p-focus > .p-speeddial-action {
|
||||
@include focused-ring(rgba($buttonBg, .7));
|
||||
@include focused-ring(rgba($buttonBg, 0.7));
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +124,7 @@
|
||||
.p-message {
|
||||
.p-message-close {
|
||||
&:hover {
|
||||
background: rgba(255,255,255,.1);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,7 +133,7 @@
|
||||
.p-toast-message {
|
||||
.p-toast-icon-close {
|
||||
&:hover {
|
||||
background: rgba(255,255,255,.1);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,7 +145,12 @@
|
||||
|
||||
.p-picklist-buttons .p-button,
|
||||
.p-orderlist-controls .p-button {
|
||||
transition: opacity $transitionDuration, background-color $transitionDuration, color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
opacity $transitionDuration,
|
||||
background-color $transitionDuration,
|
||||
color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
|
||||
.p-steps {
|
||||
|
@ -41,7 +41,10 @@ $colors: (
|
||||
$shade000: rgba(255, 255, 255, 0.87) !default; //text color
|
||||
$shade100: rgba(255, 255, 255, 0.6) !default; //text secondary color
|
||||
$shade500: #6b7280 !default;
|
||||
$shade600: map-get($colors, "htwk-grau-140") !default; //input bg, border, divider
|
||||
$shade600: map-get(
|
||||
$colors,
|
||||
"htwk-grau-140"
|
||||
) !default; //input bg, border, divider
|
||||
$shade700: map-get($colors, "htwk-grau") !default; //menu bg
|
||||
$shade800: map-get($colors, "htwk-grau") !default; //elevated surface
|
||||
$shade900: rgba(map-get($colors, "htwk-schwarz"), 1) !default; //ground surface
|
||||
@ -49,15 +52,33 @@ $shade900: rgba(map-get($colors, "htwk-schwarz"), 1) !default; //ground surface
|
||||
$hoverBg: rgba(255, 255, 255, 0.03) !default;
|
||||
|
||||
//global
|
||||
$fontFamily: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, Twemoji Country Flags, Roboto, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol !default;
|
||||
$fontFamily:
|
||||
"Source Sans Pro",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Segoe UI,
|
||||
Twemoji Country Flags,
|
||||
Roboto,
|
||||
Arial,
|
||||
sans-serif,
|
||||
Apple Color Emoji,
|
||||
Segoe UI Emoji,
|
||||
Segoe UI Symbol !default;
|
||||
$fontSize: 1rem !default;
|
||||
$fontWeight: normal !default;
|
||||
$textColor: $shade000 !default;
|
||||
$textSecondaryColor: $shade100 !default;
|
||||
$borderRadius: 6px !default;
|
||||
$transitionDuration: 0.2s !default;
|
||||
$formElementTransition: background-color $transitionDuration, color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration !default;
|
||||
$actionIconTransition: background-color $transitionDuration, color $transitionDuration, box-shadow $transitionDuration !default;
|
||||
$formElementTransition:
|
||||
background-color $transitionDuration,
|
||||
color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration !default;
|
||||
$actionIconTransition:
|
||||
background-color $transitionDuration,
|
||||
color $transitionDuration,
|
||||
box-shadow $transitionDuration !default;
|
||||
$listItemTransition: box-shadow $transitionDuration !default;
|
||||
$primeIconFontSize: 1rem !default;
|
||||
$divider: 1px solid $shade600 !default;
|
||||
@ -135,8 +156,10 @@ $inputListHeaderBorder: 1px solid $shade600 !default;
|
||||
$inputOverlayBg: $inputListBg !default;
|
||||
$inputOverlayHeaderBg: $inputListHeaderBg !default;
|
||||
$inputOverlayBorder: 1px solid $shade600 !default;
|
||||
$inputOverlayShadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||
0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12) !default;
|
||||
$inputOverlayShadow:
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||
0 4px 5px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 10px 0 rgba(0, 0, 0, 0.12) !default;
|
||||
|
||||
//password
|
||||
$passwordMeterBg: $shade600 !default;
|
||||
@ -157,8 +180,10 @@ $buttonHoverBorderColor: $primaryLightColor !default;
|
||||
$buttonActiveBg: $primaryLighterColor !default;
|
||||
$buttonTextActiveColor: $primaryTextColor !default;
|
||||
$buttonActiveBorderColor: $primaryLighterColor !default;
|
||||
$raisedButtonShadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
|
||||
0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12) !default;
|
||||
$raisedButtonShadow:
|
||||
0px 3px 1px -2px rgba(0, 0, 0, 0.2),
|
||||
0px 2px 2px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 5px 0px rgba(0, 0, 0, 0.12) !default;
|
||||
$roundedButtonBorderRadius: 2rem !default;
|
||||
|
||||
$textButtonHoverBgOpacity: 0.04 !default;
|
||||
@ -189,7 +214,8 @@ $infoButtonHoverBorderColor: map-get($colors, "htwk-cyan") !default;
|
||||
$infoButtonActiveBg: map-get($colors, "htwk-cyan") !default;
|
||||
$infoButtonTextActiveColor: $infoButtonTextColor !default;
|
||||
$infoButtonActiveBorderColor: map-get($colors, "htwk-cyan") !default;
|
||||
$infoButtonFocusShadow: 0 0 0 1px scale-color($infoButtonHoverBg, $lightness: 30%) !default;
|
||||
$infoButtonFocusShadow: 0 0 0 1px
|
||||
scale-color($infoButtonHoverBg, $lightness: 30%) !default;
|
||||
|
||||
$successButtonBg: map-get($colors, "htwk-gruen") !default;
|
||||
$successButtonTextColor: #052e16 !default;
|
||||
@ -208,10 +234,16 @@ $warningButtonTextColor: #493c08 !default;
|
||||
$warningButtonBorder: 1px solid map-get($colors, "htwk-yellow") !default;
|
||||
$warningButtonHoverBg: scale-color($warningButtonBg, $lightness: 30%) !default;
|
||||
$warningButtonTextHoverColor: $warningButtonTextColor !default;
|
||||
$warningButtonHoverBorderColor: scale-color($warningButtonBg, $lightness: 10%) !default;
|
||||
$warningButtonHoverBorderColor: scale-color(
|
||||
$warningButtonBg,
|
||||
$lightness: 10%
|
||||
) !default;
|
||||
$warningButtonActiveBg: scale-color($warningButtonBg, $lightness: 30%) !default;
|
||||
$warningButtonTextActiveColor: $warningButtonTextColor !default;
|
||||
$warningButtonActiveBorderColor: scale-color($warningButtonBg, $lightness: 30%) !default;
|
||||
$warningButtonActiveBorderColor: scale-color(
|
||||
$warningButtonBg,
|
||||
$lightness: 30%
|
||||
) !default;
|
||||
$warningButtonFocusShadow: 0 0 0 1px
|
||||
scale-color($warningButtonBg, $lightness: 30%) !default;
|
||||
|
||||
@ -478,7 +510,9 @@ $cardSubTitleFontWeight: 100 !default;
|
||||
$cardSubTitleColor: $shade100 !default;
|
||||
$cardContentPadding: 1.25rem 0 !default;
|
||||
$cardFooterPadding: 1.25rem 0 0 0 !default;
|
||||
$cardShadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14),
|
||||
$cardShadow:
|
||||
0 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0 1px 1px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 3px 0 rgba(0, 0, 0, 0.12) !default;
|
||||
|
||||
//editor
|
||||
@ -644,8 +678,10 @@ $contrastMessageIconColor: $contrastButtonTextColor !default;
|
||||
//overlays
|
||||
$overlayContentBorder: 1px solid $shade600 !default;
|
||||
$overlayContentBg: $panelContentBg !default;
|
||||
$overlayContainerShadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2),
|
||||
0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12) !default;
|
||||
$overlayContainerShadow:
|
||||
0px 11px 15px -7px rgba(0, 0, 0, 0.2),
|
||||
0px 24px 38px 3px rgba(0, 0, 0, 0.14),
|
||||
0px 9px 46px 8px rgba(0, 0, 0, 0.12) !default;
|
||||
|
||||
//dialog
|
||||
$dialogHeaderBg: $shade800 !default;
|
||||
@ -719,8 +755,10 @@ $submenuHeaderBorderRadius: 0 !default;
|
||||
$submenuHeaderFontWeight: 0 !default;
|
||||
$overlayMenuBg: $menuBg !default;
|
||||
$overlayMenuBorder: 1px solid $shade600 !default;
|
||||
$overlayMenuShadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||
0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12) !default;
|
||||
$overlayMenuShadow:
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||
0 4px 5px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 10px 0 rgba(0, 0, 0, 0.12) !default;
|
||||
$verticalMenuPadding: 0.25rem 0 !default;
|
||||
$menuSeparatorMargin: 0.25rem 0 !default;
|
||||
|
||||
@ -887,10 +925,23 @@ $imagePreviewActionIconFontSize: 1.5rem !default;
|
||||
$imagePreviewActionIconBorderRadius: 50% !default;
|
||||
|
||||
:root {
|
||||
font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, Twemoji Country Flags, Roboto, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
|
||||
font-family:
|
||||
"Source Sans Pro",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Segoe UI,
|
||||
Twemoji Country Flags,
|
||||
Roboto,
|
||||
Arial,
|
||||
sans-serif,
|
||||
Apple Color Emoji,
|
||||
Segoe UI Emoji,
|
||||
Segoe UI Symbol;
|
||||
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
||||
font-variation-settings: normal;
|
||||
--font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, Twemoji Country Flags, Roboto, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
|
||||
--font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI,
|
||||
Twemoji Country Flags, Roboto, Arial, sans-serif, Apple Color Emoji,
|
||||
Segoe UI Emoji, Segoe UI Symbol;
|
||||
--font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
||||
--surface-a: #{$shade800};
|
||||
--surface-b: #{$shade900};
|
||||
|
@ -5,16 +5,21 @@ $primaryLightestColor: rgba(255, 237, 0, 0.1) !default;
|
||||
$primaryTextColor: #030712 !default;
|
||||
|
||||
$highlightBg: rgba($primaryColor, 0.16) !default;
|
||||
$highlightTextColor: rgba(255,255,255,0.87) !default;
|
||||
$highlightTextColor: rgba(255, 255, 255, 0.87) !default;
|
||||
$highlightFocusBg: rgba($primaryColor, 0.24) !default;
|
||||
|
||||
@import '../_variables';
|
||||
@import './_fonts';
|
||||
@import '../../../../theme-base/_components';
|
||||
@import '../_extensions';
|
||||
@import "../_variables";
|
||||
@import "./_fonts";
|
||||
@import "../../../../theme-base/_components";
|
||||
@import "../_extensions";
|
||||
|
||||
:root {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@ -150,9 +155,15 @@ $highlightFocusBg: rgba($primaryColor, 0.24) !default;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
}
|
||||
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-dayGridMonth-button.fc-button-active,
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-timeGridWeek-button.fc-button-active,
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-timeGridDay-button.fc-button-active {
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-dayGridMonth-button.fc-button-active,
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-timeGridWeek-button.fc-button-active,
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-timeGridDay-button.fc-button-active {
|
||||
background: map-get($colors, "htwk-yellow"); /*#93c5fd*/
|
||||
border-color: map-get($colors, "htwk-yellow"); /*#93c5fd*/
|
||||
color: map-get($colors, "htwk-schwarz"); /*#1c2127*/
|
||||
@ -460,6 +471,18 @@ $highlightFocusBg: rgba($primaryColor, 0.24) !default;
|
||||
|
||||
.fc.fc-theme-standard .fc-highlight {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background: rgba(map-get($colors, "htwk-yellow"), 0.16); /*rgba(147, 197, 253, 0.16)*/
|
||||
background: rgba(
|
||||
map-get($colors, "htwk-yellow"),
|
||||
0.16
|
||||
); /*rgba(147, 197, 253, 0.16)*/
|
||||
}
|
||||
}
|
||||
|
||||
.fc-event-selected::after, .fc-event:focus::after {
|
||||
background: var(--fc-event-selected-overlay-color);
|
||||
inset: -1px;
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
border-radius: 5px;
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
@mixin focused-ring($ring-color) {
|
||||
box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px $ring-color, 0 1px 2px 0 rgba(0, 0, 0, 1.0);
|
||||
box-shadow:
|
||||
0 0 0 2px #ffffff,
|
||||
0 0 0 4px $ring-color,
|
||||
0 1px 2px 0 rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
@layer primevue {
|
||||
@ -9,13 +12,19 @@
|
||||
|
||||
.p-selectbutton > .p-button,
|
||||
.p-togglebutton.p-button {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
|
||||
.p-accordion {
|
||||
.p-accordion-header {
|
||||
.p-accordion-header-link {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,7 +33,10 @@
|
||||
.p-tabview-nav {
|
||||
li {
|
||||
.p-tabview-nav-link {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,7 +46,10 @@
|
||||
.p-tabmenu-nav {
|
||||
.p-tabmenuitem {
|
||||
.p-menuitem-link {
|
||||
transition: background-color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
background-color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,7 +127,12 @@
|
||||
|
||||
.p-picklist-buttons .p-button,
|
||||
.p-orderlist-controls .p-button {
|
||||
transition: opacity $transitionDuration, background-color $transitionDuration, color $transitionDuration, border-color $transitionDuration, box-shadow $transitionDuration;
|
||||
transition:
|
||||
opacity $transitionDuration,
|
||||
background-color $transitionDuration,
|
||||
color $transitionDuration,
|
||||
border-color $transitionDuration,
|
||||
box-shadow $transitionDuration;
|
||||
}
|
||||
|
||||
.p-steps {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,17 +4,22 @@ $primaryDarkColor: #002d67 !default;
|
||||
$primaryDarkerColor: #022541 !default;
|
||||
$primaryTextColor: #ffffff !default;
|
||||
|
||||
$highlightBg: #EFF6FF !default;
|
||||
$highlightBg: #eff6ff !default;
|
||||
$highlightTextColor: $primaryDarkerColor !default;
|
||||
$highlightFocusBg: rgba($primaryColor, .24) !default;
|
||||
$highlightFocusBg: rgba($primaryColor, 0.24) !default;
|
||||
|
||||
@import '../_variables';
|
||||
@import './_fonts';
|
||||
@import '../../../../theme-base/_components';
|
||||
@import '../_extensions';
|
||||
@import "../_variables";
|
||||
@import "./_fonts";
|
||||
@import "../../../../theme-base/_components";
|
||||
@import "../_extensions";
|
||||
|
||||
:root {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@ -139,9 +144,15 @@ $highlightFocusBg: rgba($primaryColor, .24) !default;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
}
|
||||
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-dayGridMonth-button.fc-button-active,
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-timeGridWeek-button.fc-button-active,
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-timeGridDay-button.fc-button-active {
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-dayGridMonth-button.fc-button-active,
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-timeGridWeek-button.fc-button-active,
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-timeGridDay-button.fc-button-active {
|
||||
background: map-get($colors, "primary"); /*#93c5fd*/
|
||||
border-color: map-get($colors, "primary"); /*#93c5fd*/
|
||||
color: $primaryTextColor; /*#1c2127*/
|
||||
@ -449,6 +460,9 @@ $highlightFocusBg: rgba($primaryColor, .24) !default;
|
||||
|
||||
.fc.fc-theme-standard .fc-highlight {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background: rgba(map-get($colors, "primary"), 0.16); /*rgba(147, 197, 253, 0.16)*/
|
||||
background: rgba(
|
||||
map-get($colors, "primary"),
|
||||
0.16
|
||||
); /*rgba(147, 197, 253, 0.16)*/
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,35 @@
|
||||
@use 'sass:color';
|
||||
@use "sass:color";
|
||||
|
||||
$primaryColor: #00944c !default;
|
||||
$primaryLightColor: color.scale($primaryColor, $lightness: 10%, $saturation: -10%) !default;
|
||||
$primaryLightColor: color.scale(
|
||||
$primaryColor,
|
||||
$lightness: 10%,
|
||||
$saturation: -10%
|
||||
) !default;
|
||||
$primaryDarkColor: color.scale($primaryColor, $lightness: -40%) !default;
|
||||
$primaryDarkerColor: color.scale($primaryColor, $lightness: -80%) !default;
|
||||
$primaryTextColor: #ffffff !default;
|
||||
|
||||
$highlightBg: color.scale($primaryColor, $lightness: 90%, $saturation: -80%) !default;
|
||||
$highlightBg: color.scale(
|
||||
$primaryColor,
|
||||
$lightness: 90%,
|
||||
$saturation: -80%
|
||||
) !default;
|
||||
$highlightTextColor: $primaryDarkerColor !default;
|
||||
$highlightFocusBg: rgba($primaryColor, .24) !default;
|
||||
$highlightFocusBg: rgba($primaryColor, 0.24) !default;
|
||||
|
||||
@import '../_variables';
|
||||
@import './_fonts';
|
||||
@import '../../../../theme-base/_components';
|
||||
@import '../_extensions';
|
||||
@import "../_variables";
|
||||
@import "./_fonts";
|
||||
@import "../../../../theme-base/_components";
|
||||
@import "../_extensions";
|
||||
|
||||
:root {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@ -63,6 +76,10 @@ $highlightFocusBg: rgba($primaryColor, .24) !default;
|
||||
margin: 1%;
|
||||
}
|
||||
|
||||
.fc-v-event .fc-event-main {
|
||||
color: map-get($colors, "htwk-schwarz"); /*#1c2127*/
|
||||
}
|
||||
|
||||
.fc.fc-unthemed .fc-view-container .fc-divider {
|
||||
background: $primaryTextColor; /*#071426*/
|
||||
border: 1px solid map-get($colors, "htwk-grau-140"); /*#0b213f*/
|
||||
@ -148,9 +165,15 @@ $highlightFocusBg: rgba($primaryColor, .24) !default;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
}
|
||||
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-dayGridMonth-button.fc-button-active,
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-timeGridWeek-button.fc-button-active,
|
||||
.fc.fc-unthemed .fc-toolbar .fc-button.fc-timeGridDay-button.fc-button-active {
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-dayGridMonth-button.fc-button-active,
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-timeGridWeek-button.fc-button-active,
|
||||
.fc.fc-unthemed
|
||||
.fc-toolbar
|
||||
.fc-button.fc-timeGridDay-button.fc-button-active {
|
||||
background: map-get($colors, "primary"); /*#93c5fd*/
|
||||
border-color: map-get($colors, "primary"); /*#93c5fd*/
|
||||
color: $primaryTextColor; /*#1c2127*/
|
||||
@ -458,6 +481,18 @@ $highlightFocusBg: rgba($primaryColor, .24) !default;
|
||||
|
||||
.fc.fc-theme-standard .fc-highlight {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background: rgba(map-get($colors, "primary"), 0.16); /*rgba(147, 197, 253, 0.16)*/
|
||||
background: rgba(
|
||||
map-get($colors, "primary"),
|
||||
0.16
|
||||
); /*rgba(147, 197, 253, 0.16)*/
|
||||
}
|
||||
}
|
||||
|
||||
.fc-event-selected::after, .fc-event:focus::after {
|
||||
background: var(--fc-event-selected-overlay-color);
|
||||
inset: -1px;
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
border-radius: 5px;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -21,8 +21,12 @@ import MenuBar from "./components/MenuBar.vue";
|
||||
import { RouteRecordName, RouterView } from "vue-router";
|
||||
import CalendarPreview from "./components/CalendarPreview.vue";
|
||||
import moduleStore from "./store/moduleStore.ts";
|
||||
import { provide, ref } from "vue";
|
||||
import { onMounted, provide, ref } from "vue";
|
||||
import { VueQueryDevtools } from "@tanstack/vue-query-devtools";
|
||||
import settingsStore from "@/store/settingsStore.ts";
|
||||
import { setTheme } from "@/helpers/theme.ts";
|
||||
import { usePrimeVue } from "primevue/config";
|
||||
const primeVue = usePrimeVue();
|
||||
|
||||
const disabledPages = [
|
||||
"room-finder",
|
||||
@ -33,7 +37,7 @@ const disabledPages = [
|
||||
"edit-calendar",
|
||||
"rooms",
|
||||
"free-rooms",
|
||||
"room-schedule",
|
||||
"room-schedule"
|
||||
];
|
||||
|
||||
const store = moduleStore();
|
||||
@ -49,14 +53,26 @@ const updateMobile = () => {
|
||||
};
|
||||
|
||||
updateMobile();
|
||||
|
||||
window.addEventListener("resize", updateMobile);
|
||||
|
||||
const settings = settingsStore;
|
||||
const emit = defineEmits(["dark-mode-toggled"]);
|
||||
|
||||
onMounted(() => {
|
||||
// set theme matching browser preference
|
||||
settings().setDarkMode(window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
setTheme(settings, primeVue, emit);
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
|
||||
settings().setDarkMode(e.matches)
|
||||
setTheme(settings, primeVue, emit);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MenuBar />
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<transition name="scale" mode="out-in">
|
||||
<transition mode="out-in" name="scale">
|
||||
<div :key="route.name ?? ''" class="origin-near-top">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
|
@ -78,12 +78,13 @@ export async function fetchRoomOccupancy(
|
||||
}
|
||||
|
||||
var roomOccupancyList: RoomOccupancyList = new RoomOccupancyList(
|
||||
new Date(), 0, 0, []
|
||||
new Date(),
|
||||
0,
|
||||
0,
|
||||
[],
|
||||
);
|
||||
|
||||
await fetch(
|
||||
"/api/schedule/rooms?from=" + from_date + "&to=" + to_date,
|
||||
)
|
||||
await fetch("/api/schedule/rooms?from=" + from_date + "&to=" + to_date)
|
||||
.then((response) => {
|
||||
return response.arrayBuffer();
|
||||
})
|
||||
|
@ -104,7 +104,7 @@ const actions = computed(() => [
|
||||
label: t("calendarLink.toHTWKalendar"),
|
||||
icon: "pi pi-home",
|
||||
command: forwardToHTWKalendar,
|
||||
}
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
|
@ -1,8 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
<!--
|
||||
Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import { computed, ComputedRef, inject, Ref, ref, watch } from "vue";
|
||||
import { CalendarOptions, DatesSetArg } from "@fullcalendar/core";
|
||||
import {
|
||||
CalendarOptions,
|
||||
DatesSetArg,
|
||||
EventClickArg,
|
||||
} from "@fullcalendar/core";
|
||||
import allLocales from "@fullcalendar/core/locales-all";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
@ -11,7 +32,7 @@ import iCalenderPlugin from "@fullcalendar/icalendar";
|
||||
import router from "@/router";
|
||||
import { formatYearMonthDay } from "@/helpers/dates.ts";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/vue-query";
|
||||
import tokenStore from "@/store/tokenStore.ts";
|
||||
import { parseICalData } from "@/helpers/ical.ts";
|
||||
import { fetchICalendarEvents } from "@/api/loadICal.ts";
|
||||
@ -25,21 +46,68 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const selectedToken = computed(() => props.token);
|
||||
const op = ref();
|
||||
const clickedEvent = ref();
|
||||
|
||||
const toggle = (info: EventClickArg) => {
|
||||
const start = !info.event.start ? "" : info.event.start;
|
||||
const end = !info.event.end ? "" : info.event.end;
|
||||
|
||||
if (op.value.visible) {
|
||||
clickedEvent.value = null;
|
||||
op.value.hide();
|
||||
return;
|
||||
} else {
|
||||
clickedEvent.value = {
|
||||
title: info.event._def.title,
|
||||
start: start,
|
||||
end: end,
|
||||
notes: info.event._def.extendedProps.notes,
|
||||
allDay: info.event._def.allDay,
|
||||
location: info.event._def.extendedProps.location,
|
||||
};
|
||||
op.value.show(info.jsEvent);
|
||||
op.value.target = info.el;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const selectedToken = computed(() => {
|
||||
return props.token;
|
||||
});
|
||||
|
||||
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||
const date: Ref<Date> = ref(new Date());
|
||||
|
||||
const { data: calendar } = useQuery({
|
||||
const { data: calendar} = useQuery({
|
||||
queryKey: ["userCalendar", selectedToken],
|
||||
queryFn: () =>
|
||||
fetchICalendarEvents(selectedToken.value),
|
||||
queryFn: () => fetchICalendarEvents(selectedToken.value),
|
||||
select: (data) => {
|
||||
return data;
|
||||
},
|
||||
staleTime: 12 * 60 * 60 * 1000, // 12 hours
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchOnReconnect: "always",
|
||||
networkMode: "offlineFirst",
|
||||
enabled: () => tokenStore().token !== "",
|
||||
staleTime: 5000000, // 500 seconds
|
||||
enabled: () => tokenStore().token !== ""
|
||||
});
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const invalidateAndRefetchCalendar = () => {
|
||||
console.debug("invalidateAndRefetchCalendar", selectedToken);
|
||||
const queryKey = ["userCalendar", selectedToken];
|
||||
queryClient.invalidateQueries({queryKey: queryKey}).then(() => {
|
||||
queryClient.refetchQueries({queryKey: queryKey});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
invalidateAndRefetchCalendar
|
||||
});
|
||||
|
||||
const events = computed(() => {
|
||||
return parseICalData(calendar.value);
|
||||
});
|
||||
|
||||
const fullCalendar = ref<InstanceType<typeof FullCalendar>>();
|
||||
@ -58,6 +126,9 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
},
|
||||
eventClick(info) {
|
||||
toggle(info);
|
||||
},
|
||||
height: "auto",
|
||||
views: {
|
||||
week: {
|
||||
@ -112,7 +183,7 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
},
|
||||
});
|
||||
},
|
||||
events: parseICalData(calendar.value),
|
||||
events: events.value,
|
||||
}));
|
||||
|
||||
watch(mobilePage, () => {
|
||||
@ -121,7 +192,22 @@ watch(mobilePage, () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
|
||||
<FullCalendar
|
||||
id="overlay-mount-point"
|
||||
ref="fullCalendar"
|
||||
:options="calendarOptions"
|
||||
>
|
||||
</FullCalendar>
|
||||
|
||||
<OverlayPanel ref="op">
|
||||
<div>
|
||||
<h3>{{ clickedEvent.title }}</h3>
|
||||
<p>Location: {{ clickedEvent.location }}</p>
|
||||
<p>Start: {{ clickedEvent.start?.toLocaleString() }}</p>
|
||||
<p>End: {{ clickedEvent.end?.toLocaleString() }}</p>
|
||||
<p>Notes: {{ clickedEvent.notes }}</p>
|
||||
</div>
|
||||
</OverlayPanel>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
@ -17,53 +17,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { toggleTheme } from "@/helpers/theme.ts";
|
||||
import settingsStore from "@/store/settingsStore.ts";
|
||||
import { computed } from "vue";
|
||||
import { usePrimeVue } from "primevue/config";
|
||||
|
||||
const PrimeVue = usePrimeVue();
|
||||
const primeVue = usePrimeVue();
|
||||
|
||||
const emit = defineEmits(["dark-mode-toggled"]);
|
||||
|
||||
const isDark = ref(true);
|
||||
const darkTheme = ref("lara-dark-blue"),
|
||||
lightTheme = ref("lara-light-blue");
|
||||
const store = settingsStore;
|
||||
|
||||
function toggleTheme() {
|
||||
isDark.value = !isDark.value;
|
||||
setTheme(isDark.value);
|
||||
}
|
||||
const isDark = computed(() => store().isDark);
|
||||
|
||||
function setTheme(shouldBeDark: boolean) {
|
||||
isDark.value = shouldBeDark;
|
||||
const newTheme = isDark.value ? darkTheme.value : lightTheme.value,
|
||||
oldTheme = isDark.value ? lightTheme.value : darkTheme.value;
|
||||
PrimeVue.changeTheme(oldTheme, newTheme, "theme-link", () => {});
|
||||
emit("dark-mode-toggled", isDark.value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// set theme matching browser preference
|
||||
setTheme(
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches,
|
||||
);
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (e) => {
|
||||
setTheme(e.matches);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
id="dark-mode-switcher"
|
||||
size="small"
|
||||
class="p-button-rounded w-full md:w-auto"
|
||||
class="p-button-rounded md:w-auto"
|
||||
style="margin-right: 1rem"
|
||||
:severity="isDark ? 'warning' : 'success'"
|
||||
@click="toggleTheme();"
|
||||
@click="toggleTheme(store, primeVue, emit)"
|
||||
>
|
||||
<i v-if="isDark" class="pi pi-sun"></i>
|
||||
<i v-else class="pi pi-moon"></i>
|
||||
|
45
frontend/src/components/DefaultPageSwitcher.vue
Normal file
45
frontend/src/components/DefaultPageSwitcher.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<!--
|
||||
Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ComputedRef, ref } from "vue";
|
||||
import settingsStore from "../store/settingsStore.ts";
|
||||
|
||||
const pageOptions: ComputedRef<(string | {
|
||||
label: string;
|
||||
value: string;
|
||||
})[]> = computed(() => [...settingsStore().getDefaultPageOptions()]);
|
||||
|
||||
const selectedPage = ref(settingsStore().defaultPage);
|
||||
|
||||
function updateDefaultPage(page: { label: string; value: string }) {
|
||||
settingsStore().setDefaultPage(page);
|
||||
}
|
||||
|
||||
updateDefaultPage(settingsStore().defaultPage);
|
||||
</script>
|
||||
<template>
|
||||
<Dropdown
|
||||
v-model="selectedPage"
|
||||
:options="pageOptions"
|
||||
placeholder="Select a Page"
|
||||
class="w-full md:w-14rem"
|
||||
option-label="label"
|
||||
@change="updateDefaultPage($event.value)"
|
||||
></Dropdown>
|
||||
</template>
|
@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import localeStore from "../store/localeStore.ts";
|
||||
import settingsStore from "../store/settingsStore.ts";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { usePrimeVue } from "primevue/config";
|
||||
import primeVue_de from "@/i18n/translations/primevue/prime_vue_local_de.json";
|
||||
@ -43,7 +43,7 @@ function displayCountry(code: string) {
|
||||
const primeVueConfig = usePrimeVue();
|
||||
|
||||
function updateLocale(locale: string) {
|
||||
localeStore().setLocale(locale);
|
||||
settingsStore().setLocale(locale);
|
||||
|
||||
if (locale === "de") {
|
||||
primeVueConfig.config.locale = primeVue_de;
|
||||
@ -54,7 +54,7 @@ function updateLocale(locale: string) {
|
||||
}
|
||||
}
|
||||
|
||||
updateLocale(localeStore().locale);
|
||||
updateLocale(settingsStore().locale);
|
||||
</script>
|
||||
<template>
|
||||
<Dropdown
|
||||
@ -68,7 +68,14 @@ updateLocale(localeStore().locale);
|
||||
<template #value="slotProps">
|
||||
<div v-if="slotProps.value" class="flex align-items-center">
|
||||
<div class="mr-2 flag">{{ displayIcon(slotProps.value) }}</div>
|
||||
<div style="font-family: 'Twemoji Country Flags', 'Helvetica', 'Comic Sans', serif;">{{ displayCountry(slotProps.value) }}</div>
|
||||
<div
|
||||
style="
|
||||
font-family: "Twemoji Country Flags",
|
||||
"Helvetica", "Comic Sans", serif;
|
||||
"
|
||||
>
|
||||
{{ displayCountry(slotProps.value) }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>
|
||||
{{ slotProps.placeholder }}
|
||||
|
@ -17,13 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import LocaleSwitcher from "./LocaleSwitcher.vue";
|
||||
import DarkModeSwitcher from "./DarkModeSwitcher.vue";
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
const isDark = ref(true);
|
||||
|
||||
|
||||
const items = computed(() => [
|
||||
{
|
||||
@ -67,7 +65,7 @@ const items = computed(() => [
|
||||
label: t("roomFinderPage.roomSchedule") + " (offline)",
|
||||
icon: "pi pi-fw pi-ban",
|
||||
route: "/rooms/occupancy/offline",
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -86,13 +84,6 @@ const items = computed(() => [
|
||||
url: "https://www.htwk-leipzig.de/hochschule/kontakt/datenschutzerklaerung/",
|
||||
},
|
||||
]);
|
||||
|
||||
function handleDarkModeToggled(isDarkVar: boolean) {
|
||||
// Do something with isDark value
|
||||
// For example, update the root isDark value
|
||||
// Assuming the root component has an isDark ref
|
||||
isDark.value = isDarkVar;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -140,8 +131,10 @@ function handleDarkModeToggled(isDarkVar: boolean) {
|
||||
</template>
|
||||
<template #end>
|
||||
<div class="flex align-items-stretch justify-content-center">
|
||||
<DarkModeSwitcher @dark-mode-toggled="handleDarkModeToggled"></DarkModeSwitcher>
|
||||
<LocaleSwitcher></LocaleSwitcher>
|
||||
<!-- Settings Button with Gear Icon -->
|
||||
<router-link v-slot="{ navigate }" :to="`/settings`" custom>
|
||||
<Button icon="pi pi-cog" severity="secondary" rounded text size="large" aria-label="Settings" @click="navigate" />
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</Menubar>
|
||||
|
@ -181,9 +181,7 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
borderColor: event.showFree
|
||||
? "var(--htwk-gruen-600)"
|
||||
: "var(--htwk-grau-60-600)",
|
||||
textColor: event.showFree
|
||||
? "var(--green-50)"
|
||||
: "white",
|
||||
textColor: event.showFree ? "var(--green-50)" : "white",
|
||||
title: event.showFree
|
||||
? t("roomFinderPage.available")
|
||||
: t("roomFinderPage.occupied"),
|
||||
|
@ -79,7 +79,11 @@ const selectedRoom = computed(() => props.room);
|
||||
*/
|
||||
function transformData(data: RoomOccupancyList) {
|
||||
const events = data
|
||||
.decodeOccupancy(selectedRoom.value, new Date(currentDateFrom.value), new Date(currentDateTo.value))
|
||||
.decodeOccupancy(
|
||||
selectedRoom.value,
|
||||
new Date(currentDateFrom.value),
|
||||
new Date(currentDateTo.value),
|
||||
)
|
||||
.map((event, index) => ({
|
||||
id: index,
|
||||
event: event,
|
||||
|
123
frontend/src/helpers/ical.test.ts
Normal file
123
frontend/src/helpers/ical.test.ts
Normal file
@ -0,0 +1,123 @@
|
||||
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
//This program is free software: you can redistribute it and/or modify
|
||||
//it under the terms of the GNU Affero General Public License as published by
|
||||
//the Free Software Foundation, either version 3 of the License, or
|
||||
//(at your option) any later version.
|
||||
|
||||
//This program is distributed in the hope that it will be useful,
|
||||
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//GNU Affero General Public License for more details.
|
||||
|
||||
//You should have received a copy of the GNU Affero General Public License
|
||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import { expect, test } from "vitest";
|
||||
import { exportedForTesting } from "@/helpers/ical.ts";
|
||||
import { CalendarComponent } from "ical";
|
||||
|
||||
// colorizeEvents has only the function to colorize the events that are passed to it
|
||||
test("colorizeEventsSameSummary", () => {
|
||||
const events: CalendarComponent[] = [
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Operations Research",
|
||||
},
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Operations Research",
|
||||
},
|
||||
];
|
||||
|
||||
expect(exportedForTesting.colorizeEvents(events)).toEqual([
|
||||
{ summary: "Operations Research", color: "var(--htwk-rot-200)" },
|
||||
{ summary: "Operations Research", color: "var(--htwk-rot-200)" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("colorizeEventsDifferentSummary", () => {
|
||||
const events: CalendarComponent[] = [
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Algorithmische Mathematik",
|
||||
},
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Funktionale Programmierung",
|
||||
},
|
||||
];
|
||||
|
||||
expect(exportedForTesting.colorizeEvents(events)).toEqual([
|
||||
{ summary: "Algorithmische Mathematik", color: "var(--htwk-rot-200)" },
|
||||
{ summary: "Funktionale Programmierung", color: "var(--htwk-gruen-300)" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("filterEventsDistinct", () => {
|
||||
const events: CalendarComponent[] = [
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Operations Research",
|
||||
},
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Operations Research",
|
||||
},
|
||||
];
|
||||
|
||||
expect(exportedForTesting.filterEventsDistinct(events)).toEqual([
|
||||
{ type: "VEVENT", summary: "Operations Research" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("filterEventsDistinctDifferentSummary", () => {
|
||||
const events: CalendarComponent[] = [
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Algorithmische Mathematik",
|
||||
},
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Funktionale Programmierung",
|
||||
},
|
||||
];
|
||||
|
||||
expect(exportedForTesting.filterEventsDistinct(events)).toEqual(events);
|
||||
});
|
||||
|
||||
test("extractedColorizedEvents", () => {
|
||||
const events: CalendarComponent[] = [
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Operations Research",
|
||||
},
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Operations Research",
|
||||
},
|
||||
];
|
||||
|
||||
expect(exportedForTesting.extractedColorizedEvents(events)).toEqual([
|
||||
{ summary: "Operations Research", color: "var(--htwk-rot-200)" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("extractedColorizedEventsDifferentSummary", () => {
|
||||
const events: CalendarComponent[] = [
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Algorithmische Mathematik",
|
||||
},
|
||||
{
|
||||
type: "VEVENT",
|
||||
summary: "Funktionale Programmierung",
|
||||
},
|
||||
];
|
||||
|
||||
expect(exportedForTesting.extractedColorizedEvents(events)).toEqual([
|
||||
{ summary: "Algorithmische Mathematik", color: "var(--htwk-rot-200)" },
|
||||
{ summary: "Funktionale Programmierung", color: "var(--htwk-gruen-300)" },
|
||||
]);
|
||||
});
|
@ -1,14 +1,50 @@
|
||||
import ICAL from 'ical.js';
|
||||
import { CalendarComponent } from 'ical';
|
||||
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
export function parseICalData(icalData: string | undefined) {
|
||||
//This program is free software: you can redistribute it and/or modify
|
||||
//it under the terms of the GNU Affero General Public License as published by
|
||||
//the Free Software Foundation, either version 3 of the License, or
|
||||
//(at your option) any later version.
|
||||
|
||||
//This program is distributed in the hope that it will be useful,
|
||||
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//GNU Affero General Public License for more details.
|
||||
|
||||
//You should have received a copy of the GNU Affero General Public License
|
||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import ICAL from "ical.js";
|
||||
import { CalendarComponent } from "ical";
|
||||
|
||||
/**
|
||||
* Interface for the color distinction event
|
||||
* @param title Event name
|
||||
* @param color Color code for the event
|
||||
*/
|
||||
export interface ColorDistinctionEvent {
|
||||
summary: string | undefined;
|
||||
color: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses iCal data and returns an array of calendar components
|
||||
* @param icalData iCal data to parse
|
||||
* @returns Array of calendar components
|
||||
*/
|
||||
export function parseICalData(
|
||||
icalData: string | undefined,
|
||||
): CalendarComponent[] {
|
||||
if (icalData === undefined || !icalData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
} else {
|
||||
const jCalData = ICAL.parse(icalData);
|
||||
const comp = new ICAL.Component(jCalData);
|
||||
const vEvents = comp.getAllSubcomponents('vevent');
|
||||
const vEvents = comp.getAllSubcomponents("vevent");
|
||||
const events: CalendarComponent[] = vEvents.map((vevent: CalendarComponent) => {
|
||||
return new ICAL.Event(vevent);
|
||||
});
|
||||
const colorDistinctionEvents: ColorDistinctionEvent[] = extractedColorizedEvents(events);
|
||||
|
||||
return vEvents.map((vevent: CalendarComponent) => {
|
||||
const event = new ICAL.Event(vevent);
|
||||
@ -17,8 +53,86 @@ export function parseICalData(icalData: string | undefined) {
|
||||
title: event.summary,
|
||||
start: event.startDate.toJSDate(),
|
||||
end: event.endDate.toJSDate(),
|
||||
notes: event.description,
|
||||
allDay: event.startDate.isDate,
|
||||
// Include other properties as needed
|
||||
color: colorDistinctionEvents.find(
|
||||
(e: ColorDistinctionEvent) => e.summary === event.summary,
|
||||
)?.color,
|
||||
id: event.uid,
|
||||
location: event.location,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the event names and assigns a color to each event
|
||||
* @param vEvents Array of calendar components
|
||||
* @returns Array of objects with event name and color
|
||||
*/
|
||||
function extractedColorizedEvents(
|
||||
vEvents: CalendarComponent[],
|
||||
): ColorDistinctionEvent[] {
|
||||
return colorizeEvents(filterEventsDistinct(vEvents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out duplicate events
|
||||
* @param vEvents Array of calendar components
|
||||
* @returns Array of calendar components without duplicates
|
||||
*/
|
||||
function filterEventsDistinct(
|
||||
vEvents: CalendarComponent[],
|
||||
): CalendarComponent[] {
|
||||
return vEvents.filter(
|
||||
(vevent: CalendarComponent, index: number, self: CalendarComponent[]) => {
|
||||
return (
|
||||
self.findIndex((v) => {
|
||||
return v.summary === vevent.summary;
|
||||
}) === index
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a color to each event
|
||||
* @param vEvents Array of calendar components
|
||||
* @returns Array of objects with event name and color
|
||||
*/
|
||||
function colorizeEvents(vEvents: CalendarComponent[]): ColorDistinctionEvent[] {
|
||||
return vEvents.map((vevent: CalendarComponent) => {
|
||||
const colors: string[] = [
|
||||
"var(--htwk-rot-200)",
|
||||
"var(--htwk-gruen-300)",
|
||||
"var(--htwk-magenta-400)",
|
||||
"var(--htwk-cyan-400)",
|
||||
"var(--htwk-silbergrau-600)",
|
||||
"var(--htwk-yellow-300)",
|
||||
"var(--htwk-blau-300)",
|
||||
"var(--htwk-dunkelblau-200)",
|
||||
"var(--htwk-rot-400)",
|
||||
"var(--htwk-gruen-400)",
|
||||
"var(--htwk-blau-200)",
|
||||
];
|
||||
|
||||
const randomColor =
|
||||
colors[
|
||||
vEvents.findIndex((e: CalendarComponent) => {
|
||||
return e.summary === vevent.summary;
|
||||
}) % colors.length
|
||||
];
|
||||
|
||||
return {
|
||||
summary: vevent.summary,
|
||||
color: randomColor,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Exported for testing
|
||||
export const exportedForTesting = {
|
||||
extractedColorizedEvents,
|
||||
filterEventsDistinct,
|
||||
colorizeEvents,
|
||||
};
|
||||
|
46
frontend/src/helpers/theme.ts
Normal file
46
frontend/src/helpers/theme.ts
Normal file
@ -0,0 +1,46 @@
|
||||
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
//This program is free software: you can redistribute it and/or modify
|
||||
//it under the terms of the GNU Affero General Public License as published by
|
||||
//the Free Software Foundation, either version 3 of the License, or
|
||||
//(at your option) any later version.
|
||||
|
||||
//This program is distributed in the hope that it will be useful,
|
||||
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//GNU Affero General Public License for more details.
|
||||
|
||||
//You should have received a copy of the GNU Affero General Public License
|
||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import { ref } from "vue";
|
||||
import { PrimeVueChangeTheme } from "primevue/config";
|
||||
import { EmitFn } from "primevue/ts-helpers";
|
||||
import settingsStore from "@/store/settingsStore.ts";
|
||||
|
||||
const darkTheme = ref("lara-dark-blue"),
|
||||
lightTheme = ref("lara-light-blue");
|
||||
|
||||
export type SettingsStore = typeof settingsStore;
|
||||
|
||||
export function toggleTheme(
|
||||
store: SettingsStore,
|
||||
primeVue: { changeTheme: PrimeVueChangeTheme },
|
||||
emit: EmitFn<"dark-mode-toggled"[]>,
|
||||
): void {
|
||||
store().setDarkMode(!store().isDark);
|
||||
setTheme(store, primeVue, emit);
|
||||
}
|
||||
|
||||
export function setTheme(
|
||||
store: SettingsStore,
|
||||
{ changeTheme }: { changeTheme: PrimeVueChangeTheme },
|
||||
emit: EmitFn<"dark-mode-toggled"[]>
|
||||
) {
|
||||
const isDark = ref(store().isDark);
|
||||
const newTheme = isDark.value ? darkTheme.value : lightTheme.value,
|
||||
oldTheme = isDark.value ? lightTheme.value : darkTheme.value;
|
||||
changeTheme(oldTheme, newTheme, "theme-link", () => { });
|
||||
emit("dark-mode-toggled", isDark.value);
|
||||
}
|
@ -1,3 +1,19 @@
|
||||
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
//This program is free software: you can redistribute it and/or modify
|
||||
//it under the terms of the GNU Affero General Public License as published by
|
||||
//the Free Software Foundation, either version 3 of the License, or
|
||||
//(at your option) any later version.
|
||||
|
||||
//This program is distributed in the hope that it will be useful,
|
||||
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//GNU Affero General Public License for more details.
|
||||
|
||||
//You should have received a copy of the GNU Affero General Public License
|
||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const tokenRegex = /^[a-z0-9]{15}$/;
|
||||
const tokenUriRegex = /[?&]token=([a-z0-9]{15})(?:&|$)/;
|
||||
|
||||
|
@ -18,7 +18,7 @@ import { createI18n } from "vue-i18n";
|
||||
import en from "./translations/en.json";
|
||||
import de from "./translations/de.json";
|
||||
import ja from "./translations/ja.json";
|
||||
import localeStore from "../store/localeStore.ts";
|
||||
import settingsStore from "../store/settingsStore.ts";
|
||||
|
||||
// Private instance of VueI18n object
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -27,7 +27,7 @@ let _i18n: any;
|
||||
function setup() {
|
||||
_i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: localeStore().locale,
|
||||
locale: settingsStore().locale,
|
||||
fallbackLocale: "en",
|
||||
messages: {
|
||||
en,
|
||||
|
@ -11,6 +11,10 @@
|
||||
"english": "Englisch",
|
||||
"german": "Deutsch",
|
||||
"japanese": "Japanisch",
|
||||
"notFound": {
|
||||
"headline": "404",
|
||||
"subTitle": "Seite nicht gefunden"
|
||||
},
|
||||
"courseSelection": {
|
||||
"headline": "Willkommen beim HTWKalender",
|
||||
"winterSemester": "Wintersemester",
|
||||
@ -90,7 +94,8 @@
|
||||
"success": "Erfolg",
|
||||
"error": "Fehler",
|
||||
"successDetail": "Kalender erfolgreich gelöscht",
|
||||
"errorDetail": "Fehler beim Löschen des Kalenders"
|
||||
"errorDetail": "Fehler beim Löschen des Kalenders",
|
||||
"successDetailLoad": "Kalender erfolgreich geladen"
|
||||
}
|
||||
},
|
||||
"additionalModules": {
|
||||
@ -249,5 +254,12 @@
|
||||
"subTitle": "Hier findest du die Kalenderansicht von deinem persönlichen Feed.",
|
||||
"searchPlaceholder": "Token",
|
||||
"searchButton": "Kalender laden"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Einstellungen",
|
||||
"subTitle": "Hier kannst du deine Einstellungen bearbeiten.",
|
||||
"language": "Sprache einstellen",
|
||||
"darkMode": "Design auswählen",
|
||||
"defaultPage": "Standardseite"
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,11 @@
|
||||
"privacy": "privacy",
|
||||
"english": "English",
|
||||
"german": "German",
|
||||
"japanese" : "Japanese",
|
||||
"japanese": "Japanese",
|
||||
"notFound": {
|
||||
"headline": "404",
|
||||
"subTitle": "page not found"
|
||||
},
|
||||
"courseSelection": {
|
||||
"headline": "welcome to HTWKalender",
|
||||
"winterSemester": "winter semester",
|
||||
@ -90,7 +94,8 @@
|
||||
"success": "Success",
|
||||
"error": "Error",
|
||||
"successDetail": "calendar successfully deleted",
|
||||
"errorDetail": "calendar could not be deleted"
|
||||
"errorDetail": "calendar could not be deleted",
|
||||
"successDetailLoad": "calendar successfully loaded"
|
||||
}
|
||||
},
|
||||
"additionalModules": {
|
||||
@ -249,5 +254,12 @@
|
||||
"subTitle": "Here you can find the calendar view of your personal feed.",
|
||||
"searchPlaceholder": "calendar token",
|
||||
"searchButton": "load calendar"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Settings",
|
||||
"subTitle": "Here you can change your settings.",
|
||||
"language": "Choose your language",
|
||||
"darkMode": "Switch page theme",
|
||||
"defaultPage": "Default page"
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@
|
||||
"english": "英語",
|
||||
"german": "ドイツ語",
|
||||
"japanese": "日本語",
|
||||
"notFound": {
|
||||
"headline": "404",
|
||||
"subTitle": "ページが見つかりません"
|
||||
},
|
||||
"courseSelection": {
|
||||
"headline": "HTWカレンダーへようこそ",
|
||||
"winterSemester": "冬学期",
|
||||
@ -90,7 +94,8 @@
|
||||
"success": "成功",
|
||||
"error": "エラー",
|
||||
"successDetail": "カレンダーが正常に削除されました",
|
||||
"errorDetail": "カレンダーを削除できませんでした"
|
||||
"errorDetail": "カレンダーを削除できませんでした",
|
||||
"successDetailLoad": "カレンダーが正常に読み込まれました"
|
||||
}
|
||||
},
|
||||
"additionalModules": {
|
||||
@ -249,5 +254,12 @@
|
||||
"subTitle": "ここでは、個人のフィードのカレンダー表示を見つけることができます。",
|
||||
"searchPlaceholder": "カレンダートークン",
|
||||
"searchButton": "ロードカレンダー"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "設定",
|
||||
"subTitle": "ここで設定を編集できます。",
|
||||
"language": "言語",
|
||||
"darkMode": "ダークモード",
|
||||
"defaultPage": "デフォルトページ"
|
||||
}
|
||||
}
|
||||
|
@ -26,41 +26,15 @@
|
||||
"金曜日",
|
||||
"土曜日"
|
||||
],
|
||||
"dayNamesMin": [
|
||||
"日",
|
||||
"月",
|
||||
"火",
|
||||
"水",
|
||||
"木",
|
||||
"金",
|
||||
"土"
|
||||
],
|
||||
"dayNamesShort": [
|
||||
"日",
|
||||
"月",
|
||||
"火",
|
||||
"水",
|
||||
"木",
|
||||
"金",
|
||||
"土"
|
||||
],
|
||||
"dayNamesMin": ["日", "月", "火", "水", "木", "金", "土"],
|
||||
"dayNamesShort": ["日", "月", "火", "水", "木", "金", "土"],
|
||||
"emptyFilterMessage": "オプションなし",
|
||||
"emptyMessage": "結果なし",
|
||||
"emptySearchMessage": "該当なし",
|
||||
"emptySelectionMessage": "選択なし",
|
||||
"endsWith": "終わる",
|
||||
"equals": "等しい",
|
||||
"fileSizeTypes": [
|
||||
"B",
|
||||
"KB",
|
||||
"MB",
|
||||
"GB",
|
||||
"TB",
|
||||
"PB",
|
||||
"EB",
|
||||
"ZB",
|
||||
"YB"
|
||||
],
|
||||
"fileSizeTypes": ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||
"filter": "フィルター",
|
||||
"firstDayOfWeek": 0,
|
||||
"gt": "超える",
|
||||
|
@ -31,6 +31,7 @@ import Card from "primevue/card";
|
||||
import DataView from "primevue/dataview";
|
||||
import Dialog from "primevue/dialog";
|
||||
import Slider from "primevue/slider";
|
||||
import OverlayPanel from "primevue/overlaypanel";
|
||||
import ToggleButton from "primevue/togglebutton";
|
||||
import "primeicons/primeicons.css";
|
||||
import "primeflex/primeflex.css";
|
||||
@ -56,23 +57,23 @@ import Calendar from "primevue/calendar";
|
||||
import i18n from "./i18n";
|
||||
import { VueQueryPlugin } from "@tanstack/vue-query";
|
||||
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
|
||||
polyfillCountryFlagEmojis();
|
||||
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
app.use(VueQueryPlugin, {
|
||||
queryClientConfig: {
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.use(PrimeVue);
|
||||
@ -109,5 +110,6 @@ app.component("ProgressSpinner", ProgressSpinner);
|
||||
app.component("Checkbox", Checkbox);
|
||||
app.component("Skeleton", Skeleton);
|
||||
app.component("Calendar", Calendar);
|
||||
app.component("OverlayPanel", OverlayPanel);
|
||||
|
||||
app.mount("#app");
|
||||
|
@ -21,18 +21,18 @@ import { addHours, addMinutes, interval, subHours } from "date-fns";
|
||||
import { toZonedTime } from "date-fns-tz";
|
||||
|
||||
const testListStart = new Date("2022-01-01T00:00:00Z");
|
||||
var testList : RoomOccupancyList; //= RoomOccupancyList.fromJSON({});
|
||||
var alternating : Uint8Array = new Uint8Array(Array(4).fill(0xF0));
|
||||
var booked : Uint8Array = new Uint8Array(Array(4).fill(0xFF));
|
||||
var empty : Uint8Array = new Uint8Array(Array(4).fill(0x00));
|
||||
var counting : Uint8Array = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
|
||||
var testList: RoomOccupancyList; //= RoomOccupancyList.fromJSON({});
|
||||
var alternating: Uint8Array = new Uint8Array(Array(4).fill(0xf0));
|
||||
var booked: Uint8Array = new Uint8Array(Array(4).fill(0xff));
|
||||
var empty: Uint8Array = new Uint8Array(Array(4).fill(0x00));
|
||||
var counting: Uint8Array = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
|
||||
|
||||
const localTimezone = "Europe/Berlin";
|
||||
|
||||
describe("RoomOccupancyList", () => {
|
||||
beforeEach(() => {
|
||||
alternating = new Uint8Array(Array(4).fill(0xF0));
|
||||
booked = new Uint8Array(Array(4).fill(0xFF));
|
||||
alternating = new Uint8Array(Array(4).fill(0xf0));
|
||||
booked = new Uint8Array(Array(4).fill(0xff));
|
||||
empty = new Uint8Array(Array(4).fill(0x00));
|
||||
counting = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
|
||||
testList = RoomOccupancyList.fromJSON({
|
||||
@ -55,8 +55,8 @@ describe("RoomOccupancyList", () => {
|
||||
{
|
||||
name: "COUNTING",
|
||||
occupancy: new Binary(counting, 0),
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@ -66,12 +66,7 @@ describe("RoomOccupancyList", () => {
|
||||
const rooms = testList["getRooms"]();
|
||||
|
||||
// assert
|
||||
expect(rooms).toEqual([
|
||||
"BOOKED",
|
||||
"EMPTY",
|
||||
"ALTERNATING",
|
||||
"COUNTING"
|
||||
]);
|
||||
expect(rooms).toEqual(["BOOKED", "EMPTY", "ALTERNATING", "COUNTING"]);
|
||||
});
|
||||
|
||||
test("get empty rooms", () => {
|
||||
@ -80,7 +75,7 @@ describe("RoomOccupancyList", () => {
|
||||
start: testListStart,
|
||||
granularity: 60,
|
||||
blocks: 32,
|
||||
rooms: []
|
||||
rooms: [],
|
||||
});
|
||||
|
||||
// act
|
||||
@ -89,7 +84,7 @@ describe("RoomOccupancyList", () => {
|
||||
// assert
|
||||
expect(rooms).toEqual([]);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe("decodeOccupancy", () => {
|
||||
test("generate stubs for missing room", () => {
|
||||
@ -109,8 +104,8 @@ describe("RoomOccupancyList", () => {
|
||||
end: "2022-01-01T08:00:00.000Z",
|
||||
rooms: "MISSING",
|
||||
free: true,
|
||||
stub: true
|
||||
}
|
||||
stub: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@ -131,8 +126,8 @@ describe("RoomOccupancyList", () => {
|
||||
end: "2021-12-31T19:00:00.000Z",
|
||||
rooms: "BOOKED",
|
||||
free: true,
|
||||
stub: true
|
||||
}
|
||||
stub: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@ -151,48 +146,41 @@ describe("RoomOccupancyList", () => {
|
||||
end: "2022-01-01T08:00:00.000Z",
|
||||
rooms: "BOOKED",
|
||||
free: false,
|
||||
stub: false
|
||||
}
|
||||
stub: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sliceOccupancy", () => {
|
||||
test.each([
|
||||
booked,
|
||||
empty,
|
||||
alternating
|
||||
])("getCompleteOccupancy of %j", (occupancy) => {
|
||||
test.each([booked, empty, alternating])(
|
||||
"getCompleteOccupancy of %j",
|
||||
(occupancy) => {
|
||||
// arrange
|
||||
const startTime = new Date(testList.start);
|
||||
const endTime = new Date(addHours(startTime, 32));
|
||||
const sliceInterval = interval(startTime, endTime);
|
||||
|
||||
// act
|
||||
const sliced = testList["sliceOccupancy"](
|
||||
sliceInterval,
|
||||
occupancy
|
||||
)
|
||||
const sliced = testList["sliceOccupancy"](sliceInterval, occupancy);
|
||||
|
||||
// assert
|
||||
expect(sliced).toEqual({
|
||||
decodeSliceStart: startTime,
|
||||
decodeSlice: new Uint8Array(occupancy),
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("throws start out of bounds", () => {
|
||||
// arrange
|
||||
const startTime = new Date(subHours(testList.start,1));
|
||||
const startTime = new Date(subHours(testList.start, 1));
|
||||
const endTime = new Date(addHours(startTime, 32));
|
||||
const sliceInterval = interval(startTime, endTime);
|
||||
|
||||
// act and assert
|
||||
expect(() => {
|
||||
testList["sliceOccupancy"](
|
||||
sliceInterval,
|
||||
alternating
|
||||
)
|
||||
testList["sliceOccupancy"](sliceInterval, alternating);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
@ -204,10 +192,7 @@ describe("RoomOccupancyList", () => {
|
||||
|
||||
// act and assert
|
||||
expect(() => {
|
||||
testList["sliceOccupancy"](
|
||||
sliceInterval,
|
||||
alternating
|
||||
)
|
||||
testList["sliceOccupancy"](sliceInterval, alternating);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
@ -218,10 +203,7 @@ describe("RoomOccupancyList", () => {
|
||||
const sliceInterval = interval(startTime, endTime);
|
||||
|
||||
// act
|
||||
const sliced = testList["sliceOccupancy"](
|
||||
sliceInterval,
|
||||
counting
|
||||
)
|
||||
const sliced = testList["sliceOccupancy"](sliceInterval, counting);
|
||||
|
||||
// assert
|
||||
expect(sliced).toEqual({
|
||||
@ -237,14 +219,11 @@ describe("RoomOccupancyList", () => {
|
||||
const sliceInterval = interval(startTime, endTime);
|
||||
|
||||
// act
|
||||
const sliced = testList["sliceOccupancy"](
|
||||
sliceInterval,
|
||||
counting
|
||||
)
|
||||
const sliced = testList["sliceOccupancy"](sliceInterval, counting);
|
||||
|
||||
// assert
|
||||
expect(sliced).toEqual({
|
||||
decodeSliceStart: addHours(testListStart,8),
|
||||
decodeSliceStart: addHours(testListStart, 8),
|
||||
decodeSlice: new Uint8Array([0x01]),
|
||||
});
|
||||
});
|
||||
@ -257,7 +236,7 @@ describe("RoomOccupancyList", () => {
|
||||
start: testListStart,
|
||||
granularity: 60,
|
||||
blocks: 0,
|
||||
rooms: []
|
||||
rooms: [],
|
||||
});
|
||||
|
||||
// act
|
||||
@ -272,7 +251,9 @@ describe("RoomOccupancyList", () => {
|
||||
const testInterval = testList["getOccupancyInterval"]();
|
||||
|
||||
// assert
|
||||
expect(testInterval).toEqual(interval(testListStart, addHours(testListStart, 32)));
|
||||
expect(testInterval).toEqual(
|
||||
interval(testListStart, addHours(testListStart, 32)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -284,7 +265,7 @@ describe("RoomOccupancyList", () => {
|
||||
new Uint8Array([]),
|
||||
testListStart,
|
||||
15,
|
||||
"Raum"
|
||||
"Raum",
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -298,7 +279,7 @@ describe("RoomOccupancyList", () => {
|
||||
booked,
|
||||
testListStart,
|
||||
15,
|
||||
"BOOKED"
|
||||
"BOOKED",
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -308,8 +289,8 @@ describe("RoomOccupancyList", () => {
|
||||
end: addHours(testListStart, 8).toISOString(),
|
||||
rooms: "BOOKED",
|
||||
free: false,
|
||||
stub: false
|
||||
}
|
||||
stub: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@ -320,7 +301,7 @@ describe("RoomOccupancyList", () => {
|
||||
empty,
|
||||
testListStart,
|
||||
15,
|
||||
"BOOKED"
|
||||
"BOOKED",
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -334,7 +315,7 @@ describe("RoomOccupancyList", () => {
|
||||
alternating,
|
||||
new Date("2024-01-01T00:00:00Z"),
|
||||
15,
|
||||
"ALTERNATING"
|
||||
"ALTERNATING",
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -344,29 +325,29 @@ describe("RoomOccupancyList", () => {
|
||||
end: "2024-01-01T01:00:00.000Z",
|
||||
rooms: "ALTERNATING",
|
||||
free: false,
|
||||
stub: false
|
||||
stub: false,
|
||||
},
|
||||
{
|
||||
start: "2024-01-01T02:00:00.000Z",
|
||||
end: "2024-01-01T03:00:00.000Z",
|
||||
rooms: "ALTERNATING",
|
||||
free: false,
|
||||
stub: false
|
||||
stub: false,
|
||||
},
|
||||
{
|
||||
start: "2024-01-01T04:00:00.000Z",
|
||||
end: "2024-01-01T05:00:00.000Z",
|
||||
rooms: "ALTERNATING",
|
||||
free: false,
|
||||
stub: false
|
||||
stub: false,
|
||||
},
|
||||
{
|
||||
start: "2024-01-01T06:00:00.000Z",
|
||||
end: "2024-01-01T07:00:00.000Z",
|
||||
rooms: "ALTERNATING",
|
||||
free: false,
|
||||
stub: false
|
||||
}
|
||||
stub: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -381,7 +362,7 @@ describe("RoomOccupancyList", () => {
|
||||
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
||||
"ROOM",
|
||||
startTime,
|
||||
endTime
|
||||
endTime,
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -397,7 +378,7 @@ describe("RoomOccupancyList", () => {
|
||||
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
||||
"ROOM",
|
||||
startTime,
|
||||
endTime
|
||||
endTime,
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -413,7 +394,7 @@ describe("RoomOccupancyList", () => {
|
||||
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
||||
"ROOM",
|
||||
startTime,
|
||||
endTime
|
||||
endTime,
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -466,7 +447,7 @@ describe("RoomOccupancyList", () => {
|
||||
rooms: "ROOM",
|
||||
free: true,
|
||||
stub: true,
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@ -479,7 +460,7 @@ describe("RoomOccupancyList", () => {
|
||||
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
||||
"ROOM",
|
||||
startTime,
|
||||
endTime
|
||||
endTime,
|
||||
);
|
||||
|
||||
// assert
|
||||
@ -490,7 +471,7 @@ describe("RoomOccupancyList", () => {
|
||||
rooms: "ROOM",
|
||||
free: true,
|
||||
stub: true,
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -501,10 +482,13 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-01-01T20:00:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
||||
const shiftedTime =
|
||||
RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
||||
|
||||
// assert
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T00:00:00Z"));
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(
|
||||
new Date("2022-01-02T00:00:00Z"),
|
||||
);
|
||||
});
|
||||
|
||||
test("don't shift time on the same day", () => {
|
||||
@ -512,10 +496,13 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-01-02T01:00:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
||||
const shiftedTime =
|
||||
RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
||||
|
||||
// assert
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T02:00:00Z"));
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(
|
||||
new Date("2022-01-02T02:00:00Z"),
|
||||
);
|
||||
});
|
||||
|
||||
test("don't shift if already inside workday", () => {
|
||||
@ -523,10 +510,13 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-01-02T12:30:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
||||
const shiftedTime =
|
||||
RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
||||
|
||||
// assert
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T13:30:00Z"));
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(
|
||||
new Date("2022-01-02T13:30:00Z"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -536,10 +526,13 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-01-02T05:30:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
||||
const shiftedTime =
|
||||
RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
||||
|
||||
// assert
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-01T23:59:59.999Z"));
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(
|
||||
new Date("2022-01-01T23:59:59.999Z"),
|
||||
);
|
||||
});
|
||||
|
||||
test("don't shift time on the same day", () => {
|
||||
@ -547,10 +540,13 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-01-02T22:00:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
||||
const shiftedTime =
|
||||
RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
||||
|
||||
// assert
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T23:00:00Z"));
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(
|
||||
new Date("2022-01-02T23:00:00Z"),
|
||||
);
|
||||
});
|
||||
|
||||
test("don't shift if already inside workday", () => {
|
||||
@ -558,10 +554,13 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-01-02T12:30:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
||||
const shiftedTime =
|
||||
RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
||||
|
||||
// assert
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T13:30:00Z"));
|
||||
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(
|
||||
new Date("2022-01-02T13:30:00Z"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -582,7 +581,11 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-06-02T12:30:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 23, minutes: 59, seconds: 59});
|
||||
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {
|
||||
hours: 23,
|
||||
minutes: 59,
|
||||
seconds: 59,
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(shiftedTime).toEqual(new Date("2022-06-02T21:59:59Z"));
|
||||
@ -593,7 +596,11 @@ describe("RoomOccupancyList", () => {
|
||||
const startTime = new Date("2022-01-02T12:30:00Z");
|
||||
|
||||
// act
|
||||
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 13, minutes: 30, seconds: 0});
|
||||
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {
|
||||
hours: 13,
|
||||
minutes: 30,
|
||||
seconds: 0,
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(shiftedTime).toEqual(new Date("2022-01-02T12:30:00Z"));
|
||||
@ -647,5 +654,4 @@ describe("RoomOccupancyList", () => {
|
||||
expect(shiftedTime).toEqual(new Date("2022-06-01:22:00Z"));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -16,13 +16,31 @@
|
||||
|
||||
import { Binary } from "bson";
|
||||
import { AnonymizedOccupancy } from "./event";
|
||||
import { Duration, NormalizedInterval, add, addDays, addMinutes, clamp, differenceInMinutes, eachDayOfInterval, endOfDay, interval, isAfter, isBefore, isEqual, max, min, startOfDay, subDays } from "date-fns";
|
||||
import {
|
||||
Duration,
|
||||
NormalizedInterval,
|
||||
add,
|
||||
addDays,
|
||||
addMinutes,
|
||||
clamp,
|
||||
differenceInMinutes,
|
||||
eachDayOfInterval,
|
||||
endOfDay,
|
||||
interval,
|
||||
isAfter,
|
||||
isBefore,
|
||||
isEqual,
|
||||
max,
|
||||
min,
|
||||
startOfDay,
|
||||
subDays,
|
||||
} from "date-fns";
|
||||
import { fromZonedTime, toZonedTime } from "date-fns-tz";
|
||||
|
||||
/// The start time of the day. 07:00
|
||||
const START_OF_WORKDAY : Duration = {hours: 7};
|
||||
const START_OF_WORKDAY: Duration = { hours: 7 };
|
||||
/// The end time of the day. 20:00
|
||||
const END_OF_WORKDAY : Duration = {hours: 20};
|
||||
const END_OF_WORKDAY: Duration = { hours: 20 };
|
||||
/// The timezone of the data (Leipzig)
|
||||
const TIMEZONE = "Europe/Berlin";
|
||||
|
||||
@ -32,8 +50,8 @@ const TIMEZONE = "Europe/Berlin";
|
||||
*/
|
||||
class RoomOccupancy {
|
||||
constructor(
|
||||
public name : string,
|
||||
public occupancy : Binary,
|
||||
public name: string,
|
||||
public occupancy: Binary,
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -46,17 +64,17 @@ class RoomOccupancy {
|
||||
*/
|
||||
export class RoomOccupancyList {
|
||||
constructor(
|
||||
public start : Date,
|
||||
public granularity : number,
|
||||
public blocks : number,
|
||||
public rooms : RoomOccupancy[],
|
||||
public start: Date,
|
||||
public granularity: number,
|
||||
public blocks: number,
|
||||
public rooms: RoomOccupancy[],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get a list of all rooms encoded in this occupancy list.
|
||||
* @returns a list of room names.
|
||||
*/
|
||||
public getRooms() : string[] {
|
||||
public getRooms(): string[] {
|
||||
return this.rooms.map((room) => room.name);
|
||||
}
|
||||
|
||||
@ -67,7 +85,11 @@ export class RoomOccupancyList {
|
||||
* @param to the end of the time range.
|
||||
* @returns a list of AnonymizedEventDTO objects representing the occupancy of the room.
|
||||
*/
|
||||
public decodeOccupancy(room : string, from : Date, to : Date) : AnonymizedOccupancy[] {
|
||||
public decodeOccupancy(
|
||||
room: string,
|
||||
from: Date,
|
||||
to: Date,
|
||||
): AnonymizedOccupancy[] {
|
||||
const roomOccupancy = this.rooms.find((r) => r.name === room);
|
||||
|
||||
// Get start and end of decoded time range (within encoded list and requested range)
|
||||
@ -82,19 +104,34 @@ export class RoomOccupancyList {
|
||||
|
||||
let {decodeSliceStart, decodeSlice} = this.sliceOccupancy(
|
||||
decodeInterval,
|
||||
roomOccupancy.occupancy.buffer
|
||||
roomOccupancy.occupancy.buffer,
|
||||
);
|
||||
|
||||
// Decode the occupancy data
|
||||
occupancyList.push(...RoomOccupancyList.decodeOccupancyData(new Uint8Array(decodeSlice), decodeSliceStart, this.granularity, room));
|
||||
occupancyList.push(
|
||||
...RoomOccupancyList.decodeOccupancyData(
|
||||
new Uint8Array(decodeSlice),
|
||||
decodeSliceStart,
|
||||
this.granularity,
|
||||
room,
|
||||
),
|
||||
);
|
||||
|
||||
// add stub events for the time before and after the decoded time range
|
||||
if (!isEqual(from, decodeInterval.start)) {
|
||||
occupancyList.push(...RoomOccupancyList.generateStubEvents(room, from, decodeInterval.start));
|
||||
occupancyList.push(
|
||||
...RoomOccupancyList.generateStubEvents(
|
||||
room,
|
||||
from,
|
||||
decodeInterval.start,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isEqual(to, decodeInterval.end)) {
|
||||
occupancyList.push(...RoomOccupancyList.generateStubEvents(room, decodeInterval.end, to));
|
||||
occupancyList.push(
|
||||
...RoomOccupancyList.generateStubEvents(room, decodeInterval.end, to),
|
||||
);
|
||||
}
|
||||
|
||||
return occupancyList;
|
||||
@ -107,10 +144,16 @@ export class RoomOccupancyList {
|
||||
* @returns a new occupancy byte array with the starting time of the first byte
|
||||
* @throws an error, if the selected time range is outside of the occupancy list.
|
||||
*/
|
||||
private sliceOccupancy(decodeInterval : NormalizedInterval, occupancy : Uint8Array) : {decodeSliceStart: Date, decodeSlice: Uint8Array} {
|
||||
private sliceOccupancy(
|
||||
decodeInterval: NormalizedInterval,
|
||||
occupancy: Uint8Array,
|
||||
): { decodeSliceStart: Date; decodeSlice: Uint8Array } {
|
||||
// Calculate the slice of bytes, that are needed to decode the requested time range
|
||||
// Note: differenceInMinutes calculates (left - right)
|
||||
let minutesFromStart = differenceInMinutes(decodeInterval.start, this.start);
|
||||
let minutesFromStart = differenceInMinutes(
|
||||
decodeInterval.start,
|
||||
this.start,
|
||||
);
|
||||
let minutesToEnd = differenceInMinutes(decodeInterval.end, this.start);
|
||||
|
||||
let firstByte = Math.floor(minutesFromStart / this.granularity / 8);
|
||||
@ -118,13 +161,18 @@ export class RoomOccupancyList {
|
||||
|
||||
// check if firstByte and lastByte are within the bounds of the occupancy array and throw an error if not
|
||||
if (
|
||||
firstByte < 0 || firstByte >= occupancy.length ||
|
||||
lastByte < 0 || lastByte > occupancy.length
|
||||
firstByte < 0 ||
|
||||
firstByte >= occupancy.length ||
|
||||
lastByte < 0 ||
|
||||
lastByte > occupancy.length
|
||||
) {
|
||||
throw new Error("Requested time range is outside of the occupancy list.");
|
||||
}
|
||||
|
||||
let decodeSliceStart = addMinutes(this.start, firstByte * 8 * this.granularity);
|
||||
let decodeSliceStart = addMinutes(
|
||||
this.start,
|
||||
firstByte * 8 * this.granularity,
|
||||
);
|
||||
let decodeSlice = occupancy.buffer.slice(firstByte, lastByte);
|
||||
|
||||
return { decodeSliceStart, decodeSlice: new Uint8Array(decodeSlice) };
|
||||
@ -134,8 +182,11 @@ export class RoomOccupancyList {
|
||||
* Get the decoded time interval within the current occupancy list.
|
||||
* @returns the interval of the occupancy list.
|
||||
*/
|
||||
private getOccupancyInterval() : NormalizedInterval<Date> {
|
||||
return interval(this.start, addMinutes(this.start, this.granularity * this.blocks));
|
||||
private getOccupancyInterval(): NormalizedInterval<Date> {
|
||||
return interval(
|
||||
this.start,
|
||||
addMinutes(this.start, this.granularity * this.blocks),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,9 +197,14 @@ export class RoomOccupancyList {
|
||||
* @param room the room name.
|
||||
* @returns a list of AnonymizedOccupancy objects representing the occupancy of the room.
|
||||
*/
|
||||
public static decodeOccupancyData(occupancy : Uint8Array, start : Date, granularity : number, room : string) : AnonymizedOccupancy[] {
|
||||
public static decodeOccupancyData(
|
||||
occupancy: Uint8Array,
|
||||
start: Date,
|
||||
granularity: number,
|
||||
room: string,
|
||||
): AnonymizedOccupancy[] {
|
||||
let occupancyList = [];
|
||||
let firstOccupancyBit : number | null = null;
|
||||
let firstOccupancyBit: number | null = null;
|
||||
|
||||
// Iterate over all bytes that are in the array
|
||||
for (let byte_i = 0; byte_i < occupancy.length; byte_i++) {
|
||||
@ -156,7 +212,7 @@ export class RoomOccupancyList {
|
||||
|
||||
// Iterate over all bits in the current byte
|
||||
for (let bit_i = 0; bit_i < 8; bit_i++) {
|
||||
let isOccupied = (byte & (1 << (7-bit_i))) !== 0;
|
||||
let isOccupied = (byte & (1 << (7 - bit_i))) !== 0;
|
||||
|
||||
if (firstOccupancyBit === null && isOccupied) {
|
||||
firstOccupancyBit = byte_i * 8 + bit_i;
|
||||
@ -165,13 +221,15 @@ export class RoomOccupancyList {
|
||||
let endTime = addMinutes(start, (byte_i * 8 + bit_i) * granularity);
|
||||
|
||||
// add event between start and end of a block of boolean true values
|
||||
occupancyList.push(new AnonymizedOccupancy(
|
||||
occupancyList.push(
|
||||
new AnonymizedOccupancy(
|
||||
startTime.toISOString(),
|
||||
endTime.toISOString(),
|
||||
room,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
firstOccupancyBit = null;
|
||||
}
|
||||
@ -183,13 +241,15 @@ export class RoomOccupancyList {
|
||||
let startTime = addMinutes(start, firstOccupancyBit * granularity);
|
||||
let endTime = addMinutes(start, occupancy.length * 8 * granularity);
|
||||
|
||||
occupancyList.push(new AnonymizedOccupancy(
|
||||
occupancyList.push(
|
||||
new AnonymizedOccupancy(
|
||||
startTime.toISOString(),
|
||||
endTime.toISOString(),
|
||||
room,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return occupancyList;
|
||||
@ -203,7 +263,11 @@ export class RoomOccupancyList {
|
||||
* @param to The end time within the specified end day.
|
||||
* @returns a list of AnonymizedEventDTO objects, from start to end.
|
||||
*/
|
||||
public static generateStubEvents(rooms : string, from : Date, to : Date) : AnonymizedOccupancy[] {
|
||||
public static generateStubEvents(
|
||||
rooms: string,
|
||||
from: Date,
|
||||
to: Date,
|
||||
): AnonymizedOccupancy[] {
|
||||
from = RoomOccupancyList.shiftTimeForwardInsideWorkday(from);
|
||||
to = RoomOccupancyList.shiftTimeBackwardInsideWorkday(to);
|
||||
|
||||
@ -211,9 +275,15 @@ export class RoomOccupancyList {
|
||||
return [];
|
||||
}
|
||||
|
||||
return eachDayOfInterval({start: from, end: to}).map((day) => {
|
||||
let startTime = max([from, RoomOccupancyList.setTimeOfDay(day, START_OF_WORKDAY)]);
|
||||
let endTime = min([to, RoomOccupancyList.setTimeOfDay(day, END_OF_WORKDAY)]);
|
||||
return eachDayOfInterval({ start: from, end: to }).map((day) => {
|
||||
let startTime = max([
|
||||
from,
|
||||
RoomOccupancyList.setTimeOfDay(day, START_OF_WORKDAY),
|
||||
]);
|
||||
let endTime = min([
|
||||
to,
|
||||
RoomOccupancyList.setTimeOfDay(day, END_OF_WORKDAY),
|
||||
]);
|
||||
|
||||
return new AnonymizedOccupancy(
|
||||
startTime.toISOString(),
|
||||
@ -231,13 +301,15 @@ export class RoomOccupancyList {
|
||||
* @param json the JS object to read from.
|
||||
* @returns a RoomOccupancyList object.
|
||||
*/
|
||||
public static fromJSON(json : any) : RoomOccupancyList {
|
||||
public static fromJSON(json: any): RoomOccupancyList {
|
||||
return new RoomOccupancyList(
|
||||
json.start,
|
||||
json.granularity,
|
||||
json.blocks,
|
||||
json.rooms.map((room : any) => new RoomOccupancy(room.name, room.occupancy)
|
||||
));
|
||||
json.rooms.map(
|
||||
(room: any) => new RoomOccupancy(room.name, room.occupancy),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,11 +318,11 @@ export class RoomOccupancyList {
|
||||
* @param date the date time to check if in bounds.
|
||||
* @returns the shifted time.
|
||||
*/
|
||||
private static shiftTimeForwardInsideWorkday(date : Date) : Date {
|
||||
private static shiftTimeForwardInsideWorkday(date: Date): Date {
|
||||
// if the time of date is after the end of the workday
|
||||
if (isAfter(date, RoomOccupancyList.setTimeOfDay(date, END_OF_WORKDAY))) {
|
||||
// shift the time to the start of the next day
|
||||
return RoomOccupancyList.startOfDay(addDays(date,1));
|
||||
return RoomOccupancyList.startOfDay(addDays(date, 1));
|
||||
} else {
|
||||
return date;
|
||||
}
|
||||
@ -262,11 +334,13 @@ export class RoomOccupancyList {
|
||||
* @param date the date time to check if in bounds.
|
||||
* @returns the shifted time.
|
||||
*/
|
||||
private static shiftTimeBackwardInsideWorkday(date : Date) : Date {
|
||||
private static shiftTimeBackwardInsideWorkday(date: Date): Date {
|
||||
// if the time of date is before the start of the workday
|
||||
if (isBefore(date, RoomOccupancyList.setTimeOfDay(date, START_OF_WORKDAY))) {
|
||||
if (
|
||||
isBefore(date, RoomOccupancyList.setTimeOfDay(date, START_OF_WORKDAY))
|
||||
) {
|
||||
// shift the time to the end of the previous day
|
||||
return RoomOccupancyList.endOfDay(subDays(date,1));
|
||||
return RoomOccupancyList.endOfDay(subDays(date, 1));
|
||||
} else {
|
||||
return date;
|
||||
}
|
||||
@ -278,7 +352,7 @@ export class RoomOccupancyList {
|
||||
* @param time the time as Duration after 00:00.
|
||||
* @returns new date with changed time values.
|
||||
*/
|
||||
private static setTimeOfDay(date : Date, time : Duration) : Date {
|
||||
private static setTimeOfDay(date: Date, time: Duration): Date {
|
||||
return add(RoomOccupancyList.startOfDay(date), time);
|
||||
}
|
||||
|
||||
@ -287,7 +361,7 @@ export class RoomOccupancyList {
|
||||
* @param date
|
||||
* @returns the start of the day.
|
||||
*/
|
||||
private static startOfDay(date : Date) : Date {
|
||||
private static startOfDay(date: Date): Date {
|
||||
const dateInLocalTimezone = toZonedTime(date, TIMEZONE);
|
||||
return fromZonedTime(startOfDay(dateInLocalTimezone), TIMEZONE);
|
||||
}
|
||||
@ -297,10 +371,8 @@ export class RoomOccupancyList {
|
||||
* @param date
|
||||
* @returns the end of the day.
|
||||
*/
|
||||
private static endOfDay(date : Date) : Date {
|
||||
private static endOfDay(date: Date): Date {
|
||||
const dateInLocalTimezone = toZonedTime(date, TIMEZONE);
|
||||
return fromZonedTime(endOfDay(dateInLocalTimezone), TIMEZONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -29,14 +29,23 @@ const EditModules = () => import("../view/editCalendar/EditModules.vue");
|
||||
const CourseSelection = () => import("../view/CourseSelection.vue");
|
||||
const FreeRooms = () => import("../view/FreeRooms.vue");
|
||||
const CalenderViewer = () => import("../view/UserCalendar.vue");
|
||||
const SettingsView = () => import("../view/SettingsView.vue");
|
||||
const NotFound = () => import("../view/NotFound.vue");
|
||||
|
||||
import i18n from "../i18n";
|
||||
import settingsStore from "@/store/settingsStore.ts";
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: "/",
|
||||
name: "default",
|
||||
component: CourseSelection,
|
||||
},
|
||||
{
|
||||
path: "/home",
|
||||
name: "home",
|
||||
component: CourseSelection,
|
||||
},
|
||||
@ -118,17 +127,35 @@ const router = createRouter({
|
||||
name: "rename-modules",
|
||||
component: RenameModules,
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
name: "settings",
|
||||
component: SettingsView,
|
||||
},
|
||||
{
|
||||
path: "/:catchAll(.*)", // Catch all undefined routes
|
||||
name: "not-found",
|
||||
component: NotFound, // Replace with your NotFound component
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
router.beforeEach(async (to, from) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const newLocale = to.params.locale;
|
||||
const prevLocale = from.params.locale;
|
||||
// If the locale hasn't changed, do nothing
|
||||
if (newLocale === prevLocale) {
|
||||
return;
|
||||
}
|
||||
if (!(newLocale === prevLocale)) {
|
||||
i18n.setLocale(newLocale);
|
||||
}
|
||||
|
||||
const userSettings = settingsStore();
|
||||
const defaultPath = userSettings.defaultPage || "/home";
|
||||
|
||||
if (to.path === "/") {
|
||||
next(defaultPath.value);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -16,18 +16,54 @@
|
||||
|
||||
import { defineStore } from "pinia";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import router from "@/router";
|
||||
|
||||
const localeStore = defineStore("localeStore", {
|
||||
const settingsStore = defineStore("settingsStore", {
|
||||
state: () => {
|
||||
return {
|
||||
locale: useLocalStorage("locale", "en"), //useLocalStorage takes in a key of 'count' and default value of 0
|
||||
isDark: true,
|
||||
defaultPage: useLocalStorage("defaultPage", {label: "Home", value: "/home"}),
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
setLocale(locale: string) {
|
||||
this.locale = locale;
|
||||
},
|
||||
setDarkMode(isDark: boolean) {
|
||||
this.isDark = isDark;
|
||||
},
|
||||
getDarkMode(): boolean {
|
||||
return this.isDark;
|
||||
},
|
||||
setDefaultPage(page: {
|
||||
label: string;
|
||||
value: string;
|
||||
}) {
|
||||
this.defaultPage = page;
|
||||
},
|
||||
getDefaultPageOptions(): {
|
||||
label: string;
|
||||
value: string;
|
||||
}[] {
|
||||
// get a string array of all the route names
|
||||
const options: {
|
||||
label: string;
|
||||
value: string;
|
||||
}[] = [];
|
||||
router.getRoutes().forEach((route) => {
|
||||
if (route.name) {
|
||||
if (typeof route.name === "string") {
|
||||
options.push({
|
||||
label: route.name,
|
||||
value: route.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return options;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default localeStore;
|
||||
export default settingsStore;
|
@ -47,9 +47,9 @@ const hasContent = computed(() => {
|
||||
class="flex align-items-center justify-content-center gap-3 mx-2 mb-4 transition-rolldown"
|
||||
:class="{ 'md:mt-8': hideContent }"
|
||||
>
|
||||
<h3 class="text-4xl">
|
||||
<h1 class="text-4xl">
|
||||
{{ headline }}
|
||||
</h3>
|
||||
</h1>
|
||||
<i v-if="icon" :class="icon" style="font-size: 2rem"></i>
|
||||
</div>
|
||||
<div v-if="subTitle" class="flex justify-content-center">
|
||||
|
@ -129,8 +129,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
/>
|
||||
</template>
|
||||
<template #body="slotProps">
|
||||
<div class="flex flex-column sm:flex-row justify-content-between flex-1 column-gap-4 mx-2 md:mx-4">
|
||||
<p class="flex-1 align-self-stretch sm:align-self-center my-2">{{ slotProps.data.room }}</p>
|
||||
<div
|
||||
class="flex flex-column sm:flex-row justify-content-between flex-1 column-gap-4 mx-2 md:mx-4"
|
||||
>
|
||||
<p class="flex-1 align-self-stretch sm:align-self-center my-2">
|
||||
{{ slotProps.data.room }}
|
||||
</p>
|
||||
<Button
|
||||
:label="$t('freeRooms.viewOccupancy')"
|
||||
icon="pi pi-hourglass"
|
||||
|
16
frontend/src/view/NotFound.vue
Normal file
16
frontend/src/view/NotFound.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import DynamicPage from "@/view/DynamicPage.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DynamicPage
|
||||
hide-content
|
||||
:headline="$t('notFound.headline')"
|
||||
:sub-title="$t('notFound.subTitle')"
|
||||
>
|
||||
</DynamicPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
78
frontend/src/view/SettingsView.vue
Normal file
78
frontend/src/view/SettingsView.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import LocaleSwitcher from "@/components/LocaleSwitcher.vue";
|
||||
import { ref } from "vue";
|
||||
import DarkModeSwitcher from "@/components/DarkModeSwitcher.vue";
|
||||
import DefaultPageSwitcher from "@/components/DefaultPageSwitcher.vue";
|
||||
|
||||
const icon = "pi pi-cog";
|
||||
const isDark = ref(true);
|
||||
|
||||
function handleDarkModeToggled(isDarkVar: boolean) {
|
||||
// Do something with isDark value
|
||||
// For example, update the root isDark value
|
||||
// Assuming the root component has an isDark ref
|
||||
isDark.value = isDarkVar;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-column align-items-center mt-0">
|
||||
<div
|
||||
class="flex align-items-center justify-content-center gap-3 mx-2 mb-4 transition-rolldown md:mt-8"
|
||||
>
|
||||
<h1 class="text-4xl">
|
||||
{{ $t("settings.headline") }}
|
||||
</h1>
|
||||
<i v-if="icon" :class="icon" style="font-size: 2rem"></i>
|
||||
</div>
|
||||
<div v-if="$t('settings.subTitle')" class="flex justify-content-center">
|
||||
<h5 class="text-2xl m-2">{{ $t("settings.subTitle") }}</h5>
|
||||
</div>
|
||||
<div class="flex flex-wrap mx-0 gap-2 my-4 w-full lg:w-8">
|
||||
<slot flex-specs="flex-1 m-0" name="selection"></slot>
|
||||
</div>
|
||||
<div
|
||||
class="opacity-100 transition-all transition-duration-500 transition-ease-in-out w-full lg:w-8"
|
||||
>
|
||||
<div class="flex flex-column justify-content-center">
|
||||
<div class="grid my-2">
|
||||
<div class="col text-center">
|
||||
{{ $t("settings.language") }}
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<LocaleSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid my-2">
|
||||
<div class="col text-center">
|
||||
{{ $t("settings.defaultPage") }}
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<DefaultPageSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid my-2">
|
||||
<div class="col text-center">
|
||||
{{ $t("settings.darkMode") }}
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<DarkModeSwitcher
|
||||
@dark-mode-toggled="handleDarkModeToggled"
|
||||
></DarkModeSwitcher>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import CalendarViewer from "@/components/CalendarViewer.vue";
|
||||
import DynamicPage from "@/view/DynamicPage.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
@ -12,7 +11,7 @@ import tokenStore from "@/store/tokenStore.ts";
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
const toast = useToast();
|
||||
|
||||
const token = ref(tokenStore().token || "" as string );
|
||||
const token = ref(tokenStore().token || ("" as string));
|
||||
|
||||
// parse token from query parameter
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@ -23,6 +22,8 @@ if (tokenFromUrl) {
|
||||
loadCalendar();
|
||||
}
|
||||
|
||||
const calendarViewerRef = ref<InstanceType<typeof CalendarViewer>>();
|
||||
|
||||
function loadCalendar() {
|
||||
try {
|
||||
token.value = extractToken(token.value);
|
||||
@ -36,19 +37,26 @@ function loadCalendar() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
moduleStore().removeAllModules();
|
||||
tokenStore().setToken(token.value);
|
||||
|
||||
calendarViewerRef.value?.invalidateAndRefetchCalendar();
|
||||
|
||||
toast.add({
|
||||
severity: "success",
|
||||
summary: t("editCalendarView.toast.success"),
|
||||
detail: t("editCalendarView.toast.successDetailLoad"),
|
||||
life: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (token.value && token.value !== "") {
|
||||
loadCalendar();
|
||||
//loadCalendar();
|
||||
tokenStore().setToken(token.value);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DynamicPage
|
||||
:hide-content="false"
|
||||
@ -66,16 +74,12 @@ onMounted(() => {
|
||||
:label="$t('userCalender.searchButton')"
|
||||
icon="pi pi-refresh"
|
||||
@click="loadCalendar()"
|
||||
/>
|
||||
></Button>
|
||||
</template>
|
||||
<template #content>
|
||||
<CalendarViewer
|
||||
:token="tokenStore().token"
|
||||
/>
|
||||
<CalendarViewer :token="tokenStore().token" ref="calendarViewerRef" />
|
||||
</template>
|
||||
</DynamicPage>
|
||||
</DynamicPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
@ -34,9 +34,9 @@ async function nextStep() {
|
||||
<template>
|
||||
<div class="flex flex-column align-items-center w-full mb-7">
|
||||
<div class="flex align-items-center justify-content-center m-2">
|
||||
<h3>
|
||||
<h1>
|
||||
{{ $t("additionalModules.subTitle") }}
|
||||
</h3>
|
||||
</h1>
|
||||
</div>
|
||||
<AdditionalModuleTable />
|
||||
<div
|
||||
|
2
frontend/src/vite-env.d.ts
vendored
2
frontend/src/vite-env.d.ts
vendored
@ -16,4 +16,4 @@
|
||||
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module 'ical.js';
|
||||
declare module "ical.js";
|
||||
|
@ -21,7 +21,7 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [
|
||||
|
@ -17,63 +17,90 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import basicSsl from "@vitejs/plugin-basic-ssl";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
basicSsl(),
|
||||
VitePWA({
|
||||
mode: 'development',
|
||||
base: '/',
|
||||
injectRegister: 'auto',
|
||||
includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'mask-icon.svg'],
|
||||
mode: "development",
|
||||
base: "/",
|
||||
injectRegister: "auto",
|
||||
includeAssets: ["favicon.ico", "apple-touch-icon.png", "mask-icon.svg"],
|
||||
manifest: {
|
||||
name: 'HTWKalender',
|
||||
short_name: 'HTWKalender',
|
||||
description: 'Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.',
|
||||
theme_color: '#FFFFFF',
|
||||
background_color: '#FFFFFF',
|
||||
display: 'standalone',
|
||||
start_url: '/',
|
||||
name: "HTWKalender",
|
||||
short_name: "HTWKalender",
|
||||
description:
|
||||
"Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.",
|
||||
theme_color: "#FFFFFF",
|
||||
background_color: "#FFFFFF",
|
||||
display: "standalone",
|
||||
start_url: "/",
|
||||
id: "de.htwk-leipzig.htwkalender",
|
||||
screenshots: [
|
||||
{
|
||||
src: "/1280x720.png",
|
||||
sizes: "1280x720",
|
||||
form_factor: "wide",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/390x844.png",
|
||||
sizes: "1170x2532",
|
||||
form_factor: "narrow",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
icons: [
|
||||
{
|
||||
src: "/pwa-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "any"
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/pwa-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "any"
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/pwa-maskable-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "maskable"
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "/pwa-maskable-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable"
|
||||
}
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
},
|
||||
registerType: 'autoUpdate',
|
||||
registerType: "autoUpdate",
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,json,vue,txt,woff2}'],
|
||||
globPatterns: ["**/*.{js,css,html,ico,png,svg,json,vue,txt,woff2}"],
|
||||
cleanupOutdatedCaches: true,
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https?.*/,
|
||||
urlPattern: ({ url }) => url.pathname.startsWith('/api/feed'),
|
||||
method: 'GET',
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'https-calls',
|
||||
cacheName: 'calendar-feed-cache',
|
||||
expiration: {
|
||||
maxAgeSeconds: 12 * 60 * 60, // 12 hours
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: /^https?.*/,
|
||||
handler: "NetworkFirst",
|
||||
options: {
|
||||
cacheName: "https-calls",
|
||||
expiration: {
|
||||
maxEntries: 150,
|
||||
maxAgeSeconds: 30 * 12 * 60 * 60, // 1 month
|
||||
@ -86,11 +113,12 @@ export default defineConfig({
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
/* when using generateSW the PWA plugin will switch to classic */
|
||||
type: 'module',
|
||||
navigateFallback: 'index.html',
|
||||
type: "module",
|
||||
navigateFallback: "index.html",
|
||||
suppressWarnings: true,
|
||||
}
|
||||
})],
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
@ -112,14 +140,14 @@ export default defineConfig({
|
||||
},
|
||||
esbuild: {
|
||||
supported: {
|
||||
'top-level-await': true
|
||||
"top-level-await": true,
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
supported: {
|
||||
'top-level-await': true
|
||||
}
|
||||
"top-level-await": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,11 +1,14 @@
|
||||
// vitest.config.ts
|
||||
import {mergeConfig} from 'vite'
|
||||
import {defineConfig} from 'vitest/config'
|
||||
import viteConfig from './vite.config'
|
||||
import { mergeConfig } from "vite";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import viteConfig from "./vite.config";
|
||||
|
||||
export default mergeConfig(viteConfig, defineConfig({
|
||||
export default mergeConfig(
|
||||
viteConfig,
|
||||
defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
globalSetup: './vitest.global-setup.ts',
|
||||
globalSetup: "./vitest.global-setup.ts",
|
||||
},
|
||||
}))
|
||||
}),
|
||||
);
|
||||
|
@ -1,3 +1,3 @@
|
||||
export const setup = () => {
|
||||
process.env.TZ = 'UTC'
|
||||
}
|
||||
process.env.TZ = "UTC";
|
||||
};
|
||||
|
Reference in New Issue
Block a user