Merge remote-tracking branch 'origin/master' into feat/exercises

This commit is contained in:
SJost 2018-04-19 09:29:41 +02:00
commit 8725f935d0
20 changed files with 233 additions and 133 deletions

View File

@ -1,7 +1,7 @@
SummerTerm year@Integer: Sommersemester #{tshow year}
WinterTerm year@Integer: Wintersemester #{tshow year}/#{tshow $ succ year}
PSLimitNonPositive: “pagesize” muss größer als null sein
Page n@Int64 num@Int64: Seite #{tshow n} von #{tshow num}
Page n@Int64: #{tshow n}
TermEdited tid@TermIdentifier: Semester #{termToText tid} erfolgreich editiert.
TermNewTitle: Semester editiere/anlegen.
InvalidInput: Eingaben bitte korrigieren.

View File

@ -96,9 +96,14 @@ data MenuItem = MenuItem
{ menuItemLabel :: Text
, menuItemIcon :: Maybe Text
, menuItemRoute :: Route UniWorX
, menuItemAccessCallback :: Handler Bool
, menuItemAccessCallback' :: Handler Bool
}
menuItemAccessCallback :: MenuItem -> Handler Bool
menuItemAccessCallback MenuItem{..} = (&&) <$> ((==) Authorized <$> authCheck) <*> menuItemAccessCallback'
where
authCheck = handleAny (\_ -> return . Unauthorized $ error "authCheck caught exception") $ isAuthorized menuItemRoute False
data MenuTypes -- Semantische Rolle:
= NavbarAside { menuItem :: MenuItem } -- TODO
| NavbarExtra { menuItem :: MenuItem } -- TODO
@ -357,49 +362,49 @@ defaultLinks = -- Define the menu items of the header.
{ menuItemLabel = "Home"
, menuItemIcon = Just "home"
, menuItemRoute = HomeR
, menuItemAccessCallback = return True
, menuItemAccessCallback' = return True
}
, NavbarRight $ MenuItem
{ menuItemLabel = "Profile"
, menuItemIcon = Just "profile"
, menuItemRoute = ProfileR
, menuItemAccessCallback = isJust <$> maybeAuthPair
, menuItemAccessCallback' = isJust <$> maybeAuthPair
}
, NavbarSecondary $ MenuItem
{ menuItemLabel = "Login"
, menuItemIcon = Just "login"
, menuItemRoute = AuthR LoginR
, menuItemAccessCallback = isNothing <$> maybeAuthPair
, menuItemAccessCallback' = isNothing <$> maybeAuthPair
}
, NavbarSecondary $ MenuItem
{ menuItemLabel = "Logout"
, menuItemIcon = Just "logout"
, 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
, menuItemAccessCallback' = return True
}
, NavbarAside $ MenuItem
{ menuItemLabel = "Alte Veranstaltungen"
, menuItemIcon = Just "book"
, menuItemRoute = CourseListR -- should be CourseListInactiveR or similar in the future
, menuItemAccessCallback = return True
, menuItemAccessCallback' = return True
}
, NavbarAside $ MenuItem
{ menuItemLabel = "Veranstaltungen"
, menuItemIcon = Just "book"
, menuItemRoute = CourseListR
, menuItemAccessCallback = return True
, menuItemAccessCallback' = return True
}
, NavbarAside $ MenuItem
{ menuItemLabel = "Benutzer"
, menuItemIcon = Just "user"
, menuItemRoute = UsersR
, menuItemAccessCallback = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False
, menuItemAccessCallback' = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False
}
]

View File

@ -68,7 +68,7 @@ getCourseListTermR tidini = do
{ menuItemLabel = "Neuer Kurs"
, menuItemIcon = Just "book"
, menuItemRoute = CourseNewR
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseNewR False
, menuItemAccessCallback' = return True
}
]
let coursesTable = encodeWidgetTable tableSortable colonnadeTerms courses
@ -97,7 +97,7 @@ getCourseShowR tid csh = do
{ menuItemLabel = "Übungsblätter"
, menuItemIcon = Nothing
, menuItemRoute = CSheetR tid csh SheetListR
, menuItemAccessCallback = (== Authorized) <$> isAuthorized (CSheetR tid csh SheetListR) False
, menuItemAccessCallback' = return True
}
]
defaultLinkLayout pageActions $ do

View File

@ -168,7 +168,7 @@ getSheetList courseEnt = do
{ menuItemLabel = "Neues Übungsblatt"
, menuItemIcon = Nothing
, menuItemRoute = CSheetR tid csh SheetNewR
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseNewR False
, menuItemAccessCallback' = return True
}
]
defaultLinkLayout pageActions $ do

