Merge branch 'master' into feat/exercises
This commit is contained in:
commit
217ae28d9e
80
README.md
80
README.md
@ -1,21 +1,24 @@
|
|||||||
# Quick Start Guide
|
# Quick Start Guide
|
||||||
|
|
||||||
Assuming Ubuntu or similar
|
The following Description applies to Ubuntu or similar.
|
||||||
|
|
||||||
|
## Clone repository
|
||||||
|
Clone this repository `git clone https://gitlab.cip.ifi.lmu.de/jost/UniWorX.git` and navigate into the new directory `cd UniWorX`.
|
||||||
|
|
||||||
## LDAP
|
## LDAP
|
||||||
install:<br>
|
install:
|
||||||
`sudo apt-get install slapd ldap-utils`
|
`sudo apt-get install slapd ldap-utils`
|
||||||
|
|
||||||
|
|
||||||
## PostgreSQL
|
## PostgreSQL
|
||||||
install:<br>
|
install:
|
||||||
`sudo apt-get install postgresql`
|
`sudo apt-get install postgresql`
|
||||||
|
|
||||||
switch to user *postgres* (got created during installation):<br>
|
switch to user *postgres* (got created during installation):
|
||||||
`sudo -i -u postgres`
|
`sudo -i -u postgres`
|
||||||
|
|
||||||
add db user *uniworx*:<br>
|
add db user *uniworx*:
|
||||||
`createuser --interactive`
|
`createuser --interactive`
|
||||||
|
|
||||||
you'll get a prompt:
|
you'll get a prompt:
|
||||||
|
|
||||||
@ -24,49 +27,60 @@ Assuming Ubuntu or similar
|
|||||||
Shall the new role be a superuser? (y/n)` - [not exactly sure. Guess not?]
|
Shall the new role be a superuser? (y/n)` - [not exactly sure. Guess not?]
|
||||||
```
|
```
|
||||||
|
|
||||||
create database *uniworx*:<br>
|
create database *uniworx*:
|
||||||
`createdb uniworx`
|
`createdb uniworx`
|
||||||
|
|
||||||
to access the database as user *uniworx* you now need to add a new linux-user called *uniworx*:<br>
|
to access the database as user *uniworx* you now need to add a new linux-user called *uniworx*:
|
||||||
`sudo adduser uniworx`
|
`sudo adduser uniworx`
|
||||||
|
|
||||||
log-in as new user *uniworx*:<br>
|
log-in as new user *uniworx*:
|
||||||
`sudo -i -u uniworx`
|
`sudo -i -u uniworx`
|
||||||
|
|
||||||
you can now use `psql uniworx` to execute SQL-commands and such.
|
you can now use `psql uniworx` to execute SQL-commands and such.
|
||||||
|
|
||||||
you might for example want to add a test-account to be able to login on the page:<br>
|
|
||||||
`INSERT INTO user (plugin, ident, matrikelnummer, email, display_name) VALUES ('LDAP', '[YOUR_EMAIL_ADDRESS]', null, '[YOUR_EMAIL_ADDRESS]', '[YOUR_NAME]');`
|
|
||||||
|
|
||||||
## stack
|
## stack
|
||||||
Install with:<br>
|
Install with:
|
||||||
`curl -sSL https://get.haskellstack.org/ | sh`
|
`curl -sSL https://get.haskellstack.org/ | sh`
|
||||||
|
|
||||||
setup stack and install dependencies:<br>
|
setup stack and install dependencies:
|
||||||
`stack setup`
|
`stack setup`
|
||||||
|
|
||||||
There might be packages missing during setup. You most probably simply need to install them and try again.<br>
|
During this step or the next you might get an error that says something about missing C libraries for `ldap` and `lber`. You can install these using
|
||||||
Instructions are easy to find via search engine of your choice and the specific error you got.<br>
|
`sudo apt-get install libsasl2-dev libldap2-dev`
|
||||||
Example from experience: For LDAP `ldab` and `lber` header files were missing.
|
|
||||||
|
|
||||||
Build the app:<br>
|
If you get an error that says *You need to install postgresql-server-dev-X.Y for building a server-side extension or libpq-dev for building a client-side application.*
|
||||||
`stack build`
|
Go ahead an install `libpq-dev` with
|
||||||
|
`sudo apt-get install libpq-dev`
|
||||||
|
|
||||||
Run the app (with environment variable DUMMY_LOGIN set to true):<br>
|
Build the app:
|
||||||
`env DUMMY_LOGIN=true stack exec -- yesod devel`
|
`stack build`
|
||||||
|
|
||||||
`Devel application launched: http://localhost:3000`<br>
|
This might take a few minutes if not hours... be prepared.
|
||||||
|
|
||||||
|
install yesod:
|
||||||
|
`stack install yesod-bin --install-ghc`
|
||||||
|
|
||||||
|
## Add Dumy-Data and run the app
|
||||||
|
After building the app you can prepare the database and add some dummy data:
|
||||||
|
`./fill-db.hs`
|
||||||
|
|
||||||
|
Run the app:
|
||||||
|
`./start.sh`
|
||||||
|
|
||||||
|
`Devel application launched: http://localhost:3000`
|
||||||
means you are good to go.
|
means you are good to go.
|
||||||
|
|
||||||
|
If you followed the steps above you should now be able to login as user Gregor Kleen using `LDAP:g.kleen@ifi.lmu.de` as dummy login.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
# Sources and more infos
|
# Sources and more infos
|
||||||
PostgreSQl: <br>
|
PostgreSQl:
|
||||||
https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-16-04
|
https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-16-04
|
||||||
|
|
||||||
stack:<br> https://docs.haskellstack.org/en/stable/README/#how-to-install
|
stack: https://docs.haskellstack.org/en/stable/README/#how-to-install
|
||||||
|
|
||||||
ldap:<br>https://wiki.ubuntuusers.de/OpenLDAP_ab_Precise/
|
ldap: https://wiki.ubuntuusers.de/OpenLDAP_ab_Precise/
|
||||||
|
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|||||||
127
fill-db.hs
127
fill-db.hs
@ -14,6 +14,7 @@ main :: IO ()
|
|||||||
main = db $ do
|
main = db $ do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
let
|
let
|
||||||
|
summer2017 = TermIdentifier 2017 Summer
|
||||||
winter2017 = TermIdentifier 2017 Winter
|
winter2017 = TermIdentifier 2017 Winter
|
||||||
summer2018 = TermIdentifier 2018 Summer
|
summer2018 = TermIdentifier 2018 Summer
|
||||||
gkleen <- insert User
|
gkleen <- insert User
|
||||||
@ -23,6 +24,29 @@ main = db $ do
|
|||||||
, userEmail = "G.Kleen@campus.lmu.de"
|
, userEmail = "G.Kleen@campus.lmu.de"
|
||||||
, userDisplayName = "Gregor Kleen"
|
, userDisplayName = "Gregor Kleen"
|
||||||
}
|
}
|
||||||
|
fhamann <- insert User
|
||||||
|
{ userPlugin = "LDAP"
|
||||||
|
, userIdent = "felix.hamann@campus.lmu.de"
|
||||||
|
, userMatrikelnummer = Nothing
|
||||||
|
, userEmail = "felix.hamann@campus.lmu.de"
|
||||||
|
, userDisplayName = "Felix Hamann"
|
||||||
|
}
|
||||||
|
jost <- insert User
|
||||||
|
{ userPlugin = "LDAP"
|
||||||
|
, userIdent = "jost@tcs.ifi.lmu.de"
|
||||||
|
, userMatrikelnummer = Nothing
|
||||||
|
, userEmail = "jost@tcs.ifi.lmu.de"
|
||||||
|
, userDisplayName = "Steffen Jost"
|
||||||
|
}
|
||||||
|
void . insert $ Term
|
||||||
|
{ termName = summer2017
|
||||||
|
, termStart = fromGregorian 2017 04 09
|
||||||
|
, termEnd = fromGregorian 2017 07 14
|
||||||
|
, termHolidays = []
|
||||||
|
, termLectureStart = fromGregorian 2017 04 09
|
||||||
|
, termLectureEnd = fromGregorian 2018 07 14
|
||||||
|
, termActive = False
|
||||||
|
}
|
||||||
void . insert $ Term
|
void . insert $ Term
|
||||||
{ termName = winter2017
|
{ termName = winter2017
|
||||||
, termStart = fromGregorian 2017 10 16
|
, termStart = fromGregorian 2017 10 16
|
||||||
@ -45,9 +69,16 @@ main = db $ do
|
|||||||
mi <- insert $ School "Institut für Mathematik" "MI"
|
mi <- insert $ School "Institut für Mathematik" "MI"
|
||||||
void . insert $ UserAdmin gkleen ifi
|
void . insert $ UserAdmin gkleen ifi
|
||||||
void . insert $ UserAdmin gkleen mi
|
void . insert $ UserAdmin gkleen mi
|
||||||
|
void . insert $ UserAdmin fhamann ifi
|
||||||
|
void . insert $ UserAdmin jost ifi
|
||||||
|
void . insert $ UserAdmin jost mi
|
||||||
void . insert $ UserLecturer gkleen ifi
|
void . insert $ UserLecturer gkleen ifi
|
||||||
|
void . insert $ UserLecturer fhamann ifi
|
||||||
|
void . insert $ UserLecturer jost ifi
|
||||||
ifiBsc <- insert $ Degree "Bachelor Informatik" ifi
|
ifiBsc <- insert $ Degree "Bachelor Informatik" ifi
|
||||||
ifiMsc <- insert $ Degree "Master Informatik" ifi
|
ifiMsc <- insert $ Degree "Master Informatik" ifi
|
||||||
|
miBsc <- insert $ Degree "Bachelor Mathematik" mi
|
||||||
|
-- FFP
|
||||||
ffp <- insert Course
|
ffp <- insert Course
|
||||||
{ courseName = "Fortgeschrittene Funktionale Programmierung"
|
{ courseName = "Fortgeschrittene Funktionale Programmierung"
|
||||||
, courseDescription = Nothing
|
, courseDescription = Nothing
|
||||||
@ -69,3 +100,99 @@ main = db $ do
|
|||||||
void . insert $ Lecturer gkleen ffp
|
void . insert $ Lecturer gkleen ffp
|
||||||
void . insert $ Corrector gkleen ffp (ByProportion 1)
|
void . insert $ Corrector gkleen ffp (ByProportion 1)
|
||||||
void . insert $ Sheet ffp "Blatt 1" Nothing NotGraded Nothing now now Nothing Nothing now now gkleen gkleen
|
void . insert $ Sheet ffp "Blatt 1" Nothing NotGraded Nothing now now Nothing Nothing now now gkleen gkleen
|
||||||
|
-- EIP
|
||||||
|
eip <- insert Course
|
||||||
|
{ courseName = "Einführung in die Programmierung"
|
||||||
|
, courseDescription = Nothing
|
||||||
|
, courseLinkExternal = Nothing
|
||||||
|
, courseShorthand = "eip"
|
||||||
|
, courseTermId = TermKey summer2017
|
||||||
|
, courseSchoolId = ifi
|
||||||
|
, courseCapacity = Just 20
|
||||||
|
, courseCreated = now
|
||||||
|
, courseChanged = now
|
||||||
|
, courseCreatedBy = fhamann
|
||||||
|
, courseChangedBy = fhamann
|
||||||
|
, courseHasRegistration = False
|
||||||
|
, courseRegisterFrom = Nothing
|
||||||
|
, courseRegisterTo = Nothing
|
||||||
|
}
|
||||||
|
void . insert $ DegreeCourse ifiBsc eip
|
||||||
|
void . insert $ DegreeCourse ifiMsc eip
|
||||||
|
void . insert $ Lecturer fhamann eip
|
||||||
|
-- interaction design
|
||||||
|
ixd <- insert Course
|
||||||
|
{ courseName = "Interaction Design (User Experience Design I & II)"
|
||||||
|
, courseDescription = Nothing
|
||||||
|
, courseLinkExternal = Nothing
|
||||||
|
, courseShorthand = "ixd"
|
||||||
|
, courseTermId = TermKey summer2018
|
||||||
|
, courseSchoolId = ifi
|
||||||
|
, courseCapacity = Just 20
|
||||||
|
, courseCreated = now
|
||||||
|
, courseChanged = now
|
||||||
|
, courseCreatedBy = fhamann
|
||||||
|
, courseChangedBy = fhamann
|
||||||
|
, courseHasRegistration = True
|
||||||
|
, courseRegisterFrom = Just now
|
||||||
|
, courseRegisterTo = Just ((3600 * 24 * 60) `addUTCTime` now )
|
||||||
|
}
|
||||||
|
void . insert $ DegreeCourse ifiBsc ixd
|
||||||
|
void . insert $ Lecturer fhamann ixd
|
||||||
|
-- concept development
|
||||||
|
ux3 <- insert Course
|
||||||
|
{ courseName = "Concept Development (User Experience Design III)"
|
||||||
|
, courseDescription = Nothing
|
||||||
|
, courseLinkExternal = Nothing
|
||||||
|
, courseShorthand = "ux3"
|
||||||
|
, courseTermId = TermKey winter2017
|
||||||
|
, courseSchoolId = ifi
|
||||||
|
, courseCapacity = Just 30
|
||||||
|
, courseCreated = now
|
||||||
|
, courseChanged = now
|
||||||
|
, courseCreatedBy = fhamann
|
||||||
|
, courseChangedBy = fhamann
|
||||||
|
, courseHasRegistration = False
|
||||||
|
, courseRegisterFrom = Nothing
|
||||||
|
, courseRegisterTo = Nothing
|
||||||
|
}
|
||||||
|
void . insert $ DegreeCourse ifiBsc ux3
|
||||||
|
void . insert $ Lecturer fhamann ux3
|
||||||
|
-- promo
|
||||||
|
pmo <- insert Course
|
||||||
|
{ courseName = "Programmierung und Modellierung"
|
||||||
|
, courseDescription = Nothing
|
||||||
|
, courseLinkExternal = Nothing
|
||||||
|
, courseShorthand = "pmo"
|
||||||
|
, courseTermId = TermKey summer2017
|
||||||
|
, courseSchoolId = ifi
|
||||||
|
, courseCapacity = Just 50
|
||||||
|
, courseCreated = now
|
||||||
|
, courseChanged = now
|
||||||
|
, courseCreatedBy = jost
|
||||||
|
, courseChangedBy = jost
|
||||||
|
, courseHasRegistration = False
|
||||||
|
, courseRegisterFrom = Nothing
|
||||||
|
, courseRegisterTo = Nothing
|
||||||
|
}
|
||||||
|
void . insert $ DegreeCourse ifiBsc pmo
|
||||||
|
void . insert $ Lecturer jost pmo
|
||||||
|
-- datenbanksysteme
|
||||||
|
dbs <- insert Course
|
||||||
|
{ courseName = "Datenbanksysteme"
|
||||||
|
, courseDescription = Nothing
|
||||||
|
, courseLinkExternal = Nothing
|
||||||
|
, courseShorthand = "dbs"
|
||||||
|
, courseTermId = TermKey summer2018
|
||||||
|
, courseSchoolId = ifi
|
||||||
|
, courseCapacity = Just 50
|
||||||
|
, courseCreated = now
|
||||||
|
, courseChanged = now
|
||||||
|
, courseCreatedBy = jost
|
||||||
|
, courseChangedBy = jost
|
||||||
|
, courseHasRegistration = False
|
||||||
|
, courseRegisterFrom = Nothing
|
||||||
|
, courseRegisterTo = Nothing
|
||||||
|
}
|
||||||
|
void . insert $ DegreeCourse ifiBsc dbs
|
||||||
|
void . insert $ Lecturer jost dbs
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
SummerTerm year@Integer: Sommersemester #{tshow year}
|
SummerTerm year@Integer: Sommersemester #{tshow year}
|
||||||
WinterTerm year@Integer: Wintersemester #{tshow year}/#{tshow $ succ year}
|
WinterTerm year@Integer: Wintersemester #{tshow year}/#{tshow $ succ year}
|
||||||
|
PSLimitNonPositive: “pagesize” muss größer als null sein
|
||||||
TermEdited tid@TermIdentifier: Semester #{termToText tid} erfolgreich editiert.
|
TermEdited tid@TermIdentifier: Semester #{termToText tid} erfolgreich editiert.
|
||||||
TermNewTitle: Semester editiere/anlegen.
|
TermNewTitle: Semester editiere/anlegen.
|
||||||
InvalidInput: Eingaben bitte korrigieren.
|
InvalidInput: Eingaben bitte korrigieren.
|
||||||
|
|||||||
@ -51,6 +51,8 @@ import Handler.Utils.StudyFeatures
|
|||||||
|
|
||||||
import System.FilePath
|
import System.FilePath
|
||||||
|
|
||||||
|
import Handler.Utils.Templates
|
||||||
|
|
||||||
-- | The foundation datatype for your application. This can be a good place to
|
-- | The foundation datatype for your application. This can be a good place to
|
||||||
-- keep settings and values requiring initialization before your application
|
-- keep settings and values requiring initialization before your application
|
||||||
-- starts running, such as database connections. Every handler will have
|
-- starts running, such as database connections. Every handler will have
|
||||||
@ -82,14 +84,16 @@ type DB a = YesodDB UniWorX a
|
|||||||
|
|
||||||
data MenuItem = MenuItem
|
data MenuItem = MenuItem
|
||||||
{ menuItemLabel :: Text
|
{ menuItemLabel :: Text
|
||||||
|
, menuItemIcon :: Maybe Text
|
||||||
, menuItemRoute :: Route UniWorX
|
, menuItemRoute :: Route UniWorX
|
||||||
, menuItemAccessCallback :: Handler Bool
|
, menuItemAccessCallback :: Handler Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
data MenuTypes
|
data MenuTypes
|
||||||
= NavbarLeft { menuItem :: MenuItem }
|
= NavbarAside { menuItem :: MenuItem }
|
||||||
| NavbarRight { menuItem :: MenuItem }
|
| NavbarRight { menuItem :: MenuItem }
|
||||||
| NavbarExtra { menuItem :: MenuItem }
|
| NavbarExtra { menuItem :: MenuItem }
|
||||||
|
| NavbarSecondary { menuItem :: MenuItem }
|
||||||
|
|
||||||
-- | A convenient synonym for creating forms.
|
-- | A convenient synonym for creating forms.
|
||||||
type Form x = Html -> MForm (HandlerT UniWorX IO) (FormResult x, Widget)
|
type Form x = Html -> MForm (HandlerT UniWorX IO) (FormResult x, Widget)
|
||||||
@ -302,36 +306,54 @@ instance YesodBreadcrumbs UniWorX where
|
|||||||
|
|
||||||
defaultLinks :: [MenuTypes]
|
defaultLinks :: [MenuTypes]
|
||||||
defaultLinks = -- Define the menu items of the header.
|
defaultLinks = -- Define the menu items of the header.
|
||||||
[ NavbarLeft $ MenuItem
|
[ NavbarRight $ MenuItem
|
||||||
{ menuItemLabel = "Home"
|
{ menuItemLabel = "Home"
|
||||||
|
, menuItemIcon = Just "home"
|
||||||
, menuItemRoute = HomeR
|
, menuItemRoute = HomeR
|
||||||
, menuItemAccessCallback = return True
|
, menuItemAccessCallback = return True
|
||||||
}
|
}
|
||||||
, NavbarLeft $ MenuItem
|
|
||||||
{ menuItemLabel = "Kurse"
|
|
||||||
, menuItemRoute = CourseListR
|
|
||||||
, menuItemAccessCallback = return True
|
|
||||||
}
|
|
||||||
, NavbarRight $ MenuItem
|
|
||||||
{ menuItemLabel = "Users"
|
|
||||||
, menuItemRoute = UsersR
|
|
||||||
, menuItemAccessCallback = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False
|
|
||||||
}
|
|
||||||
, NavbarRight $ MenuItem
|
, NavbarRight $ MenuItem
|
||||||
{ menuItemLabel = "Profile"
|
{ menuItemLabel = "Profile"
|
||||||
|
, menuItemIcon = Just "profile"
|
||||||
, menuItemRoute = ProfileR
|
, menuItemRoute = ProfileR
|
||||||
, menuItemAccessCallback = isJust <$> maybeAuthPair
|
, menuItemAccessCallback = isJust <$> maybeAuthPair
|
||||||
}
|
}
|
||||||
, NavbarRight $ MenuItem
|
, NavbarSecondary $ MenuItem
|
||||||
{ menuItemLabel = "Login"
|
{ menuItemLabel = "Login"
|
||||||
|
, menuItemIcon = Just "login"
|
||||||
, menuItemRoute = AuthR LoginR
|
, menuItemRoute = AuthR LoginR
|
||||||
, menuItemAccessCallback = isNothing <$> maybeAuthPair
|
, menuItemAccessCallback = isNothing <$> maybeAuthPair
|
||||||
}
|
}
|
||||||
, NavbarRight $ MenuItem
|
, NavbarSecondary $ MenuItem
|
||||||
{ menuItemLabel = "Logout"
|
{ menuItemLabel = "Logout"
|
||||||
|
, menuItemIcon = Just "logout"
|
||||||
, menuItemRoute = AuthR LogoutR
|
, menuItemRoute = AuthR LogoutR
|
||||||
, menuItemAccessCallback = isJust <$> maybeAuthPair
|
, menuItemAccessCallback = isJust <$> maybeAuthPair
|
||||||
}
|
}
|
||||||
|
, NavbarAside $ MenuItem
|
||||||
|
{ menuItemLabel = "Aktuelle Veranstaltungen"
|
||||||
|
, menuItemIcon = Just "book"
|
||||||
|
, menuItemRoute = CourseListR -- should be CourseListActiveR or similar in the future
|
||||||
|
, menuItemAccessCallback = return True
|
||||||
|
}
|
||||||
|
, NavbarAside $ MenuItem
|
||||||
|
{ menuItemLabel = "Alte Veranstaltungen"
|
||||||
|
, menuItemIcon = Just "book"
|
||||||
|
, menuItemRoute = CourseListR -- should be CourseListInactiveR or similar in the future
|
||||||
|
, menuItemAccessCallback = return True
|
||||||
|
}
|
||||||
|
, NavbarAside $ MenuItem
|
||||||
|
{ menuItemLabel = "Veranstaltungen"
|
||||||
|
, menuItemIcon = Just "book"
|
||||||
|
, menuItemRoute = CourseListR
|
||||||
|
, menuItemAccessCallback = return True
|
||||||
|
}
|
||||||
|
, NavbarAside $ MenuItem
|
||||||
|
{ menuItemLabel = "Benutzer"
|
||||||
|
, menuItemIcon = Just "user"
|
||||||
|
, menuItemRoute = UsersR
|
||||||
|
, menuItemAccessCallback = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
defaultLinkLayout :: [MenuTypes] -> Widget -> Handler Html
|
defaultLinkLayout :: [MenuTypes] -> Widget -> Handler Html
|
||||||
@ -355,11 +377,24 @@ defaultMenuLayout menu widget = do
|
|||||||
-- value passed to hamletToRepHtml cannot be a widget, this allows
|
-- value passed to hamletToRepHtml cannot be a widget, this allows
|
||||||
-- you to use normal widget features in default-layout.
|
-- you to use normal widget features in default-layout.
|
||||||
|
|
||||||
pc <- widgetToPageContent $ do
|
let
|
||||||
addStylesheet $ StaticR css_bootstrap_css
|
navbar :: Widget
|
||||||
$(widgetFile "default-layout")
|
navbar = $(widgetFile "widgets/navbar")
|
||||||
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
asidenav :: Widget
|
||||||
|
asidenav = $(widgetFile "widgets/asidenav")
|
||||||
|
breadcrumbs :: Widget
|
||||||
|
breadcrumbs = $(widgetFile "widgets/breadcrumbs")
|
||||||
|
|
||||||
|
pc <- widgetToPageContent $ do
|
||||||
|
addStylesheetRemote "https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,800,900"
|
||||||
|
addScript $ StaticR js_featureChecker_js
|
||||||
|
addStylesheet $ StaticR css_fonts_css
|
||||||
|
addStylesheet $ StaticR css_icons_css
|
||||||
|
$(widgetFile "default-layout")
|
||||||
|
$(widgetFile "standalone/showHide")
|
||||||
|
$(widgetFile "standalone/sortable")
|
||||||
|
$(widgetFile "standalone/inputs")
|
||||||
|
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
||||||
|
|
||||||
-- How to run database actions.
|
-- How to run database actions.
|
||||||
instance YesodPersist UniWorX where
|
instance YesodPersist UniWorX where
|
||||||
|
|||||||
@ -63,18 +63,18 @@ getCourseListTermR tidini = do
|
|||||||
|]
|
|]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
let pageActions =
|
let pageLinks =
|
||||||
[ NavbarLeft $ MenuItem
|
[ NavbarAside $ MenuItem
|
||||||
{ menuItemLabel = "Neuer Kurs"
|
{ menuItemLabel = "Neuer Kurs"
|
||||||
|
, menuItemIcon = Just "book"
|
||||||
, menuItemRoute = CourseNewR
|
, menuItemRoute = CourseNewR
|
||||||
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseNewR False
|
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseNewR False
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
defaultLinkLayout pageActions $ do
|
let coursesTable = encodeWidgetTable tableSortable colonnadeTerms courses
|
||||||
-- defaultLayout $ do
|
defaultLinkLayout pageLinks $ do
|
||||||
setTitle "Semesterkurse"
|
setTitle "Semesterkurse"
|
||||||
linkButton "Neuen Kurs anlegen" BCPrimary CourseNewR
|
$(widgetFile "courses")
|
||||||
encodeWidgetTable tableDefault colonnadeTerms courses -- (map entityVal courses)
|
|
||||||
|
|
||||||
getCourseShowR :: TermId -> Text -> Handler Html
|
getCourseShowR :: TermId -> Text -> Handler Html
|
||||||
getCourseShowR tid csh = do
|
getCourseShowR tid csh = do
|
||||||
@ -93,8 +93,9 @@ getCourseShowR tid csh = do
|
|||||||
let course = entityVal courseEnt
|
let course = entityVal courseEnt
|
||||||
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerButton $ mbRegistered
|
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerButton $ mbRegistered
|
||||||
let pageActions =
|
let pageActions =
|
||||||
[ NavbarLeft $ MenuItem
|
[ NavbarAside $ MenuItem
|
||||||
{ menuItemLabel = "Übungsblätter"
|
{ menuItemLabel = "Übungsblätter"
|
||||||
|
, menuItemIcon = Nothing
|
||||||
, menuItemRoute = SheetListR tid csh
|
, menuItemRoute = SheetListR tid csh
|
||||||
, menuItemAccessCallback = (== Authorized) <$> isAuthorized (SheetListR tid csh) False
|
, menuItemAccessCallback = (== Authorized) <$> isAuthorized (SheetListR tid csh) False
|
||||||
}
|
}
|
||||||
@ -361,5 +362,3 @@ validateCourse (CourseForm{..}) =
|
|||||||
, "Anmeldungen aktivieren oder Anmeldezeitraum löschen"
|
, "Anmeldungen aktivieren oder Anmeldezeitraum löschen"
|
||||||
)
|
)
|
||||||
] ]
|
] ]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -55,4 +55,3 @@ postHomeR = do
|
|||||||
(FormSuccess CreateMath) -> addMessage "warning" "Knopf Mathematik erkannt"
|
(FormSuccess CreateMath) -> addMessage "warning" "Knopf Mathematik erkannt"
|
||||||
_other -> return ()
|
_other -> return ()
|
||||||
getHomeR
|
getHomeR
|
||||||
|
|
||||||
|
|||||||
@ -182,8 +182,9 @@ getSheetList courseEnt = do
|
|||||||
then colBase `mappend` colAdmin
|
then colBase `mappend` colAdmin
|
||||||
else colBase
|
else colBase
|
||||||
let pageActions =
|
let pageActions =
|
||||||
[ NavbarLeft $ MenuItem
|
[ NavbarAside $ MenuItem
|
||||||
{ menuItemLabel = "Neues Übungsblatt"
|
{ menuItemLabel = "Neues Übungsblatt"
|
||||||
|
, menuItemIcon = Nothing
|
||||||
, menuItemRoute = SheetNewR tid csh
|
, menuItemRoute = SheetNewR tid csh
|
||||||
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseNewR False
|
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseNewR False
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,18 +28,19 @@ getTermShowR = do
|
|||||||
-- E.orderBy [E.desc $ term E.^. TermStart ]
|
-- E.orderBy [E.desc $ term E.^. TermStart ]
|
||||||
-- return term
|
-- return term
|
||||||
--
|
--
|
||||||
termData <- runDB $ E.select . E.from $ \term -> do
|
let
|
||||||
E.orderBy [E.desc $ term E.^. TermStart ]
|
termData = E.from $ \term -> do
|
||||||
let courseCount :: E.SqlExpr (E.Value Int)
|
E.orderBy [E.desc $ term E.^. TermStart ]
|
||||||
courseCount = E.sub_select . E.from $ \course -> do
|
let courseCount :: E.SqlExpr (E.Value Int)
|
||||||
E.where_ $ term E.^. TermId E.==. course E.^. CourseTermId
|
courseCount = E.sub_select . E.from $ \course -> do
|
||||||
return E.countRows
|
E.where_ $ term E.^. TermId E.==. course E.^. CourseTermId
|
||||||
return (term, courseCount)
|
return E.countRows
|
||||||
|
return (term, courseCount)
|
||||||
selectRep $ do
|
selectRep $ do
|
||||||
provideRep $ return $ toJSON $ map fst termData
|
provideRep $ toJSON . map fst <$> runDB (E.select termData)
|
||||||
provideRep $ do
|
provideRep $ do
|
||||||
let colonnadeTerms = mconcat
|
let colonnadeTerms = mconcat
|
||||||
[ headed "Kürzel" $ \(Entity tid Term{..},_) -> do
|
[ headed "Kürzel" $ \(Entity tid Term{..},_) -> cell $ do
|
||||||
-- Scrap this if to slow, create term edit page instead
|
-- Scrap this if to slow, create term edit page instead
|
||||||
adminLink <- handlerToWidget $ isAuthorized (TermEditExistR tid) False
|
adminLink <- handlerToWidget $ isAuthorized (TermEditExistR tid) False
|
||||||
[whamlet|
|
[whamlet|
|
||||||
@ -50,34 +51,41 @@ getTermShowR = do
|
|||||||
#{termToText termName}
|
#{termToText termName}
|
||||||
|]
|
|]
|
||||||
, headed "Beginn Vorlesungen" $ \(Entity _ Term{..},_) ->
|
, headed "Beginn Vorlesungen" $ \(Entity _ Term{..},_) ->
|
||||||
fromString $ formatTimeGerWD termLectureStart
|
stringCell $ formatTimeGerWD termLectureStart
|
||||||
, headed "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
|
, headed "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
|
||||||
fromString $ formatTimeGerWD termLectureEnd
|
stringCell $ formatTimeGerWD termLectureEnd
|
||||||
, headed "Aktiv" $ \(Entity _ Term{..},_) ->
|
, headed "Aktiv" $ \(Entity _ Term{..},_) ->
|
||||||
bool "" tickmark termActive
|
textCell $ bool "" tickmark termActive
|
||||||
, headed "Kursliste" $ \(Entity tid Term{..}, E.Value numCourses) ->
|
, headed "Kursliste" $ \(Entity tid Term{..}, E.Value numCourses) ->
|
||||||
[whamlet|
|
cell [whamlet|
|
||||||
<a href=@{CourseListTermR tid}>
|
<a href=@{CourseListTermR tid}>
|
||||||
#{show numCourses} Kurse
|
#{show numCourses} Kurse
|
||||||
|]
|
|]
|
||||||
, headed "Semesteranfang" $ \(Entity _ Term{..},_) ->
|
, headed "Semesteranfang" $ \(Entity _ Term{..},_) ->
|
||||||
fromString $ formatTimeGerWD termStart
|
stringCell $ formatTimeGerWD termStart
|
||||||
, headed "Semesterende" $ \(Entity _ Term{..},_) ->
|
, headed "Semesterende" $ \(Entity _ Term{..},_) ->
|
||||||
fromString $ formatTimeGerWD termEnd
|
stringCell $ formatTimeGerWD termEnd
|
||||||
, headed "Feiertage im Semester" $ \(Entity _ Term{..},_) ->
|
, headed "Feiertage im Semester" $ \(Entity _ Term{..},_) ->
|
||||||
fromString $ (intercalate ", ") $ map formatTimeGerWD termHolidays
|
stringCell $ (intercalate ", ") $ map formatTimeGerWD termHolidays
|
||||||
]
|
]
|
||||||
|
table <- dbTable def $ DBTable
|
||||||
|
{ dbtSQLQuery = termData
|
||||||
|
, dbtColonnade = colonnadeTerms
|
||||||
|
, dbtSorting = mempty
|
||||||
|
, dbtAttrs = tableDefault
|
||||||
|
, dbtIdent = "terms" :: Text
|
||||||
|
}
|
||||||
let pageActions =
|
let pageActions =
|
||||||
[ NavbarLeft $ MenuItem
|
[ NavbarAside $ MenuItem
|
||||||
{ menuItemLabel = "Neues Semester"
|
{ menuItemLabel = "Neues Semester"
|
||||||
|
, menuItemIcon = Nothing
|
||||||
, menuItemRoute = TermEditR
|
, menuItemRoute = TermEditR
|
||||||
, menuItemAccessCallback = (== Authorized) <$> isAuthorized TermEditR True
|
, menuItemAccessCallback = (== Authorized) <$> isAuthorized TermEditR True
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
defaultLinkLayout pageActions $ do
|
defaultLinkLayout pageActions $ do
|
||||||
setTitle "Freigeschaltete Semester"
|
setTitle "Freigeschaltete Semester"
|
||||||
encodeWidgetTable tableDefault colonnadeTerms termData
|
$(widgetFile "terms")
|
||||||
|
|
||||||
|
|
||||||
getTermEditR :: Handler Html
|
getTermEditR :: Handler Html
|
||||||
getTermEditR = do
|
getTermEditR = do
|
||||||
@ -113,7 +121,6 @@ termEditHandler term = do
|
|||||||
let actionUrl = TermEditR
|
let actionUrl = TermEditR
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitle [shamlet| #{formTitle} |]
|
setTitle [shamlet| #{formTitle} |]
|
||||||
-- setTitle [whamlet| _{MsgTermNewTitle} |] -- TODO, does not work
|
|
||||||
$(widgetFile "formPage")
|
$(widgetFile "formPage")
|
||||||
|
|
||||||
newTermForm :: Maybe Term -> Form Term
|
newTermForm :: Maybe Term -> Form Term
|
||||||
|
|||||||
@ -41,5 +41,5 @@ getUsersR = do
|
|||||||
-- ++ map (\school -> headed (text2widget $ schoolName $ entityVal school) (\u -> "xx")) schools
|
-- ++ map (\school -> headed (text2widget $ schoolName $ entityVal school) (\u -> "xx")) schools
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitle "Comprehensive User List"
|
setTitle "Comprehensive User List"
|
||||||
let userList = encodeWidgetTable tableDefault colonnadeUsers users
|
let userList = encodeWidgetTable tableSortable colonnadeUsers users
|
||||||
$(widgetFile "users")
|
$(widgetFile "users")
|
||||||
|
|||||||
@ -16,10 +16,12 @@ import Handler.Utils.DateTime as Handler.Utils
|
|||||||
import Handler.Utils.Term as Handler.Utils
|
import Handler.Utils.Term as Handler.Utils
|
||||||
import Handler.Utils.Form as Handler.Utils
|
import Handler.Utils.Form as Handler.Utils
|
||||||
import Handler.Utils.Table as Handler.Utils
|
import Handler.Utils.Table as Handler.Utils
|
||||||
|
import Handler.Utils.Table.Pagination as Handler.Utils
|
||||||
|
|
||||||
import Handler.Utils.Zip as Handler.Utils
|
import Handler.Utils.Zip as Handler.Utils
|
||||||
import Handler.Utils.Rating as Handler.Utils
|
import Handler.Utils.Rating as Handler.Utils
|
||||||
import Handler.Utils.Submission as Handler.Utils
|
import Handler.Utils.Submission as Handler.Utils
|
||||||
|
import Handler.Utils.Templates as Handler.Utils
|
||||||
|
|
||||||
import Text.Blaze (Markup, ToMarkup)
|
import Text.Blaze (Markup, ToMarkup)
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,7 @@ data FormLayout = FormStandard
|
|||||||
renderAForm :: Monad m => FormLayout -> FormRender m a
|
renderAForm :: Monad m => FormLayout -> FormRender m a
|
||||||
renderAForm formLayout aform fragment = do
|
renderAForm formLayout aform fragment = do
|
||||||
(res, (($ []) -> views)) <- aFormToForm aform
|
(res, (($ []) -> views)) <- aFormToForm aform
|
||||||
let widget = $(widgetFile "form")
|
let widget = $(widgetFile "widgets/form")
|
||||||
return (res, widget)
|
return (res, widget)
|
||||||
|
|
||||||
----------------------------
|
----------------------------
|
||||||
@ -322,4 +322,3 @@ setTooltip :: String -> FieldSettings site -> FieldSettings site
|
|||||||
setTooltip tt fs
|
setTooltip tt fs
|
||||||
| null tt = fs { fsTooltip = Nothing }
|
| null tt = fs { fsTooltip = Nothing }
|
||||||
| otherwise = fs { fsTooltip = Just $ fromString tt }
|
| otherwise = fs { fsTooltip = Just $ fromString tt }
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,9 @@ import Data.Either
|
|||||||
tableDefault :: Attribute
|
tableDefault :: Attribute
|
||||||
tableDefault = customAttribute "class" "table table-striped table-hover"
|
tableDefault = customAttribute "class" "table table-striped table-hover"
|
||||||
|
|
||||||
|
tableSortable :: Attribute
|
||||||
|
tableSortable = customAttribute "class" "js-sortable"
|
||||||
|
|
||||||
-- Colonnade Tools
|
-- Colonnade Tools
|
||||||
numberColonnade :: (IsString c) => Colonnade Headed Int c
|
numberColonnade :: (IsString c) => Colonnade Headed Int c
|
||||||
numberColonnade = headed "Nr" (fromString.show)
|
numberColonnade = headed "Nr" (fromString.show)
|
||||||
|
|||||||
121
src/Handler/Utils/Table/Pagination.hs
Normal file
121
src/Handler/Utils/Table/Pagination.hs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
{-# LANGUAGE NoImplicitPrelude
|
||||||
|
, ExistentialQuantification
|
||||||
|
, RecordWildCards
|
||||||
|
, OverloadedStrings
|
||||||
|
, TemplateHaskell
|
||||||
|
, LambdaCase
|
||||||
|
, ViewPatterns
|
||||||
|
#-}
|
||||||
|
|
||||||
|
module Handler.Utils.Table.Pagination where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Esqueleto.Internal.Sql as E (SqlSelect)
|
||||||
|
import Text.Blaze (Attribute)
|
||||||
|
|
||||||
|
import Control.Monad.RWS hiding ((<>), Foldable(..), mapM_)
|
||||||
|
|
||||||
|
import Data.Map (Map)
|
||||||
|
|
||||||
|
import Colonnade hiding (bool, fromMaybe)
|
||||||
|
import Yesod.Colonnade
|
||||||
|
|
||||||
|
import Text.Hamlet (hamletFile)
|
||||||
|
|
||||||
|
|
||||||
|
data SortColumn = forall a. PersistField a => SortColumn { getSortColumn :: E.SqlExpr (E.Value a) }
|
||||||
|
data SortDirection = SortAsc | SortDesc
|
||||||
|
deriving (Eq, Ord, Enum, Show, Read)
|
||||||
|
|
||||||
|
sqlSortDirection :: (SortColumn, SortDirection) -> E.SqlExpr E.OrderBy
|
||||||
|
sqlSortDirection (SortColumn e, SortAsc ) = E.asc e
|
||||||
|
sqlSortDirection (SortColumn e, SortDesc) = E.desc e
|
||||||
|
|
||||||
|
data DBTable = forall a r h i.
|
||||||
|
( Headedness h
|
||||||
|
, E.SqlSelect a r
|
||||||
|
, PathPiece i
|
||||||
|
) => DBTable
|
||||||
|
{ dbtSQLQuery :: E.SqlQuery a
|
||||||
|
, dbtColonnade :: Colonnade h r (Cell UniWorX)
|
||||||
|
, dbtSorting :: Map Text SortColumn
|
||||||
|
, dbtAttrs :: Attribute
|
||||||
|
, dbtIdent :: i
|
||||||
|
}
|
||||||
|
|
||||||
|
data PaginationSettings = PaginationSettings
|
||||||
|
{ psSorting :: [(SortColumn, SortDirection)]
|
||||||
|
, psLimit :: Int64
|
||||||
|
, psPage :: Int64
|
||||||
|
, psShortcircuit :: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Default PaginationSettings where
|
||||||
|
def = PaginationSettings
|
||||||
|
{ psSorting = []
|
||||||
|
, psLimit = 50
|
||||||
|
, psPage = 0
|
||||||
|
, psShortcircuit = False
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype PSValidator = PSValidator { runPSValidator :: Maybe PaginationSettings -> ([SomeMessage UniWorX], PaginationSettings) }
|
||||||
|
|
||||||
|
instance Default PSValidator where
|
||||||
|
def = PSValidator $ \case
|
||||||
|
Nothing -> def
|
||||||
|
Just ps -> swap . (\act -> execRWS act () ps) $ do
|
||||||
|
l <- gets psLimit
|
||||||
|
when (l <= 0) $ do
|
||||||
|
modify $ \ps -> ps { psLimit = psLimit def }
|
||||||
|
tell . pure $ SomeMessage MsgPSLimitNonPositive
|
||||||
|
|
||||||
|
dbTable :: PSValidator -> DBTable -> Handler Widget
|
||||||
|
dbTable PSValidator{..} DBTable{ dbtIdent = (toPathPiece -> dbtIdent), .. } = do
|
||||||
|
let
|
||||||
|
sortingOptions = mkOptionList
|
||||||
|
[ Option t' (c, d) t'
|
||||||
|
| (t, c) <- mapToList dbtSorting
|
||||||
|
, d <- [SortAsc, SortDesc]
|
||||||
|
, let t' = t <> "-" <> tshow d
|
||||||
|
]
|
||||||
|
sortingField = Field parse (\_ _ _ _ _ -> return ()) UrlEncoded
|
||||||
|
where
|
||||||
|
parse optlist _ = case mapM (olReadExternal sortingOptions) optlist of
|
||||||
|
Nothing -> return $ Left "Error parsing values"
|
||||||
|
Just res -> return $ Right $ Just res
|
||||||
|
(_, defPS) = runPSValidator Nothing
|
||||||
|
wIdent n
|
||||||
|
| not $ null dbtIdent = dbtIdent <> "-" <> n
|
||||||
|
| otherwise = n
|
||||||
|
|
||||||
|
psResult <- runInputGetResult $ PaginationSettings
|
||||||
|
<$> ireq sortingField (wIdent "sorting")
|
||||||
|
<*> (fromMaybe (psLimit defPS) <$> iopt intField (wIdent "pagesize"))
|
||||||
|
<*> (fromMaybe (psPage defPS) <$> iopt intField (wIdent "page"))
|
||||||
|
<*> ireq checkBoxField (wIdent "table-only")
|
||||||
|
|
||||||
|
$(logDebug) . tshow $ (,,,) <$> (length . psSorting <$> psResult)
|
||||||
|
<*> (psLimit <$> psResult)
|
||||||
|
<*> (psPage <$> psResult)
|
||||||
|
<*> (psShortcircuit <$> psResult)
|
||||||
|
|
||||||
|
let
|
||||||
|
(errs, PaginationSettings{..}) = case psResult of
|
||||||
|
FormSuccess ps -> runPSValidator $ Just ps
|
||||||
|
FormFailure errs -> first (map SomeMessage errs <>) $ runPSValidator Nothing
|
||||||
|
FormMissing -> runPSValidator Nothing
|
||||||
|
sqlQuery' = dbtSQLQuery
|
||||||
|
<* E.orderBy (map sqlSortDirection psSorting)
|
||||||
|
<* E.limit psLimit
|
||||||
|
<* E.offset (psPage * psLimit)
|
||||||
|
|
||||||
|
mapM_ (addMessageI "warning") errs
|
||||||
|
|
||||||
|
rows <- runDB $ E.select sqlQuery'
|
||||||
|
|
||||||
|
bool return (sendResponse <=< tblLayout) psShortcircuit $ do
|
||||||
|
encodeCellTable dbtAttrs dbtColonnade rows
|
||||||
|
where
|
||||||
|
tblLayout :: Widget -> Handler Html
|
||||||
|
tblLayout = widgetToPageContent >=> (\tbl -> withUrlRenderer $(hamletFile "templates/table-layout.hamlet"))
|
||||||
8
src/Handler/Utils/Templates.hs
Normal file
8
src/Handler/Utils/Templates.hs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{-# LANGUAGE NoImplicitPrelude, TemplateHaskell #-}
|
||||||
|
|
||||||
|
module Handler.Utils.Templates where
|
||||||
|
|
||||||
|
import Import.NoFoundation
|
||||||
|
|
||||||
|
lipsum :: WidgetT site IO ()
|
||||||
|
lipsum = $(widgetFile "widgets/lipsum")
|
||||||
@ -16,3 +16,6 @@ import Data.Fixed as Import
|
|||||||
|
|
||||||
import CryptoID as Import
|
import CryptoID as Import
|
||||||
import Data.UUID as Import (UUID)
|
import Data.UUID as Import (UUID)
|
||||||
|
|
||||||
|
|
||||||
|
import Text.Lucius as Import
|
||||||
|
|||||||
7022
static/css/bootstrap.css
vendored
7022
static/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
9
static/css/fonts.css
Normal file
9
static/css/fonts.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Glyphicons Halflings';
|
||||||
|
src: url('../fonts/glyphicons-halflings-regular.eot');
|
||||||
|
src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),
|
||||||
|
url('../fonts/glyphicons-halflings-regular.woff') format('woff'),
|
||||||
|
url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),
|
||||||
|
url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
|
||||||
|
}
|
||||||
34
static/css/icons.css
Normal file
34
static/css/icons.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
.glyphicon {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.glyphicon::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-family: 'Glyphicons Halflings';
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glyphicon--home::before {
|
||||||
|
content: '\e021';
|
||||||
|
}
|
||||||
|
.glyphicon--book::before {
|
||||||
|
content: '\e043';
|
||||||
|
}
|
||||||
|
.glyphicon--profile::before {
|
||||||
|
content: '\e019';
|
||||||
|
}
|
||||||
|
.glyphicon--user::before {
|
||||||
|
content: '\e008';
|
||||||
|
}
|
||||||
|
.glyphicon--login::before {
|
||||||
|
content: '\e161';
|
||||||
|
}
|
||||||
|
.glyphicon--logout::before {
|
||||||
|
content: '\e163';
|
||||||
|
}
|
||||||
7
static/js/featureChecker.js
Normal file
7
static/js/featureChecker.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
window.addEventListener('touchstart', function onFirstTouch() {
|
||||||
|
// we could use a class
|
||||||
|
document.body.classList.add('touch-supported');
|
||||||
|
|
||||||
|
// we only need to know once that a human touched the screen, so we can stop listening now
|
||||||
|
window.removeEventListener('touchstart', onFirstTouch, false);
|
||||||
|
}, false);
|
||||||
@ -1,38 +1,34 @@
|
|||||||
<div .masthead>
|
<div .course-header>
|
||||||
<div .container>
|
<div .course-header__info>
|
||||||
<div .row>
|
<table>
|
||||||
<h1 .header>
|
<tr>
|
||||||
#{courseName course}
|
<th>Teilnehmer
|
||||||
$maybe school <- schoolMB
|
<td>
|
||||||
<h4>
|
#{participants}
|
||||||
#{schoolName school}
|
$maybe capacity <- courseCapacity course
|
||||||
|
\ von #{capacity}
|
||||||
|
<tr>
|
||||||
|
<th>Anmeldezeitraum
|
||||||
|
<td>
|
||||||
|
$maybe regFrom <- courseRegisterFrom course
|
||||||
|
#{formatTimeGerWD regFrom}
|
||||||
|
$maybe regTo <- courseRegisterTo course
|
||||||
|
\ bis #{formatTimeGerWD regTo}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form method=post action=@{CourseShowR tid csh} enctype=#{regEnctype}>
|
||||||
|
^{regWidget}
|
||||||
|
|
||||||
|
<div .course-header__title>
|
||||||
|
<h1>#{courseName course}
|
||||||
|
$maybe school <- schoolMB
|
||||||
|
<h4>#{schoolName school}
|
||||||
|
|
||||||
|
|
||||||
<div .container>
|
<div .container>
|
||||||
<div .bs-docs-section>
|
$maybe descr <- courseDescription course
|
||||||
<div .row>
|
<h2 #description>Beschreibung
|
||||||
<div .col-lg-12>
|
<p> #{descr}
|
||||||
<div .page-header>
|
$maybe link <- courseLinkExternal course
|
||||||
|
<h4 #linl>Homepage
|
||||||
$maybe descr <- courseDescription course
|
<a href=#{link}>#{link}
|
||||||
<h2 #description>Beschreibung
|
|
||||||
<p> #{descr}
|
|
||||||
$maybe link <- courseLinkExternal course
|
|
||||||
<h4 #linl>Homepage
|
|
||||||
<a href=#{link}>#{link}
|
|
||||||
<div .row>
|
|
||||||
<div .col-lg-12>
|
|
||||||
<h4>Teilnehmer
|
|
||||||
#{participants}
|
|
||||||
$maybe capacity <- courseCapacity course
|
|
||||||
\ von #{capacity}
|
|
||||||
<br>
|
|
||||||
$maybe regFrom <- courseRegisterFrom course
|
|
||||||
Anmeldezeitraum: #{formatTimeGerWD regFrom}
|
|
||||||
$maybe regTo <- courseRegisterTo course
|
|
||||||
\ bis #{formatTimeGerWD regTo}
|
|
||||||
<form method=post action=@{CourseShowR tid csh} enctype=#{regEnctype}>
|
|
||||||
^{regWidget}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
|
|||||||
19
templates/course.lucius
Normal file
19
templates/course.lucius
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.course-header {
|
||||||
|
/*display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-header__title {
|
||||||
|
align-self: baseline;
|
||||||
|
}
|
||||||
|
.course-header__info {
|
||||||
|
border: 1px solid var(--greybase);
|
||||||
|
padding: 13px;
|
||||||
|
align-self: center;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-header__info table {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
4
templates/courses.hamlet
Normal file
4
templates/courses.hamlet
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<div .container>
|
||||||
|
<h1>Kursübersicht für Semester #{termToText $ unTermKey tidini}
|
||||||
|
<div .scrolltable>
|
||||||
|
^{coursesTable}
|
||||||
@ -19,12 +19,7 @@ $newline never
|
|||||||
\<!--[if lt IE 9]>
|
\<!--[if lt IE 9]>
|
||||||
\<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
\<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
\<![endif]-->
|
\<![endif]-->
|
||||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js">
|
|
||||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.0.3/js.cookie.min.js">
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.0.3/js.cookie.min.js">
|
||||||
|
|
||||||
\<!-- Bootstrap-3.3.7 compiled and minified JavaScript -->
|
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous">
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* The `defaultCsrfMiddleware` Middleware added in Foundation.hs adds a CSRF token to the request cookies. */
|
/* The `defaultCsrfMiddleware` Middleware added in Foundation.hs adds a CSRF token to the request cookies. */
|
||||||
/* AJAX requests should add that token to a header to be validated by the server. */
|
/* AJAX requests should add that token to a header to be validated by the server. */
|
||||||
@ -43,8 +38,6 @@ $newline never
|
|||||||
\ });
|
\ });
|
||||||
}
|
}
|
||||||
|
|
||||||
<script>
|
|
||||||
document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/,'js');
|
|
||||||
<body>
|
<body>
|
||||||
^{pageBody pc}
|
^{pageBody pc}
|
||||||
|
|
||||||
|
|||||||
@ -1,60 +1,17 @@
|
|||||||
|
<!-- navigation -->
|
||||||
|
^{navbar}
|
||||||
|
|
||||||
<!-- Static navbar -->
|
<div .main>
|
||||||
<nav .navbar.navbar-default.navbar-static-top>
|
|
||||||
<div .container>
|
|
||||||
<div .navbar-header>
|
|
||||||
<button type="button" .navbar-toggle.collapsed data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
|
||||||
<span class="sr-only">Toggle navigation</span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
|
|
||||||
<div #navbar .collapse.navbar-collapse>
|
<!-- secondary navigation at the side -->
|
||||||
<ul .nav.navbar-nav>
|
^{asidenav}
|
||||||
$forall menuType <- menuTypes
|
|
||||||
$case menuType
|
|
||||||
$of NavbarLeft (MenuItem label route _)
|
|
||||||
<li :Just route == mcurrentRoute:.active>
|
|
||||||
<a href=@{route}>#{label}
|
|
||||||
$of NavbarExtra (MenuItem label route _)
|
|
||||||
<li :Just route == mcurrentRoute:.active>
|
|
||||||
<a href=@{route}>#{label}
|
|
||||||
$of _
|
|
||||||
|
|
||||||
<ul .nav.navbar-nav.navbar-right>
|
<div .main__content>
|
||||||
$forall menuType <- menuTypes
|
|
||||||
$case menuType
|
|
||||||
$of NavbarRight (MenuItem label route _)
|
|
||||||
<li :Just route == mcurrentRoute:.active>
|
|
||||||
<a href=@{route}>#{label}
|
|
||||||
$of _
|
|
||||||
|
|
||||||
<!-- Page Contents -->
|
|
||||||
|
|
||||||
<div .container>
|
|
||||||
$if not $ Just HomeR == mcurrentRoute
|
|
||||||
<ul .breadcrumb>
|
|
||||||
$forall bc <- parents
|
|
||||||
<li>
|
|
||||||
<a href="@{fst bc}">#{snd bc}
|
|
||||||
|
|
||||||
<li .active>#{title}
|
|
||||||
|
|
||||||
|
<!-- alerts -->
|
||||||
$forall (status, msg) <- mmsgs
|
$forall (status, msg) <- mmsgs
|
||||||
$with status2 <- bool status "info" (status == "")
|
$with status2 <- bool status "info" (status == "")
|
||||||
<div class="alert alert-#{status2}">#{msg}
|
<div class="alert alert-#{status2}">#{msg}
|
||||||
|
|
||||||
|
<!-- actual content -->
|
||||||
$if (Just HomeR == mcurrentRoute)
|
|
||||||
^{widget}
|
^{widget}
|
||||||
$else
|
|
||||||
<div .container>
|
|
||||||
<div .row>
|
|
||||||
<div .col-md-12>
|
|
||||||
^{widget}
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<footer .footer>
|
|
||||||
<div .container>
|
|
||||||
<p .text-muted>
|
|
||||||
#{appCopyright $ appSettings master}
|
|
||||||
|
|||||||
@ -1,75 +1,211 @@
|
|||||||
.masthead,
|
:root {
|
||||||
.navbar {
|
/* THEME 1 */
|
||||||
background-color: rgb(27, 28, 29);
|
--base00: #72a85b;
|
||||||
|
--base-bg-color: #1d1c1d;
|
||||||
|
--base-font-color: #fff;
|
||||||
|
--sec-font-color: #fff;
|
||||||
|
--box-bg-color: #3c3c3c;
|
||||||
|
/* THEME 2 */
|
||||||
|
--base00: #38428a;
|
||||||
|
--base-bg-color: #ffffff;
|
||||||
|
--base-font-color: rgb(53, 53, 53);
|
||||||
|
--sec-font-color: #eaf2ff;
|
||||||
|
--box-bg-color: #dddddd;
|
||||||
|
/* THEME 3 */
|
||||||
|
--darkbase: #364B60;
|
||||||
|
--lightbase: #2490E8;
|
||||||
|
--lighterbase: #60C2FF;
|
||||||
|
--whitebase: #FCFFFA;
|
||||||
|
--greybase: #B1B5C0;
|
||||||
|
--fontbase: #34303a;
|
||||||
|
--fontsec: #5b5861;
|
||||||
|
/* THEME 4 */
|
||||||
|
--darkbase: #263C4C;
|
||||||
|
--lightbase: #598EB5;
|
||||||
|
--lighterbase: #5F98C2;
|
||||||
|
--whitebase: #FCFFFA;
|
||||||
|
--greybase: #B1B5C0;
|
||||||
|
--blackbase: #1A2A36;
|
||||||
|
--fontbase: #34303a;
|
||||||
|
--fontsec: #5b5861;
|
||||||
|
--primarybase: #4C7A9C;
|
||||||
|
|
||||||
|
|
||||||
|
/* THEME INDEPENDENT COLORS */
|
||||||
|
--errorbase: red;
|
||||||
|
--warningbase: #fe7700;
|
||||||
|
--validbase: #2dcc35;
|
||||||
|
--infobase: var(--darkbase);
|
||||||
|
|
||||||
|
|
||||||
|
/* FONTS */
|
||||||
|
--fontfamilybase: "Source Sans Pro", Helvetica, sans-serif;
|
||||||
|
|
||||||
|
/* DIMENSIONS */
|
||||||
|
--header-height: 80px;
|
||||||
|
--header-height-collapsed: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-default .navbar-nav > .active > a {
|
* {
|
||||||
background-color: transparent;
|
box-sizing: border-box;
|
||||||
border-bottom: 2px solid white;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav {
|
body {
|
||||||
padding-bottom: 1em;
|
background-color: white;
|
||||||
|
color: var(--fontbase);
|
||||||
|
font-family: var(--fontfamilybase);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.masthead {
|
a,
|
||||||
margin-top: -21px;
|
a:visited {
|
||||||
color: white;
|
text-decoration: none;
|
||||||
text-align: center;
|
font-weight: 600;
|
||||||
min-height: 500px;
|
transition: color .2s ease, background-color .2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.masthead .header {
|
ul {
|
||||||
max-width: 700px;
|
list-style-type: none;
|
||||||
margin: 0 auto;
|
|
||||||
font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.masthead h1.header {
|
.list--inline > li {
|
||||||
margin-top: 1em;
|
display: inline-block;
|
||||||
margin-bottom: 0em;
|
|
||||||
font-size: 4.5em;
|
|
||||||
line-height: 1.2em;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.masthead h2 {
|
|
||||||
font-size: 1.7em;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.masthead .btn {
|
h1, h2, h3, h4, h5 {
|
||||||
margin: 1em 0;
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
margin: 20px 0 10px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 10px 0 5px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
margin: 21px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scrolltable {
|
||||||
/* Common styles for all types */
|
width: 100%;
|
||||||
.bs-callout {
|
overflow: auto;
|
||||||
padding: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-left-width: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-callout p:last-child {
|
th, td {
|
||||||
margin-bottom: 0;
|
text-align: left;
|
||||||
|
padding: 0 13px 0 7px;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
th:first-child,
|
||||||
|
td:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
border-left: 2px solid var(--greybase);
|
||||||
|
}
|
||||||
|
/* LAYOUT */
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
min-height: calc(100vh - var(--header-height));
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-callout-info {
|
.main__content {
|
||||||
border-left-color: #1b809e;
|
position: relative;
|
||||||
|
background-color: white;
|
||||||
|
padding: 0 40px;
|
||||||
|
flex: 1;
|
||||||
|
z-index: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> .container {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--darkbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--lightbase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Space things out */
|
.pseudo-focus {
|
||||||
.bs-docs-section {
|
outline: 5px auto var(--lightbase);
|
||||||
margin-bottom: 60px;
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
}
|
|
||||||
.bs-docs-section:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#message {
|
/* GENERAL BUTTON STYLES */
|
||||||
margin-bottom: 40px;
|
input[type="submit"],
|
||||||
|
input[type="button"],
|
||||||
|
button,
|
||||||
|
.btn {
|
||||||
|
outline: 0;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: 0;
|
||||||
|
background-color: var(--lightbase);
|
||||||
|
color: white;
|
||||||
|
padding: 10px 17px;
|
||||||
|
min-width: 100px;
|
||||||
|
transition: all .1s;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
input.btn-primary,
|
||||||
|
button.btn-primary,
|
||||||
|
.btn.btn-primary {
|
||||||
|
background-color: var(--primarybase);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.btn-info,
|
||||||
|
button.btn-info,
|
||||||
|
.btn.btn-info {
|
||||||
|
background-color: var(--infobase)
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="submit"][disabled],
|
||||||
|
input[type="button"][disabled],
|
||||||
|
button[disabled],
|
||||||
|
.btn[disabled] {
|
||||||
|
opacity: 0.3;
|
||||||
|
background-color: var(--greybase);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="submit"]:not([disabled]):hover,
|
||||||
|
input[type="button"]:not([disabled]):hover,
|
||||||
|
button:not([disabled]):hover,
|
||||||
|
.btn:not([disabled]):hover {
|
||||||
|
background-color: var(--lighterbase);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="submit"].btn-info:hover,
|
||||||
|
input[type="button"].btn-info:hover,
|
||||||
|
button.btn-info:hover,
|
||||||
|
.btn.btn-info:hover {
|
||||||
|
background-color: var(--greybase)
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-debug {
|
.alert-debug {
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
$newline never
|
|
||||||
#{fragment}
|
|
||||||
$case formLayout
|
|
||||||
$of FormStandard
|
|
||||||
$forall view <- views
|
|
||||||
<div :fvRequired view:.required :not $ fvRequired view:.optional :isJust $ fvErrors view:.has-error>
|
|
||||||
$if not (Blaze.null $ fvLabel view)
|
|
||||||
<label for=#{fvId view}>#{fvLabel view}
|
|
||||||
^{fvInput view}
|
|
||||||
@ -1,82 +1,69 @@
|
|||||||
<div .masthead>
|
|
||||||
<div .container>
|
|
||||||
<div .row>
|
|
||||||
<h1 .header>
|
|
||||||
ReWorX - Demo
|
|
||||||
<h3>
|
|
||||||
Testumgebung für die Re-Implementierung von
|
|
||||||
<a href="https://uniworx.ifi.lmu.de/">
|
|
||||||
UniWorX
|
|
||||||
|
|
||||||
<div .container>
|
<div .container>
|
||||||
<!-- Starting
|
<h1>ReWorX - Demo
|
||||||
================================================== -->
|
<h3>
|
||||||
<div .bs-docs-section>
|
Testumgebung für die Re-Implementierung von <a href="https://uniworx.ifi.lmu.de/">UniWorX</a>
|
||||||
<div .row>
|
<p>
|
||||||
<div .col-lg-12>
|
Die Reimplementierung von
|
||||||
<div .page-header>
|
UniWorX ist noch nicht abgeschlossen.
|
||||||
<h2 #start>Übersicht
|
|
||||||
|
|
||||||
<p>
|
<p .alert .alert-danger>Das System ist noch nicht produktiv einsetzbar
|
||||||
Die Reimplementierung von
|
|
||||||
UniWorX ist noch nicht abgeschlossen.
|
|
||||||
|
|
||||||
<div .alert .alert-danger>
|
<hr>
|
||||||
Das System ist noch nicht produktiv
|
<div .container>
|
||||||
einsetzbar
|
<h2 .js-show-hide__toggle>Teilweise funktionierende Abschnitte
|
||||||
|
|
||||||
<div .bs-docs-section>
|
<ul>
|
||||||
<div .row>
|
<li .list-group-item>
|
||||||
<div .col-lg-12>
|
<a href=@{UsersR}>Benutzer Verwaltung
|
||||||
<div .page-header>
|
|
||||||
<h2 #design>Design
|
|
||||||
|
|
||||||
<p>
|
<li .list-group-item>
|
||||||
Wir konzentrieren uns derzeit
|
<a href=@{TermShowR}>Semester Verwaltung
|
||||||
ausschließlich auf die Funktionalität.
|
<a href=@{TermEditR}>Neues Semester anlegen
|
||||||
<p>
|
|
||||||
Insbesondere Formulare zeigen
|
|
||||||
alle Eingabefelder und Knöpfe
|
|
||||||
ohne eine gezielte Anordnung
|
|
||||||
und Reihenfolge.
|
|
||||||
Dies läßt sich leicht nachträglich einstellen.
|
|
||||||
<p>
|
|
||||||
Momentan werden noch keine speziellen Grafiken oder CSS verwendet;
|
|
||||||
sondern nur gewöhnliches Bootstrap3.
|
|
||||||
|
|
||||||
<div .bs-docs-section>
|
<li .list-group-item>
|
||||||
<div .row>
|
|
||||||
<div .col-lg-12>
|
|
||||||
<div .page-header>
|
|
||||||
<h3 #design>Teilweise funktionierende Abschnitte
|
|
||||||
|
|
||||||
<ul .list-group>
|
|
||||||
|
|
||||||
<li .list-group-item>
|
|
||||||
<a href=@{UsersR}>Benutzer Verwaltung
|
|
||||||
|
|
||||||
<li .list-group-item>
|
|
||||||
<a href=@{TermShowR}>Semester Verwaltung
|
|
||||||
<a href=@{TermEditR}>Neues Semester anlegen
|
|
||||||
|
|
||||||
|
|
||||||
<li .list-group-item>
|
|
||||||
<a href=@{CourseNewR}>Kurse anlegen
|
<a href=@{CourseNewR}>Kurse anlegen
|
||||||
editieren und anzeigen
|
|
||||||
|
|
||||||
<li .list-group-item>
|
<li .list-group-item>
|
||||||
<a href=@{SubmissionListR}>Dateien hochladen und abrufen
|
<a href=@{SubmissionListR}>Dateien hochladen und abrufen
|
||||||
|
|
||||||
<h3 #design>Funktionen zum Testen
|
<hr>
|
||||||
|
<div .container>
|
||||||
|
<h2 .js-show-hide__toggle data-collapsed=true>Tabellen
|
||||||
|
<table .js-sortable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th .sorted-asc>ID
|
||||||
|
<th>TH1
|
||||||
|
<th>TH2
|
||||||
|
<th>TH3
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>0
|
||||||
|
<td>14
|
||||||
|
<td>CON2
|
||||||
|
<td>3
|
||||||
|
<tr>
|
||||||
|
<td>1
|
||||||
|
<td>5
|
||||||
|
<td>ONT2
|
||||||
|
<td>13
|
||||||
|
<tr>
|
||||||
|
<td>2
|
||||||
|
<td>CONT1
|
||||||
|
<td>NT2
|
||||||
|
<td>43
|
||||||
|
<tr>
|
||||||
|
<td>3
|
||||||
|
<td>43
|
||||||
|
<td>T2C2
|
||||||
|
<td>35
|
||||||
|
|
||||||
<ul .list-group>
|
<hr>
|
||||||
|
<div .container>
|
||||||
<li .list-group-item>
|
<h2>Funktionen zum Testen
|
||||||
Knopf-Test:
|
|
||||||
<form .form-inline method=post action=@{HomeR} enctype=#{btnEnctype}>
|
|
||||||
^{btnWdgt}
|
|
||||||
|
|
||||||
<li .list-group-item>
|
|
||||||
<a href=@{CourseNewR}>Kurse anlegen
|
|
||||||
editieren und anzeigen
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Knopf-Test:
|
||||||
|
<form .form-inline method=post action=@{HomeR} enctype=#{btnEnctype}>
|
||||||
|
^{btnWdgt}
|
||||||
|
|||||||
0
templates/home.julius
Normal file
0
templates/home.julius
Normal file
1
templates/standalone/inputs.hamlet
Normal file
1
templates/standalone/inputs.hamlet
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- only here to be able to include inputs using `toWidget` -->
|
||||||
177
templates/standalone/inputs.julius
Normal file
177
templates/standalone/inputs.julius
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.utils = window.utils || {};
|
||||||
|
|
||||||
|
// makes <label> smaller if <input> is focussed
|
||||||
|
window.utils.reactiveInputLabel = function(input, label) {
|
||||||
|
// updates to dom
|
||||||
|
if (input.value.length > 0) {
|
||||||
|
label.classList.add('reactive-label--small');
|
||||||
|
}
|
||||||
|
// add event listeners
|
||||||
|
input.addEventListener('focus', function() {
|
||||||
|
label.classList.add('reactive-label--small');
|
||||||
|
});
|
||||||
|
label.addEventListener('click', function() {
|
||||||
|
label.classList.add('reactive-label--small');
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
input.addEventListener('blur', function() {
|
||||||
|
if (input.value.length < 1) {
|
||||||
|
label.classList.remove('reactive-label--small');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.utils.reactiveFileUpload = function(input, parent) {
|
||||||
|
var currValidInputCount = 0;
|
||||||
|
var addMore = false;
|
||||||
|
var inputName = input.getAttribute('name');
|
||||||
|
// FileInput PseudoClass
|
||||||
|
function FileInput(container, input, label, remover) {
|
||||||
|
this.container = container;
|
||||||
|
this.input = input;
|
||||||
|
this.label = label;
|
||||||
|
this.remover = remover;
|
||||||
|
addListener(this);
|
||||||
|
|
||||||
|
this.addTo = function(parentElement) {
|
||||||
|
parentElement.appendChild(this.container);
|
||||||
|
}
|
||||||
|
this.remove = function() {
|
||||||
|
this.container.remove();
|
||||||
|
}
|
||||||
|
this.isValid = function() {
|
||||||
|
return this.container.classList.contains('file-input__container--valid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function addNextInput() {
|
||||||
|
var inputs = parent.querySelectorAll('.file-input__container');
|
||||||
|
if (inputs[inputs.length - 1].classList.contains('file-input__container--valid')) {
|
||||||
|
makeInput(inputName).addTo(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// updates submitbutton and form-group-stripe
|
||||||
|
function updateForm() {
|
||||||
|
var submitBtn = parent.parentElement.querySelector('[type=submit]');
|
||||||
|
parent.classList.remove('form-group--has-error');
|
||||||
|
if (currValidInputCount > 0) {
|
||||||
|
if (parent.classList.contains('form-group')) {
|
||||||
|
parent.classList.add('form-group--valid')
|
||||||
|
}
|
||||||
|
submitBtn.removeAttribute('disabled');
|
||||||
|
addNextInput();
|
||||||
|
} else {
|
||||||
|
if (parent.classList.contains('form-group')) {
|
||||||
|
parent.classList.remove('form-group--valid')
|
||||||
|
}
|
||||||
|
submitBtn.setAttribute('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// addseventlistener destInput
|
||||||
|
function addListener(fileInput) {
|
||||||
|
fileInput.input.addEventListener('change', function(event) {
|
||||||
|
if (fileInput.input.value.length > 0) {
|
||||||
|
// update label
|
||||||
|
var filePath = fileInput.input.value.replace(/\\/g, '/').split('/');
|
||||||
|
var fileName = filePath[filePath.length - 1];
|
||||||
|
fileInput.label.innerHTML = fileName;
|
||||||
|
// increase count if this field was empty previously
|
||||||
|
if (!fileInput.isValid()) {
|
||||||
|
currValidInputCount++;
|
||||||
|
}
|
||||||
|
fileInput.container.classList.add('file-input__container--valid')
|
||||||
|
// show next input
|
||||||
|
} else {
|
||||||
|
currValidInputCount--;
|
||||||
|
fileInput.remove();
|
||||||
|
}
|
||||||
|
updateForm();
|
||||||
|
});
|
||||||
|
fileInput.input.addEventListener('focus', function() {
|
||||||
|
fileInput.container.classList.add('pseudo-focus');
|
||||||
|
});
|
||||||
|
fileInput.input.addEventListener('blur', function() {
|
||||||
|
fileInput.container.classList.remove('pseudo-focus');
|
||||||
|
});
|
||||||
|
fileInput.label.addEventListener('click', function() {
|
||||||
|
fileInput.input.click();
|
||||||
|
});
|
||||||
|
fileInput.remover.addEventListener('click', function() {
|
||||||
|
if (fileInput.isValid()) {
|
||||||
|
currValidInputCount--;
|
||||||
|
}
|
||||||
|
fileInput.remove();
|
||||||
|
updateForm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// create new wrapped input element with name name
|
||||||
|
function makeInput(name) {
|
||||||
|
var cont = document.createElement('div');
|
||||||
|
var desc = document.createElement('span');
|
||||||
|
var nextInput = document.createElement('input');
|
||||||
|
var remover = document.createElement('div');
|
||||||
|
cont.classList.add('file-input__container');
|
||||||
|
desc.classList.add('file-input__label', 'btn');
|
||||||
|
remover.classList.add('file-input__remover');
|
||||||
|
nextInput.setAttribute('name', name);
|
||||||
|
nextInput.setAttribute('type', 'file');
|
||||||
|
cont.appendChild(nextInput);
|
||||||
|
cont.appendChild(desc);
|
||||||
|
cont.appendChild(remover);
|
||||||
|
return new FileInput(cont, nextInput, desc, remover);
|
||||||
|
}
|
||||||
|
// initial setup
|
||||||
|
function setup() {
|
||||||
|
var newInput = makeInput(inputName);
|
||||||
|
input.remove();
|
||||||
|
newInput.addTo(parent);
|
||||||
|
updateForm();
|
||||||
|
}
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.utils.reactiveFormGroup = function(formGroup, input) {
|
||||||
|
// updates to dom
|
||||||
|
if (input.value.length > 0) {
|
||||||
|
formGroup.classList.add('form-group--valid');
|
||||||
|
} else {
|
||||||
|
formGroup.classList.remove('form-group--valid');
|
||||||
|
}
|
||||||
|
input.addEventListener('input', function() {
|
||||||
|
formGroup.classList.remove('form-group--has-error');
|
||||||
|
if (input.value.length > 0) {
|
||||||
|
formGroup.classList.add('form-group--valid');
|
||||||
|
} else {
|
||||||
|
formGroup.classList.remove('form-group--valid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
// setup reactive labels
|
||||||
|
Array.from(document.querySelectorAll('.reactive-label')).forEach(function(label) {
|
||||||
|
var input = document.querySelector('#' + label.getAttribute('for'));
|
||||||
|
var parent = label.parentElement;
|
||||||
|
var type = input.getAttribute('type');
|
||||||
|
var isFileInput = /file/i.test(type);
|
||||||
|
var isListening = !RegExp(['date', 'checkbox', 'radio', 'hidden', 'file'].join('|')).test(type);
|
||||||
|
var isInFormGroup = parent.classList.contains('form-group') && parent.classList.contains('form-group--required');
|
||||||
|
|
||||||
|
if (isInFormGroup) {
|
||||||
|
window.utils.reactiveFormGroup(parent, input);
|
||||||
|
}
|
||||||
|
if (isFileInput) {
|
||||||
|
window.utils.reactiveFileUpload(input, parent);
|
||||||
|
}
|
||||||
|
if (isListening) {
|
||||||
|
window.utils.reactiveInputLabel(input, label);
|
||||||
|
} else {
|
||||||
|
label.classList.remove('reactive-label');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
306
templates/standalone/inputs.lucius
Normal file
306
templates/standalone/inputs.lucius
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
/* GENERAL STYLES FOR FORMS */
|
||||||
|
form {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TEXT INPUTS */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="url"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="email"] {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
padding: 7px 3px 7px;
|
||||||
|
outline: 0;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 2px solid var(--darkbase);
|
||||||
|
box-shadow: 0 2px 13px rgba(0, 0, 0, 0.05);
|
||||||
|
color: var(--fontbase);
|
||||||
|
transition: all .1s;
|
||||||
|
font-size: 16px;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus,
|
||||||
|
input[type="password"]:focus,
|
||||||
|
input[type="url"]:focus,
|
||||||
|
input[type="number"]:focus,
|
||||||
|
input[type="email"]:focus {
|
||||||
|
border-bottom-color: var(--lightbase);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BUTTON STYLE SEE default-layout.lucius */
|
||||||
|
|
||||||
|
/* TEXTAREAS */
|
||||||
|
textarea {
|
||||||
|
outline: 0;
|
||||||
|
border: 0;
|
||||||
|
padding: 7px 4px;
|
||||||
|
min-width: 300px;
|
||||||
|
min-height: 100px;
|
||||||
|
font-family: var(--fontfamilybase);
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--fontbase);
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
box-shadow: 0 2px 13px rgba(0, 0, 0, 0.05);
|
||||||
|
border-bottom: 2px solid var(--darkbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus {
|
||||||
|
background-color: transparent;
|
||||||
|
border-bottom-color: var(--lightbase);
|
||||||
|
}
|
||||||
|
/* FORM GROUPS */
|
||||||
|
.form-group {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(150px, max-content));
|
||||||
|
grid-auto-columns: minmax(150px, max-content);
|
||||||
|
grid-gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--required {
|
||||||
|
border-left: 8px solid var(--lighterbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--valid {
|
||||||
|
border-left: 8px solid var(--validbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--has-error {
|
||||||
|
border-left: 8px solid var(--errorbase) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CUSTOM LEGACY CHECKBOX AND RADIO BOXES */
|
||||||
|
input[type="checkbox"] {
|
||||||
|
position: relative;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input[type="checkbox"]::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: var(--lighterbase);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked::before {
|
||||||
|
background-color: var(--lightbase);
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:checked::after {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CUSTOM CHECKBOXES AND RADIO BOXES */
|
||||||
|
/* Completely replaces legacy checkbox and radiobox */
|
||||||
|
.checkbox,
|
||||||
|
.radio {
|
||||||
|
position: relative;
|
||||||
|
margin: 3px;
|
||||||
|
|
||||||
|
> [type="checkbox"],
|
||||||
|
> [type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> label {
|
||||||
|
display: block;
|
||||||
|
padding: 7px 13px 7px 30px;
|
||||||
|
background-color: var(--darkbase);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
> label::before,
|
||||||
|
> label::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 4px;
|
||||||
|
display: block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: white;
|
||||||
|
transition: all .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
> label::before {
|
||||||
|
width: 20px;
|
||||||
|
height: 2px;
|
||||||
|
transform: scale(0.8, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
> label::after {
|
||||||
|
width: 20px;
|
||||||
|
height: 2px;
|
||||||
|
transform: scale(0.8, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
> :checked + label {
|
||||||
|
background-color: var(--lightbase);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover > label::before,
|
||||||
|
> :checked + label::before {
|
||||||
|
transform: scale(1, 1) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover > label::after,
|
||||||
|
> :checked + label::after {
|
||||||
|
transform: scale(1, 1) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.radio > label::before {
|
||||||
|
transform: scale(0.01, 0.01) rotate(45deg);
|
||||||
|
}
|
||||||
|
.radio > label::after {
|
||||||
|
transform: scale(0.01, 0.01) rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
right: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid white;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* REACTIVE LABELS */
|
||||||
|
.reactive-label {
|
||||||
|
cursor: text;
|
||||||
|
color: var(--fontsec);
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transition: all .1s;
|
||||||
|
}
|
||||||
|
.reactive-label--small {
|
||||||
|
cursor: default;
|
||||||
|
color: var(--fontbase);
|
||||||
|
}
|
||||||
|
@media (max-width: 999px) {
|
||||||
|
.form-group {
|
||||||
|
grid-template-rows: 30px;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-top: 17px;
|
||||||
|
}
|
||||||
|
.reactive-label {
|
||||||
|
position: relative;
|
||||||
|
transform: translate(2px, 30px);
|
||||||
|
}
|
||||||
|
.reactive-label--small {
|
||||||
|
transform: translate(2px, 0px);
|
||||||
|
color: var(--fontsec);
|
||||||
|
/*font-size: 14px;*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CUSTOM FILE INPUT */
|
||||||
|
input[type="file"] {
|
||||||
|
color: white;
|
||||||
|
width: 0.1px;
|
||||||
|
height: 0.1px;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
outline: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.file-input__container {
|
||||||
|
grid-column-start: 2;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.file-input__label,
|
||||||
|
.file-input__remover {
|
||||||
|
display: block;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 5px 13px;
|
||||||
|
color: var(--whitebase);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.file-input__label {
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.file-input__label.btn {
|
||||||
|
padding: 5px 13px;
|
||||||
|
}
|
||||||
|
.file-input__label::after,
|
||||||
|
.file-input__label::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
background-color: white;
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
top: 14px;
|
||||||
|
top: 50%;
|
||||||
|
left: 12px;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input__label::after {
|
||||||
|
transform: translate(-50%, -50%) rotate(90deg);
|
||||||
|
}
|
||||||
|
.file-input__label::before {
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.file-input__remover {
|
||||||
|
display: none;
|
||||||
|
width: 40px;
|
||||||
|
height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: var(--warningbase);
|
||||||
|
position: relative;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.file-input__remover::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
width: 16px;
|
||||||
|
height: 2px;
|
||||||
|
top: 14px;
|
||||||
|
left: 12px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.file-input__container--valid > .file-input__label {
|
||||||
|
background-color: var(--lightbase);
|
||||||
|
}
|
||||||
|
.file-input__container--valid > .file-input__label::before,
|
||||||
|
.file-input__container--valid > .file-input__label::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
.file-input__container--valid > .file-input__remover {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
@media (max-width: 999px) {
|
||||||
|
.file-input__container {
|
||||||
|
grid-column-start: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
templates/standalone/showHide.hamlet
Normal file
1
templates/standalone/showHide.hamlet
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- only here to be able to include showHide using `toWidget` -->
|
||||||
59
templates/standalone/showHide.julius
Normal file
59
templates/standalone/showHide.julius
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* div.js-show-hide
|
||||||
|
* div.js-show-hide-toggle
|
||||||
|
* toggle here
|
||||||
|
* div
|
||||||
|
* content here
|
||||||
|
*/
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
var elements = Array.from(document.querySelectorAll('.js-show-hide__toggle')),
|
||||||
|
toggles = [],
|
||||||
|
path = new URL(window.location.href).pathname;
|
||||||
|
|
||||||
|
|
||||||
|
function addEventHandler(el) {
|
||||||
|
el.addEventListener('click', function elClickListener() {
|
||||||
|
var toggle = toggles[el.dataset.index];
|
||||||
|
toggle.collapsed = !toggle.collapsed;
|
||||||
|
toggle.parent.classList.toggle('js-show-hide--collapsed', toggle.collapsed);
|
||||||
|
updateLocalStorage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLocalStorage(id) {
|
||||||
|
var lsData = getStateFromLocalStorage();
|
||||||
|
lsData[path] = toggles.map(t => {
|
||||||
|
return {id: t.index, collapsed: t.collapsed};
|
||||||
|
});
|
||||||
|
window.localStorage.setItem('showHidesToggles', JSON.stringify(lsData));
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapsedStateInLocalStorage(id, fallBack) {
|
||||||
|
var lsData = getStateFromLocalStorage();
|
||||||
|
if (lsData[path] && lsData[path][id] && lsData[path][id].id === id) {
|
||||||
|
return lsData[path][id].collapsed;
|
||||||
|
}
|
||||||
|
return fallBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStateFromLocalStorage() {
|
||||||
|
return JSON.parse(window.localStorage.getItem('showHidesToggles')) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.forEach(function(el, i) {
|
||||||
|
el.dataset.index = i;
|
||||||
|
var coll = collapsedStateInLocalStorage(i, el.dataset.collapsed === 'true');
|
||||||
|
if (coll) {
|
||||||
|
el.parentElement.classList.add('js-show-hide--collapsed')
|
||||||
|
}
|
||||||
|
Array.from(el.parentElement.children).forEach(function(el) {
|
||||||
|
if (!el.classList.contains('js-show-hide__toggle')) {
|
||||||
|
el.classList.add('js-show-hide__target');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toggles.push({index: i, collapsed: coll, parent: el.parentElement});
|
||||||
|
addEventHandler(el);
|
||||||
|
});
|
||||||
|
});
|
||||||
49
templates/standalone/showHide.lucius
Normal file
49
templates/standalone/showHide.lucius
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
.js-show-hide {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide__toggle {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide__toggle:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide__toggle::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-right: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide__toggle::before,
|
||||||
|
.js-show-hide--collapsed .js-show-hide__toggle:hover::before {
|
||||||
|
left: -28px;
|
||||||
|
top: 10px;
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
border-top: 8px solid var(--lightbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide__toggle:hover::before,
|
||||||
|
.js-show-hide--collapsed .js-show-hide__toggle::before {
|
||||||
|
border-left: 8px solid var(--lightbase);
|
||||||
|
border-top: 8px solid transparent;
|
||||||
|
top: 5px;
|
||||||
|
left: -22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide__target {
|
||||||
|
transition: all .2s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide--collapsed > .js-show-hide__target {
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
1
templates/standalone/sortable.hamlet
Normal file
1
templates/standalone/sortable.hamlet
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- only here to be able to include sortable using `toWidget` -->
|
||||||
88
templates/standalone/sortable.julius
Normal file
88
templates/standalone/sortable.julius
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* delcare a table as sortable by adding class 'js-sortable'
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
var tables = [];
|
||||||
|
var ASC = 1;
|
||||||
|
var DESC = -1;
|
||||||
|
|
||||||
|
function initTable(table, tableIndex) {
|
||||||
|
var trs = table.querySelectorAll('tr');
|
||||||
|
var ths = table.querySelectorAll('th');
|
||||||
|
var trContents = [];
|
||||||
|
|
||||||
|
Array.from(trs).forEach(function(tr, rowIndex) {
|
||||||
|
if (rowIndex === 0) {
|
||||||
|
// register table headers as sort-listener
|
||||||
|
Array.from(tr.querySelectorAll('th')).forEach(function(th, thIndex) {
|
||||||
|
th.addEventListener('click', function(el) {
|
||||||
|
sortTableBy(tableIndex, thIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// register table rows
|
||||||
|
trContents.push(Array.from(tr.querySelectorAll('td')).map(function(td) {
|
||||||
|
return td.innerHTML;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tables.push({
|
||||||
|
el: table,
|
||||||
|
ths: ths,
|
||||||
|
sortBy: 0,
|
||||||
|
sortDir: ASC,
|
||||||
|
trContents,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateThs(tableIndex, thIndex, sortOrder) {
|
||||||
|
Array.from(tables[tableIndex].ths).forEach(function (th) {
|
||||||
|
th.classList.remove('sorted-asc', 'sorted-desc');
|
||||||
|
});
|
||||||
|
var suffix = sortOrder > 0 ? 'asc' : 'desc';
|
||||||
|
tables[tableIndex].ths[thIndex].classList.add('sorted-' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTableBy(tableIndex, thIndex) {
|
||||||
|
var table = tables[tableIndex];
|
||||||
|
var sortKey = thIndex;
|
||||||
|
var sortOrder = ASC;
|
||||||
|
if (table.sortBy === sortKey) {
|
||||||
|
sortOrder = table.sortDir === ASC ? DESC : ASC;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.trContents.sort(dynamicSort(sortKey, sortOrder));
|
||||||
|
tables[tableIndex].sortBy = thIndex;
|
||||||
|
tables[tableIndex].sortDir = sortOrder;
|
||||||
|
updateThs(tableIndex, thIndex, sortOrder);
|
||||||
|
|
||||||
|
Array.from(table.el.querySelectorAll('tr')).forEach(function(tr, trIndex) {
|
||||||
|
if (trIndex > 0) {
|
||||||
|
Array.from(tr.querySelectorAll('td')).forEach(function (td, tdIndex) {
|
||||||
|
td.innerHTML = table.trContents[trIndex - 1][tdIndex];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function dynamicSort(key, order) {
|
||||||
|
return function (a,b) {
|
||||||
|
var aVal = parseInt(a[key]);
|
||||||
|
var bVal = parseInt(b[key]);
|
||||||
|
if ((isNaN(aVal) && !isNaN(bVal)) || (!isNaN(aVal) && isNaN(bVal))) {
|
||||||
|
console.error('trying to sort table by row with mixed content: "%s", "%s"', a[key], b[key]);
|
||||||
|
}
|
||||||
|
aVal = isNaN(aVal) ? a[key] : aVal;
|
||||||
|
bVal = isNaN(bVal) ? b[key] : bVal;
|
||||||
|
var result = (aVal < bVal) ? -1 : (aVal > bVal) ? 1 : 0;
|
||||||
|
return result * order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawTables = document.querySelectorAll('.js-sortable');
|
||||||
|
Array.from(rawTables).forEach(function(table, i) {
|
||||||
|
initTable(table, i);
|
||||||
|
});
|
||||||
|
});
|
||||||
31
templates/standalone/sortable.lucius
Normal file
31
templates/standalone/sortable.lucius
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
table.js-sortable th {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.js-sortable th.sorted-asc,
|
||||||
|
table.js-sortable th.sorted-desc {
|
||||||
|
color: var(--darkbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.js-sortable th.sorted-asc::after,
|
||||||
|
table.js-sortable th.sorted-desc::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 15px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
border-right: 8px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.js-sortable th.sorted-asc::after {
|
||||||
|
border-top: 8px solid var(--lightbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.js-sortable th.sorted-desc::after {
|
||||||
|
border-bottom: 8px solid var(--lightbase);
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
<form method=POST enctype=#{uploadEnctype} action=@{SubmissionListR}>
|
<div .container>
|
||||||
^{uploadWidget}
|
<form method=POST enctype=#{uploadEnctype} action=@{SubmissionListR}>
|
||||||
|
^{uploadWidget}
|
||||||
|
|
||||||
<form method=POST enctype=#{selectEncoding} target=_blank action=@{SubmissionDownloadMultiArchiveR}>
|
<div .container>
|
||||||
^{subTable}
|
<form method=POST enctype=#{selectEncoding} target=_blank action=@{SubmissionDownloadMultiArchiveR}>
|
||||||
<button .btn .btn-default type=submit >Markierte herunterladen
|
^{subTable}
|
||||||
|
<button .btn .btn-default type=submit >Markierte herunterladen
|
||||||
|
|||||||
1
templates/table-layout.hamlet
Normal file
1
templates/table-layout.hamlet
Normal file
@ -0,0 +1 @@
|
|||||||
|
^{pageBody tbl}
|
||||||
5
templates/terms.hamlet
Normal file
5
templates/terms.hamlet
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div .container>
|
||||||
|
<h1>Semesterübersicht
|
||||||
|
|
||||||
|
<div .scrolltable>
|
||||||
|
^{table}
|
||||||
32
templates/widgets/asidenav.hamlet
Normal file
32
templates/widgets/asidenav.hamlet
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<aside .main__aside>
|
||||||
|
<div .asidenav>
|
||||||
|
<div .asidenav__box--dont-hide>
|
||||||
|
<ul .asidenav__list>
|
||||||
|
$forall menuType <- menuTypes
|
||||||
|
$case menuType
|
||||||
|
$of NavbarAside (MenuItem label mIcon route _)
|
||||||
|
<li .asidenav__list-item :Just route == mcurrentRoute:.asidenav__list-item--active>
|
||||||
|
<a .asidenav__link-wrapper href=@{route}>
|
||||||
|
$if isJust mIcon
|
||||||
|
<div .glyphicon.glyphicon--#{fromMaybe "" mIcon}>
|
||||||
|
<div .asidenav__link-label>#{label}
|
||||||
|
$of _
|
||||||
|
|
||||||
|
<div .asidenav__box--dont-hide>
|
||||||
|
<h3 .asidenav__box-title.js-show-hide__toggle>
|
||||||
|
WiSe 17/18
|
||||||
|
<ul .asidenav__list>
|
||||||
|
<li .asidenav__list-item>
|
||||||
|
<a .asidenav__link-wrapper href="/course/S2018/ixd/show">
|
||||||
|
<div .asidenav__link-triple>IXD
|
||||||
|
<div .asidenav__link-label>Interaction Design
|
||||||
|
<li .asidenav__list-item>
|
||||||
|
<a .asidenav__link-wrapper href="/course/S2018/ffp/show">
|
||||||
|
<div .asidenav__link-triple>FFP
|
||||||
|
<div .asidenav__link-label>Fortgeschrittene Funktionale Programmierung
|
||||||
|
<li .asidenav__list-item>
|
||||||
|
<a .asidenav__link-wrapper href="/course/S2018/dbs/show">
|
||||||
|
<div .asidenav__link-triple>DBS
|
||||||
|
<div .asidenav__link-label>Datenbanksysteme
|
||||||
|
|
||||||
|
<div .asidenav__toggler>
|
||||||
81
templates/widgets/asidenav.julius
Normal file
81
templates/widgets/asidenav.julius
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.utils = window.utils || {};
|
||||||
|
|
||||||
|
window.utils.aside = function(asideEl, topNav) {
|
||||||
|
var collapsed = false;
|
||||||
|
var collClass = 'main__aside--collapsed';
|
||||||
|
var animClass = 'main__aside--transitioning';
|
||||||
|
var aboveCollapsedNav = false;
|
||||||
|
|
||||||
|
init();
|
||||||
|
function init() {
|
||||||
|
var collLS = window.localStorage.getItem('asidenavCollapsed') === 'true';
|
||||||
|
if (document.body.getBoundingClientRect().width < 999 || collLS) {
|
||||||
|
asideEl.classList.add(collClass);
|
||||||
|
collapsed = true;
|
||||||
|
if (topNav) {
|
||||||
|
topNav.style.paddingLeft = '90px';
|
||||||
|
window.setTimeout(function() {
|
||||||
|
topNav.classList.add('navbar--animated');
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
} else if (topNav) {
|
||||||
|
topNav.classList.add('navbar--animated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
if (collapsed && !hasClass() || !collapsed && hasClass()) {
|
||||||
|
asideEl.classList.add(animClass);
|
||||||
|
asideEl.classList.toggle(collClass, collapsed);
|
||||||
|
if (topNav) {
|
||||||
|
topNav.style.paddingLeft = collapsed ? '90px' : '';
|
||||||
|
}
|
||||||
|
window.localStorage.setItem('asidenavCollapsed', collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasClass() {
|
||||||
|
return asideEl.classList.contains(collClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
asideEl.querySelector('.asidenav__toggler').addEventListener('click', function(event) {
|
||||||
|
collapsed = !collapsed;
|
||||||
|
check();
|
||||||
|
}, false);
|
||||||
|
asideEl.addEventListener('transitionend', function(event) {
|
||||||
|
if (event.propertyName === 'opacity') {
|
||||||
|
asideEl.classList.remove(animClass);
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
collapsed = document.body.getBoundingClientRect().width < 999;
|
||||||
|
check();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
asideEl.addEventListener('mouseover', function(event) {
|
||||||
|
if (!collapsed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
aboveCollapsedNav = true;
|
||||||
|
console.log(event);
|
||||||
|
window.setTimeout(function() {
|
||||||
|
if (aboveCollapsedNav && !document.body.classList.contains('touch-supported')) {
|
||||||
|
asideEl.classList.add('pseudo-hover');
|
||||||
|
}
|
||||||
|
}, 430);
|
||||||
|
}, false);
|
||||||
|
asideEl.addEventListener('mouseleave', function(event) {
|
||||||
|
aboveCollapsedNav = false;
|
||||||
|
asideEl.classList.remove('pseudo-hover');
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
utils.aside(document.querySelector('.main__aside'), document.querySelector('.navbar'));
|
||||||
|
|
||||||
|
});
|
||||||
163
templates/widgets/asidenav.lucius
Normal file
163
templates/widgets/asidenav.lucius
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
.main__aside {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--darkbase);
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 1;
|
||||||
|
flex: 0 0 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.main__aside--transitioning {
|
||||||
|
transition: flex-basis .2s ease;
|
||||||
|
}
|
||||||
|
.main__aside--transitioning .asidenav__box{
|
||||||
|
transition: opacity .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main__aside--collapsed.pseudo-hover {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.main__aside--collapsed {
|
||||||
|
width: 50px;
|
||||||
|
flex-basis: 50px;
|
||||||
|
|
||||||
|
.asidenav__box-title {
|
||||||
|
width: 50px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.main__aside--collapsed .asidenav__box {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.main__aside--collapsed .asidenav__box--dont-hide {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main__aside--collapsed:not(.main__aside--transitioning) .asidenav__box {
|
||||||
|
height: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidenav {
|
||||||
|
width: 300px;
|
||||||
|
margin-top: 20px;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
.js-show-hide__toggle::before {
|
||||||
|
top: 14px;
|
||||||
|
right: 12px;
|
||||||
|
left: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-show-hide__toggle:hover::before,
|
||||||
|
.js-show-hide--collapsed .js-show-hide__toggle::before {
|
||||||
|
top: 10px !important;
|
||||||
|
right: 8px !important;
|
||||||
|
}
|
||||||
|
.js-show-hide--collapsed .js-show-hide__toggle:hover::before {
|
||||||
|
top: 14px !important;
|
||||||
|
right: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.asidenav__box {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px 0;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 4px solid var(--whitebase);
|
||||||
|
background-color: var(--darkbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidenav__box-title {
|
||||||
|
padding: 7px 13px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidenav__list-item {
|
||||||
|
position: relative;
|
||||||
|
background-color: white;
|
||||||
|
color: var(--darkbase);
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
&:not(.asidenav__list-item--active):hover {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--darkbase);
|
||||||
|
|
||||||
|
.asidenav__link-wrapper,
|
||||||
|
.asidenav__link-label {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidenav__link-triple {
|
||||||
|
transform: scale(1.2, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.asidenav__list-item--active {
|
||||||
|
background-color: var(--darkbase);
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
.asidenav__link-wrapper {
|
||||||
|
pointer-events: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidenav__link-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
height: 50px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
color: var(--darkbase);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.glyphicon {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidenav__link-triple {
|
||||||
|
background-color: var(--darkbase);
|
||||||
|
color: var(--whitebase);
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 13px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
outline: 1px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidenav__toggler {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background-color .2s ease;
|
||||||
|
border-top: 1px solid var(--whitebase);
|
||||||
|
border-bottom: 1px solid var(--whitebase);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '\e079';
|
||||||
|
display: block;
|
||||||
|
font-family: 'Glyphicons Halflings';
|
||||||
|
color: var(--whitebase);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--lightbase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main__aside--collapsed .asidenav__toggler::before {
|
||||||
|
content: '\e080';
|
||||||
|
}
|
||||||
7
templates/widgets/breadcrumbs.hamlet
Normal file
7
templates/widgets/breadcrumbs.hamlet
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<div .breadcrumbs__container>
|
||||||
|
<ul .breadcrumbs__list.list--inline>
|
||||||
|
$forall bc <- parents
|
||||||
|
<li .breadcrumbs__item>
|
||||||
|
<a .breadcrumbs__link href="@{fst bc}">#{snd bc}
|
||||||
|
>
|
||||||
|
<li .breadcrumbs__item--active>#{title}
|
||||||
14
templates/widgets/breadcrumbs.lucius
Normal file
14
templates/widgets/breadcrumbs.lucius
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.breadcrumbs__container {
|
||||||
|
position: relative;
|
||||||
|
color: white;
|
||||||
|
z-index: 10;
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: margin-bottom .2s ease;
|
||||||
|
}
|
||||||
|
.breadcrumbs__container--animated {
|
||||||
|
transition: left .2s ease;
|
||||||
|
}
|
||||||
|
.breadcrumbs__container .breadcrumbs__link {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
9
templates/widgets/form.hamlet
Normal file
9
templates/widgets/form.hamlet
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
$newline never
|
||||||
|
#{fragment}
|
||||||
|
$case formLayout
|
||||||
|
$of FormStandard
|
||||||
|
$forall view <- views
|
||||||
|
<div .form-group :fvRequired view:.form-group--required :not $ fvRequired view:.form-group--optional :isJust $ fvErrors view:.form-group--has-error>
|
||||||
|
$if not (Blaze.null $ fvLabel view)
|
||||||
|
<label .form-group__label .reactive-label for=#{fvId view}>#{fvLabel view}
|
||||||
|
^{fvInput view}
|
||||||
51
templates/widgets/form.julius
Normal file
51
templates/widgets/form.julius
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.utils = window.utils || {};
|
||||||
|
|
||||||
|
// registers input-listener for each element in <elements> (array) and
|
||||||
|
// enables <button> if <fn> for these elements returns true
|
||||||
|
window.utils.reactiveButton = function(elements, button, fn) {
|
||||||
|
if (elements.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var checkboxes = elements[0].getAttribute('type') === 'checkbox';
|
||||||
|
var eventType = checkboxes ? 'change' : 'input';
|
||||||
|
updateButtonState();
|
||||||
|
elements.forEach(function(el) {
|
||||||
|
el.addEventListener(eventType, function() {
|
||||||
|
updateButtonState();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateButtonState() {
|
||||||
|
if (fn.call(null, elements)) {
|
||||||
|
button.removeAttribute('disabled');
|
||||||
|
} else {
|
||||||
|
button.setAttribute('disabled', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
// auto reactiveButton submit-buttons with required fields
|
||||||
|
var forms = document.querySelectorAll('form');
|
||||||
|
Array.from(forms).forEach(function(form) {
|
||||||
|
var requireds = form.querySelectorAll('[required]');
|
||||||
|
var submitBtn = form.querySelector('[type=submit]');
|
||||||
|
if (submitBtn && requireds) {
|
||||||
|
window.utils.reactiveButton(Array.from(requireds), submitBtn, function(inputs) {
|
||||||
|
var done = true;
|
||||||
|
inputs.forEach(function(inp) {
|
||||||
|
var len = inp.value.trim().length;
|
||||||
|
if (done && len === 0) {
|
||||||
|
done = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return done;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
0
templates/widgets/form.lucius
Normal file
0
templates/widgets/form.lucius
Normal file
10
templates/widgets/lipsum.hamlet
Normal file
10
templates/widgets/lipsum.hamlet
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<div>
|
||||||
|
<h2>A little lorem ipsum to make the page scrollable
|
||||||
|
Aute velit consectetur consequat excepteur ut in qui do reprehenderit sit consequat occaecat incididunt sint eu. Nulla cillum quis et sit labore aliquip et ad excepteur duis elit deserunt aute. Anim esse reprehenderit et ipsum et sit quis minim enim sint et pariatur duis nostrud. Do minim dolore adipisicing ullamco ad aute veniam id magna est proident tempor labore non. Mollit et laboris duis esse commodo ex tempor.
|
||||||
|
Elit labore qui dolor ea non ut anim occaecat do aliqua cillum qui esse. Aliqua nisi non veniam labore esse irure velit qui veniam labore consectetur ut. Tempor laboris anim officia veniam nostrud cupidatat reprehenderit aute incididunt nostrud dolore occaecat deserunt pariatur in non dolore.
|
||||||
|
Proident anim pariatur eu laborum Lorem duis cillum est anim magna sit pariatur eu. Nulla consectetur quis ea sunt proident sint laborum. Esse est ea Lorem mollit aute excepteur fugiat ipsum ipsum ad irure do reprehenderit voluptate. Sint tempor nulla mollit voluptate pariatur aliquip culpa amet ullamco fugiat incididunt minim sint ipsum exercitation eiusmod ad. Nostrud ipsum nulla cillum ex aute elit voluptate proident proident magna ut. Ipsum officia cillum officia nostrud enim fugiat ut. Voluptate laboris aute dolore incididunt aliquip sit sit. Ea ut laboris Lorem ea ad non nostrud aute tempor non nisi.
|
||||||
|
Qui cupidatat nisi id et excepteur sint aliquip fugiat sint reprehenderit aliquip enim anim aliqua sint dolore proident. Voluptate labore tempor laboris nisi eiusmod sunt occaecat deserunt adipisicing. Consectetur fugiat quis non ad laboris aliqua voluptate in eu id. Duis velit cillum aliquip dolor non ea mollit incididunt elit ex excepteur aute consequat amet.
|
||||||
|
Aute velit consectetur consequat excepteur ut in qui do reprehenderit sit consequat occaecat incididunt sint eu. Nulla cillum quis et sit labore aliquip et ad excepteur duis elit deserunt aute. Anim esse reprehenderit et ipsum et sit quis minim enim sint et pariatur duis nostrud. Do minim dolore adipisicing ullamco ad aute veniam id magna est proident tempor labore non. Mollit et laboris duis esse commodo ex tempor.
|
||||||
|
Elit labore qui dolor ea non ut anim occaecat do aliqua cillum qui esse. Aliqua nisi non veniam labore esse irure velit qui veniam labore consectetur ut. Tempor laboris anim officia veniam nostrud cupidatat reprehenderit aute incididunt nostrud dolore occaecat deserunt pariatur in non dolore.
|
||||||
|
Proident anim pariatur eu laborum Lorem duis cillum est anim magna sit pariatur eu. Nulla consectetur quis ea sunt proident sint laborum. Esse est ea Lorem mollit aute excepteur fugiat ipsum ipsum ad irure do reprehenderit voluptate. Sint tempor nulla mollit voluptate pariatur aliquip culpa amet ullamco fugiat incididunt minim sint ipsum exercitation eiusmod ad. Nostrud ipsum nulla cillum ex aute elit voluptate proident proident magna ut. Ipsum officia cillum officia nostrud enim fugiat ut. Voluptate laboris aute dolore incididunt aliquip sit sit. Ea ut laboris Lorem ea ad non nostrud aute tempor non nisi.
|
||||||
|
Qui cupidatat nisi id et excepteur sint aliquip fugiat sint reprehenderit aliquip enim anim aliqua sint dolore proident. Voluptate labore tempor laboris nisi eiusmod sunt occaecat deserunt adipisicing. Consectetur fugiat quis non ad laboris aliqua voluptate in eu id. Duis velit cillum aliquip dolor non ea mollit incididunt elit ex excepteur aute consequat amet.
|
||||||
25
templates/widgets/navbar.hamlet
Normal file
25
templates/widgets/navbar.hamlet
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<div .navbar-container>
|
||||||
|
<nav .navbar.js-sticky-navbar>
|
||||||
|
|
||||||
|
<!-- breadcrumbs -->
|
||||||
|
$if not $ Just HomeR == mcurrentRoute
|
||||||
|
^{breadcrumbs}
|
||||||
|
|
||||||
|
<ul .navbar__list.list--inline>
|
||||||
|
$forall menuType <- menuTypes
|
||||||
|
$case menuType
|
||||||
|
$of NavbarRight (MenuItem label mIcon route _)
|
||||||
|
<li .navbar__list-item :Just route == mcurrentRoute:.navbar__list-item--active>
|
||||||
|
<a .navbar__link-wrapper href=@{route}>
|
||||||
|
$if isJust mIcon
|
||||||
|
<div .glyphicon.glyphicon--#{fromMaybe "" mIcon}>
|
||||||
|
<div .navbar__link-label>#{label}
|
||||||
|
$of NavbarSecondary (MenuItem label mIcon route _)
|
||||||
|
<li .navbar__list-item.navbar__list-item--secondary :Just route == mcurrentRoute:.navbar__list-item--active>
|
||||||
|
<a .navbar__link-wrapper href=@{route}>
|
||||||
|
$if isJust mIcon
|
||||||
|
<div .glyphicon.glyphicon--#{fromMaybe "" mIcon}>
|
||||||
|
<div .navbar__link-label>#{label}
|
||||||
|
$of _
|
||||||
|
|
||||||
|
<div .navbar__pushdown>
|
||||||
28
templates/widgets/navbar.julius
Normal file
28
templates/widgets/navbar.julius
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* .js-sticky-navbar
|
||||||
|
* ul
|
||||||
|
* li Item 1
|
||||||
|
* li Item 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
var ticking = false;
|
||||||
|
var nav = document.querySelector('.js-sticky-navbar');
|
||||||
|
|
||||||
|
window.addEventListener('scroll', function(e) {
|
||||||
|
if (!ticking) {
|
||||||
|
window.requestAnimationFrame(checkScroll);
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// checks scroll direction and shows/hides navbar accordingly
|
||||||
|
function checkScroll() {
|
||||||
|
var sticky = window.scrollY > 0;
|
||||||
|
nav.classList.toggle('navbar--sticky', sticky);
|
||||||
|
ticking = false;
|
||||||
|
}
|
||||||
|
checkScroll();
|
||||||
|
|
||||||
|
});
|
||||||
127
templates/widgets/navbar.lucius
Normal file
127
templates/widgets/navbar.lucius
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
.navbar {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: var(--header-height);
|
||||||
|
padding-right: 5vw;
|
||||||
|
padding-left: 340px;
|
||||||
|
background: var(--darkbase); /* Old browsers */
|
||||||
|
background: -moz-linear-gradient(bottom, var(--darkbase) 0%, #425d79 100%); /* FF3.6-15 */
|
||||||
|
background: -webkit-linear-gradient(bottom, var(--darkbase) 0%,#425d79 100%); /* Chrome10-25,Safari5.1-6 */
|
||||||
|
background: linear-gradient(to top, var(--darkbase) 0%,#425d79 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__list {
|
||||||
|
align-self: flex-end;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__list-item {
|
||||||
|
position: relative;
|
||||||
|
transition: background-color .1s ease;
|
||||||
|
|
||||||
|
.glyphicon {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glyphicon::before {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navbar :last-child {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .navbar__link-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 80px;
|
||||||
|
color: var(--whitebase);
|
||||||
|
transition: height .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__link-label {
|
||||||
|
transition: opacity .2s ease;
|
||||||
|
padding: 0 13px;
|
||||||
|
color: white;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__list-item--secondary {
|
||||||
|
margin-left: 20px;
|
||||||
|
color: var(--greybase);
|
||||||
|
}
|
||||||
|
.navbar__list-item--secondary + .navbar__list-item--secondary {
|
||||||
|
margin-left: 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__list-item--active {
|
||||||
|
background-color: white;
|
||||||
|
color: var(--darkbase);
|
||||||
|
|
||||||
|
.navbar__link-wrapper {
|
||||||
|
color: var(--darkbase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navbar__list-item--active .navbar__link-wrapper {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.navbar__list-item--active .navbar__link-label {
|
||||||
|
color: var(--darkbase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .navbar__list-item:not(.navbar__list-item--active):hover {
|
||||||
|
background-color: var(--darkbase);
|
||||||
|
color: var(--whitebase);
|
||||||
|
}
|
||||||
|
.navbar .navbar__list-item:not(.navbar__list-item--active):hover .navbar__link-wrapper {
|
||||||
|
color: var(--whitebase);
|
||||||
|
}
|
||||||
|
.navbar .navbar__list-item:not(.navbar__list-item--active):hover .navbar__link-label {
|
||||||
|
color: var(--whitebase);
|
||||||
|
}
|
||||||
|
.navbar__list-item--secondary .navbar__link-wrapper,
|
||||||
|
.navbar__list-item--secondary .navbar__link-label {
|
||||||
|
color: var(--greybase);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar--sticky {
|
||||||
|
height: var(--header-height-collapsed);
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
.navbar__link-wrapper {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs__container {
|
||||||
|
margin-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.navbar--animated {
|
||||||
|
transition: all .2s ease;
|
||||||
|
}
|
||||||
|
.navbar__pushdown {
|
||||||
|
/*display: none;*/
|
||||||
|
height: var(--header-height);
|
||||||
|
transition: height .2s ease;
|
||||||
|
}
|
||||||
|
.navbar--sticky + .navbar__pushdown {
|
||||||
|
display: block;
|
||||||
|
height: var(--header-height-collapsed);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user