[server] Implement shared editing module
This commit is contained in:
parent
e917893a9b
commit
f2e77b7c03
3 changed files with 100 additions and 18 deletions
|
|
@ -2,13 +2,15 @@
|
||||||
|
|
||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
|
import Control.Concurrent.MVar
|
||||||
import qualified Network.WebSockets as WS
|
import qualified Network.WebSockets as WS
|
||||||
|
|
||||||
|
import Forest.Broadcast
|
||||||
import Forest.Node
|
import Forest.Node
|
||||||
import Forest.Server
|
import Forest.Server
|
||||||
import Forest.TreeModule.Animate
|
|
||||||
import Forest.TreeModule.Const
|
import Forest.TreeModule.Const
|
||||||
import Forest.TreeModule.Fork
|
import Forest.TreeModule.Fork
|
||||||
|
import Forest.TreeModule.SharedEditing
|
||||||
|
|
||||||
pingDelay :: Int
|
pingDelay :: Int
|
||||||
pingDelay = 10
|
pingDelay = 10
|
||||||
|
|
@ -23,24 +25,13 @@ options = WS.defaultServerOptions
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
|
putStrLn "Preparing shared edit module"
|
||||||
|
sharedEditNodeVar <- newMVar $ txtNode "r" ""
|
||||||
|
sharedEditBroadcaster <- newBroadcaster
|
||||||
|
|
||||||
putStrLn "Starting server"
|
putStrLn "Starting server"
|
||||||
WS.runServerWithOptions options $ serverApp pingDelay $ forkModule "Forest"
|
WS.runServerWithOptions options $ serverApp pingDelay $ forkModule "Forest"
|
||||||
[ ProngConstructor "Test" $ constModule $ newNode "" "" [txtNode "" "Bla"]
|
[ ProngConstructor "Test" $ constModule $ newNode "" "" [txtNode "" "Bla"]
|
||||||
, ProngConstructor "Animation" $ animateModule 200000 $ map (newNode "" "")
|
, ProngConstructor "Sandbox" $ sharedEditingModule sharedEditNodeVar sharedEditBroadcaster
|
||||||
[ [txtNode "" "|> |", txtNode "" "Ping!"]
|
|
||||||
, [txtNode "" "|-> |", txtNode "" "Ping!"]
|
|
||||||
, [txtNode "" "| -> |", txtNode "" "Ping!"]
|
|
||||||
, [txtNode "" "| -> |", txtNode "" "Ping!"]
|
|
||||||
, [txtNode "" "| ->|", txtNode "" "Ping!"]
|
|
||||||
, [txtNode "" "| -|", txtNode "" "Ping!"]
|
|
||||||
, [txtNode "" "| |", txtNode "" "Ping!"]
|
|
||||||
, [txtNode "" "| <|", txtNode "" "Pong!"]
|
|
||||||
, [txtNode "" "| <-|", txtNode "" "Pong!"]
|
|
||||||
, [txtNode "" "| <- |", txtNode "" "Pong!"]
|
|
||||||
, [txtNode "" "| <- |", txtNode "" "Pong!"]
|
|
||||||
, [txtNode "" "|<- |", txtNode "" "Pong!"]
|
|
||||||
, [txtNode "" "|- |", txtNode "" "Pong!"]
|
|
||||||
, [txtNode "" "| |", txtNode "" "Pong!"]
|
|
||||||
]
|
|
||||||
, ProngConstructor "About" $ constModule projectDescriptionNode
|
, ProngConstructor "About" $ constModule projectDescriptionNode
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
module Forest.Node
|
module Forest.Node
|
||||||
( NodeId
|
( NodeId
|
||||||
, enumerateIds
|
, enumerateIds
|
||||||
|
, findUnusedId
|
||||||
, NodeFlags(..)
|
, NodeFlags(..)
|
||||||
, readFlags
|
, readFlags
|
||||||
, Node(..)
|
, Node(..)
|
||||||
|
|
@ -15,8 +16,12 @@ module Forest.Node
|
||||||
, applyPath
|
, applyPath
|
||||||
, adjustAt
|
, adjustAt
|
||||||
, replaceAt
|
, replaceAt
|
||||||
|
, deleteAt
|
||||||
|
, appendAt
|
||||||
, diffNodes
|
, diffNodes
|
||||||
, Path(..)
|
, Path(..)
|
||||||
|
, split
|
||||||
|
, parent
|
||||||
, narrow
|
, narrow
|
||||||
, narrowSet
|
, narrowSet
|
||||||
) where
|
) where
|
||||||
|
|
@ -35,6 +40,10 @@ type NodeId = T.Text
|
||||||
enumerateIds :: [NodeId]
|
enumerateIds :: [NodeId]
|
||||||
enumerateIds = map (T.pack . show) [(0::Integer)..]
|
enumerateIds = map (T.pack . show) [(0::Integer)..]
|
||||||
|
|
||||||
|
findUnusedId :: Set.Set NodeId -> NodeId
|
||||||
|
findUnusedId usedIds =
|
||||||
|
head $ filter (\nid -> not $ nid `Set.member` usedIds) enumerateIds
|
||||||
|
|
||||||
data NodeFlags = NodeFlags
|
data NodeFlags = NodeFlags
|
||||||
{ flagEdit :: !Bool
|
{ flagEdit :: !Bool
|
||||||
, flagDelete :: !Bool
|
, flagDelete :: !Bool
|
||||||
|
|
@ -146,6 +155,25 @@ adjustAt f (Path (x:xs)) node =
|
||||||
replaceAt :: Node -> Path -> Node -> Node
|
replaceAt :: Node -> Path -> Node -> Node
|
||||||
replaceAt node = adjustAt $ const node
|
replaceAt node = adjustAt $ const node
|
||||||
|
|
||||||
|
-- | Delete a subnode at a specified path. Does nothing if the path is 'mempty'.
|
||||||
|
deleteAt :: Path -> Node -> Node
|
||||||
|
deleteAt path node = case split path of
|
||||||
|
Nothing -> node
|
||||||
|
Just (parentPath, nodeId) -> adjustAt
|
||||||
|
(\n -> n{nodeChildren = OMap.delete nodeId $ nodeChildren n})
|
||||||
|
parentPath
|
||||||
|
node
|
||||||
|
|
||||||
|
-- | Append a new child node to the node at the specified path. Chooses an
|
||||||
|
-- unused node id.
|
||||||
|
appendAt :: Node -> Path -> Node -> Node
|
||||||
|
appendAt node =
|
||||||
|
adjustAt (\n -> n {nodeChildren = appendAtNewId $ nodeChildren n})
|
||||||
|
where
|
||||||
|
appendAtNewId m =
|
||||||
|
let nid = findUnusedId $ OMap.keysSet m
|
||||||
|
in OMap.append nid node m
|
||||||
|
|
||||||
diffNodes :: Node -> Node -> Maybe (Path, Node)
|
diffNodes :: Node -> Node -> Maybe (Path, Node)
|
||||||
diffNodes a b
|
diffNodes a b
|
||||||
| nodesDiffer || childrenChanged = Just (Path [], b)
|
| nodesDiffer || childrenChanged = Just (Path [], b)
|
||||||
|
|
@ -165,6 +193,13 @@ newtype Path = Path
|
||||||
{ pathElements :: [NodeId]
|
{ pathElements :: [NodeId]
|
||||||
} deriving (Show, Eq, Ord, Semigroup, Monoid, ToJSON, FromJSON)
|
} deriving (Show, Eq, Ord, Semigroup, Monoid, ToJSON, FromJSON)
|
||||||
|
|
||||||
|
split :: Path -> Maybe (Path, NodeId)
|
||||||
|
split (Path []) = Nothing
|
||||||
|
split (Path xs) = Just (Path (init xs), last xs)
|
||||||
|
|
||||||
|
parent :: Path -> Maybe Path
|
||||||
|
parent path = fst <$> split path
|
||||||
|
|
||||||
-- | Try to remove a 'NodeId' from the beginning of a 'Path'.
|
-- | Try to remove a 'NodeId' from the beginning of a 'Path'.
|
||||||
narrow :: NodeId -> Path -> Maybe Path
|
narrow :: NodeId -> Path -> Maybe Path
|
||||||
narrow nid (Path (x:xs))
|
narrow nid (Path (x:xs))
|
||||||
|
|
|
||||||
56
src/Forest/TreeModule/SharedEditing.hs
Normal file
56
src/Forest/TreeModule/SharedEditing.hs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
|
||||||
|
module Forest.TreeModule.SharedEditing
|
||||||
|
( SharedEditingModule
|
||||||
|
, sharedEditingModule
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Concurrent.MVar
|
||||||
|
import Control.Monad
|
||||||
|
|
||||||
|
import Forest.Broadcast
|
||||||
|
import Forest.Node
|
||||||
|
import Forest.TreeModule
|
||||||
|
import Forest.Util
|
||||||
|
|
||||||
|
data SharedEditingModule r = SharedEditingModule
|
||||||
|
{ seNodeVar :: MVar Node
|
||||||
|
, seBroadcaster :: Broadcaster Node
|
||||||
|
}
|
||||||
|
|
||||||
|
instance TreeModule SharedEditingModule r where
|
||||||
|
edit _ (Path []) _ = pure Nothing
|
||||||
|
edit se path text = do
|
||||||
|
node' <- modifyMVar (seNodeVar se) $ \node -> do
|
||||||
|
let updatedNode = adjustAt (\n -> n{nodeText = text}) path node
|
||||||
|
pure (updatedNode, updatedNode)
|
||||||
|
broadcast (seBroadcaster se) node'
|
||||||
|
pure Nothing
|
||||||
|
|
||||||
|
delete _ (Path []) = pure Nothing
|
||||||
|
delete se path = do
|
||||||
|
node' <- modifyMVar (seNodeVar se) $ \node -> do
|
||||||
|
let updatedNode = deleteAt path node
|
||||||
|
pure (updatedNode, updatedNode)
|
||||||
|
broadcast (seBroadcaster se) node'
|
||||||
|
pure Nothing
|
||||||
|
|
||||||
|
reply se path text = do
|
||||||
|
node' <- modifyMVar (seNodeVar se) $ \node -> do
|
||||||
|
let updatedNode = appendAt (txtNode "edr" text) path node
|
||||||
|
pure (updatedNode, updatedNode)
|
||||||
|
broadcast (seBroadcaster se) node'
|
||||||
|
pure Nothing
|
||||||
|
|
||||||
|
sharedEditingModule ::
|
||||||
|
MVar Node -> Broadcaster Node -> ModuleConstructor (SharedEditingModule ())
|
||||||
|
sharedEditingModule nodeVar broadcaster sendNode continue = do
|
||||||
|
listener <- attachListener broadcaster
|
||||||
|
withThread (updateOnNewBroadcast listener) $ do
|
||||||
|
withMVar nodeVar sendNode -- We need to show our initial edit state
|
||||||
|
continue $ SharedEditingModule nodeVar broadcaster
|
||||||
|
where
|
||||||
|
updateOnNewBroadcast listener = forever $ do
|
||||||
|
node <- listen listener
|
||||||
|
sendNode node
|
||||||
Loading…
Add table
Add a link
Reference in a new issue