mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-08-07 04:09:17 +02:00
Merge branch '13-popup-informations-in-ical-viewer' into 'main'
Resolve "popup informations in ical viewer" Closes #13 See merge request htwk-software/htwkalender-pwa!7
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"
|
||||
}
|
||||
|
@@ -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,9 @@ $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)*/
|
||||
}
|
||||
}
|
||||
|
@@ -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,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)*/
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -22,12 +22,13 @@ export async function fetchRoomOccupancy(
|
||||
to_date: string,
|
||||
): Promise<RoomOccupancyList> {
|
||||
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,15 +1,36 @@
|
||||
<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";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import iCalenderPlugin from "@fullcalendar/icalendar";
|
||||
import router from "@/router";
|
||||
import { formatYearMonthDay } from "@/helpers/dates.ts";
|
||||
import { formatYearMonthDay, removeTZ } from "@/helpers/dates.ts";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import tokenStore from "@/store/tokenStore.ts";
|
||||
@@ -25,6 +46,31 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const op = ref();
|
||||
const clickedEvent = ref();
|
||||
|
||||
const toggle = (info: EventClickArg) => {
|
||||
const start = !info.event.start ? "" : removeTZ(info.event.start);
|
||||
const end = !info.event.end ? "" : removeTZ(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(() => props.token);
|
||||
|
||||
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||
@@ -32,8 +78,7 @@ const date: Ref<Date> = ref(new Date());
|
||||
|
||||
const { data: calendar } = useQuery({
|
||||
queryKey: ["userCalendar", selectedToken],
|
||||
queryFn: () =>
|
||||
fetchICalendarEvents(selectedToken.value),
|
||||
queryFn: () => fetchICalendarEvents(selectedToken.value),
|
||||
select: (data) => {
|
||||
return data;
|
||||
},
|
||||
@@ -42,6 +87,10 @@ const { data: calendar } = useQuery({
|
||||
staleTime: 5000000, // 500 seconds
|
||||
});
|
||||
|
||||
const events = computed(() => {
|
||||
return parseICalData(calendar.value);
|
||||
});
|
||||
|
||||
const fullCalendar = ref<InstanceType<typeof FullCalendar>>();
|
||||
|
||||
const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
@@ -58,6 +107,9 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
},
|
||||
eventClick(info) {
|
||||
toggle(info);
|
||||
},
|
||||
height: "auto",
|
||||
views: {
|
||||
week: {
|
||||
@@ -112,7 +164,7 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
},
|
||||
});
|
||||
},
|
||||
events: parseICalData(calendar.value),
|
||||
events: events.value,
|
||||
}));
|
||||
|
||||
watch(mobilePage, () => {
|
||||
@@ -121,7 +173,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>
|
||||
|
@@ -63,7 +63,7 @@ onMounted(() => {
|
||||
class="p-button-rounded w-full md:w-auto"
|
||||
style="margin-right: 1rem"
|
||||
:severity="isDark ? 'warning' : 'success'"
|
||||
@click="toggleTheme();"
|
||||
@click="toggleTheme()"
|
||||
>
|
||||
<i v-if="isDark" class="pi pi-sun"></i>
|
||||
<i v-else class="pi pi-moon"></i>
|
||||
|
@@ -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 }}
|
||||
|
@@ -67,7 +67,7 @@ const items = computed(() => [
|
||||
label: t("roomFinderPage.roomSchedule") + " (offline)",
|
||||
icon: "pi pi-fw pi-ban",
|
||||
route: "/rooms/occupancy/offline",
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -140,7 +140,9 @@ function handleDarkModeToggled(isDarkVar: boolean) {
|
||||
</template>
|
||||
<template #end>
|
||||
<div class="flex align-items-stretch justify-content-center">
|
||||
<DarkModeSwitcher @dark-mode-toggled="handleDarkModeToggled"></DarkModeSwitcher>
|
||||
<DarkModeSwitcher
|
||||
@dark-mode-toggled="handleDarkModeToggled"
|
||||
></DarkModeSwitcher>
|
||||
<LocaleSwitcher></LocaleSwitcher>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -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,
|
||||
@@ -92,7 +96,7 @@ const { data: occupations } = useQuery({
|
||||
queryFn: () =>
|
||||
fetchRoomOccupancy(
|
||||
new Date(currentDateFrom.value).toISOString(),
|
||||
new Date(currentDateTo.value).toISOString()
|
||||
new Date(currentDateTo.value).toISOString(),
|
||||
),
|
||||
select: (data) => transformData(data),
|
||||
enabled: () => selectedRoom.value !== "" && currentDateFrom.value !== "",
|
||||
@@ -188,9 +192,7 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
color: event.event.free
|
||||
? "var(--htwk-gruen-500)"
|
||||
: "var(--htwk-grau-60-500)",
|
||||
textColor: event.event.free
|
||||
? "var(--green-50)"
|
||||
: "white",
|
||||
textColor: event.event.free ? "var(--green-50)" : "white",
|
||||
title: event.event.stub
|
||||
? t("roomFinderPage.stub")
|
||||
: event.event.free
|
||||
|
@@ -22,3 +22,7 @@
|
||||
export function formatYearMonthDay(date: Date): string {
|
||||
return date.toISOString().split("T")[0].replace(/-/g, "");
|
||||
}
|
||||
|
||||
export function removeTZ(date: Date): Date {
|
||||
return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
|
||||
}
|
||||
|
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,47 @@
|
||||
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 colorDistinctionEvents = extractedColorizedEvents(vEvents);
|
||||
|
||||
return vEvents.map((vevent: CalendarComponent) => {
|
||||
const event = new ICAL.Event(vevent);
|
||||
@@ -17,8 +50,85 @@ 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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const exportedForTesting = {
|
||||
extractedColorizedEvents,
|
||||
filterEventsDistinct,
|
||||
colorizeEvents,
|
||||
};
|
||||
|
@@ -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})(?:&|$)/;
|
||||
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"privacy": "privacy",
|
||||
"english": "English",
|
||||
"german": "German",
|
||||
"japanese" : "Japanese",
|
||||
"japanese": "Japanese",
|
||||
"courseSelection": {
|
||||
"headline": "welcome to HTWKalender",
|
||||
"winterSemester": "winter semester",
|
||||
|
@@ -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,14 +57,14 @@ 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: {
|
||||
@@ -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);
|
||||
|
||||
if (roomOccupancy === undefined) {
|
||||
@@ -77,23 +99,41 @@ export class RoomOccupancyList {
|
||||
const occupancyList = [];
|
||||
|
||||
// Get start and end of decoded time range (within encoded list and requested range)
|
||||
let decodeInterval = interval(clamp(from, this.getOccupancyInterval()), clamp(to, this.getOccupancyInterval()));
|
||||
let decodeInterval = interval(
|
||||
clamp(from, this.getOccupancyInterval()),
|
||||
clamp(to, this.getOccupancyInterval()),
|
||||
);
|
||||
|
||||
let {decodeSliceStart, decodeSlice} = this.sliceOccupancy(
|
||||
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;
|
||||
@@ -106,10 +146,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);
|
||||
@@ -117,13 +163,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) };
|
||||
@@ -133,8 +184,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),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,9 +199,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++) {
|
||||
@@ -155,7 +214,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;
|
||||
@@ -164,13 +223,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;
|
||||
}
|
||||
@@ -182,13 +243,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;
|
||||
@@ -202,7 +265,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);
|
||||
|
||||
@@ -210,9 +277,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(),
|
||||
@@ -230,13 +303,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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,11 +320,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;
|
||||
}
|
||||
@@ -261,11 +336,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;
|
||||
}
|
||||
@@ -277,7 +354,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);
|
||||
}
|
||||
|
||||
@@ -286,7 +363,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);
|
||||
}
|
||||
@@ -296,10 +373,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -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"
|
||||
|
@@ -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);
|
||||
@@ -46,7 +45,6 @@ onMounted(() => {
|
||||
loadCalendar();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -69,13 +67,9 @@ onMounted(() => {
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<CalendarViewer
|
||||
:token="tokenStore().token"
|
||||
/>
|
||||
<CalendarViewer :token="tokenStore().token" />
|
||||
</template>
|
||||
</DynamicPage>
|
||||
</DynamicPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
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,64 @@
|
||||
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: "/",
|
||||
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?.*/,
|
||||
handler: 'NetworkFirst',
|
||||
handler: "NetworkFirst",
|
||||
options: {
|
||||
cacheName: 'https-calls',
|
||||
cacheName: "https-calls",
|
||||
expiration: {
|
||||
maxEntries: 150,
|
||||
maxAgeSeconds: 30 * 12 * 60 * 60, // 1 month
|
||||
@@ -86,11 +87,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 +114,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