View File

@ -94,7 +94,7 @@ getTermShowR = do
{ menuItemLabel = "Neues Semester"
, menuItemIcon = Nothing
, menuItemRoute = TermEditR
, menuItemAccessCallback = (== Authorized) <$> isAuthorized TermEditR True
, menuItemAccessCallback' = return True
}
]
defaultLinkLayout pageActions $ do

View File

@ -152,11 +152,18 @@ dbTable PSValidator{..} DBTable{ dbtIdent = (toPathPiece -> dbtIdent), .. } = do
getParams <- handlerToWidget $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest
let table = $(widgetFile "table/colonnade")
pageCount = max 1 . ceiling $ rowCount % psLimit
pageNumbers = [0..pred pageCount]
tblLink f = decodeUtf8 . Builder.toLazyByteString . renderQueryText True $ f getParams
withSortLinks Sortable{ sortableContent = Cell{..}, .. } = Cell
{ cellContents = $(widgetFile "table/sortable-header")
, ..
, cellAttrs = maybe mempty (const sortableAttr) sortableKey <> cellAttrs
}
where
directions = [dir | (k, dir) <- psSorting, Just k == sortableKey ]
sortableAttr = Html5.class_ . fromString . unwords $ "sortable" : foldMap toAttr directions
toAttr SortAsc = ["sorted-asc"]
toAttr SortDesc = ["sorted-desc"]
$(widgetFile "table/layout")
where
tblLayout :: Widget -> Handler Html

View File

@ -20,7 +20,8 @@
--fontbase: #34303a;
--fontsec: #5b5861;
/* THEME 4 */
--darkbase: #263C4C;
--darkerbase: #274a65;
--darkbase: #425d79;
--lightbase: #598EB5;
--lighterbase: #5F98C2;
--whitebase: #FCFFFA;

View File

@ -70,7 +70,7 @@
justify-content: center;
width: 30px;
height: 30px;
background-color: var(--darkbase);
background-color: var(--darkerbase);
border-radius: 2px;
cursor: pointer;
z-index: 20;

View File

@ -1,61 +0,0 @@
(function collonadeClosure() {
'use strict';
document.addEventListener('DOMContentLoaded', function DOMContentLoaded() {
var ASC = 'asc';
var DESC = 'desc';
// TODO: Make use of interpolated dbtIdent
var table = document.querySelector('table');
var ths = Array.from(table.querySelectorAll('th'));
// attach click handler to each table-header
ths.map(function(th) {
var link = th.querySelector('a');
if (link) {
link.addEventListener('click', clickHandler);
}
});
// handles click on table header
function clickHandler(event) {
event.preventDefault();
var url = new URL(window.location.origin + window.location.pathname + this.getAttribute('href'));
var order = this.parentNode.dataset.order || ASC;
// TODO: not working here... getting whole page as response...
url.searchParams.set('table-only', 'true');
updateTableFrom(url);
markSorted(this.parentNode, order);
}
function markSorted(th, order) {
ths.forEach(function(th) {
th.classList.remove('sorted-asc', 'sorted-desc');
});
th.classList.add('sorted-' + order);
th.dataset.order = order;
}
// fetches new sorted table from url with params and replaces contents of current table
function updateTableFrom(url) {
fetch(url, {
headers: {
'Accept': 'text/html'
}
}).then(function(response) {
var contentType = response.headers.get("content-type");
if (!response.ok) {
throw ('Looks like there was a problem fetching ' + url.toString() + '. Status Code: ' + response.status);
}
return response.text();
}).then(function(data) {
// replace contents of table body
table.querySelector('tbody').innerHTML = data;
}).catch(function(err) {
console.error(err);
});
}
});
})();

View File

