feat: navbar header containers
BREAKING CHANGE: major navigation refactor
This commit is contained in:
parent
4c58699d1f
commit
1348c91c3c
@ -160,7 +160,7 @@ h4
|
||||
--current-header-height: var(--header-height-collapsed)
|
||||
position: relative
|
||||
background-color: white
|
||||
transition: padding-left .2s ease-out
|
||||
transition: padding-left .2s ease-out, margin-top 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
|
||||
margin-top: var(--current-header-height)
|
||||
margin-left: 0
|
||||
|
||||
@ -173,6 +173,9 @@ h4
|
||||
> .container
|
||||
margin: 20px 0
|
||||
|
||||
.navbar__container-radio:checked ~ * &
|
||||
margin-top: calc(var(--current-header-height) + 40px)
|
||||
|
||||
.main__content, .modal__content
|
||||
a
|
||||
text-decoration: underline
|
||||
@ -228,6 +231,7 @@ input[type="submit"],
|
||||
input[type="button"],
|
||||
button,
|
||||
.btn
|
||||
font-family: var(--font-base)
|
||||
outline: 0
|
||||
border: 0
|
||||
box-shadow: 0
|
||||
@ -639,6 +643,9 @@ section
|
||||
z-index: 19
|
||||
pointer-events: none
|
||||
|
||||
.navbar__container-radio:checked ~ &
|
||||
top: calc(80px + var(--header-height))
|
||||
|
||||
@media (max-width: 768px)
|
||||
.ribbon
|
||||
top: calc(20px + var(--header-height-collapsed))
|
||||
|
||||
@ -1,48 +1,89 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './navbar.sass';
|
||||
import * as throttle from 'lodash.throttle';
|
||||
|
||||
|
||||
export const LANGUAGE_SELECT_UTIL_SELECTOR = '[uw-language-select]';
|
||||
const LANGUAGE_SELECT_INITIALIZED_CLASS = 'language-select--initialized';
|
||||
|
||||
export const HEADER_CONTAINER_UTIL_SELECTOR = '.navbar__list-item--container-selector .navbar__link-wrapper';
|
||||
const HEADER_CONTAINER_INITIALIZED_CLASS = '.navbar-header-container--initialized';
|
||||
|
||||
@Utility({
|
||||
selector: LANGUAGE_SELECT_UTIL_SELECTOR,
|
||||
selector: HEADER_CONTAINER_UTIL_SELECTOR,
|
||||
})
|
||||
export class LanguageSelectUtil {
|
||||
export class NavHeaderContainerUtil {
|
||||
_element;
|
||||
checkbox;
|
||||
|
||||
radioButton;
|
||||
closeButton;
|
||||
container;
|
||||
|
||||
wasOpen;
|
||||
|
||||
_throttleUpdateWasOpen;
|
||||
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
throw new Error('Language Select utility needs to be passed an element!');
|
||||
throw new Error('Navbar Header Container utility needs to be passed an element!');
|
||||
}
|
||||
|
||||
if (element.classList.contains(LANGUAGE_SELECT_INITIALIZED_CLASS)) {
|
||||
if (element.classList.contains(HEADER_CONTAINER_INITIALIZED_CLASS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._element = element;
|
||||
this.checkbox = element.querySelector('#lang-checkbox');
|
||||
this.radioButton = document.getElementById(`${this._element.id}-radio`);
|
||||
if (!this.radioButton) {
|
||||
throw new Error('Navbar Header Container utility could not find associated radio button!');
|
||||
}
|
||||
|
||||
window.addEventListener('click', event => this.close(event));
|
||||
this.closeButton = document.getElementById('container-radio-none');
|
||||
if (!this.closeButton) {
|
||||
throw new Error('Navbar Header Container utility could not find radio button for closing!');
|
||||
}
|
||||
|
||||
this.container = document.getElementById(`${this._element.id}-container`);
|
||||
if (!this.container) {
|
||||
throw new Error('Navbar Header Container utility could not find associated container!');
|
||||
}
|
||||
|
||||
element.classList.add(LANGUAGE_SELECT_INITIALIZED_CLASS);
|
||||
const closer = this.container.querySelector('.navbar__container-list-closer');
|
||||
if (closer) {
|
||||
closer.classList.add('navbar__container-list-closer--hidden');
|
||||
}
|
||||
|
||||
this.updateWasOpen();
|
||||
this.throttleUpdateWasOpen = throttle(this.updateWasOpen.bind(this), 100, { leading: false, trailing: true });
|
||||
|
||||
this._element.classList.add(HEADER_CONTAINER_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
close(event) {
|
||||
if (!this._element.contains(event.target) && window.document.contains(event.target)) {
|
||||
this.checkbox.checked = false;
|
||||
start() {
|
||||
window.addEventListener('click', this.clickHandler.bind(this));
|
||||
this.radioButton.addEventListener('change', this.throttleUpdateWasOpen.bind(this));
|
||||
}
|
||||
|
||||
clickHandler() {
|
||||
if (!this.container.contains(event.target) && window.document.contains(event.target) && this.wasOpen) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// TODO
|
||||
close() {
|
||||
this.radioButton.checked = false;
|
||||
this.closeButton.checked = true;
|
||||
this.throttleUpdateWasOpen();
|
||||
}
|
||||
|
||||
isOpen() {
|
||||
return this.radioButton.checked;
|
||||
}
|
||||
|
||||
updateWasOpen() {
|
||||
this.wasOpen = this.isOpen();
|
||||
}
|
||||
|
||||
destroy() { /* TODO */ }
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const NavbarUtils = [
|
||||
LanguageSelectUtil,
|
||||
NavHeaderContainerUtil,
|
||||
];
|
||||
|
||||
@ -21,28 +21,115 @@
|
||||
|
||||
.navbar
|
||||
position: fixed
|
||||
display: flex
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
justify-content: flex-start
|
||||
right: 0
|
||||
top: 0
|
||||
left: var(--asidenav-width-xl)
|
||||
height: var(--header-height)
|
||||
min-height: var(--header-height)
|
||||
background-color: var(--color-primary)
|
||||
color: white
|
||||
z-index: 20
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2)
|
||||
overflow: auto
|
||||
transition: all 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
|
||||
margin: 0
|
||||
padding: 10px 0
|
||||
|
||||
@media (max-width: 1199px)
|
||||
.navbar
|
||||
@media (max-width: 1199px)
|
||||
left: var(--asidenav-width-lg)
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar
|
||||
@media (max-width: 768px), (max-height: 500px)
|
||||
left: 0
|
||||
min-height: var(--header-height-collapsed)
|
||||
padding: 0
|
||||
|
||||
@media (max-width: 768px), (max-height: 500px)
|
||||
.navbar__link-wrapper
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
.navbar__list-wrapper
|
||||
display: flex
|
||||
flex-flow: row nowrap
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
margin: 0 40px
|
||||
|
||||
.navbar__list-left
|
||||
margin-right: 40px
|
||||
|
||||
.navbar__list
|
||||
display: flex
|
||||
flex-flow: row nowrap
|
||||
justify-content: flex-end
|
||||
align-items: center
|
||||
list-style-type: none
|
||||
|
||||
&.navbar__list-left
|
||||
justify-content: flex-start
|
||||
|
||||
& > *
|
||||
display: block
|
||||
|
||||
.navbar__container-list
|
||||
position: relative
|
||||
width: 100%
|
||||
/* margin: 10px 0 0 0 */
|
||||
padding: 0 40px
|
||||
overflow: hidden
|
||||
|
||||
& > ul
|
||||
display: flex
|
||||
flex-flow: row nowrap
|
||||
align-items: center
|
||||
overflow: auto
|
||||
list-style-type: none
|
||||
justify-content: flex-end
|
||||
|
||||
& > *
|
||||
display: block
|
||||
margin-right: 12px
|
||||
|
||||
&:last-child
|
||||
margin-right: 0
|
||||
|
||||
&.navbar__container-list--left > ul
|
||||
justify-content: flex-start
|
||||
|
||||
@media (max-width: 768px), (max-height: 500px)
|
||||
padding: 0
|
||||
|
||||
visibility: collapse
|
||||
margin: 0
|
||||
height: 0
|
||||
transition: height 0.2s cubic-bezier(0.03, 0.43, 0.58, 1), margin 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
|
||||
|
||||
.navbar__container-list-closer
|
||||
position: absolute
|
||||
top: 5px
|
||||
right: 10px
|
||||
width: 20px
|
||||
height: 20px
|
||||
text-align: center
|
||||
|
||||
transform-origin: 10px 10px
|
||||
transform: rotate(-0.25turn)
|
||||
&.navbar__container-list--left
|
||||
transform: rotate(0.25turn)
|
||||
|
||||
opacity: 0.5
|
||||
transition: transform 0.2s, opacity 0.2s ease
|
||||
|
||||
&:hover
|
||||
opacity: 1
|
||||
|
||||
transform: scale(1.4)
|
||||
|
||||
&.navbar__container-list-closer--hidden
|
||||
visibility: hidden
|
||||
|
||||
&.navbar__container-list--left .navbar__container-list-closer
|
||||
left: 14.5px
|
||||
right: auto
|
||||
|
||||
|
||||
// links
|
||||
.navbar__link-wrapper
|
||||
@ -67,12 +154,13 @@
|
||||
padding: 2px 4px
|
||||
text-transform: uppercase
|
||||
font-weight: 600
|
||||
font-size: 16px
|
||||
|
||||
@media (min-width: 769px)
|
||||
@media (min-width: 769px) and (min-height: 501px)
|
||||
.navbar__link-wrapper
|
||||
border: 1px solid rgba(255, 255, 255, 0.7)
|
||||
|
||||
@media (max-width: 768px)
|
||||
@media (max-width: 768px), (max-height: 500px)
|
||||
.navbar__link-wrapper
|
||||
box-shadow: none
|
||||
min-width: 0
|
||||
@ -86,22 +174,42 @@
|
||||
transform: scale(0.65)
|
||||
margin-bottom: 0
|
||||
|
||||
// navbar list
|
||||
.navbar__list
|
||||
white-space: nowrap
|
||||
.navbar__container-link
|
||||
display: block
|
||||
|
||||
@media (min-width: 769px) and (min-height: 501px)
|
||||
border: 1px solid rgba(255, 255, 255, 0.7)
|
||||
text-decoration: none
|
||||
|
||||
+ .navbar__list
|
||||
margin-left: 12px
|
||||
@media (max-width: 768px), (max-height: 500px)
|
||||
text-decoration: underline
|
||||
|
||||
@media (min-width: 769px)
|
||||
.navbar__list:last-of-type
|
||||
padding-right: 40px
|
||||
height: 30px
|
||||
color: var(--color-lightwhite) !important
|
||||
background-color: rgba(0, 0, 0, 0) !important
|
||||
padding: 5px 10px
|
||||
text-transform: uppercase
|
||||
font-weight: 600
|
||||
font-size: 16px
|
||||
outline: 0
|
||||
min-width: 0
|
||||
transition: none
|
||||
cursor: pointer
|
||||
|
||||
&:not(.navbar__container-link--active):hover
|
||||
text-decoration: none
|
||||
|
||||
@media (min-width: 769px) and (min-height: 501px)
|
||||
background-color: var(--color-dark) !important
|
||||
color: var(--color-lightwhite) !important
|
||||
|
||||
&.navbar__container-link--active
|
||||
text-decoration: none
|
||||
|
||||
@media (min-width: 769px) and (min-height: 501px)
|
||||
background-color: var(--color-lightwhite) !important
|
||||
color: var(--color-dark) !important
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar__list
|
||||
+ .navbar__list
|
||||
margin-left: 0
|
||||
padding-right: 40px
|
||||
|
||||
// list item
|
||||
.navbar__list-item
|
||||
@ -124,34 +232,10 @@
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item--lang-wrapper
|
||||
margin-left: 0
|
||||
|
||||
.navbar__list-left
|
||||
flex: 5
|
||||
padding-left: 40px
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar__list-left
|
||||
padding-left: 0
|
||||
|
||||
// "Favorites" list item, only visible on small screens and logged in
|
||||
.navbar__list-item
|
||||
&.navbar__list-item--favorite
|
||||
display: none
|
||||
|
||||
.navbar__list-item--favorite
|
||||
display: none
|
||||
background-color: var(--color-primary)
|
||||
|
||||
.logged-in
|
||||
.navbar__list
|
||||
li.navbar__list-item--favorite,
|
||||
.navbar__list-item--favorite
|
||||
display: inline-block
|
||||
|
||||
@media (min-width: 426px)
|
||||
.logged-in
|
||||
.navbar__list
|
||||
.navbar__list-item--favorite
|
||||
display: none !important
|
||||
.navbar__list-item--favorite
|
||||
display: none !important
|
||||
|
||||
.navbar__list-item--active
|
||||
background-color: var(--color-lightwhite)
|
||||
@ -163,7 +247,7 @@
|
||||
.navbar__list-item--active .navbar__link-wrapper
|
||||
color: var(--color-dark)
|
||||
|
||||
.navbar .navbar__list-item:not(.navbar__list-item--active):not(.navbar__list-item--favorite):hover .navbar__link-wrapper, #lang-checkbox:checked ~ * .navbar__link-wrapper
|
||||
.navbar__list-item:not(.navbar__list-item--active):hover .navbar__link-wrapper
|
||||
background-color: var(--color-dark)
|
||||
color: var(--color-lightwhite)
|
||||
|
||||
@ -186,43 +270,5 @@
|
||||
display: block
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar,
|
||||
.navbar__pushdown
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
.navbar__link-wrapper
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
@media (max-height: 500px)
|
||||
.navbar,
|
||||
.navbar__pushdown
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
.navbar__link-wrapper
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
#lang-dropdown
|
||||
.navbar__container-radio--none, .navbar__container-radio
|
||||
display: none
|
||||
position: fixed
|
||||
top: var(--header-height)
|
||||
right: 0
|
||||
min-width: 200px
|
||||
z-index: 10
|
||||
background-color: white
|
||||
border-radius: 2px
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3)
|
||||
|
||||
select
|
||||
display: block
|
||||
|
||||
button
|
||||
display: block
|
||||
width: 100%
|
||||
|
||||
#lang-checkbox:checked ~ #lang-dropdown
|
||||
display: block
|
||||
|
||||
@media (max-width: 768px)
|
||||
#lang-dropdown
|
||||
top: var(--header-height-collapsed)
|
||||
|
||||
@ -493,7 +493,7 @@ CorrectorsPlaceholder: Korrektoren...
|
||||
CorrectorsDefaulted: Korrektoren-Liste wurde aus bisherigen Übungsblättern diesen Kurses generiert. Es sind keine Daten gespeichert.
|
||||
|
||||
Users: Benutzer
|
||||
HomeHeading: Aktuelle Termine
|
||||
NewsHeading: Aktuelles
|
||||
LoginHeading: Authentifizierung
|
||||
LoginTitle: Authentifizierung
|
||||
ProfileHeading: Benutzereinstellungen
|
||||
@ -508,9 +508,9 @@ NotificationSettingsHeading displayName@Text: Benachrichtigungs-Einstellungen f
|
||||
TokensLastReset: Tokens zuletzt invalidiert
|
||||
TokensResetSuccess: Authorisierungs-Tokens invalidiert
|
||||
|
||||
HomeOpenAllocations: Offene Zentralanmeldungen
|
||||
HomeUpcomingSheets: Anstehende Übungsblätter
|
||||
HomeUpcomingExams: Bevorstehende Prüfungen
|
||||
NewsOpenAllocations: Offene Zentralanmeldungen
|
||||
NewsUpcomingSheets: Anstehende Übungsblätter
|
||||
NewsUpcomingExams: Bevorstehende Prüfungen
|
||||
|
||||
NumCourses num@Int64: #{num} #{pluralDE num "Kurs" "Kurse"}
|
||||
CloseAlert: Schliessen
|
||||
@ -1114,7 +1114,7 @@ InvalidRoute: Konnte URL nicht interpretieren
|
||||
|
||||
MenuOpenCourses: Kurse mit offener Registrierung
|
||||
MenuOpenAllocations: Aktive Zentralanmeldungen
|
||||
MenuHome: Aktuell
|
||||
MenuNews: Aktuell
|
||||
MenuInformation: Informationen
|
||||
MenuLegal: Rechtliche Informationen
|
||||
MenuDataProt: Datenschutzerklärung
|
||||
@ -1561,7 +1561,6 @@ ExamBonusRule: Prüfungsbonus aus Übungsbetrieb
|
||||
ExamNoBonus': Kein automatischer Bonus
|
||||
ExamBonusPoints': Umrechnung von Übungspunkten
|
||||
ExamBonusManual': Manuelle Berechnung
|
||||
ExamGradesExplanation: Diese Ansicht zeigt die selben Daten an, wie die Tabelle von Prüfungsteilnehmern. Anpassen der Teilnehmerdaten und Ergebnisse ist nur dort möglich. Hier können Sie vor Allem einsehen und markieren, welche Prüfungsleistungen von den zuständigen Prüfungsbeauftragten bereits vollständig bearbeitet wurden.
|
||||
|
||||
ExamRegisterForOccurrence: Anmeldung zur Klausur erfolgt durch Anmeldung zu einem Termin/Raum
|
||||
|
||||
|
||||
@ -491,7 +491,7 @@ CorrectorsPlaceholder: Correctors...
|
||||
CorrectorsDefaulted: List of correctors was automatically generated based on those of preceding sheets for this course. No data has been saved, yet.
|
||||
|
||||
Users: Users
|
||||
HomeHeading: Home
|
||||
NewsHeading: News
|
||||
LoginHeading: Authentication
|
||||
LoginTitle: Authentication
|
||||
ProfileHeading: Settings
|
||||
@ -506,9 +506,9 @@ NotificationSettingsHeading displayName: Notification settings for #{displayName
|
||||
TokensLastReset: Tokens last reset
|
||||
TokensResetSuccess: Successfully invalidated all authorisation tokens
|
||||
|
||||
HomeOpenAllocations: Active central allocations
|
||||
HomeUpcomingSheets: Upcoming exercise sheets
|
||||
HomeUpcomingExams: Upcoming exams
|
||||
NewsOpenAllocations: Active central allocations
|
||||
NewsUpcomingSheets: Upcoming exercise sheets
|
||||
NewsUpcomingExams: Upcoming exams
|
||||
|
||||
NumCourses num: #{num} #{pluralEN num "course" "courses"}
|
||||
CloseAlert: Close
|
||||
@ -1113,7 +1113,7 @@ InvalidRoute: Could not interpret url
|
||||
|
||||
MenuOpenCourses: Courses with open registration
|
||||
MenuOpenAllocations: Active central allocations
|
||||
MenuHome: Home
|
||||
MenuNews: News
|
||||
MenuInformation: Information
|
||||
MenuLegal: Legal
|
||||
MenuDataProt: Data protection
|
||||
@ -1559,7 +1559,6 @@ ExamBonusRule: Bonus points from exercises
|
||||
ExamNoBonus': No automatic exam bonus
|
||||
ExamBonusPoints': Compute from exercise achievements
|
||||
ExamBonusManual': Manual computation
|
||||
ExamGradesExplanation: This view shows the same data as the table of exam participants. Changing participant's data and achievements is only possible via the table of exam participants. Primarily, this view allows you to check and adjust which exam achievements were properly handled by the relevant exam offices.
|
||||
|
||||
ExamRegisterForOccurrence: Registration for this exam is done by registering for an occurrence/room
|
||||
|
||||
|
||||
2
routes
2
routes
@ -41,7 +41,7 @@
|
||||
|
||||
/metrics MetricsR GET
|
||||
|
||||
/ HomeR GET !free
|
||||
/ NewsR GET !free
|
||||
/users UsersR GET POST -- no tags, i.e. admins only
|
||||
/users/#CryptoUUIDUser AdminUserR GET POST
|
||||
/users/#CryptoUUIDUser/delete AdminUserDeleteR POST
|
||||
|
||||
@ -101,7 +101,7 @@ import Data.List (cycle)
|
||||
|
||||
-- Import all relevant handler modules here.
|
||||
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
||||
import Handler.Home
|
||||
import Handler.News
|
||||
import Handler.Info
|
||||
import Handler.Help
|
||||
import Handler.Profile
|
||||
|
||||
2735
src/Foundation.hs
2735
src/Foundation.hs
File diff suppressed because it is too large
Load Diff
@ -168,7 +168,7 @@ instance RenderMessage UniWorX MsgLanguage where
|
||||
| ("en" : _) <- lang' = mr MsgEnglish
|
||||
| otherwise = lang
|
||||
where
|
||||
mr = renderMessage foundation ls
|
||||
mr = renderMessage foundation $ lang : filter (/= lang) ls
|
||||
|
||||
embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>)
|
||||
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
|
||||
|
||||
@ -1089,5 +1089,6 @@ postEUsersR tid ssh csh examn = do
|
||||
|
||||
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamUsersHeading) $ do
|
||||
setTitleI $ prependCourseTitle tid ssh csh MsgExamUsersHeading
|
||||
let computedValuesTip = $(i18nWidgetFile "exam-users/computed-values-tip")
|
||||
let computedValuesTip = notificationWidget NotificationBroad Warning
|
||||
$(i18nWidgetFile "exam-users/computed-values-tip")
|
||||
$(widgetFile "exam-users")
|
||||
|
||||
@ -441,4 +441,5 @@ postEGradesR tid ssh csh examn = do
|
||||
|
||||
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading) $ do
|
||||
setTitleI $ prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading
|
||||
let examGradesExplanation = notificationWidget NotificationBroad Info $(i18nWidgetFile "exam-office/exam-grades-explanation")
|
||||
$(widgetFile "exam-office/exam-results")
|
||||
|
||||
@ -30,4 +30,5 @@ postEEGradesR tid ssh coursen examn = do
|
||||
|
||||
siteLayoutMsg (MsgExternalExamGrades coursen examn) $ do
|
||||
setTitleI MsgBreadcrumbExternalExamGrades
|
||||
let examGradesExplanation = notificationWidget NotificationBroad Info $(i18nWidgetFile "exam-office/exam-grades-explanation")
|
||||
$(widgetFile "exam-office/externalExamGrades")
|
||||
|
||||
@ -80,7 +80,7 @@ examOfficeUserInvitationConfig = InvitationConfig{..}
|
||||
return res
|
||||
invitationSuccessMsg _ _ =
|
||||
return $ SomeMessage MsgExamOfficeUserInvitationAccepted
|
||||
invitationUltDest _ _ = return $ SomeRoute HomeR
|
||||
invitationUltDest _ _ = return $ SomeRoute NewsR
|
||||
|
||||
|
||||
makeExamOfficeUsersForm :: Maybe (Set (Either UserEmail UserId)) -> Form (Set (Either UserEmail UserId))
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module Handler.Home where
|
||||
module Handler.News where
|
||||
|
||||
import Import
|
||||
|
||||
@ -9,21 +9,25 @@ import Database.Esqueleto.Utils.TH
|
||||
import qualified Database.Esqueleto as E
|
||||
import qualified Database.Esqueleto.Utils as E
|
||||
|
||||
getHomeR :: Handler Html
|
||||
getHomeR = do
|
||||
getNewsR :: Handler Html
|
||||
getNewsR = do
|
||||
muid <- maybeAuthId
|
||||
defaultLayout $ do
|
||||
setTitleI MsgHomeHeading
|
||||
setTitleI MsgNewsHeading
|
||||
|
||||
when (is _Nothing muid) $
|
||||
notificationWidget NotificationBroad Info $(i18nWidgetFile "pitch")
|
||||
|
||||
case muid of
|
||||
Just uid -> do
|
||||
homeUpcomingExams uid
|
||||
homeUpcomingSheets uid
|
||||
newsUpcomingExams uid
|
||||
newsUpcomingSheets uid
|
||||
Nothing ->
|
||||
$(i18nWidgetFile "unauth-home")
|
||||
$(i18nWidgetFile "unauth-news")
|
||||
|
||||
|
||||
homeUpcomingSheets :: UserId -> Widget
|
||||
homeUpcomingSheets uid = do
|
||||
newsUpcomingSheets :: UserId -> Widget
|
||||
newsUpcomingSheets uid = do
|
||||
cTime <- liftIO getCurrentTime
|
||||
let tableData :: E.LeftOuterJoin
|
||||
(E.InnerJoin (E.InnerJoin (E.SqlExpr (Entity CourseParticipant)) (E.SqlExpr (Entity Course))) (E.SqlExpr (Entity Sheet)))
|
||||
@ -121,11 +125,11 @@ homeUpcomingSheets uid = do
|
||||
, dbtCsvEncode = noCsvEncode
|
||||
, dbtCsvDecode = Nothing
|
||||
}
|
||||
$(widgetFile "home/upcomingSheets")
|
||||
$(widgetFile "news/upcomingSheets")
|
||||
|
||||
|
||||
homeUpcomingExams :: UserId -> Widget
|
||||
homeUpcomingExams uid = do
|
||||
newsUpcomingExams :: UserId -> Widget
|
||||
newsUpcomingExams uid = do
|
||||
now <- liftIO getCurrentTime
|
||||
((Any hasExams, examTable), warningDays) <- liftHandler . runDB $ do
|
||||
User {userWarningDays} <- get404 uid
|
||||
@ -255,6 +259,6 @@ homeUpcomingExams uid = do
|
||||
|
||||
(, userWarningDays) <$> dbTable examDBTableValidator examDBTable
|
||||
|
||||
$(widgetFile "home/upcomingExams")
|
||||
$(widgetFile "news/upcomingExams")
|
||||
|
||||
|
||||
@ -850,17 +850,14 @@ postCsvOptionsR = do
|
||||
, formAttrs = [ asyncSubmitAttr | isModal ]
|
||||
}
|
||||
|
||||
postLangR :: Handler ()
|
||||
postLangR :: Handler Void
|
||||
postLangR = do
|
||||
((langRes, _), _) <- runFormPost $ identifyForm FIDLanguage langForm
|
||||
requestedLang <- selectLanguage' appLanguages . hoistMaybe <$> lookupGlobalPostParam PostLanguage
|
||||
lang' <- runDB . updateUserLanguage $ Just requestedLang
|
||||
|
||||
formResult langRes $ \(lang, route) -> do
|
||||
lang' <- runDB . updateUserLanguage $ Just lang
|
||||
|
||||
app <- getYesod
|
||||
let mr | Just lang'' <- lang' = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang''
|
||||
| otherwise = renderMessage app []
|
||||
addMessage Success . toHtml $ mr MsgLanguageChanged
|
||||
redirect route
|
||||
app <- getYesod
|
||||
let mr | Just lang'' <- lang' = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang''
|
||||
| otherwise = renderMessage app []
|
||||
addMessage Success . toHtml $ mr MsgLanguageChanged
|
||||
|
||||
invalidArgs ["Language form required"]
|
||||
redirect . fromMaybe NewsR =<< lookupGlobalGetParam GetReferer
|
||||
|
||||
@ -399,7 +399,7 @@ invitationR' InvitationConfig{..} = liftHandler $ do
|
||||
Nothing -> do
|
||||
addMessageI Info MsgInvitationDeclined
|
||||
deleteBy . UniqueInvitation itEmail $ invRef @junction fid
|
||||
return . Just $ SomeRoute HomeR
|
||||
return . Just $ SomeRoute NewsR
|
||||
Just (jData, formCtx) -> do
|
||||
let junction = review _InvitableJunction (invitee, fid, jData)
|
||||
mResult <- invitationInsertHook itEmail fEnt iData junction formCtx $ insertUniqueEntity junction
|
||||
|
||||
@ -10,6 +10,7 @@ import Model.Submission as Import
|
||||
import Model.Tokens as Import
|
||||
import Utils.Tokens as Import
|
||||
import Utils.Frontend.Modal as Import
|
||||
import Utils.Frontend.Notification as Import
|
||||
import Utils.Lens as Import
|
||||
|
||||
import Settings as Import
|
||||
|
||||
@ -46,6 +46,7 @@ import Data.Scientific
|
||||
import Data.Time.Clock (NominalDiffTime, nominalDay)
|
||||
|
||||
import Utils
|
||||
import Utils.Frontend.Notification
|
||||
-- import Utils.Message
|
||||
-- import Utils.PathPiece
|
||||
-- import Utils.Route
|
||||
@ -869,26 +870,15 @@ wformMessage :: (MonadHandler m) => Message -> WForm m ()
|
||||
wformMessage = void . aFormToWForm . aformMessage
|
||||
|
||||
formMessage :: (MonadHandler m) => Message -> MForm m (FormResult (), FieldView site)
|
||||
formMessage Message{..} = do
|
||||
formMessage msg = do
|
||||
return (FormSuccess (), FieldView
|
||||
{ fvLabel = mempty
|
||||
, fvTooltip = Nothing
|
||||
, fvId = idFormMessageNoinput
|
||||
, fvErrors = Nothing
|
||||
, fvRequired = False
|
||||
, fvInput = [whamlet|
|
||||
$newline never
|
||||
<div .notification .notification-#{toPathPiece messageStatus} .fa-#{maybe defaultIcon iconText messageIcon}>
|
||||
<div .notification__content>
|
||||
#{messageContent}
|
||||
|]
|
||||
, fvInput = notification NotificationNarrow msg
|
||||
})
|
||||
where
|
||||
defaultIcon = case messageStatus of
|
||||
Success -> "check-circle"
|
||||
Info -> "info-circle"
|
||||
Warning -> "exclamation-circle"
|
||||
Error -> "exclamation-triangle"
|
||||
|
||||
---------------------
|
||||
-- Form evaluation --
|
||||
|
||||
43
src/Utils/Frontend/Notification.hs
Normal file
43
src/Utils/Frontend/Notification.hs
Normal file
@ -0,0 +1,43 @@
|
||||
module Utils.Frontend.Notification
|
||||
( NotificationType(..)
|
||||
, notification
|
||||
, notificationWidget
|
||||
) where
|
||||
|
||||
import ClassyPrelude.Yesod
|
||||
import Settings
|
||||
|
||||
import Utils.Message
|
||||
import Utils.Icon
|
||||
|
||||
import Control.Lens
|
||||
import Control.Lens.Extras (is)
|
||||
|
||||
|
||||
data NotificationType
|
||||
= NotificationNarrow
|
||||
| NotificationBroad
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
|
||||
makePrisms ''NotificationType
|
||||
|
||||
|
||||
notification :: NotificationType
|
||||
-> Message
|
||||
-> WidgetFor site ()
|
||||
notification nType Message{ messageIcon = messageIcon', .. }
|
||||
= $(widgetFile "widgets/notification")
|
||||
where
|
||||
messageIcon = fromMaybe defaultIcon messageIcon'
|
||||
defaultIcon = case messageStatus of
|
||||
Success -> IconNotificationSuccess
|
||||
Info -> IconNotificationInfo
|
||||
Warning -> IconNotificationWarning
|
||||
Error -> IconNotificationError
|
||||
|
||||
notificationWidget :: Yesod site
|
||||
=> NotificationType
|
||||
-> MessageStatus
|
||||
-> WidgetFor site ()
|
||||
-> WidgetFor site ()
|
||||
notificationWidget nType ms = notification nType <=< messageWidget ms
|
||||
@ -63,44 +63,58 @@ data Icon
|
||||
| IconApplicationVeto
|
||||
| IconApplicationFiles
|
||||
| IconTooltipDefault
|
||||
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
||||
| IconNotificationSuccess
|
||||
| IconNotificationInfo
|
||||
| IconNotificationWarning
|
||||
| IconNotificationError
|
||||
| IconFavourite
|
||||
| IconLanguage
|
||||
| IconNavContainerClose
|
||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Generic, Typeable)
|
||||
|
||||
iconText :: Icon -> Text
|
||||
iconText = \case
|
||||
IconNew -> "seedling"
|
||||
IconOK -> "check"
|
||||
IconNotOK -> "times"
|
||||
IconWarning -> "exclamation"
|
||||
IconProblem -> "bolt"
|
||||
IconVisible -> "eye"
|
||||
IconInvisible -> "eye-slash"
|
||||
IconCourse -> "graduation-cap"
|
||||
IconEnrolTrue -> "user-plus"
|
||||
IconEnrolFalse -> "user-slash"
|
||||
IconPlanned -> "cog"
|
||||
IconAnnounce -> "bullhorn"
|
||||
IconExam -> "poll-h"
|
||||
IconExamRegisterTrue -> "calendar-check"
|
||||
IconExamRegisterFalse -> "calendar-times"
|
||||
IconCommentTrue -> "comment-alt"
|
||||
IconCommentFalse -> "comment-slash" -- comment-alt-slash is not available for free
|
||||
IconLink -> "link"
|
||||
IconFileDownload -> "file-download"
|
||||
IconFileUpload -> "file-upload"
|
||||
IconFileZip -> "file-archive"
|
||||
IconFileCSV -> "file-csv"
|
||||
IconSFTQuestion -> "question-circle" -- for SheetFileType only, should all be round (similar)
|
||||
IconSFTHint -> "life-ring" -- for SheetFileType only
|
||||
IconSFTSolution -> "exclamation-circle" -- for SheetFileType only
|
||||
IconSFTMarking -> "check-circle" -- for SheetFileType only
|
||||
IconEmail -> "envelope"
|
||||
IconRegisterTemplate -> "file-alt"
|
||||
IconApplyTrue -> "file-alt"
|
||||
IconApplyFalse -> "trash"
|
||||
IconNoCorrectors -> "user-slash"
|
||||
IconApplicationVeto -> "times"
|
||||
IconApplicationFiles -> "file-alt"
|
||||
IconTooltipDefault -> "question-circle"
|
||||
IconNew -> "seedling"
|
||||
IconOK -> "check"
|
||||
IconNotOK -> "times"
|
||||
IconWarning -> "exclamation"
|
||||
IconProblem -> "bolt"
|
||||
IconVisible -> "eye"
|
||||
IconInvisible -> "eye-slash"
|
||||
IconCourse -> "graduation-cap"
|
||||
IconEnrolTrue -> "user-plus"
|
||||
IconEnrolFalse -> "user-slash"
|
||||
IconPlanned -> "cog"
|
||||
IconAnnounce -> "bullhorn"
|
||||
IconExam -> "poll-h"
|
||||
IconExamRegisterTrue -> "calendar-check"
|
||||
IconExamRegisterFalse -> "calendar-times"
|
||||
IconCommentTrue -> "comment-alt"
|
||||
IconCommentFalse -> "comment-alt-slash"
|
||||
IconLink -> "link"
|
||||
IconFileDownload -> "file-download"
|
||||
IconFileUpload -> "file-upload"
|
||||
IconFileZip -> "file-archive"
|
||||
IconFileCSV -> "file-csv"
|
||||
IconSFTQuestion -> "question-circle" -- for SheetFileType only, should all be round (similar)
|
||||
IconSFTHint -> "life-ring" -- for SheetFileType only
|
||||
IconSFTSolution -> "exclamation-circle" -- for SheetFileType only
|
||||
IconSFTMarking -> "check-circle" -- for SheetFileType only
|
||||
IconEmail -> "envelope"
|
||||
IconRegisterTemplate -> "file-alt"
|
||||
IconApplyTrue -> "file-alt"
|
||||
IconApplyFalse -> "trash"
|
||||
IconNoCorrectors -> "user-slash"
|
||||
IconApplicationVeto -> "times"
|
||||
IconApplicationFiles -> "file-alt"
|
||||
IconTooltipDefault -> "question-circle"
|
||||
IconNotificationSuccess -> "check-circle"
|
||||
IconNotificationInfo -> "info-circle"
|
||||
IconNotificationWarning -> "exclamation-circle"
|
||||
IconNotificationError -> "exclamation-triangle"
|
||||
IconFavourite -> "star"
|
||||
IconLanguage -> "flag-alt"
|
||||
IconNavContainerClose -> "chevron-up"
|
||||
|
||||
instance Universe Icon
|
||||
instance Finite Icon
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
module Utils.Lens.TH
|
||||
( makeLenses_, makeClassyFor_
|
||||
( lensRules_
|
||||
, makeLenses_, makeClassyFor_
|
||||
, multifocusG, multifocusL
|
||||
) where
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ module Utils.Message
|
||||
, addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget
|
||||
, statusToUrgencyClass
|
||||
, Message(..)
|
||||
, messageIconI
|
||||
, messageIconI, messageIconIHamlet, messageIconWidget
|
||||
, messageI, messageIHamlet, messageFile, messageWidget, messageTooltip
|
||||
) where
|
||||
|
||||
@ -163,6 +163,15 @@ messageIHamlet ms iHamlet = do
|
||||
let mi = Nothing
|
||||
Message ms <$> withUrlRenderer (iHamlet $ toHtml . mr) <*> pure mi
|
||||
|
||||
messageIconIHamlet :: ( MonadHandler m
|
||||
, RenderMessage (HandlerSite m) msg
|
||||
, HandlerSite m ~ site
|
||||
) => MessageStatus -> Icon -> HtmlUrlI18n msg (Route site) -> m Message
|
||||
messageIconIHamlet messageStatus (Just -> messageIcon) iHamlet = do
|
||||
mr <- getMessageRender
|
||||
messageContent <- withUrlRenderer (iHamlet $ toHtml . mr)
|
||||
return Message{..}
|
||||
|
||||
addMessageFile :: MessageStatus -> FilePath -> ExpQ
|
||||
addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|]
|
||||
|
||||
@ -189,6 +198,15 @@ messageWidget mc wgt = do
|
||||
PageContent{pageBody} <- liftHandler $ widgetToPageContent wgt
|
||||
messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
||||
|
||||
messageIconWidget :: forall m site.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ site
|
||||
, Yesod site
|
||||
) => MessageStatus -> Icon -> WidgetFor site () -> m Message
|
||||
messageIconWidget ms mi wgt = do
|
||||
PageContent{pageBody} <- liftHandler $ widgetToPageContent wgt
|
||||
messageIconIHamlet ms mi (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
||||
|
||||
|
||||
getMessages :: MonadHandler m => m [Message]
|
||||
getMessages = fmap decodeMessage <$> ClassyPrelude.Yesod.getMessages
|
||||
|
||||
@ -58,6 +58,7 @@ data GlobalPostParam = PostFormIdentifier
|
||||
| PostDBCsvImportAction
|
||||
| PostLoginDummy
|
||||
| PostExamAutoOccurrencePrevious
|
||||
| PostLanguage
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||
|
||||
instance Universe GlobalPostParam
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
$newline never
|
||||
$if not isModal
|
||||
$with containers <- filter isNavHeaderContainer nav
|
||||
$if not (null containers)
|
||||
<input name=nav-container type=radio .navbar__container-radio--none checked #container-radio-none>
|
||||
$forall (_, containerIdent, _, _) <- containers
|
||||
<input name=nav-container type=radio .navbar__container-radio ##{containerIdent}-radio>
|
||||
|
||||
<!-- secondary navigation at the side -->
|
||||
^{asidenav}
|
||||
<!-- navigation -->
|
||||
@ -15,7 +21,7 @@ $if not isModal
|
||||
|
||||
$if not isModal
|
||||
<!-- breadcrumbs -->
|
||||
$if not $ Just HomeR == mcurrentRoute
|
||||
$if not $ Just NewsR == mcurrentRoute
|
||||
^{breadcrumbsWgt}
|
||||
|
||||
<div .main__content-body>
|
||||
|
||||
@ -3,7 +3,5 @@ $newline never
|
||||
^{closeWgt}
|
||||
<section>
|
||||
$if hasUsers
|
||||
<div .notification .notification-info .fa-question .notification--broad>
|
||||
<div .notification__content>
|
||||
_{MsgExamGradesExplanation}
|
||||
^{examGradesExplanation}
|
||||
^{examUsersTable}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
$newline never
|
||||
$if hasUsers
|
||||
<div .notification .notification-info .fa-question .notification--broad>
|
||||
<div .notification__content>
|
||||
_{MsgExamGradesExplanation}
|
||||
^{examGradesExplanation}
|
||||
^{table}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
$newline never
|
||||
<p>
|
||||
Diese Ansicht zeigt die selben Daten an, wie die Tabelle von Prüfungsteilnehmern.<br />
|
||||
Anpassen der Teilnehmerdaten und Ergebnisse ist nur dort möglich.
|
||||
|
||||
<p>
|
||||
Hier können Sie vor Allem einsehen und markieren, welche #
|
||||
Prüfungsleistungen von den zuständigen Prüfungsbeauftragten bereits #
|
||||
vollständig bearbeitet wurden.
|
||||
@ -0,0 +1,9 @@
|
||||
$newline never
|
||||
<p>
|
||||
This view shows the same data as the table of exam participants.<br />
|
||||
Changing participant's data and achievements is only possible via #
|
||||
the table of exam participants.
|
||||
|
||||
<p>
|
||||
Primarily, this view allows you to check and adjust which exam #
|
||||
achievements were properly handled by the relevant exam offices.
|
||||
@ -1,24 +1,22 @@
|
||||
$newline never
|
||||
<div .notification .notification-warning .fa-exclamation-triangle .notification--broad>
|
||||
<div .notification__content>
|
||||
<p>
|
||||
Die Tabelle enthält Werte, die automatisch berechnet wurden.
|
||||
<p>
|
||||
Automatisch berechnete Werte (Bonus und Prüfungsergebnis) werden weder dem #
|
||||
entsprechenden Teilnehmer angezeigt, noch an das Prüfungsamt gemeldet #
|
||||
bevor sie manuell übernommen wurden.<br />
|
||||
Hierzu können Sie die Aktion „Berechnetes Prüfungsergebnis übernehmen“ #
|
||||
verwenden.
|
||||
<p>
|
||||
Sie können die automatisch berechneten Werte auch manuell (via CSV-Import) #
|
||||
überschreiben.<br />
|
||||
Wenn die so gesetzten Werte nicht den automatisch Berechneten entsprechen #
|
||||
sind sie <i>inkonsistent</i>.
|
||||
<p>
|
||||
Automatisch berechnete Werte sind gekennzeichnet wie folgt:
|
||||
<p>
|
||||
Die Tabelle enthält Werte, die automatisch berechnet wurden.
|
||||
<p>
|
||||
Automatisch berechnete Werte (Bonus und Prüfungsergebnis) werden weder dem #
|
||||
entsprechenden Teilnehmer angezeigt, noch an das Prüfungsamt gemeldet #
|
||||
bevor sie manuell übernommen wurden.<br />
|
||||
Hierzu können Sie die Aktion „Berechnetes Prüfungsergebnis übernehmen“ #
|
||||
verwenden.
|
||||
<p>
|
||||
Sie können die automatisch berechneten Werte auch manuell (via CSV-Import) #
|
||||
überschreiben.<br />
|
||||
Wenn die so gesetzten Werte nicht den automatisch Berechneten entsprechen #
|
||||
sind sie <i>inkonsistent</i>.
|
||||
<p>
|
||||
Automatisch berechnete Werte sind gekennzeichnet wie folgt:
|
||||
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatisch berechnet
|
||||
<td style="padding: 0 7px" .table__td>Normaler Wert
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inkonsistent
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatisch berechnet
|
||||
<td style="padding: 0 7px" .table__td>Normaler Wert
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inkonsistent
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
$newline never
|
||||
<div .notification .notification-warning .fa-exclamation-triangle .notification--broad>
|
||||
<div .notification__content>
|
||||
<p>
|
||||
This table contains values that were computed automatically.
|
||||
<p>
|
||||
Values computed automatically (bonus and result) are shown to neither the #
|
||||
participant nor relevant exam offices until they are manually accepted.<br />
|
||||
To do this you may use the action “Accept computed result”.
|
||||
<p>
|
||||
You are also able to override the automatically computed values manually #
|
||||
(via CSV import).<br />
|
||||
<p>
|
||||
This table contains values that were computed automatically.
|
||||
<p>
|
||||
Values computed automatically (bonus and result) are shown to neither the #
|
||||
participant nor relevant exam offices until they are manually accepted.<br />
|
||||
To do this you may use the action “Accept computed result”.
|
||||
<p>
|
||||
You are also able to override the automatically computed values manually #
|
||||
(via CSV import).<br />
|
||||
|
||||
If values thus overriden do not match the automatically computed values #
|
||||
they are considered <i>inconsistent</i>.
|
||||
<p>
|
||||
Automatically computed values are marked as follows:
|
||||
If values thus overriden do not match the automatically computed values #
|
||||
they are considered <i>inconsistent</i>.
|
||||
<p>
|
||||
Automatically computed values are marked as follows:
|
||||
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatically computed
|
||||
<td style="padding: 0 7px" .table__td>Normal value
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inconsistent
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatically computed
|
||||
<td style="padding: 0 7px" .table__td>Normal value
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inconsistent
|
||||
|
||||
11
templates/i18n/pitch/de-de-formal.hamlet
Normal file
11
templates/i18n/pitch/de-de-formal.hamlet
Normal file
@ -0,0 +1,11 @@
|
||||
$newline never
|
||||
<p>
|
||||
Uni2work ist ein Lehrverwaltungssystem, welches an der #
|
||||
Ludwig-Maximilians-Universität München entwickelt und eingesetzt #
|
||||
wird.
|
||||
<p>
|
||||
Insbesondere unterstützt Uni2work teilnehmende Institute bei #
|
||||
Übungsbetrieb, Prüfungs- und Notenverwaltung und bietet vollständige #
|
||||
Kurshomepages inkl. Vorlesungsmaterial und Terminen.
|
||||
|
||||
|
||||
8
templates/i18n/pitch/en-eu.hamlet
Normal file
8
templates/i18n/pitch/en-eu.hamlet
Normal file
@ -0,0 +1,8 @@
|
||||
$newline never
|
||||
<p>
|
||||
Uni2work is a teaching management system, developed and deployed at #
|
||||
Ludwig-Maximilians-Universität München.
|
||||
<p>
|
||||
Uni2work supports participating departments in managing their course #
|
||||
exercises, exams and exam achievements, and provides complete course #
|
||||
homepages including course material and dates.
|
||||
@ -1,6 +1,6 @@
|
||||
$newline never
|
||||
<section>
|
||||
<h2>_{MsgHomeUpcomingExams}
|
||||
<h2>_{MsgNewsUpcomingExams}
|
||||
$if hasExams
|
||||
^{examTable}
|
||||
$else
|
||||
@ -1,4 +1,4 @@
|
||||
$newline never
|
||||
<section>
|
||||
<h2>_{MsgHomeUpcomingSheets}
|
||||
<h2>_{MsgNewsUpcomingSheets}
|
||||
^{sheetTable}
|
||||
@ -28,12 +28,9 @@ $newline never
|
||||
<div .asidenav__link-label>#{courseName}
|
||||
<div .asidenav__nested-list-wrapper>
|
||||
<ul .asidenav__nested-list.list--iconless>
|
||||
$forall (MenuItem{menuItemType, menuItemLabel}, route) <- pageActions
|
||||
$case menuItemType
|
||||
$of PageActionPrime
|
||||
<li .asidenav__nested-list-item>
|
||||
<a .asidenav__link-wrapper href=#{route}>_{menuItemLabel}
|
||||
$of _
|
||||
$forall (NavLink{navLabel}, route) <- pageActions
|
||||
<li .asidenav__nested-list-item>
|
||||
<a .asidenav__link-wrapper href=#{route}>_{navLabel}
|
||||
|
||||
<div .asidenav__sigillum>
|
||||
<img src=@{StaticR img_lmu_sigillum_svg}>
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
.breadcrumbs__container {
|
||||
position: relative;
|
||||
color: var(--color-lightwhite);
|
||||
padding: 4px 13px;
|
||||
background-color: var(--color-dark);
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
@media (min-width: 426px) {
|
||||
.breadcrumbs__container {
|
||||
padding: 7px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.breadcrumbs__container {
|
||||
padding: 7px 40px;
|
||||
}
|
||||
}
|
||||
|
||||
a.breadcrumbs__link {
|
||||
color: var(--color-lightwhite);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-white);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs__item {
|
||||
padding-right: 14px;
|
||||
position: relative;
|
||||
line-height: 28px;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
margin-right: 10px;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 0;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
border-bottom-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-color: var(--color-white);
|
||||
transform: rotate(-45deg);
|
||||
z-index: 10;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs__last-item {
|
||||
line-height: 28px;
|
||||
vertical-align: bottom;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -1,13 +1,6 @@
|
||||
$newline never
|
||||
<footer .footer>
|
||||
<div .footer-links>
|
||||
$forall (MenuItem{menuItemType, menuItemRoute = _, menuItemIcon = _, menuItemLabel, menuItemModal = _}, menuIdent, route) <- menuTypes
|
||||
$case menuItemType
|
||||
$of Footer
|
||||
$# Not used but available (remove ` = _` from the pattern match above, as needed):
|
||||
$# highlight (urlRoute menuItemRoute) :: Bool -- ^ Is this menu item currently active (i.e.: are we on this page)
|
||||
$# menuItemModal :: Bool -- ^ Should this menu item open a modal instead of being a normal link
|
||||
$# menuItemIcon :: Maybe Text -- ^ Should this menu item have an icon, if yes, then the name of the icon
|
||||
<a href=#{route} ##{menuIdent}>
|
||||
_{SomeMessage menuItemLabel}
|
||||
$of _
|
||||
$forall (NavFooter{ navLink }, navIdent, navRoute', _) <- filter isNavFooter nav
|
||||
<a href=#{navRoute'} ##{navIdent}>
|
||||
_{navLink}
|
||||
|
||||
11
templates/widgets/navbar/container-radio.cassius
Normal file
11
templates/widgets/navbar/container-radio.cassius
Normal file
@ -0,0 +1,11 @@
|
||||
##{containerIdent}-radio:checked ~ * ##{containerIdent}-container
|
||||
visibility: visible
|
||||
margin: 14px 0 0 0
|
||||
height: 30px
|
||||
|
||||
##{containerIdent}-radio:checked ~ * ##{containerIdent}
|
||||
background-color: var(--color-dark)
|
||||
color: var(--color-lightwhite)
|
||||
|
||||
.navbar__link-icon
|
||||
opacity: 1
|
||||
5
templates/widgets/navbar/container.hamlet
Normal file
5
templates/widgets/navbar/container.hamlet
Normal file
@ -0,0 +1,5 @@
|
||||
$newline never
|
||||
<label .navbar__link-wrapper for=#{navIdent}-radio ##{navIdent}>
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-#{iconText navIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage navLabel}
|
||||
@ -1,6 +1,5 @@
|
||||
$newline never
|
||||
<a .navbar__link-wrapper href=#{route} ##{menuIdent}>
|
||||
$if isJust menuItemIcon
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-#{fromMaybe "none" menuItemIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage menuItemLabel}
|
||||
<a .navbar__link-wrapper href=#{route} ##{ident}>
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-#{iconText navIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage navLabel}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
$maybe csrf <- csrfToken
|
||||
<input type=hidden name=#{defaultCsrfParamName} value=#{csrf}>
|
||||
$forall (k, v) <- navData
|
||||
<input type=hidden name=#{k} value=#{v}>
|
||||
<button .navbar__container-link :highlightNav iN:.navbar__container-link--active type=submit>
|
||||
_{SomeMessage navLabel}
|
||||
@ -0,0 +1,3 @@
|
||||
$newline never
|
||||
<a .navbar__container-link :highlightNav iN:.navbar__container-link--active href=#{route} ##{ident}>
|
||||
_{SomeMessage navLabel}
|
||||
@ -1,48 +1,54 @@
|
||||
$newline never
|
||||
<div .navbar-container>
|
||||
<div .navbar-shadow>
|
||||
<nav .navbar.js-sticky-navbar>
|
||||
|
||||
<ul .navbar__list.list--inline.navbar__list-left>
|
||||
$# manually add favorites to navbar for small screens
|
||||
<li .navbar__list-item.navbar__list-item--favorite>
|
||||
<a .navbar__link-wrapper href="#">
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-star>
|
||||
<div .navbar__link-label>_{MsgNavigationFavourites}
|
||||
|
||||
$forall (menuItem@MenuItem{menuItemType, menuItemRoute, menuItemModal}, menuIdent, _) <- menuTypes
|
||||
$case menuItemType
|
||||
$of NavbarAside
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
^{navbarModal (menuItem, menuIdent)}
|
||||
$else
|
||||
^{navbarItem (menuItem, menuIdent)}
|
||||
$of _
|
||||
|
||||
<ul .navbar__list.list--inline>
|
||||
$forall (menuItem@MenuItem{menuItemType, menuItemRoute, menuItemModal}, menuIdent, _) <- menuTypes
|
||||
$case menuItemType
|
||||
$of NavbarRight
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
^{navbarModal (menuItem, menuIdent)}
|
||||
$else
|
||||
^{navbarItem (menuItem, menuIdent)}
|
||||
$of NavbarSecondary
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
^{navbarModal (menuItem, menuIdent)}
|
||||
$else
|
||||
^{navbarItem (menuItem, menuIdent)}
|
||||
$of _
|
||||
<li .navbar__list-item--lang-wrapper uw-language-select>
|
||||
<input type="checkbox" id="lang-checkbox" uw-no-checkbox>
|
||||
<div id="lang-dropdown">
|
||||
^{langFormView'}
|
||||
<div .navbar__list-item .navbar__list-item--language>
|
||||
<label .navbar__link-wrapper for="lang-checkbox">
|
||||
<nav .navbar>
|
||||
<div .navbar__list-wrapper>
|
||||
<ul .navbar__list.navbar__list-left>
|
||||
$# manually add favorites to navbar for small screens
|
||||
<li .navbar__list-item.navbar__list-item--favorite>
|
||||
<a .navbar__link-wrapper href="#">
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-flag-alt>
|
||||
<div .navbar__link-label>_{MsgMenuLanguage}
|
||||
<i .fas .fa-2x .fa-#{iconText IconFavourite}>
|
||||
<div .navbar__link-label>_{MsgNavigationFavourites}
|
||||
|
||||
$forall n <- filter isNavHeaderPrimary nav
|
||||
$case view _1 n
|
||||
$of NavHeader{ navLink }
|
||||
<li .navbar__list-item :highlightNav navLink:.navbar__list-item--active>
|
||||
^{navWidget n}
|
||||
$of NavHeaderContainer{}
|
||||
<li .navbar__list-item.navbar__list-item--container-selector>
|
||||
^{navWidget n}
|
||||
$of _
|
||||
|
||||
<ul .navbar__list>
|
||||
$forall n <- filter isNavHeaderSecondary $ reverse nav
|
||||
$case view _1 n
|
||||
$of NavHeader{ navLink }
|
||||
<li .navbar__list-item :highlightNav navLink:.navbar__list-item--active>
|
||||
^{navWidget n}
|
||||
$of NavHeaderContainer{}
|
||||
<li .navbar__list-item.navbar__list-item--container-selector>
|
||||
^{navWidget n}
|
||||
$of _
|
||||
|
||||
|
||||
$forall n@(NavHeaderContainer{ navHeaderRole }, containerIdent, _, ns) <- filter isNavHeaderContainer nav
|
||||
<div .navbar__container-list :navHeaderRole == NavHeaderPrimary:.navbar__container-list--left ##{containerIdent}-container>
|
||||
<label .navbar__container-list-closer for=container-radio-none>
|
||||
<i .fas .fa-fw .fa-#{iconText IconNavContainerClose}>
|
||||
<ul>
|
||||
$forall iN@(nl, _, _) <- ns
|
||||
<li .navbar__container-list-item :highlightNav nl:.navbar__container-list-item--active>
|
||||
^{navContainerItemWidget n iN}
|
||||
|
||||
|
||||
$# <li .navbar__list-item--lang-wrapper uw-language-select>
|
||||
$# <input type="checkbox" id="lang-checkbox" uw-no-checkbox>
|
||||
$# <div id="lang-dropdown">
|
||||
$# ^{langFormView'}
|
||||
$# <div .navbar__list-item .navbar__list-item--language>
|
||||
$# <label .navbar__link-wrapper for="lang-checkbox">
|
||||
$# <div .navbar__link-icon>
|
||||
$# <i .fas .fa-2x .fa-flag-alt>
|
||||
$# <div .navbar__link-label>_{MsgMenuLanguage}
|
||||
|
||||
4
templates/widgets/notification.hamlet
Normal file
4
templates/widgets/notification.hamlet
Normal file
@ -0,0 +1,4 @@
|
||||
$newline never
|
||||
<div .notification .notification-#{toPathPiece messageStatus} .fa-#{iconText messageIcon} :is _NotificationBroad nType:.notification--broad>
|
||||
<div .notification__content>
|
||||
#{messageContent}
|
||||
@ -2,22 +2,12 @@ $newline never
|
||||
<div .pagenav>
|
||||
$if hasPrimaryPageActions
|
||||
<div .pagenav-prime>
|
||||
$forall (MenuItem{menuItemLabel, menuItemType, menuItemModal}, menuIdent, route) <- menuTypes
|
||||
$case menuItemType
|
||||
$of PageActionPrime
|
||||
<div .pagenav__list-item>
|
||||
$if menuItemModal
|
||||
<div uw-modal data-modal-trigger=#{menuIdent} data-modal-closeable>
|
||||
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||
$of _
|
||||
$forall n@(NavPageActionPrimary{}, _, _, _) <- nav
|
||||
<div .pagenav__list-item>
|
||||
^{navWidget n}
|
||||
$if hasSecondaryPageActions
|
||||
<div .pagenav-secondary>
|
||||
<div .pagenav-secondary__list>
|
||||
$forall (MenuItem{menuItemLabel, menuItemType, menuItemModal}, menuIdent, route) <- menuTypes
|
||||
$case menuItemType
|
||||
$of PageActionSecondary
|
||||
<div .pagenav__list-item.pagenav__list-item--secondary>
|
||||
$if menuItemModal
|
||||
<div uw-modal data-modal-trigger=#{menuIdent} data-modal-closeable>
|
||||
<a .pagenav__link-wrapper.pagenav__link-wrapper--secondary href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||
$of _
|
||||
$forall n@(NavPageActionPrimary{}, _, _, _) <- nav
|
||||
<div .pagenav__list-item.pagenav__list-item--secondary>
|
||||
^{navWidget n}
|
||||
|
||||
2
templates/widgets/pageaction/primary.hamlet
Normal file
2
templates/widgets/pageaction/primary.hamlet
Normal file
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
<a .pagenav__link-wrapper href=#{route} ##{ident}>_{SomeMessage navLabel}
|
||||
2
templates/widgets/pageaction/secondary.hamlet
Normal file
2
templates/widgets/pageaction/secondary.hamlet
Normal file
@ -0,0 +1,2 @@
|
||||
$newline never
|
||||
<a .pagenav__link-wrapper.pagenav__link-wrapper--secondary href=#{route} ##{ident}>_{SomeMessage navLabel}
|
||||
Loading…
Reference in New Issue
Block a user