feat(course-communication): one recipient group per tutorial
Fixes #428
This commit is contained in:
parent
4fb6762b89
commit
99f23f2558
@ -1445,7 +1445,7 @@ RGCourseParticipants: Kursteilnehmer
|
|||||||
RGCourseLecturers: Kursverwalter
|
RGCourseLecturers: Kursverwalter
|
||||||
RGCourseCorrectors: Korrektoren
|
RGCourseCorrectors: Korrektoren
|
||||||
RGCourseTutors: Tutoren
|
RGCourseTutors: Tutoren
|
||||||
RGTutorialParticipants: Tutorium-Teilnehmer
|
RGTutorialParticipants tutn@TutorialName: Tutorium-Teilnehmer (#{tutn})
|
||||||
|
|
||||||
MultiSelectFieldTip: Mehrfach-Auswahl ist möglich (Umschalt bzw. Strg)
|
MultiSelectFieldTip: Mehrfach-Auswahl ist möglich (Umschalt bzw. Strg)
|
||||||
MultiEmailFieldTip: Es sind mehrere, Komma-separierte, E-Mail-Addressen möglich
|
MultiEmailFieldTip: Es sind mehrere, Komma-separierte, E-Mail-Addressen möglich
|
||||||
|
|||||||
@ -1445,7 +1445,7 @@ RGCourseParticipants: Course participants
|
|||||||
RGCourseLecturers: Course administrators
|
RGCourseLecturers: Course administrators
|
||||||
RGCourseCorrectors: Course correctors
|
RGCourseCorrectors: Course correctors
|
||||||
RGCourseTutors: Course tutors
|
RGCourseTutors: Course tutors
|
||||||
RGTutorialParticipants: Tutorial participants
|
RGTutorialParticipants tutn: Tutorial participants (#{tutn})
|
||||||
|
|
||||||
MultiSelectFieldTip: Multiple selections are possible (Shift or Ctrl)
|
MultiSelectFieldTip: Multiple selections are possible (Shift or Ctrl)
|
||||||
MultiEmailFieldTip: Multiple emails addresses may be specified (comma-separated)
|
MultiEmailFieldTip: Multiple emails addresses may be specified (comma-separated)
|
||||||
|
|||||||
@ -16,21 +16,28 @@ import qualified Data.Map as Map
|
|||||||
getTCommR, postTCommR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> Handler Html
|
getTCommR, postTCommR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> Handler Html
|
||||||
getTCommR = postTCommR
|
getTCommR = postTCommR
|
||||||
postTCommR tid ssh csh tutn = do
|
postTCommR tid ssh csh tutn = do
|
||||||
(cid, tutid) <- runDB $ fetchCourseIdTutorialId tid ssh csh tutn
|
((cid, tutid), usertuts) <- runDB $ do
|
||||||
|
tutData@(cid, _) <- fetchCourseIdTutorialId tid ssh csh tutn
|
||||||
|
tuts <- selectList [TutorialCourse ==. cid] []
|
||||||
|
usertuts <- forMaybeM tuts $ \(Entity tutid Tutorial{..}) -> do
|
||||||
|
cID <- encrypt tutid
|
||||||
|
guardM . hasReadAccessTo $ CTutorialR tid ssh csh tutorialName TUsersR
|
||||||
|
return ( RGTutorialParticipants cID
|
||||||
|
, E.from $ \(user `E.InnerJoin` participant) -> do
|
||||||
|
E.on $ user E.^. UserId E.==. participant E.^. TutorialParticipantUser
|
||||||
|
E.where_ $ participant E.^. TutorialParticipantTutorial E.==. E.val tutid
|
||||||
|
return user
|
||||||
|
)
|
||||||
|
return (tutData, usertuts)
|
||||||
|
|
||||||
|
|
||||||
commR CommunicationRoute
|
commR CommunicationRoute
|
||||||
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommTutorialHeading
|
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommTutorialHeading
|
||||||
, crUltDest = SomeRoute $ CTutorialR tid ssh csh tutn TCommR
|
, crUltDest = SomeRoute $ CTutorialR tid ssh csh tutn TCommR
|
||||||
, crJobs = crJobsCourseCommunication cid
|
, crJobs = crJobsCourseCommunication cid
|
||||||
, crTestJobs = crTestJobsCourseCommunication cid
|
, crTestJobs = crTestJobsCourseCommunication cid
|
||||||
, crRecipients = Map.fromList
|
, crRecipients = Map.fromList $
|
||||||
[ ( RGTutorialParticipants
|
[ ( RGCourseLecturers
|
||||||
, E.from $ \(user `E.InnerJoin` participant) -> do
|
|
||||||
E.on $ user E.^. UserId E.==. participant E.^. TutorialParticipantUser
|
|
||||||
E.where_ $ participant E.^. TutorialParticipantTutorial E.==. E.val tutid
|
|
||||||
return user
|
|
||||||
)
|
|
||||||
, ( RGCourseLecturers
|
|
||||||
, E.from $ \(user `E.InnerJoin` lecturer) -> do
|
, E.from $ \(user `E.InnerJoin` lecturer) -> do
|
||||||
E.on $ user E.^. UserId E.==. lecturer E.^. LecturerUser
|
E.on $ user E.^. UserId E.==. lecturer E.^. LecturerUser
|
||||||
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid
|
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid
|
||||||
@ -52,7 +59,7 @@ postTCommR tid ssh csh tutn = do
|
|||||||
E.&&. tutor E.^. TutorUser E.==. user E.^. UserId
|
E.&&. tutor E.^. TutorUser E.==. user E.^. UserId
|
||||||
return user
|
return user
|
||||||
)
|
)
|
||||||
]
|
] ++ usertuts
|
||||||
, crRecipientAuth = Just $ \uid -> do
|
, crRecipientAuth = Just $ \uid -> do
|
||||||
isTutorialUser <- E.selectExists . E.from $ \tutorialUser ->
|
isTutorialUser <- E.selectExists . E.from $ \tutorialUser ->
|
||||||
E.where_ $ tutorialUser E.^. TutorialParticipantUser E.==. E.val uid
|
E.where_ $ tutorialUser E.^. TutorialParticipantUser E.==. E.val uid
|
||||||
|
|||||||
@ -23,17 +23,14 @@ import qualified Data.Conduit.Combinators as C
|
|||||||
|
|
||||||
|
|
||||||
data RecipientGroup = RGCourseParticipants | RGCourseLecturers | RGCourseCorrectors | RGCourseTutors
|
data RecipientGroup = RGCourseParticipants | RGCourseLecturers | RGCourseCorrectors | RGCourseTutors
|
||||||
| RGTutorialParticipants
|
| RGTutorialParticipants CryptoUUIDTutorial
|
||||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
instance Universe RecipientGroup
|
instance LowerBounded RecipientGroup where
|
||||||
instance Finite RecipientGroup
|
minBound' = RGCourseParticipants
|
||||||
nullaryPathPiece ''RecipientGroup $ camelToPathPiece' 1
|
|
||||||
embedRenderMessage ''UniWorX ''RecipientGroup id
|
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
derivePathPiece ''RecipientGroup (camelToPathPiece' 1) "--"
|
||||||
{ constructorTagModifier = camelToPathPiece' 1
|
pathPieceJSON ''RecipientGroup
|
||||||
} ''RecipientGroup
|
|
||||||
|
|
||||||
|
|
||||||
data RecipientCategory
|
data RecipientCategory
|
||||||
@ -41,19 +38,8 @@ data RecipientCategory
|
|||||||
| RecipientCustom
|
| RecipientCustom
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
deriveFinite ''RecipientCategory
|
instance LowerBounded RecipientCategory where
|
||||||
finiteEnum ''RecipientCategory
|
minBound' = RecipientGroup minBound'
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
|
||||||
{ constructorTagModifier = camelToPathPiece' 1
|
|
||||||
, unwrapUnaryRecords = True
|
|
||||||
, sumEncoding = UntaggedValue
|
|
||||||
} ''RecipientCategory
|
|
||||||
|
|
||||||
instance ToJSONKey RecipientCategory where
|
|
||||||
toJSONKey = toJSONKeyText toPathPiece
|
|
||||||
instance FromJSONKey RecipientCategory where
|
|
||||||
fromJSONKey = FromJSONKeyTextParser $ maybe (fail "Could not pars RecipientCategory") return . fromPathPiece
|
|
||||||
|
|
||||||
instance PathPiece RecipientCategory where
|
instance PathPiece RecipientCategory where
|
||||||
toPathPiece RecipientCustom = "custom"
|
toPathPiece RecipientCustom = "custom"
|
||||||
@ -62,13 +48,8 @@ instance PathPiece RecipientCategory where
|
|||||||
fromPathPiece "custom" = Just RecipientCustom
|
fromPathPiece "custom" = Just RecipientCustom
|
||||||
fromPathPiece t = RecipientGroup <$> fromPathPiece t
|
fromPathPiece t = RecipientGroup <$> fromPathPiece t
|
||||||
|
|
||||||
instance RenderMessage UniWorX RecipientCategory where
|
pathPieceJSON ''RecipientCategory
|
||||||
renderMessage foundation ls = \case
|
pathPieceJSONKey ''RecipientCategory
|
||||||
RecipientCustom -> renderMessage' MsgRecipientCustom
|
|
||||||
RecipientGroup g -> renderMessage' g
|
|
||||||
where
|
|
||||||
renderMessage' :: forall msg. RenderMessage UniWorX msg => msg -> Text
|
|
||||||
renderMessage' = renderMessage foundation ls
|
|
||||||
|
|
||||||
data CommunicationButton
|
data CommunicationButton
|
||||||
= BtnCommunicationSend
|
= BtnCommunicationSend
|
||||||
@ -147,13 +128,13 @@ commR CommunicationRoute{..} = do
|
|||||||
= entityVal . unsafeHead . filter ((== lId) . entityKey) $ concat (Map.elems suggestedRecipients) ++ chosenRecipients
|
= entityVal . unsafeHead . filter ((== lId) . entityKey) $ concat (Map.elems suggestedRecipients) ++ chosenRecipients
|
||||||
|
|
||||||
let chosenRecipients' = Map.fromList $
|
let chosenRecipients' = Map.fromList $
|
||||||
[ ( (EnumPosition $ RecipientGroup g, pos)
|
[ ( (BoundedPosition $ RecipientGroup g, pos)
|
||||||
, (Right recp, recp `elem` map entityKey chosenRecipients)
|
, (Right recp, recp `elem` map entityKey chosenRecipients)
|
||||||
)
|
)
|
||||||
| (g, recps) <- Map.toList suggestedRecipients
|
| (g, recps) <- Map.toList suggestedRecipients
|
||||||
, (pos, recp) <- zip [0..] $ map entityKey recps
|
, (pos, recp) <- zip [0..] $ map entityKey recps
|
||||||
] ++
|
] ++
|
||||||
[ ( (EnumPosition RecipientCustom, pos)
|
[ ( (BoundedPosition RecipientCustom, pos)
|
||||||
, (Right recp, True)
|
, (Right recp, True)
|
||||||
)
|
)
|
||||||
| (pos, recp) <- zip [0..] . Set.toList $ Set.fromList (map entityKey chosenRecipients) \\ Set.fromList (concatMap (map entityKey) $ Map.elems suggestedRecipients)
|
| (pos, recp) <- zip [0..] . Set.toList $ Set.fromList (map entityKey chosenRecipients) \\ Set.fromList (concatMap (map entityKey) $ Map.elems suggestedRecipients)
|
||||||
@ -163,10 +144,10 @@ commR CommunicationRoute{..} = do
|
|||||||
let recipientAForm :: AForm Handler (Set (Either UserEmail UserId))
|
let recipientAForm :: AForm Handler (Set (Either UserEmail UserId))
|
||||||
recipientAForm = postProcess <$> massInputA MassInput{..} (fslI MsgCommRecipients & setTooltip MsgCommRecipientsTip) True (Just chosenRecipients')
|
recipientAForm = postProcess <$> massInputA MassInput{..} (fslI MsgCommRecipients & setTooltip MsgCommRecipientsTip) True (Just chosenRecipients')
|
||||||
where
|
where
|
||||||
miAdd (EnumPosition RecipientCustom, 0) 1 nudge submitView = Just $ \csrf -> do
|
miAdd (BoundedPosition RecipientCustom, 0) 1 nudge submitView = Just $ \csrf -> do
|
||||||
(addRes, addView) <- mpreq (multiUserField True Nothing) (fslpI MsgEMail (mr MsgEMail) & setTooltip MsgMultiEmailFieldTip & addName (nudge "email")) Nothing
|
(addRes, addView) <- mpreq (multiUserField True Nothing) (fslpI MsgEMail (mr MsgEMail) & setTooltip MsgMultiEmailFieldTip & addName (nudge "email")) Nothing
|
||||||
let
|
let
|
||||||
addRes' = addRes <&> \nEmails ((Map.elems &&& maybe 0 (succ . snd . fst) . Map.lookupMax) . Map.filterWithKey (\(EnumPosition c, _) _ -> c == RecipientCustom) -> (oEmails, kStart)) -> FormSuccess . Map.fromList . zip (map (EnumPosition RecipientCustom, ) [kStart..]) . Set.toList $ nEmails `Set.difference` Set.fromList oEmails
|
addRes' = addRes <&> \nEmails ((Map.elems &&& maybe 0 (succ . snd . fst) . Map.lookupMax) . Map.filterWithKey (\(BoundedPosition c, _) _ -> c == RecipientCustom) -> (oEmails, kStart)) -> FormSuccess . Map.fromList . zip (map (BoundedPosition RecipientCustom, ) [kStart..]) . Set.toList $ nEmails `Set.difference` Set.fromList oEmails
|
||||||
return (addRes', $(widgetFile "widgets/communication/recipientAdd"))
|
return (addRes', $(widgetFile "widgets/communication/recipientAdd"))
|
||||||
miAdd _ _ _ _ = Nothing
|
miAdd _ _ _ _ = Nothing
|
||||||
miCell _ (Left (CI.original -> email)) initRes nudge csrf = do
|
miCell _ (Left (CI.original -> email)) initRes nudge csrf = do
|
||||||
@ -179,31 +160,36 @@ commR CommunicationRoute{..} = do
|
|||||||
| otherwise
|
| otherwise
|
||||||
-> mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
-> mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
||||||
return (tickRes, $(widgetFile "widgets/communication/recipientName"))
|
return (tickRes, $(widgetFile "widgets/communication/recipientName"))
|
||||||
miAllowAdd (EnumPosition RecipientCustom, 0) 1 _ = True
|
miAllowAdd (BoundedPosition RecipientCustom, 0) 1 _ = True
|
||||||
miAllowAdd _ _ _ = False
|
miAllowAdd _ _ _ = False
|
||||||
miAddEmpty _ 0 _ = Set.singleton (EnumPosition RecipientCustom, 0)
|
miAddEmpty _ 0 _ = Set.singleton (BoundedPosition RecipientCustom, 0)
|
||||||
miAddEmpty _ _ _ = Set.empty
|
miAddEmpty _ _ _ = Set.empty
|
||||||
miButtonAction :: forall p . PathPiece p => p -> Maybe (SomeRoute UniWorX)
|
miButtonAction :: forall p . PathPiece p => p -> Maybe (SomeRoute UniWorX)
|
||||||
miButtonAction anchor = SomeRoute . (:#: anchor) <$> mbCurrentRoute
|
miButtonAction anchor = SomeRoute . (:#: anchor) <$> mbCurrentRoute
|
||||||
miLayout :: MapLiveliness (EnumLiveliness RecipientCategory) ListLength
|
miLayout :: MapLiveliness (BoundedLiveliness RecipientCategory) ListLength
|
||||||
-> Map (EnumPosition RecipientCategory, ListPosition) (_, FormResult Bool)
|
-> Map (BoundedPosition RecipientCategory, ListPosition) (_, FormResult Bool)
|
||||||
-> Map (EnumPosition RecipientCategory, ListPosition) Widget
|
-> Map (BoundedPosition RecipientCategory, ListPosition) Widget
|
||||||
-> Map (EnumPosition RecipientCategory, ListPosition) (FieldView UniWorX)
|
-> Map (BoundedPosition RecipientCategory, ListPosition) (FieldView UniWorX)
|
||||||
-> Map (Natural, (EnumPosition RecipientCategory, ListPosition)) Widget
|
-> Map (Natural, (BoundedPosition RecipientCategory, ListPosition)) Widget
|
||||||
-> Widget
|
-> Widget
|
||||||
miLayout liveliness cState cellWdgts _delButtons addWdgts = do
|
miLayout liveliness cState cellWdgts _delButtons addWdgts = do
|
||||||
checkedIdentBase <- newIdent
|
checkedIdentBase <- newIdent
|
||||||
let checkedCategories = Set.mapMonotonic (unEnumPosition . fst) . Set.filter (\k' -> Map.foldrWithKey (\k (_, checkState) -> (||) $ k == k' && checkState /= FormSuccess False && (checkState /= FormMissing || maybe True snd (chosenRecipients' !? k))) False cState) $ Map.keysSet cState
|
let checkedCategories = Set.mapMonotonic (unBoundedPosition . fst) . Set.filter (\k' -> Map.foldrWithKey (\k (_, checkState) -> (||) $ k == k' && checkState /= FormSuccess False && (checkState /= FormMissing || maybe True snd (chosenRecipients' !? k))) False cState) $ Map.keysSet cState
|
||||||
checkedIdent c = checkedIdentBase <> "-" <> toPathPiece c
|
checkedIdent c = checkedIdentBase <> "-" <> toPathPiece c
|
||||||
hasContent c = not (null $ categoryIndices c) || Map.member (1, (EnumPosition c, 0)) addWdgts
|
hasContent c = not (null $ categoryIndices c) || Map.member (1, (BoundedPosition c, 0)) addWdgts
|
||||||
categoryIndices c = Set.filter ((== c) . unEnumPosition . fst) $ review liveCoords liveliness
|
categoryIndices c = Set.filter ((== c) . unBoundedPosition . fst) $ review liveCoords liveliness
|
||||||
|
rgTutorialParticipantsCaption :: CryptoUUIDTutorial -> Widget
|
||||||
|
rgTutorialParticipantsCaption cID = do
|
||||||
|
tutId <- decrypt cID
|
||||||
|
Tutorial{..} <- liftHandler . runDBRead $ get404 tutId
|
||||||
|
i18n $ MsgRGTutorialParticipants tutorialName
|
||||||
$(widgetFile "widgets/communication/recipientLayout")
|
$(widgetFile "widgets/communication/recipientLayout")
|
||||||
miDelete :: Map (EnumPosition RecipientCategory, ListPosition) (Either UserEmail UserId) -> (EnumPosition RecipientCategory, ListPosition) -> MaybeT (MForm Handler) (Map (EnumPosition RecipientCategory, ListPosition) (EnumPosition RecipientCategory, ListPosition))
|
miDelete :: Map (BoundedPosition RecipientCategory, ListPosition) (Either UserEmail UserId) -> (BoundedPosition RecipientCategory, ListPosition) -> MaybeT (MForm Handler) (Map (BoundedPosition RecipientCategory, ListPosition) (BoundedPosition RecipientCategory, ListPosition))
|
||||||
-- miDelete liveliness@(MapLiveliness lMap) (EnumPosition RecipientCustom, delPos) = mappend (Map.fromSet id . Set.filter (\(EnumPosition c, _) -> c /= RecipientCustom) $ review liveCoords liveliness) . fmap (EnumPosition RecipientCustom, ) . Map.mapKeysMonotonic (EnumPosition RecipientCustom, ) <$> miDeleteList (lMap ! EnumPosition RecipientCustom) delPos
|
-- miDelete liveliness@(MapLiveliness lMap) (BoundedPosition RecipientCustom, delPos) = mappend (Map.fromSet id . Set.filter (\(BoundedPosition c, _) -> c /= RecipientCustom) $ review liveCoords liveliness) . fmap (BoundedPosition RecipientCustom, ) . Map.mapKeysMonotonic (BoundedPosition RecipientCustom, ) <$> miDeleteList (lMap ! BoundedPosition RecipientCustom) delPos
|
||||||
miDelete _ _ = mzero
|
miDelete _ _ = mzero
|
||||||
miIdent :: Text
|
miIdent :: Text
|
||||||
miIdent = "recipients"
|
miIdent = "recipients"
|
||||||
postProcess :: Map (EnumPosition RecipientCategory, ListPosition) (Either UserEmail UserId, Bool) -> Set (Either UserEmail UserId)
|
postProcess :: Map (BoundedPosition RecipientCategory, ListPosition) (Either UserEmail UserId, Bool) -> Set (Either UserEmail UserId)
|
||||||
postProcess = Set.fromList . map fst . filter snd . Map.elems
|
postProcess = Set.fromList . map fst . filter snd . Map.elems
|
||||||
|
|
||||||
recipientsListMsg <- messageI Info MsgCommRecipientsList
|
recipientsListMsg <- messageI Info MsgCommRecipientsList
|
||||||
|
|||||||
@ -13,6 +13,7 @@ module Handler.Utils.Form.MassInput
|
|||||||
, ListLength(..), ListPosition(..), miDeleteList
|
, ListLength(..), ListPosition(..), miDeleteList
|
||||||
, EnumLiveliness(..), EnumPosition(..)
|
, EnumLiveliness(..), EnumPosition(..)
|
||||||
, MapLiveliness(..)
|
, MapLiveliness(..)
|
||||||
|
, LowerBounded(..), BoundedLiveliness(..), BoundedPosition(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
@ -81,17 +82,10 @@ instance Liveliness ListLength where
|
|||||||
|
|
||||||
newtype EnumLiveliness enum = EnumLiveliness { unEnumLiveliness :: IntSet }
|
newtype EnumLiveliness enum = EnumLiveliness { unEnumLiveliness :: IntSet }
|
||||||
deriving (Eq, Ord, Generic, Typeable, Read, Show)
|
deriving (Eq, Ord, Generic, Typeable, Read, Show)
|
||||||
|
deriving newtype (Lattice, BoundedJoinSemiLattice)
|
||||||
|
|
||||||
makeWrapped ''EnumLiveliness
|
makeWrapped ''EnumLiveliness
|
||||||
|
|
||||||
instance Lattice (EnumLiveliness enum) where
|
|
||||||
(EnumLiveliness a) \/ (EnumLiveliness b) = EnumLiveliness $ a `IntSet.union` b
|
|
||||||
(EnumLiveliness a) /\ (EnumLiveliness b) = EnumLiveliness $ a `IntSet.intersection` b
|
|
||||||
instance BoundedJoinSemiLattice (EnumLiveliness enum) where
|
|
||||||
bottom = EnumLiveliness IntSet.empty
|
|
||||||
instance (Enum enum, Bounded enum) => BoundedMeetSemiLattice (EnumLiveliness enum) where
|
|
||||||
top = EnumLiveliness . IntSet.fromList $ map (fromEnum :: enum -> Int) [minBound..maxBound]
|
|
||||||
|
|
||||||
|
|
||||||
newtype EnumPosition enum = EnumPosition { unEnumPosition :: enum }
|
newtype EnumPosition enum = EnumPosition { unEnumPosition :: enum }
|
||||||
deriving newtype (Enum, Bounded, PathPiece, ToJSON, FromJSON, ToJSONKey, FromJSONKey)
|
deriving newtype (Enum, Bounded, PathPiece, ToJSON, FromJSON, ToJSONKey, FromJSONKey)
|
||||||
@ -99,11 +93,11 @@ newtype EnumPosition enum = EnumPosition { unEnumPosition :: enum }
|
|||||||
|
|
||||||
makeWrapped ''EnumPosition
|
makeWrapped ''EnumPosition
|
||||||
|
|
||||||
instance (Enum enum, Bounded enum, PathPiece enum, ToJSON enum, FromJSON enum, ToJSONKey enum, FromJSONKey enum, Eq enum, Ord enum) => IsBoxCoord (EnumPosition enum) where
|
instance (Bounded enum, PathPiece enum, ToJSON enum, FromJSON enum, ToJSONKey enum, FromJSONKey enum, Ord enum) => IsBoxCoord (EnumPosition enum) where
|
||||||
boxDimensions = [BoxDimension _Wrapped]
|
boxDimensions = [BoxDimension _Wrapped]
|
||||||
boxOrigin = minBound
|
boxOrigin = minBound
|
||||||
|
|
||||||
instance (Enum enum, Bounded enum, PathPiece enum, ToJSON enum, FromJSON enum, ToJSONKey enum, FromJSONKey enum, Eq enum, Ord enum) => Liveliness (EnumLiveliness enum) where
|
instance (Enum enum, Bounded enum, PathPiece enum, ToJSON enum, FromJSON enum, ToJSONKey enum, FromJSONKey enum, Ord enum) => Liveliness (EnumLiveliness enum) where
|
||||||
type BoxCoord (EnumLiveliness enum) = EnumPosition enum
|
type BoxCoord (EnumLiveliness enum) = EnumPosition enum
|
||||||
liveCoords = iso fromSet toSet
|
liveCoords = iso fromSet toSet
|
||||||
where
|
where
|
||||||
@ -113,6 +107,28 @@ instance (Enum enum, Bounded enum, PathPiece enum, ToJSON enum, FromJSON enum, T
|
|||||||
fromSet = EnumLiveliness . IntSet.fromList . map fromEnum . Set.toList
|
fromSet = EnumLiveliness . IntSet.fromList . map fromEnum . Set.toList
|
||||||
|
|
||||||
|
|
||||||
|
class Ord coord => LowerBounded coord where
|
||||||
|
minBound' :: coord
|
||||||
|
|
||||||
|
newtype BoundedLiveliness coord = BoundedLiveliness { unBoundedLiveliness :: Set coord }
|
||||||
|
deriving (Eq, Ord, Generic, Typeable, Read, Show)
|
||||||
|
deriving newtype (Lattice, BoundedJoinSemiLattice, BoundedMeetSemiLattice)
|
||||||
|
makeWrapped ''BoundedLiveliness
|
||||||
|
|
||||||
|
newtype BoundedPosition coord = BoundedPosition { unBoundedPosition :: coord }
|
||||||
|
deriving newtype (Enum, Bounded, PathPiece, ToJSON, FromJSON, ToJSONKey, FromJSONKey, LowerBounded)
|
||||||
|
deriving (Eq, Ord, Generic, Typeable, Read, Show)
|
||||||
|
makeWrapped ''BoundedPosition
|
||||||
|
|
||||||
|
instance (LowerBounded coord, PathPiece coord, ToJSON coord, FromJSON coord, ToJSONKey coord, FromJSONKey coord, Ord coord) => IsBoxCoord (BoundedPosition coord) where
|
||||||
|
boxDimensions = [BoxDimension _Wrapped]
|
||||||
|
boxOrigin = minBound'
|
||||||
|
|
||||||
|
instance (LowerBounded coord, PathPiece coord, ToJSON coord, FromJSON coord, ToJSONKey coord, FromJSONKey coord, Ord coord) => Liveliness (BoundedLiveliness coord) where
|
||||||
|
type BoxCoord (BoundedLiveliness coord) = BoundedPosition coord
|
||||||
|
liveCoords = iso (Set.mapMonotonic $ view _Wrapped) (Set.mapMonotonic $ view _Unwrapped) . _Unwrapped
|
||||||
|
|
||||||
|
|
||||||
newtype MapLiveliness l1 l2 = MapLiveliness { unMapLiveliness :: Map (BoxCoord l1) l2 }
|
newtype MapLiveliness l1 l2 = MapLiveliness { unMapLiveliness :: Map (BoxCoord l1) l2 }
|
||||||
deriving (Generic, Typeable)
|
deriving (Generic, Typeable)
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import qualified Data.Set as Set
|
|||||||
import Data.List (genericLength, genericIndex)
|
import Data.List (genericLength, genericIndex)
|
||||||
|
|
||||||
|
|
||||||
data BoxDimension x = forall n. (Enum n, Eq n) => BoxDimension (Lens' x n)
|
data BoxDimension x = forall n. Eq n => BoxDimension (Lens' x n)
|
||||||
|
|
||||||
class (ToJSON x, FromJSON x, ToJSONKey x, FromJSONKey x, PathPiece x, Eq x, Ord x) => IsBoxCoord x where
|
class (ToJSON x, FromJSON x, ToJSONKey x, FromJSONKey x, PathPiece x, Eq x, Ord x) => IsBoxCoord x where
|
||||||
boxDimensions :: [BoxDimension x]
|
boxDimensions :: [BoxDimension x]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
module Utils.PathPiece
|
module Utils.PathPiece
|
||||||
( nullaryToPathPiece', nullaryToPathPiece
|
( nullaryToPathPiece', nullaryToPathPiece
|
||||||
, nullaryPathPiece', nullaryPathPiece, finitePathPiece
|
, nullaryPathPiece', nullaryPathPiece, finitePathPiece
|
||||||
|
, derivePathPiece
|
||||||
, splitCamel
|
, splitCamel
|
||||||
, camelToPathPiece, camelToPathPiece'
|
, camelToPathPiece, camelToPathPiece'
|
||||||
, nameToPathPiece, nameToPathPiece'
|
, nameToPathPiece, nameToPathPiece'
|
||||||
@ -13,6 +14,7 @@ import ClassyPrelude.Yesod
|
|||||||
|
|
||||||
import Language.Haskell.TH
|
import Language.Haskell.TH
|
||||||
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
|
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
|
||||||
|
import Language.Haskell.TH.Datatype
|
||||||
import Data.Universe
|
import Data.Universe
|
||||||
|
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
@ -85,6 +87,51 @@ finitePathPiece finiteType verbs = do
|
|||||||
, funD 'fromPathPiece
|
, funD 'fromPathPiece
|
||||||
[ clause [] (normalB $ return finExp) [] ]
|
[ clause [] (normalB $ return finExp) [] ]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
derivePathPiece :: Name -> (Text -> Text) -> Text -> DecsQ
|
||||||
|
derivePathPiece adt mangle joinPP = do
|
||||||
|
let mangle' = TH.lift . mangle . pack . nameBase
|
||||||
|
DatatypeInfo{..} <- reifyDatatype adt
|
||||||
|
mapName <- newName $ "pathPieceConstructorMap" <> nameBase adt
|
||||||
|
let
|
||||||
|
toClause ConstructorInfo{..} = do
|
||||||
|
vars <- mapM (const $ newName "x") constructorFields
|
||||||
|
clause [conP constructorName $ map varP vars] (normalB [e|Text.intercalate joinPP $ $(mangle' constructorName) : $(listE $ map (\v -> [e|toPathPiece $(varE v)|]) vars)|]) []
|
||||||
|
fromClause = do
|
||||||
|
constrName <- newName "c"
|
||||||
|
argsName <- newName "args"
|
||||||
|
clause [viewP [e|Text.splitOn joinPP|] $ infixP (varP constrName) '(:) (varP argsName)]
|
||||||
|
(normalB [e|HashMap.lookup $(varE constrName) $(varE mapName) >>= ($ $(varE argsName))|])
|
||||||
|
[]
|
||||||
|
finDecs =
|
||||||
|
[ pragInlD mapName NoInline FunLike AllPhases
|
||||||
|
, sigD mapName [t|HashMap Text ([Text] -> Maybe $(conT adt))|]
|
||||||
|
, funD mapName
|
||||||
|
[ clause [] (normalB finClause) [] ]
|
||||||
|
]
|
||||||
|
where finClause = ([e|HashMap.fromList|] `appE`) . listE $ map listItem datatypeCons
|
||||||
|
listItem ConstructorInfo{..} = do
|
||||||
|
vars <- mapM (const $ newName "x") constructorFields
|
||||||
|
tupE [ mangle' constructorName
|
||||||
|
, lamCaseE
|
||||||
|
[ match (listP $ map varP vars)
|
||||||
|
(normalB $ case vars of
|
||||||
|
[] -> [e|Just $(conE constructorName)|]
|
||||||
|
v : vs -> foldl' (\acc v' -> [e|$(acc) <*> fromPathPiece $(varE v')|]) [e|$(conE constructorName) <$> fromPathPiece $(varE v)|] vs
|
||||||
|
)
|
||||||
|
[]
|
||||||
|
, match wildP (normalB [e|Nothing|]) []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
sequence . (finDecs ++ ) . pure $
|
||||||
|
instanceD (cxt []) [t|PathPiece $(conT adt)|]
|
||||||
|
[ funD 'toPathPiece
|
||||||
|
(map toClause datatypeCons)
|
||||||
|
, funD 'fromPathPiece
|
||||||
|
[ fromClause
|
||||||
|
, clause [wildP] (normalB [e|Nothing|]) []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
splitCamel :: Textual t => t -> [t]
|
splitCamel :: Textual t => t -> [t]
|
||||||
|
|||||||
@ -5,7 +5,19 @@ $if not (null activeCategories)
|
|||||||
<div .recipient-category>
|
<div .recipient-category>
|
||||||
<input type=checkbox id=#{checkedIdent category} .recipient-category__checkbox :elem category checkedCategories:checked>
|
<input type=checkbox id=#{checkedIdent category} .recipient-category__checkbox :elem category checkedCategories:checked>
|
||||||
<label .recipient-category__label for=#{checkedIdent category}>
|
<label .recipient-category__label for=#{checkedIdent category}>
|
||||||
_{category}
|
$case category
|
||||||
|
$of RecipientCustom
|
||||||
|
_{MsgRecipientCustom}
|
||||||
|
$of RecipientGroup RGCourseParticipants
|
||||||
|
_{MsgRGCourseParticipants}
|
||||||
|
$of RecipientGroup RGCourseLecturers
|
||||||
|
_{MsgRGCourseLecturers}
|
||||||
|
$of RecipientGroup RGCourseCorrectors
|
||||||
|
_{MsgRGCourseCorrectors}
|
||||||
|
$of RecipientGroup RGCourseTutors
|
||||||
|
_{MsgRGCourseTutors}
|
||||||
|
$of RecipientGroup (RGTutorialParticipants tutid)
|
||||||
|
^{rgTutorialParticipantsCaption tutid}
|
||||||
|
|
||||||
$if hasContent category
|
$if hasContent category
|
||||||
<fieldset .recipient-category__fieldset uw-interactive-fieldset .interactive-fieldset__target data-conditional-input=#{checkedIdent category}>
|
<fieldset .recipient-category__fieldset uw-interactive-fieldset .interactive-fieldset__target data-conditional-input=#{checkedIdent category}>
|
||||||
@ -18,5 +30,5 @@ $if not (null activeCategories)
|
|||||||
$forall optIx <- categoryIndices category
|
$forall optIx <- categoryIndices category
|
||||||
^{cellWdgts ! optIx}
|
^{cellWdgts ! optIx}
|
||||||
|
|
||||||
$maybe addWdgt <- addWdgts !? (1, (EnumPosition category, 0))
|
$maybe addWdgt <- addWdgts !? (1, (BoundedPosition category, 0))
|
||||||
^{addWdgt}
|
^{addWdgt}
|
||||||
|
|||||||
Reference in New Issue
Block a user