Initial commit
This commit is contained in:
commit
fa8ea8424b
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist-newstyle
|
22
cabal.project
Normal file
22
cabal.project
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
packages: .
|
||||||
|
allow-newer:
|
||||||
|
react-haskell:void,
|
||||||
|
ghcjs-base,
|
||||||
|
aeson,
|
||||||
|
attoparsec,
|
||||||
|
lens
|
||||||
|
|
||||||
|
packages react-haskell
|
||||||
|
flags: +ghcjs
|
||||||
|
build-depends: ghcjs-dom,
|
||||||
|
ghcjs-base
|
||||||
|
|
||||||
|
package reflex-dom
|
||||||
|
flags: -webkit2gtk
|
||||||
|
|
||||||
|
|
||||||
|
constraints:
|
||||||
|
--TODO: This should be pinned in reflex-platform
|
||||||
|
--TODO: Make sure to pin both of these
|
||||||
|
--hashable == 1.3.5.0,
|
||||||
|
aeson == 2.0.3.0
|
25
default.nix
Normal file
25
default.nix
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
reflex-platform ? import ./dep/reflex-platform { system = builtins.currentSystem; }
|
||||||
|
}: (reflex-platform.project ({ pkgs, thunkSource, ... }: {
|
||||||
|
name = "reflex-react";
|
||||||
|
src = ./.;
|
||||||
|
ghcjs-compiler-nix-name = "ghcjs8107JSString"; #TODO: This must be default
|
||||||
|
compiler-nix-name = "ghc8107Splices"; #TODO: This must be default
|
||||||
|
shells = ps: with ps; [ react ];
|
||||||
|
inputThunks = [
|
||||||
|
{
|
||||||
|
thunk = ./dep/jsaddle;
|
||||||
|
subdirs = [
|
||||||
|
"jsaddle"
|
||||||
|
"jsaddle-warp"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
./dep/jsaddle-dom
|
||||||
|
];
|
||||||
|
})).extend (self: super: {
|
||||||
|
shells = super.shells // {
|
||||||
|
ghc = self.shell-driver {
|
||||||
|
crossBuilds = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
2
dep/jsaddle-dom/default.nix
Normal file
2
dep/jsaddle-dom/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/jsaddle-dom/github.json
Normal file
8
dep/jsaddle-dom/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "jsaddle-dom",
|
||||||
|
"branch": "dylang/jsaddle-core2",
|
||||||
|
"private": false,
|
||||||
|
"rev": "330d5da385b1823b1d5a38eb47f2c4d3aae3639a",
|
||||||
|
"sha256": "1xxwqs3v6q2148bivy2j40029njpv90x83hxdmr2998yakcjlqmk"
|
||||||
|
}
|
12
dep/jsaddle-dom/thunk.nix
Normal file
12
dep/jsaddle-dom/thunk.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import (builtins.fetchTarball {
|
||||||
|
url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz";
|
||||||
|
sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr";
|
||||||
|
}) {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/jsaddle/default.nix
Normal file
2
dep/jsaddle/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/jsaddle/github.json
Normal file
8
dep/jsaddle/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "obsidiansystems",
|
||||||
|
"repo": "jsaddle",
|
||||||
|
"branch": "ryantrinkle/cors-allow-all-origins",
|
||||||
|
"private": false,
|
||||||
|
"rev": "5dd4e7f0e2534c1a53bb1b7df413f2de0febbcd8",
|
||||||
|
"sha256": "0phn9bw9c20s7hmn9f0zf0ij9y35j2pla7fj2kkjcc473sldnjk3"
|
||||||
|
}
|
12
dep/jsaddle/thunk.nix
Normal file
12
dep/jsaddle/thunk.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import (builtins.fetchTarball {
|
||||||
|
url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz";
|
||||||
|
sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr";
|
||||||
|
}) {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
58
dep/reexportsToStubs.hs
Normal file
58
dep/reexportsToStubs.hs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
|
import Distribution.PackageDescription.Parsec
|
||||||
|
import Distribution.PackageDescription
|
||||||
|
import Distribution.Types.Library
|
||||||
|
import Distribution.Types.ModuleReexport
|
||||||
|
import Distribution.Verbosity
|
||||||
|
import Distribution.ModuleName (components)
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import System.Directory (createDirectoryIfMissing)
|
||||||
|
import System.FilePath ((</>), (<.>), joinPath, takeDirectory)
|
||||||
|
import Data.List.Split (splitOn)
|
||||||
|
import Data.List (intercalate)
|
||||||
|
|
||||||
|
-- Helper function to convert a module name to a file path
|
||||||
|
convertModuleToPath :: String -> FilePath
|
||||||
|
convertModuleToPath moduleName = "src" </> joinPath (splitOn "." moduleName) <.> "hs"
|
||||||
|
|
||||||
|
-- Function to write a file with the given module structure
|
||||||
|
writeModuleFile :: (String, String) -> IO ()
|
||||||
|
writeModuleFile (originalModule, newModule) = do
|
||||||
|
let content = unlines
|
||||||
|
[ "module " ++ newModule ++ " (module X) where"
|
||||||
|
, ""
|
||||||
|
, "import " ++ originalModule ++ " as X"
|
||||||
|
]
|
||||||
|
filePath = convertModuleToPath newModule
|
||||||
|
createDirectoryIfMissing True (takeDirectory filePath)
|
||||||
|
writeFile filePath content
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
args <- getArgs
|
||||||
|
case args of
|
||||||
|
[fileName] -> do
|
||||||
|
result <- parseCabalFile fileName
|
||||||
|
case result of
|
||||||
|
Left err -> putStrLn $ "Error: " ++ err
|
||||||
|
Right reexports -> mapM_ writeModuleFile reexports
|
||||||
|
_ -> putStrLn "Usage: cabal-file-parser <filename.cabal>"
|
||||||
|
|
||||||
|
parseCabalFile :: FilePath -> IO (Either String [(String, String)])
|
||||||
|
parseCabalFile filePath = do
|
||||||
|
result <- readGenericPackageDescription silent filePath
|
||||||
|
let maybeMainLib = case condLibrary result of
|
||||||
|
Just (CondNode lib _ _) -> Just lib
|
||||||
|
Nothing -> Nothing
|
||||||
|
subLibs = [lib | (_, CondNode lib _ _) <- condSubLibraries result]
|
||||||
|
libs = maybe subLibs (:subLibs) maybeMainLib
|
||||||
|
return $ case libs of
|
||||||
|
(lib:_) -> Right (extractReexportedModules lib)
|
||||||
|
[] -> Left "No library found in .cabal file."
|
||||||
|
|
||||||
|
extractReexportedModules :: Library -> [(String, String)]
|
||||||
|
extractReexportedModules lib =
|
||||||
|
map (\ModuleReexport{..} -> (moduleNameString moduleReexportOriginalName, moduleNameString moduleReexportName)) (reexportedModules lib)
|
||||||
|
|
||||||
|
moduleNameString = intercalate "." . components
|
2
dep/reflex-dom/default.nix
Normal file
2
dep/reflex-dom/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/reflex-dom/github.json
Normal file
8
dep/reflex-dom/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "reflex-frp",
|
||||||
|
"repo": "reflex-dom",
|
||||||
|
"branch": "dn-jsaddle-core2-4",
|
||||||
|
"private": false,
|
||||||
|
"rev": "4bd2ae77727180d2cd2d4ea4be79ddcb7c39fe10",
|
||||||
|
"sha256": "16l9iw287p9vzdk7w7fdfxm1zsxd158x87z2yk9h5ksvsq5vdfhv"
|
||||||
|
}
|
12
dep/reflex-dom/thunk.nix
Normal file
12
dep/reflex-dom/thunk.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import (builtins.fetchTarball {
|
||||||
|
url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz";
|
||||||
|
sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr";
|
||||||
|
}) {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/reflex-platform/default.nix
Normal file
2
dep/reflex-platform/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/reflex-platform/github.json
Normal file
8
dep/reflex-platform/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "reflex-frp",
|
||||||
|
"repo": "reflex-platform",
|
||||||
|
"branch": "dn/mars-ghcjs-fixes",
|
||||||
|
"private": false,
|
||||||
|
"rev": "250ff12b4fc340f461413118d365bebaf2024378",
|
||||||
|
"sha256": "0chcaamcwxh0lqrp5kdy4b5kl7ii070kzc2adwnsfnfl34c0fql4"
|
||||||
|
}
|
12
dep/reflex-platform/thunk.nix
Normal file
12
dep/reflex-platform/thunk.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import (builtins.fetchTarball {
|
||||||
|
url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz";
|
||||||
|
sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr";
|
||||||
|
}) {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
2
dep/reflex/default.nix
Normal file
2
dep/reflex/default.nix
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
import (import ./thunk.nix)
|
8
dep/reflex/github.json
Normal file
8
dep/reflex/github.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"owner": "reflex-frp",
|
||||||
|
"repo": "reflex",
|
||||||
|
"branch": "dn/debugging-06-08-rebased-on-0822",
|
||||||
|
"private": false,
|
||||||
|
"rev": "d492217c586dd1eadbbdbcfe9ccd87eae3009e81",
|
||||||
|
"sha256": "001c9civdpv14sb0fssscgyw8mly7bb7iiw6bh15n339ls01cf8k"
|
||||||
|
}
|
12
dep/reflex/thunk.nix
Normal file
12
dep/reflex/thunk.nix
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# DO NOT HAND-EDIT THIS FILE
|
||||||
|
let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
|
||||||
|
if !fetchSubmodules && !private then builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
|
||||||
|
} else (import (builtins.fetchTarball {
|
||||||
|
url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz";
|
||||||
|
sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr";
|
||||||
|
}) {}).fetchFromGitHub {
|
||||||
|
inherit owner repo rev sha256 fetchSubmodules private;
|
||||||
|
};
|
||||||
|
json = builtins.fromJSON (builtins.readFile ./github.json);
|
||||||
|
in fetch json
|
63
react.cabal
Normal file
63
react.cabal
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
cabal-version: 3.0
|
||||||
|
name: react
|
||||||
|
version: 0.1.0.0
|
||||||
|
license: BSD-3-Clause
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Obsidian Systems LLC
|
||||||
|
maintainer: maintainer@obsidian.systems
|
||||||
|
category: Web
|
||||||
|
build-type: Simple
|
||||||
|
extra-doc-files: CHANGELOG.md
|
||||||
|
|
||||||
|
common warnings
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
library
|
||||||
|
import: warnings
|
||||||
|
exposed-modules:
|
||||||
|
React
|
||||||
|
|
||||||
|
build-depends: base
|
||||||
|
, jsaddle
|
||||||
|
, jsaddle-dom
|
||||||
|
, ghcjs-dom
|
||||||
|
, text
|
||||||
|
, containers
|
||||||
|
, mtl
|
||||||
|
, deepseq
|
||||||
|
, aeson
|
||||||
|
, scientific
|
||||||
|
, bytestring
|
||||||
|
, stm
|
||||||
|
, ref-tf
|
||||||
|
, transformers
|
||||||
|
, unliftio-core
|
||||||
|
, exceptions
|
||||||
|
, async
|
||||||
|
, primitive
|
||||||
|
, lens
|
||||||
|
, ghc-prim
|
||||||
|
, http-types
|
||||||
|
, entropy
|
||||||
|
, base64-bytestring
|
||||||
|
, witherable
|
||||||
|
, foreign-store
|
||||||
|
, dependent-sum
|
||||||
|
, reflection
|
||||||
|
hs-source-dirs:
|
||||||
|
src
|
||||||
|
|
||||||
|
if impl(ghcjs)
|
||||||
|
build-depends: ghcjs-base == 0.2.*
|
||||||
|
, ghcjs-prim
|
||||||
|
-- This is to allow the hashable patches to work
|
||||||
|
-- the hashable should to be pinned in reflex-platform
|
||||||
|
, hashable == 1.3.5.0
|
||||||
|
else
|
||||||
|
build-depends: websockets
|
||||||
|
, wai
|
||||||
|
, wai-websockets
|
||||||
|
, warp
|
||||||
|
, jsaddle-warp
|
||||||
|
|
||||||
|
default-language: Haskell2010
|
273
src/React.hs
Normal file
273
src/React.hs
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE ApplicativeDo #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||||
|
module React where
|
||||||
|
|
||||||
|
import Prelude hiding ((!!))
|
||||||
|
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
|
import Language.Javascript.JSaddle hiding (Ref)
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Data.Map (Map)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.Except
|
||||||
|
import Control.Monad.Reader
|
||||||
|
import Data.String
|
||||||
|
|
||||||
|
#ifndef ghcjs_HOST_OS
|
||||||
|
import GHCJS.Prim.Internal (primToJSVal)
|
||||||
|
import Data.Coerce (coerce)
|
||||||
|
#else
|
||||||
|
import GHCJS.Foreign.Callback
|
||||||
|
import qualified JavaScript.Array as Array (toListIO, fromListIO)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
t :: Text -> Text
|
||||||
|
t = id
|
||||||
|
|
||||||
|
tshow :: Show a => a -> Text
|
||||||
|
tshow = T.pack . show
|
||||||
|
|
||||||
|
#ifndef ghcjs_HOST_OS
|
||||||
|
printJavaScriptException :: JavaScriptException -> JSM ()
|
||||||
|
printJavaScriptException (JavaScriptException e) = do
|
||||||
|
s <- e # t "toString" $ ()
|
||||||
|
j <- valToJSON s
|
||||||
|
liftIO $ T.putStrLn $ "Exception: " <> tshow j
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ghcjs_HOST_OS
|
||||||
|
instance PToJSVal Text where
|
||||||
|
pToJSVal s = primToJSVal $ PrimVal_String s
|
||||||
|
|
||||||
|
instance PToJSVal Int where
|
||||||
|
pToJSVal i = primToJSVal $ PrimVal_Number $ fromIntegral i
|
||||||
|
#endif
|
||||||
|
|
||||||
|
instance IsString JSVal where
|
||||||
|
fromString = pToJSVal . T.pack
|
||||||
|
|
||||||
|
instance ToJSVal v => ToJSVal (Map Text v) where
|
||||||
|
toJSVal m = do
|
||||||
|
o@(Object oVal) <- obj
|
||||||
|
forM_ (Map.toList m) $ \(k, v) -> do
|
||||||
|
(o <# k) =<< toJSVal v
|
||||||
|
pure oVal
|
||||||
|
|
||||||
|
consoleLog :: ToJSVal a => a -> JSM JSVal
|
||||||
|
consoleLog x = (global ! t "console") # t "log" $ [x]
|
||||||
|
|
||||||
|
instance ToJSVal (Component props refVal) where
|
||||||
|
toJSVal (Component f) = toJSVal f
|
||||||
|
|
||||||
|
instance PToJSVal (Component props refVal) where
|
||||||
|
pToJSVal (Component f) = pToJSVal f
|
||||||
|
|
||||||
|
instance PToJSVal Function where
|
||||||
|
pToJSVal (Function _ o) = pToJSVal o
|
||||||
|
|
||||||
|
instance PToJSVal Object where
|
||||||
|
pToJSVal (Object v) = v
|
||||||
|
|
||||||
|
newtype Component props refVal = Component { unComponent :: Function' }
|
||||||
|
|
||||||
|
newtype Hook a = Hook { unHook :: ReaderT React JSM a }
|
||||||
|
deriving (Functor, Applicative, Monad)
|
||||||
|
|
||||||
|
newtype Render a = Render { unRender :: ReaderT React JSM a }
|
||||||
|
deriving (Functor, Applicative, Monad)
|
||||||
|
|
||||||
|
-- | An object that contains the React library
|
||||||
|
newtype React = React { unReact :: Object }
|
||||||
|
|
||||||
|
instance MakeObject React where
|
||||||
|
makeObject = pure . unReact
|
||||||
|
|
||||||
|
instance MakeObject (Component props refVal) where
|
||||||
|
makeObject = makeObject . functionObject' . unComponent
|
||||||
|
|
||||||
|
newtype Element = Element { unElement :: ReaderT React JSM JSVal }
|
||||||
|
|
||||||
|
instance IsString Element where
|
||||||
|
fromString = Element . pure . pToJSVal . T.pack
|
||||||
|
|
||||||
|
newtype Tag = Tag { unTag :: JSVal }
|
||||||
|
|
||||||
|
instance IsString Tag where
|
||||||
|
fromString = Tag . pToJSVal . T.pack
|
||||||
|
|
||||||
|
createElement :: Tag -> Map Text JSVal -> [Element] -> Element
|
||||||
|
createElement etag props children = Element $ do
|
||||||
|
react <- ask
|
||||||
|
createdChildren <- mapM unElement children
|
||||||
|
lift $ react # t "createElement" $ [pure $ unTag etag, toJSVal props] <> fmap pure createdChildren
|
||||||
|
|
||||||
|
createFragment :: [Element] -> Element
|
||||||
|
createFragment = createFragmentWithProps mempty
|
||||||
|
|
||||||
|
createFragmentWithProps :: Map Text JSVal -> [Element] -> Element
|
||||||
|
createFragmentWithProps props children = Element $ do
|
||||||
|
react <- ask
|
||||||
|
fragmentTag <- lift $ fmap Tag $ react ! t "Fragment"
|
||||||
|
unElement $ createElement fragmentTag props children
|
||||||
|
|
||||||
|
--TODO: The Hook section shouldn't have any control flow to it; probably it also shouldn't depend on props except in specific ways
|
||||||
|
component :: Hook (JSVal -> Render Element) -> ReaderT React JSM (Component JSVal ())
|
||||||
|
component (Hook hook) = do
|
||||||
|
react <- ask
|
||||||
|
f <- lift $ function' $ \_ _ args -> flip runReaderT react $ do
|
||||||
|
render <- hook
|
||||||
|
let props = case args of
|
||||||
|
[] -> jsUndefined
|
||||||
|
arg0 : _ -> arg0
|
||||||
|
e <- unRender $ render props
|
||||||
|
unElement e
|
||||||
|
pure $ Component f
|
||||||
|
|
||||||
|
--TODO: Input can be an initializer function rather than value
|
||||||
|
--TODO: `set` can take `a -> a` instead of `a`
|
||||||
|
useState :: (ToJSVal a, FromJSVal a) => a -> Hook (a, a -> JSM ())
|
||||||
|
useState initialValue = Hook $ do
|
||||||
|
react <- ask
|
||||||
|
initialJSVal <- lift $ toJSVal initialValue
|
||||||
|
result <- lift $ (react # t "useState") initialJSVal
|
||||||
|
Just s <- lift $ fromJSVal =<< result !! 0 --TODO: Exception handling
|
||||||
|
setter <- lift $ result !! 1
|
||||||
|
pure
|
||||||
|
( s
|
||||||
|
, \v' -> void $ call setter nullObject [v']
|
||||||
|
)
|
||||||
|
|
||||||
|
useRef :: JSVal -> Hook JSVal
|
||||||
|
useRef initialValue = Hook $ do
|
||||||
|
react <- ask
|
||||||
|
lift $ (react # t "useRef") initialValue
|
||||||
|
|
||||||
|
useEffect :: (JSVal -> JSVal -> [JSVal] -> JSM JSVal) -> Maybe [JSVal] -> Hook ()
|
||||||
|
useEffect f deps = Hook $ do
|
||||||
|
react <- ask
|
||||||
|
Function' _ cb <- lift $ function' f
|
||||||
|
depsArg <- case deps of
|
||||||
|
Nothing -> pure []
|
||||||
|
Just someDeps -> do
|
||||||
|
depsArray <- lift $ toJSVal someDeps
|
||||||
|
pure [depsArray]
|
||||||
|
_ <- lift $ (react # t "useEffect") $ [pToJSVal cb] <> depsArg
|
||||||
|
pure ()
|
||||||
|
|
||||||
|
useMemo :: (ToJSVal a, FromJSVal a) => JSM a -> Maybe [JSVal] -> Hook a
|
||||||
|
useMemo a deps = Hook $ do
|
||||||
|
react <- ask
|
||||||
|
Function' _ cb <- lift $ function' $ \_ _ _ -> toJSVal =<< a
|
||||||
|
depsArg <- case deps of
|
||||||
|
Nothing -> pure []
|
||||||
|
Just someDeps -> do
|
||||||
|
depsArray <- lift $ toJSVal someDeps
|
||||||
|
pure [depsArray]
|
||||||
|
resultVal <- lift $ (react # t "useMemo") $ [pToJSVal cb] <> depsArg
|
||||||
|
Just result <- lift $ fromJSVal resultVal
|
||||||
|
pure result
|
||||||
|
|
||||||
|
useCallback :: ToJSVal result => (JSVal -> JSVal -> [JSVal] -> JSM result) -> Maybe [JSM JSVal] -> Hook JSVal
|
||||||
|
useCallback f deps = Hook $ do
|
||||||
|
react <- ask
|
||||||
|
Function' _ cb <- lift $ function' $ \fObj this args -> toJSVal =<< f fObj this args
|
||||||
|
depsArg <- case deps of
|
||||||
|
Nothing -> pure []
|
||||||
|
Just someDeps -> do
|
||||||
|
depsArray <- lift $ toJSVal =<< sequence someDeps
|
||||||
|
pure [depsArray]
|
||||||
|
lift $ (react # t "useCallback") $ [pToJSVal cb] <> depsArg
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Not yet supported
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type DispatchFunction a = a -> Effect ()
|
||||||
|
|
||||||
|
type Reducer s a = s -> a -> s
|
||||||
|
|
||||||
|
useContext :: Context a -> Hook a
|
||||||
|
useContext = undefined
|
||||||
|
|
||||||
|
data Context a
|
||||||
|
|
||||||
|
createContext :: a -> IO (Context a)
|
||||||
|
createContext = undefined
|
||||||
|
|
||||||
|
provider :: Context a -> a -> Render b -> Render b
|
||||||
|
provider = undefined
|
||||||
|
|
||||||
|
data Ref a
|
||||||
|
|
||||||
|
forwardRef :: (props -> Ref refVal -> Hook (Render ())) -> Component props refVal
|
||||||
|
forwardRef = undefined
|
||||||
|
|
||||||
|
useImperativeHandle :: Ref a -> Effect a -> Maybe [JSVal] -> Hook ()
|
||||||
|
useImperativeHandle = undefined
|
||||||
|
|
||||||
|
useReducer :: Reducer s a -> a -> Maybe (a -> a) -> Hook (a, DispatchFunction a)
|
||||||
|
useReducer = undefined
|
||||||
|
|
||||||
|
useTransition :: Hook (Bool, Effect () -> Effect ())
|
||||||
|
useTransition = undefined
|
||||||
|
|
||||||
|
useDeferredValue :: a -> Hook a
|
||||||
|
useDeferredValue = undefined
|
||||||
|
|
||||||
|
useDebugValue :: a -> Maybe (a -> b) -> Hook ()
|
||||||
|
useDebugValue = undefined
|
||||||
|
|
||||||
|
useId :: Hook Text
|
||||||
|
useId = undefined
|
||||||
|
|
||||||
|
useSyncExternalStore :: (IO () -> IO (IO ())) -> IO a -> Maybe (IO a) -> Hook ()
|
||||||
|
useSyncExternalStore = undefined
|
||||||
|
|
||||||
|
newtype Effect a = Effect { unEffect :: JSM a }
|
||||||
|
deriving (Functor, Applicative, Monad)
|
||||||
|
|
||||||
|
-- TODO: Add the following in jsaddle
|
||||||
|
type JSCallAsFunction' = JSVal -- ^ Function object
|
||||||
|
-> JSVal -- ^ this
|
||||||
|
-> [JSVal] -- ^ Function arguments
|
||||||
|
-> JSM JSVal -- ^ Return value
|
||||||
|
|
||||||
|
function' :: JSCallAsFunction' -- ^ Haskell function to call
|
||||||
|
-> JSM Function' -- ^ Returns a JavaScript function object that will
|
||||||
|
-- call the Haskell one when it is called
|
||||||
|
#ifdef ghcjs_HOST_OS
|
||||||
|
function' f = do
|
||||||
|
callback <- syncCallback2' $ \this args -> do
|
||||||
|
rargs <- Array.toListIO (coerce args)
|
||||||
|
f this this rargs -- TODO pass function object through
|
||||||
|
Function' callback <$> makeFunctionWithCallback' callback
|
||||||
|
#else
|
||||||
|
function' f = do
|
||||||
|
(cb, f') <- newSyncCallback'' f --TODO: "ContinueAsync" behavior
|
||||||
|
return $ Function' cb $ Object f'
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ghcjs_HOST_OS
|
||||||
|
data Function' = Function' {functionCallback' :: Callback (JSVal -> JSVal -> IO JSVal), functionObject' :: Object}
|
||||||
|
#else
|
||||||
|
data Function' = Function' {functionCallback' :: CallbackId, functionObject' :: Object}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ghcjs_HOST_OS
|
||||||
|
foreign import javascript unsafe "$r = function () { return $1(this, arguments); }"
|
||||||
|
makeFunctionWithCallback' :: Callback (JSVal -> JSVal -> IO JSVal) -> IO Object
|
||||||
|
#endif
|
||||||
|
|
||||||
|
instance ToJSVal Function' where
|
||||||
|
toJSVal = toJSVal . functionObject'
|
||||||
|
|
||||||
|
instance PToJSVal Function' where
|
||||||
|
pToJSVal (Function' _ o) = pToJSVal o
|
Loading…
Reference in New Issue
Block a user