@ -1,7 +1,14 @@
table th {
cursor: pointer;
position: relative;
padding-right: 20px;
&.sortable {
cursor: pointer;
}
a {
font-weight: 800;
}
}
table th.sorted-asc,
@ -9,12 +16,11 @@ table th.sorted-desc {
color: var(--lightbase);
}
table th.sorted-asc::after,
table th.sorted-desc::after {
table th.sortable::after,
table th.sortable::before {
content: '';
position: absolute;
right: 0;
top: 15px;
width: 0;
height: 0;
transform: translateY(-100%);
@ -22,7 +28,15 @@ table th.sorted-desc::after {
border-right: 8px solid transparent;
}
table th.sorted-asc::after {
table th.sortable::before {
top: 21px;
border-top: 8px solid rgba(0, 0, 0, 0.1);
}
table th.sortable::after {
top: 9px;
border-bottom: 8px solid rgba(0, 0, 0, 0.1);
}
table th.sorted-asc::before {
border-top: 8px solid var(--lightbase);
}

View File

@ -1,6 +1,10 @@
<div .table>
^{table}
$newline never
<div ##{dbtIdent}-table-wrapper>
<div .scrolltable>
^{table}
$if pageCount > 1
<p style="text-align:center">
$# TODO: foreach (reachable pages) print link to that page
_{MsgPage (succ psPage) pageCount}
<ul ##{dbtIdent}-pagination .pagination>
$forall p <- pageNumbers
<li .pagination-link :p == psPage:.current>
<a href=#{tblLink $ setParam (wIdent "page") (Just $ tshow p)}>
_{MsgPage (succ p)}

View File

@ -0,0 +1,70 @@
(function collonadeClosure() {
'use strict';
document.addEventListener('DOMContentLoaded', function DOMContentLoaded() {
var ASC = 'asc';
var DESC = 'desc';
function setupAsync(wrapper) {
var table = wrapper.querySelector('#' + #{String $ dbtIdent});
var ths = Array.from(table.querySelectorAll('th.sortable'));
if (ths) {
// attach click handler to each sortable column if any
ths.forEach(function(th) {
th.addEventListener('click', clickHandler);
});
}
var pagination = wrapper.querySelector('#' + #{String $ dbtIdent} + '-pagination');
if (pagination) {
var paginationLinks = Array.from(pagination.querySelectorAll('.pagination-link'));
// attach click handler to pagination links if any
paginationLinks.forEach(function(p) {
p.addEventListener('click', clickHandler);
});
}
function clickHandler(event) {
event.preventDefault();
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
url.searchParams.set(#{String $ wIdent "table-only"}, 'yes');
updateTableFrom(url);
}
function getClickDestination(el) {
var link = el.querySelector('a');
if (!link) { return false; }
return link.getAttribute('href');
}
// fetches new sorted table from url with params and replaces contents of current table
function updateTableFrom(url) {
fetch(url, {
credentials: 'same-origin',
headers: {
'Accept': 'text/html'
}
}).then(function(response) {
var contentType = response.headers.get("content-type");
if (!response.ok) {
throw ('Looks like there was a problem fetching ' + url.toString() + '. Status Code: ' + response.status);
}
return response.text();
}).then(function(data) {
// replace contents of table body
wrapper.innerHTML = data;
// set up async functionality again
setupAsync(wrapper);
table.querySelector('tbody').innerHTML = data;
}).catch(function(err) {
console.error(err);
});
}
}
var selector = '#' + #{String $ dbtIdent} + '-table-wrapper';
setupAsync(document.querySelector(selector));
});
})();

View File

@ -0,0 +1,38 @@
.pagination {
margin-top: 20px;
text-align: center;
.pagination-link {
margin: 0 7px;
display: inline-block;
background-color: var(--greybase);
a {
color: var(--whitebase);
padding: 7px 13px;
display: inline-block;
}
&:not(.current):hover {
background-color: var(--lighterbase);
a {
color: var(--whitebase);
}
}
&.current {
pointer-events: none;
background-color: var(--lightbase);
a {
text-decoration: underline;
pointer-events: none;
}
}
&:last-child {
margin-right: 0;
}
}
}

View File

@ -1,7 +1,10 @@
^{cellContents}
$maybe flag <- sortableKey
<br>
<a href=#{tblLink $ setParam (wIdent "sorting") (Just $ flag <> "-asc")}>asc
/
<a href=#{tblLink $ setParam (wIdent "sorting") (Just $ flag <> "-desc")}>desc
$case directions
$of [SortAsc]
<a href=#{tblLink $ setParam (wIdent "sorting") (Just $ flag <> "-desc")}>
^{cellContents}
$of _
<a href=#{tblLink $ setParam (wIdent "sorting") (Just $ flag <> "-asc")}>
^{cellContents}
$nothing
^{cellContents}

View File

@ -1,5 +1,4 @@
<div .container>
<h1>Semesterübersicht
<div .scrolltable>
^{table}
^{table}

View File

@ -18,10 +18,11 @@ $newline never
WiSe 17/18
<ul .asidenav__list>
$forall (Entity _ Course{..}) <- favourites
<li .asidenav__list-item>
<a .asidenav__link-wrapper href=@{CourseR courseTermId courseShorthand CourseShowR}>
<div .asidenav__link-shorthand>#{courseShorthand}
<div .asidenav__link-label>#{courseName}
$with route <- CourseR courseTermId courseShorthand CourseShowR
<li .asidenav__list-item :Just route == mcurrentRoute:.asidenav__list-item--active>
<a .asidenav__link-wrapper href=@{route}>
<div .asidenav__link-shorthand>#{courseShorthand}
<div .asidenav__link-label>#{courseName}
<li .asidenav__list-item>
<a .asidenav__link-wrapper href="/course/S2018/ixd/show">

View File

@ -1,6 +1,6 @@
.main__aside {
position: relative;
background-color: var(--darkbase);
background-color: #425d79;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 1;
flex: 0 0 300px;
@ -22,12 +22,33 @@
.asidenav__box-title {
width: 50px;
padding: 0;
padding: 1px;
font-size: 18px;
text-align: center;
margin-bottom: 0;
background-color: var(--fontsec);
}
.asidenav__link-wrapper {
.asidenav__link-shorthand {
display: flex;
position: static;
background-color: var(--darkbase);
color: var(--whitebase);
height: 50px;
width: 50px;
text-align: center;
opacity: 1;
font-size: 15px;
line-height: 1em;
margin-right: 13px;
flex-shrink: 0;
padding: 1px;
outline: 1px solid white;
text-transform: uppercase;
word-break: break-all;
align-items: center;
justify-content: center;
}
.asidenav__link-label {
padding-left: 0;
@ -62,6 +83,9 @@
.asidenav__box-title {
padding: 7px 13px;
margin-top: 13px;
background-color: transparent;
transition: all .2s ease;
a {
color: white;
@ -76,7 +100,9 @@
color: var(--fontbase);
transform: translateX(0);
opacity: 0;
transition: all .2s ease;
transition: all .2s ease-out;
width: 0;
overflow: hidden;
z-index: -1;
.asidenav__list-item {
@ -115,6 +141,7 @@
.asidenav__nested-list {
transform: translateX(100%);
opacity: 1;
width: 200px;
}
.asidenav__link-wrapper,
@ -123,8 +150,8 @@
}
.asidenav__link-shorthand {
background-color: var(--whitebase);
color: var(--darkbase);
transform: scale(1.05, 1.0);
transform-origin: right;
}
}
}
@ -136,6 +163,11 @@
pointer-events: none;
color: white;
}
.asidenav__link-shorthand {
transform: scale(1.05, 1.0);
transform-origin: right;
}
}
.asidenav__link-wrapper {
@ -152,19 +184,16 @@
}
.asidenav__link-shorthand {
background-color: var(--darkbase);
color: var(--whitebase);
height: 50px;
width: 50px;
display: none;
text-align: center;
margin-right: 13px;
flex-shrink: 0;
outline: 1px solid white;
display: block;
position: absolute;
color: var(--greybase);
line-height: 50px;
opacity: 0.3;
right: 10px;
top: 0;
font-size: 40px;
text-transform: uppercase;
word-break: break-all;
align-items: center;
justify-content: center;
transition: transform .2s ease;
}
.asidenav__link-label {

View File

@ -7,11 +7,11 @@
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+ */
padding-left: 90px;
background: var(--darkerbase); /* Old browsers */
background: -moz-linear-gradient(bottom, var(--darkerbase) 0%, #425d79 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(bottom, var(--darkerbase) 0%,#425d79 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to top, var(--darkerbase) 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;
@ -86,8 +86,7 @@
}
.navbar .navbar__list-item:not(.navbar__list-item--active):hover {
background-color: var(--darkbase);
color: var(--whitebase);
background-color: var(--darkerbase);
}
.navbar .navbar__list-item:not(.navbar__list-item--active):hover .navbar__link-wrapper {
color: var(--whitebase);

View File

@ -1,7 +1,6 @@
$newline never
$if hasPageActions
<div .page-nav-prime>
<h3>Aktionen:
<ul .pagenav__list>
$forall menuType <- menuTypes
$case menuType

View File

@ -1,8 +1,6 @@
.page-nav-prime {
background-color: var(--lightgreybase);
box-shadow: -20px -20px 0 20px var(--lightgreybase),
20px -20px 0 20px var(--lightgreybase);
padding: 13px 0;
padding: 13px;
}
.page-nav-prime .pagenav__list {
@ -11,11 +9,5 @@
}
.page-nav-prime .pagenav__list-item {
display: inline-block;
border-bottom: 2px solid var(--lightbase);
margin-right: 7px;
transition: border-bottom-color .2s ease;
&:hover {
border-bottom-color: var(--lighterbase);
}
}